github.com/koron/hk@v0.0.0-20150303213137-b8aeaa3ab34c/main.go (about) 1 package main 2 3 import ( 4 "bufio" 5 "fmt" 6 "log" 7 "os" 8 "runtime" 9 "strings" 10 11 "github.com/heroku/hk/Godeps/_workspace/src/github.com/bgentry/heroku-go" 12 flag "github.com/heroku/hk/Godeps/_workspace/src/github.com/bgentry/pflag" 13 "github.com/heroku/hk/Godeps/_workspace/src/github.com/mgutz/ansi" 14 "github.com/heroku/hk/hkclient" 15 "github.com/heroku/hk/postgresql" 16 "github.com/heroku/hk/rollbar" 17 "github.com/heroku/hk/term" 18 ) 19 20 var ( 21 apiURL = "https://api.heroku.com" 22 stdin = bufio.NewReader(os.Stdin) 23 ) 24 25 type Command struct { 26 // args does not include the command name 27 Run func(cmd *Command, args []string) 28 Flag flag.FlagSet 29 NeedsApp bool 30 31 Usage string // first word is the command name 32 Category string // i.e. "App", "Account", etc. 33 Short string // `hk help` output 34 Long string // `hk help cmd` output 35 } 36 37 func (c *Command) PrintUsage() { 38 if c.Runnable() { 39 fmt.Fprintf(os.Stderr, "Usage: hk %s\n", c.FullUsage()) 40 } 41 fmt.Fprintf(os.Stderr, "Use 'hk help %s' for more information.\n", c.Name()) 42 } 43 44 func (c *Command) PrintLongUsage() { 45 if c.Runnable() { 46 fmt.Printf("Usage: hk %s\n\n", c.FullUsage()) 47 } 48 fmt.Println(strings.Trim(c.Long, "\n")) 49 } 50 51 func (c *Command) FullUsage() string { 52 if c.NeedsApp { 53 return c.Name() + " [-a <app or remote>]" + strings.TrimPrefix(c.Usage, c.Name()) 54 } 55 return c.Usage 56 } 57 58 func (c *Command) Name() string { 59 name := c.Usage 60 i := strings.Index(name, " ") 61 if i >= 0 { 62 name = name[:i] 63 } 64 return name 65 } 66 67 func (c *Command) Runnable() bool { 68 return c.Run != nil 69 } 70 71 const extra = " (extra)" 72 73 func (c *Command) List() bool { 74 return c.Short != "" && !strings.HasSuffix(c.Short, extra) 75 } 76 77 func (c *Command) ListAsExtra() bool { 78 return c.Short != "" && strings.HasSuffix(c.Short, extra) 79 } 80 81 func (c *Command) ShortExtra() string { 82 return c.Short[:len(c.Short)-len(extra)] 83 } 84 85 // Running `hk help` will list commands in this order. 86 var commands = []*Command{ 87 cmdCreate, 88 cmdApps, 89 cmdDynos, 90 cmdReleases, 91 cmdReleaseInfo, 92 cmdRollback, 93 cmdAddons, 94 cmdAddonAdd, 95 cmdAddonDestroy, 96 cmdScale, 97 cmdRestart, 98 cmdSet, 99 cmdUnset, 100 cmdEnv, 101 cmdRun, 102 cmdLog, 103 cmdInfo, 104 cmdRename, 105 cmdDestroy, 106 cmdDomains, 107 cmdDomainAdd, 108 cmdDomainRemove, 109 cmdVersion, 110 cmdHelp, 111 112 helpCommands, 113 helpEnviron, 114 helpPlugins, 115 helpMore, 116 helpAbout, 117 118 // listed by hk help more 119 cmdAccess, 120 cmdAccessAdd, 121 cmdAccessRemove, 122 cmdAccountFeatures, 123 cmdAccountFeatureInfo, 124 cmdAccountFeatureEnable, 125 cmdAccountFeatureDisable, 126 cmdAddonOpen, 127 cmdAddonPlan, 128 cmdAddonPlans, 129 cmdAddonServices, 130 cmdAPI, 131 cmdAuthorize, 132 cmdCreds, 133 cmdDrains, 134 cmdDrainInfo, 135 cmdDrainAdd, 136 cmdDrainRemove, 137 cmdFeatures, 138 cmdFeatureInfo, 139 cmdFeatureEnable, 140 cmdFeatureDisable, 141 cmdGet, 142 cmdKeys, 143 cmdKeyAdd, 144 cmdKeyRemove, 145 cmdLogin, 146 cmdLogout, 147 cmdMaintenance, 148 cmdMaintenanceEnable, 149 cmdMaintenanceDisable, 150 cmdMembers, 151 cmdOpen, 152 cmdOrgs, 153 cmdPgList, 154 cmdPgInfo, 155 cmdPgUnfollow, 156 cmdPsql, 157 cmdRegions, 158 cmdSSL, 159 cmdSSLCertAdd, 160 cmdSSLCertRollback, 161 cmdSSLDestroy, 162 cmdStatus, 163 cmdTransfer, 164 cmdTransfers, 165 cmdTransferAccept, 166 cmdTransferDecline, 167 cmdTransferCancel, 168 cmdURL, 169 cmdWhichApp, 170 171 // unlisted 172 cmdUpdate, 173 } 174 175 var ( 176 flagApp string 177 client *heroku.Client 178 pgclient *postgresql.Client 179 hkAgent = "hk/" + Version + " (" + runtime.GOOS + "; " + runtime.GOARCH + ")" 180 userAgent = hkAgent + " " + heroku.DefaultUserAgent 181 ) 182 183 func initClients() { 184 loadNetrc() 185 suite, err := hkclient.New(nrc, hkAgent) 186 if err != nil { 187 printFatal(err.Error()) 188 } 189 190 client = suite.Client 191 pgclient = suite.PgClient 192 apiURL = suite.ApiURL 193 } 194 195 func main() { 196 log.SetFlags(0) 197 198 // make sure command is specified, disallow global args 199 args := os.Args[1:] 200 if len(args) < 1 || strings.IndexRune(args[0], '-') == 0 { 201 printUsageTo(os.Stderr) 202 os.Exit(2) 203 } 204 205 // Run the update command as early as possible to avoid the possibility of 206 // installations being stranded without updates due to errors in other code 207 if args[0] == cmdUpdate.Name() { 208 cmdUpdate.Run(cmdUpdate, args) 209 return 210 } else if updater != nil { 211 defer updater.backgroundRun() // doesn't run if os.Exit is called 212 } 213 214 if !term.IsANSI(os.Stdout) { 215 ansi.DisableColors(true) 216 } 217 218 initClients() 219 220 for _, cmd := range commands { 221 if cmd.Name() == args[0] && cmd.Run != nil { 222 defer recoverPanic() 223 224 cmd.Flag.SetDisableDuplicates(true) // disallow duplicate flag options 225 if !gitConfigBool("hk.strict-flag-ordering") { 226 cmd.Flag.SetInterspersed(true) // allow flags & non-flag args to mix 227 } 228 cmd.Flag.Usage = func() { 229 cmd.PrintUsage() 230 } 231 if cmd.NeedsApp { 232 cmd.Flag.StringVarP(&flagApp, "app", "a", "", "app name") 233 } 234 if err := cmd.Flag.Parse(args[1:]); err == flag.ErrHelp { 235 cmdHelp.Run(cmdHelp, args[:1]) 236 return 237 } else if err != nil { 238 printError(err.Error()) 239 os.Exit(2) 240 } 241 if flagApp != "" { 242 if gitRemoteApp, err := appFromGitRemote(flagApp); err == nil { 243 flagApp = gitRemoteApp 244 } 245 } 246 if cmd.NeedsApp { 247 a, err := app() 248 switch { 249 case err == errMultipleHerokuRemotes, err == nil && a == "": 250 msg := "no app specified" 251 if err != nil { 252 msg = err.Error() 253 } 254 printError(msg) 255 cmd.PrintUsage() 256 os.Exit(2) 257 case err != nil: 258 printFatal(err.Error()) 259 } 260 } 261 cmd.Run(cmd, cmd.Flag.Args()) 262 return 263 } 264 } 265 266 path := findPlugin(args[0]) 267 if path == "" { 268 fmt.Fprintf(os.Stderr, "Unknown command: %s\n", args[0]) 269 if g := suggest(args[0]); len(g) > 0 { 270 fmt.Fprintf(os.Stderr, "Possible alternatives: %v\n", strings.Join(g, " ")) 271 } 272 fmt.Fprintf(os.Stderr, "Run 'hk help' for usage.\n") 273 os.Exit(2) 274 } 275 err := execPlugin(path, args) 276 printFatal("exec error: %s", err) 277 } 278 279 var rollbarClient = &rollbar.Client{ 280 AppName: "hk", 281 AppVersion: Version, 282 Endpoint: "https://api.rollbar.com/api/1/item/", 283 Token: "d344db7a09fa481e983694bfa326e6d9", 284 } 285 286 func recoverPanic() { 287 if Version != "dev" { 288 if rec := recover(); rec != nil { 289 message := "" 290 switch rec := rec.(type) { 291 case error: 292 message = rec.Error() 293 default: 294 message = fmt.Sprintf("%v", rec) 295 } 296 if err := rollbarClient.Report(message); err != nil { 297 printError("reporting crash failed: %s", err.Error()) 298 panic(rec) 299 } 300 printFatal("hk encountered and reported an internal client error") 301 } 302 } 303 } 304 305 func app() (string, error) { 306 if flagApp != "" { 307 return flagApp, nil 308 } 309 310 if app := os.Getenv("HKAPP"); app != "" { 311 return app, nil 312 } 313 314 return appFromGitRemote(remoteFromGitConfig()) 315 } 316 317 func mustApp() string { 318 name, err := app() 319 if err != nil { 320 printFatal(err.Error()) 321 } 322 return name 323 }