Browse Source

Remove global commands (#3428)

Stefan McShane 2 years ago
parent
commit
63d3960ffc

+ 4 - 4
api/client/api.go

@@ -29,9 +29,6 @@ type Client struct {
 
 
 	// cfToken is a cloudflare token for accessing the API
 	// cfToken is a cloudflare token for accessing the API
 	cfToken string
 	cfToken string
-
-	// // Config contains all config read from flags, environment variables, or porter.yaml config. This is used to automatically pull hosts, projectIDs, clusterIDs etc. in API calls
-	// Config config.CLIConfig
 }
 }
 
 
 // NewClientInput contains all information required to create a new API Client
 // NewClientInput contains all information required to create a new API Client
@@ -79,9 +76,12 @@ func NewClientWithConfig(ctx context.Context, input NewClientInput) (Client, err
 		}
 		}
 		return client, nil
 		return client, nil
 	}
 	}
-	return client, errors.New("unable to create an API session with cookie nor token")
+	return client, ErrNoAuthCredential
 }
 }
 
 
+// ErrNoAuthCredential returns an error when no auth credentials have been provided such as cookies or tokens
+var ErrNoAuthCredential = errors.New("unable to create an API session with cookie nor token")
+
 // NewClient constructs a new client based on a set of options
 // NewClient constructs a new client based on a set of options
 func NewClient(baseURL string, cookieFileName string) *Client {
 func NewClient(baseURL string, cookieFileName string) *Client {
 	home := homedir.HomeDir()
 	home := homedir.HomeDir()

+ 52 - 0
cli/cmd/commands/all.go

@@ -0,0 +1,52 @@
+package commands
+
+import (
+	"fmt"
+
+	"github.com/porter-dev/porter/cli/cmd/config"
+	"github.com/porter-dev/porter/cli/cmd/utils"
+	"github.com/spf13/cobra"
+)
+
+// RegisterCommands initiates config and sets up all commands.
+// Error returned here is a placeholder as the register commands do not currently return errors, and handle exits themselves. This may change at a later date.
+func RegisterCommands() (*cobra.Command, error) {
+	cliConf, err := config.InitAndLoadConfig()
+	if err != nil {
+		return nil, fmt.Errorf("error loading porter config: %w", err)
+	}
+
+	rootCmd := &cobra.Command{
+		Use:   "porter",
+		Short: "Porter is a dashboard for managing Kubernetes clusters.",
+		Long:  `Porter is a tool for creating, versioning, and updating Kubernetes deployments using a visual dashboard. For more information, visit github.com/porter-dev/porter`,
+	}
+	rootCmd.PersistentFlags().AddFlagSet(utils.DefaultFlagSet)
+
+	rootCmd.AddCommand(registerCommand_App(cliConf))
+	rootCmd.AddCommand(registerCommand_Apply(cliConf))
+	rootCmd.AddCommand(registerCommand_Auth(cliConf))
+	rootCmd.AddCommand(registerCommand_Cluster(cliConf))
+	rootCmd.AddCommand(registerCommand_Config(cliConf))
+	rootCmd.AddCommand(registerCommand_Connect(cliConf))
+	rootCmd.AddCommand(registerCommand_Create(cliConf))
+	rootCmd.AddCommand(registerCommand_Delete(cliConf))
+	rootCmd.AddCommand(registerCommand_Deploy(cliConf))
+	rootCmd.AddCommand(registerCommand_Docker(cliConf))
+	rootCmd.AddCommand(registerCommand_Get(cliConf))
+	rootCmd.AddCommand(registerCommand_Helm(cliConf))
+	rootCmd.AddCommand(registerCommand_Job(cliConf))
+	rootCmd.AddCommand(registerCommand_Kubectl(cliConf))
+	rootCmd.AddCommand(registerCommand_List(cliConf))
+	rootCmd.AddCommand(registerCommand_Logs(cliConf))
+	rootCmd.AddCommand(registerCommand_Open(cliConf))
+	rootCmd.AddCommand(registerCommand_PortForward(cliConf))
+	rootCmd.AddCommand(registerCommand_Project(cliConf))
+	rootCmd.AddCommand(registerCommand_Registry(cliConf))
+	rootCmd.AddCommand(registerCommand_Run(cliConf))
+	rootCmd.AddCommand(registerCommand_Server(cliConf))
+	rootCmd.AddCommand(registerCommand_Stack(cliConf))
+	rootCmd.AddCommand(registerCommand_Update(cliConf))
+	rootCmd.AddCommand(registerCommand_Version(cliConf))
+	return rootCmd, nil
+}

+ 57 - 56
cli/cmd/app.go → cli/cmd/commands/app.go

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 
 import (
 import (
 	"context"
 	"context"
@@ -43,55 +43,67 @@ var (
 	appMemoryMi      int
 	appMemoryMi      int
 )
 )
 
 
-// appCmd represents the "porter app" base command when called
-// without any subcommands
-var appCmd = &cobra.Command{
-	Use:   "app",
-	Short: "Runs a command for your application.",
-}
+func registerCommand_App(cliConf config.CLIConfig) *cobra.Command {
+	appCmd := &cobra.Command{
+		Use:   "app",
+		Short: "Runs a command for your application.",
+	}
+
+	// appRunCmd represents the "porter app run" subcommand
+	appRunCmd := &cobra.Command{
+		Use:   "run [application] -- COMMAND [args...]",
+		Args:  cobra.MinimumNArgs(2),
+		Short: "Runs a command inside a connected cluster container.",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, appRun)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
+	appRunFlags(appRunCmd)
+	appCmd.AddCommand(appRunCmd)
 
 
-// appRunCmd represents the "porter app run" subcommand
-var appRunCmd = &cobra.Command{
-	Use:   "run [application] -- COMMAND [args...]",
-	Args:  cobra.MinimumNArgs(2),
-	Short: "Runs a command inside a connected cluster container.",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, appRun)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+	// appRunCleanupCmd represents the "porter app run cleanup" subcommand
+	appRunCleanupCmd := &cobra.Command{
+		Use:   "cleanup",
+		Args:  cobra.NoArgs,
+		Short: "Delete any lingering ephemeral pods that were created with \"porter app run\".",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, appCleanup)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
+	appRunCmd.AddCommand(appRunCleanupCmd)
 
 
-// appRunCleanupCmd represents the "porter app run cleanup" subcommand
-var appRunCleanupCmd = &cobra.Command{
-	Use:   "cleanup",
-	Args:  cobra.NoArgs,
-	Short: "Delete any lingering ephemeral pods that were created with \"porter app run\".",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, appCleanup)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+	// appUpdateTagCmd represents the "porter app update-tag" subcommand
+	appUpdateTagCmd := &cobra.Command{
+		Use:   "update-tag [application]",
+		Args:  cobra.MinimumNArgs(1),
+		Short: "Updates the image tag for an application.",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, appUpdateTag)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-// appUpdateTagCmd represents the "porter app update-tag" subcommand
-var appUpdateTagCmd = &cobra.Command{
-	Use:   "update-tag [application]",
-	Args:  cobra.MinimumNArgs(1),
-	Short: "Updates the image tag for an application.",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, appUpdateTag)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+	appUpdateTagCmd.PersistentFlags().StringVarP(
+		&appTag,
+		"tag",
+		"t",
+		"",
+		"the specified tag to use, default is \"latest\"",
+	)
+	appCmd.AddCommand(appUpdateTagCmd)
 
 
-func init() {
-	rootCmd.AddCommand(appCmd)
+	return appCmd
+}
 
 
+func appRunFlags(appRunCmd *cobra.Command) {
 	appRunCmd.PersistentFlags().BoolVarP(
 	appRunCmd.PersistentFlags().BoolVarP(
 		&appExistingPod,
 		&appExistingPod,
 		"existing_pod",
 		"existing_pod",
@@ -138,17 +150,6 @@ func init() {
 		"",
 		"",
 		"name of the container inside pod to run the command in",
 		"name of the container inside pod to run the command in",
 	)
 	)
-	appRunCmd.AddCommand(appRunCleanupCmd)
-
-	appUpdateTagCmd.PersistentFlags().StringVarP(
-		&appTag,
-		"tag",
-		"t",
-		"",
-		"the specified tag to use, default is \"latest\"",
-	)
-	appCmd.AddCommand(appRunCmd)
-	appCmd.AddCommand(appUpdateTagCmd)
 }
 }
 
 
 func appRun(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, args []string) error {
 func appRun(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, args []string) error {

+ 38 - 41
cli/cmd/apply.go → cli/cmd/commands/apply.go

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 
 import (
 import (
 	"context"
 	"context"
@@ -39,12 +39,13 @@ import (
 	"gopkg.in/yaml.v2"
 	"gopkg.in/yaml.v2"
 )
 )
 
 
-// applyCmd represents the "porter apply" base command when called
-// with a porter.yaml file as an argument
-var applyCmd = &cobra.Command{
-	Use:   "apply",
-	Short: "Applies a configuration to an application",
-	Long: fmt.Sprintf(`
+var porterYAML string
+
+func registerCommand_Apply(cliConf config.CLIConfig) *cobra.Command {
+	applyCmd := &cobra.Command{
+		Use:   "apply",
+		Short: "Applies a configuration to an application",
+		Long: fmt.Sprintf(`
 %s
 %s
 
 
 Applies a configuration to an application by either creating a new one or updating an existing
 Applies a configuration to an application by either creating a new one or updating an existing
@@ -68,47 +69,43 @@ applying a configuration:
   PORTER_SOURCE_VERSION       The version of the Helm chart to use
   PORTER_SOURCE_VERSION       The version of the Helm chart to use
   PORTER_TAG                  The Docker image tag to use (like the git commit hash)
   PORTER_TAG                  The Docker image tag to use (like the git commit hash)
 	`,
 	`,
-		color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter apply\":"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter apply -f porter.yaml"),
-	),
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, apply)
-		if err != nil {
-			if strings.Contains(err.Error(), "Forbidden") {
-				color.New(color.FgRed).Fprintf(os.Stderr, "You may have to update your GitHub secret token")
-			}
-
-			os.Exit(1)
-		}
-	},
-}
-
-// applyValidateCmd represents the "porter apply validate" command when called
-// with a porter.yaml file as an argument
-var applyValidateCmd = &cobra.Command{
-	Use:   "validate",
-	Short: "Validates a porter.yaml",
-	Run: func(*cobra.Command, []string) {
-		err := applyValidate()
-
-		if err != nil {
-			color.New(color.FgRed).Fprintf(os.Stderr, "Error: %s\n", err.Error())
-			os.Exit(1)
-		} else {
-			color.New(color.FgGreen).Printf("The porter.yaml file is valid!\n")
-		}
-	},
-}
+			color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter apply\":"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter apply -f porter.yaml"),
+		),
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, apply)
+			if err != nil {
+				if strings.Contains(err.Error(), "Forbidden") {
+					_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "You may have to update your GitHub secret token")
+				}
 
 
-var porterYAML string
+				os.Exit(1)
+			}
+		},
+	}
+	// applyValidateCmd represents the "porter apply validate" command when called
+	// with a porter.yaml file as an argument
+	applyValidateCmd := &cobra.Command{
+		Use:   "validate",
+		Short: "Validates a porter.yaml",
+		Run: func(*cobra.Command, []string) {
+			err := applyValidate()
 
 
-func init() {
-	rootCmd.AddCommand(applyCmd)
+			if err != nil {
+				_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "Error: %s\n", err.Error())
+				os.Exit(1)
+			} else {
+				_, _ = color.New(color.FgGreen).Printf("The porter.yaml file is valid!\n")
+			}
+		},
+	}
 
 
 	applyCmd.AddCommand(applyValidateCmd)
 	applyCmd.AddCommand(applyValidateCmd)
 
 
 	applyCmd.PersistentFlags().StringVarP(&porterYAML, "file", "f", "", "path to porter.yaml")
 	applyCmd.PersistentFlags().StringVarP(&porterYAML, "file", "f", "", "path to porter.yaml")
 	applyCmd.MarkFlagRequired("file")
 	applyCmd.MarkFlagRequired("file")
+
+	return applyCmd
 }
 }
 
 
 func apply(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, _ []string) (err error) {
 func apply(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, _ []string) (err error) {

+ 111 - 111
cli/cmd/auth.go → cli/cmd/commands/auth.go

@@ -1,9 +1,11 @@
-package cmd
+package commands
 
 
 import (
 import (
 	"context"
 	"context"
+	"errors"
 	"fmt"
 	"fmt"
 	"os"
 	"os"
+	"strings"
 
 
 	"github.com/fatih/color"
 	"github.com/fatih/color"
 
 
@@ -15,50 +17,48 @@ import (
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 )
 )
 
 
-var authCmd = &cobra.Command{
-	Use:   "auth",
-	Short: "Commands for authenticating to a Porter server",
-}
-
-var loginCmd = &cobra.Command{
-	Use:   "login",
-	Short: "Authorizes a user for a given Porter server",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := login(cmd.Context())
-		if err != nil {
-			color.Red("Error logging in: %s\n", err.Error())
-			os.Exit(1)
-		}
-	},
-}
+var manual bool = false
 
 
-var registerCmd = &cobra.Command{
-	Use:   "register",
-	Short: "Creates a user for a given Porter server",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := register(cmd.Context())
-		if err != nil {
-			color.Red("Error registering: %s\n", err.Error())
-			os.Exit(1)
-		}
-	},
-}
+func registerCommand_Auth(cliConf config.CLIConfig) *cobra.Command {
+	authCmd := &cobra.Command{
+		Use:   "auth",
+		Short: "Commands for authenticating to a Porter server",
+	}
 
 
-var logoutCmd = &cobra.Command{
-	Use:   "logout",
-	Short: "Logs a user out of a given Porter server",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, logout)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+	loginCmd := &cobra.Command{
+		Use:   "login",
+		Short: "Authorizes a user for a given Porter server",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := login(cmd.Context(), cliConf)
+			if err != nil {
+				color.Red("Error logging in: %s\n", err.Error())
+				os.Exit(1)
+			}
+		},
+	}
 
 
-var manual bool = false
+	registerCmd := &cobra.Command{
+		Use:   "register",
+		Short: "Creates a user for a given Porter server",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := register(cmd.Context(), cliConf)
+			if err != nil {
+				color.Red("Error registering: %s\n", err.Error())
+				os.Exit(1)
+			}
+		},
+	}
 
 
-func init() {
-	rootCmd.AddCommand(authCmd)
+	logoutCmd := &cobra.Command{
+		Use:   "logout",
+		Short: "Logs a user out of a given Porter server",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, logout)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
 	authCmd.AddCommand(loginCmd)
 	authCmd.AddCommand(loginCmd)
 	authCmd.AddCommand(registerCmd)
 	authCmd.AddCommand(registerCmd)
@@ -70,101 +70,104 @@ func init() {
 		false,
 		false,
 		"whether to prompt for manual authentication (username/pw)",
 		"whether to prompt for manual authentication (username/pw)",
 	)
 	)
-}
 
 
-func login(ctx context.Context) error {
-	cliConf, err := config.InitAndLoadConfig()
-	if err != nil {
-		return fmt.Errorf("error loading porter config: %w", err)
-	}
+	return authCmd
+}
 
 
+func login(ctx context.Context, cliConf config.CLIConfig) error {
 	client, err := api.NewClientWithConfig(ctx, api.NewClientInput{
 	client, err := api.NewClientWithConfig(ctx, api.NewClientInput{
 		BaseURL:     fmt.Sprintf("%s/api", cliConf.Host),
 		BaseURL:     fmt.Sprintf("%s/api", cliConf.Host),
 		BearerToken: cliConf.Token,
 		BearerToken: cliConf.Token,
 	})
 	})
 	if err != nil {
 	if err != nil {
-		return fmt.Errorf("error creating porter API client: %w", err)
+		if !errors.Is(err, api.ErrNoAuthCredential) {
+			return fmt.Errorf("error creating porter API client: %w", err)
+		}
 	}
 	}
 
 
 	user, err := client.AuthCheck(ctx)
 	user, err := client.AuthCheck(ctx)
+	if err != nil {
+		if !strings.Contains(err.Error(), "Forbidden") {
+			return fmt.Errorf("unexpected error performing authorization check")
+		}
+		fmt.Println(err)
+	}
 
 
-	if err == nil {
-		// set the token if the user calls login with the --token flag or the PORTER_TOKEN env
-		if cliConf.Token != "" {
-			cliConf.SetToken(cliConf.Token)
-			color.New(color.FgGreen).Println("Successfully logged in!")
+	if cliConf.Token == "" {
+		// check for the --manual flag
+		if manual {
+			return loginManual(ctx, cliConf, client)
+		}
 
 
-			projID, exists, err := api.GetProjectIDFromToken(cliConf.Token)
-			if err != nil {
-				return err
-			}
+		// log the user in
+		token, err := loginBrowser.Login(cliConf.Host)
+		if err != nil {
+			return err
+		}
 
 
-			// if project ID does not exist for the token, this is a user-issued CLI token, so the project
-			// ID should be queried
-			if !exists {
-				err = setProjectForUser(ctx, client, cliConf, user.ID)
+		// set the token in config
+		err = cliConf.SetToken(token)
 
 
-				if err != nil {
-					return err
-				}
-			} else {
-				// if the project ID does exist for the token, this is a project-issued token, and
-				// the project should be set automatically
-				err = cliConf.SetProject(ctx, client, projID)
+		if err != nil {
+			return err
+		}
 
 
-				if err != nil {
-					return err
-				}
+		client, err = api.NewClientWithConfig(ctx, api.NewClientInput{
+			BaseURL:     fmt.Sprintf("%s/api", cliConf.Host),
+			BearerToken: token,
+		})
+		if err != nil {
+			return fmt.Errorf("error creating porter API client: %w", err)
+		}
 
 
-				err = setProjectCluster(ctx, client, cliConf, projID)
+		user, err = client.AuthCheck(ctx)
 
 
-				if err != nil {
-					return err
-				}
-			}
-		} else {
-			color.Yellow("You are already logged in. If you'd like to log out, run \"porter auth logout\".")
+		if err != nil {
+			color.Red("Invalid token.")
+			return err
 		}
 		}
 
 
-		return nil
-	}
+		_, _ = color.New(color.FgGreen).Println("Successfully logged in!")
+
+		return setProjectForUser(ctx, client, cliConf, user.ID)
 
 
-	// check for the --manual flag
-	if manual {
-		return loginManual(ctx, cliConf, client)
 	}
 	}
 
 
-	// log the user in
-	token, err := loginBrowser.Login(cliConf.Host)
+	err = cliConf.SetToken(cliConf.Token)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
+	_, _ = color.New(color.FgGreen).Println("Successfully logged in!")
 
 
-	// set the token in config
-	err = cliConf.SetToken(token)
-
+	projID, exists, err := api.GetProjectIDFromToken(cliConf.Token)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	client, err = api.NewClientWithConfig(ctx, api.NewClientInput{
-		BaseURL:     fmt.Sprintf("%s/api", cliConf.Host),
-		BearerToken: token,
-	})
-	if err != nil {
-		return fmt.Errorf("error creating porter API client: %w", err)
-	}
+	// if project ID does not exist for the token, this is a user-issued CLI token, so the project
+	// ID should be queried
+	if !exists {
+		err = setProjectForUser(ctx, client, cliConf, user.ID)
 
 
-	user, err = client.AuthCheck(ctx)
+		if err != nil {
+			return err
+		}
+	} else {
+		// if the project ID does exist for the token, this is a project-issued token, and
+		// the project should be set automatically
+		err = cliConf.SetProject(ctx, client, projID)
 
 
-	if err != nil {
-		color.Red("Invalid token.")
-		return err
-	}
+		if err != nil {
+			return err
+		}
 
 
-	color.New(color.FgGreen).Println("Successfully logged in!")
+		err = setProjectCluster(ctx, client, cliConf, projID)
 
 
-	return setProjectForUser(ctx, client, cliConf, user.ID)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
 }
 }
 
 
 func setProjectForUser(ctx context.Context, client api.Client, config config.CLIConfig, _ uint) error {
 func setProjectForUser(ctx context.Context, client api.Client, config config.CLIConfig, _ uint) error {
@@ -240,18 +243,15 @@ func loginManual(ctx context.Context, cliConf config.CLIConfig, client api.Clien
 	return nil
 	return nil
 }
 }
 
 
-func register(ctx context.Context) error {
-	config, err := config.InitAndLoadConfig()
-	if err != nil {
-		return fmt.Errorf("error loading porter config: %w", err)
-	}
-
+func register(ctx context.Context, cliConf config.CLIConfig) error {
 	client, err := api.NewClientWithConfig(ctx, api.NewClientInput{
 	client, err := api.NewClientWithConfig(ctx, api.NewClientInput{
-		BaseURL:     fmt.Sprintf("%s/api", config.Host),
-		BearerToken: config.Token,
+		BaseURL:     fmt.Sprintf("%s/api", cliConf.Host),
+		BearerToken: cliConf.Token,
 	})
 	})
 	if err != nil {
 	if err != nil {
-		return fmt.Errorf("error creating porter API client: %w", err)
+		if !errors.Is(err, api.ErrNoAuthCredential) {
+			return fmt.Errorf("error creating porter API client: %w", err)
+		}
 	}
 	}
 
 
 	fmt.Println("Please register your admin account with an email and password:")
 	fmt.Println("Please register your admin account with an email and password:")

+ 47 - 51
cli/cmd/cluster.go → cli/cmd/commands/cluster.go

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 
 import (
 import (
 	"context"
 	"context"
@@ -16,62 +16,58 @@ import (
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 )
 )
 
 
-// clusterCmd represents the "porter cluster" base command when called
-// without any subcommands
-var clusterCmd = &cobra.Command{
-	Use:     "cluster",
-	Aliases: []string{"clusters"},
-	Short:   "Commands that read from a connected cluster",
-}
-
-var clusterListCmd = &cobra.Command{
-	Use:   "list",
-	Short: "Lists the linked clusters in the current project",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, listClusters)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
-
-var clusterDeleteCmd = &cobra.Command{
-	Use:   "delete [id]",
-	Args:  cobra.ExactArgs(1),
-	Short: "Deletes the cluster with the given id",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, deleteCluster)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
-
-var clusterNamespaceCmd = &cobra.Command{
-	Use:     "namespace",
-	Aliases: []string{"namespaces"},
-	Short:   "Commands that perform operations on cluster namespaces",
-}
+func registerCommand_Cluster(cliConf config.CLIConfig) *cobra.Command {
+	clusterCmd := &cobra.Command{
+		Use:     "cluster",
+		Aliases: []string{"clusters"},
+		Short:   "Commands that read from a connected cluster",
+	}
 
 
-var clusterNamespaceListCmd = &cobra.Command{
-	Use:   "list",
-	Short: "Lists the namespaces in a cluster",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, listNamespaces)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+	clusterListCmd := &cobra.Command{
+		Use:   "list",
+		Short: "Lists the linked clusters in the current project",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, listClusters)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
+	clusterCmd.AddCommand(clusterListCmd)
 
 
-func init() {
-	rootCmd.AddCommand(clusterCmd)
+	clusterDeleteCmd := &cobra.Command{
+		Use:   "delete [id]",
+		Args:  cobra.ExactArgs(1),
+		Short: "Deletes the cluster with the given id",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, deleteCluster)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
+	clusterCmd.AddCommand(clusterDeleteCmd)
 
 
+	clusterNamespaceCmd := &cobra.Command{
+		Use:     "namespace",
+		Aliases: []string{"namespaces"},
+		Short:   "Commands that perform operations on cluster namespaces",
+	}
 	clusterCmd.AddCommand(clusterNamespaceCmd)
 	clusterCmd.AddCommand(clusterNamespaceCmd)
-	clusterCmd.AddCommand(clusterListCmd)
-	clusterCmd.AddCommand(clusterDeleteCmd)
 
 
+	clusterNamespaceListCmd := &cobra.Command{
+		Use:   "list",
+		Short: "Lists the namespaces in a cluster",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, listNamespaces)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 	clusterNamespaceCmd.AddCommand(clusterNamespaceListCmd)
 	clusterNamespaceCmd.AddCommand(clusterNamespaceListCmd)
+
+	return clusterCmd
 }
 }
 
 
 func listClusters(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {
 func listClusters(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {

+ 317 - 0
cli/cmd/commands/config.go

@@ -0,0 +1,317 @@
+package commands
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/briandowns/spinner"
+	"github.com/fatih/color"
+	api "github.com/porter-dev/porter/api/client"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/cli/cmd/config"
+	"github.com/porter-dev/porter/cli/cmd/utils"
+	"github.com/spf13/cobra"
+)
+
+func registerCommand_Config(cliConf config.CLIConfig) *cobra.Command {
+	configCmd := &cobra.Command{
+		Use:   "config",
+		Short: "Commands that control local configuration settings",
+		Run: func(cmd *cobra.Command, args []string) {
+			if err := printConfig(); err != nil {
+				_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
+				os.Exit(1)
+			}
+		},
+	}
+
+	configSetProjectCmd := &cobra.Command{
+		Use:   "set-project [id]",
+		Args:  cobra.MaximumNArgs(1),
+		Short: "Saves the project id in the default configuration",
+		Run: func(cmd *cobra.Command, args []string) {
+			client, err := api.NewClientWithConfig(cmd.Context(), api.NewClientInput{
+				BaseURL:        fmt.Sprintf("%s/api", cliConf.Host),
+				BearerToken:    cliConf.Token,
+				CookieFileName: "cookie.json",
+			})
+			if err != nil {
+				_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "error creating porter API client: %s\n", err.Error())
+				os.Exit(1)
+			}
+
+			if len(args) == 0 {
+				err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, listAndSetProject)
+				if err != nil {
+					os.Exit(1)
+				}
+			} else {
+				projID, err := strconv.ParseUint(args[0], 10, 64)
+				if err != nil {
+					_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %s\n", err.Error())
+					os.Exit(1)
+				}
+
+				err = cliConf.SetProject(cmd.Context(), client, uint(projID))
+				if err != nil {
+					_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %s\n", err.Error())
+					os.Exit(1)
+				}
+			}
+		},
+	}
+
+	configSetClusterCmd := &cobra.Command{
+		Use:   "set-cluster [id]",
+		Args:  cobra.MaximumNArgs(1),
+		Short: "Saves the cluster id in the default configuration",
+		Run: func(cmd *cobra.Command, args []string) {
+			if len(args) == 0 {
+				err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, listAndSetCluster)
+				if err != nil {
+					os.Exit(1)
+				}
+			} else {
+				clusterID, err := strconv.ParseUint(args[0], 10, 64)
+				if err != nil {
+					_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
+					os.Exit(1)
+				}
+
+				err = cliConf.SetCluster(uint(clusterID))
+
+				if err != nil {
+					_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
+					os.Exit(1)
+				}
+			}
+		},
+	}
+
+	configSetRegistryCmd := &cobra.Command{
+		Use:   "set-registry [id]",
+		Args:  cobra.MaximumNArgs(1),
+		Short: "Saves the registry id in the default configuration",
+		Run: func(cmd *cobra.Command, args []string) {
+			if len(args) == 0 {
+				err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, listAndSetRegistry)
+				if err != nil {
+					os.Exit(1)
+				}
+			} else {
+				registryID, err := strconv.ParseUint(args[0], 10, 64)
+				if err != nil {
+					_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
+					os.Exit(1)
+				}
+
+				err = cliConf.SetRegistry(uint(registryID))
+
+				if err != nil {
+					_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
+					os.Exit(1)
+				}
+			}
+		},
+	}
+
+	configSetHelmRepoCmd := &cobra.Command{
+		Use:   "set-helmrepo [id]",
+		Args:  cobra.ExactArgs(1),
+		Short: "Saves the helm repo id in the default configuration",
+		Run: func(cmd *cobra.Command, args []string) {
+			hrID, err := strconv.ParseUint(args[0], 10, 64)
+			if err != nil {
+				_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
+				os.Exit(1)
+			}
+
+			err = cliConf.SetHelmRepo(uint(hrID))
+
+			if err != nil {
+				_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
+				os.Exit(1)
+			}
+		},
+	}
+
+	configSetHostCmd := &cobra.Command{
+		Use:   "set-host [host]",
+		Args:  cobra.ExactArgs(1),
+		Short: "Saves the host in the default configuration",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := cliConf.SetHost(args[0])
+			if err != nil {
+				_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %s\n", err.Error())
+				os.Exit(1)
+			}
+		},
+	}
+
+	configSetKubeconfigCmd := &cobra.Command{
+		Use:   "set-kubeconfig [kubeconfig-path]",
+		Args:  cobra.ExactArgs(1),
+		Short: "Saves the path to kubeconfig in the default configuration",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := cliConf.SetKubeconfig(args[0])
+			if err != nil {
+				_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %s\n", err.Error())
+				os.Exit(1)
+			}
+		},
+	}
+
+	configCmd.AddCommand(configSetProjectCmd)
+	configCmd.AddCommand(configSetClusterCmd)
+	configCmd.AddCommand(configSetHostCmd)
+	configCmd.AddCommand(configSetRegistryCmd)
+	configCmd.AddCommand(configSetHelmRepoCmd)
+	configCmd.AddCommand(configSetKubeconfigCmd)
+	return configCmd
+}
+
+func printConfig() error {
+	config, err := os.ReadFile(filepath.Join(home, ".porter", "porter.yaml"))
+	if err != nil {
+		return err
+	}
+
+	fmt.Println(string(config))
+
+	return nil
+}
+
+func listAndSetProject(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {
+	s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
+	_ = s.Color("cyan")
+	s.Suffix = " Loading list of projects"
+	s.Start()
+
+	resp, err := client.ListUserProjects(ctx)
+
+	s.Stop()
+
+	if err != nil {
+		return err
+	}
+
+	var projID uint64
+
+	if len(*resp) > 1 {
+		// only give the option to select when more than one option exists
+		projName, err := utils.PromptSelect("Select a project with ID", func() []string {
+			var names []string
+
+			for _, proj := range *resp {
+				names = append(names, fmt.Sprintf("%s - %d", proj.Name, proj.ID))
+			}
+
+			return names
+		}())
+		if err != nil {
+			return err
+		}
+
+		projID, _ = strconv.ParseUint(strings.Split(projName, " - ")[1], 10, 64)
+	} else {
+		projID = uint64((*resp)[0].ID)
+	}
+
+	err = cliConf.SetProject(ctx, client, uint(projID))
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func listAndSetCluster(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {
+	s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
+	_ = s.Color("cyan")
+	s.Suffix = " Loading list of clusters"
+	s.Start()
+
+	resp, err := client.ListProjectClusters(ctx, cliConf.Project)
+
+	s.Stop()
+
+	if err != nil {
+		return err
+	}
+
+	var clusterID uint64
+
+	if len(*resp) > 1 {
+		clusterName, err := utils.PromptSelect("Select a cluster with ID", func() []string {
+			var names []string
+
+			for _, cluster := range *resp {
+				names = append(names, fmt.Sprintf("%s - %d", cluster.Name, cluster.ID))
+			}
+
+			return names
+		}())
+		if err != nil {
+			return err
+		}
+
+		clusterID, _ = strconv.ParseUint(strings.Split(clusterName, " - ")[1], 10, 64)
+	} else {
+		clusterID = uint64((*resp)[0].ID)
+	}
+
+	err = cliConf.SetCluster(uint(clusterID))
+	if err != nil {
+		return fmt.Errorf("unable to set cluster: %w", err)
+	}
+
+	return nil
+}
+
+func listAndSetRegistry(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {
+	s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
+	_ = s.Color("cyan")
+	s.Suffix = " Loading list of registries"
+	s.Start()
+
+	resp, err := client.ListRegistries(ctx, cliConf.Project)
+
+	s.Stop()
+
+	if err != nil {
+		return err
+	}
+
+	var regID uint64
+
+	if len(*resp) > 1 {
+		regName, err := utils.PromptSelect("Select a registry with ID", func() []string {
+			var names []string
+
+			for _, cluster := range *resp {
+				names = append(names, fmt.Sprintf("%s - %d", cluster.Name, cluster.ID))
+			}
+
+			return names
+		}())
+		if err != nil {
+			return err
+		}
+
+		regID, _ = strconv.ParseUint(strings.Split(regName, " - ")[1], 10, 64)
+	} else {
+		regID = uint64((*resp)[0].ID)
+	}
+
+	err = cliConf.SetRegistry(uint(regID))
+	if err != nil {
+		return fmt.Errorf("error setting registry: %w", err)
+	}
+
+	return nil
+}

+ 88 - 89
cli/cmd/connect.go → cli/cmd/commands/connect.go

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 
 import (
 import (
 	"context"
 	"context"
@@ -17,102 +17,100 @@ var (
 	contexts       *[]string
 	contexts       *[]string
 )
 )
 
 
-var connectCmd = &cobra.Command{
-	Use:   "connect",
-	Short: "Commands that connect to external clusters and providers",
-}
-
-var connectKubeconfigCmd = &cobra.Command{
-	Use:   "kubeconfig",
-	Short: "Uses the local kubeconfig to add a cluster",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, runConnectKubeconfig)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+func registerCommand_Connect(cliConf config.CLIConfig) *cobra.Command {
+	connectCmd := &cobra.Command{
+		Use:   "connect",
+		Short: "Commands that connect to external clusters and providers",
+	}
 
 
-var connectECRCmd = &cobra.Command{
-	Use:   "ecr",
-	Short: "Adds an ECR instance to a project",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, runConnectECR)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+	connectKubeconfigCmd := &cobra.Command{
+		Use:   "kubeconfig",
+		Short: "Uses the local kubeconfig to add a cluster",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, runConnectKubeconfig)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-var connectDockerhubCmd = &cobra.Command{
-	Use:   "dockerhub",
-	Short: "Adds a Docker Hub registry integration to a project",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, runConnectDockerhub)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+	connectECRCmd := &cobra.Command{
+		Use:   "ecr",
+		Short: "Adds an ECR instance to a project",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, runConnectECR)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-var connectRegistryCmd = &cobra.Command{
-	Use:   "registry",
-	Short: "Adds a custom image registry to a project",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, runConnectRegistry)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+	connectDockerhubCmd := &cobra.Command{
+		Use:   "dockerhub",
+		Short: "Adds a Docker Hub registry integration to a project",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, runConnectDockerhub)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-var connectHelmRepoCmd = &cobra.Command{
-	Use:   "helm",
-	Short: "Adds a custom Helm registry to a project",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, runConnectHelmRepo)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+	connectRegistryCmd := &cobra.Command{
+		Use:   "registry",
+		Short: "Adds a custom image registry to a project",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, runConnectRegistry)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-var connectGCRCmd = &cobra.Command{
-	Use:   "gcr",
-	Short: "Adds a GCR instance to a project",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, runConnectGCR)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+	connectHelmRepoCmd := &cobra.Command{
+		Use:   "helm",
+		Short: "Adds a custom Helm registry to a project",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, runConnectHelmRepo)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-var connectGARCmd = &cobra.Command{
-	Use:   "gar",
-	Short: "Adds a GAR instance to a project",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, runConnectGAR)
-		if err != nil {
-			os.Exit(1)
-		}
-		cmd.Context()
-	},
-}
+	connectGCRCmd := &cobra.Command{
+		Use:   "gcr",
+		Short: "Adds a GCR instance to a project",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, runConnectGCR)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-var connectDOCRCmd = &cobra.Command{
-	Use:   "docr",
-	Short: "Adds a DOCR instance to a project",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, runConnectDOCR)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+	connectGARCmd := &cobra.Command{
+		Use:   "gar",
+		Short: "Adds a GAR instance to a project",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, runConnectGAR)
+			if err != nil {
+				os.Exit(1)
+			}
+			cmd.Context()
+		},
+	}
 
 
-func init() {
-	rootCmd.AddCommand(connectCmd)
+	connectDOCRCmd := &cobra.Command{
+		Use:   "docr",
+		Short: "Adds a DOCR instance to a project",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, runConnectDOCR)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
 	connectCmd.AddCommand(connectKubeconfigCmd)
 	connectCmd.AddCommand(connectKubeconfigCmd)
 
 
@@ -137,6 +135,7 @@ func init() {
 	connectCmd.AddCommand(connectGARCmd)
 	connectCmd.AddCommand(connectGARCmd)
 	connectCmd.AddCommand(connectDOCRCmd)
 	connectCmd.AddCommand(connectDOCRCmd)
 	connectCmd.AddCommand(connectHelmRepoCmd)
 	connectCmd.AddCommand(connectHelmRepoCmd)
+	return connectCmd
 }
 }
 
 
 func runConnectKubeconfig(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, _ []string) error {
 func runConnectKubeconfig(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, _ []string) error {

+ 31 - 34
cli/cmd/create.go → cli/cmd/commands/create.go

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 
 import (
 import (
 	"context"
 	"context"
@@ -22,13 +22,21 @@ import (
 	"sigs.k8s.io/yaml"
 	"sigs.k8s.io/yaml"
 )
 )
 
 
-// createCmd represents the "porter create" base command when called
-// without any subcommands
-var createCmd = &cobra.Command{
-	Use:   "create [kind]",
-	Args:  cobra.ExactArgs(1),
-	Short: "Creates a new application with name given by the --app flag.",
-	Long: fmt.Sprintf(`
+var (
+	name        string
+	values      string
+	source      string
+	image       string
+	registryURL string
+	forceBuild  bool
+)
+
+func registerCommand_Create(cliConf config.CLIConfig) *cobra.Command {
+	createCmd := &cobra.Command{
+		Use:   "create [kind]",
+		Args:  cobra.ExactArgs(1),
+		Short: "Creates a new application with name given by the --app flag.",
+		Long: fmt.Sprintf(`
 %s
 %s
 
 
 Creates a new application with name given by the --app flag and a "kind", which can be one of
 Creates a new application with name given by the --app flag and a "kind", which can be one of
@@ -62,32 +70,20 @@ To deploy an application from a Docker registry, use "--source registry" and pas
 
 
   %s
   %s
 `,
 `,
-		color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter create\":"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter create web --app example-app"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter create web --app example-app --values values.yaml"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter create web --app example-app --path ./path/to/app"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter create web --app example-app --source github"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter create web --app example-app --source registry --image gcr.io/snowflake-12345/example-app:latest"),
-	),
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, createFull)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
-
-var (
-	name        string
-	values      string
-	source      string
-	image       string
-	registryURL string
-	forceBuild  bool
-)
-
-func init() {
-	rootCmd.AddCommand(createCmd)
+			color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter create\":"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter create web --app example-app"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter create web --app example-app --values values.yaml"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter create web --app example-app --path ./path/to/app"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter create web --app example-app --source github"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter create web --app example-app --source registry --image gcr.io/snowflake-12345/example-app:latest"),
+		),
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, createFull)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
 	createCmd.PersistentFlags().StringVar(
 	createCmd.PersistentFlags().StringVar(
 		&name,
 		&name,
@@ -179,6 +175,7 @@ func init() {
 	)
 	)
 
 
 	createCmd.PersistentFlags().MarkDeprecated("force-build", "--force-build is deprecated")
 	createCmd.PersistentFlags().MarkDeprecated("force-build", "--force-build is deprecated")
+	return createCmd
 }
 }
 
 
 var supportedKinds = map[string]string{"web": "", "job": "", "worker": ""}
 var supportedKinds = map[string]string{"web": "", "job": "", "worker": ""}

+ 69 - 70
cli/cmd/delete.go → cli/cmd/commands/delete.go

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 
 import (
 import (
 	"context"
 	"context"
@@ -15,11 +15,11 @@ import (
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 )
 )
 
 
-// deleteCmd represents the "porter delete" base command
-var deleteCmd = &cobra.Command{
-	Use:   "delete",
-	Short: "Deletes a deployment",
-	Long: fmt.Sprintf(`
+func registerCommand_Delete(cliConf config.CLIConfig) *cobra.Command {
+	deleteCmd := &cobra.Command{
+		Use:   "delete",
+		Short: "Deletes a deployment",
+		Long: fmt.Sprintf(`
 %s
 %s
 
 
 Destroys a deployment, which is read based on env variables.
 Destroys a deployment, which is read based on env variables.
@@ -31,74 +31,73 @@ deleting a configuration:
   PORTER_CLUSTER              Cluster ID that contains the project
   PORTER_CLUSTER              Cluster ID that contains the project
   PORTER_PROJECT              Project ID that contains the application
   PORTER_PROJECT              Project ID that contains the application
 	`,
 	`,
-		color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter delete\":"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter delete"),
-	),
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, deleteDeployment)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+			color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter delete\":"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter delete"),
+		),
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, deleteDeployment)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-// deleteAppsCmd represents the "porter delete apps" subcommand
-var deleteAppsCmd = &cobra.Command{
-	Use:     "apps",
-	Aliases: []string{"app", "applications", "application"},
-	Short:   "Deletes an existing app",
-	Args:    cobra.ExactArgs(1),
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, deleteApp)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+	// deleteAppsCmd represents the "porter delete apps" subcommand
+	deleteAppsCmd := &cobra.Command{
+		Use:     "apps",
+		Aliases: []string{"app", "applications", "application"},
+		Short:   "Deletes an existing app",
+		Args:    cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, deleteApp)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-// deleteJobsCmd represents the "porter delete jobs" subcommand
-var deleteJobsCmd = &cobra.Command{
-	Use:     "jobs",
-	Aliases: []string{"job"},
-	Short:   "Deletes an existing job",
-	Args:    cobra.ExactArgs(1),
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, deleteJob)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+	// deleteJobsCmd represents the "porter delete jobs" subcommand
+	deleteJobsCmd := &cobra.Command{
+		Use:     "jobs",
+		Aliases: []string{"job"},
+		Short:   "Deletes an existing job",
+		Args:    cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, deleteJob)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-// deleteAddonsCmd represents the "porter delete addons" subcommand
-var deleteAddonsCmd = &cobra.Command{
-	Use:     "addons",
-	Aliases: []string{"addon"},
-	Short:   "Deletes an existing addon",
-	Args:    cobra.ExactArgs(1),
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, deleteAddon)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+	// deleteAddonsCmd represents the "porter delete addons" subcommand
+	deleteAddonsCmd := &cobra.Command{
+		Use:     "addons",
+		Aliases: []string{"addon"},
+		Short:   "Deletes an existing addon",
+		Args:    cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, deleteAddon)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-// deleteHelmCmd represents the "porter delete helm" subcommand
-var deleteHelmCmd = &cobra.Command{
-	Use:     "helm",
-	Aliases: []string{"helmrepo", "helmrepos"},
-	Short:   "Deletes an existing helm repo",
-	Args:    cobra.ExactArgs(1),
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, deleteHelm)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+	// deleteHelmCmd represents the "porter delete helm" subcommand
+	deleteHelmCmd := &cobra.Command{
+		Use:     "helm",
+		Aliases: []string{"helmrepo", "helmrepos"},
+		Short:   "Deletes an existing helm repo",
+		Args:    cobra.ExactArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, deleteHelm)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-func init() {
 	deleteCmd.PersistentFlags().StringVar(
 	deleteCmd.PersistentFlags().StringVar(
 		&namespace,
 		&namespace,
 		"namespace",
 		"namespace",
@@ -111,7 +110,7 @@ func init() {
 	deleteCmd.AddCommand(deleteAddonsCmd)
 	deleteCmd.AddCommand(deleteAddonsCmd)
 	deleteCmd.AddCommand(deleteHelmCmd)
 	deleteCmd.AddCommand(deleteHelmCmd)
 
 
-	rootCmd.AddCommand(deleteCmd)
+	return deleteCmd
 }
 }
 
 
 func deleteDeployment(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {
 func deleteDeployment(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {

+ 16 - 17
cli/cmd/bluegreen.go → cli/cmd/commands/deploy_bluegreen.go

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 
 import (
 import (
 	"context"
 	"context"
@@ -19,23 +19,21 @@ import (
 	intstrutil "k8s.io/apimachinery/pkg/util/intstr"
 	intstrutil "k8s.io/apimachinery/pkg/util/intstr"
 )
 )
 
 
-var deployCmd = &cobra.Command{
-	Use: "deploy",
-}
-
-var bluegreenCmd = &cobra.Command{
-	Use:   "blue-green-switch",
-	Short: "Automatically switches the traffic of a blue-green deployment once the new application is ready.",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, bluegreenSwitch)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+func registerCommand_Deploy(cliConf config.CLIConfig) *cobra.Command {
+	deployCmd := &cobra.Command{
+		Use: "deploy",
+	}
 
 
-func init() {
-	rootCmd.AddCommand(deployCmd)
+	bluegreenCmd := &cobra.Command{
+		Use:   "blue-green-switch",
+		Short: "Automatically switches the traffic of a blue-green deployment once the new application is ready.",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, bluegreenSwitch)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 	deployCmd.AddCommand(bluegreenCmd)
 	deployCmd.AddCommand(bluegreenCmd)
 
 
 	bluegreenCmd.PersistentFlags().StringVar(
 	bluegreenCmd.PersistentFlags().StringVar(
@@ -60,6 +58,7 @@ func init() {
 		"",
 		"",
 		"The namespace of the jobs.",
 		"The namespace of the jobs.",
 	)
 	)
+	return deployCmd
 }
 }
 
 
 func bluegreenSwitch(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, args []string) error {
 func bluegreenSwitch(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, args []string) error {

+ 36 - 0
cli/cmd/commands/docker.go

@@ -0,0 +1,36 @@
+package commands
+
+import (
+	"context"
+	"os"
+
+	api "github.com/porter-dev/porter/api/client"
+	ptypes "github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/cli/cmd/config"
+	"github.com/spf13/cobra"
+)
+
+func registerCommand_Docker(cliConf config.CLIConfig) *cobra.Command {
+	dockerCmd := &cobra.Command{
+		Use:   "docker",
+		Short: "Commands to configure Docker for a project",
+	}
+
+	configureCmd := &cobra.Command{
+		Use:   "configure",
+		Short: "Configures the host's Docker instance",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, dockerConfig)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
+
+	dockerCmd.AddCommand(configureCmd)
+	return dockerCmd
+}
+
+func dockerConfig(ctx context.Context, user *ptypes.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {
+	return config.SetDockerConfig(ctx, client, cliConf.Project)
+}

+ 2 - 7
cli/cmd/errors.go → cli/cmd/commands/errors.go

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 
 import (
 import (
 	"context"
 	"context"
@@ -19,12 +19,7 @@ var (
 	ErrCannotConnect error = errors.New("Unable to connect to the Porter server.")
 	ErrCannotConnect error = errors.New("Unable to connect to the Porter server.")
 )
 )
 
 
-func checkLoginAndRun(ctx context.Context, args []string, runner func(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, args []string) error) error {
-	cliConf, err := config.InitAndLoadConfig()
-	if err != nil {
-		return fmt.Errorf("error loading porter config: %w", err)
-	}
-
+func checkLoginAndRunWithConfig(ctx context.Context, cliConf config.CLIConfig, args []string, runner func(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error) error {
 	client, err := api.NewClientWithConfig(ctx, api.NewClientInput{
 	client, err := api.NewClientWithConfig(ctx, api.NewClientInput{
 		BaseURL:        fmt.Sprintf("%s/api", cliConf.Host),
 		BaseURL:        fmt.Sprintf("%s/api", cliConf.Host),
 		BearerToken:    cliConf.Token,
 		BearerToken:    cliConf.Token,

+ 27 - 29
cli/cmd/get.go → cli/cmd/commands/get.go

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 
 import (
 import (
 	"context"
 	"context"
@@ -16,36 +16,34 @@ import (
 	"gopkg.in/yaml.v2"
 	"gopkg.in/yaml.v2"
 )
 )
 
 
-// getCmd represents the "porter get" base command when called
-// without any subcommands
-var getCmd = &cobra.Command{
-	Use:   "get [release]",
-	Args:  cobra.ExactArgs(1),
-	Short: "Fetches a release.",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, get)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+var output string
 
 
-// getValuesCmd represents the "porter get values" command
-var getValuesCmd = &cobra.Command{
-	Use:   "values [release]",
-	Args:  cobra.ExactArgs(1),
-	Short: "Fetches the Helm values for a release.",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, getValues)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+func registerCommand_Get(cliConf config.CLIConfig) *cobra.Command {
+	getCmd := &cobra.Command{
+		Use:   "get [release]",
+		Args:  cobra.ExactArgs(1),
+		Short: "Fetches a release.",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, get)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-var output string
+	// getValuesCmd represents the "porter get values" command
+	getValuesCmd := &cobra.Command{
+		Use:   "values [release]",
+		Args:  cobra.ExactArgs(1),
+		Short: "Fetches the Helm values for a release.",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, getValues)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-func init() {
 	getCmd.PersistentFlags().StringVar(
 	getCmd.PersistentFlags().StringVar(
 		&namespace,
 		&namespace,
 		"namespace",
 		"namespace",
@@ -62,7 +60,7 @@ func init() {
 
 
 	getCmd.AddCommand(getValuesCmd)
 	getCmd.AddCommand(getValuesCmd)
 
 
-	rootCmd.AddCommand(getCmd)
+	return getCmd
 }
 }
 
 
 type getReleaseInfo struct {
 type getReleaseInfo struct {

+ 13 - 13
cli/cmd/helm.go → cli/cmd/commands/helm.go

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 
 import (
 import (
 	"context"
 	"context"
@@ -12,19 +12,19 @@ import (
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 )
 )
 
 
-var helmCmd = &cobra.Command{
-	Use:   "helm",
-	Short: "Use helm to interact with a Porter cluster",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, runHelm)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+func registerCommand_Helm(cliConf config.CLIConfig) *cobra.Command {
+	helmCmd := &cobra.Command{
+		Use:   "helm",
+		Short: "Use helm to interact with a Porter cluster",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, runHelm)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-func init() {
-	rootCmd.AddCommand(helmCmd)
+	return helmCmd
 }
 }
 
 
 func runHelm(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {
 func runHelm(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {

+ 53 - 53
cli/cmd/job.go → cli/cmd/commands/job.go

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 
 import (
 import (
 	"context"
 	"context"
@@ -16,14 +16,17 @@ import (
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 )
 )
 
 
-var jobCmd = &cobra.Command{
-	Use: "job",
-}
+var imageRepoURI string
+
+func registerCommand_Job(cliConf config.CLIConfig) *cobra.Command {
+	jobCmd := &cobra.Command{
+		Use: "job",
+	}
 
 
-var batchImageUpdateCmd = &cobra.Command{
-	Use:   "update-images",
-	Short: "Updates the image tag of all jobs in a namespace which use a specific image.",
-	Long: fmt.Sprintf(`
+	batchImageUpdateCmd := &cobra.Command{
+		Use:   "update-images",
+		Short: "Updates the image tag of all jobs in a namespace which use a specific image.",
+		Long: fmt.Sprintf(`
 %s
 %s
 
 
 Updates the image tag of all jobs in a namespace which use a specific image. Note that for all
 Updates the image tag of all jobs in a namespace which use a specific image. Note that for all
@@ -39,22 +42,22 @@ use the --namespace flag:
 
 
   %s
   %s
 `,
 `,
-		color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter job update-images\":"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter job update-images --image-repo-uri my-image.registry.io --tag newtag"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter job update-images --namespace custom-namespace --image-repo-uri my-image.registry.io --tag newtag"),
-	),
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, batchImageUpdate)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+			color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter job update-images\":"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter job update-images --image-repo-uri my-image.registry.io --tag newtag"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter job update-images --namespace custom-namespace --image-repo-uri my-image.registry.io --tag newtag"),
+		),
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, batchImageUpdate)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-var waitCmd = &cobra.Command{
-	Use:   "wait",
-	Short: "Waits for a job to complete.",
-	Long: fmt.Sprintf(`
+	waitCmd := &cobra.Command{
+		Use:   "wait",
+		Short: "Waits for a job to complete.",
+		Long: fmt.Sprintf(`
 %s
 %s
 
 
 Waits for a job with a given name and namespace to complete a run. If the job completes successfully,
 Waits for a job with a given name and namespace to complete a run. If the job completes successfully,
@@ -69,22 +72,22 @@ use the --namespace flag:
 
 
   %s
   %s
 `,
 `,
-		color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter job wait\":"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter job wait --name job-example"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter job wait --name job-example --namespace custom-namespace"),
-	),
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, waitForJob)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+			color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter job wait\":"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter job wait --name job-example"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter job wait --name job-example --namespace custom-namespace"),
+		),
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, waitForJob)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-var runJobCmd = &cobra.Command{
-	Use:   "run",
-	Short: "Manually runs a job and waits for it to complete.",
-	Long: fmt.Sprintf(`
+	runJobCmd := &cobra.Command{
+		Use:   "run",
+		Short: "Manually runs a job and waits for it to complete.",
+		Long: fmt.Sprintf(`
 %s
 %s
 
 
 Manually runs a job and waits for it to complete a run. If the job completes successfully,
 Manually runs a job and waits for it to complete a run. If the job completes successfully,
@@ -99,22 +102,18 @@ use the --namespace flag:
 
 
   %s
   %s
 `,
 `,
-		color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter job run\":"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter job run --name job-example"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter job run --name job-example --namespace custom-namespace"),
-	),
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, runJob)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
-
-var imageRepoURI string
+			color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter job run\":"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter job run --name job-example"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter job run --name job-example --namespace custom-namespace"),
+		),
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, runJob)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-func init() {
-	rootCmd.AddCommand(jobCmd)
 	jobCmd.AddCommand(batchImageUpdateCmd)
 	jobCmd.AddCommand(batchImageUpdateCmd)
 	jobCmd.AddCommand(waitCmd)
 	jobCmd.AddCommand(waitCmd)
 	jobCmd.AddCommand(runJobCmd)
 	jobCmd.AddCommand(runJobCmd)
@@ -175,6 +174,7 @@ func init() {
 	)
 	)
 
 
 	runJobCmd.MarkPersistentFlagRequired("name")
 	runJobCmd.MarkPersistentFlagRequired("name")
+	return jobCmd
 }
 }
 
 
 func batchImageUpdate(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {
 func batchImageUpdate(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {

+ 13 - 14
cli/cmd/kubectl.go → cli/cmd/commands/kubectl.go

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 
 import (
 import (
 	"context"
 	"context"
@@ -12,19 +12,18 @@ import (
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 )
 )
 
 
-var kubectlCmd = &cobra.Command{
-	Use:   "kubectl",
-	Short: "Use kubectl to interact with a Porter cluster",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, runKubectl)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
-
-func init() {
-	rootCmd.AddCommand(kubectlCmd)
+func registerCommand_Kubectl(cliConf config.CLIConfig) *cobra.Command {
+	kubectlCmd := &cobra.Command{
+		Use:   "kubectl",
+		Short: "Use kubectl to interact with a Porter cluster",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, runKubectl)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
+	return kubectlCmd
 }
 }
 
 
 func runKubectl(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {
 func runKubectl(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {

+ 48 - 49
cli/cmd/list.go → cli/cmd/commands/list.go

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 
 import (
 import (
 	"context"
 	"context"
@@ -18,59 +18,58 @@ import (
 
 
 var allNamespaces bool
 var allNamespaces bool
 
 
-// listCmd represents the "porter list" base command and "porter list all" subcommand
-var listCmd = &cobra.Command{
-	Use:   "list",
-	Short: "List applications, addons or jobs.",
-	Run: func(cmd *cobra.Command, args []string) {
-		if len(args) == 0 || (args[0] == "all") {
-			err := checkLoginAndRun(cmd.Context(), args, listAll)
+func registerCommand_List(cliConf config.CLIConfig) *cobra.Command {
+	listCmd := &cobra.Command{
+		Use:   "list",
+		Short: "List applications, addons or jobs.",
+		Run: func(cmd *cobra.Command, args []string) {
+			if len(args) == 0 || (args[0] == "all") {
+				err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, listAll)
+				if err != nil {
+					os.Exit(1)
+				}
+			} else {
+				_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "invalid command: %s\n", args[0])
+			}
+		},
+	}
+
+	listAppsCmd := &cobra.Command{
+		Use:     "apps",
+		Aliases: []string{"applications", "app", "application"},
+		Short:   "Lists applications in a specific namespace, or across all namespaces",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, listApps)
 			if err != nil {
 			if err != nil {
 				os.Exit(1)
 				os.Exit(1)
 			}
 			}
-		} else {
-			color.New(color.FgRed).Fprintf(os.Stderr, "invalid command: %s\n", args[0])
-		}
-	},
-}
-
-var listAppsCmd = &cobra.Command{
-	Use:     "apps",
-	Aliases: []string{"applications", "app", "application"},
-	Short:   "Lists applications in a specific namespace, or across all namespaces",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, listApps)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+		},
+	}
 
 
-var listJobsCmd = &cobra.Command{
-	Use:     "jobs",
-	Aliases: []string{"job"},
-	Short:   "Lists jobs in a specific namespace, or across all namespaces",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, listJobs)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+	listJobsCmd := &cobra.Command{
+		Use:     "jobs",
+		Aliases: []string{"job"},
+		Short:   "Lists jobs in a specific namespace, or across all namespaces",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, listJobs)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-var listAddonsCmd = &cobra.Command{
-	Use:     "addons",
-	Aliases: []string{"addon"},
-	Short:   "Lists addons in a specific namespace, or across all namespaces",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, listAddons)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+	listAddonsCmd := &cobra.Command{
+		Use:     "addons",
+		Aliases: []string{"addon"},
+		Short:   "Lists addons in a specific namespace, or across all namespaces",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, listAddons)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-func init() {
 	listCmd.PersistentFlags().StringVar(
 	listCmd.PersistentFlags().StringVar(
 		&namespace,
 		&namespace,
 		"namespace",
 		"namespace",
@@ -89,7 +88,7 @@ func init() {
 	listCmd.AddCommand(listJobsCmd)
 	listCmd.AddCommand(listJobsCmd)
 	listCmd.AddCommand(listAddonsCmd)
 	listCmd.AddCommand(listAddonsCmd)
 
 
-	rootCmd.AddCommand(listCmd)
+	return listCmd
 }
 }
 
 
 func listAll(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {
 func listAll(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {

+ 14 - 17
cli/cmd/logs.go → cli/cmd/commands/logs.go

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 
 import (
 import (
 	"context"
 	"context"
@@ -12,24 +12,20 @@ import (
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 )
 )
 
 
-// logsCmd represents the "porter logs" base command when called
-// without any subcommands
-var logsCmd = &cobra.Command{
-	Use:   "logs [release]",
-	Args:  cobra.ExactArgs(1),
-	Short: "Logs the output from a given application.",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, logs)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
-
 var follow bool
 var follow bool
 
 
-func init() {
-	rootCmd.AddCommand(logsCmd)
+func registerCommand_Logs(cliConf config.CLIConfig) *cobra.Command {
+	logsCmd := &cobra.Command{
+		Use:   "logs [release]",
+		Args:  cobra.ExactArgs(1),
+		Short: "Logs the output from a given application.",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, logs)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
 	logsCmd.PersistentFlags().StringVar(
 	logsCmd.PersistentFlags().StringVar(
 		&namespace,
 		&namespace,
@@ -45,6 +41,7 @@ func init() {
 		false,
 		false,
 		"specify if the logs should be streamed",
 		"specify if the logs should be streamed",
 	)
 	)
+	return logsCmd
 }
 }
 
 
 func logs(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, args []string) error {
 func logs(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, args []string) error {

+ 43 - 0
cli/cmd/commands/open.go

@@ -0,0 +1,43 @@
+package commands
+
+import (
+	"fmt"
+	"os"
+
+	"github.com/fatih/color"
+	api "github.com/porter-dev/porter/api/client"
+	"github.com/porter-dev/porter/cli/cmd/config"
+	"github.com/porter-dev/porter/cli/cmd/utils"
+
+	"github.com/spf13/cobra"
+)
+
+func registerCommand_Open(cliConf config.CLIConfig) *cobra.Command {
+	openCmd := &cobra.Command{
+		Use:   "open",
+		Short: "Opens the browser at the currently set Porter instance",
+		Run: func(cmd *cobra.Command, args []string) {
+			ctx := cmd.Context()
+
+			client, err := api.NewClientWithConfig(ctx, api.NewClientInput{
+				BaseURL:        fmt.Sprintf("%s/api", cliConf.Host),
+				BearerToken:    cliConf.Token,
+				CookieFileName: "cookie.json",
+			})
+			if err != nil {
+				_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "error creating porter API client: %v\n", err)
+				os.Exit(1)
+			}
+
+			user, err := client.AuthCheck(ctx)
+			if err != nil {
+				_ = utils.OpenBrowser(fmt.Sprintf("%s/register", cliConf.Host))
+				return
+			}
+
+			_ = utils.OpenBrowser(fmt.Sprintf("%s/login?email=%s", cliConf.Host, user.Email)) //nolint:errcheck,gosec // do not want to change logic of CLI. New linter error
+		},
+	}
+
+	return openCmd
+}

+ 21 - 0
cli/cmd/commands/portforward.go

@@ -0,0 +1,21 @@
+package commands
+
+import (
+	"fmt"
+
+	"github.com/fatih/color"
+	"github.com/porter-dev/porter/cli/cmd/config"
+	"github.com/spf13/cobra"
+)
+
+func registerCommand_PortForward(_ config.CLIConfig) *cobra.Command {
+	portForwardCmd := &cobra.Command{
+		Use: "port-forward [release] [LOCAL_PORT:]REMOTE_PORT [...[LOCAL_PORT_N:]REMOTE_PORT_N]",
+		Deprecated: fmt.Sprintf("please use the %s command instead.",
+			color.New(color.FgYellow, color.Bold).Sprintf("porter kubectl -- port-forward"),
+		),
+		DisableFlagParsing: true,
+	}
+
+	return portForwardCmd
+}

+ 43 - 46
cli/cmd/project.go → cli/cmd/commands/project.go

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 
 import (
 import (
 	"context"
 	"context"
@@ -16,55 +16,52 @@ import (
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 )
 )
 
 
-// projectCmd represents the "porter project" base command when called
-// without any subcommands
-var projectCmd = &cobra.Command{
-	Use:     "project",
-	Aliases: []string{"projects"},
-	Short:   "Commands that control Porter project settings",
-}
-
-var createProjectCmd = &cobra.Command{
-	Use:   "create [name]",
-	Args:  cobra.ExactArgs(1),
-	Short: "Creates a project with the authorized user as admin",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, createProject)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
-
-var deleteProjectCmd = &cobra.Command{
-	Use:   "delete [id]",
-	Args:  cobra.ExactArgs(1),
-	Short: "Deletes the project with the given id",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, deleteProject)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
-
-var listProjectCmd = &cobra.Command{
-	Use:   "list",
-	Short: "Lists the projects for the logged in user",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, listProjects)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
-
-func init() {
-	rootCmd.AddCommand(projectCmd)
+func registerCommand_Project(cliConf config.CLIConfig) *cobra.Command {
+	projectCmd := &cobra.Command{
+		Use:     "project",
+		Aliases: []string{"projects"},
+		Short:   "Commands that control Porter project settings",
+	}
 
 
+	createProjectCmd := &cobra.Command{
+		Use:   "create [name]",
+		Args:  cobra.ExactArgs(1),
+		Short: "Creates a project with the authorized user as admin",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, createProject)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 	projectCmd.AddCommand(createProjectCmd)
 	projectCmd.AddCommand(createProjectCmd)
+
+	deleteProjectCmd := &cobra.Command{
+		Use:   "delete [id]",
+		Args:  cobra.ExactArgs(1),
+		Short: "Deletes the project with the given id",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, deleteProject)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 	projectCmd.AddCommand(deleteProjectCmd)
 	projectCmd.AddCommand(deleteProjectCmd)
+
+	listProjectCmd := &cobra.Command{
+		Use:   "list",
+		Short: "Lists the projects for the logged in user",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, listProjects)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 	projectCmd.AddCommand(listProjectCmd)
 	projectCmd.AddCommand(listProjectCmd)
+
+	return projectCmd
 }
 }
 
 
 func createProject(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {
 func createProject(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {

+ 61 - 63
cli/cmd/registry.go → cli/cmd/commands/registry.go

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 
 import (
 import (
 	"context"
 	"context"
@@ -16,74 +16,70 @@ import (
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 )
 )
 
 
-// registryCmd represents the "porter registry" base command when called
-// without any subcommands
-var registryCmd = &cobra.Command{
-	Use:     "registry",
-	Aliases: []string{"registries"},
-	Short:   "Commands that read from a connected registry",
-}
-
-var registryListCmd = &cobra.Command{
-	Use:   "list",
-	Short: "Lists the registries linked to a project",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, listRegistries)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+func registerCommand_Registry(cliConf config.CLIConfig) *cobra.Command {
+	registryCmd := &cobra.Command{
+		Use:     "registry",
+		Aliases: []string{"registries"},
+		Short:   "Commands that read from a connected registry",
+	}
 
 
-var registryDeleteCmd = &cobra.Command{
-	Use:   "delete [id]",
-	Args:  cobra.ExactArgs(1),
-	Short: "Deletes the registry with the given id",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, deleteRegistry)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+	registryListCmd := &cobra.Command{
+		Use:   "list",
+		Short: "Lists the registries linked to a project",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, listRegistries)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-var registryReposCmd = &cobra.Command{
-	Use:     "repo",
-	Aliases: []string{"repos", "repository", "repositories"},
-	Short:   "Commands that perform operations on image registry repositories",
-}
+	registryDeleteCmd := &cobra.Command{
+		Use:   "delete [id]",
+		Args:  cobra.ExactArgs(1),
+		Short: "Deletes the registry with the given id",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, deleteRegistry)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-var registryReposListCmd = &cobra.Command{
-	Use:   "list",
-	Short: "Lists the repositories in an image registry",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, listRepos)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+	registryReposCmd := &cobra.Command{
+		Use:     "repo",
+		Aliases: []string{"repos", "repository", "repositories"},
+		Short:   "Commands that perform operations on image registry repositories",
+	}
 
 
-var registryImageCmd = &cobra.Command{
-	Use:     "image",
-	Aliases: []string{"images"},
-	Short:   "Commands that perform operations on image in a repository",
-}
+	registryReposListCmd := &cobra.Command{
+		Use:   "list",
+		Short: "Lists the repositories in an image registry",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, listRepos)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-var registryImageListCmd = &cobra.Command{
-	Use:   "list [repo_name]",
-	Args:  cobra.ExactArgs(1),
-	Short: "Lists the images the specified image repository",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, listImages)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+	registryImageCmd := &cobra.Command{
+		Use:     "image",
+		Aliases: []string{"images"},
+		Short:   "Commands that perform operations on image in a repository",
+	}
 
 
-func init() {
-	rootCmd.AddCommand(registryCmd)
+	registryImageListCmd := &cobra.Command{
+		Use:   "list [repo_name]",
+		Args:  cobra.ExactArgs(1),
+		Short: "Lists the images the specified image repository",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, listImages)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
 	registryCmd.PersistentFlags().AddFlagSet(utils.RegistryFlagSet)
 	registryCmd.PersistentFlags().AddFlagSet(utils.RegistryFlagSet)
 
 
@@ -95,6 +91,8 @@ func init() {
 
 
 	registryCmd.AddCommand(registryImageCmd)
 	registryCmd.AddCommand(registryImageCmd)
 	registryImageCmd.AddCommand(registryImageListCmd)
 	registryImageCmd.AddCommand(registryImageListCmd)
+
+	return registryCmd
 }
 }
 
 
 func listRegistries(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {
 func listRegistries(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {

+ 6 - 12
cli/cmd/root.go → cli/cmd/commands/root.go

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 
 import (
 import (
 	"context"
 	"context"
@@ -12,25 +12,14 @@ import (
 	"github.com/fatih/color"
 	"github.com/fatih/color"
 	"github.com/google/go-github/v41/github"
 	"github.com/google/go-github/v41/github"
 	cfg "github.com/porter-dev/porter/cli/cmd/config"
 	cfg "github.com/porter-dev/porter/cli/cmd/config"
-	"github.com/porter-dev/porter/cli/cmd/utils"
-	"github.com/spf13/cobra"
 	"k8s.io/client-go/util/homedir"
 	"k8s.io/client-go/util/homedir"
 )
 )
 
 
-// rootCmd represents the base command when called without any subcommands
-var rootCmd = &cobra.Command{
-	Use:   "porter",
-	Short: "Porter is a dashboard for managing Kubernetes clusters.",
-	Long:  `Porter is a tool for creating, versioning, and updating Kubernetes deployments using a visual dashboard. For more information, visit github.com/porter-dev/porter`,
-}
-
 var home = homedir.HomeDir()
 var home = homedir.HomeDir()
 
 
 // Execute adds all child commands to the root command and sets flags appropriately.
 // Execute adds all child commands to the root command and sets flags appropriately.
 // This is called by main.main(). It only needs to happen once to the rootCmd.
 // This is called by main.main(). It only needs to happen once to the rootCmd.
 func Execute(ctx context.Context) error {
 func Execute(ctx context.Context) error {
-	rootCmd.PersistentFlags().AddFlagSet(utils.DefaultFlagSet)
-
 	if cfg.Version != "dev" {
 	if cfg.Version != "dev" {
 		ghClient := github.NewClient(nil)
 		ghClient := github.NewClient(nil)
 		ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
 		ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
@@ -57,6 +46,11 @@ func Execute(ctx context.Context) error {
 		}
 		}
 	}
 	}
 
 
+	rootCmd, err := RegisterCommands()
+	if err != nil {
+		return fmt.Errorf("error setting up commands")
+	}
+
 	if err := rootCmd.Execute(); err != nil {
 	if err := rootCmd.Execute(); err != nil {
 		color.New(color.FgRed).Println(err)
 		color.New(color.FgRed).Println(err)
 		os.Exit(1)
 		os.Exit(1)

+ 26 - 29
cli/cmd/run.go → cli/cmd/commands/run.go

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 
 import (
 import (
 	"context"
 	"context"
@@ -42,35 +42,31 @@ var (
 	memoryMi       int
 	memoryMi       int
 )
 )
 
 
-// runCmd represents the "porter run" base command when called
-// without any subcommands
-var runCmd = &cobra.Command{
-	Use:   "run [release] -- COMMAND [args...]",
-	Args:  cobra.MinimumNArgs(2),
-	Short: "Runs a command inside a connected cluster container.",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, run)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
-
-// cleanupCmd represents the "porter run cleanup" subcommand
-var cleanupCmd = &cobra.Command{
-	Use:   "cleanup",
-	Args:  cobra.NoArgs,
-	Short: "Delete any lingering ephemeral pods that were created with \"porter run\".",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, cleanup)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+func registerCommand_Run(cliConf config.CLIConfig) *cobra.Command {
+	runCmd := &cobra.Command{
+		Use:   "run [release] -- COMMAND [args...]",
+		Args:  cobra.MinimumNArgs(2),
+		Short: "Runs a command inside a connected cluster container.",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, run)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-func init() {
-	rootCmd.AddCommand(runCmd)
+	// cleanupCmd represents the "porter run cleanup" subcommand
+	cleanupCmd := &cobra.Command{
+		Use:   "cleanup",
+		Args:  cobra.NoArgs,
+		Short: "Delete any lingering ephemeral pods that were created with \"porter run\".",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, cleanup)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
 	runCmd.PersistentFlags().StringVar(
 	runCmd.PersistentFlags().StringVar(
 		&namespace,
 		&namespace,
@@ -127,6 +123,7 @@ func init() {
 	)
 	)
 
 
 	runCmd.AddCommand(cleanupCmd)
 	runCmd.AddCommand(cleanupCmd)
+	return runCmd
 }
 }
 
 
 func run(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {
 func run(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {

+ 59 - 69
cli/cmd/server.go → cli/cmd/commands/server.go

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 
 import (
 import (
 	"context"
 	"context"
@@ -25,83 +25,72 @@ type startOps struct {
 
 
 var opts = &startOps{}
 var opts = &startOps{}
 
 
-var serverCmd = &cobra.Command{
-	Use:     "server",
-	Aliases: []string{"svr"},
-	Short:   "Commands to control a local Porter server",
-}
-
-// startCmd represents the start command
-var startCmd = &cobra.Command{
-	Use:   "start",
-	Short: "Starts a Porter server instance on the host",
-	Run: func(cmd *cobra.Command, args []string) {
-		ctx := cmd.Context()
-		cliConf, err := config.InitAndLoadConfig()
-		if err != nil {
-			os.Exit(1)
-		}
+func registerCommand_Server(cliConf config.CLIConfig) *cobra.Command {
+	serverCmd := &cobra.Command{
+		Use:     "server",
+		Aliases: []string{"svr"},
+		Short:   "Commands to control a local Porter server",
+	}
 
 
-		if cliConf.Driver == "docker" {
-			cliConf.SetDriver("docker")
+	// startCmd represents the start command
+	startCmd := &cobra.Command{
+		Use:   "start",
+		Short: "Starts a Porter server instance on the host",
+		Run: func(cmd *cobra.Command, args []string) {
+			ctx := cmd.Context()
+
+			if cliConf.Driver == "docker" {
+				_ = cliConf.SetDriver("docker")
+
+				err := startDocker(
+					ctx,
+					cliConf,
+					opts.imageTag,
+					opts.db,
+					*opts.port,
+				)
+				if err != nil {
+					red := color.New(color.FgRed)
+					_, _ = red.Println("Error running start:", err.Error())
+					_, _ = red.Println("Shutting down...")
 
 
-			err := startDocker(
-				ctx,
-				cliConf,
-				opts.imageTag,
-				opts.db,
-				*opts.port,
-			)
-			if err != nil {
-				red := color.New(color.FgRed)
-				red.Println("Error running start:", err.Error())
-				red.Println("Shutting down...")
+					err = stopDocker(ctx)
 
 
-				err = stopDocker(ctx)
+					if err != nil {
+						_, _ = red.Println("Shutdown unsuccessful:", err.Error())
+					}
 
 
+					os.Exit(1)
+				}
+			} else {
+				_ = cliConf.SetDriver("local")
+				err := startLocal(
+					ctx,
+					cliConf,
+					opts.db,
+					*opts.port,
+				)
 				if err != nil {
 				if err != nil {
-					red.Println("Shutdown unsuccessful:", err.Error())
+					red := color.New(color.FgRed)
+					_, _ = red.Println("Error running start:", err.Error())
+					os.Exit(1)
 				}
 				}
-
-				os.Exit(1)
 			}
 			}
-		} else {
-			cliConf.SetDriver("local")
-			err := startLocal(
-				ctx,
-				cliConf,
-				opts.db,
-				*opts.port,
-			)
-			if err != nil {
-				red := color.New(color.FgRed)
-				red.Println("Error running start:", err.Error())
-				os.Exit(1)
-			}
-		}
-	},
-}
-
-var stopCmd = &cobra.Command{
-	Use:   "stop",
-	Short: "Stops a Porter instance running on the Docker engine",
-	Run: func(cmd *cobra.Command, args []string) {
-		cliConf, err := config.InitAndLoadConfig()
-		if err != nil {
-			os.Exit(1)
-		}
+		},
+	}
 
 
-		if cliConf.Driver == "docker" {
-			if err := stopDocker(cmd.Context()); err != nil {
-				color.New(color.FgRed).Println("Shutdown unsuccessful:", err.Error())
-				os.Exit(1)
+	stopCmd := &cobra.Command{
+		Use:   "stop",
+		Short: "Stops a Porter instance running on the Docker engine",
+		Run: func(cmd *cobra.Command, args []string) {
+			if cliConf.Driver == "docker" {
+				if err := stopDocker(cmd.Context()); err != nil {
+					_, _ = color.New(color.FgRed).Println("Shutdown unsuccessful:", err.Error())
+					os.Exit(1)
+				}
 			}
 			}
-		}
-	},
-}
-
-func init() {
-	rootCmd.AddCommand(serverCmd)
+		},
+	}
 
 
 	serverCmd.AddCommand(startCmd)
 	serverCmd.AddCommand(startCmd)
 	serverCmd.AddCommand(stopCmd)
 	serverCmd.AddCommand(stopCmd)
@@ -128,6 +117,7 @@ func init() {
 		8080,
 		8080,
 		"the host port to run the server on",
 		"the host port to run the server on",
 	)
 	)
+	return serverCmd
 }
 }
 
 
 func startDocker(
 func startDocker(

+ 39 - 41
cli/cmd/stack.go → cli/cmd/commands/stack.go

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 
 import (
 import (
 	"context"
 	"context"
@@ -16,49 +16,45 @@ import (
 
 
 var linkedApps []string
 var linkedApps []string
 
 
-// stackCmd represents the "porter stack" base command when called
-// without any subcommands
-var stackCmd = &cobra.Command{
-	Use:     "stack",
-	Aliases: []string{"stacks"},
-	Short:   "Commands that control Porter Stacks",
-}
-
-var stackEnvGroupCmd = &cobra.Command{
-	Use:     "env-group",
-	Aliases: []string{"eg", "envgroup", "env-groups", "envgroups"},
-	Short:   "Commands to add or remove an env group in a stack",
-	Run: func(cmd *cobra.Command, args []string) {
-		color.New(color.FgRed).Fprintln(os.Stderr, "need to specify an operation to continue")
-	},
-}
+func registerCommand_Stack(cliConf config.CLIConfig) *cobra.Command {
+	stackCmd := &cobra.Command{
+		Use:     "stack",
+		Aliases: []string{"stacks"},
+		Short:   "Commands that control Porter Stacks",
+	}
 
 
-var stackEnvGroupAddCmd = &cobra.Command{
-	Use:   "add [name]",
-	Args:  cobra.ExactArgs(1),
-	Short: "Add an env group to a stack",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, stackAddEnvGroup)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+	stackEnvGroupCmd := &cobra.Command{
+		Use:     "env-group",
+		Aliases: []string{"eg", "envgroup", "env-groups", "envgroups"},
+		Short:   "Commands to add or remove an env group in a stack",
+		Run: func(cmd *cobra.Command, args []string) {
+			_, _ = color.New(color.FgRed).Fprintln(os.Stderr, "need to specify an operation to continue")
+		},
+	}
 
 
-var stackEnvGroupRemoveCmd = &cobra.Command{
-	Use:   "remove [name]",
-	Args:  cobra.ExactArgs(1),
-	Short: "Remove an existing env group from a stack",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, stackRemoveEnvGroup)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+	stackEnvGroupAddCmd := &cobra.Command{
+		Use:   "add [name]",
+		Args:  cobra.ExactArgs(1),
+		Short: "Add an env group to a stack",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, stackAddEnvGroup)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-func init() {
-	rootCmd.AddCommand(stackCmd)
+	stackEnvGroupRemoveCmd := &cobra.Command{
+		Use:   "remove [name]",
+		Args:  cobra.ExactArgs(1),
+		Short: "Remove an existing env group from a stack",
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, stackRemoveEnvGroup)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
 	stackCmd.AddCommand(stackEnvGroupCmd)
 	stackCmd.AddCommand(stackEnvGroupCmd)
 
 
@@ -101,6 +97,8 @@ func init() {
 
 
 	stackEnvGroupCmd.AddCommand(stackEnvGroupAddCmd)
 	stackEnvGroupCmd.AddCommand(stackEnvGroupAddCmd)
 	stackEnvGroupCmd.AddCommand(stackEnvGroupRemoveCmd)
 	stackEnvGroupCmd.AddCommand(stackEnvGroupRemoveCmd)
+
+	return stackCmd
 }
 }
 
 
 func stackAddEnvGroup(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {
 func stackAddEnvGroup(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {

+ 138 - 137
cli/cmd/deploy.go → cli/cmd/commands/update.go

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 
 import (
 import (
 	"context"
 	"context"
@@ -25,12 +25,33 @@ import (
 	"k8s.io/client-go/util/homedir"
 	"k8s.io/client-go/util/homedir"
 )
 )
 
 
-// updateCmd represents the "porter update" base command when called
-// without any subcommands
-var updateCmd = &cobra.Command{
-	Use:   "update",
-	Short: "Builds and updates a specified application given by the --app flag.",
-	Long: fmt.Sprintf(`
+var (
+	app                     string
+	getEnvFileDest          string
+	localPath               string
+	tag                     string
+	dockerfile              string
+	method                  string
+	stream                  bool
+	buildFlagsEnv           []string
+	forcePush               bool
+	useCache                bool
+	version                 uint
+	varType                 string
+	normalEnvGroupVars      []string
+	secretEnvGroupVars      []string
+	waitForSuccessfulDeploy bool
+)
+
+func registerCommand_Update(cliConf config.CLIConfig) *cobra.Command {
+	buildFlagsEnv = []string{}
+
+	// updateCmd represents the "porter update" base command when called
+	// without any subcommands
+	updateCmd := &cobra.Command{
+		Use:   "update",
+		Short: "Builds and updates a specified application given by the --app flag.",
+		Long: fmt.Sprintf(`
 %s
 %s
 
 
 Builds and updates a specified application given by the --app flag. For example:
 Builds and updates a specified application given by the --app flag. For example:
@@ -63,25 +84,25 @@ specify it as follows:
 
 
   %s
   %s
 `,
 `,
-		color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter update\":"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter update --app example-app"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter update --app example-app --path ~/path-to-dir --tag testing"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter update --app remote-git-app --source github"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter update --app example-app --values my-values.yaml"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter update --app example-app --method docker --dockerfile ./docker/prod.Dockerfile"),
-	),
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, updateFull)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+			color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter update\":"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter update --app example-app"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter update --app example-app --path ~/path-to-dir --tag testing"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter update --app remote-git-app --source github"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter update --app example-app --values my-values.yaml"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter update --app example-app --method docker --dockerfile ./docker/prod.Dockerfile"),
+		),
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, updateFull)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-var updateGetEnvCmd = &cobra.Command{
-	Use:   "get-env",
-	Short: "Gets environment variables for a deployment for a specified application given by the --app flag.",
-	Long: fmt.Sprintf(`
+	updateGetEnvCmd := &cobra.Command{
+		Use:   "get-env",
+		Short: "Gets environment variables for a deployment for a specified application given by the --app flag.",
+		Long: fmt.Sprintf(`
 %s
 %s
 
 
 Gets environment variables for a deployment for a specified application given by the --app
 Gets environment variables for a deployment for a specified application given by the --app
@@ -94,22 +115,22 @@ destination path for a .env file. For example:
 
 
   %s
   %s
 `,
 `,
-		color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter update get-env\":"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter update get-env --app example-app | xargs"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter update get-env --app example-app --file .env"),
-	),
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, updateGetEnv)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+			color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter update get-env\":"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter update get-env --app example-app | xargs"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter update get-env --app example-app --file .env"),
+		),
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, updateGetEnv)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-var updateBuildCmd = &cobra.Command{
-	Use:   "build",
-	Short: "Builds a new version of the application specified by the --app flag.",
-	Long: fmt.Sprintf(`
+	updateBuildCmd := &cobra.Command{
+		Use:   "build",
+		Short: "Builds a new version of the application specified by the --app flag.",
+		Long: fmt.Sprintf(`
 %s
 %s
 
 
 Builds a new version of the application specified by the --app flag. Depending on the
 Builds a new version of the application specified by the --app flag. Depending on the
@@ -135,24 +156,24 @@ for the application:
 
 
   %s
   %s
 `,
 `,
-		color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter update build\":"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter update build --app example-app"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter update build --app example-app --method docker"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter update build --app example-app --method docker --dockerfile ./prod.Dockerfile"),
-	),
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, updateBuild)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+			color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter update build\":"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter update build --app example-app"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter update build --app example-app --method docker"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter update build --app example-app --method docker --dockerfile ./prod.Dockerfile"),
+		),
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, updateBuild)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-var updatePushCmd = &cobra.Command{
-	Use:   "push",
-	Short: "Pushes an image to a Docker registry linked to your Porter project.",
-	Args:  cobra.MaximumNArgs(1),
-	Long: fmt.Sprintf(`
+	updatePushCmd := &cobra.Command{
+		Use:   "push",
+		Short: "Pushes an image to a Docker registry linked to your Porter project.",
+		Args:  cobra.MaximumNArgs(1),
+		Long: fmt.Sprintf(`
 %s
 %s
 
 
 Pushes a local Docker image to a registry linked to your Porter project. This command
 Pushes a local Docker image to a registry linked to your Porter project. This command
@@ -174,24 +195,24 @@ This command will not use your pre-saved authentication set up via "docker login
 are using an image registry that was created outside of Porter, make sure that you have
 are using an image registry that was created outside of Porter, make sure that you have
 linked it via "porter connect".
 linked it via "porter connect".
 `,
 `,
-		color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter update push\":"),
-		color.New(color.FgBlue).Sprintf("porter config set-project"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter update push gcr.io/snowflake-123456/nginx:1234567"),
-		color.New(color.Bold).Sprintf("LEGACY USAGE:"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter update push --app nginx --tag new-tag"),
-	),
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, updatePush)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+			color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter update push\":"),
+			color.New(color.FgBlue).Sprintf("porter config set-project"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter update push gcr.io/snowflake-123456/nginx:1234567"),
+			color.New(color.Bold).Sprintf("LEGACY USAGE:"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter update push --app nginx --tag new-tag"),
+		),
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, updatePush)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-var updateConfigCmd = &cobra.Command{
-	Use:   "config",
-	Short: "Updates the configuration for an application specified by the --app flag.",
-	Long: fmt.Sprintf(`
+	updateConfigCmd := &cobra.Command{
+		Use:   "config",
+		Short: "Updates the configuration for an application specified by the --app flag.",
+		Long: fmt.Sprintf(`
 %s
 %s
 
 
 Updates the configuration for an application specified by the --app flag, using the configuration
 Updates the configuration for an application specified by the --app flag, using the configuration
@@ -206,72 +227,50 @@ the image that the application uses if no --values file is specified:
 
 
   %s
   %s
 `,
 `,
-		color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter update config\":"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter update config --app example-app --values my-values.yaml"),
-		color.New(color.FgGreen, color.Bold).Sprintf("porter update config --app example-app --tag custom-tag"),
-	),
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, updateUpgrade)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
-
-var updateEnvGroupCmd = &cobra.Command{
-	Use:     "env-group",
-	Aliases: []string{"eg", "envgroup", "env-groups", "envgroups"},
-	Short:   "Updates an environment group's variables, specified by the --name flag.",
-	Run: func(cmd *cobra.Command, args []string) {
-		color.New(color.FgRed).Fprintln(os.Stderr, "need to specify an operation to continue")
-	},
-}
-
-var updateSetEnvGroupCmd = &cobra.Command{
-	Use:   "set",
-	Short: "Sets the desired value of an environment variable in an env group in the form VAR=VALUE.",
-	Args:  cobra.MaximumNArgs(1),
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, updateSetEnvGroup)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+			color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter update config\":"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter update config --app example-app --values my-values.yaml"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter update config --app example-app --tag custom-tag"),
+		),
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, updateUpgrade)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-var updateUnsetEnvGroupCmd = &cobra.Command{
-	Use:   "unset",
-	Short: "Removes an environment variable from an env group.",
-	Args:  cobra.MinimumNArgs(1),
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, updateUnsetEnvGroup)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
+	updateEnvGroupCmd := &cobra.Command{
+		Use:     "env-group",
+		Aliases: []string{"eg", "envgroup", "env-groups", "envgroups"},
+		Short:   "Updates an environment group's variables, specified by the --name flag.",
+		Run: func(cmd *cobra.Command, args []string) {
+			_, _ = color.New(color.FgRed).Fprintln(os.Stderr, "need to specify an operation to continue")
+		},
+	}
 
 
-var (
-	app                     string
-	getEnvFileDest          string
-	localPath               string
-	tag                     string
-	dockerfile              string
-	method                  string
-	stream                  bool
-	buildFlagsEnv           []string
-	forcePush               bool
-	useCache                bool
-	version                 uint
-	varType                 string
-	normalEnvGroupVars      []string
-	secretEnvGroupVars      []string
-	waitForSuccessfulDeploy bool
-)
+	updateSetEnvGroupCmd := &cobra.Command{
+		Use:   "set",
+		Short: "Sets the desired value of an environment variable in an env group in the form VAR=VALUE.",
+		Args:  cobra.MaximumNArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, updateSetEnvGroup)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
-func init() {
-	buildFlagsEnv = []string{}
-	rootCmd.AddCommand(updateCmd)
+	updateUnsetEnvGroupCmd := &cobra.Command{
+		Use:   "unset",
+		Short: "Removes an environment variable from an env group.",
+		Args:  cobra.MinimumNArgs(1),
+		Run: func(cmd *cobra.Command, args []string) {
+			err := checkLoginAndRunWithConfig(cmd.Context(), cliConf, args, updateUnsetEnvGroup)
+			if err != nil {
+				os.Exit(1)
+			}
+		},
+	}
 
 
 	updateCmd.PersistentFlags().StringVar(
 	updateCmd.PersistentFlags().StringVar(
 		&app,
 		&app,
@@ -440,6 +439,8 @@ func init() {
 	updateCmd.AddCommand(updatePushCmd)
 	updateCmd.AddCommand(updatePushCmd)
 	updateCmd.AddCommand(updateConfigCmd)
 	updateCmd.AddCommand(updateConfigCmd)
 	updateCmd.AddCommand(updateEnvGroupCmd)
 	updateCmd.AddCommand(updateEnvGroupCmd)
+
+	return updateCmd
 }
 }
 
 
 func updateFull(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {
 func updateFull(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {

+ 20 - 0
cli/cmd/commands/version.go

@@ -0,0 +1,20 @@
+package commands
+
+import (
+	"fmt"
+
+	"github.com/porter-dev/porter/cli/cmd/config"
+	"github.com/spf13/cobra"
+)
+
+func registerCommand_Version(_ config.CLIConfig) *cobra.Command {
+	versionCmd := &cobra.Command{
+		Use:     "version",
+		Aliases: []string{"v", "--version"},
+		Short:   "Prints the version of the Porter CLI",
+		Run: func(cmd *cobra.Command, args []string) {
+			fmt.Println(config.Version)
+		},
+	}
+	return versionCmd
+}

+ 0 - 347
cli/cmd/config.go

@@ -1,347 +0,0 @@
-package cmd
-
-import (
-	"context"
-	"fmt"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"strconv"
-	"strings"
-	"time"
-
-	"github.com/briandowns/spinner"
-	"github.com/fatih/color"
-	api "github.com/porter-dev/porter/api/client"
-	"github.com/porter-dev/porter/api/types"
-	"github.com/porter-dev/porter/cli/cmd/config"
-	"github.com/porter-dev/porter/cli/cmd/utils"
-	"github.com/spf13/cobra"
-)
-
-// var cliConf = cliConfig.GetCLIConfig()
-
-var configCmd = &cobra.Command{
-	Use:   "config",
-	Short: "Commands that control local configuration settings",
-	Run: func(cmd *cobra.Command, args []string) {
-		if err := printConfig(); err != nil {
-			color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
-			os.Exit(1)
-		}
-	},
-}
-
-var configSetProjectCmd = &cobra.Command{
-	Use:   "set-project [id]",
-	Args:  cobra.MaximumNArgs(1),
-	Short: "Saves the project id in the default configuration",
-	Run: func(cmd *cobra.Command, args []string) {
-		cliConf, err := config.InitAndLoadConfig()
-		if err != nil {
-			color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred loading config: %s\n", err.Error()) //nolint:errcheck,gosec // do not want to change logic of CLI. New linter error
-			os.Exit(1)
-		}
-		client, err := api.NewClientWithConfig(cmd.Context(), api.NewClientInput{
-			BaseURL:        fmt.Sprintf("%s/api", cliConf.Host),
-			BearerToken:    cliConf.Token,
-			CookieFileName: "cookie.json",
-		})
-		if err != nil {
-			_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "error creating porter API client: %s\n", err.Error())
-			os.Exit(1)
-		}
-
-		if len(args) == 0 {
-			err := checkLoginAndRun(cmd.Context(), args, listAndSetProject)
-			if err != nil {
-				os.Exit(1)
-			}
-		} else {
-			projID, err := strconv.ParseUint(args[0], 10, 64)
-			if err != nil {
-				_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %s\n", err.Error())
-				os.Exit(1)
-			}
-
-			err = cliConf.SetProject(cmd.Context(), client, uint(projID))
-			if err != nil {
-				_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %s\n", err.Error())
-				os.Exit(1)
-			}
-		}
-	},
-}
-
-var configSetClusterCmd = &cobra.Command{
-	Use:   "set-cluster [id]",
-	Args:  cobra.MaximumNArgs(1),
-	Short: "Saves the cluster id in the default configuration",
-	Run: func(cmd *cobra.Command, args []string) {
-		cliConf, err := config.InitAndLoadConfig()
-		if err != nil {
-			color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred loading config: %v\n", err) //nolint:errcheck,gosec // do not want to change logic of CLI. New linter error
-			os.Exit(1)
-		}
-		if len(args) == 0 {
-			err := checkLoginAndRun(cmd.Context(), args, listAndSetCluster)
-			if err != nil {
-				os.Exit(1)
-			}
-		} else {
-			clusterID, err := strconv.ParseUint(args[0], 10, 64)
-			if err != nil {
-				color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
-				os.Exit(1)
-			}
-
-			err = cliConf.SetCluster(uint(clusterID))
-
-			if err != nil {
-				color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
-				os.Exit(1)
-			}
-		}
-	},
-}
-
-var configSetRegistryCmd = &cobra.Command{
-	Use:   "set-registry [id]",
-	Args:  cobra.MaximumNArgs(1),
-	Short: "Saves the registry id in the default configuration",
-	Run: func(cmd *cobra.Command, args []string) {
-		cliConf, err := config.InitAndLoadConfig()
-		if err != nil {
-			color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred loading config: %v\n", err) //nolint:errcheck,gosec // do not want to change logic of CLI. New linter error
-			os.Exit(1)
-		}
-
-		if len(args) == 0 {
-			err := checkLoginAndRun(cmd.Context(), args, listAndSetRegistry)
-			if err != nil {
-				os.Exit(1)
-			}
-		} else {
-			registryID, err := strconv.ParseUint(args[0], 10, 64)
-			if err != nil {
-				color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
-				os.Exit(1)
-			}
-
-			err = cliConf.SetRegistry(uint(registryID))
-
-			if err != nil {
-				color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
-				os.Exit(1)
-			}
-		}
-	},
-}
-
-var configSetHelmRepoCmd = &cobra.Command{
-	Use:   "set-helmrepo [id]",
-	Args:  cobra.ExactArgs(1),
-	Short: "Saves the helm repo id in the default configuration",
-	Run: func(cmd *cobra.Command, args []string) {
-		cliConf, err := config.InitAndLoadConfig()
-		if err != nil {
-			color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred loading config: %v\n", err) //nolint:errcheck,gosec // do not want to change logic of CLI. New linter error
-			os.Exit(1)
-		}
-
-		hrID, err := strconv.ParseUint(args[0], 10, 64)
-		if err != nil {
-			color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
-			os.Exit(1)
-		}
-
-		err = cliConf.SetHelmRepo(uint(hrID))
-
-		if err != nil {
-			color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
-			os.Exit(1)
-		}
-	},
-}
-
-var configSetHostCmd = &cobra.Command{
-	Use:   "set-host [host]",
-	Args:  cobra.ExactArgs(1),
-	Short: "Saves the host in the default configuration",
-	Run: func(cmd *cobra.Command, args []string) {
-		cliConf, err := config.InitAndLoadConfig()
-		if err != nil {
-			color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred loading config: %v\n", err) //nolint:errcheck,gosec // do not want to change logic of CLI. New linter error
-			os.Exit(1)
-		}
-		err = cliConf.SetHost(args[0])
-		if err != nil {
-			color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
-			os.Exit(1)
-		}
-	},
-}
-
-var configSetKubeconfigCmd = &cobra.Command{
-	Use:   "set-kubeconfig [kubeconfig-path]",
-	Args:  cobra.ExactArgs(1),
-	Short: "Saves the path to kubeconfig in the default configuration",
-	Run: func(cmd *cobra.Command, args []string) {
-		cliConf, err := config.InitAndLoadConfig()
-		if err != nil {
-			color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred loading config: %v\n", err) //nolint:errcheck,gosec // do not want to change logic of CLI. New linter error
-			os.Exit(1)
-		}
-		err = cliConf.SetKubeconfig(args[0])
-		if err != nil {
-			color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
-			os.Exit(1)
-		}
-	},
-}
-
-func init() {
-	rootCmd.AddCommand(configCmd)
-
-	configCmd.AddCommand(configSetProjectCmd)
-	configCmd.AddCommand(configSetClusterCmd)
-	configCmd.AddCommand(configSetHostCmd)
-	configCmd.AddCommand(configSetRegistryCmd)
-	configCmd.AddCommand(configSetHelmRepoCmd)
-	configCmd.AddCommand(configSetKubeconfigCmd)
-}
-
-func printConfig() error {
-	config, err := ioutil.ReadFile(filepath.Join(home, ".porter", "porter.yaml"))
-	if err != nil {
-		return err
-	}
-
-	fmt.Println(string(config))
-
-	return nil
-}
-
-func listAndSetProject(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {
-	s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
-	s.Color("cyan")
-	s.Suffix = " Loading list of projects"
-	s.Start()
-
-	resp, err := client.ListUserProjects(ctx)
-
-	s.Stop()
-
-	if err != nil {
-		return err
-	}
-
-	var projID uint64
-
-	if len(*resp) > 1 {
-		// only give the option to select when more than one option exists
-		projName, err := utils.PromptSelect("Select a project with ID", func() []string {
-			var names []string
-
-			for _, proj := range *resp {
-				names = append(names, fmt.Sprintf("%s - %d", proj.Name, proj.ID))
-			}
-
-			return names
-		}())
-		if err != nil {
-			return err
-		}
-
-		projID, _ = strconv.ParseUint(strings.Split(projName, " - ")[1], 10, 64)
-	} else {
-		projID = uint64((*resp)[0].ID)
-	}
-
-	err = cliConf.SetProject(ctx, client, uint(projID))
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func listAndSetCluster(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {
-	s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
-	s.Color("cyan")
-	s.Suffix = " Loading list of clusters"
-	s.Start()
-
-	resp, err := client.ListProjectClusters(ctx, cliConf.Project)
-
-	s.Stop()
-
-	if err != nil {
-		return err
-	}
-
-	var clusterID uint64
-
-	if len(*resp) > 1 {
-		clusterName, err := utils.PromptSelect("Select a cluster with ID", func() []string {
-			var names []string
-
-			for _, cluster := range *resp {
-				names = append(names, fmt.Sprintf("%s - %d", cluster.Name, cluster.ID))
-			}
-
-			return names
-		}())
-		if err != nil {
-			return err
-		}
-
-		clusterID, _ = strconv.ParseUint(strings.Split(clusterName, " - ")[1], 10, 64)
-	} else {
-		clusterID = uint64((*resp)[0].ID)
-	}
-
-	cliConf.SetCluster(uint(clusterID))
-
-	return nil
-}
-
-func listAndSetRegistry(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {
-	s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
-	s.Color("cyan")
-	s.Suffix = " Loading list of registries"
-	s.Start()
-
-	resp, err := client.ListRegistries(ctx, cliConf.Project)
-
-	s.Stop()
-
-	if err != nil {
-		return err
-	}
-
-	var regID uint64
-
-	if len(*resp) > 1 {
-		regName, err := utils.PromptSelect("Select a registry with ID", func() []string {
-			var names []string
-
-			for _, cluster := range *resp {
-				names = append(names, fmt.Sprintf("%s - %d", cluster.Name, cluster.ID))
-			}
-
-			return names
-		}())
-		if err != nil {
-			return err
-		}
-
-		regID, _ = strconv.ParseUint(strings.Split(regName, " - ")[1], 10, 64)
-	} else {
-		regID = uint64((*resp)[0].ID)
-	}
-
-	cliConf.SetRegistry(uint(regID))
-
-	return nil
-}

+ 6 - 7
cli/cmd/config/config.go

@@ -17,9 +17,6 @@ import (
 
 
 var home = homedir.HomeDir()
 var home = homedir.HomeDir()
 
 
-// config is a shared object used by all commands
-// var config = &CLIConfig{}
-
 // CLIConfig is the set of shared configuration options for the CLI commands.
 // CLIConfig is the set of shared configuration options for the CLI commands.
 // This config is used by viper: calling Set() function for any parameter will
 // This config is used by viper: calling Set() function for any parameter will
 // update the corresponding field in the viper config file.
 // update the corresponding field in the viper config file.
@@ -98,12 +95,18 @@ func initAndLoadConfig() (CLIConfig, error) {
 		"token for Porter authentication",
 		"token for Porter authentication",
 	)
 	)
 
 
+	err = viper.BindPFlags(utils.DefaultFlagSet)
+	if err != nil {
+		return config, err
+	}
+
 	utils.RegistryFlagSet.UintVar(
 	utils.RegistryFlagSet.UintVar(
 		&config.Registry,
 		&config.Registry,
 		"registry",
 		"registry",
 		0,
 		0,
 		"registry ID of connected Porter registry",
 		"registry ID of connected Porter registry",
 	)
 	)
+
 	err = viper.BindPFlags(utils.RegistryFlagSet)
 	err = viper.BindPFlags(utils.RegistryFlagSet)
 	if err != nil {
 	if err != nil {
 		return config, err
 		return config, err
@@ -119,10 +122,6 @@ func initAndLoadConfig() (CLIConfig, error) {
 	if err != nil {
 	if err != nil {
 		return config, err
 		return config, err
 	}
 	}
-	err = viper.BindPFlags(utils.DefaultFlagSet)
-	if err != nil {
-		return config, err
-	}
 
 
 	viper.SetEnvPrefix("PORTER")
 	viper.SetEnvPrefix("PORTER")
 	err = viper.BindEnv("host")
 	err = viper.BindEnv("host")

+ 0 - 37
cli/cmd/docker.go

@@ -1,37 +0,0 @@
-package cmd
-
-import (
-	"context"
-	"os"
-
-	api "github.com/porter-dev/porter/api/client"
-	ptypes "github.com/porter-dev/porter/api/types"
-	"github.com/porter-dev/porter/cli/cmd/config"
-	"github.com/spf13/cobra"
-)
-
-var dockerCmd = &cobra.Command{
-	Use:   "docker",
-	Short: "Commands to configure Docker for a project",
-}
-
-var configureCmd = &cobra.Command{
-	Use:   "configure",
-	Short: "Configures the host's Docker instance",
-	Run: func(cmd *cobra.Command, args []string) {
-		err := checkLoginAndRun(cmd.Context(), args, dockerConfig)
-		if err != nil {
-			os.Exit(1)
-		}
-	},
-}
-
-func init() {
-	rootCmd.AddCommand(dockerCmd)
-
-	dockerCmd.AddCommand(configureCmd)
-}
-
-func dockerConfig(ctx context.Context, user *ptypes.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, args []string) error {
-	return config.SetDockerConfig(ctx, client, cliConf.Project)
-}

+ 0 - 48
cli/cmd/open.go

@@ -1,48 +0,0 @@
-package cmd
-
-import (
-	"fmt"
-	"os"
-
-	"github.com/fatih/color"
-	api "github.com/porter-dev/porter/api/client"
-	"github.com/porter-dev/porter/cli/cmd/config"
-	"github.com/porter-dev/porter/cli/cmd/utils"
-
-	"github.com/spf13/cobra"
-)
-
-var openCmd = &cobra.Command{
-	Use:   "open",
-	Short: "Opens the browser at the currently set Porter instance",
-	Run: func(cmd *cobra.Command, args []string) {
-		ctx := cmd.Context()
-		cliConf, err := config.InitAndLoadConfig()
-		if err != nil {
-			color.New(color.FgRed).Fprintf(os.Stderr, "error loading porter config: %s\n", err) //nolint:errcheck,gosec // do not want to change logic of CLI. New linter error
-			os.Exit(1)
-		}
-
-		client, err := api.NewClientWithConfig(ctx, api.NewClientInput{
-			BaseURL:        fmt.Sprintf("%s/api", cliConf.Host),
-			BearerToken:    cliConf.Token,
-			CookieFileName: "cookie.json",
-		})
-		if err != nil {
-			_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "error creating porter API client: %v\n", err)
-			os.Exit(1)
-		}
-
-		user, err := client.AuthCheck(ctx)
-		if err != nil {
-			utils.OpenBrowser(fmt.Sprintf("%s/register", cliConf.Host))
-			return
-		}
-
-		utils.OpenBrowser(fmt.Sprintf("%s/login?email=%s", cliConf.Host, user.Email)) //nolint:errcheck,gosec // do not want to change logic of CLI. New linter error
-	},
-}
-
-func init() {
-	rootCmd.AddCommand(openCmd)
-}

+ 0 - 20
cli/cmd/portforward.go

@@ -1,20 +0,0 @@
-package cmd
-
-import (
-	"fmt"
-
-	"github.com/fatih/color"
-	"github.com/spf13/cobra"
-)
-
-var portForwardCmd = &cobra.Command{
-	Use: "port-forward [release] [LOCAL_PORT:]REMOTE_PORT [...[LOCAL_PORT_N:]REMOTE_PORT_N]",
-	Deprecated: fmt.Sprintf("please use the %s command instead.",
-		color.New(color.FgYellow, color.Bold).Sprintf("porter kubectl -- port-forward"),
-	),
-	DisableFlagParsing: true,
-}
-
-func init() {
-	rootCmd.AddCommand(portForwardCmd)
-}

+ 0 - 21
cli/cmd/version.go

@@ -1,21 +0,0 @@
-package cmd
-
-import (
-	"fmt"
-
-	"github.com/porter-dev/porter/cli/cmd/config"
-	"github.com/spf13/cobra"
-)
-
-var versionCmd = &cobra.Command{
-	Use:     "version",
-	Aliases: []string{"v", "--version"},
-	Short:   "Prints the version of the Porter CLI",
-	Run: func(cmd *cobra.Command, args []string) {
-		fmt.Println(config.Version)
-	},
-}
-
-func init() {
-	rootCmd.AddCommand(versionCmd)
-}

+ 2 - 2
cli/main.go

@@ -10,7 +10,7 @@ import (
 
 
 	"github.com/fatih/color"
 	"github.com/fatih/color"
 	"github.com/getsentry/sentry-go"
 	"github.com/getsentry/sentry-go"
-	"github.com/porter-dev/porter/cli/cmd"
+	"github.com/porter-dev/porter/cli/cmd/commands"
 	"github.com/porter-dev/porter/cli/cmd/config"
 	"github.com/porter-dev/porter/cli/cmd/config"
 	"github.com/porter-dev/porter/cli/cmd/errors"
 	"github.com/porter-dev/porter/cli/cmd/errors"
 )
 )
@@ -35,7 +35,7 @@ func main() {
 		defer sentry.Flush(2 * time.Second)
 		defer sentry.Flush(2 * time.Second)
 	}
 	}
 
 
-	err := cmd.Execute(ctx)
+	err := commands.Execute(ctx)
 	if err != nil {
 	if err != nil {
 		color.New(color.FgRed).Fprintf(os.Stderr, "error executing command: %s\n", err)
 		color.New(color.FgRed).Fprintf(os.Stderr, "error executing command: %s\n", err)
 		os.Exit(1)
 		os.Exit(1)