Stefan McShane 2 лет назад
Родитель
Сommit
add7d48a42

+ 48 - 39
cli/cmd/commands/all.go

@@ -1,6 +1,7 @@
 package commands
 
 import (
+	"errors"
 	"fmt"
 
 	"github.com/porter-dev/porter/cli/cmd/config"
@@ -8,52 +9,53 @@ import (
 	"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()
+func rootFunc(cmd *cobra.Command, args []string) error {
+	ctx := cmd.Context()
+
+	flagsConfig := parseRootConfigFlags(cmd)
+
+	profile, err := cmd.Flags().GetString("profile")
 	if err != nil {
-		return nil, fmt.Errorf("error loading porter config: %w", err)
+		return fmt.Errorf("error getting profile flag: %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`,
+	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)
 	}
-	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
+	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
 }
 
-// overrideConfigWithFlags grabs the runtime value of registered flags, and overrides the values in CLIConfig.
-// It was done this way to reduce the size of a refactor, as the codebase conflates initialisation of the commands, with the runtime values.
-func overrideConfigWithFlags(cmd *cobra.Command, config config.CLIConfig) config.CLIConfig {
+// parseRootConfigFlags grabs the runtime value of registered  root level persisted flags.
+func parseRootConfigFlags(cmd *cobra.Command) config.CLIConfig {
 	type flag struct {
 		// stringName is the name of the flag which is a string
 		stringName string
@@ -66,6 +68,9 @@ func overrideConfigWithFlags(cmd *cobra.Command, config config.CLIConfig) config
 		uintConfigTarget *uint
 	}
 
+	var profile string
+	var config config.CLIConfig
+
 	flagsToOverride := []flag{
 		{
 			stringName:         "driver",
@@ -83,6 +88,10 @@ func overrideConfigWithFlags(cmd *cobra.Command, config config.CLIConfig) config
 			stringName:         "kubeconfig",
 			stringConfigTarget: &config.Kubeconfig,
 		},
+		{
+			stringName:         "profile",
+			stringConfigTarget: &profile,
+		},
 		{
 			uintName:         "project",
 			uintConfigTarget: &config.Project,

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

@@ -51,7 +51,7 @@ const (
 	CommandPrefix_LAUNCHER = "launcher"
 )
 
-func registerCommand_App(cliConf config.CLIConfig) *cobra.Command {
+func registerCommand_App(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
 	appCmd := &cobra.Command{
 		Use:   "app",
 		Short: "Runs a command for your application.",
@@ -63,7 +63,7 @@ func registerCommand_App(cliConf config.CLIConfig) *cobra.Command {
 		Args:  cobra.MinimumNArgs(2),
 		Short: "Runs a command inside a connected cluster container.",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, args, appRun)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, appRun)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -78,7 +78,7 @@ func registerCommand_App(cliConf config.CLIConfig) *cobra.Command {
 		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, args, appCleanup)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, appCleanup)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -92,7 +92,7 @@ func registerCommand_App(cliConf config.CLIConfig) *cobra.Command {
 		Args:  cobra.MinimumNArgs(1),
 		Short: "Updates the image tag for an application.",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, args, appUpdateTag)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, appUpdateTag)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -114,7 +114,7 @@ func registerCommand_App(cliConf config.CLIConfig) *cobra.Command {
 		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, args, appRollback)
+			return checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, appRollback)
 		},
 	}
 	appCmd.AddCommand(appRollbackCmd)
@@ -171,7 +171,7 @@ func appRunFlags(appRunCmd *cobra.Command) {
 	)
 }
 
-func appRollback(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, _ config.FeatureFlags, _ *cobra.Command, args []string) error {
+func appRollback(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, currentProfile string, _ config.FeatureFlags, _ *cobra.Command, args []string) error {
 	project, err := client.GetProject(ctx, cliConfig.Project)
 	if err != nil {
 		return fmt.Errorf("could not retrieve project from Porter API. Please contact support@porter.run")
@@ -198,7 +198,7 @@ func appRollback(ctx context.Context, _ *types.GetAuthenticatedUserResponse, cli
 	return nil
 }
 
-func appRun(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, _ config.FeatureFlags, _ *cobra.Command, args []string) error {
+func appRun(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, currentProfile string, _ config.FeatureFlags, _ *cobra.Command, args []string) error {
 	execArgs := args[1:]
 
 	color.New(color.FgGreen).Println("Attempting to run", strings.Join(execArgs, " "), "for application", args[0])
@@ -333,7 +333,7 @@ func getImageNameFromPod(ctx context.Context, clientset *kubernetes.Clientset, n
 	return "", fmt.Errorf("could not find container %s in pod %s", containerName, podName)
 }
 
-func appCleanup(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
+func appCleanup(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, currentProfile string, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
 	config := &AppPorterRunSharedConfig{
 		Client:    client,
 		CLIConfig: cliConfig,
@@ -1183,7 +1183,7 @@ func appCreateEphemeralPodFromExisting(
 	)
 }
 
-func appUpdateTag(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func appUpdateTag(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	project, err := client.GetProject(ctx, cliConf.Project)
 	if err != nil {
 		return fmt.Errorf("could not retrieve project from Porter API. Please contact support@porter.run")

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

@@ -44,7 +44,7 @@ var (
 	previewApply bool
 )
 
-func registerCommand_Apply(cliConf config.CLIConfig) *cobra.Command {
+func registerCommand_Apply(cliConf config.CLIConfig, currentProfile string) *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, args, apply)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, 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")
@@ -122,7 +122,7 @@ func appNameFromEnvironmentVariable() string {
 	return ""
 }
 
-func apply(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, _ config.FeatureFlags, _ *cobra.Command, _ []string) (err error) {
+func apply(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, currentProfile string, _ config.FeatureFlags, _ *cobra.Command, _ []string) (err error) {
 	project, err := client.GetProject(ctx, cliConfig.Project)
 	if err != nil {
 		return fmt.Errorf("could not retrieve project from Porter API. Please contact support@porter.run")

+ 17 - 18
cli/cmd/commands/auth.go

@@ -19,7 +19,7 @@ import (
 
 var manual bool = false
 
-func registerCommand_Auth(cliConf config.CLIConfig) *cobra.Command {
+func registerCommand_Auth(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
 	authCmd := &cobra.Command{
 		Use:   "auth",
 		Short: "Commands for authenticating to a Porter server",
@@ -29,9 +29,9 @@ func registerCommand_Auth(cliConf config.CLIConfig) *cobra.Command {
 		Use:   "login",
 		Short: "Authorizes a user for a given Porter server",
 		Run: func(cmd *cobra.Command, args []string) {
-			cliConf = overrideConfigWithFlags(cmd, cliConf)
+			// cliConf = overrideConfigWithFlags(cmd, cliConf)
 
-			err := login(cmd.Context(), cliConf)
+			err := login(cmd.Context(), cliConf, currentProfile)
 			if err != nil {
 				color.Red("Error logging in: %s\n", err.Error())
 				if strings.Contains(err.Error(), "Forbidden") {
@@ -46,7 +46,7 @@ func registerCommand_Auth(cliConf config.CLIConfig) *cobra.Command {
 		Use:   "register",
 		Short: "Creates a user for a given Porter server",
 		Run: func(cmd *cobra.Command, args []string) {
-			cliConf = overrideConfigWithFlags(cmd, cliConf)
+			// cliConf = overrideConfigWithFlags(cmd, cliConf)
 
 			err := register(cmd.Context(), cliConf)
 			if err != nil {
@@ -60,7 +60,7 @@ func registerCommand_Auth(cliConf config.CLIConfig) *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, args, logout)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, logout)
 			if err != nil {
 				_ = cliConf.SetToken("")
 				os.Exit(1)
@@ -82,7 +82,7 @@ func registerCommand_Auth(cliConf config.CLIConfig) *cobra.Command {
 	return authCmd
 }
 
-func login(ctx context.Context, cliConf config.CLIConfig) error {
+func login(ctx context.Context, cliConf config.CLIConfig, currentProfile string) error {
 	client, err := api.NewClientWithConfig(ctx, api.NewClientInput{
 		BaseURL:     fmt.Sprintf("%s/api", cliConf.Host),
 		BearerToken: cliConf.Token,
@@ -93,7 +93,7 @@ func login(ctx context.Context, cliConf config.CLIConfig) error {
 		}
 	}
 
-	user, err := client.AuthCheck(ctx)
+	_, err = client.AuthCheck(ctx)
 	if err != nil {
 		if !strings.Contains(err.Error(), "Forbidden") {
 			return fmt.Errorf("unexpected error performing authorization check: %w", err)
@@ -103,7 +103,7 @@ func login(ctx context.Context, cliConf config.CLIConfig) error {
 	if cliConf.Token == "" {
 		// check for the --manual flag
 		if manual {
-			return loginManual(ctx, cliConf, client)
+			return loginManual(ctx, cliConf, client, currentProfile)
 		}
 
 		// log the user in
@@ -126,14 +126,14 @@ func login(ctx context.Context, cliConf config.CLIConfig) error {
 			return fmt.Errorf("error creating porter API client: %w", err)
 		}
 
-		user, err = client.AuthCheck(ctx)
+		_, err = client.AuthCheck(ctx)
 		if err != nil {
 			color.Red("Invalid token.")
 			return err
 		}
 
 		_, _ = color.New(color.FgGreen).Println("Successfully logged in!")
-		return setProjectForUser(ctx, client, cliConf, user.ID)
+		return setProjectForUser(ctx, client, cliConf, currentProfile)
 
 	}
 
@@ -150,15 +150,14 @@ func login(ctx context.Context, cliConf config.CLIConfig) error {
 	// 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)
-
+		err = setProjectForUser(ctx, client, cliConf, currentProfile)
 		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)
+		err = cliConf.SetProject(ctx, client, projID, currentProfile)
 
 		if err != nil {
 			return err
@@ -175,7 +174,7 @@ func login(ctx context.Context, cliConf config.CLIConfig) error {
 	return nil
 }
 
-func setProjectForUser(ctx context.Context, client api.Client, config config.CLIConfig, _ uint) error {
+func setProjectForUser(ctx context.Context, client api.Client, config config.CLIConfig, currentProfile string) error {
 	// get a list of projects, and set the current project
 	resp, err := client.ListUserProjects(ctx)
 	if err != nil {
@@ -185,7 +184,7 @@ func setProjectForUser(ctx context.Context, client api.Client, config config.CLI
 	projects := *resp
 
 	if len(projects) > 0 {
-		config.SetProject(ctx, client, projects[0].ID) //nolint:errcheck,gosec // do not want to change logic of CLI. New linter error
+		config.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, config, projects[0].ID)
 		if err != nil {
@@ -196,7 +195,7 @@ func setProjectForUser(ctx context.Context, client api.Client, config config.CLI
 	return nil
 }
 
-func loginManual(ctx context.Context, cliConf config.CLIConfig, client api.Client) error {
+func loginManual(ctx context.Context, cliConf config.CLIConfig, client api.Client, currentProfile string) error {
 	client.CookieFilePath = "cookie.json" // required as this uses cookies for auth instead of a token
 	var username, pw string
 
@@ -233,7 +232,7 @@ func loginManual(ctx context.Context, cliConf config.CLIConfig, client api.Clien
 	projects := *resp
 
 	if len(projects) > 0 {
-		cliConf.SetProject(ctx, client, projects[0].ID) //nolint:errcheck,gosec // do not want to change logic of CLI. New linter error
+		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)
 
@@ -281,7 +280,7 @@ func register(ctx context.Context, cliConf config.CLIConfig) error {
 	return nil
 }
 
-func logout(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) 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.") {

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

@@ -16,7 +16,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func registerCommand_Cluster(cliConf config.CLIConfig) *cobra.Command {
+func registerCommand_Cluster(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
 	clusterCmd := &cobra.Command{
 		Use:     "cluster",
 		Aliases: []string{"clusters"},
@@ -27,7 +27,7 @@ func registerCommand_Cluster(cliConf config.CLIConfig) *cobra.Command {
 		Use:   "list",
 		Short: "Lists the linked clusters in the current project",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, args, listClusters)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, listClusters)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -40,7 +40,7 @@ func registerCommand_Cluster(cliConf config.CLIConfig) *cobra.Command {
 		Args:  cobra.ExactArgs(1),
 		Short: "Deletes the cluster with the given id",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, args, deleteCluster)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, deleteCluster)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -59,7 +59,7 @@ func registerCommand_Cluster(cliConf config.CLIConfig) *cobra.Command {
 		Use:   "list",
 		Short: "Lists the namespaces in a cluster",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, args, listNamespaces)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, listNamespaces)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -70,7 +70,7 @@ func registerCommand_Cluster(cliConf config.CLIConfig) *cobra.Command {
 	return clusterCmd
 }
 
-func listClusters(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func listClusters(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	resp, err := client.ListProjectClusters(ctx, cliConf.Project)
 	if err != nil {
 		return err
@@ -98,7 +98,7 @@ func listClusters(ctx context.Context, user *types.GetAuthenticatedUserResponse,
 	return nil
 }
 
-func deleteCluster(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func deleteCluster(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	userResp, err := utils.PromptPlaintext(
 		fmt.Sprintf(
 			`Are you sure you'd like to delete the cluster with id %s? %s `,
@@ -128,7 +128,7 @@ func deleteCluster(ctx context.Context, user *types.GetAuthenticatedUserResponse
 	return nil
 }
 
-func listNamespaces(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func listNamespaces(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	pID := cliConf.Project
 
 	// get the service account based on the cluster id

+ 10 - 10
cli/cmd/commands/config.go

@@ -18,7 +18,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func registerCommand_Config(cliConf config.CLIConfig) *cobra.Command {
+func registerCommand_Config(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
 	configCmd := &cobra.Command{
 		Use:   "config",
 		Short: "Commands that control local configuration settings",
@@ -46,7 +46,7 @@ func registerCommand_Config(cliConf config.CLIConfig) *cobra.Command {
 			}
 
 			if len(args) == 0 {
-				err := checkLoginAndRunWithConfig(cmd, cliConf, args, listAndSetProject)
+				err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, listAndSetProject)
 				if err != nil {
 					os.Exit(1)
 				}
@@ -57,7 +57,7 @@ func registerCommand_Config(cliConf config.CLIConfig) *cobra.Command {
 					os.Exit(1)
 				}
 
-				err = cliConf.SetProject(cmd.Context(), client, uint(projID))
+				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)
@@ -72,7 +72,7 @@ func registerCommand_Config(cliConf config.CLIConfig) *cobra.Command {
 		Short: "Saves the cluster id in the default configuration",
 		Run: func(cmd *cobra.Command, args []string) {
 			if len(args) == 0 {
-				err := checkLoginAndRunWithConfig(cmd, cliConf, args, listAndSetCluster)
+				err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, listAndSetCluster)
 				if err != nil {
 					os.Exit(1)
 				}
@@ -99,7 +99,7 @@ func registerCommand_Config(cliConf config.CLIConfig) *cobra.Command {
 		Short: "Saves the registry id in the default configuration",
 		Run: func(cmd *cobra.Command, args []string) {
 			if len(args) == 0 {
-				err := checkLoginAndRunWithConfig(cmd, cliConf, args, listAndSetRegistry)
+				err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, listAndSetRegistry)
 				if err != nil {
 					os.Exit(1)
 				}
@@ -145,7 +145,7 @@ func registerCommand_Config(cliConf config.CLIConfig) *cobra.Command {
 		Args:  cobra.ExactArgs(1),
 		Short: "Saves the host in the default configuration",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := cliConf.SetHost(args[0])
+			err := cliConf.SetHost(args[0], currentProfile)
 			if err != nil {
 				_, _ = color.New(color.FgRed).Fprintf(os.Stderr, "An error occurred: %s\n", err.Error())
 				os.Exit(1)
@@ -186,7 +186,7 @@ func printConfig() error {
 	return nil
 }
 
-func listAndSetProject(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func listAndSetProject(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")
 	s.Suffix = " Loading list of projects"
@@ -222,7 +222,7 @@ func listAndSetProject(ctx context.Context, _ *types.GetAuthenticatedUserRespons
 		projID = uint64((*resp)[0].ID)
 	}
 
-	err = cliConf.SetProject(ctx, client, uint(projID))
+	err = cliConf.SetProject(ctx, client, uint(projID), currentProfile)
 	if err != nil {
 		return err
 	}
@@ -230,7 +230,7 @@ func listAndSetProject(ctx context.Context, _ *types.GetAuthenticatedUserRespons
 	return nil
 }
 
-func listAndSetCluster(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+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")
 	s.Suffix = " Loading list of clusters"
@@ -273,7 +273,7 @@ func listAndSetCluster(ctx context.Context, _ *types.GetAuthenticatedUserRespons
 	return nil
 }
 
-func listAndSetRegistry(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func listAndSetRegistry(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")
 	s.Suffix = " Loading list of registries"

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

@@ -17,7 +17,7 @@ var (
 	contexts       *[]string
 )
 
-func registerCommand_Connect(cliConf config.CLIConfig) *cobra.Command {
+func registerCommand_Connect(cliConf config.CLIConfig, currentProfile string) *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) *cobra.Command {
 		Use:   "kubeconfig",
 		Short: "Uses the local kubeconfig to add a cluster",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, args, runConnectKubeconfig)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, runConnectKubeconfig)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -38,7 +38,7 @@ func registerCommand_Connect(cliConf config.CLIConfig) *cobra.Command {
 		Use:   "ecr",
 		Short: "Adds an ECR instance to a project",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, args, runConnectECR)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, runConnectECR)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -49,7 +49,7 @@ func registerCommand_Connect(cliConf config.CLIConfig) *cobra.Command {
 		Use:   "dockerhub",
 		Short: "Adds a Docker Hub registry integration to a project",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, args, runConnectDockerhub)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, runConnectDockerhub)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -60,7 +60,7 @@ func registerCommand_Connect(cliConf config.CLIConfig) *cobra.Command {
 		Use:   "registry",
 		Short: "Adds a custom image registry to a project",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, args, runConnectRegistry)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, runConnectRegistry)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -71,7 +71,7 @@ func registerCommand_Connect(cliConf config.CLIConfig) *cobra.Command {
 		Use:   "helm",
 		Short: "Adds a custom Helm registry to a project",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, args, runConnectHelmRepo)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, runConnectHelmRepo)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -82,7 +82,7 @@ func registerCommand_Connect(cliConf config.CLIConfig) *cobra.Command {
 		Use:   "gcr",
 		Short: "Adds a GCR instance to a project",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, args, runConnectGCR)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, runConnectGCR)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -93,7 +93,7 @@ func registerCommand_Connect(cliConf config.CLIConfig) *cobra.Command {
 		Use:   "gar",
 		Short: "Adds a GAR instance to a project",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, args, runConnectGAR)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, runConnectGAR)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -105,7 +105,7 @@ func registerCommand_Connect(cliConf config.CLIConfig) *cobra.Command {
 		Use:   "docr",
 		Short: "Adds a DOCR instance to a project",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, args, runConnectDOCR)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, runConnectDOCR)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -138,7 +138,7 @@ func registerCommand_Connect(cliConf config.CLIConfig) *cobra.Command {
 	return connectCmd
 }
 
-func runConnectKubeconfig(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
+func runConnectKubeconfig(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
 	isLocal := false
 
 	if cliConf.Driver == "local" {
@@ -160,7 +160,7 @@ func runConnectKubeconfig(ctx context.Context, _ *types.GetAuthenticatedUserResp
 	return cliConf.SetCluster(id)
 }
 
-func runConnectECR(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
+func runConnectECR(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
 	regID, err := connect.ECR(
 		ctx,
 		client,
@@ -173,7 +173,7 @@ func runConnectECR(ctx context.Context, _ *types.GetAuthenticatedUserResponse, c
 	return cliConf.SetRegistry(regID)
 }
 
-func runConnectGCR(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
+func runConnectGCR(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
 	regID, err := connect.GCR(
 		ctx,
 		client,
@@ -186,7 +186,7 @@ func runConnectGCR(ctx context.Context, _ *types.GetAuthenticatedUserResponse, c
 	return cliConf.SetRegistry(regID)
 }
 
-func runConnectGAR(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
+func runConnectGAR(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
 	regID, err := connect.GAR(
 		ctx,
 		client,
@@ -199,7 +199,7 @@ func runConnectGAR(ctx context.Context, _ *types.GetAuthenticatedUserResponse, c
 	return cliConf.SetRegistry(regID)
 }
 
-func runConnectDOCR(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
+func runConnectDOCR(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
 	regID, err := connect.DOCR(
 		ctx,
 		client,
@@ -212,7 +212,7 @@ func runConnectDOCR(ctx context.Context, _ *types.GetAuthenticatedUserResponse,
 	return cliConf.SetRegistry(regID)
 }
 
-func runConnectDockerhub(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
+func runConnectDockerhub(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
 	regID, err := connect.Dockerhub(
 		ctx,
 		client,
@@ -225,7 +225,7 @@ func runConnectDockerhub(ctx context.Context, _ *types.GetAuthenticatedUserRespo
 	return cliConf.SetRegistry(regID)
 }
 
-func runConnectRegistry(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
+func runConnectRegistry(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
 	regID, err := connect.Registry(
 		ctx,
 		client,
@@ -238,7 +238,7 @@ func runConnectRegistry(ctx context.Context, _ *types.GetAuthenticatedUserRespon
 	return cliConf.SetRegistry(regID)
 }
 
-func runConnectHelmRepo(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
+func runConnectHelmRepo(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
 	hrID, err := connect.HelmRepo(
 		ctx,
 		client,

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

@@ -31,7 +31,7 @@ var (
 	forceBuild  bool
 )
 
-func registerCommand_Create(cliConf config.CLIConfig) *cobra.Command {
+func registerCommand_Create(cliConf config.CLIConfig, currentProfile string) *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, args, createFull)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, createFull)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -180,7 +180,7 @@ To deploy an application from a Docker registry, use "--source registry" and pas
 
 var supportedKinds = map[string]string{"web": "", "job": "", "worker": ""}
 
-func createFull(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func createFull(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	if featureFlags.ValidateApplyV2Enabled {
 		err := v2.CreateFull(ctx)
 		if err != nil {

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

@@ -15,7 +15,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func registerCommand_Delete(cliConf config.CLIConfig) *cobra.Command {
+func registerCommand_Delete(cliConf config.CLIConfig, currentProfile string) *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, args, deleteDeployment)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, 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, args, deleteApp)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, 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, args, deleteJob)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, 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, args, deleteAddon)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, 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, args, deleteHelm)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, deleteHelm)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -113,7 +113,7 @@ deleting a configuration:
 	return deleteCmd
 }
 
-func deleteDeployment(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func deleteDeployment(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	if featureFlags.ValidateApplyV2Enabled {
 		err := v2.DeleteDeployment(ctx)
 		if err != nil {
@@ -152,7 +152,7 @@ func deleteDeployment(ctx context.Context, _ *types.GetAuthenticatedUserResponse
 	)
 }
 
-func deleteApp(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func deleteApp(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	if featureFlags.ValidateApplyV2Enabled {
 		err := v2.DeleteApp(ctx)
 		if err != nil {
@@ -189,7 +189,7 @@ func deleteApp(ctx context.Context, _ *types.GetAuthenticatedUserResponse, clien
 	return nil
 }
 
-func deleteJob(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func deleteJob(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	if featureFlags.ValidateApplyV2Enabled {
 		err := v2.DeleteJob(ctx)
 		if err != nil {
@@ -226,7 +226,7 @@ func deleteJob(ctx context.Context, _ *types.GetAuthenticatedUserResponse, clien
 	return nil
 }
 
-func deleteAddon(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func deleteAddon(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	name := args[0]
 
 	resp, err := client.GetRelease(
@@ -255,7 +255,7 @@ func deleteAddon(ctx context.Context, _ *types.GetAuthenticatedUserResponse, cli
 	return nil
 }
 
-func deleteHelm(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func deleteHelm(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	name := args[0]
 
 	resp, err := client.ListHelmRepos(ctx, cliConf.Project)

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

@@ -19,7 +19,7 @@ import (
 	intstrutil "k8s.io/apimachinery/pkg/util/intstr"
 )
 
-func registerCommand_Deploy(cliConf config.CLIConfig) *cobra.Command {
+func registerCommand_Deploy(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
 	deployCmd := &cobra.Command{
 		Use: "deploy",
 	}
@@ -28,7 +28,7 @@ func registerCommand_Deploy(cliConf config.CLIConfig) *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, cliConf, args, bluegreenSwitch)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, bluegreenSwitch)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -61,7 +61,7 @@ func registerCommand_Deploy(cliConf config.CLIConfig) *cobra.Command {
 	return deployCmd
 }
 
-func bluegreenSwitch(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, _ config.FeatureFlags, _ *cobra.Command, args []string) error {
+func bluegreenSwitch(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, currentProfile string, _ config.FeatureFlags, _ *cobra.Command, args []string) error {
 	project, err := client.GetProject(ctx, cliConfig.Project)
 	if err != nil {
 		return fmt.Errorf("could not retrieve project from Porter API. Please contact support@porter.run")

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

@@ -10,7 +10,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func registerCommand_Docker(cliConf config.CLIConfig) *cobra.Command {
+func registerCommand_Docker(cliConf config.CLIConfig, currentProfile string) *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) *cobra.Command {
 		Use:   "configure",
 		Short: "Configures the host's Docker instance",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, args, dockerConfig)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, dockerConfig)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -31,6 +31,6 @@ func registerCommand_Docker(cliConf config.CLIConfig) *cobra.Command {
 	return dockerCmd
 }
 
-func dockerConfig(ctx context.Context, user *ptypes.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func dockerConfig(ctx context.Context, user *ptypes.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	return config.SetDockerConfig(ctx, client, cliConf.Project)
 }

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

@@ -20,11 +20,11 @@ var (
 	ErrCannotConnect error = errors.New("Unable to connect to the Porter server.")
 )
 
-type authenticatedRunnerFunc func(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error
+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, args []string, runner authenticatedRunnerFunc) error {
+func checkLoginAndRunWithConfig(cmd *cobra.Command, cliConf config.CLIConfig, currentProfile string, args []string, runner authenticatedRunnerFunc) error {
 	ctx := cmd.Context()
-	cliConf = overrideConfigWithFlags(cmd, cliConf)
+	// cliConf = overrideConfigWithFlags(cmd, cliConf)
 
 	client, err := api.NewClientWithConfig(ctx, api.NewClientInput{
 		BaseURL:        fmt.Sprintf("%s/api", cliConf.Host),
@@ -65,7 +65,7 @@ func checkLoginAndRunWithConfig(cmd *cobra.Command, cliConf config.CLIConfig, ar
 		ValidateApplyV2Enabled: project.ValidateApplyV2,
 	}
 
-	err = runner(ctx, user, client, cliConf, flags, cmd, args)
+	err = runner(ctx, user, client, cliConf, currentProfile, flags, cmd, args)
 	if err != nil {
 		red := color.New(color.FgRed)
 
@@ -79,7 +79,7 @@ func checkLoginAndRunWithConfig(cmd *cobra.Command, cliConf config.CLIConfig, ar
 			return nil
 		}
 
-		cliErrors.GetErrorHandler(cliConf).HandleError(err)
+		cliErrors.GetErrorHandler(cliConf, currentProfile).HandleError(err)
 
 		return err
 	}

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

@@ -18,13 +18,13 @@ import (
 
 var output string
 
-func registerCommand_Get(cliConf config.CLIConfig) *cobra.Command {
+func registerCommand_Get(cliConf config.CLIConfig, currentProfile string) *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, args, get)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, get)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -37,7 +37,7 @@ func registerCommand_Get(cliConf config.CLIConfig) *cobra.Command {
 		Args:  cobra.ExactArgs(1),
 		Short: "Fetches the Helm values for a release.",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, args, getValues)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, getValues)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -71,7 +71,7 @@ type getReleaseInfo struct {
 	RevisionID   int       `json:"revision_id" yaml:"revision_id"`
 }
 
-func get(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func get(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	if featureFlags.ValidateApplyV2Enabled {
 		err := v2.Get(ctx)
 		if err != nil {
@@ -118,7 +118,7 @@ func get(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.
 	return nil
 }
 
-func getValues(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func getValues(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	if featureFlags.ValidateApplyV2Enabled {
 		err := v2.GetValues(ctx)
 		if err != nil {

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

@@ -12,12 +12,12 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func registerCommand_Helm(cliConf config.CLIConfig) *cobra.Command {
+func registerCommand_Helm(cliConf config.CLIConfig, currentProfile string) *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, args, runHelm)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, runHelm)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -27,7 +27,7 @@ func registerCommand_Helm(cliConf config.CLIConfig) *cobra.Command {
 	return helmCmd
 }
 
-func runHelm(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func runHelm(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	_, err := exec.LookPath("helm")
 	if err != nil {
 		return fmt.Errorf("error finding helm: %w", err)

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

@@ -18,7 +18,7 @@ import (
 
 var imageRepoURI string
 
-func registerCommand_Job(cliConf config.CLIConfig) *cobra.Command {
+func registerCommand_Job(cliConf config.CLIConfig, currentProfile string) *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, args, batchImageUpdate)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, 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, args, waitForJob)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, 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, args, runJob)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, runJob)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -177,7 +177,7 @@ use the --namespace flag:
 	return jobCmd
 }
 
-func batchImageUpdate(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func batchImageUpdate(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	if featureFlags.ValidateApplyV2Enabled {
 		err := v2.BatchImageUpdate(ctx)
 		if err != nil {
@@ -201,7 +201,7 @@ func batchImageUpdate(ctx context.Context, _ *types.GetAuthenticatedUserResponse
 }
 
 // waits for a job with a given name/namespace
-func waitForJob(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func waitForJob(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	if featureFlags.ValidateApplyV2Enabled {
 		err := v2.WaitForJob(ctx)
 		if err != nil {
@@ -218,7 +218,7 @@ func waitForJob(ctx context.Context, _ *types.GetAuthenticatedUserResponse, clie
 	})
 }
 
-func runJob(ctx context.Context, authRes *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func runJob(ctx context.Context, authRes *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	if featureFlags.ValidateApplyV2Enabled {
 		err := v2.RunJob(ctx)
 		if err != nil {
@@ -252,7 +252,7 @@ func runJob(ctx context.Context, authRes *types.GetAuthenticatedUserResponse, cl
 		return fmt.Errorf("error running job: %w", err)
 	}
 
-	err = waitForJob(ctx, authRes, client, cliConf, featureFlags, cmd, args)
+	err = waitForJob(ctx, authRes, client, cliConf, currentProfile, featureFlags, cmd, args)
 	if err != nil {
 		return fmt.Errorf("error waiting for job to complete: %w", err)
 	}

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

@@ -12,12 +12,12 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func registerCommand_Kubectl(cliConf config.CLIConfig) *cobra.Command {
+func registerCommand_Kubectl(cliConf config.CLIConfig, currentProfile string) *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, args, runKubectl)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, runKubectl)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -28,7 +28,7 @@ func registerCommand_Kubectl(cliConf config.CLIConfig) *cobra.Command {
 	return kubectlCmd
 }
 
-func runKubectl(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func runKubectl(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	_, err := exec.LookPath("kubectl")
 	if err != nil {
 		return fmt.Errorf("error finding kubectl: %w", err)

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

@@ -18,13 +18,13 @@ import (
 
 var allNamespaces bool
 
-func registerCommand_List(cliConf config.CLIConfig) *cobra.Command {
+func registerCommand_List(cliConf config.CLIConfig, currentProfile string) *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, args, listAll)
+				err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, listAll)
 				if err != nil {
 					os.Exit(1)
 				}
@@ -39,7 +39,7 @@ func registerCommand_List(cliConf config.CLIConfig) *cobra.Command {
 		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, args, listApps)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, listApps)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -51,7 +51,7 @@ func registerCommand_List(cliConf config.CLIConfig) *cobra.Command {
 		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, args, listJobs)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, listJobs)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -63,7 +63,7 @@ func registerCommand_List(cliConf config.CLIConfig) *cobra.Command {
 		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, args, listAddons)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, listAddons)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -91,7 +91,7 @@ func registerCommand_List(cliConf config.CLIConfig) *cobra.Command {
 	return listCmd
 }
 
-func listAll(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func listAll(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	if featureFlags.ValidateApplyV2Enabled {
 		err := v2.ListAll(ctx)
 		if err != nil {
@@ -108,7 +108,7 @@ func listAll(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client
 	return nil
 }
 
-func listApps(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func listApps(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	if featureFlags.ValidateApplyV2Enabled {
 		err := v2.ListApps(ctx)
 		if err != nil {
@@ -125,7 +125,7 @@ func listApps(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client
 	return nil
 }
 
-func listJobs(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func listJobs(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	if featureFlags.ValidateApplyV2Enabled {
 		err := v2.ListJobs(ctx)
 		if err != nil {
@@ -142,7 +142,7 @@ func listJobs(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client
 	return nil
 }
 
-func listAddons(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func listAddons(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	err := writeReleases(ctx, client, cliConf, "addon")
 	if err != nil {
 		return err

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

@@ -14,13 +14,13 @@ import (
 
 var follow bool
 
-func registerCommand_Logs(cliConf config.CLIConfig) *cobra.Command {
+func registerCommand_Logs(cliConf config.CLIConfig, currentProfile string) *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, args, logs)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, logs)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -44,7 +44,7 @@ func registerCommand_Logs(cliConf config.CLIConfig) *cobra.Command {
 	return logsCmd
 }
 
-func logs(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, _ config.FeatureFlags, _ *cobra.Command, args []string) error {
+func logs(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, currentProfile string, _ config.FeatureFlags, _ *cobra.Command, args []string) error {
 	podsSimple, err := getPods(ctx, client, cliConfig, namespace, args[0])
 	if err != nil {
 		return fmt.Errorf("Could not retrieve list of pods: %s", err.Error())

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

@@ -12,7 +12,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func registerCommand_Open(cliConf config.CLIConfig) *cobra.Command {
+func registerCommand_Open(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
 	openCmd := &cobra.Command{
 		Use:   "open",
 		Short: "Opens the browser at the currently set Porter instance",

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

@@ -8,7 +8,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func registerCommand_PortForward(_ config.CLIConfig) *cobra.Command {
+func registerCommand_PortForward(_ config.CLIConfig, _ string) *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.",

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

@@ -16,7 +16,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func registerCommand_Project(cliConf config.CLIConfig) *cobra.Command {
+func registerCommand_Project(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
 	projectCmd := &cobra.Command{
 		Use:     "project",
 		Aliases: []string{"projects"},
@@ -28,7 +28,7 @@ func registerCommand_Project(cliConf config.CLIConfig) *cobra.Command {
 		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, args, createProject)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, createProject)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -41,7 +41,7 @@ func registerCommand_Project(cliConf config.CLIConfig) *cobra.Command {
 		Args:  cobra.ExactArgs(1),
 		Short: "Deletes the project with the given id",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, args, deleteProject)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, deleteProject)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -53,7 +53,7 @@ func registerCommand_Project(cliConf config.CLIConfig) *cobra.Command {
 		Use:   "list",
 		Short: "Lists the projects for the logged in user",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, args, listProjects)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, listProjects)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -64,7 +64,7 @@ func registerCommand_Project(cliConf config.CLIConfig) *cobra.Command {
 	return projectCmd
 }
 
-func createProject(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func createProject(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	resp, err := client.CreateProject(ctx, &types.CreateProjectRequest{
 		Name: args[0],
 	})
@@ -74,10 +74,10 @@ 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)
+	return cliConf.SetProject(ctx, client, resp.ID, currentProfile)
 }
 
-func listProjects(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+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 {
 	resp, err := client.ListUserProjects(ctx)
 	if err != nil {
 		return err
@@ -105,7 +105,7 @@ func listProjects(ctx context.Context, user *types.GetAuthenticatedUserResponse,
 	return nil
 }
 
-func deleteProject(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, _ config.FeatureFlags, _ *cobra.Command, args []string) error {
+func deleteProject(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, _ config.FeatureFlags, _ *cobra.Command, args []string) error {
 	userResp, err := utils.PromptPlaintext(
 		fmt.Sprintf(
 			`Are you sure you'd like to delete the project with id %s? %s `,

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

@@ -16,7 +16,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func registerCommand_Registry(cliConf config.CLIConfig) *cobra.Command {
+func registerCommand_Registry(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
 	registryCmd := &cobra.Command{
 		Use:     "registry",
 		Aliases: []string{"registries"},
@@ -27,7 +27,7 @@ func registerCommand_Registry(cliConf config.CLIConfig) *cobra.Command {
 		Use:   "list",
 		Short: "Lists the registries linked to a project",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, args, listRegistries)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, listRegistries)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -39,7 +39,7 @@ func registerCommand_Registry(cliConf config.CLIConfig) *cobra.Command {
 		Args:  cobra.ExactArgs(1),
 		Short: "Deletes the registry with the given id",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, args, deleteRegistry)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, deleteRegistry)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -56,7 +56,7 @@ func registerCommand_Registry(cliConf config.CLIConfig) *cobra.Command {
 		Use:   "list",
 		Short: "Lists the repositories in an image registry",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, args, listRepos)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, listRepos)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -74,7 +74,7 @@ func registerCommand_Registry(cliConf config.CLIConfig) *cobra.Command {
 		Args:  cobra.ExactArgs(1),
 		Short: "Lists the images the specified image repository",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, args, listImages)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, listImages)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -95,7 +95,7 @@ func registerCommand_Registry(cliConf config.CLIConfig) *cobra.Command {
 	return registryCmd
 }
 
-func listRegistries(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func listRegistries(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	pID := cliConf.Project
 
 	// get the list of namespaces
@@ -129,7 +129,7 @@ func listRegistries(ctx context.Context, user *types.GetAuthenticatedUserRespons
 	return nil
 }
 
-func deleteRegistry(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func deleteRegistry(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	userResp, err := utils.PromptPlaintext(
 		fmt.Sprintf(
 			`Are you sure you'd like to delete the registry with id %s? %s `,
@@ -159,7 +159,7 @@ func deleteRegistry(ctx context.Context, user *types.GetAuthenticatedUserRespons
 	return nil
 }
 
-func listRepos(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func listRepos(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	pID := cliConf.Project
 	rID := cliConf.Registry
 
@@ -189,7 +189,7 @@ func listRepos(ctx context.Context, user *types.GetAuthenticatedUserResponse, cl
 	return nil
 }
 
-func listImages(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func listImages(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	pID := cliConf.Project
 	rID := cliConf.Registry
 	repoName := args[0]

+ 17 - 3
cli/cmd/commands/root.go

@@ -12,6 +12,7 @@ import (
 	"github.com/fatih/color"
 	"github.com/google/go-github/v41/github"
 	cfg "github.com/porter-dev/porter/cli/cmd/config"
+	"github.com/spf13/cobra"
 	"k8s.io/client-go/util/homedir"
 )
 
@@ -46,11 +47,24 @@ func Execute(ctx context.Context) error {
 		}
 	}
 
-	rootCmd, err := RegisterCommands()
-	if err != nil {
-		return fmt.Errorf("error setting up commands")
+	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`,
+		RunE:  rootFunc,
 	}
 
+	// we shouldnt set default values in flags or they will overwrite all other values.
+	// These should be set in defaultCLIConfig
+	rootCmd.PersistentFlags().String("driver", "", "driver to use (local or docker)")
+	rootCmd.PersistentFlags().String("host", "", "url of the porter instance to use")
+	rootCmd.PersistentFlags().String("token", "", "token used for authentication")
+	rootCmd.PersistentFlags().String("profile", "", "name of the profile to use with the CLI")
+	rootCmd.PersistentFlags().Uint("project", 0, "project ID of the porter project to target")
+	rootCmd.PersistentFlags().Uint("cluster", 0, "cluster ID of the porter cluster to target")
+	rootCmd.PersistentFlags().Uint("registry", 0, "registry ID of connected Porter registry")
+	rootCmd.PersistentFlags().Uint("helmrepo", 0, "helm repo ID of connected Porter Helm repository")
+
 	if err := rootCmd.Execute(); err != nil {
 		color.New(color.FgRed).Println(err)
 		os.Exit(1)

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

@@ -42,13 +42,13 @@ var (
 	memoryMi       int
 )
 
-func registerCommand_Run(cliConf config.CLIConfig) *cobra.Command {
+func registerCommand_Run(cliConf config.CLIConfig, currentProfile string) *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, args, run)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, run)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -61,7 +61,7 @@ func registerCommand_Run(cliConf config.CLIConfig) *cobra.Command {
 		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, args, cleanup)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, cleanup)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -126,7 +126,7 @@ func registerCommand_Run(cliConf config.CLIConfig) *cobra.Command {
 	return runCmd
 }
 
-func run(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func run(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	execArgs := args[1:]
 
 	color.New(color.FgGreen).Println("Running", strings.Join(execArgs, " "), "for release", args[0])
@@ -242,7 +242,7 @@ func run(ctx context.Context, user *types.GetAuthenticatedUserResponse, client a
 	return executeRunEphemeral(ctx, config, namespace, selectedPod.Name, selectedContainerName, execArgs)
 }
 
-func cleanup(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
+func cleanup(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, currentProfile string, _ config.FeatureFlags, _ *cobra.Command, _ []string) error {
 	config := &PorterRunSharedConfig{
 		Client:    client,
 		CLIConfig: cliConfig,

+ 9 - 18
cli/cmd/commands/server.go

@@ -25,7 +25,7 @@ type startOps struct {
 
 var opts = &startOps{}
 
-func registerCommand_Server(cliConf config.CLIConfig) *cobra.Command {
+func registerCommand_Server(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
 	serverCmd := &cobra.Command{
 		Use:     "server",
 		Aliases: []string{"svr"},
@@ -40,7 +40,7 @@ func registerCommand_Server(cliConf config.CLIConfig) *cobra.Command {
 			ctx := cmd.Context()
 
 			if cliConf.Driver == "docker" {
-				_ = cliConf.SetDriver("docker")
+				_ = cliConf.SetDriver("docker", currentProfile)
 
 				err := startDocker(
 					ctx,
@@ -48,6 +48,7 @@ func registerCommand_Server(cliConf config.CLIConfig) *cobra.Command {
 					opts.imageTag,
 					opts.db,
 					*opts.port,
+					currentProfile,
 				)
 				if err != nil {
 					red := color.New(color.FgRed)
@@ -63,12 +64,13 @@ func registerCommand_Server(cliConf config.CLIConfig) *cobra.Command {
 					os.Exit(1)
 				}
 			} else {
-				_ = cliConf.SetDriver("local")
+				_ = cliConf.SetDriver("local", currentProfile)
 				err := startLocal(
 					ctx,
 					cliConf,
 					opts.db,
 					*opts.port,
+					currentProfile,
 				)
 				if err != nil {
 					red := color.New(color.FgRed)
@@ -120,13 +122,7 @@ func registerCommand_Server(cliConf config.CLIConfig) *cobra.Command {
 	return serverCmd
 }
 
-func startDocker(
-	ctx context.Context,
-	cliConf config.CLIConfig,
-	imageTag string,
-	db string,
-	port int,
-) error {
+func startDocker(ctx context.Context, cliConf config.CLIConfig, imageTag string, db string, port int, currentProfile string) error {
 	env := []string{
 		"NODE_ENV=production",
 		"FULLSTORY_ORG_ID=VXNSS",
@@ -158,20 +154,15 @@ func startDocker(
 
 	green.Printf("Server ready: listening on localhost:%d\n", port)
 
-	return cliConf.SetHost(fmt.Sprintf("http://localhost:%d", port))
+	return cliConf.SetHost(fmt.Sprintf("http://localhost:%d", port), currentProfile)
 }
 
-func startLocal(
-	ctx context.Context,
-	cliConf config.CLIConfig,
-	db string,
-	port int,
-) error {
+func startLocal(ctx context.Context, cliConf config.CLIConfig, db string, port int, currentProfile string) error {
 	if db == "postgres" {
 		return fmt.Errorf("postgres not available for local driver, run \"porter server start --db postgres --driver docker\"")
 	}
 
-	cliConf.SetHost(fmt.Sprintf("http://localhost:%d", port))
+	cliConf.SetHost(fmt.Sprintf("http://localhost:%d", port), currentProfile)
 
 	porterDir := filepath.Join(home, ".porter")
 	cmdPath := filepath.Join(home, ".porter", "portersvr")

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

@@ -16,7 +16,7 @@ import (
 
 var linkedApps []string
 
-func registerCommand_Stack(cliConf config.CLIConfig) *cobra.Command {
+func registerCommand_Stack(cliConf config.CLIConfig, currentProfile string) *cobra.Command {
 	stackCmd := &cobra.Command{
 		Use:     "stack",
 		Aliases: []string{"stacks"},
@@ -37,7 +37,7 @@ func registerCommand_Stack(cliConf config.CLIConfig) *cobra.Command {
 		Args:  cobra.ExactArgs(1),
 		Short: "Add an env group to a stack",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, args, stackAddEnvGroup)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, stackAddEnvGroup)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -49,7 +49,7 @@ func registerCommand_Stack(cliConf config.CLIConfig) *cobra.Command {
 		Args:  cobra.ExactArgs(1),
 		Short: "Remove an existing env group from a stack",
 		Run: func(cmd *cobra.Command, args []string) {
-			err := checkLoginAndRunWithConfig(cmd, cliConf, args, stackRemoveEnvGroup)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, stackRemoveEnvGroup)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -101,7 +101,7 @@ func registerCommand_Stack(cliConf config.CLIConfig) *cobra.Command {
 	return stackCmd
 }
 
-func stackAddEnvGroup(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func stackAddEnvGroup(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	if featureFlags.ValidateApplyV2Enabled {
 		err := v2.StackAddEnvGroup(ctx)
 		if err != nil {
@@ -179,7 +179,7 @@ func stackAddEnvGroup(ctx context.Context, _ *types.GetAuthenticatedUserResponse
 	return nil
 }
 
-func stackRemoveEnvGroup(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func stackRemoveEnvGroup(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	if featureFlags.ValidateApplyV2Enabled {
 		err := v2.StackRemoveEnvGroup(ctx)
 		if err != nil {

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

@@ -43,7 +43,7 @@ var (
 	waitForSuccessfulDeploy bool
 )
 
-func registerCommand_Update(cliConf config.CLIConfig) *cobra.Command {
+func registerCommand_Update(cliConf config.CLIConfig, currentProfile string) *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, args, updateFull)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, 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, args, updateGetEnv)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, 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, args, updateBuild)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, 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, args, updatePush)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, 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, args, updateUpgrade)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, 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, args, updateSetEnvGroup)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, 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, args, updateUnsetEnvGroup)
+			err := checkLoginAndRunWithConfig(cmd, cliConf, currentProfile, args, updateUnsetEnvGroup)
 			if err != nil {
 				os.Exit(1)
 			}
@@ -443,7 +443,7 @@ the image that the application uses if no --values file is specified:
 	return updateCmd
 }
 
-func updateFull(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func updateFull(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	if featureFlags.ValidateApplyV2Enabled {
 		err := v2.UpdateFull(ctx, cliConf, client, app)
 		if err != nil {
@@ -503,7 +503,7 @@ func updateFull(ctx context.Context, _ *types.GetAuthenticatedUserResponse, clie
 	return nil
 }
 
-func updateGetEnv(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func updateGetEnv(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	updateAgent, err := updateGetAgent(ctx, client, cliConf)
 	if err != nil {
 		return err
@@ -526,7 +526,7 @@ func updateGetEnv(ctx context.Context, _ *types.GetAuthenticatedUserResponse, cl
 	return updateAgent.WriteBuildEnv(getEnvFileDest)
 }
 
-func updateBuild(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func updateBuild(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	if featureFlags.ValidateApplyV2Enabled {
 		err := v2.UpdateBuild(ctx)
 		if err != nil {
@@ -543,7 +543,7 @@ func updateBuild(ctx context.Context, _ *types.GetAuthenticatedUserResponse, cli
 	return updateBuildWithAgent(ctx, updateAgent)
 }
 
-func updatePush(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func updatePush(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	if app == "" {
 		if len(args) == 0 {
 			return fmt.Errorf("please provide the docker image name")
@@ -602,7 +602,7 @@ func updatePush(ctx context.Context, _ *types.GetAuthenticatedUserResponse, clie
 	return updatePushWithAgent(ctx, updateAgent)
 }
 
-func updateUpgrade(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func updateUpgrade(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	if featureFlags.ValidateApplyV2Enabled {
 		err := v2.UpdateUpgrade(ctx)
 		if err != nil {
@@ -635,7 +635,7 @@ func updateUpgrade(ctx context.Context, _ *types.GetAuthenticatedUserResponse, c
 	return nil
 }
 
-func updateSetEnvGroup(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func updateSetEnvGroup(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	if len(normalEnvGroupVars) == 0 && len(secretEnvGroupVars) == 0 && len(args) == 0 {
 		return fmt.Errorf("please provide one or more variables to update")
 	}
@@ -743,7 +743,7 @@ func validateVarValue(in string) (string, string, error) {
 	return key, value, nil
 }
 
-func updateUnsetEnvGroup(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
+func updateUnsetEnvGroup(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, currentProfile string, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
 	if len(args) == 0 {
 		return fmt.Errorf("required variable name")
 	}

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

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

+ 43 - 281
cli/cmd/config/config.go

@@ -2,20 +2,23 @@ package config
 
 import (
 	"context"
-	"errors"
 	"fmt"
 	"os"
 	"path/filepath"
-	"strings"
 
-	"github.com/fatih/color"
-	api "github.com/porter-dev/porter/api/client"
-	"github.com/porter-dev/porter/cli/cmd/utils"
-	"github.com/spf13/viper"
 	"k8s.io/client-go/util/homedir"
 )
 
-var home = homedir.HomeDir()
+var (
+	// These values are globals to reduce the size of the refactor.
+	// These should be passed around
+	home                               = homedir.HomeDir()
+	defaultPorterConfigFileName string = "porter.yaml"
+	defaultPorterConfigDir      string = filepath.Join(home, ".porter")
+	porterConfigFilePath        string = filepath.Clean(filepath.Join(defaultPorterConfigDir, defaultPorterConfigFileName))
+	// currentProfile is used to set the profile for which any applied setting should be read or set
+	currentProfile string = "default"
+)
 
 // 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
@@ -23,17 +26,14 @@ var home = homedir.HomeDir()
 type CLIConfig struct {
 	// Driver can be either "docker" or "local", and represents which driver is
 	// used to run an instance of the server.
-	Driver string `yaml:"driver"`
-
-	Host    string `yaml:"host"`
-	Project uint   `yaml:"project"`
-	Cluster uint   `yaml:"cluster"`
-
-	Token string `yaml:"token"`
-
-	Registry   uint   `yaml:"registry"`
-	HelmRepo   uint   `yaml:"helm_repo"`
-	Kubeconfig string `yaml:"kubeconfig"`
+	Driver     string `yaml:"driver" env:"PORTER_DRIVER"`
+	Host       string `yaml:"host" env:"PORTER_HOST"`
+	Project    uint   `yaml:"project" env:"PORTER_PROJECT"`
+	Cluster    uint   `yaml:"cluster" env:"PORTER_CLUSTER"`
+	Token      string `yaml:"token" env:"PORTER_TOKEN"`
+	Registry   uint   `yaml:"registry" env:"PORTER_REGISTRY"`
+	HelmRepo   uint   `yaml:"helm_repo" env:"PORTER_HELM_REPO"`
+	Kubeconfig string `yaml:"kubeconfig" env:"PORTER_KUBECONFIG"`
 }
 
 // FeatureFlags are any flags that are relevant to the feature set of the CLI. This should not include all feature flags, only those relevant to client-side CLI operations
@@ -47,295 +47,57 @@ type FeatureFlags struct {
 // 2. env
 // 3. config
 // 4. default
-// Make sure to call overrideConfigWithFlags during runtime, to ensure that the flag values are considered
-func InitAndLoadConfig() (CLIConfig, error) {
+// If flagsConfig and envConfig are empty, then the default values or config file values will be preferred.
+// This returns the config which should be applied to all subsequent requests, as well as the current profile that the command was run with
+func InitAndLoadConfig(ctx context.Context, flagsProfile string, flagsConfig CLIConfig) (CLIConfig, string, error) {
 	var config CLIConfig
+	currentProfile := defaultProfileName
 
-	porterDir, err := getOrCreatePorterDirectoryAndConfig()
-	if err != nil {
-		return config, fmt.Errorf("unable to get or create porter directory: %w", err)
-	}
-	viper.SetConfigName("porter")
-	viper.SetConfigType("yaml")
-	viper.AddConfigPath(porterDir)
-
-	err = createAndLoadPorterYaml(porterDir)
-	if err != nil {
-		return config, fmt.Errorf("unable to load porter config: %w", err)
-	}
-
-	utils.DriverFlagSet.StringVar(
-		&config.Driver,
-		"driver",
-		"local",
-		"driver to use (local or docker)",
-	)
-	err = viper.BindPFlags(utils.DriverFlagSet)
-	if err != nil {
-		return config, err
-	}
-
-	utils.DefaultFlagSet.StringVar(
-		&config.Host,
-		"host",
-		"https://dashboard.getporter.dev",
-		"host URL of Porter instance",
-	)
-
-	utils.DefaultFlagSet.UintVar(
-		&config.Project,
-		"project",
-		0,
-		"project ID of Porter project",
-	)
-
-	utils.DefaultFlagSet.UintVar(
-		&config.Cluster,
-		"cluster",
-		0,
-		"cluster ID of Porter cluster",
-	)
-
-	utils.DefaultFlagSet.StringVar(
-		&config.Token,
-		"token",
-		"",
-		"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
-	}
-
-	utils.HelmRepoFlagSet.UintVar(
-		&config.HelmRepo,
-		"helmrepo",
-		0,
-		"helm repo ID of connected Porter Helm repository",
-	)
-	err = viper.BindPFlags(utils.HelmRepoFlagSet)
-	if err != nil {
-		return config, err
-	}
-
-	viper.SetEnvPrefix("PORTER")
-	err = viper.BindEnv("host")
-	if err != nil {
-		return config, err
-	}
-	err = viper.BindEnv("project")
-	if err != nil {
-		return config, err
-	}
-	err = viper.BindEnv("cluster")
-	if err != nil {
-		return config, err
-	}
-	err = viper.BindEnv("token")
-	if err != nil {
-		return config, err
-	}
-
-	err = viper.Unmarshal(&config)
-	if err != nil {
-		return config, fmt.Errorf("unable to unmarshal porter config: %w", err)
-	}
-
-	return config, nil
-}
-
-// getOrCreatePorterDirectoryAndConfig checks that the .porter folder exists; create if not
-func getOrCreatePorterDirectoryAndConfig() (string, error) {
-	porterDir := filepath.Join(home, ".porter")
-
-	_, err := os.Stat(porterDir)
-	if err != nil {
-		if !os.IsNotExist(err) {
-			return "", fmt.Errorf("error reading porter directory: %w", err)
-		}
-		err = os.Mkdir(porterDir, 0o700)
-		if err != nil {
-			return "", fmt.Errorf("error creating porter directory: %w", err)
-		}
-	}
-	return porterDir, nil
-}
-
-// createAndLoadPorterYaml loads a porter.yaml config into Viper if it exists, or creates the file if it does not
-func createAndLoadPorterYaml(porterDir string) error {
-	err := viper.ReadInConfig()
-	if err != nil {
-		_, ok := err.(viper.ConfigFileNotFoundError)
-		if !ok {
-			return fmt.Errorf("unknown error reading ~/.porter/porter.yaml config: %w", err)
-		}
-
-		err := os.WriteFile(filepath.Join(porterDir, "porter.yaml"), []byte{}, 0o644) //nolint:gosec // do not want to change program logic. Should be addressed later
-		if err != nil {
-			return fmt.Errorf("unable to create ~/.porter/porter.yaml config: %w", err)
-		}
-	}
-	return nil
-}
-
-func (c *CLIConfig) SetDriver(driver string) error {
-	viper.Set("driver", driver)
-	color.New(color.FgGreen).Printf("Set the current driver as %s\n", driver)
-	err := viper.WriteConfig()
+	err := ensurePorterConfigDirectoryExists()
 	if err != nil {
-		return err
+		return config, currentProfile, fmt.Errorf("unable to get or create porter directory: %w", err)
 	}
-
-	c.Driver = driver
-
-	return nil
-}
-
-func (c *CLIConfig) SetHost(host string) error {
-	// a trailing / can lead to errors with the api server
-	host = strings.TrimRight(host, "/")
-
-	viper.Set("host", host)
-
-	// let us clear the project ID, cluster ID, and token when we reset a host
-	viper.Set("project", 0)
-	viper.Set("cluster", 0)
-	viper.Set("token", "")
-
-	err := viper.WriteConfig()
+	err = ensurePorterConfigFileExists()
 	if err != nil {
-		return err
+		return config, currentProfile, fmt.Errorf("unable to get or create porter config file: %w", err)
 	}
 
-	color.New(color.FgGreen).Printf("Set the current host as %s\n", host)
-
-	c.Host = host
-	c.Project = 0
-	c.Cluster = 0
-	c.Token = ""
-
-	return nil
-}
-
-// SetProject sets a project for all API commands
-func (c *CLIConfig) SetProject(ctx context.Context, apiClient api.Client, projectID uint) error {
-	viper.Set("project", projectID)
-
-	color.New(color.FgGreen).Printf("Set the current project as %d\n", projectID)
+	defaultConfig := defaultCLIConfig()
 
-	if c.Kubeconfig != "" || viper.IsSet("kubeconfig") {
-		color.New(color.FgYellow).Println("Please change local kubeconfig if needed")
+	envProfile := os.Getenv("PORTER_PROFILE")
+	if envProfile != "" {
+		currentProfile = envProfile
 	}
-
-	err := viper.WriteConfig()
-	if err != nil {
-		return err
-	}
-
-	c.Project = projectID
-
-	resp, err := apiClient.ListProjectClusters(ctx, projectID)
-	if err == nil {
-		clusters := *resp
-		if len(clusters) == 1 {
-			_ = c.SetCluster(clusters[0].ID)
-		}
+	if flagsProfile != "" {
+		currentProfile = flagsProfile
 	}
 
-	return nil
-}
-
-func (c *CLIConfig) SetCluster(clusterID uint) error {
-	viper.Set("cluster", clusterID)
-
-	color.New(color.FgGreen).Printf("Set the current cluster as %d\n", clusterID)
-
-	if c.Kubeconfig != "" || viper.IsSet("kubeconfig") {
-		color.New(color.FgYellow).Println("Please change local kubeconfig if needed")
-	}
-
-	err := viper.WriteConfig()
+	currentProfileConfig, err := configForProfileFromConfigFile(currentProfile, porterConfigFilePath)
 	if err != nil {
-		return err
+		return config, currentProfile, fmt.Errorf("unable to read profile variables from config file")
 	}
 
-	c.Cluster = clusterID
-
-	return nil
-}
-
-func (c *CLIConfig) SetToken(token string) error {
-	viper.Set("token", token)
-	err := viper.WriteConfig()
+	overlayedCurrentProfileConfig, err := overlayProfiles(defaultConfig, currentProfileConfig)
 	if err != nil {
-		return err
+		return config, currentProfile, fmt.Errorf("unable to overlay profile onto default values")
 	}
 
-	c.Token = token
-
-	return nil
-}
-
-func (c *CLIConfig) SetRegistry(registryID uint) error {
-	viper.Set("registry", registryID)
-	color.New(color.FgGreen).Printf("Set the current registry as %d\n", registryID)
-	err := viper.WriteConfig()
+	envVarsConfig, err := profileConfigFromEnvVars(ctx)
 	if err != nil {
-		return err
+		return config, currentProfile, fmt.Errorf("unable to read profile variables from env vars")
 	}
 
-	c.Registry = registryID
-
-	return nil
-}
-
-func (c *CLIConfig) SetHelmRepo(helmRepoID uint) error {
-	viper.Set("helm_repo", helmRepoID)
-	color.New(color.FgGreen).Printf("Set the current Helm repo as %d\n", helmRepoID)
-	err := viper.WriteConfig()
+	overlayedEnvVarsConfig, err := overlayProfiles(overlayedCurrentProfileConfig, envVarsConfig)
 	if err != nil {
-		return err
+		return config, currentProfile, fmt.Errorf("unable to overlay env vars profile onto values in porter config file")
 	}
 
-	c.HelmRepo = helmRepoID
-
-	return nil
-}
-
-func (c *CLIConfig) SetKubeconfig(kubeconfig string) error {
-	path, err := filepath.Abs(kubeconfig)
-	if err != nil {
-		return err
-	}
-
-	if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
-		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)
-	err = viper.WriteConfig()
-
+	configWithAllOverlays, err := overlayProfiles(overlayedEnvVarsConfig, flagsConfig)
 	if err != nil {
-		return err
+		return config, currentProfile, fmt.Errorf("unable to overlay onto env vars config values onto flag values values")
 	}
 
-	c.Kubeconfig = kubeconfig
-
-	return nil
+	return configWithAllOverlays, currentProfile, nil
 }
 
 // ValidateCLIEnvironment checks that all required variables are present for running the CLI

+ 271 - 0
cli/cmd/config/profiles.go

@@ -0,0 +1,271 @@
+package config
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"os"
+	"path/filepath"
+	"strconv"
+
+	"github.com/sethvargo/go-envconfig"
+	"gopkg.in/yaml.v2"
+)
+
+// ProfilesConfig is the top level config from the porter config file, containing all possible profiles.
+// This is only used when parsing and writing, and should now be passed around as config.
+// Instead, pass the specific profile to the relevant functions
+type ProfilesConfig struct {
+	CurrentProfile string               `yaml:"current_profile" env:"PORTER_PROFILE,default=default"`
+	Profiles       map[string]CLIConfig `yaml:"profiles"`
+}
+
+var defaultProfileName string = "default"
+
+// migrateExistingConfigYaml moves the existing config layout to the new config layout with profiles support.
+// This can be deprecated after 2025-01-01 as all tokens which were part of the old config, will have expired, meaning all users
+// will have had to log out
+func migrateExistingConfigYaml(configPath string) error {
+	fi, err := os.ReadFile(filepath.Clean(configPath))
+	if err != nil {
+		return fmt.Errorf("error reading config file: %w", err)
+	}
+
+	// handle edge case where numbers are stored as strings for some values
+	var existingConfig map[string]any
+	err = yaml.Unmarshal(fi, &existingConfig)
+	if err != nil {
+		return fmt.Errorf("config file is invalid yaml: %w", err)
+	}
+	for k, v := range existingConfig {
+		stringValue, ok := v.(string)
+		if ok {
+			valueAsInt, err := strconv.Atoi(stringValue)
+			if err == nil {
+				existingConfig[k] = valueAsInt
+			}
+		}
+	}
+
+	by, err := yaml.Marshal(existingConfig)
+	if err != nil {
+		return fmt.Errorf("unable to marshal existing config to bytes: %w", err)
+	}
+
+	var c CLIConfig
+	err = yaml.Unmarshal(by, &c)
+	if err != nil {
+		return fmt.Errorf("config file is invalid yaml: %w", err)
+	}
+
+	err = updateValuesForSelectedProfile(defaultProfileName, c, configPath)
+	if err != nil {
+		return fmt.Errorf("unable to write migrated default config to file: %w", err)
+	}
+	err = updateCurrentProfileInFile(defaultProfileName, configPath)
+	if err != nil {
+		return fmt.Errorf("unable to update current_profile in config file: %w", err)
+	}
+
+	return nil
+}
+
+// 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
+func writeProfileToProfilesConfigFile(profilesConfig ProfilesConfig, configPath string) error {
+	by, err := yaml.Marshal(profilesConfig)
+	if err != nil {
+		return fmt.Errorf("error marshalling profiles config: %w", err)
+	}
+	err = os.WriteFile(configPath, by, os.ModePerm)
+	if err != nil {
+		return fmt.Errorf("error writing profiles config to file")
+	}
+	return nil
+}
+
+// updateCurrentProfileInFile changes the current_profile that is selected in the file for the next run.
+func updateCurrentProfileInFile(newProfile string, configPath string) error {
+	if newProfile == "" {
+		return errors.New("cannot update profile to an empty profile")
+	}
+
+	profilesConfig, err := readProfilesConfigFromFile(configPath)
+	if err != nil {
+		return fmt.Errorf("error reading profiles config file for updating current profile: %w", err)
+	}
+	profilesConfig.CurrentProfile = newProfile
+
+	err = writeProfileToProfilesConfigFile(profilesConfig, configPath)
+	if err != nil {
+		return fmt.Errorf("unable to update current profile in config file: %w", err)
+	}
+
+	return nil
+}
+
+// updateValuesForSelectedProfile updates config for a given profile. This can be used with the --profile flag or the PORTER_PROFILE env var,
+// and will not necessarily update the current_profile in the config file.
+func updateValuesForSelectedProfile(selectedProfile string, updatedProfile CLIConfig, configPath string) error {
+	if selectedProfile == "" {
+		return errors.New("must specify a profile to update")
+	}
+
+	profilesConfig, err := readProfilesConfigFromFile(configPath)
+	if err != nil {
+		return fmt.Errorf("error reading profiles config file for updating selected profile: %w", err)
+	}
+	if profilesConfig.Profiles == nil {
+		profilesConfig.Profiles = map[string]CLIConfig{
+			selectedProfile: defaultCLIConfig(),
+		}
+	}
+
+	if _, ok := profilesConfig.Profiles[selectedProfile]; !ok {
+		profilesConfig.Profiles[selectedProfile] = defaultCLIConfig()
+	}
+
+	baseProfile := profilesConfig.Profiles[selectedProfile]
+	overlayedProfile, err := overlayProfiles(baseProfile, updatedProfile)
+	if err != nil {
+		return fmt.Errorf("unable to add new profile values to existing values")
+	}
+
+	profilesConfig.Profiles[selectedProfile] = overlayedProfile
+
+	err = writeProfileToProfilesConfigFile(profilesConfig, configPath)
+	if err != nil {
+		return fmt.Errorf("unable to update current profile in config file: %w", err)
+	}
+	return nil
+}
+
+// readProfilesConfigFromFile reads the config file which supports profiles
+func readProfilesConfigFromFile(configPath string) (ProfilesConfig, error) {
+	var profiles ProfilesConfig
+	fi, err := os.ReadFile(filepath.Clean(configPath))
+	if err != nil {
+		return profiles, fmt.Errorf("error reading config file: %w", err)
+	}
+
+	err = yaml.Unmarshal(fi, &profiles)
+	if err != nil {
+		return profiles, fmt.Errorf("config file is invalid yaml: %w", err)
+	}
+	return profiles, nil
+}
+
+// defaultCLIConfig sets the default values for give profile
+func defaultCLIConfig() CLIConfig {
+	return CLIConfig{
+		Driver:   "local",
+		Host:     "https://dashboard.getporter.dev",
+		Project:  0,
+		Cluster:  0,
+		Token:    "",
+		Registry: 0,
+		HelmRepo: 0,
+	}
+}
+
+// ensurePorterConfigDirectoryExists checks that the .porter folder exists, and creates it if it doesn't exist
+func ensurePorterConfigDirectoryExists() error {
+	_, err := os.Stat(defaultPorterConfigDir)
+	if err != nil {
+		if !os.IsNotExist(err) {
+			return fmt.Errorf("error reading porter directory: %w", err)
+		}
+		err = os.Mkdir(defaultPorterConfigDir, 0o700)
+		if err != nil {
+			return fmt.Errorf("error creating porter directory: %w", err)
+		}
+	}
+	return nil
+}
+
+// ensurePorterConfigFileExists checks that the porter.yaml config file exists, and creates it if it doesn't exist
+func ensurePorterConfigFileExists() error {
+	_, err := os.Stat(porterConfigFilePath)
+	if err != nil {
+		if !os.IsNotExist(err) {
+			return fmt.Errorf("error reading porter config file: %w", err)
+		}
+		err = os.Mkdir(defaultPorterConfigDir, 0o700)
+		if err != nil {
+			return fmt.Errorf("error creating porter config file: %w", err)
+		}
+	}
+	return nil
+}
+
+// overlayProfiles will add all values from the profileToOverlay to the baseProfile,
+// returning the new profile with both values
+func overlayProfiles(baseProfile CLIConfig, profileToOverlay CLIConfig) (CLIConfig, error) {
+	if profileToOverlay.Cluster != 0 {
+		baseProfile.Cluster = profileToOverlay.Cluster
+	}
+	if profileToOverlay.Driver != "" {
+		baseProfile.Driver = profileToOverlay.Driver
+	}
+	if profileToOverlay.HelmRepo != 0 {
+		baseProfile.HelmRepo = profileToOverlay.HelmRepo
+	}
+	if profileToOverlay.Host != "" {
+		baseProfile.Host = profileToOverlay.Host
+	}
+	if profileToOverlay.Kubeconfig != "" {
+		baseProfile.Kubeconfig = profileToOverlay.Kubeconfig
+	}
+	if profileToOverlay.Project != 0 {
+		baseProfile.Project = profileToOverlay.Project
+	}
+	if profileToOverlay.Registry != 0 {
+		baseProfile.Registry = profileToOverlay.Registry
+	}
+	if profileToOverlay.Token != "" {
+		baseProfile.Token = profileToOverlay.Token
+	}
+	return baseProfile, nil
+}
+
+// configForProfileFromConfigFile gets the profile for the current_profile specified in the porter config file
+func configForProfileFromConfigFile(selectedProfile string, configPath string) (CLIConfig, error) {
+	profilesConfig, err := readProfilesConfigFromFile(configPath)
+	if err != nil {
+		return CLIConfig{}, fmt.Errorf("error reading profiles config file: %w", err)
+	}
+
+	if selectedProfile == "" {
+		selectedProfile = defaultProfileName
+	}
+
+	if profilesConfig.CurrentProfile == "" && len(profilesConfig.Profiles) == 0 {
+		err := migrateExistingConfigYaml(configPath)
+		if err != nil {
+			return CLIConfig{}, fmt.Errorf("error migrating porter.yaml config file. Please delete file at %s. %w", configPath, err)
+		}
+
+		migrated, err := configForProfileFromConfigFile(selectedProfile, configPath)
+		if err != nil {
+			return CLIConfig{}, fmt.Errorf("error migrating existing porter.yaml to support profiles: %w", err)
+		}
+		return migrated, nil
+	}
+
+	configFile, ok := profilesConfig.Profiles[selectedProfile]
+	if ok {
+		return configFile, nil
+	}
+
+	return CLIConfig{}, nil
+}
+
+// profileConfigFromEnvVars parses any environment variables that may be setting
+// config values, such as PORTER_HOST and PORTER_PROJECT
+func profileConfigFromEnvVars(ctx context.Context) (CLIConfig, error) {
+	var c CLIConfig
+	if err := envconfig.Process(ctx, &c); err != nil {
+		return c, fmt.Errorf("error processing porter env vars: %w", err)
+	}
+	return c, nil
+}

+ 128 - 0
cli/cmd/config/update.go

@@ -0,0 +1,128 @@
+package config
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/fatih/color"
+	api "github.com/porter-dev/porter/api/client"
+	"github.com/spf13/viper"
+)
+
+func (c *CLIConfig) SetDriver(driver string, currentProfile string) error {
+	v := CLIConfig{
+		Driver: driver,
+	}
+	return updateValuesForSelectedProfile(currentProfile, v, porterConfigFilePath)
+}
+
+// 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 {
+	host = strings.TrimRight(host, "/")
+
+	v := defaultCLIConfig()
+	v.Host = host
+
+	color.New(color.FgGreen).Printf("Set the current host as %s\n", host)
+
+	return updateValuesForSelectedProfile(currentProfile, v, porterConfigFilePath)
+}
+
+// SetProject sets a project for all API commands
+func (c *CLIConfig) SetProject(ctx context.Context, apiClient api.Client, projectID uint, selectedProfile string) error {
+	color.New(color.FgGreen).Printf("Set the current project as %d\n", projectID)
+	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)
+}
+
+func (c *CLIConfig) SetCluster(clusterID uint) error {
+	color.New(color.FgGreen).Printf("Set the current cluster as %d\n", clusterID)
+
+	if c.Kubeconfig != "" || viper.IsSet("kubeconfig") {
+		color.New(color.FgYellow).Println("Please change local kubeconfig if needed")
+	}
+
+	err := viper.WriteConfig()
+	if err != nil {
+		return err
+	}
+
+	c.Cluster = clusterID
+
+	return nil
+}
+
+func (c *CLIConfig) SetToken(token string) error {
+	viper.Set("token", token)
+	err := viper.WriteConfig()
+	if err != nil {
+		return err
+	}
+
+	c.Token = token
+
+	return nil
+}
+
+func (c *CLIConfig) SetRegistry(registryID uint) error {
+	viper.Set("registry", registryID)
+	color.New(color.FgGreen).Printf("Set the current registry as %d\n", registryID)
+	err := viper.WriteConfig()
+	if err != nil {
+		return err
+	}
+
+	c.Registry = registryID
+
+	return nil
+}
+
+func (c *CLIConfig) SetHelmRepo(helmRepoID uint) error {
+	viper.Set("helm_repo", helmRepoID)
+	color.New(color.FgGreen).Printf("Set the current Helm repo as %d\n", helmRepoID)
+	err := viper.WriteConfig()
+	if err != nil {
+		return err
+	}
+
+	c.HelmRepo = helmRepoID
+
+	return nil
+}
+
+func (c *CLIConfig) SetKubeconfig(kubeconfig string) error {
+	path, err := filepath.Abs(kubeconfig)
+	if err != nil {
+		return err
+	}
+
+	if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
+		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)
+	err = viper.WriteConfig()
+
+	if err != nil {
+		return err
+	}
+
+	c.Kubeconfig = kubeconfig
+
+	return nil
+}

+ 1 - 1
cli/cmd/errors/error_handler.go

@@ -49,7 +49,7 @@ func (h *sentryErrorHandler) HandleError(err error) {
 }
 
 // GetErrorHandler returns an errorhandler.
-func GetErrorHandler(cliConf config.CLIConfig) errorHandler {
+func GetErrorHandler(cliConf config.CLIConfig, currentProfile string) errorHandler {
 	if SentryDSN != "" {
 		return &sentryErrorHandler{
 			cliConfig: cliConf,