package cmd import ( "fmt" "os" "path/filepath" "strings" "kforge/internal/config" "kforge/internal/generator" "github.com/spf13/cobra" ) var ( generateEnvs []string generateOutput string generateDry bool ) var generateCmd = &cobra.Command{ Use: "generate", Short: "Generate Kubernetes manifests from kforge.yml", Long: `Reads kforge.yml (or the file specified with --config), resolves each requested environment, and writes flat Kubernetes manifest files to the output directory. If no --env flags are given, manifests are generated for all environments defined in kforge.yml. Examples: kforge generate kforge generate --env staging kforge generate --env staging --env production kforge generate --env production --output .kube/ kforge generate --dry-run`, RunE: runGenerate, } func init() { generateCmd.Flags().StringArrayVarP(&generateEnvs, "env", "e", nil, "Environment(s) to generate (default: all)") generateCmd.Flags().StringVarP(&generateOutput, "output", "o", ".kforge-out", "Directory to write generated manifests into") generateCmd.Flags().BoolVar(&generateDry, "dry-run", false, "Print manifests to stdout instead of writing files") rootCmd.AddCommand(generateCmd) } func runGenerate(cmd *cobra.Command, args []string) error { cfg, err := loadConfig() if err != nil { return err } envKeys := generateEnvs if len(envKeys) == 0 { envKeys = config.EnvironmentKeys(cfg) } if !generateDry { if err := os.MkdirAll(generateOutput, 0o755); err != nil { return fmt.Errorf("creating output directory: %w", err) } } for _, envKey := range envKeys { if err := generateForEnv(cfg, envKey); err != nil { return fmt.Errorf("env %q: %w", envKey, err) } } return nil } func generateForEnv(cfg *config.KforgeConfig, envKey string) error { env, err := config.ResolveEnvironment(cfg, envKey) if err != nil { return err } // Core manifests (Service, Deployment, Ingress, Certs). coreYAML, err := generator.GenerateAll(&env, cfg) if err != nil { return fmt.Errorf("generating core manifests: %w", err) } // Infrastructure manifests. infraManifests, err := generator.GenerateInfrastructure(&env) if err != nil { return fmt.Errorf("generating infrastructure manifests: %w", err) } // Collect all infrastructure env vars and append to deployment. // We re-generate core manifests after injecting infra env vars. var infraEnvVars []config.EnvVarConfig for _, m := range infraManifests { infraEnvVars = append(infraEnvVars, m.EnvVars...) } if len(infraEnvVars) > 0 { env.EnvVars = config.MergeEnvVars(env.EnvVars, infraEnvVars) coreYAML, err = generator.GenerateAll(&env, cfg) if err != nil { return fmt.Errorf("re-generating core manifests with infra vars: %w", err) } } if generateDry { printDryRun(envKey, "core", coreYAML) for _, m := range infraManifests { printDryRun(envKey, m.Name, m.Content) } return nil } // Write core manifest. coreFile := filepath.Join(generateOutput, envKey+"-core.yaml") if err := os.WriteFile(coreFile, []byte(coreYAML), 0o644); err != nil { return fmt.Errorf("writing core manifest: %w", err) } fmt.Printf(" ✓ %s\n", coreFile) // Write infra manifests. for _, m := range infraManifests { infraFile := filepath.Join(generateOutput, envKey+"-infra-"+m.Name+".yaml") if err := os.WriteFile(infraFile, []byte(m.Content), 0o644); err != nil { return fmt.Errorf("writing %s manifest: %w", m.Name, err) } fmt.Printf(" ✓ %s\n", infraFile) } return nil } func printDryRun(envKey, name, content string) { fmt.Printf("\n%s\n# --- %s / %s ---\n%s\n", generator.Separator, strings.ToUpper(envKey), name, content, ) }