package cluster import ( "context" "strings" "testing" "scm.bstein.dev/bstein/ananke/internal/config" ) // TestImagePullCredentialBlockerReasonsClassifiesHarborAuth runs one // orchestration or CLI step. // Signature: TestImagePullCredentialBlockerReasonsClassifiesHarborAuth(t *testing.T). // Why: Harbor auth failures should drive secret-sync repair instead of pod // recycling or DNS remediation. func TestImagePullCredentialBlockerReasonsClassifiesHarborAuth(t *testing.T) { events := `{"items":[` + `{"involvedObject":{"kind":"Pod","namespace":"veles","name":"veles-backend"},"type":"Warning","reason":"Failed","message":"Failed to pull image \"registry.bstein.dev/veles/veles-backend:0.5.25\": no basic auth credentials"},` + `{"metadata":{"namespace":"veles"},"involvedObject":{"kind":"Pod","name":"veles-frontend"},"type":"Warning","reason":"FailedToRetrieveImagePullSecret","message":"Unable to retrieve some image pull secrets (harbor-regcred); attempting to pull the image may not succeed."},` + `{"involvedObject":{"kind":"Pod","namespace":"logging","name":"oauth2"},"type":"Warning","reason":"Failed","message":"Failed to pull image: lookup registry-1.docker.io: Try again"}` + `]}` orch := buildOrchestratorWithStubs(t, config.Config{}, []commandStub{ {match: matchContains("kubectl", "get", "events", "-A", "-o", "json"), out: events}, }) reasons, err := orch.imagePullCredentialBlockerReasons(context.Background()) if err != nil { t.Fatalf("imagePullCredentialBlockerReasons failed: %v", err) } if got := reasons["veles/veles-backend"]; got != "ImagePullCredentialBlocker:no-basic-auth" { t.Fatalf("unexpected backend credential reason %q", got) } if got := reasons["veles/veles-frontend"]; got != "ImagePullCredentialBlocker:missing-pull-secret" { t.Fatalf("unexpected frontend credential reason %q", got) } if _, ok := reasons["logging/oauth2"]; ok { t.Fatalf("DNS image-pull blocker must not be classified as credential failure") } } // TestHealImagePullCredentialSyncRestartsVaultSyncDeployment runs one // orchestration or CLI step. // Signature: TestHealImagePullCredentialSyncRestartsVaultSyncDeployment(t *testing.T). // Why: a namespace with registry credential blockers and a Vault CSI sync helper // should get that sync helper rolled instead of application pods deleted. func TestHealImagePullCredentialSyncRestartsVaultSyncDeployment(t *testing.T) { events := `{"items":[{"involvedObject":{"kind":"Pod","namespace":"veles","name":"veles-backend"},"type":"Warning","reason":"Failed","message":"Failed to pull image \"registry.bstein.dev/veles/veles-backend:0.5.25\": no basic auth credentials"}]}` deployments := `{"items":[` + `{"metadata":{"name":"veles-backend","labels":{"app.kubernetes.io/name":"veles-backend"}}},` + `{"metadata":{"name":"veles-vault-sync","labels":{"app.kubernetes.io/component":"vault-sync"}}}` + `]}` restarted := false rolledOut := false orch := buildOrchestratorWithStubs(t, config.Config{}, []commandStub{ {match: matchContains("kubectl", "get", "events", "-A", "-o", "json"), out: events}, {match: matchContains("kubectl", "-n", "veles", "get", "deployment", "-o", "json"), out: deployments}, { match: func(name string, args []string) bool { if !matchContains("kubectl", "-n", "veles", "rollout", "restart", "deployment", "veles-vault-sync")(name, args) { return false } restarted = true return true }, }, { match: func(name string, args []string) bool { if !matchContains("kubectl", "-n", "veles", "rollout", "status", "deployment/veles-vault-sync", "--timeout=60s")(name, args) { return false } rolledOut = true return true }, }, }) repaired, err := orch.healImagePullCredentialSync(context.Background()) if err != nil { t.Fatalf("healImagePullCredentialSync failed: %v", err) } if strings.Join(repaired, ",") != "veles/deployment/veles-vault-sync" { t.Fatalf("unexpected image-pull credential repair list: %#v", repaired) } if !restarted || !rolledOut { t.Fatalf("expected vault-sync deployment restart and rollout wait, restarted=%v rolledOut=%v", restarted, rolledOut) } }