mydev/cmd/dev.go
Victor Abrell 47bb2d3a69 Use Gitea module path, add MIT license
Set module to git.abrell.se/victor/mydev so it installs from the
private Gitea host, and add an MIT LICENSE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 14:41:20 +02:00

158 lines
3.9 KiB
Go

package cmd
import (
"context"
"fmt"
"os"
"sort"
"text/tabwriter"
"git.abrell.se/victor/mydev/internal/checks"
"git.abrell.se/victor/mydev/internal/config"
"git.abrell.se/victor/mydev/internal/run"
"github.com/spf13/cobra"
)
// devCompose loads config and returns the compose file path, exiting on error.
func devCompose() string {
cfg, err := config.Load()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
file, err := cfg.ComposeFile()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
return file
}
// compose runs `docker compose --file <file> <args...>` streamed to terminal.
func compose(file string, args ...string) {
full := append([]string{"compose", "--file", file}, args...)
if err := run.Stream("", "docker", full...); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
// ensureAuthed re-auths first if the AWS session is gone (mirrors `myd auth`).
func ensureAuthed() {
cfg, err := config.Load()
if err != nil {
return
}
awsmfa, err := cfg.AWSMFA()
if err != nil {
return
}
if s := checks.AWSAuth(context.Background(), awsmfa); !s.Authed {
fmt.Println("AWS session expired — re-authenticating…")
_ = run.Stream("", awsmfa, "auth")
}
}
var devCmd = &cobra.Command{
Use: "dev",
Short: "Control the dev docker-compose stack",
}
var devStartCmd = &cobra.Command{
Use: "start",
Short: "Auth, then start the stack (up -d --force-recreate)",
Run: func(cmd *cobra.Command, args []string) {
ensureAuthed()
compose(devCompose(), "up", "-d", "--force-recreate")
},
}
var devStopCmd = &cobra.Command{
Use: "stop",
Short: "Stop the stack",
Run: func(cmd *cobra.Command, args []string) { compose(devCompose(), "stop") },
}
var devRestartCmd = &cobra.Command{
Use: "restart",
Short: "Stop, auth, then start again",
Run: func(cmd *cobra.Command, args []string) {
file := devCompose()
compose(file, "stop")
ensureAuthed()
compose(file, "up", "-d", "--force-recreate")
},
}
var devRemoveCmd = &cobra.Command{
Use: "remove",
Short: "Tear down the stack (down)",
Run: func(cmd *cobra.Command, args []string) { compose(devCompose(), "down") },
}
var devStatusFetch bool
var devStatusCmd = &cobra.Command{
Use: "status",
Short: "Check if running containers match the current dev repo",
Run: func(cmd *cobra.Command, args []string) {
ctx := context.Background()
// Git freshness: is the dev repo itself behind origin?
cfg, err := config.Load()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if cfg.DevDir != "" {
if devStatusFetch {
fmt.Println("fetching dev repo…")
}
fr := checks.GitFreshness(ctx, cfg.DevDir, devStatusFetch)
switch {
case fr.Err != "":
fmt.Printf("dev repo: ? (%s)\n\n", fr.Err)
case fr.NoUpstream:
fmt.Printf("dev repo: %s (no upstream)\n\n", fr.Branch)
case fr.Behind > 0:
fmt.Printf("dev repo: %s ✗ behind origin by %d — git pull to get latest defs\n\n", fr.Branch, fr.Behind)
default:
fmt.Printf("dev repo: %s ✓ up to date with origin\n\n", fr.Branch)
}
}
file := devCompose()
drift, errMsg := checks.ComposeDrift(ctx, file)
if errMsg != "" {
fmt.Fprintln(os.Stderr, errMsg)
os.Exit(1)
}
sort.Slice(drift, func(i, j int) bool { return drift[i].Service < drift[j].Service })
stale := 0
w := tabwriter.NewWriter(os.Stdout, 0, 2, 2, ' ', 0)
for _, d := range drift {
mark := "✓"
if !d.UpToDate() {
mark = "✗"
stale++
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", mark, d.Service, d.State, d.Reason())
}
w.Flush()
if stale > 0 {
fmt.Printf("\n%d service(s) out of date — run 'mydev dev restart' to recreate\n", stale)
os.Exit(1)
}
fmt.Println("\nall services up to date")
},
}
func init() {
devStatusCmd.Flags().BoolVar(&devStatusFetch, "fetch", true, "git fetch the dev repo first (set --fetch=false to skip network)")
devCmd.AddCommand(devStartCmd, devStopCmd, devRestartCmd, devRemoveCmd, devStatusCmd)
rootCmd.AddCommand(devCmd)
}