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 <key> 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 -<goos>-<goarch> 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
}