Stefan McShane před 2 roky
rodič
revize
390585eff6

+ 2 - 4
cli/cmd/commands/errors.go

@@ -4,7 +4,6 @@ import (
 	"context"
 	"errors"
 	"fmt"
-	"os"
 	"strings"
 
 	"github.com/fatih/color"
@@ -55,8 +54,7 @@ func checkLoginAndRunWithConfig(cmd *cobra.Command, args []string, runner authen
 			return ErrCannotConnect
 		}
 
-		red.Fprintf(os.Stderr, "Error: %v\n", err.Error())
-		return err
+		return fmt.Errorf("error checking login: %w", err)
 	}
 
 	project, err := client.GetProject(ctx, cliConf.Project)
@@ -87,7 +85,7 @@ func checkLoginAndRunWithConfig(cmd *cobra.Command, args []string, runner authen
 
 		cliErrors.GetErrorHandler(cliConf, currentProfile).HandleError(err)
 
-		return err
+		return fmt.Errorf("error running command: %w", err)
 	}
 
 	return nil

+ 11 - 30
cli/cmd/config/config.go

@@ -51,19 +51,27 @@ type FeatureFlags struct {
 // 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
 
 	err := ensurePorterConfigDirectoryExists()
 	if err != nil {
-		return config, currentProfile, fmt.Errorf("unable to get or create porter directory: %w", err)
+		return config, "", fmt.Errorf("unable to get or create porter directory: %w", err)
 	}
 	err = ensurePorterConfigFileExists()
 	if err != nil {
-		return config, currentProfile, fmt.Errorf("unable to get or create porter config file: %w", err)
+		return config, "", fmt.Errorf("unable to get or create porter config file: %w", err)
 	}
 
 	defaultConfig := defaultCLIConfig()
+	currentProfile := defaultProfileName
+
+	currentProfileConfig, configFileProfile, err := configForProfileFromConfigFile(currentProfile, porterConfigFilePath)
+	if err != nil {
+		return config, currentProfile, fmt.Errorf("unable to read profile variables from config file")
+	}
 
+	if configFileProfile != "" {
+		currentProfile = configFileProfile
+	}
 	envProfile := os.Getenv("PORTER_PROFILE")
 	if envProfile != "" {
 		currentProfile = envProfile
@@ -72,11 +80,6 @@ func InitAndLoadConfig(ctx context.Context, flagsProfile string, flagsConfig CLI
 		currentProfile = flagsProfile
 	}
 
-	currentProfileConfig, err := configForProfileFromConfigFile(currentProfile, porterConfigFilePath)
-	if err != nil {
-		return config, currentProfile, fmt.Errorf("unable to read profile variables from config file")
-	}
-
 	overlayedCurrentProfileConfig, err := overlayProfiles(defaultConfig, currentProfileConfig)
 	if err != nil {
 		return config, currentProfile, fmt.Errorf("unable to overlay profile onto default values")
@@ -99,25 +102,3 @@ func InitAndLoadConfig(ctx context.Context, flagsProfile string, flagsConfig CLI
 
 	return configWithAllOverlays, currentProfile, nil
 }
-
-// ValidateCLIEnvironment checks that all required variables are present for running the CLI
-func ValidateCLIEnvironment() error {
-	c, err := configForProfileFromConfigFile("", porterConfigFilePath)
-	if err != nil {
-		return fmt.Errorf("error reading config for profile: %w", err)
-	}
-
-	if c.Token == "" {
-		return fmt.Errorf("no auth token present, please run 'porter auth login' to authenticate")
-	}
-
-	if c.Project == 0 {
-		return fmt.Errorf("no project selected, please run 'porter config set-project' to select a project")
-	}
-
-	if c.Cluster == 0 {
-		return fmt.Errorf("no cluster selected, please run 'porter config set-cluster' to select a cluster")
-	}
-
-	return nil
-}

+ 22 - 17
cli/cmd/config/profiles.go

@@ -59,7 +59,7 @@ func migrateExistingConfigYaml(configPath string) error {
 		return fmt.Errorf("config file is invalid yaml: %w", err)
 	}
 
-	err = updateValuesForSelectedProfile(defaultProfileName, c, configPath)
+	err = updateValuesForSelectedProfile(defaultProfileName, configPath, withCLIConfig(c))
 	if err != nil {
 		return fmt.Errorf("unable to write migrated default config to file: %w", err)
 	}
@@ -108,7 +108,7 @@ func updateCurrentProfileInFile(newProfile string, configPath string) error {
 
 // 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 {
+func updateValuesForSelectedProfile(selectedProfile string, configPath string, updatedCLIValues ...cliConfigValue) error {
 	if selectedProfile == "" {
 		return errors.New("must specify a profile to update")
 	}
@@ -128,12 +128,11 @@ func updateValuesForSelectedProfile(selectedProfile string, updatedProfile CLICo
 	}
 
 	baseProfile := profilesConfig.Profiles[selectedProfile]
-	overlayedProfile, err := overlayProfiles(baseProfile, updatedProfile)
-	if err != nil {
-		return fmt.Errorf("unable to add new profile values to existing values")
+	for _, v := range updatedCLIValues {
+		v(&baseProfile)
 	}
 
-	profilesConfig.Profiles[selectedProfile] = overlayedProfile
+	profilesConfig.Profiles[selectedProfile] = baseProfile
 
 	err = writeProfileToProfilesConfigFile(profilesConfig, configPath)
 	if err != nil {
@@ -233,38 +232,44 @@ func overlayProfiles(baseProfile CLIConfig, profileToOverlay CLIConfig) (CLIConf
 }
 
 // configForProfileFromConfigFile gets the profile for the current_profile specified in the porter config file
-func configForProfileFromConfigFile(selectedProfile string, configPath string) (CLIConfig, error) {
+func configForProfileFromConfigFile(selectedProfile string, configPath string) (CLIConfig, string, error) {
+	if selectedProfile == "" {
+		selectedProfile = defaultProfileName
+	}
+
 	profilesConfig, err := readProfilesConfigFromFile(configPath)
 	if err != nil {
-		return CLIConfig{}, fmt.Errorf("error reading profiles config file: %w", err)
+		return CLIConfig{}, selectedProfile, fmt.Errorf("error reading profiles config file: %w", err)
 	}
 
-	if selectedProfile == "" {
-		selectedProfile = defaultProfileName
+	if selectedProfile == defaultProfileName {
+		if profilesConfig.CurrentProfile != "" {
+			selectedProfile = profilesConfig.CurrentProfile
+		}
 	}
 
 	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)
+			return CLIConfig{}, selectedProfile, fmt.Errorf("error migrating porter.yaml config file. Please delete file at %s. %w", configPath, err)
 		}
 
-		migrated, err := configForProfileFromConfigFile(selectedProfile, configPath)
+		migrated, selectedProfile, err := configForProfileFromConfigFile(selectedProfile, configPath)
 		if err != nil {
-			return CLIConfig{}, fmt.Errorf("error migrating existing porter.yaml to support profiles: %w", err)
+			return CLIConfig{}, selectedProfile, fmt.Errorf("error migrating existing porter.yaml to support profiles: %w", err)
 		}
-		return migrated, nil
+		return migrated, selectedProfile, nil
 	}
 
 	configFile, ok := profilesConfig.Profiles[selectedProfile]
 	if !ok {
 		_, _ = color.New(color.FgGreen).Printf("Porter profile '%s' does not exist. Creating one now...\n", currentProfile)
-		err = updateValuesForSelectedProfile(selectedProfile, defaultCLIConfig(), configPath)
+		err = updateValuesForSelectedProfile(selectedProfile, configPath, withCLIConfig(defaultCLIConfig()))
 		if err != nil {
-			return CLIConfig{}, fmt.Errorf("error creating new profile: %w", err)
+			return CLIConfig{}, selectedProfile, fmt.Errorf("error creating new profile: %w", err)
 		}
 	}
-	return configFile, nil
+	return configFile, selectedProfile, nil
 }
 
 // profileConfigFromEnvVars parses any environment variables that may be setting

+ 66 - 34
cli/cmd/config/update.go

@@ -22,10 +22,7 @@ func SetProfile(currentProfile string) error {
 
 // SetDriver sets the driver used when building images. This can either be locla or github
 func SetDriver(driver string, currentProfile string) error {
-	v := CLIConfig{
-		Driver: driver,
-	}
-	return updateValuesForSelectedProfile(currentProfile, v, porterConfigFilePath)
+	return updateValuesForSelectedProfile(currentProfile, porterConfigFilePath, withDriver(driver))
 }
 
 // SetHost updates the host for the current profile. This clears the project, cluster, and token as they will not work with the new host
@@ -33,60 +30,40 @@ func SetDriver(driver string, currentProfile string) error {
 func 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) //nolint:errcheck,gosec
 
-	return updateValuesForSelectedProfile(currentProfile, v, porterConfigFilePath)
+	return updateValuesForSelectedProfile(currentProfile, porterConfigFilePath, withHost(host), withClusterID(0), withProjectID(0))
 }
 
 // SetProject sets a project for all API commands
 func SetProject(projectID uint, currentProfile string) error {
 	color.New(color.FgGreen).Printf("Set the current project as %d\n", projectID) //nolint:errcheck,gosec
 
-	v := CLIConfig{
-		Project: projectID,
-	}
-
-	return updateValuesForSelectedProfile(currentProfile, v, porterConfigFilePath)
+	return updateValuesForSelectedProfile(currentProfile, porterConfigFilePath, withProjectID(0))
 }
 
 // SetCluster sets the cluster in the current profile. All further actions will be targeted at this cluster
 func SetCluster(clusterID uint, currentProfile string) error {
 	color.New(color.FgGreen).Printf("Set the current cluster as %d\n", clusterID) //nolint:errcheck,gosec
-
-	v := CLIConfig{
-		Cluster:    clusterID,
-		Kubeconfig: "",
-	}
-	return updateValuesForSelectedProfile(currentProfile, v, porterConfigFilePath)
+	return updateValuesForSelectedProfile(currentProfile, porterConfigFilePath, withClusterID(0), withKubeconfig(""))
 }
 
 // SetToken sets the token in the current profile. All further actions will be authenticated with this token
 func SetToken(token string, currentProfile string) error {
-	v := CLIConfig{
-		Token: token,
-	}
-	return updateValuesForSelectedProfile(currentProfile, v, porterConfigFilePath)
+	return updateValuesForSelectedProfile(currentProfile, porterConfigFilePath, withToken(token))
 }
 
 // SetRegistry sets the docker registry in the current profile. All further actions will be targeted at this registry
 func SetRegistry(registryID uint) error {
 	color.New(color.FgGreen).Printf("Set the current registry as %d\n", registryID) //nolint:errcheck,gosec
-	v := CLIConfig{
-		Registry: registryID,
-	}
-	return updateValuesForSelectedProfile(currentProfile, v, porterConfigFilePath)
+	return updateValuesForSelectedProfile(currentProfile, porterConfigFilePath, withRegistryID(registryID))
 }
 
 // SetHelmRepo sets the helm repo in the current profile. All further actions will be targeted at this helm repo
 func SetHelmRepo(helmRepoID uint) error {
 	color.New(color.FgGreen).Printf("Set the current Helm repo as %d\n", helmRepoID) //nolint:errcheck,gosec
-	v := CLIConfig{
-		HelmRepo: helmRepoID,
-	}
-	return updateValuesForSelectedProfile(currentProfile, v, porterConfigFilePath)
+
+	return updateValuesForSelectedProfile(currentProfile, porterConfigFilePath, withHelmRepoID(helmRepoID))
 }
 
 // SetKubeconfig sets the kubeconfig in the current profile. All further actions which require kubernetes will be targeted at this kubeconfig
@@ -102,8 +79,63 @@ func SetKubeconfig(kubeconfig string) error {
 
 	color.New(color.FgGreen).Printf("Set the path to kubeconfig as %s\n", path) //nolint:errcheck,gosec
 
-	v := CLIConfig{
-		Kubeconfig: kubeconfig,
+	return updateValuesForSelectedProfile(currentProfile, porterConfigFilePath, withKubeconfig(kubeconfig))
+}
+
+// cliConfigValue allows for overridden CLI config values allowing for default values, without requiring nil.
+type cliConfigValue func(*CLIConfig)
+
+func withClusterID(clusterID uint) cliConfigValue {
+	return func(c *CLIConfig) {
+		c.Cluster = clusterID
+	}
+}
+
+func withToken(token string) cliConfigValue {
+	return func(c *CLIConfig) {
+		c.Token = token
+	}
+}
+
+func withRegistryID(registryID uint) cliConfigValue {
+	return func(c *CLIConfig) {
+		c.Registry = registryID
+	}
+}
+
+func withHelmRepoID(helmRepoID uint) cliConfigValue {
+	return func(c *CLIConfig) {
+		c.HelmRepo = helmRepoID
+	}
+}
+
+func withKubeconfig(kubeconfig string) cliConfigValue {
+	return func(c *CLIConfig) {
+		c.Kubeconfig = kubeconfig
+	}
+}
+
+func withHost(host string) cliConfigValue {
+	return func(c *CLIConfig) {
+		c.Host = host
+	}
+}
+
+func withProjectID(projectID uint) cliConfigValue {
+	return func(c *CLIConfig) {
+		c.Project = projectID
+	}
+}
+
+func withDriver(driver string) cliConfigValue {
+	return func(c *CLIConfig) {
+		c.Driver = driver
+	}
+}
+
+// withCLIConfig will completely override the CLI config with the provided one
+func withCLIConfig(newConfig CLIConfig) cliConfigValue {
+	return func(c *CLIConfig) {
+		c = &newConfig
 	}
-	return updateValuesForSelectedProfile(currentProfile, v, porterConfigFilePath)
 }

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

@@ -24,9 +24,25 @@ type StackConf struct {
 	projectID, clusterID uint
 }
 
+// validateCLIConfig checks that all required variables are present for running the CLI in this package
+func validateCLIConfig(c config.CLIConfig) error {
+	if c.Token == "" {
+		return fmt.Errorf("no auth token present, please run 'porter auth login' to authenticate")
+	}
+
+	if c.Project == 0 {
+		return fmt.Errorf("no project selected, please run 'porter config set-project' to select a project")
+	}
+
+	if c.Cluster == 0 {
+		return fmt.Errorf("no cluster selected, please run 'porter config set-cluster' to select a cluster")
+	}
+	return nil
+}
+
 // CreateApplicationDeploy creates everything needed to deploy a porter app
 func CreateApplicationDeploy(ctx context.Context, client api.Client, worker *switchboardWorker.Worker, app *Application, applicationName string, cliConf config.CLIConfig) ([]*switchboardTypes.Resource, error) {
-	err := config.ValidateCLIEnvironment()
+	err := validateCLIConfig(cliConf)
 	if err != nil {
 		errMsg := composePreviewMessage("porter CLI is not configured correctly", Error)
 		return nil, fmt.Errorf("%s: %w", errMsg, err)

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

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

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

@@ -19,6 +19,22 @@ type PreviewApplier struct {
 	parsed    *PorterYAML
 }
 
+// validateCLIConfig checks that all required variables are present for running the CLI in this package
+func validateCLIConfig(c config.CLIConfig) error {
+	if c.Token == "" {
+		return fmt.Errorf("no auth token present, please run 'porter auth login' to authenticate")
+	}
+
+	if c.Project == 0 {
+		return fmt.Errorf("no project selected, please run 'porter config set-project' to select a project")
+	}
+
+	if c.Cluster == 0 {
+		return fmt.Errorf("no cluster selected, please run 'porter config set-cluster' to select a cluster")
+	}
+	return nil
+}
+
 // NewApplier returns an applier for preview environments
 func NewApplier(client api.Client, cliConfig config.CLIConfig, raw []byte, namespace string) (*PreviewApplier, error) {
 	// replace all instances of ${{ porter.env.FOO }} with { .get-env.FOO }
@@ -33,7 +49,7 @@ func NewApplier(client api.Client, cliConfig config.CLIConfig, raw []byte, names
 		return nil, fmt.Errorf("%s: %w", errMsg, err)
 	}
 
-	err = config.ValidateCLIEnvironment()
+	err = validateCLIConfig(cliConfig)
 	if err != nil {
 		errMsg := composePreviewMessage("porter CLI is not configured correctly", Error)
 		return nil, fmt.Errorf("%s: %w", errMsg, err)