/
root.go
183 lines (159 loc) · 5.58 KB
/
root.go
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
package cmd
import (
"fmt"
"log"
"log/slog"
"net/url"
"os"
"strings"
cloudgo "github.com/fi-ts/cloud-go"
"github.com/fi-ts/cloud-go/api/client"
"github.com/fi-ts/cloudctl/cmd/completion"
"github.com/fi-ts/cloudctl/pkg/api"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var (
// will bind all viper flags to subcommands and
// prevent overwrite of identical flag names from other commands
// see https://github.com/spf13/viper/issues/233#issuecomment-386791444
bindPFlags = func(cmd *cobra.Command, args []string) {
err := viper.BindPFlags(cmd.Flags())
if err != nil {
fmt.Printf("error during setup:%v", err)
os.Exit(1)
}
}
)
func newRootCmd() *cobra.Command {
name := "cloudctl"
rootCmd := &cobra.Command{
Use: name,
Short: "a cli to manage cloud entities.",
Long: "with cloudctl you can manage kubernetes cluster, view networks et.al.",
SilenceUsage: true,
}
rootCmd.PersistentFlags().StringP("url", "u", "", "api server address. Can be specified with CLOUDCTL_URL environment variable.")
rootCmd.PersistentFlags().String("apitoken", "", "api token to authenticate. Can be specified with CLOUDCTL_APITOKEN environment variable.")
rootCmd.PersistentFlags().String("kubeconfig", "", "Path to the kube-config to use for authentication and authorization. Is updated by login. Uses default path if not specified.")
rootCmd.PersistentFlags().StringP("order", "", "", "order by (comma separated) column(s)")
rootCmd.PersistentFlags().BoolP("no-headers", "", false, "ommit headers in tables")
rootCmd.PersistentFlags().BoolP("debug", "", false, "enable debug")
rootCmd.PersistentFlags().Bool("force-color", false, "force colored output even without tty")
rootCmd.PersistentFlags().StringP("output-format", "o", "table", "output format (table|wide|markdown|json|yaml|template), wide is a table with more columns.")
rootCmd.PersistentFlags().StringP("template", "", "", `output template for template output-format, go template format.
For property names inspect the output of -o json for reference.
Example for clusters:
cloudctl cluster ls -o template --template "{{ .ID }} {{ .Name }}"
`)
rootCmd.PersistentFlags().BoolP("yes-i-really-mean-it", "", false, "skips security prompts (which can be dangerous to set blindly because actions can lead to data loss or additional costs)")
must(viper.BindPFlags(rootCmd.Flags()))
must(viper.BindPFlags(rootCmd.PersistentFlags()))
cfg := getConfig(name)
rootCmd.AddCommand(newAuditCmd(cfg))
rootCmd.AddCommand(newClusterCmd(cfg))
rootCmd.AddCommand(newDashboardCmd(cfg))
rootCmd.AddCommand(newUpdateCmd(cfg, name))
rootCmd.AddCommand(newLoginCmd(cfg))
rootCmd.AddCommand(newLogoutCmd(cfg))
rootCmd.AddCommand(newWhoamiCmd())
rootCmd.AddCommand(newProjectCmd(cfg))
rootCmd.AddCommand(newTenantCmd(cfg))
rootCmd.AddCommand(newContextCmd(cfg))
rootCmd.AddCommand(newS3Cmd(cfg))
rootCmd.AddCommand(newVersionCmd(cfg))
rootCmd.AddCommand(newVolumeCmd(cfg))
rootCmd.AddCommand(newPostgresCmd(cfg))
rootCmd.AddCommand(newIPCmd(cfg))
rootCmd.AddCommand(newBillingCmd(cfg))
rootCmd.AddCommand(newHealthCmd(cfg))
return rootCmd
}
// Execute is the entrypoint of the cloudctl application
func Execute() {
cmd := newRootCmd()
err := cmd.Execute()
if err != nil {
if viper.GetBool("debug") {
panic(err)
}
os.Exit(1)
}
}
type config struct {
name string
cloud *client.CloudAPI
comp *completion.Completion
consoleHost string
log *slog.Logger
}
func getConfig(name string) *config {
viper.SetEnvPrefix(strings.ToUpper(name))
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
viper.AutomaticEnv()
viper.SetConfigType("yaml")
cfgFile := viper.GetString("config")
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
if err := viper.ReadInConfig(); err != nil {
log.Fatalf("config file path set explicitly, but unreadable:%v", err)
}
} else {
viper.SetConfigName("config")
viper.AddConfigPath(fmt.Sprintf("/etc/%s", name))
h, err := os.UserHomeDir()
if err != nil {
log.Printf("unable to figure out user home directory, skipping config lookup path: %v", err)
} else {
viper.AddConfigPath(fmt.Sprintf(h+"/.%s", name))
}
viper.AddConfigPath(".")
if err := viper.ReadInConfig(); err != nil {
usedCfg := viper.ConfigFileUsed()
if usedCfg != "" {
log.Fatalf("config %s file unreadable:%v", usedCfg, err)
}
}
}
ctx := api.MustDefaultContext()
opts := &slog.HandlerOptions{}
if viper.GetBool("debug") {
opts.Level = slog.LevelDebug
}
driverURL := viper.GetString("url")
if driverURL == "" && ctx.ApiURL != "" {
driverURL = ctx.ApiURL
}
hmac := viper.GetString("hmac")
if hmac == "" && ctx.HMAC != nil {
hmac = *ctx.HMAC
}
apiToken := viper.GetString("apitoken")
// if there is no api token explicitly specified we try to pull it out of
// the kubeconfig context
if apiToken == "" {
authContext, err := api.GetAuthContext(viper.GetString("kubeconfig"))
// if there is an error, no kubeconfig exists for us ... this is not really an error
// if cloudctl is used in scripting with an hmac-key
if err == nil {
apiToken = authContext.IDToken
}
}
cloud, err := cloudgo.NewClient(driverURL, apiToken, hmac)
if err != nil {
log.Fatalf("error initializing cloud-api client: %v", err)
}
comp := completion.NewCompletion(cloud)
parsedURL, err := url.Parse(driverURL)
if err != nil {
log.Fatalf("could not parse driver url: %v", err)
}
consoleHost := parsedURL.Host
return &config{
name: name,
cloud: cloud,
comp: comp,
consoleHost: consoleHost,
log: slog.New(slog.NewJSONHandler(os.Stdout, opts)),
}
}