diff --git a/docs/ci/cleanup.md b/docs/ci/cleanup.md index f37df16995e..dd7562be1dd 100644 --- a/docs/ci/cleanup.md +++ b/docs/ci/cleanup.md @@ -96,6 +96,11 @@ To resolve the principals behind orphaned role assignments, `shared-leftovers` r For `cleanup-sweeper` `rg-ordered`, candidate resource groups are chosen using `tooling/cleanup-sweeper/resourcegroups.policy.yaml`. Discovery treats the `createdAt` tag (RFC3339 timestamp on the resource group) as required for any `action: delete` rule: groups without a parseable tag are not candidates. +The policy excludes long-lived slot-managed identity pools whose resource-group +names start with `aro-hcp-msi-container-`. These groups carry `persist=true`, +but they back repeated E2E leases and must not be treated as resources that +expire after 15 days. + Azure does not set that tag by default. Subscriptions where `rg-ordered` should run must apply an Azure Policy (or equivalent) that stamps `tags['createdAt']` when a resource group is created, using `[utcNow()]` in the policy rule. The rule body lives in `tooling/cleanup-sweeper/scripts/rg-createdat-policy-rule.json`. Because the `createdAt` tag is required for every `action: delete` rule, this policy is a hard per-subscription prerequisite: `rg-ordered` must not be enabled in a subscription (including the INT, STG, and PROD e2e subscriptions) until the `createdAt` policy is assigned there. The sweeper only requires a parseable `createdAt` tag, so groups created before the policy was assigned are not candidates until the tag is present (either stamped by the policy on new groups or backfilled onto existing ones, for example via policy remediation). Until the policy is in place, only `shared-leftovers` is safe to run in that subscription, since it does not delete resource groups. diff --git a/tooling/cleanup-sweeper/pkg/policy/policy_test.go b/tooling/cleanup-sweeper/pkg/policy/policy_test.go index 03bab6249be..6f46ff4b166 100644 --- a/tooling/cleanup-sweeper/pkg/policy/policy_test.go +++ b/tooling/cleanup-sweeper/pkg/policy/policy_test.go @@ -19,6 +19,8 @@ import ( "testing" "time" + "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/yaml" ) @@ -68,6 +70,67 @@ rgOrdered: } } +func TestRepositoryPolicyProtectsIdentityContainerPools(t *testing.T) { + t.Parallel() + + pol, err := Load("../../resourcegroups.policy.yaml") + if err != nil { + t.Fatalf("failed to load repository policy: %v", err) + } + if err := pol.Validate(); err != nil { + t.Fatalf("failed to validate repository policy: %v", err) + } + + now := time.Date(2026, time.September, 12, 12, 0, 0, 0, time.UTC) + excluded := sets.New(pol.RGOrdered.ExcludedResourceGroups...) + + testCases := []struct { + name string + resourceGroup string + expectSelected bool + expectRule string + }{ + { + name: "identity container older than 15 days is skipped case insensitively", + resourceGroup: "ARO-HCP-MSI-Container-dev-shard0-00-00", + expectSelected: false, + expectRule: "skip-identity-container-pools", + }, + { + name: "unrelated persistent group older than 15 days is selected", + resourceGroup: "example-persistent-resource-group", + expectSelected: true, + expectRule: "persist-true-delete-after-15d", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + resourceGroup := newResourceGroup( + tc.resourceGroup, + timePtr(now.Add(-16*24*time.Hour)), + map[string]string{"persist": "true"}, + false, + ) + selected, reason := pol.RGOrdered.Discovery.SelectsResourceGroup( + resourceGroup, + excluded, + sets.New[string](), + now, + ) + + if selected != tc.expectSelected { + t.Fatalf("expected selected=%t, got selected=%t", tc.expectSelected, selected) + } + if reason.Rule == nil || reason.Rule.Name != tc.expectRule { + t.Fatalf("expected rule %q, got %#v", tc.expectRule, reason.Rule) + } + }) + } +} + func TestPolicyValidate_RejectsInvalidRules(t *testing.T) { t.Parallel() diff --git a/tooling/cleanup-sweeper/resourcegroups.policy.yaml b/tooling/cleanup-sweeper/resourcegroups.policy.yaml index 745120d67fd..f30fe1613de 100644 --- a/tooling/cleanup-sweeper/resourcegroups.policy.yaml +++ b/tooling/cleanup-sweeper/resourcegroups.policy.yaml @@ -1,6 +1,7 @@ # cleanup-sweeper rg-ordered discovery policy (first matching rule wins). # - excludedResourceGroups: never candidates (see list below). # - Skip managed RGs (managedBy alive). +# - Skip slot-managed identity-container pools. # - Skip *-shared-resources RGs. # - hcp-underlay-pers-*: delete after 15d if persist=true; after 2d if persist is absent or not "true". # - hcp-underlay-prow-*: delete after 6h (transitional, remove after rename rollout). @@ -30,6 +31,10 @@ rgOrdered: any: true conditions: managedByAlive: true + - name: skip-identity-container-pools + action: skip + match: + nameRegex: "(?i)^aro-hcp-msi-container-" - name: skip-shared-resources action: skip match: