Пример #1
0
func newLogger(name string, debug bool) logging.Logger {
	log := logging.NewLogger(name)
	logHandler := logging.NewWriterHandler(os.Stderr)
	logHandler.Colorize = true
	log.SetHandler(logHandler)

	if debug {
		log.SetLevel(logging.DEBUG)
		logHandler.SetLevel(logging.DEBUG)
	}

	return log
}
Пример #2
0
func TestMain(m *testing.M) {
	var err error
	vg, err = NewVagrant(vagrantName)
	if err != nil {
		log.Fatalln(err)
	}

	vg.Log = logging.NewLogger("vagrantutil_test")
	vg.Log.SetLevel(logging.DEBUG)
	h := logging.NewWriterHandler(os.Stderr)
	h.SetLevel(logging.DEBUG)
	vg.Log.SetHandler(h)

	ret := m.Run()
	os.RemoveAll(vagrantName)
	os.Exit(ret)
}
Пример #3
0
func init() {
	discardLogger = logging.NewLogger("test")
	discardLogger.SetHandler(logging.NewWriterHandler(ioutil.Discard))
}
Пример #4
0
// UpdateCommand updates this binary if there's an update available.
func UpdateCommand(c *cli.Context, log logging.Logger, _ string) int {
	if len(c.Args()) != 0 {
		cli.ShowCommandHelp(c, "update")
		return 1
	}

	var (
		forceUpdate    = c.Bool("force")
		klientVersion  = c.Int("klient-version")
		klientChannel  = c.String("klient-channel")
		kdVersion      = c.Int("kd-version")
		kdChannel      = c.String("kd-channel")
		continueUpdate = c.Bool("continue")
	)

	if kdChannel == "" {
		kdChannel = config.Environment
	}

	if klientChannel == "" {
		klientChannel = config.Environment
	}

	// Create and open the log file, to be safe in case it's missing.
	f, err := createLogFile(LogFilePath)
	if err != nil {
		fmt.Println(`Error: Unable to open log files.`)
	} else {
		log.SetHandler(logging.NewWriterHandler(f))
		log.Info("Update created log file at %q", LogFilePath)
	}

	if !shouldTryUpdate(kdVersion, klientVersion, forceUpdate) {
		yesUpdate, err := checkUpdate()
		if err != nil {
			log.Error("Error checking if update is available. err:%s", err)
			fmt.Println(FailedCheckingUpdateAvailable)
			return 1
		}

		if !yesUpdate {
			fmt.Println("No update available.")
			return 0
		} else {
			fmt.Println("An update is available.")
		}
	}

	if kdVersion == 0 {
		var err error

		kdVersion, err = latestVersion(config.Konfig.Endpoints.KDLatest.Public.String())
		if err != nil {
			log.Error("Error fetching klientctl update version. err: %s", err)
			fmt.Println(FailedCheckingUpdateAvailable)
			return 1
		}
	}

	if klientVersion == 0 {
		var err error

		klientVersion, err = latestVersion(config.Konfig.Endpoints.KlientLatest.Public.String())
		if err != nil {
			log.Error("Error fetching klient update version. err: %s", err)
			fmt.Println(FailedCheckingUpdateAvailable)
			return 1
		}
	}

	klientPath := filepath.Join(KlientDirectory, "klient")
	klientctlPath := filepath.Join(KlientctlDirectory, "kd")
	klientUrl := config.S3Klient(klientVersion, klientChannel)
	klientctlUrl := config.S3Klientctl(kdVersion, kdChannel)

	// If --continue is not passed, download kd and then call the new kd binary with
	// `kd update --continue`, so that the new code handles updates to klient.sh,
	// service, and any migration code needed.
	if !continueUpdate {
		// Only show this message once.
		fmt.Println("Updating...")

		if err := downloadRemoteToLocal(klientctlUrl, klientctlPath); err != nil {
			log.Error("Error downloading klientctl. err:%s", err)
			fmt.Println(FailedDownloadUpdate)
			return 1
		}

		flags := flagsFromContext(c)
		// Very important to pass --continue for the subprocess.
		// --force also helps ensure it updates, since the subprocess is technically
		// the latest KD version.
		flags = append([]string{"update", "--continue=true", "--force=true"}, flags...)
		log.Info("%s", flags)
		cmd := exec.Command(klientctlPath, flags...)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		if err := cmd.Run(); err != nil {
			log.Error("error during --continue update. err: %s", err)
			return 1
		}

		return 0
	}

	klientSh := klientSh{
		User:          konfig.CurrentUser.Username,
		KlientBinPath: filepath.Join(KlientDirectory, "klient"),
	}

	// ensure the klient home dir is writeable by user
	if klientSh.User != "" {
		ensureWriteable(KlientctlDirectory, klientSh.User)
	}

	opts := &ServiceOptions{
		Username: klientSh.User,
	}

	s, err := newService(opts)
	if err != nil {
		log.Error("Error creating Service. err:%s", err)
		fmt.Println(GenericInternalNewCodeError)
		return 1
	}

	fmt.Printf("Stopping %s...\n", config.KlientName)

	// stop klient before we update it
	if err := s.Stop(); err != nil {
		log.Error("Error stopping Service. err:%s", err)
		fmt.Println(FailedStopKlient)
		return 1
	}

	if err := downloadRemoteToLocal(klientUrl, klientPath); err != nil {
		log.Error("Error downloading klient. err:%s", err)
		fmt.Println(FailedDownloadUpdate)
		return 1
	}

	klientScript := filepath.Join(KlientDirectory, "klient.sh")

	if err := klientSh.Create(klientScript); err != nil {
		log.Error("Error writing klient.sh file. err:%s", err)
		fmt.Println(FailedInstallingKlient)
		return 1
	}

	// try to migrate from old managed klient to new kd-installed klient
	switch runtime.GOOS {
	case "darwin":
		oldS, err := service.New(&serviceProgram{}, &service.Config{
			Name:       "com.koding.klient",
			Executable: klientScript,
		})

		if err != nil {
			break
		}

		oldS.Stop()
		oldS.Uninstall()
	}

	// try to uninstall first, otherwise Install may fail if
	// klient.plist or klient init script already exist
	s.Uninstall()

	// Install the klient binary as a OS service
	if err = s.Install(); err != nil {
		log.Error("Error installing Service. err:%s", err)
		fmt.Println(GenericInternalNewCodeError)
		return 1
	}

	// start klient now that it's done updating
	if err := s.Start(); err != nil {
		log.Error("Error starting Service. err:%s", err)
		fmt.Println(FailedStartKlient)
		return 1
	}

	// Best-effort attempts at fixinig permissions and ownership, ignore any errors.
	_ = uploader.FixPerms()
	_ = configstore.FixOwner()

	fmt.Printf("Successfully updated to latest version of %s.\n", config.Name)
	return 0
}
Пример #5
0
// The implementation of InstallCommandFactory, with an error return. This
// allows us to track the error metrics.
func InstallCommandFactory(c *cli.Context, log logging.Logger, _ string) (exit int, err error) {
	if len(c.Args()) != 1 {
		cli.ShowCommandHelp(c, "install")
		return 1, errors.New("incorrect cli usage: no args")
	}

	// Now that we created the logfile, set our logger handler to use that newly created
	// file, so that we can log errors during installation.
	f, err := createLogFile(LogFilePath)
	if err != nil {
		fmt.Println(`Error: Unable to open log files.`)
	} else {
		log.SetHandler(logging.NewWriterHandler(f))
		log.Info("Installation created log file at %q", LogFilePath)
	}

	// Track all failed installations.
	defer func() {
		if err != nil {
			log.Error(err.Error())
			metrics.TrackInstallFailed(err.Error(), config.VersionNum())
		}
	}()

	authToken := c.Args().Get(0)

	// We need to check if the authToken is somehow empty, because klient
	// will default to user/pass if there is no auth token (despite setting
	// the token flag)
	if strings.TrimSpace(authToken) == "" {
		cli.ShowCommandHelp(c, "install")
		return 1, errors.New("incorrect cli usage: missing token")
	}

	// Create the installation dir, if needed.
	if err := os.MkdirAll(KlientDirectory, 0755); err != nil {
		log.Error(
			"Error creating klient binary directory(s). path:%s, err:%s",
			KlientDirectory, err,
		)
		fmt.Println(FailedInstallingKlient)
		return 1, fmt.Errorf("failed creating klient binary: %s", err)
	}

	klientBinPath := filepath.Join(KlientDirectory, "klient")

	// TODO: Accept `kd install --user foo` flag to replace the
	// environ checking.
	klientSh := klientSh{
		User:          konfig.CurrentUser.Username,
		KlientBinPath: klientBinPath,
	}

	if err := klientSh.Create(filepath.Join(KlientDirectory, "klient.sh")); err != nil {
		err = fmt.Errorf("error writing klient.sh file: %s", err)
		fmt.Println(FailedInstallingKlient)
		return 1, err
	}

	fmt.Println("Downloading...")

	version, err := latestVersion(config.Konfig.Endpoints.KlientLatest.Public.String())
	if err != nil {
		fmt.Printf(FailedDownloadingKlient)
		return 1, fmt.Errorf("error getting latest klient version: %s", err)
	}

	if err := downloadRemoteToLocal(config.S3Klient(version, config.Environment), klientBinPath); err != nil {
		fmt.Printf(FailedDownloadingKlient)
		return 1, fmt.Errorf("error downloading klient binary: %s", err)
	}

	fmt.Printf("Created %s\n", klientBinPath)
	fmt.Printf(`Authenticating you to the %s

`, config.KlientName)

	cmd := exec.Command(klientBinPath, "-register",
		"-token", authToken,
		"--kontrol-url", strings.TrimSpace(c.String("kontrol")),
	)

	var errBuf bytes.Buffer

	// Note that we are *only* printing to Stdout. This is done because
	// Klient logs error messages to Stderr, and we want to control the UX for
	// that interaction.
	//
	// TODO: Logg Klient's Stderr message on error, if any.
	cmd.Stdout = os.Stdout
	cmd.Stdin = os.Stdin
	cmd.Stderr = &errBuf

	if err := cmd.Run(); err != nil {
		err = fmt.Errorf("error registering klient: %s, klient stderr: %s", err, errBuf.String())
		fmt.Println(FailedRegisteringKlient)
		return 1, err
	}

	// Best-effort attempts at fixinig permissions and ownership, ignore any errors.
	_ = configstore.FixOwner()

	opts := &ServiceOptions{
		Username: klientSh.User,
	}

	// Create our interface to the OS specific service
	s, err := newService(opts)
	if err != nil {
		fmt.Println(GenericInternalNewCodeError)
		return 1, fmt.Errorf("error creating Service: %s", err)
	}

	// try to uninstall first, otherwise Install may fail if
	// klient.plist or klient init script already exist
	s.Uninstall()

	// Install the klient binary as a OS service
	if err := s.Install(); err != nil {
		fmt.Println(GenericInternalNewCodeError)
		return 1, fmt.Errorf("error installing Service: %s", err)
	}

	// Tell the service to start. Normally it starts automatically, but
	// if the user told the service to stop (previously), it may not
	// start automatically.
	//
	// Note that the service may error if it is already running, so
	// we're ignoring any starting errors here. We will verify the
	// connection below, anyway.
	if err := s.Start(); err != nil {
		fmt.Println(FailedStartKlient)
		return 1, fmt.Errorf("error starting klient service: %s", err)
	}

	fmt.Println("Verifying installation...")
	err = WaitUntilStarted(config.Konfig.Endpoints.Klient.Private.String(), CommandAttempts, CommandWaitTime)

	// After X times, if err != nil we failed to connect to klient.
	// Inform the user.
	if err != nil {
		fmt.Println(FailedInstallingKlient)
		return 1, fmt.Errorf("error verifying the installation of klient: %s", err)
	}

	// Best-effort attempts at fixinig permissions and ownership, ignore any errors.
	_ = uploader.FixPerms()

	// track metrics
	metrics.TrackInstall(config.VersionNum())

	fmt.Printf("\n\nSuccessfully installed and started the %s!\n", config.KlientName)

	return 0, nil
}
Пример #6
0
func init() {
	DiscardLogger = logging.NewLogger("DiscardLogger")
	DiscardLogger.SetHandler(logging.NewWriterHandler(ioutil.Discard))
}
Пример #7
0
func run(args []string) {
	// For forward-compatibility with go1.5+, where GOMAXPROCS is
	// always set to a number of available cores.
	runtime.GOMAXPROCS(runtime.NumCPU())

	debug = debug || config.Konfig.Debug

	// The writer used for the logging output. Either a file, or /dev/null
	var logWriter io.Writer

	f, err := os.OpenFile(LogFilePath, os.O_WRONLY|os.O_APPEND, 0666)
	if err != nil {
		// Rather than exit `kd` because we are unable to log, we simple disable logging
		// by writing to /dev/null. This also measn that even if we can't load the log
		// file, the log instance is valid and doesn't have to be checked for being
		// nil before every usage.
		logWriter = ioutil.Discard
	} else {
		logWriter = f
		ctlcli.CloseOnExit(f)
	}

	// Setting the handler to debug, because various methods allow a
	// debug option, and this saves us from having to set the handler level every time.
	// This only sets handler, not the actual loglevel.
	handler := logging.NewWriterHandler(logWriter)
	handler.SetLevel(logging.DEBUG)
	// Create our logger.
	//
	// TODO: Single commit temporary solution, need to remove the above logger
	// in favor of this.
	log = logging.NewLogger("kd")
	log.SetHandler(handler)
	log.Info("kd binary called with: %s", os.Args)

	if debug {
		log.SetLevel(logging.DEBUG)
	}

	// Check if the command the user is giving requires sudo.
	if err := AdminRequired(os.Args, sudoRequiredFor, util.NewPermissions()); err != nil {
		// In the event of an error, simply print the error to the user
		// and exit.
		fmt.Println("Error: this command requires sudo.")
		ctlcli.Close()
		os.Exit(10)
	}

	kloud.DefaultLog = log
	testKloudHook(kloud.DefaultClient)
	defer ctlcli.Close()

	// TODO(leeola): deprecate this default, instead passing it as a dependency
	// to the users of it.
	//
	// init the defaultHealthChecker with the log.
	defaultHealthChecker = NewDefaultHealthChecker(log)

	app := cli.NewApp()
	app.Name = config.Name
	app.Version = getReadableVersion(config.Version)
	app.EnableBashCompletion = true

	app.Commands = []cli.Command{
		{
			Name:      "list",
			ShortName: "ls",
			Usage:     "List running machines for user.",
			Action:    ctlcli.ExitAction(CheckUpdateFirst(ListCommand, log, "list")),
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name:  "json",
					Usage: "Output in JSON format",
				},
				cli.BoolFlag{
					Name:  "all",
					Usage: "Include machines that have been offline for more than 24h.",
				},
			},
			Subcommands: []cli.Command{
				{
					Name:   "mounts",
					Usage:  "List the mounted machines.",
					Action: ctlcli.ExitAction(CheckUpdateFirst(MountsCommand, log, "mounts")),
					Flags: []cli.Flag{
						cli.BoolFlag{
							Name:  "json",
							Usage: "Output in JSON format",
						},
					},
				},
			},
		},
		{
			Name:        "version",
			Usage:       "Display version information.",
			HideHelp:    true,
			Description: cmdDescriptions["version"],
			Action:      ctlcli.ExitAction(VersionCommand, log, "version"),
		},
		{
			Name:        "mount",
			ShortName:   "m",
			Usage:       "Mount a remote folder to a local folder.",
			Description: cmdDescriptions["mount"],
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "remotepath, r",
					Usage: "Full path of remote folder in machine to mount.",
				},
				cli.BoolFlag{
					Name:  "oneway-sync, s",
					Usage: "Copy remote folder to local and sync on interval. (fastest runtime).",
				},
				cli.IntFlag{
					Name:  "oneway-interval",
					Usage: "Sets how frequently local folder will sync with remote, in seconds. ",
					Value: 2,
				},
				cli.BoolFlag{
					Name:  "fuse, f",
					Usage: "Mount the remote folder via Fuse.",
				},
				cli.BoolFlag{
					Name:  "noprefetch-meta, p",
					Usage: "For fuse: Retrieve only top level folder/files. Rest is fetched on request (fastest to mount).",
				},
				cli.BoolFlag{
					Name:  "prefetch-all, a",
					Usage: "For fuse: Prefetch all contents of the remote directory up front.",
				},
				cli.IntFlag{
					Name:  "prefetch-interval",
					Usage: "For fuse: Sets how frequently remote folder will sync with local, in seconds.",
				},
				cli.BoolFlag{
					Name:  "nowatch, w",
					Usage: "For fuse: Disable watching for changes on remote machine.",
				},
				cli.BoolFlag{
					Name:  "noignore, i",
					Usage: "For fuse: Retrieve all files and folders, including ignored folders like .git & .svn.",
				},
				cli.BoolFlag{
					Name:  "trace, t",
					Usage: "Turn on trace logs.",
				},
			},
			Action: ctlcli.FactoryAction(MountCommandFactory, log, "mount"),
			BashComplete: ctlcli.FactoryCompletion(
				MountCommandFactory, log, "mount",
			),
		},
		{
			Name:        "unmount",
			ShortName:   "u",
			Usage:       "Unmount previously mounted machine.",
			Description: cmdDescriptions["unmount"],
			Action:      ctlcli.FactoryAction(UnmountCommandFactory, log, "unmount"),
		},
		{
			Name:        "remount",
			ShortName:   "r",
			Usage:       "Remount previously mounted machine using same settings.",
			Description: cmdDescriptions["remount"],
			Action:      ctlcli.ExitAction(RemountCommandFactory, log, "remount"),
		},
		{
			Name:        "ssh",
			ShortName:   "s",
			Usage:       "SSH into the machine.",
			Description: cmdDescriptions["ssh"],
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name: "debug",
				},
				cli.StringFlag{
					Name:  "username",
					Usage: "The username to ssh into on the remote machine.",
				},
			},
			Action: ctlcli.ExitAction(CheckUpdateFirst(SSHCommandFactory, log, "ssh")),
		},
		{
			Name:            "run",
			Usage:           "Run command on remote or local machine.",
			Description:     cmdDescriptions["run"],
			Action:          ctlcli.ExitAction(RunCommandFactory, log, "run"),
			SkipFlagParsing: true,
		},
		{
			Name:   "repair",
			Usage:  "Repair the given mount",
			Action: ctlcli.FactoryAction(RepairCommandFactory, log, "repair"),
		},
		{
			Name:        "status",
			Usage:       fmt.Sprintf("Check status of the %s.", config.KlientName),
			Description: cmdDescriptions["status"],
			Action:      ctlcli.ExitAction(StatusCommand, log, "status"),
		},
		{
			Name:        "update",
			Usage:       fmt.Sprintf("Update %s to latest version.", config.KlientName),
			Description: cmdDescriptions["update"],
			Action:      ctlcli.ExitAction(UpdateCommand, log, "update"),
			Flags: []cli.Flag{
				cli.IntFlag{
					Name:  "kd-version",
					Usage: "Version of KD (klientctl) to update to.",
				},
				cli.StringFlag{
					Name:  "kd-channel",
					Usage: "Channel (production|development) to download update from.",
				},
				cli.IntFlag{
					Name:  "klient-version",
					Usage: "Version of klient to update to.",
				},
				cli.StringFlag{
					Name:  "klient-channel",
					Usage: "Channel (production|development) to download update from.",
				},
				cli.BoolFlag{
					Name:  "force",
					Usage: "Updates kd & klient to latest available version.",
				},
				cli.BoolFlag{
					Name:   "continue",
					Usage:  "Internal use only.",
					Hidden: true,
				},
			},
		},
		{
			Name:        "restart",
			Usage:       fmt.Sprintf("Restart the %s.", config.KlientName),
			Description: cmdDescriptions["restart"],
			Action:      ctlcli.ExitAction(RestartCommand, log, "restart"),
		},
		{
			Name:        "start",
			Usage:       fmt.Sprintf("Start the %s.", config.KlientName),
			Description: cmdDescriptions["start"],
			Action:      ctlcli.ExitAction(StartCommand, log, "start"),
		},
		{
			Name:        "stop",
			Usage:       fmt.Sprintf("Stop the %s.", config.KlientName),
			Description: cmdDescriptions["stop"],
			Action:      ctlcli.ExitAction(StopCommand, log, "stop"),
		},
		{
			Name:        "uninstall",
			Usage:       fmt.Sprintf("Uninstall the %s.", config.KlientName),
			Description: cmdDescriptions["uninstall"],
			Action:      ExitWithMessage(UninstallCommand, log, "uninstall"),
		},
		{
			Name:        "install",
			Usage:       fmt.Sprintf("Install the %s.", config.KlientName),
			Description: cmdDescriptions["install"],
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "kontrol, k",
					Usage: "Specify an alternate Kontrol",
				},
			},
			Action: ctlcli.ExitErrAction(InstallCommandFactory, log, "install"),
		},
		{
			Name:     "metrics",
			Usage:    fmt.Sprintf("Internal use only."),
			HideHelp: true,
			Action:   ctlcli.ExitAction(MetricsCommandFactory, log, "metrics"),
		},
		{
			Name:        "autocompletion",
			Usage:       "Enable autocompletion support for bash and fish shells",
			Description: cmdDescriptions["autocompletion"],
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "fish-dir",
					Usage: "The name of directory to add fish autocompletion script.",
				},
				cli.BoolFlag{
					Name:  "no-bashrc",
					Usage: "Disable appending autocompletion source command to your bash config file.",
				},
				cli.StringFlag{
					Name:  "bash-dir",
					Usage: "The name of directory to add bash autocompletion script.",
				},
			},
			Action: ctlcli.FactoryAction(
				AutocompleteCommandFactory, log, "autocompletion",
			),
			BashComplete: ctlcli.FactoryCompletion(
				AutocompleteCommandFactory, log, "autocompletion",
			),
		},
		{
			Name: "cp",
			Usage: fmt.Sprintf(
				"Copy a file from one one machine to another",
			),
			Description: cmdDescriptions["cp"],
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name: "debug",
				},
			},
			Action: ctlcli.FactoryAction(
				CpCommandFactory, log, "cp",
			),
			BashComplete: ctlcli.FactoryCompletion(
				CpCommandFactory, log, "cp",
			),
		},
		{
			Name:  "log",
			Usage: "Display logs.",
			Flags: []cli.Flag{
				cli.BoolFlag{Name: "debug", Hidden: true},
				cli.BoolFlag{Name: "no-kd-log"},
				cli.BoolFlag{Name: "no-klient-log"},
				cli.StringFlag{Name: "kd-log-file"},
				cli.StringFlag{Name: "klient-log-file"},
				cli.IntFlag{Name: "lines, n"},
			},
			Action: ctlcli.FactoryAction(LogCommandFactory, log, "log"),
		},
		{
			Name: "open",
			Usage: fmt.Sprintf(
				"Open the given file(s) on the Koding UI",
			),
			Description: cmdDescriptions["open"],
			Flags: []cli.Flag{
				cli.BoolFlag{Name: "debug"},
			},
			Action: ctlcli.FactoryAction(OpenCommandFactory, log, "log"),
		},
	}

	if experimental {
		app.Commands = append(app.Commands,
			cli.Command{
				Name:  "auth",
				Usage: "User authorization.",
				Subcommands: []cli.Command{
					{
						Name:   "login",
						Usage:  "Log in to your kd.io or koding.com account.",
						Action: ctlcli.ExitErrAction(AuthLogin, log, "login"),
						Flags: []cli.Flag{
							cli.BoolFlag{
								Name:  "json",
								Usage: "Output in JSON format.",
							},
							cli.StringFlag{
								Name:  "team, t",
								Usage: "Specify a Koding team to log in. Leaving empty logs in to kd.io by default.",
							},
							cli.StringFlag{
								Name:  "baseurl",
								Usage: "Specify a Koding endpoint to log in.",
								Value: config.Konfig.Endpoints.Koding.Public.String(),
							},
						},
					},
					// command: kd auth register
					auth.NewRegisterSubCommand(log),
				},
			},
			cli.Command{
				Name:  "config",
				Usage: "Manage tool configuration.",
				Subcommands: []cli.Command{{
					Name:   "show",
					Usage:  "Show configuration.",
					Action: ctlcli.ExitErrAction(ConfigShow, log, "show"),
					Flags: []cli.Flag{
						cli.BoolFlag{
							Name:  "defaults",
							Usage: "Show also default configuration",
						},
						cli.BoolFlag{
							Name:  "json",
							Usage: "Output in JSON format.",
						},
					},
				}, {
					Name:      "list",
					ShortName: "ls",
					Usage:     "List all available configurations.",
					Action:    ctlcli.ExitErrAction(ConfigList, log, "list"),
					Flags: []cli.Flag{
						cli.BoolFlag{
							Name:  "json",
							Usage: "Output in JSON format.",
						},
					},
				}, {
					Name:   "use",
					Usage:  "Change active configuration.",
					Action: ctlcli.ExitErrAction(ConfigUse, log, "use"),
				}, {
					Name:   "set",
					Usage:  "Set a value for the given key, overwriting default one.",
					Action: ctlcli.ExitErrAction(ConfigSet, log, "set"),
				}, {
					Name:   "unset",
					Usage:  "Unset the given key, restoring the defaut value.",
					Action: ctlcli.ExitErrAction(ConfigUnset, log, "set"),
				}, {
					Name:   "reset",
					Usage:  "Resets configuration to the default value fetched from Koding.",
					Action: ctlcli.ExitErrAction(ConfigReset, log, "reset"),
					Flags: []cli.Flag{
						cli.BoolFlag{
							Name:  "force",
							Usage: "Force retrieving configuration from Koding.",
						},
					},
				}},
			},
			cli.Command{
				Name:      "credential",
				ShortName: "c",
				Usage:     "Manage stack credentials.",
				Subcommands: []cli.Command{{
					Name:      "list",
					ShortName: "ls",
					Usage:     "List imported stack credentials.",
					Action:    ctlcli.ExitErrAction(CredentialList, log, "list"),
					Flags: []cli.Flag{
						cli.BoolFlag{
							Name:  "json",
							Usage: "Output in JSON format.",
						},
						cli.StringFlag{
							Name:  "provider, p",
							Usage: "Specify credential provider.",
						},
						cli.StringFlag{
							Name:  "team, t",
							Usage: "Specify team which the credential belongs to.",
						},
					},
				}, {
					Name:   "create",
					Usage:  "Create new stack credential.",
					Action: ctlcli.ExitErrAction(CredentialCreate, log, "create"),
					Flags: []cli.Flag{
						cli.BoolFlag{
							Name:  "json",
							Usage: "Output in JSON format.",
						},
						cli.StringFlag{
							Name:  "provider, p",
							Usage: "Specify credential provider.",
						},
						cli.StringFlag{
							Name:  "file, f",
							Value: "",
							Usage: "Read credential from a file.",
						},
						cli.StringFlag{
							Name:  "team, t",
							Usage: "Specify team which the credential belongs to.",
						},
						cli.StringFlag{
							Name:  "title",
							Usage: "Specify credential title.",
						},
					},
				}, {
					Name:   "use",
					Usage:  "Change default credential per provider.",
					Action: ctlcli.ExitErrAction(CredentialUse, log, "use"),
				}, {
					Name:   "describe",
					Usage:  "Describe credential documents.",
					Action: ctlcli.ExitErrAction(CredentialDescribe, log, "describe"),
					Flags: []cli.Flag{
						cli.BoolFlag{
							Name:  "json",
							Usage: "Output in JSON format.",
						},
						cli.StringFlag{
							Name:  "provider, p",
							Usage: "Specify credential provider.",
						},
					},
				}},
			},
			cli.Command{
				Name:  "machine",
				Usage: "Manage remote machines.",
				Subcommands: []cli.Command{{
					Name:      "list",
					ShortName: "ls",
					Usage:     "List available machines.",
					Action:    ctlcli.ExitErrAction(MachineListCommand, log, "list"),
					Flags: []cli.Flag{
						cli.BoolFlag{
							Name:  "json",
							Usage: "Output in JSON format.",
						},
					},
				}, {
					Name:      "ssh",
					ShortName: "s",
					Usage:     "SSH into provided remote machine.",
					Action:    ctlcli.ExitErrAction(MachineSSHCommand, log, "ssh"),
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "username",
							Usage: "Remote machine username.",
						},
					},
				}},
			},
			cli.Command{
				Name:  "stack",
				Usage: "Manage stacks.",
				Subcommands: []cli.Command{{
					Name:   "create",
					Usage:  "Create new stack.",
					Action: ctlcli.ExitErrAction(StackCreate, log, "create"),
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "provider, p",
							Usage: "Specify stack provider.",
						},
						cli.StringSliceFlag{
							Name:  "credential, c",
							Usage: "Specify stack credentials.",
						},
						cli.StringFlag{
							Name:  "team, t",
							Usage: "Specify team which the stack belongs to.",
						},
						cli.StringFlag{
							Name:  "file, f",
							Value: "kd.yml",
							Usage: "Read stack template from a file.",
						},
						cli.BoolFlag{
							Name:  "json",
							Usage: "Output in JSON format.",
						},
					},
				}},
			},
			cli.Command{
				Name:  "team",
				Usage: "List available teams and set team context.",
				Subcommands: []cli.Command{{
					Name:   "show",
					Usage:  "Shows your currently used team.",
					Action: ctlcli.ExitErrAction(TeamShow, log, "show"),
					Flags: []cli.Flag{
						cli.BoolFlag{
							Name:  "json",
							Usage: "Output in JSON format.",
						},
					},
				}, {
					Name:   "list",
					Usage:  "Lists user's teams.",
					Action: ctlcli.ExitErrAction(TeamList, log, "list"),
					Flags: []cli.Flag{
						cli.StringFlag{
							Name:  "slug",
							Value: "",
							Usage: "Limits the output to the specified team slug",
						},
						cli.BoolFlag{
							Name:  "json",
							Usage: "Output in JSON format.",
						},
					},
				},
				},
			},
		)
	}

	app.Run(args)
}