package cmd import ( "fmt" "runtime" "github.com/spf13/cobra" "go.bigb.es/cacher/internal/config" "go.bigb.es/cacher/internal/hash" "go.bigb.es/cacher/internal/s3" ) // Flags overridable per command (flag > env > config > defaults). var ( flagEndpoint string flagRegion string flagBucket string flagPrefix string flagArchSuffix string // "true"/"false"/"" (unset) flagKeyFile string flagSecretFile string // Key derivation (attached by addKeyFlags). flagHashFrom []string flagHashLength int ) // addKeyFlags attaches --hash-from / --hash-length. Use on every command // that takes a argument. func addKeyFlags(c *cobra.Command) { c.Flags().StringSliceVar(&flagHashFrom, "hash-from", nil, "path(s) whose sha256 is substituted for {hash} in the key (repeatable; file or directory)") c.Flags().IntVar(&flagHashLength, "hash-length", hash.DefaultLength, "truncate the derived hash to N hex chars") } // resolveKey applies --hash-from and --arch-suffix to the user-supplied // key template. With no --hash-from, the template is returned verbatim // (plus arch suffix if enabled). func resolveKey(keyTemplate string, cfg config.Config) (string, error) { var derived string if len(flagHashFrom) > 0 { var err error derived, err = hash.Derive(flagHashFrom, flagHashLength) if err != nil { return "", err } } return hash.ApplyTemplate(keyTemplate, derived, runtime.GOOS, runtime.GOARCH, cfg.ArchSuffix), nil } // addS3Flags attaches the standard S3 override flags to a command. Most // commands accept them so single-shot invocations can override config. func addS3Flags(c *cobra.Command) { c.Flags().StringVar(&flagEndpoint, "endpoint", "", "S3 endpoint URL (overrides config)") c.Flags().StringVar(&flagRegion, "region", "", "S3 region (overrides config)") c.Flags().StringVar(&flagBucket, "bucket", "", "S3 bucket (overrides config)") c.Flags().StringVar(&flagPrefix, "prefix", "", "S3 key prefix (overrides config)") c.Flags().StringVar(&flagArchSuffix, "arch-suffix", "", "append -- to key (true|false; default from config)") c.Flags().StringVar(&flagKeyFile, "key-file", "", "path to S3 access key id file (overrides config)") c.Flags().StringVar(&flagSecretFile, "secret-file", "", "path to S3 secret file (overrides config)") } // loadConfig merges file → env → flag overrides. func loadConfig() (config.Config, error) { cfg, err := config.Load(flagConfigPath) if err != nil { return cfg, err } if err := config.ApplyEnv(&cfg); err != nil { return cfg, err } if flagEndpoint != "" { cfg.Endpoint = flagEndpoint } if flagRegion != "" { cfg.Region = flagRegion } if flagBucket != "" { cfg.Bucket = flagBucket } if flagPrefix != "" { cfg.Prefix = flagPrefix } if flagKeyFile != "" { cfg.KeyFile = flagKeyFile } if flagSecretFile != "" { cfg.SecretFile = flagSecretFile } switch flagArchSuffix { case "true": cfg.ArchSuffix = true case "false": cfg.ArchSuffix = false case "": // inherit from file/env default: return cfg, fmt.Errorf("--arch-suffix must be true|false, got %q", flagArchSuffix) } return cfg, nil } // client builds a Garage-tuned S3 client from the resolved config. func client() (*s3.Client, config.Config, error) { cfg, err := loadConfig() if err != nil { return nil, cfg, err } if err := cfg.Validate(); err != nil { return nil, cfg, err } key, secret, err := cfg.Credentials() if err != nil { return nil, cfg, err } c, err := s3.New(s3.Options{ Endpoint: cfg.Endpoint, Region: cfg.Region, Bucket: cfg.Bucket, Prefix: cfg.Prefix, KeyID: key, Secret: secret, }) return c, cfg, err }