package cmd import ( "fmt" "os" "kforge/internal/config" dnsProvider "kforge/internal/dns" "kforge/internal/generator" "github.com/spf13/cobra" ) // ------------------------------------------------------------ // kforge dns ensure // ------------------------------------------------------------ var dnsCmd = &cobra.Command{ Use: "dns", Short: "Manage DNS records for kforge environments", } var dnsEnsureEnvs []string var dnsEnsureCmd = &cobra.Command{ Use: "ensure", Short: "Create or update DNS A records for ingress hosts", Long: `For each ingress host with dns_record: true, creates or updates an A record pointing to KFORGE_NODE_IP. Idempotent — safe to run on every deploy. Skips hosts where the record already points to the correct IP. Examples: kforge dns ensure --env staging kforge dns ensure --env staging --env production`, RunE: runDNSEnsure, } func init() { dnsEnsureCmd.Flags().StringArrayVarP(&dnsEnsureEnvs, "env", "e", nil, "Environment(s) to ensure DNS records for (default: all)") dnsCmd.AddCommand(dnsEnsureCmd) rootCmd.AddCommand(dnsCmd) } func runDNSEnsure(cmd *cobra.Command, args []string) error { cfg, err := loadConfig() if err != nil { return err } if cfg.DNS.SkipDNS { fmt.Println("dns.skip_dns is true — skipping DNS management") return nil } nodeIP := cfg.DNS.NodeIP if nodeIP == "" { nodeIP = os.Getenv("KFORGE_NODE_IP") } if nodeIP == "" { return fmt.Errorf("node IP not set: add dns.node_ip to kforge.yml or set KFORGE_NODE_IP") } provider, err := dnsProvider.NewProvider(cfg.DNS) if err != nil { return fmt.Errorf("initialising DNS provider: %w", err) } envKeys := dnsEnsureEnvs if len(envKeys) == 0 { envKeys = config.EnvironmentKeys(cfg) } for _, envKey := range envKeys { fmt.Printf("\nEnsuring DNS records for: %s\n", envKey) env, err := config.ResolveEnvironment(cfg, envKey) if err != nil { return err } if err := dnsProvider.EnsureRecordsForEnvironment(provider, &env, nodeIP); err != nil { return fmt.Errorf("env %q: %w", envKey, err) } } return nil } // ------------------------------------------------------------ // kforge gitea-actions // ------------------------------------------------------------ var ( giteaActionsOutput string giteaActionsBranch string giteaActionsEnvs []string ) var giteaActionsCmd = &cobra.Command{ Use: "gitea-actions", Short: "Generate a Gitea Actions workflow for this app", Long: `Generates a complete .gitea/workflows/deploy.yml that: - Builds and pushes the Docker image on every push to main - Runs kforge validate - Applies cluster secrets (idempotent) - Ensures DNS records - Generates manifests and applies them with kubectl - Rolls out the deployment The generated workflow replaces your hand-written deploy.yml. Re-run whenever you add environments or change deploy options. Examples: kforge gitea-actions kforge gitea-actions --output .gitea/workflows/deploy.yml kforge gitea-actions --env staging --env production`, RunE: runGiteaActions, } func init() { giteaActionsCmd.Flags().StringVarP(&giteaActionsOutput, "output", "o", ".gitea/workflows/deploy.yml", "Output path for the generated workflow file") giteaActionsCmd.Flags().StringVar(&giteaActionsBranch, "branch", "main", "Branch that triggers the workflow") giteaActionsCmd.Flags().StringArrayVarP(&giteaActionsEnvs, "env", "e", nil, "Environments to deploy (default: all, staging before production)") rootCmd.AddCommand(giteaActionsCmd) } func runGiteaActions(cmd *cobra.Command, args []string) error { cfg, err := loadConfig() if err != nil { return err } workflow, err := generator.GenerateGiteaActions(cfg, generator.GiteaActionsOptions{ Branch: giteaActionsBranch, Environments: giteaActionsEnvs, }) if err != nil { return fmt.Errorf("generating workflow: %w", err) } if giteaActionsOutput == "-" { fmt.Print(workflow) return nil } // Ensure parent directory exists. dir := giteaActionsOutput for i := len(dir) - 1; i >= 0; i-- { if dir[i] == '/' { dir = dir[:i] break } } if dir != giteaActionsOutput { if err := os.MkdirAll(dir, 0o755); err != nil { return fmt.Errorf("creating output directory: %w", err) } } if err := os.WriteFile(giteaActionsOutput, []byte(workflow), 0o644); err != nil { return fmt.Errorf("writing workflow: %w", err) } fmt.Printf("✓ Gitea Actions workflow written to %s\n", giteaActionsOutput) return nil }