Add TOML config file support to mcrctl

Loads defaults from ~/.config/mcrctl.toml (or XDG_CONFIG_HOME).
Resolution order: flag > env (MCR_TOKEN) > config file.
Adds --config flag to specify an explicit path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-01 17:02:23 -07:00
parent bf206ae67c
commit 0fe52afbb2
4 changed files with 188 additions and 8 deletions

View File

@@ -18,6 +18,7 @@ var version = "dev"
// Global flags, resolved in PersistentPreRunE.
var (
flagConfig string
flagServer string
flagGRPC string
flagToken string
@@ -32,15 +33,27 @@ func main() {
Use: "mcrctl",
Short: "Metacircular Container Registry admin CLI",
Version: version,
PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
// Resolve token: flag overrides env.
token := flagToken
if token == "" {
token = os.Getenv("MCR_TOKEN")
PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
cfg, err := loadConfig(flagConfig)
if err != nil {
return err
}
var err error
client, err = newClient(flagServer, flagGRPC, token, flagCACert)
// Resolution order: flag > env > config file.
server := applyDefault(cmd, "server", flagServer, cfg.Server)
grpcAddr := applyDefault(cmd, "grpc", flagGRPC, cfg.GRPC)
caCert := applyDefault(cmd, "ca-cert", flagCACert, cfg.CACert)
token := flagToken
if !cmd.Flags().Changed("token") {
if envToken := os.Getenv("MCR_TOKEN"); envToken != "" {
token = envToken
} else if cfg.Token != "" {
token = cfg.Token
}
}
client, err = newClient(server, grpcAddr, token, caCert)
if err != nil {
return err
}
@@ -53,9 +66,10 @@ func main() {
},
}
root.PersistentFlags().StringVar(&flagConfig, "config", "", "config file (default ~/.config/mcrctl.toml)")
root.PersistentFlags().StringVar(&flagServer, "server", "", "REST API base URL (e.g. https://registry.example.com)")
root.PersistentFlags().StringVar(&flagGRPC, "grpc", "", "gRPC server address (e.g. registry.example.com:9443)")
root.PersistentFlags().StringVar(&flagToken, "token", "", "bearer token (fallback: MCR_TOKEN env)")
root.PersistentFlags().StringVar(&flagToken, "token", "", "bearer token (fallback: MCR_TOKEN env, config file)")
root.PersistentFlags().StringVar(&flagCACert, "ca-cert", "", "custom CA certificate PEM file")
root.PersistentFlags().BoolVar(&flagJSON, "json", false, "output as JSON instead of table")
@@ -740,6 +754,18 @@ func runSnapshot(_ *cobra.Command, _ []string) error {
// ---------- helpers ----------
// applyDefault returns the flag value if the flag was explicitly set on
// the command line, otherwise falls back to the config file value.
func applyDefault(cmd *cobra.Command, name, flagVal, cfgVal string) string {
if cmd.Flags().Changed(name) {
return flagVal
}
if cfgVal != "" {
return cfgVal
}
return flagVal
}
// confirmPrompt displays msg and waits for y/n from stdin.
func confirmPrompt(msg string) bool {
fmt.Fprintf(os.Stderr, "%s [y/N] ", msg)