package cli import ( "context" "fmt" "os" "strings" "github.com/spf13/pflag" "tsne.dev/hopper/internal/log" ) var _ command = (*mainCommand)(nil) type mainCommand struct { flags *pflag.FlagSet zone string endpoint string accessKeyFile string accessKey string verbose bool quiet bool } func newMainCommand() *mainCommand { cmd := &mainCommand{} cmd.flags = newFlagset("hopper") cmd.flags.SetInterspersed(false) cmd.flags.StringVar(&cmd.zone, "zone", lookupEnv("BUNNY_ZONE", ""), "Bunny storage zone name (required) (env: BUNNY_ZONE)", ) cmd.flags.StringVar(&cmd.endpoint, "endpoint", lookupEnv("BUNNY_ENDPOINT", "storage.bunnycdn.com"), "Bunny storage endpoint hostname (env: BUNNY_ENDPOINT)", ) cmd.flags.StringVar(&cmd.accessKeyFile, "access-key-file", lookupEnv("BUNNY_ACCESS_KEY_FILE", ""), "path to a file containing the storage access key (env: BUNNY_ACCESS_KEY_FILE)", ) cmd.flags.BoolVar( &cmd.verbose, "verbose", false, "print more details", ) cmd.flags.BoolVar( &cmd.quiet, "quiet", false, "print only the final summary and errors", ) return cmd } func (cmd *mainCommand) Usage() string { return "Usage:" + "\n hopper [global flags] ⟨command⟩ [command flags]" + "\n" + "\nCommands:" + "\n push upload a release to Bunny Storage" + "\n prune remove orphaned files from Bunny Storage" + "\n" + "\nGlobal flags:" + "\n" + cmd.flags.FlagUsages() } func (cmd *mainCommand) Run(ctx context.Context, args []string) error { parseFlags(cmd, cmd.flags, args) log.SetVerbose(cmd.verbose) log.SetQuiet(cmd.quiet) if cmd.accessKeyFile == "" { cmd.accessKey = os.Getenv("BUNNY_ACCESS_KEY") } switch { case cmd.zone == "": return usageError{"missing zone (provide --zone or BUNNY_ZONE)"} case cmd.endpoint == "": // --endpoint has a default, so an empty value here is always deliberate. return usageError{"the endpoint must not be empty"} case cmd.accessKey == "" && cmd.accessKeyFile == "": return usageError{"missing access key (provide --access-key-file, BUNNY_ACCESS_KEY_FILE, or BUNNY_ACCESS_KEY)"} } subargs := cmd.flags.Args() if len(subargs) == 0 || subargs[0] == "" { return usageError{"missing subcommand"} } var sub command switch subargs[0] { case "push": sub = newPushCommand(cmd) case "prune": sub = newPruneCommand(cmd) default: return usageError{fmt.Sprintf("unknown subcommand %q", subargs[0])} } return runCommand(ctx, sub, subargs[1:]) } func (cmd *mainCommand) lookupAccessKey() error { if cmd.accessKey == "" { accessKey, err := os.ReadFile(cmd.accessKeyFile) if err != nil { return fmt.Errorf("read access key file: %w", err) } cmd.accessKey = strings.TrimSpace(string(accessKey)) } if cmd.accessKey == "" { return usageError{"empty access key"} } return nil } func lookupEnv(env string, defaultValue string) string { if val := os.Getenv(env); val != "" { return val } return defaultValue }