package cli import ( "context" "database/sql" "fmt" "log/slog" "os" "path/filepath" "time" "github.com/google/uuid" "github.com/spf13/cobra" "git.cloudinit.dev/coreci/orca/internal/engine" "git.cloudinit.dev/coreci/orca/internal/model" "git.cloudinit.dev/coreci/orca/internal/store" ) func dbPath() string { if p := os.Getenv("ORCA_DB"); p != "" { return p } home, _ := os.UserHomeDir() return filepath.Join(home, ".orca", "orca.db") } func openDB() (*sql.DB, func() error, error) { db, err := store.Open(dbPath()) if err != nil { return nil, nil, err } return db, db.Close, nil } func newLogger() *slog.Logger { return slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelInfo})) } func nodeRegistry() (*engine.NodeRegistry, func() error, error) { db, closer, err := openDB() if err != nil { return nil, nil, err } repo := store.NewNodeRepo(db) return engine.NewNodeRegistry(repo, newLogger()), closer, nil } var ( joinName string joinAddr string leaveID string ) var nodeCmd = &cobra.Command{ Use: "node", Short: "Manage orca nodes", Long: "Join, leave, or list orca nodes in the registry.", } var nodeJoinCmd = &cobra.Command{ Use: "join", Short: "Join a node to the orca registry", Long: "Register a node in the local orca registry. Persisted to SQLite.", RunE: func(cmd *cobra.Command, args []string) error { if joinName == "" { return fmt.Errorf("--name is required") } if joinAddr == "" { joinAddr = "localhost:8443" } ctx, cancel := context.WithTimeout(cmd.Context(), 5*time.Second) defer cancel() registry, closer, err := nodeRegistry() if err != nil { return err } defer closer() node := &model.Node{ ID: uuid.NewString(), Name: joinName, Address: joinAddr, State: model.NodeStateReady, JoinedAt: time.Now().UTC(), LastSeen: time.Now().UTC(), } if err := registry.Join(ctx, node); err != nil { return err } if jsonOutput { return printJSON(node) } fmt.Fprintf(cmd.OutOrStdout(), "✓ Node joined: %s (%s) at %s\n", node.ID, node.Name, node.Address) return nil }, } var nodeLeaveCmd = &cobra.Command{ Use: "leave [node-id]", Short: "Remove a node from the orca registry", Long: "Mark a node as left. Use --id to specify, or pass as argument.", Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { id := leaveID if id == "" && len(args) > 0 { id = args[0] } if id == "" { return fmt.Errorf("node id required (use --id or pass as argument)") } ctx, cancel := context.WithTimeout(cmd.Context(), 5*time.Second) defer cancel() registry, closer, err := nodeRegistry() if err != nil { return err } defer closer() if err := registry.Leave(ctx, id); err != nil { return err } if jsonOutput { return printJSON(map[string]string{"id": id, "state": "left"}) } fmt.Fprintf(cmd.OutOrStdout(), "✓ Node left: %s\n", id) return nil }, } var nodeListCmd = &cobra.Command{ Use: "list", Short: "List all nodes in the orca registry", Long: "Display all registered nodes and their state.", RunE: func(cmd *cobra.Command, args []string) error { ctx, cancel := context.WithTimeout(cmd.Context(), 5*time.Second) defer cancel() registry, closer, err := nodeRegistry() if err != nil { return err } defer closer() nodes, err := registry.List(ctx) if err != nil { return err } if jsonOutput { return printJSON(nodes) } if len(nodes) == 0 { fmt.Fprintln(cmd.OutOrStdout(), "No nodes registered. Use 'orca node join' to add one.") return nil } fmt.Fprintf(cmd.OutOrStdout(), "%-36s %-20s %-22s %-10s\n", "ID", "NAME", "ADDRESS", "STATE") for _, n := range nodes { fmt.Fprintf(cmd.OutOrStdout(), "%-36s %-20s %-22s %-10s\n", n.ID, n.Name, n.Address, n.State) } return nil }, } func init() { nodeJoinCmd.Flags().StringVar(&joinName, "name", "", "node name (required)") nodeJoinCmd.Flags().StringVar(&joinAddr, "addr", "", "node address (default localhost:8443)") nodeLeaveCmd.Flags().StringVar(&leaveID, "id", "", "node id") nodeCmd.AddCommand(nodeJoinCmd) nodeCmd.AddCommand(nodeLeaveCmd) nodeCmd.AddCommand(nodeListCmd) rootCmd.AddCommand(nodeCmd) }