status.go

112 lines
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
package commands

import (
	"flag"
	"fmt"
	"log"
	"os"
	"strings"
	"time"
)

const statusUsage = `Show deployment status

Usage:
  congo status [flags]

Flags:
  --server <name>  Show status for a specific instance

Shows running services and connectivity for all deployed instances.

Examples:
  congo status                  # all instances
  congo status --server web-1   # specific instance
`

func Status() {
	fs := flag.NewFlagSet("status", flag.ExitOnError)
	fs.Usage = func() { fmt.Print(statusUsage) }

	serverName := fs.String("server", "", "Show status for a specific instance")
	fs.Parse(os.Args[2:])

	cfg, err := loadInfraConfig(".")
	if err != nil {
		log.Fatalf("load infra.json: %v\n\nRun 'congo init <name>' to create a project first.", err)
	}

	all := cfg.AllInstances()

	if len(all) == 0 {
		fmt.Println("No instances deployed.")
		fmt.Println()
		if len(cfg.Servers) > 0 {
			fmt.Println("Server types defined in infra.json:")
			for name := range cfg.Servers {
				fmt.Printf("  %s\n", name)
			}
			fmt.Println()
			fmt.Println("Provision with: congo launch --new <server-type>")
		} else {
			fmt.Println("Add server types to infra.json, then run: congo launch --new <name>")
		}
		return
	}

	// Filter to a specific server if requested.
	if *serverName != "" {
		var filtered []Instance
		for _, inst := range all {
			if inst.Name == *serverName {
				filtered = append(filtered, inst)
			}
		}
		if len(filtered) == 0 {
			fmt.Fprintf(os.Stderr, "Instance %q not found. Available instances:\n", *serverName)
			for _, inst := range all {
				fmt.Fprintf(os.Stderr, "  %s (%s)\n", inst.Name, inst.IP)
			}
			os.Exit(1)
		}
		all = filtered
	}

	for i, inst := range all {
		if i > 0 {
			fmt.Println()
		}
		printInstanceStatus(inst)
	}
}

func printInstanceStatus(inst Instance) {
	server := inst.toServer()

	fmt.Printf("%s (%s)\n", inst.Name, inst.IP)

	// Check connectivity with a short timeout.
	out, err := server.SSHWithTimeout(5*time.Second, "docker", "ps", "--format", "table {{.Names}}\t{{.Status}}\t{{.Ports}}")
	if err != nil {
		fmt.Println("  Status: unreachable")
		if strings.Contains(err.Error(), "Connection refused") {
			fmt.Println("  Hint: server may be stopped or SSH is not running")
		} else if strings.Contains(err.Error(), "timed out") || strings.Contains(err.Error(), "deadline exceeded") {
			fmt.Println("  Hint: connection timed out — check firewall or server status")
		}
		return
	}

	fmt.Println("  Status: connected")

	lines := strings.Split(strings.TrimSpace(out), "\n")
	if len(lines) <= 1 {
		fmt.Println("  Services: none running")
		return
	}

	fmt.Println()
	for _, line := range lines {
		fmt.Printf("  %s\n", line)
	}
}