From d21124a42df16cfb81b5787851d65ca1a2bd345c Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Mon, 20 Oct 2025 10:32:49 -0500 Subject: [PATCH 1/2] added profile use command and --profile flag --- cli/cmd/profile_use.go | 51 ++++++++++++++++++++++++++++++ cli/cmd/root.go | 70 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 cli/cmd/profile_use.go diff --git a/cli/cmd/profile_use.go b/cli/cmd/profile_use.go new file mode 100644 index 000000000..f1ebb6d20 --- /dev/null +++ b/cli/cmd/profile_use.go @@ -0,0 +1,51 @@ +package cmd + +import ( + "fmt" + + "github.com/pkg/errors" + "github.com/replicatedhq/replicated/pkg/credentials" + "github.com/spf13/cobra" +) + +func (r *runners) InitProfileUseCommand(parent *cobra.Command) *cobra.Command { + cmd := &cobra.Command{ + Use: "use [profile-name]", + Short: "Set the default authentication profile", + Long: `Set the default authentication profile that will be used when no profile is specified +in .replicated.yaml and no environment variables are set.`, + Example: `# Use production as the default profile +replicated profile use prod`, + Args: cobra.ExactArgs(1), + SilenceUsage: true, + RunE: r.profileUse, + } + parent.AddCommand(cmd) + + return cmd +} + +func (r *runners) profileUse(_ *cobra.Command, args []string) error { + profileName := args[0] + + if profileName == "" { + return errors.New("profile name cannot be empty") + } + + // Check if profile exists + _, err := credentials.GetProfile(profileName) + if err == credentials.ErrProfileNotFound { + return errors.Errorf("profile '%s' not found", profileName) + } + if err != nil { + return errors.Wrap(err, "failed to get profile") + } + + // Set as default + if err := credentials.SetDefaultProfile(profileName); err != nil { + return errors.Wrap(err, "failed to set default profile") + } + + fmt.Printf("Now using profile '%s' as default\n", profileName) + return nil +} diff --git a/cli/cmd/root.go b/cli/cmd/root.go index d85dbc7ee..c5bb4fc25 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -32,6 +32,7 @@ const ( var ( appSlugOrID string apiToken string + profileNameFlag string platformOrigin = "https://api.replicated.com/vendor" kurlDotSHOrigin = "https://kurl.sh" cache *replicatedcache.Cache @@ -66,6 +67,7 @@ func GetRootCmd() *cobra.Command { } rootCmd.PersistentFlags().StringVar(&appSlugOrID, "app", "", "The app slug or app id to use in all calls") rootCmd.PersistentFlags().StringVar(&apiToken, "token", "", "The API token to use to access your app in the Vendor API") + rootCmd.PersistentFlags().StringVar(&profileNameFlag, "profile", "", "The authentication profile to use for this command") rootCmd.PersistentFlags().BoolVar(&debugFlag, "debug", false, "Enable debug output") return rootCmd @@ -299,6 +301,7 @@ func Execute(rootCmd *cobra.Command, stdin io.Reader, stdout io.Writer, stderr i runCmds.InitProfileLsCommand(profileCmd) runCmds.InitProfileRmCommand(profileCmd) runCmds.InitProfileSetDefaultCommand(profileCmd) + runCmds.InitProfileUseCommand(profileCmd) apiCmd := runCmds.InitAPICommand(runCmds.rootCmd) runCmds.InitAPIGet(apiCmd) @@ -314,12 +317,36 @@ func Execute(rootCmd *cobra.Command, stdin io.Reader, stdout io.Writer, stderr i preRunSetupAPIs := func(cmd *cobra.Command, args []string) error { if apiToken == "" { - // Try to load profile from .replicated.yaml + // Try to load profile from --profile flag, then .replicated.yaml var profileName string - configParser := tools.NewConfigParser() - config, err := configParser.FindAndParseConfig("") - if err == nil && config.Profile != "" { - profileName = config.Profile + var profileSource string + if profileNameFlag != "" { + // Command-line flag takes precedence + profileName = profileNameFlag + profileSource = "--profile flag" + } else { + // Fall back to profile from .replicated.yaml + configParser := tools.NewConfigParser() + config, err := configParser.FindAndParseConfig("") + if err == nil && config.Profile != "" { + profileName = config.Profile + profileSource = ".replicated.yaml" + } else { + profileSource = "default profile" + } + } + + // If no profile name yet, check if there's a default profile + if profileName == "" { + defaultProfileName, err := credentials.GetDefaultProfile() + if err == nil && defaultProfileName != "" { + profileName = defaultProfileName + profileSource = "default profile" + } + } + + if debugFlag && profileName != "" { + fmt.Fprintf(os.Stderr, "[DEBUG] Using profile '%s' (from %s)\n", profileName, profileSource) } // Get credentials with profile support @@ -337,19 +364,48 @@ func Execute(rootCmd *cobra.Command, stdin io.Reader, stdout io.Writer, stderr i apiToken = creds.APIToken + if debugFlag { + maskedToken := apiToken + if len(maskedToken) > 8 { + maskedToken = maskedToken[:4] + "..." + maskedToken[len(maskedToken)-4:] + } + fmt.Fprintf(os.Stderr, "[DEBUG] API Token: %s\n", maskedToken) + } + // If using a profile, check if it has custom origins if creds.IsProfile && profileName != "" { apiOrigin, registryOrigin, err := credentials.GetProfileOrigins(profileName) if err == nil { if apiOrigin != "" { - platformOrigin = apiOrigin + // Strip trailing slashes to avoid double-slash in URL construction + platformOrigin = strings.TrimRight(apiOrigin, "/") + if debugFlag { + if apiOrigin != platformOrigin { + fmt.Fprintf(os.Stderr, "[DEBUG] Normalized API origin (removed trailing slash): %s -> %s\n", apiOrigin, platformOrigin) + } else { + fmt.Fprintf(os.Stderr, "[DEBUG] Using custom API origin from profile: %s\n", platformOrigin) + } + } } if registryOrigin != "" { // Store registry origin for later use (if needed by commands) - os.Setenv("REPLICATED_REGISTRY_ORIGIN", registryOrigin) + // Also strip trailing slashes from registry origin + normalizedRegistryOrigin := strings.TrimRight(registryOrigin, "/") + os.Setenv("REPLICATED_REGISTRY_ORIGIN", normalizedRegistryOrigin) + if debugFlag { + if registryOrigin != normalizedRegistryOrigin { + fmt.Fprintf(os.Stderr, "[DEBUG] Normalized registry origin (removed trailing slash): %s -> %s\n", registryOrigin, normalizedRegistryOrigin) + } else { + fmt.Fprintf(os.Stderr, "[DEBUG] Using custom registry origin from profile: %s\n", normalizedRegistryOrigin) + } + } } } } + + if debugFlag { + fmt.Fprintf(os.Stderr, "[DEBUG] Platform API origin: %s\n", platformOrigin) + } } // allow override From 02d8360fc23fce1ff4150bf8018c2d92e92c74ce Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Mon, 20 Oct 2025 10:39:41 -0500 Subject: [PATCH 2/2] removed local profile name setter --- .replicated.yaml | 1 - cli/cmd/profile.go | 3 +-- cli/cmd/profile_set_default.go | 4 ++-- cli/cmd/profile_use.go | 4 ++-- cli/cmd/root.go | 17 ++--------------- pkg/tools/types.go | 1 - 6 files changed, 7 insertions(+), 23 deletions(-) diff --git a/.replicated.yaml b/.replicated.yaml index 190287dc3..34e2d70c8 100644 --- a/.replicated.yaml +++ b/.replicated.yaml @@ -1,4 +1,3 @@ -profile: "prod" appId: "" appSlug: "" promoteToChannelIds: [] diff --git a/cli/cmd/profile.go b/cli/cmd/profile.go index 2c60b3f4d..f4df92592 100644 --- a/cli/cmd/profile.go +++ b/cli/cmd/profile.go @@ -15,11 +15,10 @@ This is useful when working with different Replicated accounts (production, deve or different API endpoints. Credentials are stored in ~/.replicated/config.yaml with file permissions set to 600 (owner read/write only). -You can reference profiles in your .replicated.yaml files using the 'profile' field. Authentication priority: 1. REPLICATED_API_TOKEN environment variable (highest priority) -2. Profile specified in .replicated.yaml +2. --profile flag (per-command override) 3. Default profile from ~/.replicated/config.yaml 4. Legacy single token (backward compatibility) diff --git a/cli/cmd/profile_set_default.go b/cli/cmd/profile_set_default.go index dd64e5c3a..ed00064b6 100644 --- a/cli/cmd/profile_set_default.go +++ b/cli/cmd/profile_set_default.go @@ -12,8 +12,8 @@ func (r *runners) InitProfileSetDefaultCommand(parent *cobra.Command) *cobra.Com cmd := &cobra.Command{ Use: "set-default [profile-name]", Short: "Set the default authentication profile", - Long: `Set the default authentication profile that will be used when no profile is specified -in .replicated.yaml and no environment variables are set.`, + Long: `Set the default authentication profile that will be used when no --profile flag is specified +and no environment variables are set.`, Example: `# Set production as the default profile replicated profile set-default prod`, Args: cobra.ExactArgs(1), diff --git a/cli/cmd/profile_use.go b/cli/cmd/profile_use.go index f1ebb6d20..800b72c4a 100644 --- a/cli/cmd/profile_use.go +++ b/cli/cmd/profile_use.go @@ -12,8 +12,8 @@ func (r *runners) InitProfileUseCommand(parent *cobra.Command) *cobra.Command { cmd := &cobra.Command{ Use: "use [profile-name]", Short: "Set the default authentication profile", - Long: `Set the default authentication profile that will be used when no profile is specified -in .replicated.yaml and no environment variables are set.`, + Long: `Set the default authentication profile that will be used when no --profile flag is specified +and no environment variables are set.`, Example: `# Use production as the default profile replicated profile use prod`, Args: cobra.ExactArgs(1), diff --git a/cli/cmd/root.go b/cli/cmd/root.go index c5bb4fc25..6d7182896 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -15,7 +15,6 @@ import ( "github.com/replicatedhq/replicated/pkg/credentials" "github.com/replicatedhq/replicated/pkg/kotsclient" "github.com/replicatedhq/replicated/pkg/platformclient" - "github.com/replicatedhq/replicated/pkg/tools" "github.com/replicatedhq/replicated/pkg/types" "github.com/replicatedhq/replicated/pkg/version" "github.com/spf13/cobra" @@ -317,7 +316,7 @@ func Execute(rootCmd *cobra.Command, stdin io.Reader, stdout io.Writer, stderr i preRunSetupAPIs := func(cmd *cobra.Command, args []string) error { if apiToken == "" { - // Try to load profile from --profile flag, then .replicated.yaml + // Try to load profile from --profile flag, then default profile var profileName string var profileSource string if profileNameFlag != "" { @@ -325,19 +324,7 @@ func Execute(rootCmd *cobra.Command, stdin io.Reader, stdout io.Writer, stderr i profileName = profileNameFlag profileSource = "--profile flag" } else { - // Fall back to profile from .replicated.yaml - configParser := tools.NewConfigParser() - config, err := configParser.FindAndParseConfig("") - if err == nil && config.Profile != "" { - profileName = config.Profile - profileSource = ".replicated.yaml" - } else { - profileSource = "default profile" - } - } - - // If no profile name yet, check if there's a default profile - if profileName == "" { + // Fall back to default profile from ~/.replicated/config.yaml defaultProfileName, err := credentials.GetDefaultProfile() if err == nil && defaultProfileName != "" { profileName = defaultProfileName diff --git a/pkg/tools/types.go b/pkg/tools/types.go index 90124c849..1e929c99d 100644 --- a/pkg/tools/types.go +++ b/pkg/tools/types.go @@ -10,7 +10,6 @@ type Config struct { Preflights []PreflightConfig `yaml:"preflights,omitempty"` ReleaseLabel string `yaml:"releaseLabel,omitempty"` Manifests []string `yaml:"manifests,omitempty"` - Profile string `yaml:"profile,omitempty"` ReplLint *ReplLintConfig `yaml:"repl-lint,omitempty"` }