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  }