All import paths updated to git.wntrmute.dev/mc/. Bumps mcdsl to v1.2.0, mc-proxy to v1.1.0. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
120 lines
3.4 KiB
Go
120 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
mcpv1 "git.wntrmute.dev/mc/mcp/gen/mcp/v1"
|
|
"git.wntrmute.dev/mc/mcp/internal/config"
|
|
"git.wntrmute.dev/mc/mcp/internal/servicedef"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
func purgeCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "purge [service[/component]]",
|
|
Short: "Remove stale registry entries for gone, undefined components",
|
|
Long: `Purge removes registry entries that are both unwanted (not in any
|
|
current service definition) and gone (no corresponding container in the
|
|
runtime). It never stops or removes running containers.
|
|
|
|
Use --dry-run to preview what would be purged.`,
|
|
Args: cobra.MaximumNArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
cfg, err := config.LoadCLIConfig(cfgPath)
|
|
if err != nil {
|
|
return fmt.Errorf("load config: %w", err)
|
|
}
|
|
|
|
dryRun, _ := cmd.Flags().GetBool("dry-run")
|
|
|
|
var service, component string
|
|
if len(args) == 1 {
|
|
service, component = parseServiceArg(args[0])
|
|
}
|
|
|
|
// Load all local service definitions to build the set of
|
|
// currently-defined service/component pairs.
|
|
definedComponents := buildDefinedComponents(cfg)
|
|
|
|
// Build node address lookup.
|
|
nodeAddr := make(map[string]string, len(cfg.Nodes))
|
|
for _, n := range cfg.Nodes {
|
|
nodeAddr[n.Name] = n.Address
|
|
}
|
|
|
|
// If a specific service was given and we can find its node,
|
|
// only talk to that node. Otherwise, talk to all nodes.
|
|
targetNodes := cfg.Nodes
|
|
if service != "" {
|
|
if nodeName, nodeAddr, err := findServiceNode(cfg, service); err == nil {
|
|
targetNodes = []config.NodeConfig{{Name: nodeName, Address: nodeAddr}}
|
|
}
|
|
}
|
|
|
|
anyResults := false
|
|
for _, node := range targetNodes {
|
|
client, conn, err := dialAgent(node.Address, cfg)
|
|
if err != nil {
|
|
return fmt.Errorf("dial %s: %w", node.Name, err)
|
|
}
|
|
defer func() { _ = conn.Close() }()
|
|
|
|
resp, err := client.PurgeComponent(context.Background(), &mcpv1.PurgeRequest{
|
|
Service: service,
|
|
Component: component,
|
|
DryRun: dryRun,
|
|
DefinedComponents: definedComponents,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("purge on %s: %w", node.Name, err)
|
|
}
|
|
|
|
for _, r := range resp.GetResults() {
|
|
anyResults = true
|
|
if r.GetPurged() {
|
|
if dryRun {
|
|
fmt.Printf("would purge %s/%s (%s)\n", r.GetService(), r.GetComponent(), r.GetReason())
|
|
} else {
|
|
fmt.Printf("purged %s/%s (%s)\n", r.GetService(), r.GetComponent(), r.GetReason())
|
|
}
|
|
} else {
|
|
fmt.Printf("skipped %s/%s (%s)\n", r.GetService(), r.GetComponent(), r.GetReason())
|
|
}
|
|
}
|
|
}
|
|
|
|
if !anyResults {
|
|
fmt.Println("nothing to purge")
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().Bool("dry-run", false, "preview what would be purged without modifying the registry")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// buildDefinedComponents reads all local service definition files and returns
|
|
// a list of "service/component" strings for every defined component.
|
|
func buildDefinedComponents(cfg *config.CLIConfig) []string {
|
|
defs, err := servicedef.LoadAll(cfg.Services.Dir)
|
|
if err != nil {
|
|
// If we can't read service definitions, return an empty list.
|
|
// The agent will treat every component as undefined, which is the
|
|
// most conservative behavior (everything eligible gets purged).
|
|
return nil
|
|
}
|
|
|
|
var defined []string
|
|
for _, def := range defs {
|
|
for _, comp := range def.Components {
|
|
defined = append(defined, def.Name+"/"+comp.Name)
|
|
}
|
|
}
|
|
return defined
|
|
}
|