diff --git a/.github/workflows/helm_ci.yml b/.github/workflows/helm_ci.yml index 264483409..d195b4a40 100644 --- a/.github/workflows/helm_ci.yml +++ b/.github/workflows/helm_ci.yml @@ -1029,6 +1029,39 @@ jobs: else: print(f"{dropped}=null: the key is left out rather than rendered as null") + # Helm allows a release named "123", and an unquoted label value then + # renders as a YAML integer. Label values are strings in the API, so the + # whole object gets rejected - a policy that never applies, silently. + # Every label the policies emit, in metadata and in every selector, has + # to survive that release name as a string. + numeric = subprocess.check_output( + ["helm", "template", "123", chart, + "--set", "networkPolicy.enabled=true", + "--set", "networkPolicy.egress.enabled=true", + "--set", "networkPolicy.egress.kubeApiServer.cidrs[0]=10.96.0.1/32", + "--set", "s3.enabled=true", "--set", "s3.createBuckets[0].name=b"], + text=True, stderr=subprocess.STDOUT) + + def label_maps(policy): + yield "metadata", policy["metadata"].get("labels", {}) + yield "podSelector", policy["spec"]["podSelector"]["matchLabels"] + for direction in ("ingress", "egress"): + for i, rule in enumerate(policy["spec"].get(direction) or []): + for peer in rule.get("to") or rule.get("from") or []: + for key in ("podSelector", "namespaceSelector"): + sel = peer.get(key) or {} + yield f"{direction}[{i}].{key}", sel.get("matchLabels", {}) + + coerced = [(comp, where, k, v) + for comp, p in policies(numeric).items() + for where, labels in label_maps(p) + for k, v in labels.items() if not isinstance(v, str)] + if coerced: + failed.append(f"release name 123: label values are not strings, so the API " + f"server rejects the policy: {coerced}") + else: + print("a numeric release name keeps every policy label a string") + out = render(egress_on) pols = policies(out) diff --git a/k8s/charts/seaweedfs/templates/shared/networkpolicy.yaml b/k8s/charts/seaweedfs/templates/shared/networkpolicy.yaml index 56d565d9c..bb6bbabae 100644 --- a/k8s/charts/seaweedfs/templates/shared/networkpolicy.yaml +++ b/k8s/charts/seaweedfs/templates/shared/networkpolicy.yaml @@ -133,11 +133,11 @@ metadata: name: {{ include "seaweedfs.componentName" (list $ $component) }} namespace: {{ $.Release.Namespace }} labels: - app.kubernetes.io/name: {{ template "seaweedfs.name" $ }} + app.kubernetes.io/name: {{ include "seaweedfs.name" $ | quote }} helm.sh/chart: {{ $.Chart.Name }}-{{ $.Chart.Version | replace "+" "_" }} - app.kubernetes.io/managed-by: {{ $.Release.Service }} - app.kubernetes.io/instance: {{ $.Release.Name }} - app.kubernetes.io/component: {{ $component }} + app.kubernetes.io/managed-by: {{ $.Release.Service | quote }} + app.kubernetes.io/instance: {{ $.Release.Name | quote }} + app.kubernetes.io/component: {{ $component | quote }} {{- if $target.hook }} annotations: {{- /* Only before-hook-creation: the policy has to outlive the Job it @@ -151,9 +151,9 @@ metadata: spec: podSelector: matchLabels: - app.kubernetes.io/name: {{ template "seaweedfs.name" $ }} - app.kubernetes.io/instance: {{ $.Release.Name }} - app.kubernetes.io/component: {{ $component }} + app.kubernetes.io/name: {{ include "seaweedfs.name" $ | quote }} + app.kubernetes.io/instance: {{ $.Release.Name | quote }} + app.kubernetes.io/component: {{ $component | quote }} policyTypes: - Ingress {{- if $egressCfg.enabled }} @@ -168,8 +168,8 @@ spec: - from: - podSelector: matchLabels: - app.kubernetes.io/name: {{ template "seaweedfs.name" $ }} - app.kubernetes.io/instance: {{ $.Release.Name }} + app.kubernetes.io/name: {{ include "seaweedfs.name" $ | quote }} + app.kubernetes.io/instance: {{ $.Release.Name | quote }} ports: {{- range $port := $target.ports }} - protocol: TCP @@ -184,8 +184,8 @@ spec: - to: - podSelector: matchLabels: - app.kubernetes.io/name: {{ template "seaweedfs.name" $ }} - app.kubernetes.io/instance: {{ $.Release.Name }} + app.kubernetes.io/name: {{ include "seaweedfs.name" $ | quote }} + app.kubernetes.io/instance: {{ $.Release.Name | quote }} {{- if $egressCfg.allowDNS }} {{- /* Emit only the selectors that are set. An empty one has to be left out rather than rendered as null: a missing podSelector means every pod in