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