1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
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
}
|