ship: v0.1 Foundation milestone complete (#1)

This commit was merged in pull request #1.
This commit is contained in:
2026-06-03 20:08:57 +00:00
parent 55aae5347e
commit be9afa2d2c
59 changed files with 4152 additions and 36 deletions
+60
View File
@@ -0,0 +1,60 @@
package cli
import (
"context"
"fmt"
"time"
"github.com/spf13/cobra"
"git.cloudinit.dev/coreci/orca/internal/store"
)
var (
auditLimit int
)
var auditCmd = &cobra.Command{
Use: "audit",
Short: "View orca audit log",
Long: "Display the most recent audit log entries (security-first observability).",
}
var auditListCmd = &cobra.Command{
Use: "list",
Short: "List recent audit log entries",
RunE: func(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(cmd.Context(), 5*time.Second)
defer cancel()
db, closer, err := openDB()
if err != nil {
return err
}
defer closer()
entries, err := store.NewAuditRepo(db).List(ctx, auditLimit)
if err != nil {
return err
}
if jsonOutput {
return printJSON(entries)
}
if len(entries) == 0 {
fmt.Fprintln(cmd.OutOrStdout(), "No audit entries.")
return nil
}
fmt.Fprintf(cmd.OutOrStdout(), "%-22s %-12s %-20s %-30s %-10s\n", "TIMESTAMP", "ACTOR", "ACTION", "RESOURCE", "RESULT")
for _, e := range entries {
fmt.Fprintf(cmd.OutOrStdout(), "%-22s %-12s %-20s %-30s %-10s\n",
e.Timestamp.Format("2006-01-02T15:04:05Z"), e.Actor, e.Action, e.Resource, e.Result)
}
return nil
},
}
func init() {
auditListCmd.Flags().IntVar(&auditLimit, "limit", 50, "max entries to show")
auditCmd.AddCommand(auditListCmd)
rootCmd.AddCommand(auditCmd)
}
+76
View File
@@ -0,0 +1,76 @@
package cli
import (
"context"
"errors"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/spf13/cobra"
"git.cloudinit.dev/coreci/orca/internal/daemon"
)
var (
daemonAddr string
)
var daemonCmd = &cobra.Command{
Use: "daemon",
Short: "Run the orca daemon (HTTP API + health checks)",
Long: "Start the orca daemon. Listens on the configured address for health and API requests.",
RunE: func(cmd *cobra.Command, args []string) error {
db, closer, err := openDB()
if err != nil {
return err
}
defer closer()
srv := daemon.NewServer(daemon.Options{
DB: db,
Log: newLogger(),
Addr: daemonAddr,
Actor: "daemon",
})
srv.MarkReady()
errCh := make(chan error, 1)
go func() {
err := srv.Start()
if err != nil && !errors.Is(err, http.ErrServerClosed) {
errCh <- err
}
}()
fmt.Fprintf(cmd.OutOrStdout(), "✓ orca daemon listening on %s\n", daemonAddr)
fmt.Fprintln(cmd.OutOrStdout(), " /healthz - liveness")
fmt.Fprintln(cmd.OutOrStdout(), " /readyz - readiness (db + ready flag)")
fmt.Fprintln(cmd.OutOrStdout(), " /v1/status - status JSON")
fmt.Fprintln(cmd.OutOrStdout(), " /v1/jobs - list jobs")
fmt.Fprintln(cmd.OutOrStdout(), " /v1/nodes - list nodes")
fmt.Fprintln(cmd.OutOrStdout(), " /v1/tasks - list tasks")
fmt.Fprintln(cmd.OutOrStdout(), " press Ctrl+C to stop")
ctx, stop := signal.NotifyContext(cmd.Context(), os.Interrupt, syscall.SIGTERM)
defer stop()
select {
case <-ctx.Done():
fmt.Fprintln(cmd.OutOrStdout(), "\nshutting down...")
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
return srv.Shutdown(shutdownCtx)
case err := <-errCh:
return err
}
},
}
func init() {
daemonCmd.Flags().StringVar(&daemonAddr, "addr", ":8080", "listen address")
rootCmd.AddCommand(daemonCmd)
}
+38
View File
@@ -0,0 +1,38 @@
package cli
import (
"fmt"
"os"
"path/filepath"
"github.com/spf13/cobra"
)
var initCmd = &cobra.Command{
Use: "init",
Short: "Initialize local orca state directory",
Long: "Create the local orca state directory at ~/.orca/ and write a default config file.",
RunE: func(cmd *cobra.Command, args []string) error {
home, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("get home dir: %w", err)
}
orcaDir := filepath.Join(home, ".orca")
if err := os.MkdirAll(orcaDir, 0o755); err != nil {
return fmt.Errorf("create orca dir: %w", err)
}
result := map[string]string{
"path": orcaDir,
"status": "initialized",
}
if jsonOutput {
return printJSON(result)
}
printText("✓ Initialized orca state at %s\n", orcaDir)
return nil
},
}
func init() {
rootCmd.AddCommand(initCmd)
}
+223
View File
@@ -0,0 +1,223 @@
package cli
import (
"context"
"errors"
"fmt"
"time"
"github.com/google/uuid"
"github.com/spf13/cobra"
"git.cloudinit.dev/coreci/orca/internal/engine"
"git.cloudinit.dev/coreci/orca/internal/jobspec"
"git.cloudinit.dev/coreci/orca/internal/model"
"git.cloudinit.dev/coreci/orca/internal/store"
)
var jobCmd = &cobra.Command{
Use: "job",
Short: "Manage orca jobs",
Long: "Run, list, stop, and inspect orca jobs.",
}
func jobExecutor() (*engine.Executor, func() error, error) {
db, closer, err := openDB()
if err != nil {
return nil, nil, err
}
jobs := store.NewJobRepo(db)
tasks := store.NewTaskRepo(db)
return engine.NewExecutor(jobs, tasks, newLogger()), closer, nil
}
var jobRunCmd = &cobra.Command{
Use: "run <spec.hcl>",
Short: "Run a job from an HCL spec file",
Long: "Submit a job spec, execute its tasks, and persist the result.",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
spec, err := jobspec.ParseFile(args[0])
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(cmd.Context(), 5*time.Minute)
defer cancel()
exec, closer, err := jobExecutor()
if err != nil {
return err
}
defer closer()
job := &model.Job{
ID: uuid.NewString(),
Name: spec.Job.Name,
Spec: args[0],
Status: model.JobStatusPending,
}
if err := exec.Run(ctx, job, toTaskSpecs(spec.Tasks)); err != nil {
if jsonOutput {
_ = printJSON(map[string]any{"id": job.ID, "status": "failed", "error": err.Error()})
return err
}
fmt.Fprintf(cmd.ErrOrStderr(), "✗ Job %s failed: %v\n", job.ID, err)
return err
}
if jsonOutput {
return printJSON(map[string]any{"id": job.ID, "name": job.Name, "status": "complete"})
}
fmt.Fprintf(cmd.OutOrStdout(), "✓ Job complete: %s (%s)\n", job.ID, job.Name)
return nil
},
}
var jobListCmd = &cobra.Command{
Use: "list",
Short: "List all jobs",
Long: "Display all jobs and their status.",
RunE: func(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(cmd.Context(), 5*time.Second)
defer cancel()
db, closer, err := openDB()
if err != nil {
return err
}
defer closer()
jobs, err := store.NewJobRepo(db).List(ctx)
if err != nil {
return err
}
if jsonOutput {
return printJSON(jobs)
}
if len(jobs) == 0 {
fmt.Fprintln(cmd.OutOrStdout(), "No jobs. Use 'orca job run <spec.hcl>' to submit one.")
return nil
}
fmt.Fprintf(cmd.OutOrStdout(), "%-36s %-20s %-12s %-8s\n", "ID", "NAME", "STATUS", "EXIT")
for _, j := range jobs {
fmt.Fprintf(cmd.OutOrStdout(), "%-36s %-20s %-12s %-8d\n", j.ID, j.Name, j.Status, j.ExitCode)
}
return nil
},
}
var (
stopID string
)
var jobStopCmd = &cobra.Command{
Use: "stop [job-id]",
Short: "Stop a running job",
Long: "Mark a job as stopped. Note: this is a soft stop (cancel context for the daemon).",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
id := stopID
if id == "" && len(args) > 0 {
id = args[0]
}
if id == "" {
return fmt.Errorf("job id required (--id or argument)")
}
ctx, cancel := context.WithTimeout(cmd.Context(), 5*time.Second)
defer cancel()
db, closer, err := openDB()
if err != nil {
return err
}
defer closer()
repo := store.NewJobRepo(db)
job, err := repo.Get(ctx, id)
if err != nil {
if errors.Is(err, store.ErrNotFound) {
return fmt.Errorf("job not found: %s", id)
}
return err
}
if err := repo.UpdateStatus(ctx, id, model.JobStatusStopped, 130); err != nil {
return err
}
if jsonOutput {
return printJSON(map[string]any{"id": id, "status": "stopped", "previous_status": job.Status})
}
fmt.Fprintf(cmd.OutOrStdout(), "✓ Job stopped: %s\n", id)
return nil
},
}
var jobLogsCmd = &cobra.Command{
Use: "logs [job-id]",
Short: "Show task output for a job",
Long: "Display captured stdout/stderr for all tasks in a job.",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
id := stopID
if id == "" && len(args) > 0 {
id = args[0]
}
if id == "" {
return fmt.Errorf("job id required (--id or argument)")
}
ctx, cancel := context.WithTimeout(cmd.Context(), 5*time.Second)
defer cancel()
db, closer, err := openDB()
if err != nil {
return err
}
defer closer()
taskRepo := store.NewTaskRepo(db)
tasks, err := taskRepo.ListByJob(ctx, id)
if err != nil {
return err
}
if jsonOutput {
return printJSON(tasks)
}
if len(tasks) == 0 {
fmt.Fprintln(cmd.OutOrStdout(), "No tasks for this job.")
return nil
}
for i, t := range tasks {
fmt.Fprintf(cmd.OutOrStdout(), "--- task[%d] %s (%s) exit=%d ---\n", i, t.Command, t.Status, t.ExitCode)
if t.Stdout != "" {
fmt.Fprintln(cmd.OutOrStdout(), t.Stdout)
}
if t.Stderr != "" {
fmt.Fprintln(cmd.OutOrStderr(), t.Stderr)
}
}
return nil
},
}
func init() {
jobStopCmd.Flags().StringVar(&stopID, "id", "", "job id")
jobLogsCmd.Flags().StringVar(&stopID, "id", "", "job id")
jobCmd.AddCommand(jobRunCmd)
jobCmd.AddCommand(jobListCmd)
jobCmd.AddCommand(jobStopCmd)
jobCmd.AddCommand(jobLogsCmd)
rootCmd.AddCommand(jobCmd)
}
func toTaskSpecs(in []jobspec.TaskSpec) []engine.TaskSpec {
out := make([]engine.TaskSpec, len(in))
for i, t := range in {
out[i] = engine.TaskSpec{
Name: t.Name,
Command: t.Command,
Args: t.Args,
Env: t.Env,
}
}
return out
}
+176
View File
@@ -0,0 +1,176 @@
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)
audit := engine.NewAudit(store.NewAuditRepo(db), newLogger())
return engine.NewNodeRegistry(repo, audit, 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)
}
+52
View File
@@ -0,0 +1,52 @@
package cli
import (
"encoding/json"
"fmt"
"github.com/spf13/cobra"
)
var (
version = "0.1.0-dev"
gitCommit = "unknown"
buildTime = "unknown"
)
var rootCmd = &cobra.Command{
Use: "orca",
Short: "Orca — offline/CLI-first orchestration engine",
Long: `Orca is a minimalist, offline-first, CLI-first orchestration engine
inspired by HashiCorp Nomad, prioritizing stability, security, and simplicity
over feature richness.`,
SilenceUsage: true,
SilenceErrors: true,
}
var jsonOutput bool
func init() {
rootCmd.PersistentFlags().BoolVar(&jsonOutput, "json", false, "output in JSON format")
}
func Execute() error {
return rootCmd.Execute()
}
func printJSON(v any) error {
enc := json.NewEncoder(rootCmd.OutOrStdout())
enc.SetIndent("", " ")
return enc.Encode(v)
}
func printText(format string, args ...any) {
fmt.Fprintf(rootCmd.OutOrStdout(), format, args...)
}
func printResult(text string, jsonObj any) {
if jsonOutput {
_ = printJSON(jsonObj)
return
}
printText("%s\n", text)
}
+67
View File
@@ -0,0 +1,67 @@
package cli
import (
"strings"
"testing"
)
func TestVersionCommandExists(t *testing.T) {
found := false
for _, cmd := range rootCmd.Commands() {
if cmd.Name() == "version" {
found = true
break
}
}
if !found {
t.Fatal("version command not registered")
}
}
func TestRootHasAllSubcommands(t *testing.T) {
expected := []string{"version", "init", "status", "node", "job"}
registered := make(map[string]bool)
for _, cmd := range rootCmd.Commands() {
registered[cmd.Name()] = true
}
for _, name := range expected {
if !registered[name] {
t.Errorf("expected subcommand %q not registered", name)
}
}
}
func TestNodeSubcommands(t *testing.T) {
expected := []string{"join", "leave", "list"}
registered := make(map[string]bool)
for _, cmd := range nodeCmd.Commands() {
registered[cmd.Name()] = true
}
for _, name := range expected {
if !registered[name] {
t.Errorf("expected node subcommand %q not registered", name)
}
}
}
func TestJobSubcommands(t *testing.T) {
expected := []string{"run", "list", "stop", "logs"}
registered := make(map[string]bool)
for _, cmd := range jobCmd.Commands() {
registered[cmd.Name()] = true
}
for _, name := range expected {
if !registered[name] {
t.Errorf("expected job subcommand %q not registered", name)
}
}
}
func TestRootHelpMentionsKeyPillars(t *testing.T) {
help := rootCmd.Long
for _, pillar := range []string{"offline", "CLI", "Nomad", "simplicity"} {
if !strings.Contains(strings.ToLower(help), strings.ToLower(pillar)) {
t.Errorf("root help does not mention pillar %q", pillar)
}
}
}
+36
View File
@@ -0,0 +1,36 @@
package cli
import (
"github.com/spf13/cobra"
)
var statusCmd = &cobra.Command{
Use: "status",
Short: "Show orca daemon status",
Long: "Display the current status of the local orca daemon, including version, uptime, and connection info.",
RunE: func(cmd *cobra.Command, args []string) error {
status := map[string]any{
"version": version,
"daemon": "stopped",
"uptime": "0s",
"api_addr": "https://localhost:8443",
"health": "unknown",
"phase": "1-cli-skeleton",
"milestone": "v0.1",
}
if jsonOutput {
return printJSON(status)
}
printText("orca daemon status\n")
printText(" version: %s\n", version)
printText(" daemon: %s\n", "stopped (daemon not yet implemented in Phase 1)")
printText(" api_addr: %s\n", "https://localhost:8443")
printText(" phase: %s\n", "1-cli-skeleton")
printText(" milestone: %s\n", "v0.1")
return nil
},
}
func init() {
rootCmd.AddCommand(statusCmd)
}
+29
View File
@@ -0,0 +1,29 @@
package cli
import (
"github.com/spf13/cobra"
)
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print version information",
Long: "Print the orca version, git commit, and build time.",
RunE: func(cmd *cobra.Command, args []string) error {
info := map[string]string{
"version": version,
"git_commit": gitCommit,
"build_time": buildTime,
}
if jsonOutput {
return printJSON(info)
}
printText("orca version %s\n", version)
printText(" git commit: %s\n", gitCommit)
printText(" build time: %s\n", buildTime)
return nil
},
}
func init() {
rootCmd.AddCommand(versionCmd)
}