cli/internal/cmd/deploy/cmd_deploy.go
Snider e5e6908416 fix: address PR review comments from CodeRabbit, Copilot, and Gemini
Fixes across 25 files addressing 46+ review comments:

- pkg/ai/metrics.go: handle error from Close() on writable file handle
- pkg/ansible: restore loop vars after loop, restore become settings,
  fix Upload with become=true and no password (use sudo -n), honour
  SSH timeout config, use E() helper for contextual errors, quote git
  refs in checkout commands
- pkg/rag: validate chunk config, guard negative-to-uint64 conversion,
  use E() helper for errors, add context timeout to Ollama HTTP calls
- pkg/deploy/python: fix exec.ExitError type assertion (was os.PathError),
  handle os.UserHomeDir() error
- pkg/build/buildcmd: use cmd.Context() instead of context.Background()
  for proper Ctrl+C cancellation
- install.bat: add curl timeouts, CRLF line endings, use --connect-timeout
  for archive downloads
- install.sh: use absolute path for version check in CI mode
- tools/rag: fix broken ingest.py function def, escape HTML in query.py,
  pin qdrant-client version, add markdown code block languages
- internal/cmd/rag: add chunk size validation, env override handling

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 22:33:43 +00:00

280 lines
5.4 KiB
Go

package deploy
import (
"context"
"encoding/json"
"fmt"
"os"
"github.com/host-uk/core/pkg/cli"
"github.com/host-uk/core/pkg/deploy/coolify"
"github.com/host-uk/core/pkg/i18n"
"github.com/spf13/cobra"
)
var (
coolifyURL string
coolifyToken string
outputJSON bool
)
// Cmd is the root deploy command.
var Cmd = &cobra.Command{
Use: "deploy",
Short: i18n.T("cmd.deploy.short"),
Long: i18n.T("cmd.deploy.long"),
}
var serversCmd = &cobra.Command{
Use: "servers",
Short: "List Coolify servers",
RunE: runListServers,
}
var projectsCmd = &cobra.Command{
Use: "projects",
Short: "List Coolify projects",
RunE: runListProjects,
}
var appsCmd = &cobra.Command{
Use: "apps",
Short: "List Coolify applications",
RunE: runListApps,
}
var dbsCmd = &cobra.Command{
Use: "databases",
Short: "List Coolify databases",
Aliases: []string{"dbs", "db"},
RunE: runListDatabases,
}
var servicesCmd = &cobra.Command{
Use: "services",
Short: "List Coolify services",
RunE: runListServices,
}
var teamCmd = &cobra.Command{
Use: "team",
Short: "Show current team info",
RunE: runTeam,
}
var callCmd = &cobra.Command{
Use: "call <operation> [params-json]",
Short: "Call any Coolify API operation",
Args: cobra.RangeArgs(1, 2),
RunE: runCall,
}
func init() {
// Global flags
Cmd.PersistentFlags().StringVar(&coolifyURL, "url", os.Getenv("COOLIFY_URL"), "Coolify API URL")
Cmd.PersistentFlags().StringVar(&coolifyToken, "token", os.Getenv("COOLIFY_TOKEN"), "Coolify API token")
Cmd.PersistentFlags().BoolVar(&outputJSON, "json", false, "Output as JSON")
// Add subcommands
Cmd.AddCommand(serversCmd)
Cmd.AddCommand(projectsCmd)
Cmd.AddCommand(appsCmd)
Cmd.AddCommand(dbsCmd)
Cmd.AddCommand(servicesCmd)
Cmd.AddCommand(teamCmd)
Cmd.AddCommand(callCmd)
}
func getClient() (*coolify.Client, error) {
cfg := coolify.Config{
BaseURL: coolifyURL,
APIToken: coolifyToken,
Timeout: 30,
VerifySSL: true,
}
if cfg.BaseURL == "" {
cfg.BaseURL = os.Getenv("COOLIFY_URL")
}
if cfg.APIToken == "" {
cfg.APIToken = os.Getenv("COOLIFY_TOKEN")
}
return coolify.NewClient(cfg)
}
func outputResult(data any) error {
if outputJSON {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(data)
}
// Pretty print based on type
switch v := data.(type) {
case []map[string]any:
for _, item := range v {
printItem(item)
}
case map[string]any:
printItem(v)
default:
fmt.Printf("%v\n", data)
}
return nil
}
func printItem(item map[string]any) {
// Common fields to display
if uuid, ok := item["uuid"].(string); ok {
fmt.Printf("%s ", cli.DimStyle.Render(uuid[:8]))
}
if name, ok := item["name"].(string); ok {
fmt.Printf("%s", cli.TitleStyle.Render(name))
}
if desc, ok := item["description"].(string); ok && desc != "" {
fmt.Printf(" %s", cli.DimStyle.Render(desc))
}
if status, ok := item["status"].(string); ok {
switch status {
case "running":
fmt.Printf(" %s", cli.SuccessStyle.Render("●"))
case "stopped":
fmt.Printf(" %s", cli.ErrorStyle.Render("○"))
default:
fmt.Printf(" %s", cli.DimStyle.Render(status))
}
}
fmt.Println()
}
func runListServers(cmd *cobra.Command, args []string) error {
client, err := getClient()
if err != nil {
return err
}
servers, err := client.ListServers(context.Background())
if err != nil {
return err
}
if len(servers) == 0 {
fmt.Println("No servers found")
return nil
}
return outputResult(servers)
}
func runListProjects(cmd *cobra.Command, args []string) error {
client, err := getClient()
if err != nil {
return err
}
projects, err := client.ListProjects(context.Background())
if err != nil {
return err
}
if len(projects) == 0 {
fmt.Println("No projects found")
return nil
}
return outputResult(projects)
}
func runListApps(cmd *cobra.Command, args []string) error {
client, err := getClient()
if err != nil {
return err
}
apps, err := client.ListApplications(context.Background())
if err != nil {
return err
}
if len(apps) == 0 {
fmt.Println("No applications found")
return nil
}
return outputResult(apps)
}
func runListDatabases(cmd *cobra.Command, args []string) error {
client, err := getClient()
if err != nil {
return err
}
dbs, err := client.ListDatabases(context.Background())
if err != nil {
return err
}
if len(dbs) == 0 {
fmt.Println("No databases found")
return nil
}
return outputResult(dbs)
}
func runListServices(cmd *cobra.Command, args []string) error {
client, err := getClient()
if err != nil {
return err
}
services, err := client.ListServices(context.Background())
if err != nil {
return err
}
if len(services) == 0 {
fmt.Println("No services found")
return nil
}
return outputResult(services)
}
func runTeam(cmd *cobra.Command, args []string) error {
client, err := getClient()
if err != nil {
return err
}
team, err := client.GetTeam(context.Background())
if err != nil {
return err
}
return outputResult(team)
}
func runCall(cmd *cobra.Command, args []string) error {
client, err := getClient()
if err != nil {
return cli.WrapVerb(err, "initialize", "client")
}
operation := args[0]
var params map[string]any
if len(args) > 1 {
if err := json.Unmarshal([]byte(args[1]), &params); err != nil {
return fmt.Errorf("invalid JSON params: %w", err)
}
}
result, err := client.Call(context.Background(), operation, params)
if err != nil {
return err
}
return outputResult(result)
}