Ver Fonte

Remove global commands (#3428)

Stefan McShane há 2 anos atrás
pai
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 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
@@ -79,9 +76,12 @@ func NewClientWithConfig(ctx context.Context, input NewClientInput) (Client, err
 		}
 		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
 func NewClient(baseURL string, cookieFileName string) *Client {
 	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 (
 	"context"
@@ -43,55 +43,67 @@ var (
 	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(
 		&appExistingPod,
 		"existing_pod",
@@ -138,17 +150,6 @@ func init() {
 		"",
 		"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 {

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

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"context"
@@ -39,12 +39,13 @@ import (
 	"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
 
 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_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.PersistentFlags().StringVarP(&porterYAML, "file", "f", "", "path to porter.yaml")
 	applyCmd.MarkFlagRequired("file")
+
+	return applyCmd
 }
 
 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 (
 	"context"
+	"errors"
 	"fmt"
 	"os"
+	"strings"
 
 	"github.com/fatih/color"
 
@@ -15,50 +17,48 @@ import (
 	"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(registerCmd)
@@ -70,101 +70,104 @@ func init() {
 		false,
 		"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{
 		BaseURL:     fmt.Sprintf("%s/api", cliConf.Host),
 		BearerToken: cliConf.Token,
 	})
 	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)
+	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 {
 		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 {
 		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 {
@@ -240,18 +243,15 @@ func loginManual(ctx context.Context, cliConf config.CLIConfig, client api.Clien
 	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{
-		BaseURL:     fmt.Sprintf("%s/api", config.Host),
-		BearerToken: config.Token,
+		BaseURL:     fmt.Sprintf("%s/api", cliConf.Host),
+		BearerToken: cliConf.Token,
 	})
 	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:")

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

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"context"
@@ -16,62 +16,58 @@ import (
 	"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(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)
+
+	return clusterCmd
 }
 
 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 (
 	"context"
@@ -17,102 +17,100 @@ var (
 	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)
 
@@ -137,6 +135,7 @@ func init() {
 	connectCmd.AddCommand(connectGARCmd)
 	connectCmd.AddCommand(connectDOCRCmd)
 	connectCmd.AddCommand(connectHelmRepoCmd)
+	return connectCmd
 }
 
 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 (
 	"context"
@@ -22,13 +22,21 @@ import (
 	"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
 
 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
 `,
-		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(
 		&name,
@@ -179,6 +175,7 @@ func init() {
 	)
 
 	createCmd.PersistentFlags().MarkDeprecated("force-build", "--force-build is deprecated")
+	return createCmd
 }
 
 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 (
 	"context"
@@ -15,11 +15,11 @@ import (
 	"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
 
 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_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(
 		&namespace,
 		"namespace",
@@ -111,7 +110,7 @@ func init() {
 	deleteCmd.AddCommand(deleteAddonsCmd)
 	deleteCmd.AddCommand(deleteHelmCmd)
 
-	rootCmd.AddCommand(deleteCmd)
+	return deleteCmd
 }
 
 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 (
 	"context"
@@ -19,23 +19,21 @@ import (
 	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)
 
 	bluegreenCmd.PersistentFlags().StringVar(
@@ -60,6 +58,7 @@ func init() {
 		"",
 		"The namespace of the jobs.",
 	)
+	return deployCmd
 }
 
 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 (
 	"context"
@@ -19,12 +19,7 @@ var (
 	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{
 		BaseURL:        fmt.Sprintf("%s/api", cliConf.Host),
 		BearerToken:    cliConf.Token,

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

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"context"
@@ -16,36 +16,34 @@ import (
 	"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(
 		&namespace,
 		"namespace",
@@ -62,7 +60,7 @@ func init() {
 
 	getCmd.AddCommand(getValuesCmd)
 
-	rootCmd.AddCommand(getCmd)
+	return getCmd
 }
 
 type getReleaseInfo struct {

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

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"context"
@@ -12,19 +12,19 @@ import (
 	"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 {

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

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"context"
@@ -16,14 +16,17 @@ import (
 	"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
 
 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
 `,
-		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
 
 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
 `,
-		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
 
 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
 `,
-		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(waitCmd)
 	jobCmd.AddCommand(runJobCmd)
@@ -175,6 +174,7 @@ func init() {
 	)
 
 	runJobCmd.MarkPersistentFlagRequired("name")
+	return jobCmd
 }
 
 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 (
 	"context"
@@ -12,19 +12,18 @@ import (
 	"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 {

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

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"context"
@@ -18,59 +18,58 @@ import (
 
 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 {
 				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(
 		&namespace,
 		"namespace",
@@ -89,7 +88,7 @@ func init() {
 	listCmd.AddCommand(listJobsCmd)
 	listCmd.AddCommand(listAddonsCmd)
 
-	rootCmd.AddCommand(listCmd)
+	return listCmd
 }
 
 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 (
 	"context"
@@ -12,24 +12,20 @@ import (
 	"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
 
-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(
 		&namespace,
@@ -45,6 +41,7 @@ func init() {
 		false,
 		"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 {

+ 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 (
 	"context"
@@ -16,55 +16,52 @@ import (
 	"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)
+
+	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)
+
+	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)
+
+	return projectCmd
 }
 
 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 (
 	"context"
@@ -16,74 +16,70 @@ import (
 	"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)
 
@@ -95,6 +91,8 @@ func init() {
 
 	registryCmd.AddCommand(registryImageCmd)
 	registryImageCmd.AddCommand(registryImageListCmd)
+
+	return registryCmd
 }
 
 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 (
 	"context"
@@ -12,25 +12,14 @@ import (
 	"github.com/fatih/color"
 	"github.com/google/go-github/v41/github"
 	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"
 )
 
-// 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()
 
 // 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.
 func Execute(ctx context.Context) error {
-	rootCmd.PersistentFlags().AddFlagSet(utils.DefaultFlagSet)
-
 	if cfg.Version != "dev" {
 		ghClient := github.NewClient(nil)
 		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 {
 		color.New(color.FgRed).Println(err)
 		os.Exit(1)

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

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"context"
@@ -42,35 +42,31 @@ var (
 	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(
 		&namespace,
@@ -127,6 +123,7 @@ func init() {
 	)
 
 	runCmd.AddCommand(cleanupCmd)
+	return runCmd
 }
 
 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 (
 	"context"
@@ -25,83 +25,72 @@ type startOps struct {
 
 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 {
-					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(stopCmd)
@@ -128,6 +117,7 @@ func init() {
 		8080,
 		"the host port to run the server on",
 	)
+	return serverCmd
 }
 
 func startDocker(

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

@@ -1,4 +1,4 @@
-package cmd
+package commands
 
 import (
 	"context"
@@ -16,49 +16,45 @@ import (
 
 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)
 
@@ -101,6 +97,8 @@ func init() {
 
 	stackEnvGroupCmd.AddCommand(stackEnvGroupAddCmd)
 	stackEnvGroupCmd.AddCommand(stackEnvGroupRemoveCmd)
+
+	return stackCmd
 }
 
 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 (
 	"context"
@@ -25,12 +25,33 @@ import (
 	"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
 
 Builds and updates a specified application given by the --app flag. For example:
@@ -63,25 +84,25 @@ specify it as follows:
 
   %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
 
 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
 `,
-		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
 
 Builds a new version of the application specified by the --app flag. Depending on the
@@ -135,24 +156,24 @@ for the application:
 
   %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
 
 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
 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
 
 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
 `,
-		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(
 		&app,
@@ -440,6 +439,8 @@ func init() {
 	updateCmd.AddCommand(updatePushCmd)
 	updateCmd.AddCommand(updateConfigCmd)
 	updateCmd.AddCommand(updateEnvGroupCmd)
+
+	return updateCmd
 }
 
 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()
 
-// config is a shared object used by all commands
-// var config = &CLIConfig{}
-
 // 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
 // update the corresponding field in the viper config file.
@@ -98,12 +95,18 @@ func initAndLoadConfig() (CLIConfig, error) {
 		"token for Porter authentication",
 	)
 
+	err = viper.BindPFlags(utils.DefaultFlagSet)
+	if err != nil {
+		return config, err
+	}
+
 	utils.RegistryFlagSet.UintVar(
 		&config.Registry,
 		"registry",
 		0,
 		"registry ID of connected Porter registry",
 	)
+
 	err = viper.BindPFlags(utils.RegistryFlagSet)
 	if err != nil {
 		return config, err
@@ -119,10 +122,6 @@ func initAndLoadConfig() (CLIConfig, error) {
 	if err != nil {
 		return config, err
 	}
-	err = viper.BindPFlags(utils.DefaultFlagSet)
-	if err != nil {
-		return config, err
-	}
 
 	viper.SetEnvPrefix("PORTER")
 	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/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/errors"
 )
@@ -35,7 +35,7 @@ func main() {
 		defer sentry.Flush(2 * time.Second)
 	}
 
-	err := cmd.Execute(ctx)
+	err := commands.Execute(ctx)
 	if err != nil {
 		color.New(color.FgRed).Fprintf(os.Stderr, "error executing command: %s\n", err)
 		os.Exit(1)