소스 검색

decouple cli init from runtime

Stefan McShane 2 년 전
부모
커밋
41a8b5f2f7

+ 1 - 1
api/client/api.go

@@ -67,7 +67,7 @@ func NewClientWithConfig(ctx context.Context, input NewClientInput) (Client, err
 		client.CookieFilePath = input.CookieFileName
 		cookie, err := client.getCookie()
 		if err != nil {
-			return client, fmt.Errorf("error getting cooking from path: %w", err)
+			return client, fmt.Errorf("error getting cookie from path: %w", err)
 		}
 		if cookie == nil {
 			return client, errors.New("no cookie found at location")

+ 40 - 44
cli/cmd/commands/all.go

@@ -1,58 +1,54 @@
 package commands
 
 import (
-	"errors"
-	"fmt"
-
 	"github.com/porter-dev/porter/cli/cmd/config"
-	"github.com/porter-dev/porter/cli/cmd/utils"
 	"github.com/spf13/cobra"
 )
 
-func rootFunc(cmd *cobra.Command, args []string) error {
-	ctx := cmd.Context()
+// func rootFunc(cmd *cobra.Command, args []string) error {
+// 	ctx := cmd.Context()
 
-	flagsConfig := parseRootConfigFlags(cmd)
+// 	flagsConfig := parseRootConfigFlags(cmd)
 
-	profile, err := cmd.Flags().GetString("profile")
-	if err != nil {
-		return fmt.Errorf("error getting profile flag: %w", err)
-	}
+// 	profile, err := cmd.Flags().GetString("profile")
+// 	if err != nil {
+// 		return fmt.Errorf("error getting profile flag: %w", err)
+// 	}
 
-	cliConf, currentProfile, err := config.InitAndLoadConfig(ctx, profile, flagsConfig)
-	if err != nil {
-		fmt.Println("unwrapped", errors.Unwrap(err))
-		return fmt.Errorf("error whilst initialising config: %w", err)
-	}
+// 	cliConf, currentProfile, err := config.InitAndLoadConfig(ctx, profile, flagsConfig)
+// 	if err != nil {
+// 		fmt.Println("unwrapped", errors.Unwrap(err))
+// 		return fmt.Errorf("error whilst initialising config: %w", err)
+// 	}
 
-	cmd.PersistentFlags().AddFlagSet(utils.DefaultFlagSet)
-	cmd.AddCommand(registerCommand_App(cliConf, currentProfile))
-	cmd.AddCommand(registerCommand_Apply(cliConf, currentProfile))
-	cmd.AddCommand(registerCommand_Auth(cliConf, currentProfile))
-	cmd.AddCommand(registerCommand_Cluster(cliConf, currentProfile))
-	cmd.AddCommand(registerCommand_Config(cliConf, currentProfile))
-	cmd.AddCommand(registerCommand_Connect(cliConf, currentProfile))
-	cmd.AddCommand(registerCommand_Create(cliConf, currentProfile))
-	cmd.AddCommand(registerCommand_Delete(cliConf, currentProfile))
-	cmd.AddCommand(registerCommand_Deploy(cliConf, currentProfile))
-	cmd.AddCommand(registerCommand_Docker(cliConf, currentProfile))
-	cmd.AddCommand(registerCommand_Get(cliConf, currentProfile))
-	cmd.AddCommand(registerCommand_Helm(cliConf, currentProfile))
-	cmd.AddCommand(registerCommand_Job(cliConf, currentProfile))
-	cmd.AddCommand(registerCommand_Kubectl(cliConf, currentProfile))
-	cmd.AddCommand(registerCommand_List(cliConf, currentProfile))
-	cmd.AddCommand(registerCommand_Logs(cliConf, currentProfile))
-	cmd.AddCommand(registerCommand_Open(cliConf, currentProfile))
-	cmd.AddCommand(registerCommand_PortForward(cliConf, currentProfile))
-	cmd.AddCommand(registerCommand_Project(cliConf, currentProfile))
-	cmd.AddCommand(registerCommand_Registry(cliConf, currentProfile))
-	cmd.AddCommand(registerCommand_Run(cliConf, currentProfile))
-	cmd.AddCommand(registerCommand_Server(cliConf, currentProfile))
-	cmd.AddCommand(registerCommand_Stack(cliConf, currentProfile))
-	cmd.AddCommand(registerCommand_Update(cliConf, currentProfile))
-	cmd.AddCommand(registerCommand_Version(cliConf, currentProfile))
-	return nil
-}
+// 	cmd.PersistentFlags().AddFlagSet(utils.DefaultFlagSet)
+// 	cmd.AddCommand(registerCommand_App(cliConf, currentProfile))
+// 	cmd.AddCommand(registerCommand_Apply(cliConf, currentProfile))
+// 	cmd.AddCommand(registerCommand_Auth(cliConf, currentProfile))
+// 	cmd.AddCommand(registerCommand_Cluster(cliConf, currentProfile))
+// 	cmd.AddCommand(registerCommand_Config(cliConf, currentProfile))
+// 	cmd.AddCommand(registerCommand_Connect(cliConf, currentProfile))
+// 	cmd.AddCommand(registerCommand_Create(cliConf, currentProfile))
+// 	cmd.AddCommand(registerCommand_Delete(cliConf, currentProfile))
+// 	cmd.AddCommand(registerCommand_Deploy(cliConf, currentProfile))
+// 	cmd.AddCommand(registerCommand_Docker(cliConf, currentProfile))
+// 	cmd.AddCommand(registerCommand_Get(cliConf, currentProfile))
+// 	cmd.AddCommand(registerCommand_Helm(cliConf, currentProfile))
+// 	cmd.AddCommand(registerCommand_Job(cliConf, currentProfile))
+// 	cmd.AddCommand(registerCommand_Kubectl(cliConf, currentProfile))
+// 	cmd.AddCommand(registerCommand_List(cliConf, currentProfile))
+// 	cmd.AddCommand(registerCommand_Logs(cliConf, currentProfile))
+// 	cmd.AddCommand(registerCommand_Open(cliConf, currentProfile))
+// 	cmd.AddCommand(registerCommand_PortForward(cliConf, currentProfile))
+// 	cmd.AddCommand(registerCommand_Project(cliConf, currentProfile))
+// 	cmd.AddCommand(registerCommand_Registry(cliConf, currentProfile))
+// 	cmd.AddCommand(registerCommand_Run(cliConf, currentProfile))
+// 	cmd.AddCommand(registerCommand_Server(cliConf, currentProfile))
+// 	cmd.AddCommand(registerCommand_Stack(cliConf, currentProfile))
+// 	cmd.AddCommand(registerCommand_Update(cliConf, currentProfile))
+// 	cmd.AddCommand(registerCommand_Version(cliConf, currentProfile))
+// 	return nil
+// }
 
 // parseRootConfigFlags grabs the runtime value of registered  root level persisted flags.
 func parseRootConfigFlags(cmd *cobra.Command) config.CLIConfig {

+ 5 - 5
cli/cmd/commands/app.go

@@ -51,7 +51,7 @@ const (
 	CommandPrefix_LAUNCHER = "launcher"
 )
 
-func registerCommand_App(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
+func registerCommand_App() *cobra.Command {
 	appCmd := &cobra.Command{
 		Use:   "app",
 		Short: "Runs a command for your application.",
@@ -63,7 +63,7 @@ func registerCommand_App(cliConf config.CLIConfig, currentProfile string) *cobra
 		Args:  cobra.MinimumNArgs(2),
 		Short: "Runs a command inside a connected cluster container.",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, appRun)
+			err := checkLoginAndRunWithConfig(cmd, args, appRun)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -78,7 +78,7 @@ func registerCommand_App(cliConf config.CLIConfig, currentProfile string) *cobra
 		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, cliConf, currentProfile, args, appCleanup)
+			err := checkLoginAndRunWithConfig(cmd, args, appCleanup)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -92,7 +92,7 @@ func registerCommand_App(cliConf config.CLIConfig, currentProfile string) *cobra
 		Args:  cobra.MinimumNArgs(1),
 		Short: "Updates the image tag for an application.",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, appUpdateTag)
+			err := checkLoginAndRunWithConfig(cmd, args, appUpdateTag)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -114,7 +114,7 @@ func registerCommand_App(cliConf config.CLIConfig, currentProfile string) *cobra
 		Args:  cobra.MinimumNArgs(1),
 		Short: "Rolls back an application to the last successful revision.",
 		RunE: func(cmd *cobra.Command, args []string) error {
-			return checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, appRollback)
+			return checkLoginAndRunWithConfig(cmd, args, appRollback)
 		},
 	}
 	appCmd.AddCommand(appRollbackCmd)

+ 2 - 2
cli/cmd/commands/apply.go

@@ -44,7 +44,7 @@ var (
 	previewApply bool
 )
 
-func registerCommand_Apply(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
+func registerCommand_Apply() *cobra.Command {
 	applyCmd := &cobra.Command{
 		Use:   "apply",
 		Short: "Applies a configuration to an application",
@@ -76,7 +76,7 @@ applying a configuration:
 			color.New(color.FgGreen, color.Bold).Sprintf("porter apply -f porter.yaml"),
 		),
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, apply)
+			err := checkLoginAndRunWithConfig(cmd, 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")

+ 49 - 27
cli/cmd/commands/auth.go

@@ -19,7 +19,8 @@ import (
 
 var manual bool = false
 
-func registerCommand_Auth(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
+// func registerCommand_Auth() *cobra.Command {
+func registerCommand_Auth() *cobra.Command {
 	authCmd := &cobra.Command{
 		Use:   "auth",
 		Short: "Commands for authenticating to a Porter server",
@@ -28,43 +29,59 @@ func registerCommand_Auth(cliConf config.CLIConfig, currentProfile string) *cobr
 	loginCmd := &cobra.Command{
 		Use:   "login",
 		Short: "Authorizes a user for a given Porter server",
-		Run: func(cmd *cobra.Command, args []string) {
-			// cliConf = overrideConfigWithFlags(cmd, cliConf)
+		RunE: func(cmd *cobra.Command, args []string) error {
+			cliConf, currentProfile, err := currentProfileIncludingFlags(cmd)
+			if err != nil {
+				return fmt.Errorf("error getting current profile config: %w", err)
+			}
 
-			err := login(cmd.Context(), cliConf, currentProfile)
+			err = login(cmd.Context(), cliConf, currentProfile)
 			if err != nil {
 				color.Red("Error logging in: %s\n", err.Error())
 				if strings.Contains(err.Error(), "Forbidden") {
-					_ = cliConf.SetToken("")
+					_ = config.SetToken("", currentProfile)
 				}
 				os.Exit(1)
 			}
+			return nil
 		},
 	}
 
 	registerCmd := &cobra.Command{
 		Use:   "register",
 		Short: "Creates a user for a given Porter server",
-		Run: func(cmd *cobra.Command, args []string) {
-			// cliConf = overrideConfigWithFlags(cmd, cliConf)
+		RunE: func(cmd *cobra.Command, args []string) error {
+			cliConf, _, err := currentProfileIncludingFlags(cmd)
+			if err != nil {
+				return fmt.Errorf("error getting current profile config: %w", err)
+			}
 
-			err := register(cmd.Context(), cliConf)
+			err = register(cmd.Context(), cliConf)
 			if err != nil {
 				color.Red("Error registering: %s\n", err.Error())
 				os.Exit(1)
 			}
+			return nil
 		},
 	}
 
 	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, cliConf, currentProfile, args, logout)
+		RunE: func(cmd *cobra.Command, args []string) error {
+			_, currentProfile, err := currentProfileIncludingFlags(cmd)
 			if err != nil {
-				_ = cliConf.SetToken("")
-				os.Exit(1)
+				return fmt.Errorf("error getting current profile config: %w", err)
+			}
+
+			err = checkLoginAndRunWithConfig(cmd, args, logout)
+			if err != nil {
+				config.SetToken("", currentProfile)
+				config.SetCluster(0, currentProfile)
+				config.SetProject(0, currentProfile)
+				color.Green("Successfully logged out")
 			}
+			return nil
 		},
 	}
 
@@ -113,7 +130,7 @@ func login(ctx context.Context, cliConf config.CLIConfig, currentProfile string)
 		}
 
 		// set the token in config
-		err = cliConf.SetToken(token)
+		err = config.SetToken(token, currentProfile)
 		if err != nil {
 			return err
 		}
@@ -137,7 +154,12 @@ func login(ctx context.Context, cliConf config.CLIConfig, currentProfile string)
 
 	}
 
-	err = cliConf.SetToken(cliConf.Token)
+	err = config.SetToken(cliConf.Token, currentProfile)
+	if err != nil {
+		return err
+	}
+
+	err = config.SetHost(cliConf.Host, currentProfile)
 	if err != nil {
 		return err
 	}
@@ -157,14 +179,12 @@ func login(ctx context.Context, cliConf config.CLIConfig, currentProfile string)
 	} 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, currentProfile)
-
+		err = config.SetProject(projID, currentProfile)
 		if err != nil {
 			return err
 		}
 
-		err = setProjectCluster(ctx, client, cliConf, projID)
-
+		err = setProjectCluster(ctx, client, cliConf, currentProfile, projID)
 		if err != nil {
 			return err
 		}
@@ -174,7 +194,7 @@ func login(ctx context.Context, cliConf config.CLIConfig, currentProfile string)
 	return nil
 }
 
-func setProjectForUser(ctx context.Context, client api.Client, config config.CLIConfig, currentProfile string) error {
+func setProjectForUser(ctx context.Context, client api.Client, cliConfig config.CLIConfig, currentProfile string) error {
 	// get a list of projects, and set the current project
 	resp, err := client.ListUserProjects(ctx)
 	if err != nil {
@@ -184,9 +204,9 @@ func setProjectForUser(ctx context.Context, client api.Client, config config.CLI
 	projects := *resp
 
 	if len(projects) > 0 {
-		config.SetProject(ctx, client, projects[0].ID, currentProfile) //nolint:errcheck,gosec // do not want to change logic of CLI. New linter error
+		config.SetProject(projects[0].ID, currentProfile) //nolint:errcheck,gosec // do not want to change logic of CLI. New linter error
 
-		err = setProjectCluster(ctx, client, config, projects[0].ID)
+		err = setProjectCluster(ctx, client, cliConfig, currentProfile, projects[0].ID)
 		if err != nil {
 			return err
 		}
@@ -219,7 +239,7 @@ func loginManual(ctx context.Context, cliConf config.CLIConfig, client api.Clien
 	}
 
 	// set the token to empty since this is manual (cookie-based) login
-	cliConf.SetToken("")
+	config.SetToken("", currentProfile)
 
 	color.New(color.FgGreen).Println("Successfully logged in!")
 
@@ -232,10 +252,9 @@ func loginManual(ctx context.Context, cliConf config.CLIConfig, client api.Clien
 	projects := *resp
 
 	if len(projects) > 0 {
-		cliConf.SetProject(ctx, client, projects[0].ID, currentProfile) //nolint:errcheck,gosec // do not want to change logic of CLI. New linter error
-
-		err = setProjectCluster(ctx, client, cliConf, projects[0].ID)
+		config.SetProject(projects[0].ID, currentProfile) //nolint:errcheck,gosec // do not want to change logic of CLI. New linter error
 
+		err = setProjectCluster(ctx, client, cliConf, currentProfile, projects[0].ID)
 		if err != nil {
 			return err
 		}
@@ -283,12 +302,15 @@ func register(ctx context.Context, cliConf config.CLIConfig) error {
 func logout(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	err := client.Logout(ctx)
 	if err != nil {
-		if !strings.Contains(err.Error(), "You are not logged in.") {
+		if !strings.Contains(err.Error(), "You are not logged in.") &&
+			!strings.Contains(err.Error(), "does not have a role in project") {
 			return err
 		}
 	}
 
-	cliConf.SetToken("")
+	config.SetToken("", currentProfile)
+	config.SetCluster(0, currentProfile)
+	config.SetProject(0, currentProfile)
 
 	color.Green("Successfully logged out")
 

+ 4 - 4
cli/cmd/commands/cluster.go

@@ -16,7 +16,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func registerCommand_Cluster(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
+func registerCommand_Cluster() *cobra.Command {
 	clusterCmd := &cobra.Command{
 		Use:     "cluster",
 		Aliases: []string{"clusters"},
@@ -27,7 +27,7 @@ func registerCommand_Cluster(cliConf config.CLIConfig, currentProfile string) *c
 		Use:   "list",
 		Short: "Lists the linked clusters in the current project",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, listClusters)
+			err := checkLoginAndRunWithConfig(cmd, args, listClusters)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -40,7 +40,7 @@ func registerCommand_Cluster(cliConf config.CLIConfig, currentProfile string) *c
 		Args:  cobra.ExactArgs(1),
 		Short: "Deletes the cluster with the given id",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, deleteCluster)
+			err := checkLoginAndRunWithConfig(cmd, args, deleteCluster)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -59,7 +59,7 @@ func registerCommand_Cluster(cliConf config.CLIConfig, currentProfile string) *c
 		Use:   "list",
 		Short: "Lists the namespaces in a cluster",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, listNamespaces)
+			err := checkLoginAndRunWithConfig(cmd, args, listNamespaces)
 			if err != nil {
 				os.Exit(1)
 			}

+ 95 - 56
cli/cmd/commands/config.go

@@ -2,6 +2,7 @@ package commands
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -18,7 +19,8 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func registerCommand_Config(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
+// func registerCommand_Config() *cobra.Command {
+func registerCommand_Config() *cobra.Command {
 	configCmd := &cobra.Command{
 		Use:   "config",
 		Short: "Commands that control local configuration settings",
@@ -34,35 +36,26 @@ func registerCommand_Config(cliConf config.CLIConfig, currentProfile string) *co
 		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)
-			}
-
+		RunE: func(cmd *cobra.Command, args []string) error {
 			if len(args) == 0 {
-				err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, listAndSetProject)
+				err := checkLoginAndRunWithConfig(cmd, 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)
+					if errors.Is(err, errCreatingPorterAPIClient) {
+						return errors.New("error creating porter API client. Please ensure you are logged in")
+					}
+					return fmt.Errorf("error checking login and setting project: %w", err)
 				}
-
-				err = cliConf.SetProject(cmd.Context(), client, uint(projID), currentProfile)
-				if err != nil {
-					_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %s\n", err.Error())
-					os.Exit(1)
+				return nil
+			}
+			err := checkLoginAndRunWithConfig(cmd, args, setProjectAndCluster)
+			if err != nil {
+				if errors.Is(err, errCreatingPorterAPIClient) {
+					return errors.New("error creating porter API client. Please ensure you are logged in")
 				}
+				return fmt.Errorf("error checking login and setting project with cluster: %w", err)
 			}
+
+			return nil
 		},
 	}
 
@@ -70,26 +63,36 @@ func registerCommand_Config(cliConf config.CLIConfig, currentProfile string) *co
 		Use:   "set-cluster [id]",
 		Args:  cobra.MaximumNArgs(1),
 		Short: "Saves the cluster id in the default configuration",
-		Run: func(cmd *cobra.Command, args []string) {
+		RunE: func(cmd *cobra.Command, args []string) error {
 			if len(args) == 0 {
-				err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, listAndSetCluster)
-				if err != nil {
-					os.Exit(1)
-				}
-			} else {
-				clusterID, err := strconv.ParseUint(args[0], 10, 64)
+				err := checkLoginAndRunWithConfig(cmd, args, listAndSetCluster)
 				if err != nil {
-					_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
-					os.Exit(1)
+					if errors.Is(err, errCreatingPorterAPIClient) {
+						return errors.New("error creating porter API client. Please ensure you are logged in")
+					}
+					return fmt.Errorf("error checking login and setting project: %w", err)
 				}
+				return nil
+			}
 
-				err = cliConf.SetCluster(uint(clusterID))
+			_, currentProfile, err := currentProfileIncludingFlags(cmd)
+			if err != nil {
+				return fmt.Errorf("error whilst initialising config: %w", err)
+			}
 
-				if err != nil {
-					_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
-					os.Exit(1)
-				}
+			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 = config.SetCluster(uint(clusterID), currentProfile)
+
+			if err != nil {
+				_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
+				os.Exit(1)
+			}
+			return nil
 		},
 	}
 
@@ -99,7 +102,7 @@ func registerCommand_Config(cliConf config.CLIConfig, currentProfile string) *co
 		Short: "Saves the registry id in the default configuration",
 		Run: func(cmd *cobra.Command, args []string) {
 			if len(args) == 0 {
-				err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, listAndSetRegistry)
+				err := checkLoginAndRunWithConfig(cmd, args, listAndSetRegistry)
 				if err != nil {
 					os.Exit(1)
 				}
@@ -110,7 +113,7 @@ func registerCommand_Config(cliConf config.CLIConfig, currentProfile string) *co
 					os.Exit(1)
 				}
 
-				err = cliConf.SetRegistry(uint(registryID))
+				err = config.SetRegistry(uint(registryID))
 
 				if err != nil {
 					_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
@@ -131,7 +134,7 @@ func registerCommand_Config(cliConf config.CLIConfig, currentProfile string) *co
 				os.Exit(1)
 			}
 
-			err = cliConf.SetHelmRepo(uint(hrID))
+			err = config.SetHelmRepo(uint(hrID))
 
 			if err != nil {
 				_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %v\n", err)
@@ -144,12 +147,18 @@ func registerCommand_Config(cliConf config.CLIConfig, currentProfile string) *co
 		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], currentProfile)
+		RunE: func(cmd *cobra.Command, args []string) error {
+			_, currentProfile, err := currentProfileIncludingFlags(cmd)
+			if err != nil {
+				return fmt.Errorf("error whilst initialising config: %w", err)
+			}
+
+			err = config.SetHost(args[0], currentProfile)
 			if err != nil {
 				_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %s\n", err.Error())
-				os.Exit(1)
+				return err
 			}
+			return nil
 		},
 	}
 
@@ -158,7 +167,7 @@ func registerCommand_Config(cliConf config.CLIConfig, currentProfile string) *co
 		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])
+			err := config.SetKubeconfig(args[0])
 			if err != nil {
 				_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %s\n", err.Error())
 				os.Exit(1)
@@ -166,12 +175,22 @@ func registerCommand_Config(cliConf config.CLIConfig, currentProfile string) *co
 		},
 	}
 
+	configSetProfileCmd := &cobra.Command{
+		Use:   "set-profile [profile-name]",
+		Args:  cobra.ExactArgs(1),
+		Short: "Saves the path to kubeconfig in the default configuration",
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return config.SetProfile(args[0])
+		},
+	}
+
 	configCmd.AddCommand(configSetProjectCmd)
 	configCmd.AddCommand(configSetClusterCmd)
 	configCmd.AddCommand(configSetHostCmd)
 	configCmd.AddCommand(configSetRegistryCmd)
 	configCmd.AddCommand(configSetHelmRepoCmd)
 	configCmd.AddCommand(configSetKubeconfigCmd)
+	configCmd.AddCommand(configSetProfileCmd)
 	return configCmd
 }
 
@@ -193,13 +212,12 @@ func listAndSetProject(ctx context.Context, _ *types.GetAuthenticatedUserRespons
 	s.Start()
 
 	resp, err := client.ListUserProjects(ctx)
-
-	s.Stop()
-
 	if err != nil {
-		return err
+		return fmt.Errorf("error listing projects to set config: %w", err)
 	}
 
+	s.Stop()
+
 	var projID uint64
 
 	if len(*resp) > 1 {
@@ -222,7 +240,7 @@ func listAndSetProject(ctx context.Context, _ *types.GetAuthenticatedUserRespons
 		projID = uint64((*resp)[0].ID)
 	}
 
-	err = cliConf.SetProject(ctx, client, uint(projID), currentProfile)
+	err = config.SetProject(uint(projID), currentProfile)
 	if err != nil {
 		return err
 	}
@@ -230,6 +248,28 @@ func listAndSetProject(ctx context.Context, _ *types.GetAuthenticatedUserRespons
 	return nil
 }
 
+func setProjectAndCluster(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+	projID, err := strconv.ParseUint(args[0], 10, 64)
+	if err != nil {
+		return fmt.Errorf("error parsing project id: %w", err)
+	}
+
+	err = config.SetProject(uint(projID), currentProfile)
+	if err != nil {
+		return fmt.Errorf("error setting project: %w", err)
+	}
+
+	err = listAndSetProject(ctx, nil, client, cliConf, currentProfile, featureFlags, cmd, args)
+	if err != nil {
+		return err
+	}
+	err = listAndSetCluster(ctx, nil, client, cliConf, currentProfile, featureFlags, cmd, args)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
 func listAndSetCluster(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
 	_ = s.Color("cyan")
@@ -237,13 +277,12 @@ func listAndSetCluster(ctx context.Context, _ *types.GetAuthenticatedUserRespons
 	s.Start()
 
 	resp, err := client.ListProjectClusters(ctx, cliConf.Project)
-
-	s.Stop()
-
 	if err != nil {
 		return err
 	}
 
+	s.Stop()
+
 	var clusterID uint64
 
 	if len(*resp) > 1 {
@@ -265,7 +304,7 @@ func listAndSetCluster(ctx context.Context, _ *types.GetAuthenticatedUserRespons
 		clusterID = uint64((*resp)[0].ID)
 	}
 
-	err = cliConf.SetCluster(uint(clusterID))
+	err = config.SetCluster(uint(clusterID), currentProfile)
 	if err != nil {
 		return fmt.Errorf("unable to set cluster: %w", err)
 	}
@@ -308,7 +347,7 @@ func listAndSetRegistry(ctx context.Context, _ *types.GetAuthenticatedUserRespon
 		regID = uint64((*resp)[0].ID)
 	}
 
-	err = cliConf.SetRegistry(uint(regID))
+	err = config.SetRegistry(uint(regID))
 	if err != nil {
 		return fmt.Errorf("error setting registry: %w", err)
 	}

+ 17 - 17
cli/cmd/commands/connect.go

@@ -17,7 +17,7 @@ var (
 	contexts       *[]string
 )
 
-func registerCommand_Connect(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
+func registerCommand_Connect() *cobra.Command {
 	connectCmd := &cobra.Command{
 		Use:   "connect",
 		Short: "Commands that connect to external clusters and providers",
@@ -27,7 +27,7 @@ func registerCommand_Connect(cliConf config.CLIConfig, currentProfile string) *c
 		Use:   "kubeconfig",
 		Short: "Uses the local kubeconfig to add a cluster",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, runConnectKubeconfig)
+			err := checkLoginAndRunWithConfig(cmd, args, runConnectKubeconfig)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -38,7 +38,7 @@ func registerCommand_Connect(cliConf config.CLIConfig, currentProfile string) *c
 		Use:   "ecr",
 		Short: "Adds an ECR instance to a project",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, runConnectECR)
+			err := checkLoginAndRunWithConfig(cmd, args, runConnectECR)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -49,7 +49,7 @@ func registerCommand_Connect(cliConf config.CLIConfig, currentProfile string) *c
 		Use:   "dockerhub",
 		Short: "Adds a Docker Hub registry integration to a project",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, runConnectDockerhub)
+			err := checkLoginAndRunWithConfig(cmd, args, runConnectDockerhub)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -60,7 +60,7 @@ func registerCommand_Connect(cliConf config.CLIConfig, currentProfile string) *c
 		Use:   "registry",
 		Short: "Adds a custom image registry to a project",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, runConnectRegistry)
+			err := checkLoginAndRunWithConfig(cmd, args, runConnectRegistry)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -71,7 +71,7 @@ func registerCommand_Connect(cliConf config.CLIConfig, currentProfile string) *c
 		Use:   "helm",
 		Short: "Adds a custom Helm registry to a project",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, runConnectHelmRepo)
+			err := checkLoginAndRunWithConfig(cmd, args, runConnectHelmRepo)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -82,7 +82,7 @@ func registerCommand_Connect(cliConf config.CLIConfig, currentProfile string) *c
 		Use:   "gcr",
 		Short: "Adds a GCR instance to a project",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, runConnectGCR)
+			err := checkLoginAndRunWithConfig(cmd, args, runConnectGCR)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -93,7 +93,7 @@ func registerCommand_Connect(cliConf config.CLIConfig, currentProfile string) *c
 		Use:   "gar",
 		Short: "Adds a GAR instance to a project",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, runConnectGAR)
+			err := checkLoginAndRunWithConfig(cmd, args, runConnectGAR)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -105,7 +105,7 @@ func registerCommand_Connect(cliConf config.CLIConfig, currentProfile string) *c
 		Use:   "docr",
 		Short: "Adds a DOCR instance to a project",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, runConnectDOCR)
+			err := checkLoginAndRunWithConfig(cmd, args, runConnectDOCR)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -157,7 +157,7 @@ func runConnectKubeconfig(ctx context.Context, _ *types.GetAuthenticatedUserResp
 		return err
 	}
 
-	return cliConf.SetCluster(id)
+	return config.SetCluster(id, currentProfile)
 }
 
 func runConnectECR(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
@@ -170,7 +170,7 @@ func runConnectECR(ctx context.Context, _ *types.GetAuthenticatedUserResponse, c
 		return err
 	}
 
-	return cliConf.SetRegistry(regID)
+	return config.SetRegistry(regID)
 }
 
 func runConnectGCR(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
@@ -183,7 +183,7 @@ func runConnectGCR(ctx context.Context, _ *types.GetAuthenticatedUserResponse, c
 		return err
 	}
 
-	return cliConf.SetRegistry(regID)
+	return config.SetRegistry(regID)
 }
 
 func runConnectGAR(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
@@ -196,7 +196,7 @@ func runConnectGAR(ctx context.Context, _ *types.GetAuthenticatedUserResponse, c
 		return err
 	}
 
-	return cliConf.SetRegistry(regID)
+	return config.SetRegistry(regID)
 }
 
 func runConnectDOCR(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
@@ -209,7 +209,7 @@ func runConnectDOCR(ctx context.Context, _ *types.GetAuthenticatedUserResponse,
 		return err
 	}
 
-	return cliConf.SetRegistry(regID)
+	return config.SetRegistry(regID)
 }
 
 func runConnectDockerhub(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
@@ -222,7 +222,7 @@ func runConnectDockerhub(ctx context.Context, _ *types.GetAuthenticatedUserRespo
 		return err
 	}
 
-	return cliConf.SetRegistry(regID)
+	return config.SetRegistry(regID)
 }
 
 func runConnectRegistry(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
@@ -235,7 +235,7 @@ func runConnectRegistry(ctx context.Context, _ *types.GetAuthenticatedUserRespon
 		return err
 	}
 
-	return cliConf.SetRegistry(regID)
+	return config.SetRegistry(regID)
 }
 
 func runConnectHelmRepo(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
@@ -248,5 +248,5 @@ func runConnectHelmRepo(ctx context.Context, _ *types.GetAuthenticatedUserRespon
 		return err
 	}
 
-	return cliConf.SetHelmRepo(hrID)
+	return config.SetHelmRepo(hrID)
 }

+ 2 - 2
cli/cmd/commands/create.go

@@ -31,7 +31,7 @@ var (
 	forceBuild  bool
 )
 
-func registerCommand_Create(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
+func registerCommand_Create() *cobra.Command {
 	createCmd := &cobra.Command{
 		Use:   "create [kind]",
 		Args:  cobra.ExactArgs(1),
@@ -78,7 +78,7 @@ To deploy an application from a Docker registry, use "--source registry" and pas
 			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, cliConf, currentProfile, args, createFull)
+			err := checkLoginAndRunWithConfig(cmd, args, createFull)
 			if err != nil {
 				os.Exit(1)
 			}

+ 6 - 6
cli/cmd/commands/delete.go

@@ -15,7 +15,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func registerCommand_Delete(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
+func registerCommand_Delete() *cobra.Command {
 	deleteCmd := &cobra.Command{
 		Use:   "delete",
 		Short: "Deletes a deployment",
@@ -35,7 +35,7 @@ deleting a configuration:
 			color.New(color.FgGreen, color.Bold).Sprintf("porter delete"),
 		),
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, deleteDeployment)
+			err := checkLoginAndRunWithConfig(cmd, args, deleteDeployment)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -49,7 +49,7 @@ deleting a configuration:
 		Short:   "Deletes an existing app",
 		Args:    cobra.ExactArgs(1),
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, deleteApp)
+			err := checkLoginAndRunWithConfig(cmd, args, deleteApp)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -63,7 +63,7 @@ deleting a configuration:
 		Short:   "Deletes an existing job",
 		Args:    cobra.ExactArgs(1),
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, deleteJob)
+			err := checkLoginAndRunWithConfig(cmd, args, deleteJob)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -77,7 +77,7 @@ deleting a configuration:
 		Short:   "Deletes an existing addon",
 		Args:    cobra.ExactArgs(1),
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, deleteAddon)
+			err := checkLoginAndRunWithConfig(cmd, args, deleteAddon)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -91,7 +91,7 @@ deleting a configuration:
 		Short:   "Deletes an existing helm repo",
 		Args:    cobra.ExactArgs(1),
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, deleteHelm)
+			err := checkLoginAndRunWithConfig(cmd, args, deleteHelm)
 			if err != nil {
 				os.Exit(1)
 			}

+ 2 - 2
cli/cmd/commands/deploy_bluegreen.go

@@ -19,7 +19,7 @@ import (
 	intstrutil "k8s.io/apimachinery/pkg/util/intstr"
 )
 
-func registerCommand_Deploy(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
+func registerCommand_Deploy() *cobra.Command {
 	deployCmd := &cobra.Command{
 		Use: "deploy",
 	}
@@ -28,7 +28,7 @@ func registerCommand_Deploy(cliConf config.CLIConfig, currentProfile string) *co
 		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, cliConf, currentProfile, args, bluegreenSwitch)
+			err := checkLoginAndRunWithConfig(cmd, args, bluegreenSwitch)
 			if err != nil {
 				os.Exit(1)
 			}

+ 2 - 2
cli/cmd/commands/docker.go

@@ -10,7 +10,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func registerCommand_Docker(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
+func registerCommand_Docker() *cobra.Command {
 	dockerCmd := &cobra.Command{
 		Use:   "docker",
 		Short: "Commands to configure Docker for a project",
@@ -20,7 +20,7 @@ func registerCommand_Docker(cliConf config.CLIConfig, currentProfile string) *co
 		Use:   "configure",
 		Short: "Configures the host's Docker instance",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, dockerConfig)
+			err := checkLoginAndRunWithConfig(cmd, args, dockerConfig)
 			if err != nil {
 				os.Exit(1)
 			}

+ 27 - 3
cli/cmd/commands/errors.go

@@ -22,9 +22,15 @@ var (
 
 type authenticatedRunnerFunc func(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error
 
-func checkLoginAndRunWithConfig(cmd *cobra.Command, cliConf config.CLIConfig, currentProfile string, args []string, runner authenticatedRunnerFunc) error {
+var errCreatingPorterAPIClient = errors.New("unable to authenticate against server")
+
+func checkLoginAndRunWithConfig(cmd *cobra.Command, args []string, runner authenticatedRunnerFunc) error {
 	ctx := cmd.Context()
-	// cliConf = overrideConfigWithFlags(cmd, cliConf)
+
+	cliConf, currentProfile, err := currentProfileIncludingFlags(cmd)
+	if err != nil {
+		return fmt.Errorf("error whilst initialising config: %w", err)
+	}
 
 	client, err := api.NewClientWithConfig(ctx, api.NewClientInput{
 		BaseURL:        fmt.Sprintf("%s/api", cliConf.Host),
@@ -32,7 +38,7 @@ func checkLoginAndRunWithConfig(cmd *cobra.Command, cliConf config.CLIConfig, cu
 		CookieFileName: "cookie.json",
 	})
 	if err != nil {
-		return fmt.Errorf("error creating porter API client: %w", err)
+		return errCreatingPorterAPIClient
 	}
 
 	user, err := client.AuthCheck(ctx)
@@ -86,3 +92,21 @@ func checkLoginAndRunWithConfig(cmd *cobra.Command, cliConf config.CLIConfig, cu
 
 	return nil
 }
+
+// currentProfileIncludingFlags returns the current profile, and initialises the config.
+// This ensures the the current profile is set to the one specified in the flags, env vars, or config in the correct order or precedence
+func currentProfileIncludingFlags(cmd *cobra.Command) (config.CLIConfig, string, error) {
+	ctx := cmd.Context()
+	flagsConfig := parseRootConfigFlags(cmd)
+
+	profile, err := cmd.Flags().GetString("profile")
+	if err != nil {
+		return config.CLIConfig{}, "", fmt.Errorf("error getting profile flag: %w", err)
+	}
+
+	cliConfig, currentProfile, err := config.InitAndLoadConfig(ctx, profile, flagsConfig)
+	if err != nil {
+		return config.CLIConfig{}, "", fmt.Errorf("error whilst initialising config: %w", err)
+	}
+	return cliConfig, currentProfile, nil
+}

+ 3 - 3
cli/cmd/commands/get.go

@@ -18,13 +18,13 @@ import (
 
 var output string
 
-func registerCommand_Get(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
+func registerCommand_Get() *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, cliConf, currentProfile, args, get)
+			err := checkLoginAndRunWithConfig(cmd, args, get)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -37,7 +37,7 @@ func registerCommand_Get(cliConf config.CLIConfig, currentProfile string) *cobra
 		Args:  cobra.ExactArgs(1),
 		Short: "Fetches the Helm values for a release.",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, getValues)
+			err := checkLoginAndRunWithConfig(cmd, args, getValues)
 			if err != nil {
 				os.Exit(1)
 			}

+ 2 - 2
cli/cmd/commands/helm.go

@@ -12,12 +12,12 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func registerCommand_Helm(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
+func registerCommand_Helm() *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, cliConf, currentProfile, args, runHelm)
+			err := checkLoginAndRunWithConfig(cmd, args, runHelm)
 			if err != nil {
 				os.Exit(1)
 			}

+ 4 - 4
cli/cmd/commands/job.go

@@ -18,7 +18,7 @@ import (
 
 var imageRepoURI string
 
-func registerCommand_Job(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
+func registerCommand_Job() *cobra.Command {
 	jobCmd := &cobra.Command{
 		Use: "job",
 	}
@@ -47,7 +47,7 @@ use the --namespace flag:
 			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, cliConf, currentProfile, args, batchImageUpdate)
+			err := checkLoginAndRunWithConfig(cmd, args, batchImageUpdate)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -77,7 +77,7 @@ use the --namespace flag:
 			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, cliConf, currentProfile, args, waitForJob)
+			err := checkLoginAndRunWithConfig(cmd, args, waitForJob)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -107,7 +107,7 @@ use the --namespace flag:
 			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, cliConf, currentProfile, args, runJob)
+			err := checkLoginAndRunWithConfig(cmd, args, runJob)
 			if err != nil {
 				os.Exit(1)
 			}

+ 2 - 2
cli/cmd/commands/kubectl.go

@@ -12,12 +12,12 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func registerCommand_Kubectl(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
+func registerCommand_Kubectl() *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, cliConf, currentProfile, args, runKubectl)
+			err := checkLoginAndRunWithConfig(cmd, args, runKubectl)
 			if err != nil {
 				os.Exit(1)
 			}

+ 5 - 5
cli/cmd/commands/list.go

@@ -18,13 +18,13 @@ import (
 
 var allNamespaces bool
 
-func registerCommand_List(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
+func registerCommand_List() *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, cliConf, currentProfile, args, listAll)
+				err := checkLoginAndRunWithConfig(cmd, args, listAll)
 				if err != nil {
 					os.Exit(1)
 				}
@@ -39,7 +39,7 @@ func registerCommand_List(cliConf config.CLIConfig, currentProfile string) *cobr
 		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, cliConf, currentProfile, args, listApps)
+			err := checkLoginAndRunWithConfig(cmd, args, listApps)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -51,7 +51,7 @@ func registerCommand_List(cliConf config.CLIConfig, currentProfile string) *cobr
 		Aliases: []string{"job"},
 		Short:   "Lists jobs in a specific namespace, or across all namespaces",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, listJobs)
+			err := checkLoginAndRunWithConfig(cmd, args, listJobs)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -63,7 +63,7 @@ func registerCommand_List(cliConf config.CLIConfig, currentProfile string) *cobr
 		Aliases: []string{"addon"},
 		Short:   "Lists addons in a specific namespace, or across all namespaces",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, listAddons)
+			err := checkLoginAndRunWithConfig(cmd, args, listAddons)
 			if err != nil {
 				os.Exit(1)
 			}

+ 2 - 2
cli/cmd/commands/logs.go

@@ -14,13 +14,13 @@ import (
 
 var follow bool
 
-func registerCommand_Logs(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
+func registerCommand_Logs() *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, cliConf, currentProfile, args, logs)
+			err := checkLoginAndRunWithConfig(cmd, args, logs)
 			if err != nil {
 				os.Exit(1)
 			}

+ 10 - 5
cli/cmd/commands/open.go

@@ -6,19 +6,23 @@ import (
 
 	"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, _ string) *cobra.Command {
+func registerCommand_Open() *cobra.Command {
 	openCmd := &cobra.Command{
 		Use:   "open",
 		Short: "Opens the browser at the currently set Porter instance",
-		Run: func(cmd *cobra.Command, args []string) {
+		RunE: func(cmd *cobra.Command, args []string) error {
 			ctx := cmd.Context()
 
+			cliConf, _, err := currentProfileIncludingFlags(cmd)
+			if err != nil {
+				return fmt.Errorf("error getting current profile config: %w", err)
+			}
+
 			client, err := api.NewClientWithConfig(ctx, api.NewClientInput{
 				BaseURL:        fmt.Sprintf("%s/api", cliConf.Host),
 				BearerToken:    cliConf.Token,
@@ -26,16 +30,17 @@ func registerCommand_Open(cliConf config.CLIConfig, _ string) *cobra.Command {
 			})
 			if err != nil {
 				_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "error creating porter API client: %v\n", err)
-				os.Exit(1)
+				return err
 			}
 
 			user, err := client.AuthCheck(ctx)
 			if err != nil {
 				_ = utils.OpenBrowser(fmt.Sprintf("%s/register", cliConf.Host))
-				return
+				return nil
 			}
 
 			_ = 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 nil
 		},
 	}
 

+ 1 - 2
cli/cmd/commands/portforward.go

@@ -4,11 +4,10 @@ import (
 	"fmt"
 
 	"github.com/fatih/color"
-	"github.com/porter-dev/porter/cli/cmd/config"
 	"github.com/spf13/cobra"
 )
 
-func registerCommand_PortForward(_ config.CLIConfig, _ string) *cobra.Command {
+func registerCommand_PortForward() *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.",

+ 7 - 7
cli/cmd/commands/project.go

@@ -16,7 +16,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func registerCommand_Project(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
+func registerCommand_Project() *cobra.Command {
 	projectCmd := &cobra.Command{
 		Use:     "project",
 		Aliases: []string{"projects"},
@@ -28,7 +28,7 @@ func registerCommand_Project(cliConf config.CLIConfig, currentProfile string) *c
 		Args:  cobra.ExactArgs(1),
 		Short: "Creates a project with the authorized user as admin",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, createProject)
+			err := checkLoginAndRunWithConfig(cmd, args, createProject)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -41,7 +41,7 @@ func registerCommand_Project(cliConf config.CLIConfig, currentProfile string) *c
 		Args:  cobra.ExactArgs(1),
 		Short: "Deletes the project with the given id",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, deleteProject)
+			err := checkLoginAndRunWithConfig(cmd, args, deleteProject)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -53,7 +53,7 @@ func registerCommand_Project(cliConf config.CLIConfig, currentProfile string) *c
 		Use:   "list",
 		Short: "Lists the projects for the logged in user",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, listProjects)
+			err := checkLoginAndRunWithConfig(cmd, args, listProjects)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -74,7 +74,7 @@ func createProject(ctx context.Context, _ *types.GetAuthenticatedUserResponse, c
 
 	color.New(color.FgGreen).Printf("Created project with name %s and id %d\n", args[0], resp.ID)
 
-	return cliConf.SetProject(ctx, client, resp.ID, currentProfile)
+	return config.SetProject(resp.ID, currentProfile)
 }
 
 func listProjects(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
@@ -135,7 +135,7 @@ func deleteProject(ctx context.Context, _ *types.GetAuthenticatedUserResponse, c
 	return nil
 }
 
-func setProjectCluster(ctx context.Context, client api.Client, cliConf config.CLIConfig, projectID uint) error {
+func setProjectCluster(ctx context.Context, client api.Client, cliConf config.CLIConfig, currentProfile string, projectID uint) error {
 	resp, err := client.ListProjectClusters(ctx, projectID)
 	if err != nil {
 		return err
@@ -144,7 +144,7 @@ func setProjectCluster(ctx context.Context, client api.Client, cliConf config.CL
 	clusters := *resp
 
 	if len(clusters) > 0 {
-		cliConf.SetCluster(clusters[0].ID)
+		config.SetCluster(clusters[0].ID, currentProfile)
 	}
 
 	return nil

+ 5 - 5
cli/cmd/commands/registry.go

@@ -16,7 +16,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func registerCommand_Registry(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
+func registerCommand_Registry() *cobra.Command {
 	registryCmd := &cobra.Command{
 		Use:     "registry",
 		Aliases: []string{"registries"},
@@ -27,7 +27,7 @@ func registerCommand_Registry(cliConf config.CLIConfig, currentProfile string) *
 		Use:   "list",
 		Short: "Lists the registries linked to a project",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, listRegistries)
+			err := checkLoginAndRunWithConfig(cmd, args, listRegistries)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -39,7 +39,7 @@ func registerCommand_Registry(cliConf config.CLIConfig, currentProfile string) *
 		Args:  cobra.ExactArgs(1),
 		Short: "Deletes the registry with the given id",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, deleteRegistry)
+			err := checkLoginAndRunWithConfig(cmd, args, deleteRegistry)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -56,7 +56,7 @@ func registerCommand_Registry(cliConf config.CLIConfig, currentProfile string) *
 		Use:   "list",
 		Short: "Lists the repositories in an image registry",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, listRepos)
+			err := checkLoginAndRunWithConfig(cmd, args, listRepos)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -74,7 +74,7 @@ func registerCommand_Registry(cliConf config.CLIConfig, currentProfile string) *
 		Args:  cobra.ExactArgs(1),
 		Short: "Lists the images the specified image repository",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, listImages)
+			err := checkLoginAndRunWithConfig(cmd, args, listImages)
 			if err != nil {
 				os.Exit(1)
 			}

+ 37 - 1
cli/cmd/commands/root.go

@@ -51,7 +51,14 @@ func Execute(ctx context.Context) error {
 		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`,
-		RunE:  rootFunc,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cmd.Help()
+		},
+	}
+
+	err := registerRootCommands(rootCmd, nil)
+	if err != nil {
+		return fmt.Errorf("error registering root commands: %w", err)
 	}
 
 	// we shouldnt set default values in flags or they will overwrite all other values.
@@ -71,3 +78,32 @@ func Execute(ctx context.Context) error {
 	}
 	return nil
 }
+
+func registerRootCommands(cmd *cobra.Command, args []string) error {
+	cmd.AddCommand(registerCommand_App())
+	cmd.AddCommand(registerCommand_Apply())
+	cmd.AddCommand(registerCommand_Auth())
+	cmd.AddCommand(registerCommand_Cluster())
+	cmd.AddCommand(registerCommand_Config())
+	cmd.AddCommand(registerCommand_Connect())
+	cmd.AddCommand(registerCommand_Create())
+	cmd.AddCommand(registerCommand_Delete())
+	cmd.AddCommand(registerCommand_Deploy())
+	cmd.AddCommand(registerCommand_Docker())
+	cmd.AddCommand(registerCommand_Get())
+	cmd.AddCommand(registerCommand_Helm())
+	cmd.AddCommand(registerCommand_Job())
+	cmd.AddCommand(registerCommand_Kubectl())
+	cmd.AddCommand(registerCommand_List())
+	cmd.AddCommand(registerCommand_Logs())
+	cmd.AddCommand(registerCommand_Open())
+	cmd.AddCommand(registerCommand_PortForward())
+	cmd.AddCommand(registerCommand_Project())
+	cmd.AddCommand(registerCommand_Registry())
+	cmd.AddCommand(registerCommand_Run())
+	cmd.AddCommand(registerCommand_Server())
+	cmd.AddCommand(registerCommand_Stack())
+	cmd.AddCommand(registerCommand_Update())
+	cmd.AddCommand(registerCommand_Version())
+	return nil
+}

+ 3 - 3
cli/cmd/commands/run.go

@@ -42,13 +42,13 @@ var (
 	memoryMi       int
 )
 
-func registerCommand_Run(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
+func registerCommand_Run() *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, cliConf, currentProfile, args, run)
+			err := checkLoginAndRunWithConfig(cmd, args, run)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -61,7 +61,7 @@ func registerCommand_Run(cliConf config.CLIConfig, currentProfile string) *cobra
 		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, cliConf, currentProfile, args, cleanup)
+			err := checkLoginAndRunWithConfig(cmd, args, cleanup)
 			if err != nil {
 				os.Exit(1)
 			}

+ 24 - 12
cli/cmd/commands/server.go

@@ -25,7 +25,7 @@ type startOps struct {
 
 var opts = &startOps{}
 
-func registerCommand_Server(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
+func registerCommand_Server() *cobra.Command {
 	serverCmd := &cobra.Command{
 		Use:     "server",
 		Aliases: []string{"svr"},
@@ -36,11 +36,16 @@ func registerCommand_Server(cliConf config.CLIConfig, currentProfile string) *co
 	startCmd := &cobra.Command{
 		Use:   "start",
 		Short: "Starts a Porter server instance on the host",
-		Run: func(cmd *cobra.Command, args []string) {
+		RunE: func(cmd *cobra.Command, args []string) error {
 			ctx := cmd.Context()
 
+			cliConf, currentProfile, err := currentProfileIncludingFlags(cmd)
+			if err != nil {
+				return fmt.Errorf("error getting current profile config: %w", err)
+			}
+
 			if cliConf.Driver == "docker" {
-				_ = cliConf.SetDriver("docker", currentProfile)
+				_ = config.SetDriver("docker", currentProfile)
 
 				err := startDocker(
 					ctx,
@@ -56,15 +61,15 @@ func registerCommand_Server(cliConf config.CLIConfig, currentProfile string) *co
 					_, _ = red.Println("Shutting down...")
 
 					err = stopDocker(ctx)
-
 					if err != nil {
 						_, _ = red.Println("Shutdown unsuccessful:", err.Error())
+						return err
 					}
 
-					os.Exit(1)
+					return err
 				}
 			} else {
-				_ = cliConf.SetDriver("local", currentProfile)
+				_ = config.SetDriver("local", currentProfile)
 				err := startLocal(
 					ctx,
 					cliConf,
@@ -75,22 +80,29 @@ func registerCommand_Server(cliConf config.CLIConfig, currentProfile string) *co
 				if err != nil {
 					red := color.New(color.FgRed)
 					_, _ = red.Println("Error running start:", err.Error())
-					os.Exit(1)
+					return err
 				}
 			}
+			return nil
 		},
 	}
 
 	stopCmd := &cobra.Command{
 		Use:   "stop",
 		Short: "Stops a Porter instance running on the Docker engine",
-		Run: func(cmd *cobra.Command, args []string) {
+		RunE: func(cmd *cobra.Command, args []string) error {
+			cliConf, _, err := currentProfileIncludingFlags(cmd)
+			if err != nil {
+				return fmt.Errorf("error getting current profile config: %w", err)
+			}
+
 			if cliConf.Driver == "docker" {
 				if err := stopDocker(cmd.Context()); err != nil {
 					_, _ = color.New(color.FgRed).Println("Shutdown unsuccessful:", err.Error())
-					os.Exit(1)
+					return err
 				}
 			}
+			return nil
 		},
 	}
 
@@ -154,7 +166,7 @@ func startDocker(ctx context.Context, cliConf config.CLIConfig, imageTag string,
 
 	green.Printf("Server ready: listening on localhost:%d\n", port)
 
-	return cliConf.SetHost(fmt.Sprintf("http://localhost:%d", port), currentProfile)
+	return config.SetHost(fmt.Sprintf("http://localhost:%d", port), currentProfile)
 }
 
 func startLocal(ctx context.Context, cliConf config.CLIConfig, db string, port int, currentProfile string) error {
@@ -162,7 +174,7 @@ func startLocal(ctx context.Context, cliConf config.CLIConfig, db string, port i
 		return fmt.Errorf("postgres not available for local driver, run \"porter server start --db postgres --driver docker\"")
 	}
 
-	err := cliConf.SetHost(fmt.Sprintf("http://localhost:%d", port), currentProfile)
+	err := config.SetHost(fmt.Sprintf("http://localhost:%d", port), currentProfile)
 	if err != nil {
 		return fmt.Errorf("failed to set host: %s", err.Error())
 	}
@@ -185,7 +197,7 @@ func startLocal(ctx context.Context, cliConf config.CLIConfig, db string, port i
 	writer := &config.VersionWriter{}
 	cmdVersionPorter.Stdout = writer
 
-	err := cmdVersionPorter.Run()
+	err = cmdVersionPorter.Run()
 
 	if err != nil || writer.Version != config.Version {
 		err := downloadMatchingRelease(ctx, porterDir)

+ 3 - 3
cli/cmd/commands/stack.go

@@ -16,7 +16,7 @@ import (
 
 var linkedApps []string
 
-func registerCommand_Stack(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
+func registerCommand_Stack() *cobra.Command {
 	stackCmd := &cobra.Command{
 		Use:     "stack",
 		Aliases: []string{"stacks"},
@@ -37,7 +37,7 @@ func registerCommand_Stack(cliConf config.CLIConfig, currentProfile string) *cob
 		Args:  cobra.ExactArgs(1),
 		Short: "Add an env group to a stack",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, stackAddEnvGroup)
+			err := checkLoginAndRunWithConfig(cmd, args, stackAddEnvGroup)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -49,7 +49,7 @@ func registerCommand_Stack(cliConf config.CLIConfig, currentProfile string) *cob
 		Args:  cobra.ExactArgs(1),
 		Short: "Remove an existing env group from a stack",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, stackRemoveEnvGroup)
+			err := checkLoginAndRunWithConfig(cmd, args, stackRemoveEnvGroup)
 			if err != nil {
 				os.Exit(1)
 			}

+ 8 - 8
cli/cmd/commands/update.go

@@ -43,7 +43,7 @@ var (
 	waitForSuccessfulDeploy bool
 )
 
-func registerCommand_Update(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
+func registerCommand_Update() *cobra.Command {
 	buildFlagsEnv = []string{}
 
 	// updateCmd represents the "porter update" base command when called
@@ -92,7 +92,7 @@ specify it as follows:
 			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, cliConf, currentProfile, args, updateFull)
+			err := checkLoginAndRunWithConfig(cmd, args, updateFull)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -120,7 +120,7 @@ destination path for a .env file. For example:
 			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, cliConf, currentProfile, args, updateGetEnv)
+			err := checkLoginAndRunWithConfig(cmd, args, updateGetEnv)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -162,7 +162,7 @@ for the application:
 			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, cliConf, currentProfile, args, updateBuild)
+			err := checkLoginAndRunWithConfig(cmd, args, updateBuild)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -202,7 +202,7 @@ linked it via "porter connect".
 			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, cliConf, currentProfile, args, updatePush)
+			err := checkLoginAndRunWithConfig(cmd, args, updatePush)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -232,7 +232,7 @@ the image that the application uses if no --values file is specified:
 			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, cliConf, currentProfile, args, updateUpgrade)
+			err := checkLoginAndRunWithConfig(cmd, args, updateUpgrade)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -253,7 +253,7 @@ the image that the application uses if no --values file is specified:
 		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, cliConf, currentProfile, args, updateSetEnvGroup)
+			err := checkLoginAndRunWithConfig(cmd, args, updateSetEnvGroup)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -265,7 +265,7 @@ the image that the application uses if no --values file is specified:
 		Short: "Removes an environment variable from an env group.",
 		Args:  cobra.MinimumNArgs(1),
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, updateUnsetEnvGroup)
+			err := checkLoginAndRunWithConfig(cmd, args, updateUnsetEnvGroup)
 			if err != nil {
 				os.Exit(1)
 			}

+ 1 - 1
cli/cmd/commands/version.go

@@ -7,7 +7,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func registerCommand_Version(_ config.CLIConfig, _ string) *cobra.Command {
+func registerCommand_Version() *cobra.Command {
 	versionCmd := &cobra.Command{
 		Use:     "version",
 		Aliases: []string{"v", "--version"},

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

@@ -101,7 +101,12 @@ func InitAndLoadConfig(ctx context.Context, flagsProfile string, flagsConfig CLI
 }
 
 // ValidateCLIEnvironment checks that all required variables are present for running the CLI
-func (c *CLIConfig) ValidateCLIEnvironment() error {
+func ValidateCLIEnvironment() error {
+	c, err := configForProfileFromConfigFile("", porterConfigFilePath)
+	if err != nil {
+		return fmt.Errorf("error reading config for profile: %w", err)
+	}
+
 	if c.Token == "" {
 		return fmt.Errorf("no auth token present, please run 'porter auth login' to authenticate")
 	}

+ 10 - 5
cli/cmd/config/profiles.go

@@ -8,6 +8,7 @@ import (
 	"path/filepath"
 	"strconv"
 
+	"github.com/fatih/color"
 	"github.com/sethvargo/go-envconfig"
 	"gopkg.in/yaml.v2"
 )
@@ -71,7 +72,8 @@ func migrateExistingConfigYaml(configPath string) error {
 }
 
 // writeProfileToProfilesConfigFile will write the given profile config to file, overwriting the entire existing file.
-// Ensure to call readProfilesConfigFromFile before running this function if you with to preserve current settings
+// Ensure to call readProfilesConfigFromFile before running this function if you with to preserve current settings,
+// or call updateValuesForSelectedProfile to set specific values for a given profile
 func writeProfileToProfilesConfigFile(profilesConfig ProfilesConfig, configPath string) error {
 	by, err := yaml.Marshal(profilesConfig)
 	if err != nil {
@@ -255,11 +257,14 @@ func configForProfileFromConfigFile(selectedProfile string, configPath string) (
 	}
 
 	configFile, ok := profilesConfig.Profiles[selectedProfile]
-	if ok {
-		return configFile, nil
+	if !ok {
+		_, _ = color.New(color.FgGreen).Printf("Porter profile '%s' does not exist. Creating one now...\n", currentProfile)
+		err = updateValuesForSelectedProfile(selectedProfile, defaultCLIConfig(), configPath)
+		if err != nil {
+			return CLIConfig{}, fmt.Errorf("error creating new profile: %w", err)
+		}
 	}
-
-	return CLIConfig{}, nil
+	return configFile, nil
 }
 
 // profileConfigFromEnvVars parses any environment variables that may be setting

+ 36 - 61
cli/cmd/config/update.go

@@ -1,7 +1,6 @@
 package config
 
 import (
-	"context"
 	"errors"
 	"fmt"
 	"os"
@@ -9,12 +8,20 @@ import (
 	"strings"
 
 	"github.com/fatih/color"
-	api "github.com/porter-dev/porter/api/client"
-	"github.com/spf13/viper"
 )
 
+// SetProfile sets the current profile to the supplied name. If one does not exist, it will be created
+func SetProfile(currentProfile string) error {
+	fmt.Println("Setting profile to", currentProfile)
+	err := updateCurrentProfileInFile(currentProfile, porterConfigFilePath)
+	if err != nil {
+		return fmt.Errorf("unable to update current profile in file: %w", err)
+	}
+	return nil
+}
+
 // SetDriver sets the driver used when building images. This can either be locla or github
-func (c *CLIConfig) SetDriver(driver string, currentProfile string) error {
+func SetDriver(driver string, currentProfile string) error {
 	v := CLIConfig{
 		Driver: driver,
 	}
@@ -23,7 +30,7 @@ func (c *CLIConfig) SetDriver(driver string, currentProfile string) error {
 
 // SetHost updates the host for the current profile. This clears the project, cluster, and token as they will not work with the new host
 // Trailing slashes will be dropped from the provided host as they may cause issues with the api server
-func (c *CLIConfig) SetHost(host string, currentProfile string) error {
+func SetHost(host string, currentProfile string) error {
 	host = strings.TrimRight(host, "/")
 
 	v := defaultCLIConfig()
@@ -35,82 +42,55 @@ func (c *CLIConfig) SetHost(host string, currentProfile string) error {
 }
 
 // SetProject sets a project for all API commands
-func (c *CLIConfig) SetProject(ctx context.Context, apiClient api.Client, projectID uint, selectedProfile string) error {
+func SetProject(projectID uint, currentProfile string) error {
 	color.New(color.FgGreen).Printf("Set the current project as %d\n", projectID) //nolint:errcheck,gosec
+
 	v := CLIConfig{
 		Project: projectID,
 	}
-	resp, err := apiClient.ListProjectClusters(ctx, projectID)
-	if err == nil {
-		clusters := *resp
-		if len(clusters) == 1 {
-			v.Cluster = clusters[0].ID
-		}
-	}
+
 	return updateValuesForSelectedProfile(currentProfile, v, porterConfigFilePath)
 }
 
 // SetCluster sets the cluster in the current profile. All further actions will be targeted at this cluster
-func (c *CLIConfig) SetCluster(clusterID uint) error {
+func SetCluster(clusterID uint, currentProfile string) error {
 	color.New(color.FgGreen).Printf("Set the current cluster as %d\n", clusterID) //nolint:errcheck,gosec
 
-	if c.Kubeconfig != "" || viper.IsSet("kubeconfig") {
-		color.New(color.FgYellow).Println("Please change local kubeconfig if needed") //nolint:errcheck,gosec
-	}
-
-	err := viper.WriteConfig()
-	if err != nil {
-		return err
+	v := CLIConfig{
+		Cluster:    clusterID,
+		Kubeconfig: "",
 	}
-
-	c.Cluster = clusterID
-
-	return nil
+	return updateValuesForSelectedProfile(currentProfile, v, porterConfigFilePath)
 }
 
 // SetToken sets the token in the current profile. All further actions will be authenticated with this token
-func (c *CLIConfig) SetToken(token string) error {
-	viper.Set("token", token)
-	err := viper.WriteConfig()
-	if err != nil {
-		return err
+func SetToken(token string, currentProfile string) error {
+	v := CLIConfig{
+		Token: token,
 	}
-
-	c.Token = token
-
-	return nil
+	return updateValuesForSelectedProfile(currentProfile, v, porterConfigFilePath)
 }
 
 // SetRegistry sets the docker registry in the current profile. All further actions will be targeted at this registry
-func (c *CLIConfig) SetRegistry(registryID uint) error {
-	viper.Set("registry", registryID)
+func SetRegistry(registryID uint) error {
 	color.New(color.FgGreen).Printf("Set the current registry as %d\n", registryID) //nolint:errcheck,gosec
-	err := viper.WriteConfig()
-	if err != nil {
-		return err
+	v := CLIConfig{
+		Registry: registryID,
 	}
-
-	c.Registry = registryID
-
-	return nil
+	return updateValuesForSelectedProfile(currentProfile, v, porterConfigFilePath)
 }
 
 // SetHelmRepo sets the helm repo in the current profile. All further actions will be targeted at this helm repo
-func (c *CLIConfig) SetHelmRepo(helmRepoID uint) error {
-	viper.Set("helm_repo", helmRepoID)
+func SetHelmRepo(helmRepoID uint) error {
 	color.New(color.FgGreen).Printf("Set the current Helm repo as %d\n", helmRepoID) //nolint:errcheck,gosec
-	err := viper.WriteConfig()
-	if err != nil {
-		return err
+	v := CLIConfig{
+		HelmRepo: helmRepoID,
 	}
-
-	c.HelmRepo = helmRepoID
-
-	return nil
+	return updateValuesForSelectedProfile(currentProfile, v, porterConfigFilePath)
 }
 
 // SetKubeconfig sets the kubeconfig in the current profile. All further actions which require kubernetes will be targeted at this kubeconfig
-func (c *CLIConfig) SetKubeconfig(kubeconfig string) error {
+func SetKubeconfig(kubeconfig string) error {
 	path, err := filepath.Abs(kubeconfig)
 	if err != nil {
 		return err
@@ -120,15 +100,10 @@ func (c *CLIConfig) SetKubeconfig(kubeconfig string) error {
 		return fmt.Errorf("%s does not exist", path)
 	}
 
-	viper.Set("kubeconfig", path)
 	color.New(color.FgGreen).Printf("Set the path to kubeconfig as %s\n", path) //nolint:errcheck,gosec
-	err = viper.WriteConfig()
 
-	if err != nil {
-		return err
+	v := CLIConfig{
+		Kubeconfig: kubeconfig,
 	}
-
-	c.Kubeconfig = kubeconfig
-
-	return nil
+	return updateValuesForSelectedProfile(currentProfile, v, porterConfigFilePath)
 }

+ 1 - 1
cli/cmd/porter_app/apply.go

@@ -26,7 +26,7 @@ type StackConf struct {
 
 // CreateApplicationDeploy creates everything needed to deploy a porter app
 func CreateApplicationDeploy(ctx context.Context, client api.Client, worker *switchboardWorker.Worker, app *Application, applicationName string, cliConf config.CLIConfig) ([]*switchboardTypes.Resource, error) {
-	err := cliConf.ValidateCLIEnvironment()
+	err := config.ValidateCLIEnvironment()
 	if err != nil {
 		errMsg := composePreviewMessage("porter CLI is not configured correctly", Error)
 		return nil, fmt.Errorf("%s: %w", errMsg, err)

+ 1 - 1
cli/cmd/porter_app/hooks.go

@@ -24,7 +24,7 @@ type DeployAppHook struct {
 }
 
 func (t *DeployAppHook) PreApply() error {
-	err := t.CLIConfig.ValidateCLIEnvironment()
+	err := config.ValidateCLIEnvironment()
 	if err != nil {
 		errMsg := composePreviewMessage("porter CLI is not configured correctly", Error)
 		return fmt.Errorf("%s: %w", errMsg, err)

+ 1 - 2
cli/cmd/preview/v2beta1/apply.go

@@ -33,8 +33,7 @@ func NewApplier(client api.Client, cliConfig config.CLIConfig, raw []byte, names
 		return nil, fmt.Errorf("%s: %w", errMsg, err)
 	}
 
-	err = cliConfig.ValidateCLIEnvironment()
-
+	err = config.ValidateCLIEnvironment()
 	if err != nil {
 		errMsg := composePreviewMessage("porter CLI is not configured correctly", Error)
 		return nil, fmt.Errorf("%s: %w", errMsg, err)