1/* 2Copyright 2018 The Doctl Authors All rights reserved. 3Licensed under the Apache License, Version 2.0 (the "License"); 4you may not use this file except in compliance with the License. 5You may obtain a copy of the License at 6 http://www.apache.org/licenses/LICENSE-2.0 7Unless required by applicable law or agreed to in writing, software 8distributed under the License is distributed on an "AS IS" BASIS, 9WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10See the License for the specific language governing permissions and 11limitations under the License. 12*/ 13 14package commands 15 16import ( 17 "fmt" 18 "log" 19 "os" 20 "path/filepath" 21 "strings" 22 23 "github.com/digitalocean/doctl" 24 "github.com/fatih/color" 25 "github.com/spf13/cobra" 26 "github.com/spf13/viper" 27) 28 29const ( 30 defaultConfigName = "config.yaml" // default name of config file 31) 32 33var ( 34 //DoitCmd is the root level doctl command that all other commands attach to 35 DoitCmd = &Command{ // base command 36 Command: &cobra.Command{ 37 Use: "doctl", 38 Short: "doctl is a command line interface (CLI) for the DigitalOcean API.", 39 }, 40 } 41 42 //Writer wires up stdout for all commands to write to 43 Writer = os.Stdout 44 //APIURL customize API base URL 45 APIURL string 46 //Context current auth context 47 Context string 48 //Output global output format 49 Output string 50 //Token global authorization token 51 Token string 52 //Trace toggles http tracing output 53 Trace bool 54 //Verbose toggle verbose output on and off 55 Verbose bool 56 57 requiredColor = color.New(color.Bold).SprintfFunc() 58) 59 60func init() { 61 var cfgFile string 62 63 initConfig() 64 65 rootPFlagSet := DoitCmd.PersistentFlags() 66 rootPFlagSet.StringVarP(&cfgFile, "config", "c", 67 filepath.Join(defaultConfigHome(), defaultConfigName), "Specify a custom config file") 68 viper.BindPFlag("config", rootPFlagSet.Lookup("config")) 69 70 rootPFlagSet.StringVarP(&APIURL, "api-url", "u", "", "Override default API endpoint") 71 viper.BindPFlag("api-url", rootPFlagSet.Lookup("api-url")) 72 73 rootPFlagSet.StringVarP(&Token, doctl.ArgAccessToken, "t", "", "API V2 access token") 74 viper.BindPFlag(doctl.ArgAccessToken, rootPFlagSet.Lookup(doctl.ArgAccessToken)) 75 76 rootPFlagSet.StringVarP(&Output, doctl.ArgOutput, "o", "text", "Desired output format [text|json]") 77 viper.BindPFlag("output", rootPFlagSet.Lookup(doctl.ArgOutput)) 78 79 rootPFlagSet.StringVarP(&Context, doctl.ArgContext, "", "", "Specify a custom authentication context name") 80 DoitCmd.RegisterFlagCompletionFunc(doctl.ArgContext, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 81 return getAuthContextList(), cobra.ShellCompDirectiveNoFileComp 82 }) 83 84 rootPFlagSet.BoolVarP(&Trace, "trace", "", false, "Show a log of network activity while performing a command") 85 rootPFlagSet.BoolVarP(&Verbose, doctl.ArgVerbose, "v", false, "Enable verbose output") 86 87 addCommands() 88 89 cobra.OnInitialize(initConfig) 90} 91 92func initConfig() { 93 viper.SetEnvPrefix("DIGITALOCEAN") 94 viper.AutomaticEnv() 95 viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) 96 viper.SetConfigType("yaml") 97 98 cfgFile := viper.GetString("config") 99 viper.SetConfigFile(cfgFile) 100 101 viper.SetDefault("output", "text") 102 viper.SetDefault(doctl.ArgContext, doctl.ArgDefaultContext) 103 104 if _, err := os.Stat(cfgFile); err == nil { 105 if err := viper.ReadInConfig(); err != nil { 106 log.Fatalln("Config initialization failed:", err) 107 } 108 } 109} 110 111// in case we ever want to change this, or let folks configure it... 112func defaultConfigHome() string { 113 cfgDir, err := os.UserConfigDir() 114 checkErr(err) 115 116 return filepath.Join(cfgDir, "doctl") 117} 118 119func configHome() string { 120 ch := defaultConfigHome() 121 err := os.MkdirAll(ch, 0755) 122 checkErr(err) 123 124 return ch 125} 126 127// Execute executes the current command using DoitCmd. 128func Execute() { 129 if err := DoitCmd.Execute(); err != nil { 130 fmt.Println(err) 131 os.Exit(-1) 132 } 133} 134 135// AddCommands adds sub commands to the base command. 136func addCommands() { 137 DoitCmd.AddCommand(Account()) 138 DoitCmd.AddCommand(Apps()) 139 DoitCmd.AddCommand(Auth()) 140 DoitCmd.AddCommand(Balance()) 141 DoitCmd.AddCommand(BillingHistory()) 142 DoitCmd.AddCommand(Invoices()) 143 DoitCmd.AddCommand(computeCmd()) 144 DoitCmd.AddCommand(Kubernetes()) 145 DoitCmd.AddCommand(Databases()) 146 DoitCmd.AddCommand(Projects()) 147 DoitCmd.AddCommand(Version()) 148 DoitCmd.AddCommand(Registry()) 149 DoitCmd.AddCommand(VPCs()) 150 DoitCmd.AddCommand(OneClicks()) 151 DoitCmd.AddCommand(Monitoring()) 152} 153 154func computeCmd() *Command { 155 cmd := &Command{ 156 Command: &cobra.Command{ 157 Use: "compute", 158 Short: "Display commands that manage infrastructure", 159 Long: `The subcommands under ` + "`" + `doctl compute` + "`" + ` are for managing DigitalOcean resources.`, 160 }, 161 } 162 163 cmd.AddCommand(Actions()) 164 cmd.AddCommand(CDN()) 165 cmd.AddCommand(Certificate()) 166 cmd.AddCommand(DropletAction()) 167 cmd.AddCommand(Droplet()) 168 cmd.AddCommand(Domain()) 169 cmd.AddCommand(Firewall()) 170 cmd.AddCommand(FloatingIP()) 171 cmd.AddCommand(FloatingIPAction()) 172 cmd.AddCommand(Images()) 173 cmd.AddCommand(ImageAction()) 174 cmd.AddCommand(LoadBalancer()) 175 cmd.AddCommand(Plugin()) 176 cmd.AddCommand(Region()) 177 cmd.AddCommand(Size()) 178 cmd.AddCommand(Snapshot()) 179 cmd.AddCommand(SSHKeys()) 180 cmd.AddCommand(Tags()) 181 cmd.AddCommand(Volume()) 182 cmd.AddCommand(VolumeAction()) 183 184 // SSH is different since it doesn't have any subcommands. In this case, let's 185 // give it a parent at init time. 186 SSH(cmd) 187 188 return cmd 189} 190 191type flagOpt func(c *Command, name, key string) 192 193func requiredOpt() flagOpt { 194 return func(c *Command, name, key string) { 195 c.MarkFlagRequired(key) 196 197 key = fmt.Sprintf("required.%s", key) 198 viper.Set(key, true) 199 200 u := c.Flag(name).Usage 201 c.Flag(name).Usage = fmt.Sprintf("%s %s", u, requiredColor("(required)")) 202 } 203} 204 205// AddStringFlag adds a string flag to a command. 206func AddStringFlag(cmd *Command, name, shorthand, dflt, desc string, opts ...flagOpt) { 207 fn := flagName(cmd, name) 208 cmd.Flags().StringP(name, shorthand, dflt, desc) 209 210 for _, o := range opts { 211 o(cmd, name, fn) 212 } 213 214 viper.BindPFlag(fn, cmd.Flags().Lookup(name)) 215} 216 217// AddIntFlag adds an integr flag to a command. 218func AddIntFlag(cmd *Command, name, shorthand string, def int, desc string, opts ...flagOpt) { 219 fn := flagName(cmd, name) 220 cmd.Flags().IntP(name, shorthand, def, desc) 221 viper.BindPFlag(fn, cmd.Flags().Lookup(name)) 222 223 for _, o := range opts { 224 o(cmd, name, fn) 225 } 226} 227 228// AddBoolFlag adds a boolean flag to a command. 229func AddBoolFlag(cmd *Command, name, shorthand string, def bool, desc string, opts ...flagOpt) { 230 fn := flagName(cmd, name) 231 cmd.Flags().BoolP(name, shorthand, def, desc) 232 viper.BindPFlag(fn, cmd.Flags().Lookup(name)) 233 234 for _, o := range opts { 235 o(cmd, name, fn) 236 } 237} 238 239// AddStringSliceFlag adds a string slice flag to a command. 240func AddStringSliceFlag(cmd *Command, name, shorthand string, def []string, desc string, opts ...flagOpt) { 241 fn := flagName(cmd, name) 242 cmd.Flags().StringSliceP(name, shorthand, def, desc) 243 viper.BindPFlag(fn, cmd.Flags().Lookup(name)) 244 245 for _, o := range opts { 246 o(cmd, name, fn) 247 } 248} 249 250// AddStringMapStringFlag adds a map of strings by strings flag to a command. 251func AddStringMapStringFlag(cmd *Command, name, shorthand string, def map[string]string, desc string, opts ...flagOpt) { 252 fn := flagName(cmd, name) 253 cmd.Flags().StringToStringP(name, shorthand, def, desc) 254 viper.BindPFlag(fn, cmd.Flags().Lookup(name)) 255 256 for _, o := range opts { 257 o(cmd, name, fn) 258 } 259} 260 261func flagName(cmd *Command, name string) string { 262 if cmd.Parent() != nil { 263 return fmt.Sprintf("%s.%s.%s", cmd.Parent().Name(), cmd.Name(), name) 264 } 265 return fmt.Sprintf("%s.%s", cmd.Name(), name) 266} 267 268func cmdNS(cmd *cobra.Command) string { 269 if cmd.Parent() != nil { 270 return fmt.Sprintf("%s.%s", cmd.Parent().Name(), cmd.Name()) 271 } 272 return fmt.Sprintf("%s", cmd.Name()) 273} 274