package terraform import ( "context" "fmt" "os" "path/filepath" "github.com/urfave/cli/v3" "github.com/openstatusHQ/cli/internal/api" "github.com/openstatusHQ/cli/internal/auth" output "github.com/openstatusHQ/cli/internal/cli" ) var generatedFileNames = []string{ "provider.tf", "monitors.tf", "notifications.tf", "status_pages.tf", "private_locations.tf", "imports.tf", } func GetTerraformGenerateCmd() *cli.Command { return &cli.Command{ Name: "generate", Aliases: []string{"gen"}, Usage: "Generate Terraform configuration from workspace resources", UsageText: "openstatus terraform generate [--output-dir ./openstatus-terraform/]", Flags: []cli.Flag{ &cli.StringFlag{ Name: "access-token", Usage: "OpenStatus API Access Token", Aliases: []string{"t"}, Sources: cli.EnvVars("OPENSTATUS_API_TOKEN"), }, &cli.StringFlag{ Name: "output-dir", Usage: "Directory to write Terraform files into", Value: "./openstatus-terraform/", Aliases: []string{"o"}, }, &cli.BoolFlag{ Name: "force", Usage: "Overwrite existing files in --output-dir", Aliases: []string{"f"}, }, }, Action: func(ctx context.Context, cmd *cli.Command) error { apiKey, err := auth.ResolveAccessToken(cmd) if err != nil { return cli.Exit(err.Error(), 1) } outputDir := cmd.String("output-dir") if err := checkExistingFiles(outputDir, cmd.Bool("force")); err != nil { return cli.Exit(err.Error(), 1) } if err := os.MkdirAll(outputDir, 0o755); err != nil { return cli.Exit(fmt.Sprintf("failed to create output directory: %v", err), 1) } s := output.StartSpinner("Fetching workspace resources...") data, err := FetchWorkspaceData(ctx, apiKey) output.StopSpinner(s) if err != nil { return cli.Exit(err.Error(), 1) } gen := NewGenerator(data) // Always write provider.tf if err := writeFile(filepath.Join(outputDir, "provider.tf"), GenerateProviderFile(api.BaseURL)); err != nil { return cli.Exit(fmt.Sprintf("failed to write provider.tf: %v", err), 1) } if gen.TotalResourceCount() == 0 { fmt.Printf("No resources found in workspace. Only provider.tf was generated in %s\n", outputDir) return nil } if gen.HasMonitors() { if err := writeFile(filepath.Join(outputDir, "monitors.tf"), gen.GenerateMonitorsFile().Bytes()); err != nil { return cli.Exit(fmt.Sprintf("failed to write monitors.tf: %v", err), 1) } } if gen.HasNotifications() { if err := writeFile(filepath.Join(outputDir, "notifications.tf"), gen.GenerateNotificationsFile().Bytes()); err != nil { return cli.Exit(fmt.Sprintf("failed to write notifications.tf: %v", err), 1) } } if gen.HasStatusPages() { if err := writeFile(filepath.Join(outputDir, "status_pages.tf"), gen.GenerateStatusPagesFile().Bytes()); err != nil { return cli.Exit(fmt.Sprintf("failed to write status_pages.tf: %v", err), 1) } } if gen.HasPrivateLocations() { if err := writeFile(filepath.Join(outputDir, "private_locations.tf"), gen.GeneratePrivateLocationsFile().Bytes()); err != nil { return cli.Exit(fmt.Sprintf("failed to write private_locations.tf: %v", err), 1) } } if err := writeFile(filepath.Join(outputDir, "imports.tf"), gen.GenerateImportsFile().Bytes()); err != nil { return cli.Exit(fmt.Sprintf("failed to write imports.tf: %v", err), 1) } printSummary(outputDir, data) return nil }, } } func checkExistingFiles(outputDir string, force bool) error { if force { return nil } for _, name := range generatedFileNames { if _, err := os.Stat(filepath.Join(outputDir, name)); err == nil { return fmt.Errorf("refusing to overwrite existing file %s; pass --force to replace", name) } } return nil } func writeFile(path string, content []byte) error { f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644) if err != nil { return err } defer f.Close() _, err = f.Write(content) return err } func printSummary(outputDir string, data *WorkspaceData) { httpCount := len(data.HTTPMonitors) tcpCount := len(data.TCPMonitors) dnsCount := len(data.DNSMonitors) icmpCount := len(data.ICMPMonitors) monitorTotal := httpCount + tcpCount + dnsCount + icmpCount notifCount := len(data.Notifications) plCount := len(data.PrivateLocations) pageCount := len(data.StatusPages) compCount := 0 groupCount := 0 for _, sp := range data.StatusPages { compCount += len(sp.Components) groupCount += len(sp.Groups) } importCount := monitorTotal + notifCount + pageCount + compCount + groupCount + plCount fmt.Printf("\nGenerated Terraform configuration in %s\n\n", outputDir) if monitorTotal > 0 { fmt.Printf(" %d monitors (%d HTTP, %d TCP, %d DNS, %d ICMP)\n", monitorTotal, httpCount, tcpCount, dnsCount, icmpCount) } if notifCount > 0 { fmt.Printf(" %d notifications\n", notifCount) } if pageCount > 0 { fmt.Printf(" %d status pages (%d components, %d groups)\n", pageCount, compCount, groupCount) } if plCount > 0 { fmt.Printf(" %d private locations\n", plCount) } fmt.Printf(" %d import blocks\n", importCount) fmt.Printf("\nNext steps:\n") fmt.Printf(" cd %s\n", outputDir) fmt.Printf(" terraform init\n") fmt.Printf(" terraform plan\n") fmt.Printf("\nNote: provider version pinned to ~> 0.4. Run 'terraform init -upgrade' if you previously ran this command.\n") }