From 964d7246b0d4459f40b9ecd308c566cfe3df29ae Mon Sep 17 00:00:00 2001 From: eding Date: Sun, 2 Aug 2026 21:32:38 +0200 Subject: [PATCH] fix: refresh generated template components --- internal/bootstrap/bootstrap.go | 36 ++++---- internal/bootstrap/bootstrap_test.go | 126 +++++++++++++++++++++++++-- 2 files changed, 138 insertions(+), 24 deletions(-) diff --git a/internal/bootstrap/bootstrap.go b/internal/bootstrap/bootstrap.go index 2f0bec5..3abdd9a 100644 --- a/internal/bootstrap/bootstrap.go +++ b/internal/bootstrap/bootstrap.go @@ -70,7 +70,12 @@ var webhookTargetTimeout = 70 * time.Minute var webhookTargetPollInterval = 2 * time.Second -var templateBaseComponents = []string{"snapshot-crds", "democratic-csi", "cert-manager", "cluster-issuers", "gateway-api", "gateway", "monitoring", "openbao", "external-secrets", "external-dns", "tekton", "tekton-triggers"} +var templateBaseComponents = []string{"snapshot-crds", "democratic-csi", "cert-manager", "cluster-issuers", "gateway-api", "gateway", "monitoring", "openbao", "external-secrets", "cnpg", "external-dns", "tekton", "tekton-triggers"} + +var generatedTemplateFiles = map[string]map[string]bool{ + "democratic-csi": {"secret.sops.yaml": true}, + "openbao": {"unseal.sops.yaml": true}, +} var requiredClusterKustomizations = []string{"snapshot-crds-kustomization.yaml", "democratic-csi-kustomization.yaml", "cert-manager-kustomization.yaml", "cluster-issuers-kustomization.yaml", "gateway-api-kustomization.yaml", "cilium-kustomization.yaml", "cilium-config-kustomization.yaml", "openbao-kustomization.yaml", "external-secrets-kustomization.yaml", "cnpg-kustomization.yaml", "gateway-kustomization.yaml", "external-dns-kustomization.yaml", "cloudflare-tunnel-kustomization.yaml", "monitoring-kustomization.yaml", "tekton-kustomization.yaml", "tekton-triggers-kustomization.yaml", "cicd-manifests-repo.yaml"} @@ -161,19 +166,7 @@ func (r Runner) Run() error { if err := os.WriteFile(filepath.Join(clusterDir, "democratic-csi-kustomization.yaml"), csiKustomization, 0644); err != nil { return err } - if err := copyDir(filepath.Join(cicdTemplateDir, "base", "cilium"), filepath.Join(dir, "base", "cilium"), true); err != nil { - return err - } - if err := copyDir(filepath.Join(cicdTemplateDir, "base", "cilium-config"), filepath.Join(dir, "base", "cilium-config"), true); err != nil { - return err - } - if err := copyDir(filepath.Join(cicdTemplateDir, "base", "democratic-csi"), filepath.Join(dir, "base", "democratic-csi"), true); err != nil { - return err - } - if err := renderCiliumConfig(filepath.Join(dir, "base", "cilium"), r.Config); err != nil { - return err - } - if err := renderCiliumConfig(filepath.Join(dir, "base", "cilium-config"), r.Config); err != nil { + if err := copyAndRenderCiliumBases(cicdTemplateDir, dir, r.Config); err != nil { return err } if err := copyAndRenderDeliveryBases(cicdTemplateDir, dir, r.Config); err != nil { @@ -336,6 +329,19 @@ func copyAndRenderDeliveryBases(templateDir, repoDir string, cfg config.Config) return nil } +func copyAndRenderCiliumBases(templateDir, repoDir string, cfg config.Config) error { + for _, base := range []string{"cilium", "cilium-config"} { + baseDir := filepath.Join(repoDir, "base", base) + if err := copyDir(filepath.Join(templateDir, "base", base), baseDir, true); err != nil { + return err + } + if err := renderCiliumConfig(baseDir, cfg); err != nil { + return err + } + } + return nil +} + func writeDemocraticCSISecret(path string, csi config.DemocraticCSIConfig, ageKeyPath string) error { plaintext, err := renderDemocraticCSISecret(csi) if err != nil { @@ -645,7 +651,7 @@ func ensureClusterKustomizations(clusterDir string) error { func copyTemplateBaseComponents(templateDir, repoDir string) error { for _, component := range templateBaseComponents { - if err := copyDir(filepath.Join(templateDir, "base", component), filepath.Join(repoDir, "base", component), true); err != nil { + if err := copyDirExcept(filepath.Join(templateDir, "base", component), filepath.Join(repoDir, "base", component), true, generatedTemplateFiles[component]); err != nil { return err } } diff --git a/internal/bootstrap/bootstrap_test.go b/internal/bootstrap/bootstrap_test.go index c859d8b..af78a3e 100644 --- a/internal/bootstrap/bootstrap_test.go +++ b/internal/bootstrap/bootstrap_test.go @@ -107,7 +107,7 @@ func TestCopyAndRenderDeliveryBasesOverwritesExistingMigrationOutput(t *testing. } } -func TestCopyTemplateBaseComponentsCopiesExternalSecretsConfig(t *testing.T) { +func TestCopyTemplateBaseComponentsRefreshesCNPGAndPreservesGeneratedSecrets(t *testing.T) { templateDir := t.TempDir() repoDir := t.TempDir() for _, component := range templateBaseComponents { @@ -115,24 +115,96 @@ func TestCopyTemplateBaseComponentsCopiesExternalSecretsConfig(t *testing.T) { t.Fatal(err) } } - source := filepath.Join(templateDir, "base", "external-secrets", "config.yaml") - destination := filepath.Join(repoDir, "base", "external-secrets", "config.yaml") - if err := os.WriteFile(source, []byte("store: openbao\n"), 0644); err != nil { + cnpgSource := filepath.Join(templateDir, "base", "cnpg", "kustomization.yaml") + cnpgDestination := filepath.Join(repoDir, "base", "cnpg", "kustomization.yaml") + if err := os.WriteFile(cnpgSource, []byte("resources:\n - operator-kustomization.yaml\n - infrastructure-kustomization.yaml\n"), 0644); err != nil { t.Fatal(err) } - if err := os.MkdirAll(filepath.Dir(destination), 0755); err != nil { + if err := os.MkdirAll(filepath.Dir(cnpgDestination), 0755); err != nil { t.Fatal(err) } - if err := os.WriteFile(destination, []byte("store: stale\n"), 0644); err != nil { + if err := os.WriteFile(cnpgDestination, []byte("resources:\n - release.yaml\n - infrastructure-postgres.yaml\n"), 0644); err != nil { t.Fatal(err) } + operatorSource := filepath.Join(templateDir, "base", "cnpg", "operator", "kustomization.yaml") + if err := os.MkdirAll(filepath.Dir(operatorSource), 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(operatorSource, []byte("resources:\n - release.yaml\n"), 0644); err != nil { + t.Fatal(err) + } + for _, file := range []string{"democratic-csi/secret.sops.yaml", "openbao/unseal.sops.yaml"} { + if err := os.WriteFile(filepath.Join(templateDir, "base", file), []byte("sops: template\n"), 0600); err != nil { + t.Fatal(err) + } + destination := filepath.Join(repoDir, "base", file) + if err := os.MkdirAll(filepath.Dir(destination), 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(destination, []byte("sops: generated\n"), 0600); err != nil { + t.Fatal(err) + } + } if err := copyTemplateBaseComponents(templateDir, repoDir); err != nil { t.Fatal(err) } - content, err := os.ReadFile(destination) - if err != nil || string(content) != "store: openbao\n" { - t.Fatalf("external-secrets configuration was not copied: %q, %v", content, err) + content, err := os.ReadFile(cnpgDestination) + if err != nil || string(content) != "resources:\n - operator-kustomization.yaml\n - infrastructure-kustomization.yaml\n" { + t.Fatalf("CNPG Kustomization was not refreshed: %q, %v", content, err) + } + content, err = os.ReadFile(filepath.Join(repoDir, "base", "cnpg", "operator", "kustomization.yaml")) + if err != nil || string(content) != "resources:\n - release.yaml\n" { + t.Fatalf("CNPG operator child was not copied: %q, %v", content, err) + } + for _, file := range []string{"democratic-csi/secret.sops.yaml", "openbao/unseal.sops.yaml"} { + content, err := os.ReadFile(filepath.Join(repoDir, "base", file)) + if err != nil || string(content) != "sops: generated\n" { + t.Fatalf("generated SOPS file %q was overwritten: %q, %v", file, content, err) + } + } +} + +func TestCopyAndRenderCiliumBasesRefreshesTemplateWithoutLeavingPlaceholders(t *testing.T) { + templateDir := t.TempDir() + repoDir := t.TempDir() + files := map[string]string{ + "cilium/release.yaml": "generation: current\n", + "cilium-config/load-balancer-pool.yaml": "start: ${CILIUM_LB_START}\nstop: ${CILIUM_LB_END}\n", + "cilium-config/l2-policy.yaml": "interface: ${CILIUM_TRAFFIC_INTERFACE}\n", + } + for name, content := range files { + path := filepath.Join(templateDir, "base", name) + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(path, []byte(content), 0644); err != nil { + t.Fatal(err) + } + } + for _, base := range []string{"cilium", "cilium-config"} { + if err := os.MkdirAll(filepath.Join(repoDir, "base", base), 0755); err != nil { + t.Fatal(err) + } + } + stale := filepath.Join(repoDir, "base", "cilium-config", "load-balancer-pool.yaml") + if err := os.WriteFile(stale, []byte("start: stale\n"), 0644); err != nil { + t.Fatal(err) + } + + cfg := config.Config{Cilium: config.CiliumConfig{TrafficInterface: "eth1", LoadBalancerStart: "192.168.45.19", LoadBalancerEnd: "192.168.45.30"}} + if err := copyAndRenderCiliumBases(templateDir, repoDir, cfg); err != nil { + t.Fatal(err) + } + for name, want := range map[string]string{ + "cilium/release.yaml": "generation: current\n", + "cilium-config/load-balancer-pool.yaml": "start: 192.168.45.19\nstop: 192.168.45.30\n", + "cilium-config/l2-policy.yaml": "interface: eth1\n", + } { + content, err := os.ReadFile(filepath.Join(repoDir, "base", name)) + if err != nil || string(content) != want { + t.Fatalf("Cilium file %q was not refreshed and rendered: %q, %v", name, content, err) + } } } @@ -418,6 +490,42 @@ func TestCopyDirSkipsGitDirectory(t *testing.T) { } } +func TestCopyDirPreservesExistingDeploymentStateWithoutOverwrite(t *testing.T) { + source := t.TempDir() + destination := t.TempDir() + for path, content := range map[string]string{ + "apps/production/app.yaml": "template: current\n", + "apps/staging/app.yaml": "template: new\n", + } { + file := filepath.Join(source, path) + if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(file, []byte(content), 0644); err != nil { + t.Fatal(err) + } + } + existing := filepath.Join(destination, "apps", "production", "app.yaml") + if err := os.MkdirAll(filepath.Dir(existing), 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(existing, []byte("deployment: existing\n"), 0644); err != nil { + t.Fatal(err) + } + + if err := copyDir(source, destination, false); err != nil { + t.Fatal(err) + } + content, err := os.ReadFile(existing) + if err != nil || string(content) != "deployment: existing\n" { + t.Fatalf("existing deployment state was overwritten: %q, %v", content, err) + } + content, err = os.ReadFile(filepath.Join(destination, "apps", "staging", "app.yaml")) + if err != nil || string(content) != "template: new\n" { + t.Fatalf("new template file was not copied: %q, %v", content, err) + } +} + func TestEnsureManifestsKustomizations(t *testing.T) { dir := t.TempDir() for _, environment := range []string{"previews", "staging", "production"} {