From 2532b55952e9f41b806f0fa8125772fa8553f02c Mon Sep 17 00:00:00 2001 From: Onur Yilmaz Date: Mon, 14 Sep 2026 12:42:27 +0200 Subject: [PATCH 01/10] feat(helm): add operational labels to GithubOrganization Adds four labels to every GithubOrganization CR rendered by the Helm chart, enabling Permission Manager to look up org CRs via label selectors and read config without parsing spec fields: - repo-guard.cloudoperators.dev/github-instance: full hostname - repo-guard.cloudoperators.dev/github-instance-key: short key (defaults to first segment of the github field, e.g. "enterprise" from "enterprise.github.com"); overridable via githubInstanceKey - repo-guard.cloudoperators.dev/default-ldap-provider: LDAP provider PM writes into GithubTeam CRs it creates for this org - repo-guard.cloudoperators.dev/admin-permission: "admin" or "admin-ondemand" (defaults to "admin") Closes #246 Signed-off-by: Onur Yilmaz --- charts/repo-guard/templates/githuborganization.yaml | 4 ++++ charts/repo-guard/values.yaml | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/charts/repo-guard/templates/githuborganization.yaml b/charts/repo-guard/templates/githuborganization.yaml index cc8cc15e..9d0ed9ec 100644 --- a/charts/repo-guard/templates/githuborganization.yaml +++ b/charts/repo-guard/templates/githuborganization.yaml @@ -20,6 +20,10 @@ metadata: {{- if not (kindIs "map" $ttl) }}{{- $ttl = dict -}}{{- end }} repo-guard.cloudoperators.dev/failedTTL: "{{ ((get $ttl "failed") | default $.Values.ttl.organization.failed) }}" repo-guard.cloudoperators.dev/completedTTL: "{{ ((get $ttl "completed") | default $.Values.ttl.organization.completed) }}" + repo-guard.cloudoperators.dev/github-instance: "{{ $org.github }}" + repo-guard.cloudoperators.dev/github-instance-key: "{{ $org.githubInstanceKey | default ($org.github | splitList "." | first) }}" + repo-guard.cloudoperators.dev/default-ldap-provider: "{{ $org.defaultLdapProvider | default "" }}" + repo-guard.cloudoperators.dev/admin-permission: "{{ $org.adminPermission | default "admin" }}" {{- $skipTeams := join "," $org.skipDefaultRepositoryTeams }} {{- if $skipTeams }} annotations: diff --git a/charts/repo-guard/values.yaml b/charts/repo-guard/values.yaml index 3ba253cb..70662289 100644 --- a/charts/repo-guard/values.yaml +++ b/charts/repo-guard/values.yaml @@ -132,6 +132,13 @@ perses: # # removeRepositoryDirectCollaborator. Typically includes bot accounts and the GitHub App user. # protectedMembers: [] # disableInternalUsernames: +# # Operational labels consumed by Permission Manager for org lookup and team creation. +# # githubInstanceKey: defaults to the first segment of the 'github' field (e.g. "enterprise" from "enterprise.github.com") +# githubInstanceKey: +# # defaultLdapProvider: written by PM into spec.externalMemberProvider.ldap.provider on GithubTeams it creates for this org +# defaultLdapProvider: +# # adminPermission: "admin" or "admin-ondemand"; PM maps ADMIN role to this value (default: admin) +# adminPermission: admin # # GithubAccountLink email verification settings applied to all GALs # # associated with this GitHub (organization-level policy). # # When set, the Helm chart will populate a JSON annotation on each From 65bdb0acc8b5016b020d623862458c1fb302d056 Mon Sep 17 00:00:00 2001 From: Onur Yilmaz Date: Mon, 14 Sep 2026 13:01:44 +0200 Subject: [PATCH 02/10] fix(helm): correct github-instance-key default and document labels - github-instance-key now defaults to the full spec.github value (e.g. "enterprise.github.com") instead of just the first hostname segment. This matches the actual CR naming prefix used by the Helm chart (--), so Permission Manager constructs correct names without needing an explicit override. - Update values.yaml comment to reflect the corrected default. - Document the four new operational labels in docs/operations/labels.md, including the 63-char Kubernetes label value constraint on github-instance. Signed-off-by: Onur Yilmaz --- charts/repo-guard/templates/githuborganization.yaml | 2 +- charts/repo-guard/values.yaml | 2 +- docs/operations/labels.md | 11 +++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/charts/repo-guard/templates/githuborganization.yaml b/charts/repo-guard/templates/githuborganization.yaml index 9d0ed9ec..40dfe785 100644 --- a/charts/repo-guard/templates/githuborganization.yaml +++ b/charts/repo-guard/templates/githuborganization.yaml @@ -21,7 +21,7 @@ metadata: repo-guard.cloudoperators.dev/failedTTL: "{{ ((get $ttl "failed") | default $.Values.ttl.organization.failed) }}" repo-guard.cloudoperators.dev/completedTTL: "{{ ((get $ttl "completed") | default $.Values.ttl.organization.completed) }}" repo-guard.cloudoperators.dev/github-instance: "{{ $org.github }}" - repo-guard.cloudoperators.dev/github-instance-key: "{{ $org.githubInstanceKey | default ($org.github | splitList "." | first) }}" + repo-guard.cloudoperators.dev/github-instance-key: "{{ $org.githubInstanceKey | default $org.github }}" repo-guard.cloudoperators.dev/default-ldap-provider: "{{ $org.defaultLdapProvider | default "" }}" repo-guard.cloudoperators.dev/admin-permission: "{{ $org.adminPermission | default "admin" }}" {{- $skipTeams := join "," $org.skipDefaultRepositoryTeams }} diff --git a/charts/repo-guard/values.yaml b/charts/repo-guard/values.yaml index 70662289..2ed9cb10 100644 --- a/charts/repo-guard/values.yaml +++ b/charts/repo-guard/values.yaml @@ -133,7 +133,7 @@ perses: # protectedMembers: [] # disableInternalUsernames: # # Operational labels consumed by Permission Manager for org lookup and team creation. -# # githubInstanceKey: defaults to the first segment of the 'github' field (e.g. "enterprise" from "enterprise.github.com") +# # githubInstanceKey: defaults to the 'github' field value; used as the prefix in CR names (----) # githubInstanceKey: # # defaultLdapProvider: written by PM into spec.externalMemberProvider.ldap.provider on GithubTeams it creates for this org # defaultLdapProvider: diff --git a/docs/operations/labels.md b/docs/operations/labels.md index 8be5bc70..1e09add0 100644 --- a/docs/operations/labels.md +++ b/docs/operations/labels.md @@ -21,6 +21,17 @@ Labels control the behavior of Repo Guard controllers. All labels live under `me | `repo-guard.cloudoperators.dev/failedTTL` | Go duration (e.g. `1h`, `30m`) | Clears failed operations and failed status after the duration since last status timestamp. | Not set | | `repo-guard.cloudoperators.dev/completedTTL` | Go duration (e.g. `24h`) | Clears completed operations after the duration since last status timestamp. | Not set | +**Operational labels (read by Permission Manager):** + +These labels are set automatically by the Helm chart and are not intended to control reconciler behaviour. They allow Permission Manager to locate a `GithubOrganization` CR via label selectors and read org-level config without parsing `spec` fields. + +| Key | Description | Default (Helm) | +|---|---|---| +| `repo-guard.cloudoperators.dev/github-instance` | Full GitHub hostname (e.g. `enterprise.github.com`). PM uses this as a label selector to find the org CR from a CCRN instance segment. Must be ≤ 63 characters (Kubernetes label value limit). | `spec.github` value | +| `repo-guard.cloudoperators.dev/github-instance-key` | Key PM uses to construct CR names matching the repo-guard convention (`----`). Defaults to the full `spec.github` value, which matches how the Helm chart names `GithubOrganization` CRs. Override via `githubInstanceKey` in Helm values. | `spec.github` value | +| `repo-guard.cloudoperators.dev/default-ldap-provider` | LDAP provider name PM writes into `spec.externalMemberProvider.ldap.provider` on each `GithubTeam` it creates for this org. Empty string when not set. | `""` | +| `repo-guard.cloudoperators.dev/admin-permission` | Permission string PM uses when mapping the `ADMIN` role for this org. Either `"admin"` or `"admin-ondemand"`. | `"admin"` | + **Annotation:** | Key | Description | From 55405e8ef6f99cc1001660e5d2a69564548c4c91 Mon Sep 17 00:00:00 2001 From: Onur Yilmaz Date: Mon, 14 Sep 2026 20:58:14 +0200 Subject: [PATCH 03/10] fix(helm): derive github-instance label from githubs[].webURL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The github-instance label must carry the full GitHub hostname (e.g. enterprise.github.com) so Permission Manager can match it against the instance segment in CCRN URNs. The previous value ($org.github) was the Github CR name key, not the hostname. - Look up the matching githubs[] entry by name and use its webURL as the default; fall back to an explicit githubInstanceHostname override. - github-instance-key keeps $org.github as its default — that is the correct naming prefix used in ---- CRs. - Update values.yaml comments and docs/operations/labels.md accordingly. Signed-off-by: Onur Yilmaz --- charts/repo-guard/templates/githuborganization.yaml | 8 +++++++- charts/repo-guard/values.yaml | 6 +++++- docs/operations/labels.md | 4 ++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/charts/repo-guard/templates/githuborganization.yaml b/charts/repo-guard/templates/githuborganization.yaml index 40dfe785..0e58a105 100644 --- a/charts/repo-guard/templates/githuborganization.yaml +++ b/charts/repo-guard/templates/githuborganization.yaml @@ -20,7 +20,13 @@ metadata: {{- if not (kindIs "map" $ttl) }}{{- $ttl = dict -}}{{- end }} repo-guard.cloudoperators.dev/failedTTL: "{{ ((get $ttl "failed") | default $.Values.ttl.organization.failed) }}" repo-guard.cloudoperators.dev/completedTTL: "{{ ((get $ttl "completed") | default $.Values.ttl.organization.completed) }}" - repo-guard.cloudoperators.dev/github-instance: "{{ $org.github }}" + {{- $githubInstance := $org.githubInstanceHostname -}} + {{- if not $githubInstance -}} + {{- range $.Values.githubs -}} + {{- if eq .name $org.github -}}{{- $githubInstance = .webURL -}}{{- end -}} + {{- end -}} + {{- end }} + repo-guard.cloudoperators.dev/github-instance: "{{ $githubInstance }}" repo-guard.cloudoperators.dev/github-instance-key: "{{ $org.githubInstanceKey | default $org.github }}" repo-guard.cloudoperators.dev/default-ldap-provider: "{{ $org.defaultLdapProvider | default "" }}" repo-guard.cloudoperators.dev/admin-permission: "{{ $org.adminPermission | default "admin" }}" diff --git a/charts/repo-guard/values.yaml b/charts/repo-guard/values.yaml index 2ed9cb10..c06912cf 100644 --- a/charts/repo-guard/values.yaml +++ b/charts/repo-guard/values.yaml @@ -133,7 +133,11 @@ perses: # protectedMembers: [] # disableInternalUsernames: # # Operational labels consumed by Permission Manager for org lookup and team creation. -# # githubInstanceKey: defaults to the 'github' field value; used as the prefix in CR names (----) +# # githubInstanceHostname: GitHub hostname PM uses as a label selector to find this org CR from a CCRN instance segment. +# # Defaults to the webURL of the matching githubs[] entry. Override only if the webURL is not set or a different value is needed. +# githubInstanceHostname: +# # githubInstanceKey: short key PM uses as the prefix in CR names (----). +# # Defaults to the 'github' field value (the Github CR name), which matches how Helm names GithubOrganization CRs. # githubInstanceKey: # # defaultLdapProvider: written by PM into spec.externalMemberProvider.ldap.provider on GithubTeams it creates for this org # defaultLdapProvider: diff --git a/docs/operations/labels.md b/docs/operations/labels.md index 1e09add0..b45839d5 100644 --- a/docs/operations/labels.md +++ b/docs/operations/labels.md @@ -27,8 +27,8 @@ These labels are set automatically by the Helm chart and are not intended to con | Key | Description | Default (Helm) | |---|---|---| -| `repo-guard.cloudoperators.dev/github-instance` | Full GitHub hostname (e.g. `enterprise.github.com`). PM uses this as a label selector to find the org CR from a CCRN instance segment. Must be ≤ 63 characters (Kubernetes label value limit). | `spec.github` value | -| `repo-guard.cloudoperators.dev/github-instance-key` | Key PM uses to construct CR names matching the repo-guard convention (`----`). Defaults to the full `spec.github` value, which matches how the Helm chart names `GithubOrganization` CRs. Override via `githubInstanceKey` in Helm values. | `spec.github` value | +| `repo-guard.cloudoperators.dev/github-instance` | GitHub hostname (e.g. `enterprise.github.com`). PM uses this as a label selector to find the org CR from a CCRN instance segment (the `` path component, which is the full hostname). Defaults to the `webURL` of the matching `githubs[]` entry; override via `githubInstanceHostname` in Helm values. Must be ≤ 63 characters (Kubernetes label value limit). | `githubs[].webURL` for the matching `github` key | +| `repo-guard.cloudoperators.dev/github-instance-key` | Short key PM uses as the naming prefix in repo-guard's `----` CR convention. Defaults to `spec.github` (the `Github` CR name), which is the same prefix the Helm chart uses when naming `GithubOrganization` CRs. Override via `githubInstanceKey` in Helm values. | `spec.github` value (the `Github` CR name) | | `repo-guard.cloudoperators.dev/default-ldap-provider` | LDAP provider name PM writes into `spec.externalMemberProvider.ldap.provider` on each `GithubTeam` it creates for this org. Empty string when not set. | `""` | | `repo-guard.cloudoperators.dev/admin-permission` | Permission string PM uses when mapping the `ADMIN` role for this org. Either `"admin"` or `"admin-ondemand"`. | `"admin"` | From 319415a8d42496a2e7b818ef27d8cac9d9512642 Mon Sep 17 00:00:00 2001 From: Onur Yilmaz Date: Mon, 14 Sep 2026 21:04:44 +0200 Subject: [PATCH 04/10] fix(helm): strip scheme from webURL when deriving github-instance label The Github CR's webURL field contains a full URL including scheme (e.g. https://github.com). The PM CCRN instance segment is the hostname only (e.g. github.com). Strip the https:// / http:// prefix so the label value matches what PM extracts from CCRN URNs. Signed-off-by: Onur Yilmaz --- charts/repo-guard/templates/githuborganization.yaml | 4 +++- docs/operations/labels.md | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/charts/repo-guard/templates/githuborganization.yaml b/charts/repo-guard/templates/githuborganization.yaml index 0e58a105..a2e92a95 100644 --- a/charts/repo-guard/templates/githuborganization.yaml +++ b/charts/repo-guard/templates/githuborganization.yaml @@ -23,7 +23,9 @@ metadata: {{- $githubInstance := $org.githubInstanceHostname -}} {{- if not $githubInstance -}} {{- range $.Values.githubs -}} - {{- if eq .name $org.github -}}{{- $githubInstance = .webURL -}}{{- end -}} + {{- if eq .name $org.github -}} + {{- $githubInstance = (.webURL | trimPrefix "https://" | trimPrefix "http://") -}} + {{- end -}} {{- end -}} {{- end }} repo-guard.cloudoperators.dev/github-instance: "{{ $githubInstance }}" diff --git a/docs/operations/labels.md b/docs/operations/labels.md index b45839d5..64458094 100644 --- a/docs/operations/labels.md +++ b/docs/operations/labels.md @@ -27,7 +27,7 @@ These labels are set automatically by the Helm chart and are not intended to con | Key | Description | Default (Helm) | |---|---|---| -| `repo-guard.cloudoperators.dev/github-instance` | GitHub hostname (e.g. `enterprise.github.com`). PM uses this as a label selector to find the org CR from a CCRN instance segment (the `` path component, which is the full hostname). Defaults to the `webURL` of the matching `githubs[]` entry; override via `githubInstanceHostname` in Helm values. Must be ≤ 63 characters (Kubernetes label value limit). | `githubs[].webURL` for the matching `github` key | +| `repo-guard.cloudoperators.dev/github-instance` | GitHub hostname (e.g. `github.com`, `github.wdf.sap.corp`). PM uses this as a label selector to find the org CR from a CCRN instance segment (the `` path component, which is the full hostname). Derived from `githubs[].webURL` of the matching entry (scheme stripped); override via `githubInstanceHostname` in Helm values. Must be ≤ 63 characters (Kubernetes label value limit). | `githubs[].webURL` (scheme stripped) for the matching `github` key | | `repo-guard.cloudoperators.dev/github-instance-key` | Short key PM uses as the naming prefix in repo-guard's `----` CR convention. Defaults to `spec.github` (the `Github` CR name), which is the same prefix the Helm chart uses when naming `GithubOrganization` CRs. Override via `githubInstanceKey` in Helm values. | `spec.github` value (the `Github` CR name) | | `repo-guard.cloudoperators.dev/default-ldap-provider` | LDAP provider name PM writes into `spec.externalMemberProvider.ldap.provider` on each `GithubTeam` it creates for this org. Empty string when not set. | `""` | | `repo-guard.cloudoperators.dev/admin-permission` | Permission string PM uses when mapping the `ADMIN` role for this org. Either `"admin"` or `"admin-ondemand"`. | `"admin"` | From be1e28cee93d8728c582cbdd821273e33476c931 Mon Sep 17 00:00:00 2001 From: Onur Yilmaz Date: Mon, 14 Sep 2026 21:08:12 +0200 Subject: [PATCH 05/10] fix(helm): validate adminPermission value at render time Fail helm template/install early if adminPermission is set to anything other than "admin" or "admin-ondemand", preventing a typo from reaching Permission Manager as an invalid ADMIN role mapping. Signed-off-by: Onur Yilmaz --- charts/repo-guard/templates/githuborganization.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/charts/repo-guard/templates/githuborganization.yaml b/charts/repo-guard/templates/githuborganization.yaml index a2e92a95..d46192ee 100644 --- a/charts/repo-guard/templates/githuborganization.yaml +++ b/charts/repo-guard/templates/githuborganization.yaml @@ -31,7 +31,11 @@ metadata: repo-guard.cloudoperators.dev/github-instance: "{{ $githubInstance }}" repo-guard.cloudoperators.dev/github-instance-key: "{{ $org.githubInstanceKey | default $org.github }}" repo-guard.cloudoperators.dev/default-ldap-provider: "{{ $org.defaultLdapProvider | default "" }}" - repo-guard.cloudoperators.dev/admin-permission: "{{ $org.adminPermission | default "admin" }}" + {{- $adminPermission := $org.adminPermission | default "admin" -}} + {{- if and $org.adminPermission (not (or (eq $org.adminPermission "admin") (eq $org.adminPermission "admin-ondemand"))) -}} + {{- fail (printf "githubOrganizations[].adminPermission must be \"admin\" or \"admin-ondemand\", got %q" $org.adminPermission) -}} + {{- end }} + repo-guard.cloudoperators.dev/admin-permission: "{{ $adminPermission }}" {{- $skipTeams := join "," $org.skipDefaultRepositoryTeams }} {{- if $skipTeams }} annotations: From 357571cb646dfa2eb42d4ca1f22540f216f5e1ef Mon Sep 17 00:00:00 2001 From: Onur Yilmaz Date: Mon, 14 Sep 2026 22:10:36 +0200 Subject: [PATCH 06/10] fix(helm): strip port from webURL when deriving github-instance label The mock GitHub server URL (e.g. http://github-mock.svc:8080) contains a port number, and Kubernetes rejects label values containing colons. Also default webURL to empty string before trimming to avoid nil dereference when webURL is omitted from a githubs[] entry. Use splitList ":" | first to strip the port after scheme removal, which handles both http://host:port and https://host forms correctly. Signed-off-by: Onur Yilmaz --- charts/repo-guard/templates/githuborganization.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/charts/repo-guard/templates/githuborganization.yaml b/charts/repo-guard/templates/githuborganization.yaml index d46192ee..d6fb6003 100644 --- a/charts/repo-guard/templates/githuborganization.yaml +++ b/charts/repo-guard/templates/githuborganization.yaml @@ -20,11 +20,13 @@ metadata: {{- if not (kindIs "map" $ttl) }}{{- $ttl = dict -}}{{- end }} repo-guard.cloudoperators.dev/failedTTL: "{{ ((get $ttl "failed") | default $.Values.ttl.organization.failed) }}" repo-guard.cloudoperators.dev/completedTTL: "{{ ((get $ttl "completed") | default $.Values.ttl.organization.completed) }}" - {{- $githubInstance := $org.githubInstanceHostname -}} + {{- $githubInstance := $org.githubInstanceHostname | default "" -}} {{- if not $githubInstance -}} {{- range $.Values.githubs -}} {{- if eq .name $org.github -}} - {{- $githubInstance = (.webURL | trimPrefix "https://" | trimPrefix "http://") -}} + {{- $webURL := .webURL | default "" -}} + {{- $host := ($webURL | trimPrefix "https://" | trimPrefix "http://") -}} + {{- $githubInstance = (splitList ":" $host | first) -}} {{- end -}} {{- end -}} {{- end }} From be054a529d290eed52075120360cc1b9508ae2e9 Mon Sep 17 00:00:00 2001 From: Onur Yilmaz Date: Tue, 15 Sep 2026 12:23:43 +0200 Subject: [PATCH 07/10] fix(helm): strip path from webURL when deriving github-instance label Signed-off-by: Onur Yilmaz --- charts/repo-guard/templates/githuborganization.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/repo-guard/templates/githuborganization.yaml b/charts/repo-guard/templates/githuborganization.yaml index d6fb6003..56c5e04e 100644 --- a/charts/repo-guard/templates/githuborganization.yaml +++ b/charts/repo-guard/templates/githuborganization.yaml @@ -26,7 +26,7 @@ metadata: {{- if eq .name $org.github -}} {{- $webURL := .webURL | default "" -}} {{- $host := ($webURL | trimPrefix "https://" | trimPrefix "http://") -}} - {{- $githubInstance = (splitList ":" $host | first) -}} + {{- $githubInstance = (splitList ":" $host | first | splitList "/" | first) -}} {{- end -}} {{- end -}} {{- end }} From 40b9f258cdeef2594f9264759f2f3a1a001d9163 Mon Sep 17 00:00:00 2001 From: Onur Yilmaz Date: Tue, 15 Sep 2026 12:44:34 +0200 Subject: [PATCH 08/10] fix(helm): normalize githubs[].name comparison to lowercase and document githubInstanceKey override risk Signed-off-by: Onur Yilmaz --- charts/repo-guard/templates/githuborganization.yaml | 2 +- charts/repo-guard/values.yaml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/charts/repo-guard/templates/githuborganization.yaml b/charts/repo-guard/templates/githuborganization.yaml index 56c5e04e..6befea79 100644 --- a/charts/repo-guard/templates/githuborganization.yaml +++ b/charts/repo-guard/templates/githuborganization.yaml @@ -23,7 +23,7 @@ metadata: {{- $githubInstance := $org.githubInstanceHostname | default "" -}} {{- if not $githubInstance -}} {{- range $.Values.githubs -}} - {{- if eq .name $org.github -}} + {{- if eq (.name | lower) ($org.github | lower) -}} {{- $webURL := .webURL | default "" -}} {{- $host := ($webURL | trimPrefix "https://" | trimPrefix "http://") -}} {{- $githubInstance = (splitList ":" $host | first | splitList "/" | first) -}} diff --git a/charts/repo-guard/values.yaml b/charts/repo-guard/values.yaml index c06912cf..bcd3e12a 100644 --- a/charts/repo-guard/values.yaml +++ b/charts/repo-guard/values.yaml @@ -138,6 +138,9 @@ perses: # githubInstanceHostname: # # githubInstanceKey: short key PM uses as the prefix in CR names (----). # # Defaults to the 'github' field value (the Github CR name), which matches how Helm names GithubOrganization CRs. +# # WARNING: overriding this value causes the label to advertise a prefix that differs from the one Helm uses when +# # naming GithubOrganization, GithubTeam, and GithubTeamRepository CRs (all of which are prefixed with $org.github). +# # Only override if you are also renaming those CRs to match. # githubInstanceKey: # # defaultLdapProvider: written by PM into spec.externalMemberProvider.ldap.provider on GithubTeams it creates for this org # defaultLdapProvider: From 00fc7582b8076e88d6e77d43ccc82d5b3a297b48 Mon Sep 17 00:00:00 2001 From: Onur Yilmaz Date: Tue, 15 Sep 2026 12:57:37 +0200 Subject: [PATCH 09/10] fix(helm): validate operational label values and catch non-string adminPermission Signed-off-by: Onur Yilmaz --- .../templates/githuborganization.yaml | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/charts/repo-guard/templates/githuborganization.yaml b/charts/repo-guard/templates/githuborganization.yaml index 6befea79..ce8da2d7 100644 --- a/charts/repo-guard/templates/githuborganization.yaml +++ b/charts/repo-guard/templates/githuborganization.yaml @@ -29,13 +29,36 @@ metadata: {{- $githubInstance = (splitList ":" $host | first | splitList "/" | first) -}} {{- end -}} {{- end -}} + {{- end -}} + {{- if gt (len $githubInstance) 63 -}} + {{- fail (printf "github-instance label value exceeds 63 characters (got %d): %q — set githubInstanceHostname to a shorter value" (len $githubInstance) $githubInstance) -}} + {{- end -}} + {{- if and $githubInstance (regexFind "[^A-Za-z0-9._-]" $githubInstance) -}} + {{- fail (printf "github-instance label value contains invalid characters: %q — set githubInstanceHostname to a value matching [A-Za-z0-9._-]" $githubInstance) -}} {{- end }} repo-guard.cloudoperators.dev/github-instance: "{{ $githubInstance }}" - repo-guard.cloudoperators.dev/github-instance-key: "{{ $org.githubInstanceKey | default $org.github }}" - repo-guard.cloudoperators.dev/default-ldap-provider: "{{ $org.defaultLdapProvider | default "" }}" + {{- $githubInstanceKey := $org.githubInstanceKey | default $org.github -}} + {{- if gt (len $githubInstanceKey) 63 -}} + {{- fail (printf "github-instance-key label value exceeds 63 characters (got %d): %q — set githubInstanceKey to a shorter value" (len $githubInstanceKey) $githubInstanceKey) -}} + {{- end -}} + {{- if and $githubInstanceKey (regexFind "[^A-Za-z0-9._-]" $githubInstanceKey) -}} + {{- fail (printf "github-instance-key label value contains invalid characters: %q — set githubInstanceKey to a value matching [A-Za-z0-9._-]" $githubInstanceKey) -}} + {{- end }} + repo-guard.cloudoperators.dev/github-instance-key: "{{ $githubInstanceKey }}" + {{- $defaultLdapProvider := $org.defaultLdapProvider | default "" -}} + {{- if gt (len $defaultLdapProvider) 63 -}} + {{- fail (printf "default-ldap-provider label value exceeds 63 characters (got %d): %q — set defaultLdapProvider to a shorter value" (len $defaultLdapProvider) $defaultLdapProvider) -}} + {{- end -}} + {{- if and $defaultLdapProvider (regexFind "[^A-Za-z0-9._-]" $defaultLdapProvider) -}} + {{- fail (printf "default-ldap-provider label value contains invalid characters: %q — set defaultLdapProvider to a value matching [A-Za-z0-9._-]" $defaultLdapProvider) -}} + {{- end }} + repo-guard.cloudoperators.dev/default-ldap-provider: "{{ $defaultLdapProvider }}" + {{- if and (hasKey $org "adminPermission") (not (kindIs "string" $org.adminPermission)) -}} + {{- fail (printf "githubOrganizations[].adminPermission must be a string (\"admin\" or \"admin-ondemand\"), got %v" $org.adminPermission) -}} + {{- end -}} {{- $adminPermission := $org.adminPermission | default "admin" -}} - {{- if and $org.adminPermission (not (or (eq $org.adminPermission "admin") (eq $org.adminPermission "admin-ondemand"))) -}} - {{- fail (printf "githubOrganizations[].adminPermission must be \"admin\" or \"admin-ondemand\", got %q" $org.adminPermission) -}} + {{- if and (hasKey $org "adminPermission") $org.adminPermission (not (or (eq $adminPermission "admin") (eq $adminPermission "admin-ondemand"))) -}} + {{- fail (printf "githubOrganizations[].adminPermission must be \"admin\" or \"admin-ondemand\", got %q" $adminPermission) -}} {{- end }} repo-guard.cloudoperators.dev/admin-permission: "{{ $adminPermission }}" {{- $skipTeams := join "," $org.skipDefaultRepositoryTeams }} From ea5c8e4d21c76d18391fe9d9975ed859a990d928 Mon Sep 17 00:00:00 2001 From: Onur Yilmaz Date: Tue, 15 Sep 2026 14:00:21 +0200 Subject: [PATCH 10/10] fix(helm): enforce full Kubernetes label value grammar and catch empty adminPermission Signed-off-by: Onur Yilmaz --- .../templates/githuborganization.yaml | 20 +++++++++---------- docs/operations/labels.md | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/charts/repo-guard/templates/githuborganization.yaml b/charts/repo-guard/templates/githuborganization.yaml index ce8da2d7..aa4d6751 100644 --- a/charts/repo-guard/templates/githuborganization.yaml +++ b/charts/repo-guard/templates/githuborganization.yaml @@ -33,33 +33,33 @@ metadata: {{- if gt (len $githubInstance) 63 -}} {{- fail (printf "github-instance label value exceeds 63 characters (got %d): %q — set githubInstanceHostname to a shorter value" (len $githubInstance) $githubInstance) -}} {{- end -}} - {{- if and $githubInstance (regexFind "[^A-Za-z0-9._-]" $githubInstance) -}} - {{- fail (printf "github-instance label value contains invalid characters: %q — set githubInstanceHostname to a value matching [A-Za-z0-9._-]" $githubInstance) -}} + {{- if and $githubInstance (not (regexMatch "^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$" $githubInstance)) -}} + {{- fail (printf "github-instance label value is not a valid Kubernetes label value: %q — must start and end with alphanumeric, set githubInstanceHostname to a valid value" $githubInstance) -}} {{- end }} repo-guard.cloudoperators.dev/github-instance: "{{ $githubInstance }}" {{- $githubInstanceKey := $org.githubInstanceKey | default $org.github -}} {{- if gt (len $githubInstanceKey) 63 -}} {{- fail (printf "github-instance-key label value exceeds 63 characters (got %d): %q — set githubInstanceKey to a shorter value" (len $githubInstanceKey) $githubInstanceKey) -}} {{- end -}} - {{- if and $githubInstanceKey (regexFind "[^A-Za-z0-9._-]" $githubInstanceKey) -}} - {{- fail (printf "github-instance-key label value contains invalid characters: %q — set githubInstanceKey to a value matching [A-Za-z0-9._-]" $githubInstanceKey) -}} + {{- if and $githubInstanceKey (not (regexMatch "^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$" $githubInstanceKey)) -}} + {{- fail (printf "github-instance-key label value is not a valid Kubernetes label value: %q — must start and end with alphanumeric, set githubInstanceKey to a valid value" $githubInstanceKey) -}} {{- end }} repo-guard.cloudoperators.dev/github-instance-key: "{{ $githubInstanceKey }}" {{- $defaultLdapProvider := $org.defaultLdapProvider | default "" -}} {{- if gt (len $defaultLdapProvider) 63 -}} {{- fail (printf "default-ldap-provider label value exceeds 63 characters (got %d): %q — set defaultLdapProvider to a shorter value" (len $defaultLdapProvider) $defaultLdapProvider) -}} {{- end -}} - {{- if and $defaultLdapProvider (regexFind "[^A-Za-z0-9._-]" $defaultLdapProvider) -}} - {{- fail (printf "default-ldap-provider label value contains invalid characters: %q — set defaultLdapProvider to a value matching [A-Za-z0-9._-]" $defaultLdapProvider) -}} + {{- if and $defaultLdapProvider (not (regexMatch "^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$" $defaultLdapProvider)) -}} + {{- fail (printf "default-ldap-provider label value is not a valid Kubernetes label value: %q — must start and end with alphanumeric, set defaultLdapProvider to a valid value" $defaultLdapProvider) -}} {{- end }} repo-guard.cloudoperators.dev/default-ldap-provider: "{{ $defaultLdapProvider }}" {{- if and (hasKey $org "adminPermission") (not (kindIs "string" $org.adminPermission)) -}} {{- fail (printf "githubOrganizations[].adminPermission must be a string (\"admin\" or \"admin-ondemand\"), got %v" $org.adminPermission) -}} {{- end -}} - {{- $adminPermission := $org.adminPermission | default "admin" -}} - {{- if and (hasKey $org "adminPermission") $org.adminPermission (not (or (eq $adminPermission "admin") (eq $adminPermission "admin-ondemand"))) -}} - {{- fail (printf "githubOrganizations[].adminPermission must be \"admin\" or \"admin-ondemand\", got %q" $adminPermission) -}} - {{- end }} + {{- if and (hasKey $org "adminPermission") (kindIs "string" $org.adminPermission) (not (or (eq $org.adminPermission "admin") (eq $org.adminPermission "admin-ondemand"))) -}} + {{- fail (printf "githubOrganizations[].adminPermission must be \"admin\" or \"admin-ondemand\", got %q" $org.adminPermission) -}} + {{- end -}} + {{- $adminPermission := $org.adminPermission | default "admin" }} repo-guard.cloudoperators.dev/admin-permission: "{{ $adminPermission }}" {{- $skipTeams := join "," $org.skipDefaultRepositoryTeams }} {{- if $skipTeams }} diff --git a/docs/operations/labels.md b/docs/operations/labels.md index 64458094..b57da24d 100644 --- a/docs/operations/labels.md +++ b/docs/operations/labels.md @@ -27,7 +27,7 @@ These labels are set automatically by the Helm chart and are not intended to con | Key | Description | Default (Helm) | |---|---|---| -| `repo-guard.cloudoperators.dev/github-instance` | GitHub hostname (e.g. `github.com`, `github.wdf.sap.corp`). PM uses this as a label selector to find the org CR from a CCRN instance segment (the `` path component, which is the full hostname). Derived from `githubs[].webURL` of the matching entry (scheme stripped); override via `githubInstanceHostname` in Helm values. Must be ≤ 63 characters (Kubernetes label value limit). | `githubs[].webURL` (scheme stripped) for the matching `github` key | +| `repo-guard.cloudoperators.dev/github-instance` | GitHub hostname (e.g. `github.com`, `github.wdf.sap.corp`). PM uses this as a label selector to find the org CR from a CCRN instance segment (the `` path component, which is the full hostname). Derived from `githubs[].webURL` of the matching entry (scheme, port, and path stripped — e.g. `https://host:8080/base/` → `host`); override via `githubInstanceHostname` in Helm values. Must be a valid Kubernetes label value (≤ 63 chars, starts and ends with alphanumeric). | `githubs[].webURL` (scheme, port, and path stripped) for the matching `github` key | | `repo-guard.cloudoperators.dev/github-instance-key` | Short key PM uses as the naming prefix in repo-guard's `----` CR convention. Defaults to `spec.github` (the `Github` CR name), which is the same prefix the Helm chart uses when naming `GithubOrganization` CRs. Override via `githubInstanceKey` in Helm values. | `spec.github` value (the `Github` CR name) | | `repo-guard.cloudoperators.dev/default-ldap-provider` | LDAP provider name PM writes into `spec.externalMemberProvider.ldap.provider` on each `GithubTeam` it creates for this org. Empty string when not set. | `""` | | `repo-guard.cloudoperators.dev/admin-permission` | Permission string PM uses when mapping the `ADMIN` role for this org. Either `"admin"` or `"admin-ondemand"`. | `"admin"` |