diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ea55dd5bb..2e5230245 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -217,6 +217,10 @@ jobs: - 'tests/require-baseline-workers-for-hcloud-volumes/**' - 'tests/audit-privileged-rbac/**' - 'tests/auto-vpa/**' + # The platform's copy of the catalog auto-vpa GeneratingPolicy: its + # fixtures and its system-namespace exclusion test (#5130). + - 'tests/auto-vpa-generating-policy-*/**' + - 'scripts/tests/test-auto-vpa-generating-policy-exclusion.sh' - 'scripts/tests/test-audit-privileged-rbac.sh' # Renders the charts whose roles that policy excludes and applies the # policy to their RBAC, so a chart bump fails its own PR (#3792). @@ -1628,6 +1632,8 @@ jobs: bash scripts/tests/test-tetragon-agent-priority.sh shellcheck scripts/tests/test-audit-privileged-rbac.sh bash scripts/tests/test-audit-privileged-rbac.sh + shellcheck scripts/tests/test-auto-vpa-generating-policy-exclusion.sh + bash scripts/tests/test-auto-vpa-generating-policy-exclusion.sh shellcheck scripts/tests/test-arc-controller-availability.sh bash scripts/tests/test-arc-controller-availability.sh shellcheck scripts/tests/test-restrict-tenant-secret-stores.sh diff --git a/k8s/bases/infrastructure/cluster-policies/best-practices/auto-vpa-daemonset.yaml b/k8s/bases/infrastructure/cluster-policies/best-practices/auto-vpa-daemonset.yaml new file mode 100644 index 000000000..2be846915 --- /dev/null +++ b/k8s/bases/infrastructure/cluster-policies/best-practices/auto-vpa-daemonset.yaml @@ -0,0 +1,89 @@ +--- +# ADOPTED from devantler-tech/kyverno-policies +# policies/best-practices/auto-vpa-generating-policy.yaml at 44ed118 (#5130), which +# registers three GeneratingPolicy objects in one file. The platform keeps one +# resource per file, so it is split into auto-vpa-deployment.yaml (which also +# carries the catalog's own header), auto-vpa-statefulset.yaml and +# auto-vpa-daemonset.yaml. Each policy is copied verbatim except for one marked +# PLATFORM ADDITION: a namespaceSelector that keeps it out of the system +# namespaces, as the classic best-practices/auto-vpa.yaml did. The generate +# expressions must stay byte-for-byte the catalog's (whitespace aside): both static +# producer guards pin them (scripts/guard-consumer-discovery-conservation.sh and +# scripts/tests/crossplane-egress-policy-rules.yaml). The classic VPAs are named +# , these are -, so running both forms at once would put two VPAs +# on every workload: register these only after the classic policy's VPAs are gone. +apiVersion: policies.kyverno.io/v1 +kind: GeneratingPolicy +metadata: + name: auto-vpa-daemonset + annotations: + policies.kyverno.io/title: Auto VPA for DaemonSets (GeneratingPolicy) + policies.kyverno.io/category: Autoscaling + policies.kyverno.io/severity: low + policies.kyverno.io/subject: DaemonSet + policies.kyverno.io/minversion: 1.19.0 + policies.kyverno.io/description: >- + Generates a CPU-only recommendation-bounded VerticalPodAutoscaler for each + DaemonSet. GeneratingPolicy form of the classic auto-vpa rule of the same name. +spec: + # Parity with the classic rules' `synchronize: true` + `generateExisting: true`: + # on GeneratingPolicy both live under spec.evaluation and default to false, so + # omitting them would silently drop behavior the classic policy guarantees. + evaluation: + generateExisting: + enabled: true + synchronize: + enabled: true + matchConstraints: + resourceRules: + - apiGroups: ["apps"] + apiVersions: ["v1"] + operations: ["CREATE", "UPDATE"] + resources: ["daemonsets"] + # PLATFORM ADDITION (the only change from the catalog file): prod ships + # static VPAs for the controllers in these namespaces + # (k8s/providers/hetzner/infrastructure/vertical-pod-autoscalers/), so a + # generated one would put a second VPA on each of those pods. + namespaceSelector: + matchExpressions: + - key: kubernetes.io/metadata.name + operator: NotIn + values: + - kube-system + - kube-public + - kube-node-lease + - kyverno + generate: + - expression: >- + generator.Apply( + string(object.metadata.namespace), + [ + { + "apiVersion": dyn("autoscaling.k8s.io/v1"), + "kind": dyn("VerticalPodAutoscaler"), + "metadata": dyn({ + "name": string(object.metadata.name) + "-daemonset", + "namespace": string(object.metadata.namespace) + }), + "spec": dyn({ + "targetRef": dyn({ + "apiVersion": "apps/v1", + "kind": "DaemonSet", + "name": string(object.metadata.name) + }), + "updatePolicy": dyn({"updateMode": "InPlaceOrRecreate"}), + "resourcePolicy": dyn({ + "containerPolicies": [ + { + "containerName": dyn("*"), + "controlledResources": dyn(["cpu"]), + "controlledValues": dyn("RequestsAndLimits"), + "minAllowed": dyn({"cpu": "50m"}), + "maxAllowed": dyn({"cpu": "1"}) + } + ] + }) + }) + } + ] + ) diff --git a/k8s/bases/infrastructure/cluster-policies/best-practices/auto-vpa-deployment.yaml b/k8s/bases/infrastructure/cluster-policies/best-practices/auto-vpa-deployment.yaml new file mode 100644 index 000000000..6feb459e0 --- /dev/null +++ b/k8s/bases/infrastructure/cluster-policies/best-practices/auto-vpa-deployment.yaml @@ -0,0 +1,117 @@ +--- +# ADOPTED from devantler-tech/kyverno-policies +# policies/best-practices/auto-vpa-generating-policy.yaml at 44ed118 (#5130), which +# registers three GeneratingPolicy objects in one file. The platform keeps one +# resource per file, so it is split into auto-vpa-deployment.yaml (which also +# carries the catalog's own header), auto-vpa-statefulset.yaml and +# auto-vpa-daemonset.yaml. Each policy is copied verbatim except for one marked +# PLATFORM ADDITION: a namespaceSelector that keeps it out of the system +# namespaces, as the classic best-practices/auto-vpa.yaml did. The generate +# expressions must stay byte-for-byte the catalog's (whitespace aside): both static +# producer guards pin them (scripts/guard-consumer-discovery-conservation.sh and +# scripts/tests/crossplane-egress-policy-rules.yaml). The classic VPAs are named +# , these are -, so running both forms at once would put two VPAs +# on every workload: register these only after the classic policy's VPAs are gone. +# +# The catalog's header follows. In this repository the classic file is the rollback +# form, and "the root kustomization.yaml" means this folder's kustomization.yaml. +# "Under the same name" below is not true here: the classic VPA is and this +# one is -deployment, so running both forms gives every workload two VPAs. +# +# The catalog's auto-vpa: a port of the classic policies/best-practices/auto-vpa.yaml +# ClusterPolicy rules to the GeneratingPolicy API, which Kyverno keeps after classic +# ClusterPolicy generate rules are retired. The classic ClusterPolicy is a separate file. +# +# THIS IS THE REGISTERED FORM in the root kustomization.yaml. The classic file stays in the +# repository, unregistered, as the rollback form until every consumer has migrated: never +# apply both to one cluster, because both generate a VerticalPodAutoscaler for the same +# workload under the same name. The README's "Migrating to the GeneratingPolicy form" +# section is the consumer rollout and rollback procedure. +# +# Split into one policy per workload kind, mirroring the classic policy's three rules: +# GeneratingPolicy applies spec.matchConstraints to the whole policy, so three kinds with +# three different VPA bodies cannot share one policy without branching inside CEL. +# +# FLOOR: Kyverno 1.19.0. On 1.18.x spec.evaluation.generateExisting is schema-accepted but +# does not fire for PRE-EXISTING workloads (kyverno/kyverno#15722, a regression against the +# classic rule; fixed by kyverno/kyverno#16041 for 1.19.0 and never backported), so an +# existing workload would get its VPA only on its next CREATE/UPDATE admission. The +# minversion annotations below declare that floor. +apiVersion: policies.kyverno.io/v1 +kind: GeneratingPolicy +metadata: + name: auto-vpa-deployment + annotations: + policies.kyverno.io/title: Auto VPA for Deployments (GeneratingPolicy) + policies.kyverno.io/category: Autoscaling + policies.kyverno.io/severity: low + policies.kyverno.io/subject: Deployment + policies.kyverno.io/minversion: 1.19.0 + policies.kyverno.io/description: >- + Generates a recommendation-bounded VerticalPodAutoscaler for each Deployment. + GeneratingPolicy form of the classic auto-vpa rule of the same name. +spec: + # Parity with the classic rules' `synchronize: true` + `generateExisting: true`: + # on GeneratingPolicy both live under spec.evaluation and default to false, so + # omitting them would silently drop behavior the classic policy guarantees. + evaluation: + generateExisting: + enabled: true + synchronize: + enabled: true + matchConstraints: + resourceRules: + - apiGroups: ["apps"] + apiVersions: ["v1"] + operations: ["CREATE", "UPDATE"] + resources: ["deployments"] + # PLATFORM ADDITION (the only change from the catalog file): prod ships + # static VPAs for the controllers in these namespaces + # (k8s/providers/hetzner/infrastructure/vertical-pod-autoscalers/), so a + # generated one would put a second VPA on each of those pods. + namespaceSelector: + matchExpressions: + - key: kubernetes.io/metadata.name + operator: NotIn + values: + - kube-system + - kube-public + - kube-node-lease + - kyverno + generate: + # Every value is dyn(): CEL infers a map's value type from its first entry, so a + # resource literal mixing strings with nested maps fails to compile without it. + # Fields read off `object` are dyn and need string() before concatenation. + - expression: >- + generator.Apply( + string(object.metadata.namespace), + [ + { + "apiVersion": dyn("autoscaling.k8s.io/v1"), + "kind": dyn("VerticalPodAutoscaler"), + "metadata": dyn({ + "name": string(object.metadata.name) + "-deployment", + "namespace": string(object.metadata.namespace) + }), + "spec": dyn({ + "targetRef": dyn({ + "apiVersion": "apps/v1", + "kind": "Deployment", + "name": string(object.metadata.name) + }), + "updatePolicy": dyn({"updateMode": "InPlaceOrRecreate"}), + "resourcePolicy": dyn({ + "containerPolicies": [ + { + "containerName": dyn("*"), + "controlledResources": dyn(["cpu", "memory"]), + "controlledValues": dyn("RequestsAndLimits"), + "minAllowed": dyn({"cpu": "50m", "memory": "64Mi"}), + "maxAllowed": dyn({"cpu": "3", "memory": "6Gi"}) + } + ] + }) + }) + } + ] + ) diff --git a/k8s/bases/infrastructure/cluster-policies/best-practices/auto-vpa-statefulset.yaml b/k8s/bases/infrastructure/cluster-policies/best-practices/auto-vpa-statefulset.yaml new file mode 100644 index 000000000..bf2ec2606 --- /dev/null +++ b/k8s/bases/infrastructure/cluster-policies/best-practices/auto-vpa-statefulset.yaml @@ -0,0 +1,89 @@ +--- +# ADOPTED from devantler-tech/kyverno-policies +# policies/best-practices/auto-vpa-generating-policy.yaml at 44ed118 (#5130), which +# registers three GeneratingPolicy objects in one file. The platform keeps one +# resource per file, so it is split into auto-vpa-deployment.yaml (which also +# carries the catalog's own header), auto-vpa-statefulset.yaml and +# auto-vpa-daemonset.yaml. Each policy is copied verbatim except for one marked +# PLATFORM ADDITION: a namespaceSelector that keeps it out of the system +# namespaces, as the classic best-practices/auto-vpa.yaml did. The generate +# expressions must stay byte-for-byte the catalog's (whitespace aside): both static +# producer guards pin them (scripts/guard-consumer-discovery-conservation.sh and +# scripts/tests/crossplane-egress-policy-rules.yaml). The classic VPAs are named +# , these are -, so running both forms at once would put two VPAs +# on every workload: register these only after the classic policy's VPAs are gone. +apiVersion: policies.kyverno.io/v1 +kind: GeneratingPolicy +metadata: + name: auto-vpa-statefulset + annotations: + policies.kyverno.io/title: Auto VPA for StatefulSets (GeneratingPolicy) + policies.kyverno.io/category: Autoscaling + policies.kyverno.io/severity: low + policies.kyverno.io/subject: StatefulSet + policies.kyverno.io/minversion: 1.19.0 + policies.kyverno.io/description: >- + Generates a recommendation-bounded VerticalPodAutoscaler for each StatefulSet. + GeneratingPolicy form of the classic auto-vpa rule of the same name. +spec: + # Parity with the classic rules' `synchronize: true` + `generateExisting: true`: + # on GeneratingPolicy both live under spec.evaluation and default to false, so + # omitting them would silently drop behavior the classic policy guarantees. + evaluation: + generateExisting: + enabled: true + synchronize: + enabled: true + matchConstraints: + resourceRules: + - apiGroups: ["apps"] + apiVersions: ["v1"] + operations: ["CREATE", "UPDATE"] + resources: ["statefulsets"] + # PLATFORM ADDITION (the only change from the catalog file): prod ships + # static VPAs for the controllers in these namespaces + # (k8s/providers/hetzner/infrastructure/vertical-pod-autoscalers/), so a + # generated one would put a second VPA on each of those pods. + namespaceSelector: + matchExpressions: + - key: kubernetes.io/metadata.name + operator: NotIn + values: + - kube-system + - kube-public + - kube-node-lease + - kyverno + generate: + - expression: >- + generator.Apply( + string(object.metadata.namespace), + [ + { + "apiVersion": dyn("autoscaling.k8s.io/v1"), + "kind": dyn("VerticalPodAutoscaler"), + "metadata": dyn({ + "name": string(object.metadata.name) + "-statefulset", + "namespace": string(object.metadata.namespace) + }), + "spec": dyn({ + "targetRef": dyn({ + "apiVersion": "apps/v1", + "kind": "StatefulSet", + "name": string(object.metadata.name) + }), + "updatePolicy": dyn({"updateMode": "Initial"}), + "resourcePolicy": dyn({ + "containerPolicies": [ + { + "containerName": dyn("*"), + "controlledResources": dyn(["cpu", "memory"]), + "controlledValues": dyn("RequestsAndLimits"), + "minAllowed": dyn({"cpu": "50m", "memory": "64Mi"}), + "maxAllowed": dyn({"cpu": "3", "memory": "6Gi"}) + } + ] + }) + }) + } + ] + ) diff --git a/k8s/bases/infrastructure/cluster-policies/kustomization.yaml b/k8s/bases/infrastructure/cluster-policies/kustomization.yaml index e143931c6..1d7190f06 100644 --- a/k8s/bases/infrastructure/cluster-policies/kustomization.yaml +++ b/k8s/bases/infrastructure/cluster-policies/kustomization.yaml @@ -8,13 +8,14 @@ resources: - best-practices/add-recommended-labels.yaml - best-practices/add-resource-defaults.yaml - best-practices/add-security-context.yaml - # best-practices/auto-vpa.yaml is deliberately NOT registered: it is step 1 - # of the shared catalog's two-reconcile move to its GeneratingPolicy form - # (devantler-tech/kyverno-policies#1). Removing the classic policy deletes - # the VPAs it generated; the GeneratingPolicy form is registered in a later - # revision, once no `generate.kyverno.io/policy-name=auto-vpa` VPA remains, - # so the two forms never run at once. The file stays, still tested, as the - # rollback form until that move is complete. + # The shared catalog's GeneratingPolicy form of auto-vpa (#5130, + # devantler-tech/kyverno-policies#1). best-practices/auto-vpa.yaml, the classic + # form, is deliberately NOT registered: it stays, still tested, as the + # rollback form. Never register both, because they name their VPAs + # differently and every workload would get two. + - best-practices/auto-vpa-daemonset.yaml + - best-practices/auto-vpa-deployment.yaml + - best-practices/auto-vpa-statefulset.yaml - best-practices/audit-privileged-rbac.yaml - best-practices/disable-default-sa-automount.yaml - best-practices/disallow-latest-tag.yaml diff --git a/scripts/tests/test-auto-vpa-generating-policy-exclusion.sh b/scripts/tests/test-auto-vpa-generating-policy-exclusion.sh new file mode 100755 index 000000000..4cc454137 --- /dev/null +++ b/scripts/tests/test-auto-vpa-generating-policy-exclusion.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash +# The platform's copy of the catalog's auto-vpa GeneratingPolicy adds one thing: +# a namespaceSelector that keeps it out of the system namespaces, where prod +# ships static VPAs (k8s/providers/hetzner/infrastructure/vertical-pod-autoscalers/). +# A generated VPA there would put a second VPA on the same pods. A kyverno test +# cannot assert that a GeneratingPolicy generates nothing, so this script does +# it: for each policy and each excluded namespace, a test that expects a VPA +# there must fail with "Not found". The same test with the selector removed must +# pass, so a probe that could never see a generation cannot read as excluded +# (platform#5130). +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd "${script_dir}/../.." && pwd)" +policies="${repo_root}/k8s/bases/infrastructure/cluster-policies/best-practices" +static_vpas="${repo_root}/k8s/providers/hetzner/infrastructure/vertical-pod-autoscalers" + +work="$(mktemp -d)" +trap 'rm -rf "${work}"' EXIT + +# The classic policy's exclusions, plus every namespace prod gives static VPAs. +required="$( + { + printf '%s\n' kube-system kube-public kube-node-lease kyverno + kubectl kustomize "${static_vpas}" | + yq -N 'select(.kind == "VerticalPodAutoscaler") | .metadata.namespace' + } | sort -u +)" +if [[ -z "${required}" ]]; then + echo "FAIL: no namespaces to check" >&2 + exit 1 +fi + +checked=0 +for kind in deployment statefulset daemonset; do + name="auto-vpa-${kind}" + policy="${policies}/${name}.yaml" + fixtures="${repo_root}/tests/auto-vpa-generating-policy-${kind}" + excluded="$(yq -N "select(.metadata.name == \"${name}\") + | .spec.matchConstraints.namespaceSelector.matchExpressions[] + | select(.key == \"kubernetes.io/metadata.name\" and .operator == \"NotIn\") + | .values[]" "${policy}" | sort -u)" + missing="$(comm -23 <(printf '%s\n' "${required}") <(printf '%s\n' "${excluded}"))" + if [[ -n "${missing}" ]]; then + echo "FAIL: ${name} does not exclude: ${missing//$'\n'/ }" >&2 + exit 1 + fi + yq "(select(.metadata.name == \"${name}\") | .spec.matchConstraints) + |= del(.namespaceSelector)" "${policy}" >"${work}/${kind}-unscoped.yaml" + + while IFS= read -r ns; do + case_dir="${work}/${kind}-${ns}" + mkdir -p "${case_dir}" + sed "s/^ namespace: apps-ns\$/ namespace: ${ns}/" "${fixtures}/resources.yaml" >"${case_dir}/resources.yaml" + sed "s/^ namespace: apps-ns\$/ namespace: ${ns}/" "${fixtures}/generated-vpa.yaml" >"${case_dir}/generated-vpa.yaml" + grep -q "namespace: ${ns}\$" "${case_dir}/resources.yaml" + grep -q "namespace: ${ns}\$" "${case_dir}/generated-vpa.yaml" + cat >"${case_dir}/values.yaml" <"${case_dir}/kyverno-test.yaml" <"${case_dir}/${variant}.log" 2>&1 || status=$? + if [[ "${variant}" == scoped ]]; then + if [[ "${status}" -eq 0 ]] || ! grep -q 'Not found' "${case_dir}/${variant}.log"; then + echo "FAIL: ${name} generated a VPA in ${ns}:" >&2 + cat "${case_dir}/${variant}.log" >&2 + exit 1 + fi + elif [[ "${status}" -ne 0 ]]; then + echo "FAIL: control: ${name} without its selector generated nothing in ${ns}, so the probe proves nothing:" >&2 + cat "${case_dir}/${variant}.log" >&2 + exit 1 + fi + done + checked=$((checked + 1)) + done <<<"${required}" +done + +echo "OK: ${checked} policy/namespace cases generate no VPA, and each generates one without the selector" diff --git a/scripts/tests/test-validate-kyverno-fixture-evaluation.sh b/scripts/tests/test-validate-kyverno-fixture-evaluation.sh index 20399975c..b0cc4a784 100755 --- a/scripts/tests/test-validate-kyverno-fixture-evaluation.sh +++ b/scripts/tests/test-validate-kyverno-fixture-evaluation.sh @@ -141,6 +141,21 @@ if kyverno_passes "generated object named apart from its trigger" "${root}"; the expect_pass "generated object named apart from its trigger" "${root}" fi +# #5130: a GeneratingPolicy has no spec.rules; kyverno names its one rule after the policy. +# A row naming any other rule must still fail check 1, and a row expecting an object the +# policy never generates must still fail. +root="$(copy generating-policy-rule auto-vpa-generating-policy-deployment)" +yq -i '.results[0].rule = "generate-vpa-for-deployment"' \ + "${root}/tests/auto-vpa-generating-policy-deployment/kyverno-test.yaml" +expect_fail "GeneratingPolicy row naming a rule it does not define" "${root}" 1 \ + "names rule generate-vpa-for-deployment of policy auto-vpa-deployment" +root="$(copy generating-policy-missing auto-vpa-generating-policy-deployment)" +yq -i '.metadata.namespace = "other-ns"' "${root}/tests/auto-vpa-generating-policy-deployment/resources.yaml" +yq -i '.namespaceSelector[0].name = "other-ns" | .namespaceSelector[0].labels."kubernetes.io/metadata.name" = "other-ns"' \ + "${root}/tests/auto-vpa-generating-policy-deployment/values.yaml" +yq -i '.metadata.name = "absent-deployment"' "${root}/tests/auto-vpa-generating-policy-deployment/generated-vpa.yaml" +expect_fail "GeneratingPolicy expecting an object it never generates" "${root}" 1 "kyverno test" + # A generate row names only the object it generates, so the triggers of one entry that all # generate the same fixed-name object share its name, and kyverno drops a misspelled one # without a row or a failure. diff --git a/scripts/validate-eks-ci-role-policy/approved-surface.txt b/scripts/validate-eks-ci-role-policy/approved-surface.txt index 1446828a5..7ede6c0d9 100644 --- a/scripts/validate-eks-ci-role-policy/approved-surface.txt +++ b/scripts/validate-eks-ci-role-policy/approved-surface.txt @@ -148,6 +148,12 @@ pkg.crossplane.io/v1|Provider||provider-upjet-unifi d85c9714e797838b3a1c88bb2543 pkg.crossplane.io/v1|Provider||upbound-provider-family-aws 64c7d602bf7460b7a38657a49539f5fa945ef8216be5e4296dddd04f69a944f8 +policies.kyverno.io/v1|GeneratingPolicy||auto-vpa-daemonset 28dd85d6dedce48c3d60055aad47da956107a320ec6d06e594452253a72d13dc + +policies.kyverno.io/v1|GeneratingPolicy||auto-vpa-deployment 804637a8c0e29af0ffb16e9fff9bd03fb25192676abf04ce500dbb5372bb082b + +policies.kyverno.io/v1|GeneratingPolicy||auto-vpa-statefulset 91cde3f6d780f91211e596a9dcbc8aad4c624406c3ceef0001416d3eeff00a0d + policies.kyverno.io/v1|MutatingPolicy||helm-release-install-crds e4eff687fb6ed05f18ab75747073569c4cede6309f560dd10d17adcfc71da2df rbac.authorization.k8s.io/v1|ClusterRoleBinding||ascoachingogvaner-external-dns 2aac6fc040557b155131fdc2ad9261f6ad2b337835915d7dcb50a3704e032478 diff --git a/scripts/validate-kyverno-fixture-evaluation.sh b/scripts/validate-kyverno-fixture-evaluation.sh index e54f6e226..dbd75fa57 100755 --- a/scripts/validate-kyverno-fixture-evaluation.sh +++ b/scripts/validate-kyverno-fixture-evaluation.sh @@ -101,6 +101,11 @@ for test_file in "${test_files[@]}"; do | .spec.rules[] | [$p, .name, ([.match.resources.kinds[]?, .match.any[]?.resources.kinds[]?, .match.all[]?.resources.kinds[]?] | join(",")), $c, $generates, $patches, $filters] | join("|")' "${dir}/${policy_path}" >>"${work}/rules" + # A GeneratingPolicy has no spec.rules: kyverno reports its one generate as a rule named + # after the policy, and generates no autogen rules for it (#5130). + yq -r 'select(.kind == "GeneratingPolicy" and .metadata.name != null) + | [.metadata.name, .metadata.name, "", "", "true", "false", "false"] | join("|")' \ + "${dir}/${policy_path}" >>"${work}/rules" done < <(yq -r '.policies[]' "${test_file}") while IFS=$'\t' read -r policy rule; do diff --git a/tests/auto-vpa-generating-policy-daemonset/generated-vpa.yaml b/tests/auto-vpa-generating-policy-daemonset/generated-vpa.yaml new file mode 100644 index 000000000..71f16e096 --- /dev/null +++ b/tests/auto-vpa-generating-policy-daemonset/generated-vpa.yaml @@ -0,0 +1,23 @@ +--- +apiVersion: autoscaling.k8s.io/v1 +kind: VerticalPodAutoscaler +metadata: + name: shared-daemonset + namespace: apps-ns +spec: + targetRef: + apiVersion: apps/v1 + kind: DaemonSet + name: shared + updatePolicy: + updateMode: InPlaceOrRecreate + resourcePolicy: + containerPolicies: + - containerName: "*" + controlledResources: + - cpu + controlledValues: RequestsAndLimits + minAllowed: + cpu: 50m + maxAllowed: + cpu: "1" diff --git a/tests/auto-vpa-generating-policy-daemonset/kyverno-test.yaml b/tests/auto-vpa-generating-policy-daemonset/kyverno-test.yaml new file mode 100644 index 000000000..76e0f62f7 --- /dev/null +++ b/tests/auto-vpa-generating-policy-daemonset/kyverno-test.yaml @@ -0,0 +1,24 @@ +--- +# The platform's copy of the catalog's auto-vpa-daemonset GeneratingPolicy (#5130). +# The expected VPA is the catalog's own fixture (tests/auto-vpa/generated-daemonset-vpa.yaml +# at 44ed118), moved to a non-system namespace: the platform copy must generate +# exactly what the catalog does there. The system-namespace exclusion, which a +# kyverno test cannot assert, is pinned by +# scripts/tests/test-auto-vpa-generating-policy-exclusion.sh. +apiVersion: cli.kyverno.io/v1alpha1 +kind: Test +metadata: + name: auto-vpa-generating-policy-daemonset +policies: + - ../../k8s/bases/infrastructure/cluster-policies/best-practices/auto-vpa-daemonset.yaml +resources: + - resources.yaml +variables: values.yaml +results: + - policy: auto-vpa-daemonset + rule: auto-vpa-daemonset + resources: + - apps-ns/shared + generatedResource: generated-vpa.yaml + kind: DaemonSet + result: pass diff --git a/tests/auto-vpa-generating-policy-daemonset/resources.yaml b/tests/auto-vpa-generating-policy-daemonset/resources.yaml new file mode 100644 index 000000000..f3fccb372 --- /dev/null +++ b/tests/auto-vpa-generating-policy-daemonset/resources.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: shared + namespace: apps-ns +spec: + selector: + matchLabels: + app: web + template: + metadata: + labels: + app: web + spec: + containers: + - name: web + image: registry.k8s.io/pause:3.10 diff --git a/tests/auto-vpa-generating-policy-daemonset/values.yaml b/tests/auto-vpa-generating-policy-daemonset/values.yaml new file mode 100644 index 000000000..f1b259038 --- /dev/null +++ b/tests/auto-vpa-generating-policy-daemonset/values.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: cli.kyverno.io/v1alpha1 +kind: Values +namespaceSelector: + - name: apps-ns + labels: + kubernetes.io/metadata.name: apps-ns diff --git a/tests/auto-vpa-generating-policy-deployment/generated-vpa.yaml b/tests/auto-vpa-generating-policy-deployment/generated-vpa.yaml new file mode 100644 index 000000000..dbf25ed04 --- /dev/null +++ b/tests/auto-vpa-generating-policy-deployment/generated-vpa.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: autoscaling.k8s.io/v1 +kind: VerticalPodAutoscaler +metadata: + name: shared-deployment + namespace: apps-ns +spec: + targetRef: + apiVersion: apps/v1 + kind: Deployment + name: shared + updatePolicy: + updateMode: InPlaceOrRecreate + resourcePolicy: + containerPolicies: + - containerName: "*" + controlledResources: + - cpu + - memory + controlledValues: RequestsAndLimits + minAllowed: + cpu: 50m + memory: 64Mi + maxAllowed: + cpu: "3" + memory: 6Gi diff --git a/tests/auto-vpa-generating-policy-deployment/kyverno-test.yaml b/tests/auto-vpa-generating-policy-deployment/kyverno-test.yaml new file mode 100644 index 000000000..d7107b84a --- /dev/null +++ b/tests/auto-vpa-generating-policy-deployment/kyverno-test.yaml @@ -0,0 +1,24 @@ +--- +# The platform's copy of the catalog's auto-vpa-deployment GeneratingPolicy (#5130). +# The expected VPA is the catalog's own fixture (tests/auto-vpa/generated-deployment-vpa.yaml +# at 44ed118), moved to a non-system namespace: the platform copy must generate +# exactly what the catalog does there. The system-namespace exclusion, which a +# kyverno test cannot assert, is pinned by +# scripts/tests/test-auto-vpa-generating-policy-exclusion.sh. +apiVersion: cli.kyverno.io/v1alpha1 +kind: Test +metadata: + name: auto-vpa-generating-policy-deployment +policies: + - ../../k8s/bases/infrastructure/cluster-policies/best-practices/auto-vpa-deployment.yaml +resources: + - resources.yaml +variables: values.yaml +results: + - policy: auto-vpa-deployment + rule: auto-vpa-deployment + resources: + - apps-ns/shared + generatedResource: generated-vpa.yaml + kind: Deployment + result: pass diff --git a/tests/auto-vpa-generating-policy-deployment/resources.yaml b/tests/auto-vpa-generating-policy-deployment/resources.yaml new file mode 100644 index 000000000..3b157fae6 --- /dev/null +++ b/tests/auto-vpa-generating-policy-deployment/resources.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: shared + namespace: apps-ns +spec: + selector: + matchLabels: + app: web + template: + metadata: + labels: + app: web + spec: + containers: + - name: web + image: registry.k8s.io/pause:3.10 diff --git a/tests/auto-vpa-generating-policy-deployment/values.yaml b/tests/auto-vpa-generating-policy-deployment/values.yaml new file mode 100644 index 000000000..f1b259038 --- /dev/null +++ b/tests/auto-vpa-generating-policy-deployment/values.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: cli.kyverno.io/v1alpha1 +kind: Values +namespaceSelector: + - name: apps-ns + labels: + kubernetes.io/metadata.name: apps-ns diff --git a/tests/auto-vpa-generating-policy-statefulset/generated-vpa.yaml b/tests/auto-vpa-generating-policy-statefulset/generated-vpa.yaml new file mode 100644 index 000000000..9f7d68f80 --- /dev/null +++ b/tests/auto-vpa-generating-policy-statefulset/generated-vpa.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: autoscaling.k8s.io/v1 +kind: VerticalPodAutoscaler +metadata: + name: shared-statefulset + namespace: apps-ns +spec: + targetRef: + apiVersion: apps/v1 + kind: StatefulSet + name: shared + updatePolicy: + updateMode: Initial + resourcePolicy: + containerPolicies: + - containerName: "*" + controlledResources: + - cpu + - memory + controlledValues: RequestsAndLimits + minAllowed: + cpu: 50m + memory: 64Mi + maxAllowed: + cpu: "3" + memory: 6Gi diff --git a/tests/auto-vpa-generating-policy-statefulset/kyverno-test.yaml b/tests/auto-vpa-generating-policy-statefulset/kyverno-test.yaml new file mode 100644 index 000000000..0518ec7c6 --- /dev/null +++ b/tests/auto-vpa-generating-policy-statefulset/kyverno-test.yaml @@ -0,0 +1,24 @@ +--- +# The platform's copy of the catalog's auto-vpa-statefulset GeneratingPolicy (#5130). +# The expected VPA is the catalog's own fixture (tests/auto-vpa/generated-statefulset-vpa.yaml +# at 44ed118), moved to a non-system namespace: the platform copy must generate +# exactly what the catalog does there. The system-namespace exclusion, which a +# kyverno test cannot assert, is pinned by +# scripts/tests/test-auto-vpa-generating-policy-exclusion.sh. +apiVersion: cli.kyverno.io/v1alpha1 +kind: Test +metadata: + name: auto-vpa-generating-policy-statefulset +policies: + - ../../k8s/bases/infrastructure/cluster-policies/best-practices/auto-vpa-statefulset.yaml +resources: + - resources.yaml +variables: values.yaml +results: + - policy: auto-vpa-statefulset + rule: auto-vpa-statefulset + resources: + - apps-ns/shared + generatedResource: generated-vpa.yaml + kind: StatefulSet + result: pass diff --git a/tests/auto-vpa-generating-policy-statefulset/resources.yaml b/tests/auto-vpa-generating-policy-statefulset/resources.yaml new file mode 100644 index 000000000..62b3cb49d --- /dev/null +++ b/tests/auto-vpa-generating-policy-statefulset/resources.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: shared + namespace: apps-ns +spec: + selector: + matchLabels: + app: web + template: + metadata: + labels: + app: web + spec: + containers: + - name: web + image: registry.k8s.io/pause:3.10 diff --git a/tests/auto-vpa-generating-policy-statefulset/values.yaml b/tests/auto-vpa-generating-policy-statefulset/values.yaml new file mode 100644 index 000000000..f1b259038 --- /dev/null +++ b/tests/auto-vpa-generating-policy-statefulset/values.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: cli.kyverno.io/v1alpha1 +kind: Values +namespaceSelector: + - name: apps-ns + labels: + kubernetes.io/metadata.name: apps-ns