Compare commits

...

2 commits

6 changed files with 355 additions and 17 deletions

View file

@ -13,7 +13,8 @@ var freshConfigPath, freshOrganization, onboardConfigPath, onboardFrom string
var freshCreateOrganization, freshEnableDelivery, freshYes bool
var freshMode string
var loadFreshConfig = config.LoadRaw
var loadFreshConfig = config.Load
var loadAppOnboardConfig = config.LoadRaw
var runFreshOrganization = bootstrap.RunFreshOrganization
var resolveAppOnboarding = config.ResolveAppOnboarding
var ensureOnboardCheckoutClean = forgejo.EnsureCleanCheckout
@ -73,7 +74,7 @@ func runBootstrapInit(cmd *cobra.Command, _ []string) error {
}
func runAppOnboard(_ *cobra.Command, _ []string) error {
cfg, err := loadFreshConfig(onboardConfigPath)
cfg, err := loadAppOnboardConfig(onboardConfigPath)
if err != nil {
return err
}

View file

@ -2,21 +2,76 @@ package cmd
import (
"errors"
"io"
"os"
"path/filepath"
"testing"
"github.com/Pingu-Studio/MaidnCLI/internal/bootstrap"
"github.com/Pingu-Studio/MaidnCLI/internal/config"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
)
func TestBootstrapInitAppliesFluxDefaultsBeforeFreshValidation(t *testing.T) {
originalRun := runFreshOrganization
originalConfigPath, originalOrganization := freshConfigPath, freshOrganization
originalCreate, originalDelivery, originalMode, originalYes := freshCreateOrganization, freshEnableDelivery, freshMode, freshYes
t.Cleanup(func() {
runFreshOrganization = originalRun
freshConfigPath, freshOrganization = originalConfigPath, originalOrganization
freshCreateOrganization, freshEnableDelivery, freshMode, freshYes = originalCreate, originalDelivery, originalMode, originalYes
})
workspace := t.TempDir()
cfg := config.Config{
ClusterID: "test-cluster",
WorkspaceDir: workspace,
Git: config.GitConfig{Provider: "forgejo", BaseURL: "https://git.example.test", Username: "bot", Token: "test-token", Owner: "new-org", CloneParent: filepath.Join(workspace, "checkouts")},
Flux: config.FluxConfig{RepoName: "cluster", ClusterDomain: "example.test"},
Talos: config.TalosConfig{
Proxmox: config.TalosProxmoxConfig{APIURL: "https://proxmox.example.test:8006", APITokenID: "id", APITokenSecret: "test-secret"},
Cluster: config.TalosClusterConfig{Name: "test-cluster", Domain: "example.test"},
Image: config.TalosImageConfig{TalosVersion: "v1.13.6", SchematicID: "abcdefghijkl"},
Nodes: []config.TalosNode{{Name: "cp-01", VMID: 100, Role: "controlplane", Networks: []config.TalosNetwork{{IP: "192.168.45.3", CIDR: "192.168.45.0/28", Gateway: "192.168.45.1", VLANID: 45}, {IP: "192.168.45.18", CIDR: "192.168.45.16/28", VLANID: 451}}}},
},
Cilium: config.CiliumConfig{LoadBalancerStart: "192.168.45.19", LoadBalancerEnd: "192.168.45.30"},
DemocraticCSI: config.DemocraticCSIConfig{TrueNASAPIKey: "test-key", TrueNASHost: "truenas.example.test", TargetPortal: "truenas.example.test:3260", ShareHost: "truenas.example.test", DatasetParentNFS: "pool/kubernetes/nfs/v", DatasetSnapshotsNFS: "pool/kubernetes/nfs/s", AllowedNetworks: "192.168.45.0/24", NameSuffix: "-test"},
}
data, err := yaml.Marshal(cfg)
if err != nil {
t.Fatal(err)
}
path := filepath.Join(t.TempDir(), "config.yaml")
if err := os.WriteFile(path, data, 0600); err != nil {
t.Fatal(err)
}
runFreshOrganization = func(got config.Config, options bootstrap.FreshOrganizationOptions) (bootstrap.FreshOrganizationPlan, error) {
if got.Flux.Branch != "main" || got.Flux.ClusterPath != "./clusters/maidn-cd-0" || got.Flux.ManifestsRepo != "cicd-deployment-manifests" || got.Flux.TektonCatalogRepo != "tekton-pipelines" {
t.Fatalf("fresh init Flux defaults = %#v", got.Flux)
}
_, plan, err := bootstrap.PlanFreshOrganization(got, options)
return plan, err
}
freshConfigPath, freshOrganization = path, "new-org"
freshCreateOrganization, freshEnableDelivery, freshMode, freshYes = true, false, string(bootstrap.Reconcile), false
command := &cobra.Command{}
command.SetOut(io.Discard)
if err := runBootstrapInit(command, nil); err != nil {
t.Fatal(err)
}
}
func TestAppOnboardValidatesConfigBeforeInspectingCheckout(t *testing.T) {
originalConfig, originalResolve, originalClean := loadFreshConfig, resolveAppOnboarding, ensureOnboardCheckoutClean
originalConfig, originalResolve, originalClean := loadAppOnboardConfig, resolveAppOnboarding, ensureOnboardCheckoutClean
originalConfigPath, originalFrom := onboardConfigPath, onboardFrom
t.Cleanup(func() {
loadFreshConfig = originalConfig
loadAppOnboardConfig = originalConfig
resolveAppOnboarding = originalResolve
ensureOnboardCheckoutClean = originalClean
onboardConfigPath, onboardFrom = originalConfigPath, originalFrom
})
loadFreshConfig = func(string) (config.Config, error) { return config.Config{}, nil }
loadAppOnboardConfig = func(string) (config.Config, error) { return config.Config{}, nil }
resolveAppOnboarding = func(config.Config) (config.Config, error) { return config.Config{}, errors.New("incomplete delivery") }
ensureOnboardCheckoutClean = func(string) error {
t.Fatal("onboarding inspected checkout before validating config")
@ -29,16 +84,16 @@ func TestAppOnboardValidatesConfigBeforeInspectingCheckout(t *testing.T) {
}
func TestAppOnboardScaffoldsOnlyTheValidatedCheckout(t *testing.T) {
originalConfig, originalResolve, originalClean := loadFreshConfig, resolveAppOnboarding, ensureOnboardCheckoutClean
originalConfig, originalResolve, originalClean := loadAppOnboardConfig, resolveAppOnboarding, ensureOnboardCheckoutClean
originalOrigin, originalBranch, originalGenerate := onboardCheckoutOrigin, onboardCheckoutBranch, generateAppDelivery
originalConfigPath, originalFrom := onboardConfigPath, onboardFrom
t.Cleanup(func() {
loadFreshConfig, resolveAppOnboarding, ensureOnboardCheckoutClean = originalConfig, originalResolve, originalClean
loadAppOnboardConfig, resolveAppOnboarding, ensureOnboardCheckoutClean = originalConfig, originalResolve, originalClean
onboardCheckoutOrigin, onboardCheckoutBranch, generateAppDelivery = originalOrigin, originalBranch, originalGenerate
onboardConfigPath, onboardFrom = originalConfigPath, originalFrom
})
cfg := config.Config{Delivery: config.DeliveryConfig{AppRepoURL: "https://git.example.test/new-org/app.git", AppRepoRef: "main"}}
loadFreshConfig = func(string) (config.Config, error) { return cfg, nil }
loadAppOnboardConfig = func(string) (config.Config, error) { return cfg, nil }
resolveAppOnboarding = func(config.Config) (config.Config, error) { return cfg, nil }
ensureOnboardCheckoutClean = func(path string) error {
if path != "app-checkout" {

View file

@ -46,6 +46,7 @@ type Runner struct {
RegisterWebhook bool
EnableDelivery bool
SkipDeliveryScaffolding bool
AutoMergeBootstrapMigration bool
}
type operationalSecrets struct {
@ -270,8 +271,8 @@ func (r Runner) Run() error {
); err != nil {
return err
}
if manager.MigrationPending {
return errors.New("existing repository migration PR created; merge and rerun bootstrap before infrastructure changes")
if err := r.reconcileBootstrapMigration(manager); err != nil {
return err
}
if err := r.reconcileCloudflareTunnel(); err != nil {
return err
@ -331,6 +332,19 @@ func (r Runner) Run() error {
return nil
}
func (r Runner) reconcileBootstrapMigration(manager *forgejo.RepoManager) error {
if !manager.MigrationPending {
return nil
}
if !r.AutoMergeBootstrapMigration {
return errors.New("existing repository migration PR created; merge and rerun bootstrap before infrastructure changes")
}
if err := manager.MergePullRequest(r.Config.Flux.RepoName, manager.MigrationBranch); err != nil {
return fmt.Errorf("merge bootstrap migration PR: %w", err)
}
return nil
}
func resolveLifecycleMode(mode Mode, confirmRebuild bool) (Mode, error) {
if mode == "" {
mode = Reconcile
@ -1090,6 +1104,9 @@ func copyTemplateBaseComponents(templateDir, repoDir string, includeDelivery boo
}
func copyClusterTemplate(source, destination string) error {
if err := os.MkdirAll(destination, 0755); err != nil {
return err
}
entries, err := os.ReadDir(destination)
if err != nil && !os.IsNotExist(err) {
return err

View file

@ -749,6 +749,55 @@ func TestEnsureClusterKustomizationsUsesTemplateExternalSecretsResource(t *testi
}
}
func TestCopyClusterTemplateCopiesCurrentClusterContract(t *testing.T) {
templateDir := t.TempDir()
clusterTemplate := filepath.Join(templateDir, "clusters", "template")
resources := []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", "external-secrets-config-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",
}
if err := os.MkdirAll(clusterTemplate, 0755); err != nil {
t.Fatal(err)
}
var content strings.Builder
content.WriteString("apiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n")
for _, resource := range resources {
content.WriteString(" - " + resource + "\n")
if err := os.WriteFile(filepath.Join(clusterTemplate, resource), []byte("apiVersion: v1\nkind: ConfigMap\n"), 0644); err != nil {
t.Fatal(err)
}
}
if err := os.WriteFile(filepath.Join(clusterTemplate, "kustomization.yaml"), []byte(content.String()), 0644); err != nil {
t.Fatal(err)
}
clusterDir := filepath.Join(t.TempDir(), "clusters", "maidn-cd-0")
if err := copyClusterTemplate(clusterTemplate, clusterDir); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(clusterDir, "cicd-manifests-repo.yaml"), []byte("apiVersion: v1\nkind: ConfigMap\n"), 0644); err != nil {
t.Fatal(err)
}
if err := ensureClusterKustomizations(clusterDir, true); err != nil {
t.Fatal(err)
}
first, err := os.ReadFile(filepath.Join(clusterDir, "kustomization.yaml"))
if err != nil {
t.Fatal(err)
}
if err := ensureClusterKustomizations(clusterDir, true); err != nil {
t.Fatal(err)
}
second, err := os.ReadFile(filepath.Join(clusterDir, "kustomization.yaml"))
if err != nil || string(first) != string(second) {
t.Fatalf("cluster Kustomization was not generated idempotently: %q, %v", second, err)
}
for _, resource := range append(resources, "cicd-manifests-repo.yaml") {
if !strings.Contains(string(second), " - "+resource+"\n") {
t.Fatalf("cluster Kustomization omitted template resource %q: %q", resource, second)
}
}
}
func TestWebhookTargetTimeoutExceedsExternalSecretRefreshInterval(t *testing.T) {
if webhookTargetTimeout <= time.Hour {
t.Fatal("webhook target timeout must exceed the one-hour ExternalSecret refresh interval")
@ -812,6 +861,52 @@ func TestRunnerEnableDeliveryAppliesDefaults(t *testing.T) {
}
}
func TestRunnerAutoMergesBootstrapMigration(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
switch request.Method + " " + request.URL.Path {
case http.MethodGet + " /api/v1/repos/test-org/cluster/pulls":
if request.URL.Query().Get("state") != "open" || request.URL.Query().Get("head") != "maidn/bootstrap-test-cluster" {
t.Fatalf("unexpected migration lookup query: %q", request.URL.RawQuery)
}
_ = json.NewEncoder(writer).Encode([]struct {
Number int `json:"number"`
}{{Number: 4}})
case http.MethodPost + " /api/v1/repos/test-org/cluster/pulls/4/merge":
var body struct {
Do string `json:"Do"`
}
if err := json.NewDecoder(request.Body).Decode(&body); err != nil || body.Do != "merge" {
t.Fatalf("unexpected migration merge request: %#v, %v", body, err)
}
writer.WriteHeader(http.StatusOK)
default:
t.Fatalf("unexpected Forgejo request %s %s", request.Method, request.URL.Path)
}
}))
defer server.Close()
manager := forgejo.NewRepoManager(server.URL, "test-token", "test-org", "bot", "manifests", "cluster", "main", "maidn/bootstrap-test-cluster")
manager.HTTPClient = server.Client()
manager.MigrationPending = true
if err := (Runner{Config: config.Config{Flux: config.FluxConfig{RepoName: "cluster"}}, AutoMergeBootstrapMigration: true}).reconcileBootstrapMigration(manager); err != nil {
t.Fatal(err)
}
}
func TestRunnerRetainsMigrationApprovalGate(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
t.Fatal("normal bootstrap must not call Forgejo to merge a migration")
}))
defer server.Close()
manager := forgejo.NewRepoManager(server.URL, "test-token", "test-org", "bot", "manifests", "cluster", "main", "maidn/bootstrap-test-cluster")
manager.HTTPClient = server.Client()
manager.MigrationPending = true
if err := (Runner{Config: config.Config{Flux: config.FluxConfig{RepoName: "cluster"}}}).reconcileBootstrapMigration(manager); err == nil || !strings.Contains(err.Error(), "merge and rerun bootstrap") {
t.Fatalf("normal bootstrap migration gate = %v", err)
}
}
func TestRunnerRegisterWebhookSkipsTemplateRevisions(t *testing.T) {
originalPreflight := preflight
originalGit := runGit

View file

@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/Pingu-Studio/MaidnCLI/internal/config"
"github.com/Pingu-Studio/MaidnCLI/internal/forgejo"
@ -90,6 +91,7 @@ func freshLifecycleRunner(cfg config.Config, options FreshOrganizationOptions) R
ConfirmRebuild: options.ConfirmRebuild,
EnableDelivery: options.EnableDelivery,
SkipDeliveryScaffolding: !options.EnableDelivery,
AutoMergeBootstrapMigration: true,
}
}
@ -98,30 +100,147 @@ func validateFreshWorkspace(cfg config.Config) error {
if err != nil || filepath.Dir(cloneRelative) != "." {
return errors.New("git cloneParent must be a direct child of isolated workspaceDir")
}
entries, err := os.ReadDir(cfg.WorkspaceDir)
secretFiles, secretDirectories, err := freshWorkspaceSecretPaths(cfg, cloneRelative)
if err != nil {
return err
}
info, err := os.Lstat(cfg.WorkspaceDir)
if os.IsNotExist(err) {
return nil
}
if err != nil {
return fmt.Errorf("inspect workspaceDir: %w", err)
}
if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() {
return errors.New("workspaceDir must be an isolated directory")
}
entries, err := os.ReadDir(cfg.WorkspaceDir)
if err != nil {
return fmt.Errorf("inspect workspaceDir: %w", err)
}
if len(entries) == 0 {
return nil
}
lock, err := readTemplateRevisionLock(filepath.Join(cfg.WorkspaceDir, "maidn-template-revisions.yaml"))
lockPath := filepath.Join(cfg.WorkspaceDir, "maidn-template-revisions.yaml")
if !freshWorkspaceRegularFile(lockPath) {
return errors.New("workspaceDir contains ambiguous state; use a new empty isolated workspaceDir")
}
lock, err := readTemplateRevisionLock(lockPath)
if err != nil || !sameTemplateSource(lock.CICD, templateCheckout{Repository: cfg.Templates.CICDRepoURL, Ref: cfg.Templates.CICDRepoRef}) || !sameTemplateSource(lock.Manifests, templateCheckout{Repository: cfg.Templates.ManifestsRepoURL, Ref: cfg.Templates.ManifestsRepoRef}) || !sameTemplateSource(lock.Talos, templateCheckout{Repository: cfg.Templates.TalosRepoURL, Ref: cfg.Templates.TalosRepoRef}) {
return errors.New("workspaceDir contains ambiguous state; use a new empty isolated workspaceDir")
}
allowed := map[string]bool{
allowedFiles := map[string]bool{
"maidn-template-revisions.yaml": true,
"maidn-bootstrap.resolved.yaml": true,
}
allowedDirectories := map[string]bool{
"maidn-cicd-cluster-template": true,
"cicd-deployment-manifests-template": true,
cloneRelative: true,
}
for path := range secretFiles {
if filepath.Dir(path) == "." {
allowedFiles[path] = true
}
}
for path := range secretDirectories {
if filepath.Dir(path) == "." {
allowedDirectories[path] = true
}
}
for _, entry := range entries {
if !allowed[entry.Name()] {
if allowedFiles[entry.Name()] {
if !freshWorkspaceRegularFile(filepath.Join(cfg.WorkspaceDir, entry.Name())) {
return errors.New("workspaceDir contains ambiguous state; use a new empty isolated workspaceDir")
}
continue
}
if !allowedDirectories[entry.Name()] || !freshWorkspaceDirectory(filepath.Join(cfg.WorkspaceDir, entry.Name())) {
return errors.New("workspaceDir contains ambiguous state; use a new empty isolated workspaceDir")
}
}
return validateFreshWorkspaceSecretDirectories(cfg.WorkspaceDir, secretFiles, secretDirectories)
}
func freshWorkspaceSecretPaths(cfg config.Config, cloneRelative string) (map[string]bool, map[string]bool, error) {
files := map[string]bool{}
directories := map[string]bool{}
protected := map[string]bool{
"maidn-template-revisions.yaml": true,
"maidn-bootstrap.resolved.yaml": true,
"maidn-cicd-cluster-template": true,
"cicd-deployment-manifests-template": true,
cloneRelative: true,
}
for _, path := range []string{cfg.SOPS.AgeKeyPath, cfg.SOPS.BootstrapSecretsPath, cfg.SOPS.OperationalSecretsPath, cfg.SOPS.RecoveryIdentityPath, cfg.SOPS.RecoveryBundlePath} {
if path == "" || !filepath.IsAbs(path) {
continue
}
relative, err := filepath.Rel(cfg.WorkspaceDir, path)
if err != nil || relative == "." || relative == ".." || filepath.IsAbs(relative) || strings.HasPrefix(relative, ".."+string(filepath.Separator)) {
continue
}
parts := strings.Split(filepath.ToSlash(relative), "/")
if protected[parts[0]] {
return nil, nil, errors.New("SOPS and recovery paths must not use protected workspace paths")
}
files[relative] = true
for parent := filepath.Dir(relative); parent != "."; parent = filepath.Dir(parent) {
directories[parent] = true
}
}
for path := range files {
if directories[path] {
return nil, nil, errors.New("SOPS and recovery paths must not overlap")
}
}
return files, directories, nil
}
func validateFreshWorkspaceSecretDirectories(workspace string, files, directories map[string]bool) error {
for directory := range directories {
if directories[filepath.Dir(directory)] {
continue
}
if err := validateFreshWorkspaceSecretDirectory(workspace, directory, files, directories); err != nil {
return err
}
}
return nil
}
func validateFreshWorkspaceSecretDirectory(workspace, directory string, files, directories map[string]bool) error {
path := filepath.Join(workspace, directory)
entries, err := os.ReadDir(path)
if os.IsNotExist(err) {
return nil
}
if err != nil || !freshWorkspaceDirectory(path) {
return errors.New("workspaceDir contains ambiguous state; use a new empty isolated workspaceDir")
}
for _, entry := range entries {
relative := filepath.Join(directory, entry.Name())
path := filepath.Join(workspace, relative)
if files[relative] && freshWorkspaceRegularFile(path) {
continue
}
if directories[relative] && freshWorkspaceDirectory(path) {
if err := validateFreshWorkspaceSecretDirectory(workspace, relative, files, directories); err != nil {
return err
}
continue
}
return errors.New("workspaceDir contains ambiguous state; use a new empty isolated workspaceDir")
}
return nil
}
func freshWorkspaceRegularFile(path string) bool {
info, err := os.Lstat(path)
return err == nil && info.Mode()&os.ModeSymlink == 0 && info.Mode().IsRegular()
}
func freshWorkspaceDirectory(path string) bool {
info, err := os.Lstat(path)
return err == nil && info.Mode()&os.ModeSymlink == 0 && info.IsDir()
}

View file

@ -184,7 +184,7 @@ func TestFreshLifecycleUsesSelectedModeAndGatesDelivery(t *testing.T) {
{FreshOrganizationOptions{Mode: Rebuild, ConfirmRebuild: true, EnableDelivery: true}, Rebuild},
} {
runner := freshLifecycleRunner(cfg, test.options)
if runner.Mode != test.mode || runner.ConfirmRebuild != test.options.ConfirmRebuild || runner.EnableDelivery != test.options.EnableDelivery || runner.SkipDeliveryScaffolding == test.options.EnableDelivery {
if runner.Mode != test.mode || runner.ConfirmRebuild != test.options.ConfirmRebuild || runner.EnableDelivery != test.options.EnableDelivery || runner.SkipDeliveryScaffolding == test.options.EnableDelivery || !runner.AutoMergeBootstrapMigration {
t.Fatalf("fresh lifecycle runner = %#v", runner)
}
preflight = func(got config.Config) error {
@ -201,10 +201,61 @@ func TestFreshLifecycleUsesSelectedModeAndGatesDelivery(t *testing.T) {
func TestPlanFreshOrganizationRejectsUnrecognizedWorkspaceState(t *testing.T) {
cfg := freshPlanConfig(t)
if err := os.WriteFile(filepath.Join(cfg.WorkspaceDir, "leftover"), []byte("state"), 0600); err != nil {
prepareResumableFreshWorkspace(t, &cfg)
if err := os.WriteFile(filepath.Join(cfg.WorkspaceDir, ".age", "leftover"), []byte("state"), 0600); err != nil {
t.Fatal(err)
}
if _, _, err := PlanFreshOrganization(cfg, FreshOrganizationOptions{Organization: "new-org", CreateOrganization: true}); err == nil {
t.Fatal("ambiguous workspace state was accepted")
}
}
func TestPlanFreshOrganizationResumesKnownWorkspaceState(t *testing.T) {
cfg := freshPlanConfig(t)
prepareResumableFreshWorkspace(t, &cfg)
if _, _, err := PlanFreshOrganization(cfg, FreshOrganizationOptions{Organization: "new-org", CreateOrganization: true}); err != nil {
t.Fatalf("resumable workspace state was rejected: %v", err)
}
}
func prepareResumableFreshWorkspace(t *testing.T, cfg *config.Config) {
t.Helper()
cfg.SOPS = config.SOPSConfig{
AgeKeyPath: filepath.Join(cfg.WorkspaceDir, ".age", "key.txt"),
BootstrapSecretsPath: filepath.Join(cfg.WorkspaceDir, "bootstrap-secrets.sops.yaml"),
OperationalSecretsPath: filepath.Join(cfg.WorkspaceDir, "operational-secrets.sops.yaml"),
RecoveryIdentityPath: filepath.Join(cfg.WorkspaceDir, ".age", "recovery-key.txt"),
RecoveryBundlePath: filepath.Join(cfg.WorkspaceDir, ".recovery", "openbao-recovery.age"),
}
for _, directory := range []string{
filepath.Join(cfg.WorkspaceDir, "maidn-cicd-cluster-template"),
filepath.Join(cfg.WorkspaceDir, "cicd-deployment-manifests-template"),
cfg.Git.CloneParent,
filepath.Join(cfg.WorkspaceDir, ".age"),
filepath.Join(cfg.WorkspaceDir, ".recovery"),
} {
if err := os.MkdirAll(directory, 0700); err != nil {
t.Fatal(err)
}
}
if err := writeTemplateRevisionLock(filepath.Join(cfg.WorkspaceDir, "maidn-template-revisions.yaml"), templateRevisionLock{
Version: 1,
CICD: templateRevision{Repository: cfg.Templates.CICDRepoURL, Ref: cfg.Templates.CICDRepoRef, Commit: strings.Repeat("a", 40)},
Manifests: templateRevision{Repository: cfg.Templates.ManifestsRepoURL, Ref: cfg.Templates.ManifestsRepoRef, Commit: strings.Repeat("b", 40)},
Talos: templateRevision{Repository: cfg.Templates.TalosRepoURL, Ref: cfg.Templates.TalosRepoRef, Commit: strings.Repeat("c", 40)},
}); err != nil {
t.Fatal(err)
}
for _, path := range []string{
filepath.Join(cfg.WorkspaceDir, "maidn-bootstrap.resolved.yaml"),
cfg.SOPS.AgeKeyPath,
cfg.SOPS.BootstrapSecretsPath,
cfg.SOPS.OperationalSecretsPath,
cfg.SOPS.RecoveryIdentityPath,
cfg.SOPS.RecoveryBundlePath,
} {
if err := os.WriteFile(path, nil, 0600); err != nil {
t.Fatal(err)
}
}
}