From 80e0c4edf93e94128b83b7a3111d918fbad35e16 Mon Sep 17 00:00:00 2001 From: Jayson Grace Date: Wed, 22 Apr 2026 12:44:44 -0600 Subject: [PATCH] feat: add lab config materialization and refactor infra log path generation Materialize merged overlay configs to the path terragrunt HCL expects (ad/GOAD/data/{env}-config.json) before running any infra action. This fixes terragrunt failing with 'no file exists' when only an overlay file is present. Extract infraLogPath helper to reduce cyclomatic complexity of runInfraAction. Add gitignore pattern for generated merged lab configs. --- .gitignore | 3 +++ cli/cmd/infra_cmd.go | 57 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 6a1ce97b..b1367c83 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,9 @@ ansible/roles/vulns_adcs_templates/files/ADCSTemplate.zip /*-inventory /*-inventory.bak.* +# Generated merged lab configs (base + overlay) +ad/GOAD/data/*-config.json + # Scenario data (keep only tracked environments) ad/PURPLE ad/REDLAB diff --git a/cli/cmd/infra_cmd.go b/cli/cmd/infra_cmd.go index e984db3f..877d24bc 100644 --- a/cli/cmd/infra_cmd.go +++ b/cli/cmd/infra_cmd.go @@ -83,6 +83,31 @@ func init() { infraCmd.PersistentFlags().StringP("deployment", "d", "", "Deployment name (default: from config)") } +// materializeLabConfig ensures the merged lab config JSON exists at the path +// terragrunt HCL expects (ad/GOAD/data/{env}-config.json). When an overlay +// file exists, the base config.json is merged with the overlay and written +// to disk so that terragrunt's file() function can read it directly. +func materializeLabConfig(cfg *config.Config) error { + resolved, err := cfg.ResolvedLabConfigPath() + if err != nil { + return nil // no config to materialize — let terragrunt surface the error + } + + dataDir := filepath.Join(cfg.ProjectRoot, "ad", "GOAD", "data") + expected := filepath.Join(dataDir, cfg.Env+"-config.json") + + if resolved == expected { + return nil // already in the right place (legacy layout) + } + + // Read the resolved (merged) config and write it where terragrunt expects. + data, err := os.ReadFile(resolved) + if err != nil { + return fmt.Errorf("read resolved config: %w", err) + } + return os.WriteFile(expected, data, 0o644) +} + func runInfraAction(action string) func(*cobra.Command, []string) error { return func(cmd *cobra.Command, args []string) error { cfg, err := config.Get() @@ -90,6 +115,10 @@ func runInfraAction(action string) func(*cobra.Command, []string) error { return err } + if err := materializeLabConfig(cfg); err != nil { + return fmt.Errorf("materialize lab config: %w", err) + } + module, _ := cmd.Flags().GetString("module") exclude, _ := cmd.Flags().GetString("exclude") deployment := resolveDeployment(cmd, cfg) @@ -120,18 +149,7 @@ func runInfraAction(action string) func(*cobra.Command, []string) error { return fmt.Errorf("infra working directory not found: %s\nRun 'dreadgoad infra validate' to check your setup", workDir) } - logDir := cfg.LogDir - if logDir == "" { - home, _ := os.UserHomeDir() - logDir = filepath.Join(home, ".ansible", "logs", "goad") - } - timestamp := time.Now().Format("20060102_150405") - moduleSlug := "all" - if module != "" { - moduleSlug = strings.ReplaceAll(module, "/", "_") - } - opts.LogFile = filepath.Join(logDir, fmt.Sprintf("infra_%s_%s_%s_%s_%s.log", - action, deployment, cfg.Env, moduleSlug, timestamp)) + opts.LogFile = infraLogPath(cfg, action, deployment, module) fmt.Printf("Infra %s (%s/%s)\n", action, cfg.Env, region) if module != "" { @@ -234,6 +252,21 @@ func runInfraValidate(cmd *cobra.Command, args []string) error { return nil } +func infraLogPath(cfg *config.Config, action, deployment, module string) string { + logDir := cfg.LogDir + if logDir == "" { + home, _ := os.UserHomeDir() + logDir = filepath.Join(home, ".ansible", "logs", "goad") + } + timestamp := time.Now().Format("20060102_150405") + moduleSlug := "all" + if module != "" { + moduleSlug = strings.ReplaceAll(module, "/", "_") + } + return filepath.Join(logDir, fmt.Sprintf("infra_%s_%s_%s_%s_%s.log", + action, deployment, cfg.Env, moduleSlug, timestamp)) +} + func resolveDeployment(cmd *cobra.Command, cfg *config.Config) string { if d, _ := cmd.Flags().GetString("deployment"); d != "" { return d