Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions cli/cmd/logout.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package cmd

import (
"fmt"
"os"

"github.com/pkg/errors"
replicatedcache "github.com/replicatedhq/replicated/pkg/cache"
"github.com/replicatedhq/replicated/pkg/credentials"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -34,5 +38,11 @@ func (r *runners) logout(_ *cobra.Command, _ []string) error {
return err
}

// Clear all cache files on logout
if err = replicatedcache.DeleteAllCacheFiles(); err != nil {
// Don't fail logout if cache clear fails, just warn
fmt.Fprintf(os.Stderr, "Warning: failed to clear cache: %v\n", err)
}

return nil
}
25 changes: 11 additions & 14 deletions cli/cmd/profile_add.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,24 @@ Optionally, you can specify custom API and registry origins.
If a profile with the same name already exists, it will be updated.

The profile will be stored in ~/.replicated/config.yaml with file permissions 600 (owner read/write only).`,
Example: `# Add a production profile (will prompt for token)
replicated profile add prod
Example: `# Add a team profile (will prompt for token)
replicated profile add my-team

# Add a production profile with token flag
replicated profile add prod --token=your-prod-token

# Add a development profile with custom origins
replicated profile add dev \
--token=your-dev-token \
--api-origin=https://vendor-api-noahecampbell.okteto.repldev.com \
--registry-origin=vendor-registry-v2-noahecampbell.okteto.repldev.com`,
# Add a team profile with token flag
replicated profile add my-team --token=your-token`,
Args: cobra.ExactArgs(1),
SilenceUsage: true,
RunE: r.profileAdd,
}
parent.AddCommand(cmd)

cmd.Flags().StringVar(&r.args.profileAddToken, "token", "", "API token for this profile (optional, will prompt if not provided)")
cmd.Flags().StringVar(&r.args.profileAddAPIOrigin, "api-origin", "", "API origin (optional, e.g., https://api.replicated.com/vendor). Mutually exclusive with --namespace")
cmd.Flags().StringVar(&r.args.profileAddRegistryOrigin, "registry-origin", "", "Registry origin (optional, e.g., registry.replicated.com). Mutually exclusive with --namespace")
cmd.Flags().StringVar(&r.args.profileAddNamespace, "namespace", "", "Okteto namespace for dev environments (e.g., 'noahecampbell'). Auto-generates service URLs. Mutually exclusive with --api-origin and --registry-origin")
cmd.Flags().StringVar(&r.args.profileAddAPIOrigin, "api-origin", "", "API origin (optional, e.g., https://api.replicated.com/vendor).")
cmd.Flags().StringVar(&r.args.profileAddRegistryOrigin, "registry-origin", "", "Registry origin (optional, e.g., registry.replicated.com).")
cmd.Flags().StringVar(&r.args.profileAddOktetoNamespace, "okteto-namespace", "", "Okteto namespace for dev environments (e.g., 'your-namespace'). Auto-generates service URLs. Mutually exclusive with --api-origin and --registry-origin")

// "okteto-namespace" is hidden, it's just for replicated users
cmd.Flags().MarkHidden("okteto-namespace")

return cmd
}
Expand Down Expand Up @@ -76,7 +73,7 @@ func (r *runners) profileAdd(cmd *cobra.Command, args []string) error {
APIToken: token,
APIOrigin: r.args.profileAddAPIOrigin,
RegistryOrigin: r.args.profileAddRegistryOrigin,
Namespace: r.args.profileAddNamespace,
Namespace: r.args.profileAddOktetoNamespace,
}

if err := credentials.AddProfile(profileName, profile); err != nil {
Expand Down
8 changes: 8 additions & 0 deletions cli/cmd/profile_rm.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package cmd

import (
"fmt"
"os"

"github.com/pkg/errors"
replicatedcache "github.com/replicatedhq/replicated/pkg/cache"
"github.com/replicatedhq/replicated/pkg/credentials"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -43,6 +45,12 @@ func (r *runners) profileRm(_ *cobra.Command, args []string) error {
return errors.Wrap(err, "failed to get profile")
}

// Remove the profile's cache file before removing the profile
if err := replicatedcache.DeleteCacheFile(profileName); err != nil {
// Don't fail profile removal if cache delete fails, just warn
fmt.Fprintf(os.Stderr, "Warning: failed to delete cache for profile '%s': %v\n", profileName, err)
}

// Remove the profile
if err := credentials.RemoveProfile(profileName); err != nil {
return errors.Wrap(err, "failed to remove profile")
Expand Down
47 changes: 30 additions & 17 deletions cli/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,8 @@ func init() {
platformOrigin = originFromEnv
}

c, err := replicatedcache.InitCache()
if err != nil {
panic(err)
}
cache = c
// Note: Cache initialization is now deferred until we know which profile to use
// See initCacheForProfile() in preRunSetupAPIs

// Set debug mode from environment variable
if os.Getenv("REPLICATED_DEBUG") == "1" || os.Getenv("REPLICATED_DEBUG") == "true" {
Expand All @@ -57,6 +54,16 @@ func init() {
}
}

// initCacheForProfile initializes the cache for the given profile name
func initCacheForProfile(profileName string) error {
c, err := replicatedcache.InitCache(profileName)
if err != nil {
return errors.Wrap(err, "failed to initialize cache")
}
cache = c
return nil
}

// RootCmd represents the base command when called without any subcommands
func GetRootCmd() *cobra.Command {
rootCmd := &cobra.Command{
Expand Down Expand Up @@ -312,19 +319,25 @@ func Execute(rootCmd *cobra.Command, stdin io.Reader, stdout io.Writer, stderr i
runCmds.rootCmd.SetUsageTemplate(rootCmdUsageTmpl)

preRunSetupAPIs := func(cmd *cobra.Command, args []string) error {
if apiToken == "" {
// Try to load profile from --profile flag, then default profile
var profileName string
if profileNameFlag != "" {
// Command-line flag takes precedence
profileName = profileNameFlag
} else {
// Fall back to default profile from ~/.replicated/config.yaml
defaultProfileName, err := credentials.GetDefaultProfile()
if err == nil && defaultProfileName != "" {
profileName = defaultProfileName
}
// Determine profile name first
var profileName string
if profileNameFlag != "" {
// Command-line flag takes precedence
profileName = profileNameFlag
} else {
// Fall back to default profile from ~/.replicated/config.yaml
defaultProfileName, err := credentials.GetDefaultProfile()
if err == nil && defaultProfileName != "" {
profileName = defaultProfileName
}
}

// Initialize cache for this profile
if err := initCacheForProfile(profileName); err != nil {
return errors.Wrap(err, "initialize cache")
}

if apiToken == "" {
// Get credentials with profile support
creds, err := credentials.GetCredentialsWithProfile(profileName)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cli/cmd/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ type runnerArgs struct {
profileAddToken string
profileAddAPIOrigin string
profileAddRegistryOrigin string
profileAddNamespace string
profileAddOktetoNamespace string
profileEditToken string
profileEditAPIOrigin string
profileEditRegistryOrigin string
Expand Down
84 changes: 80 additions & 4 deletions pkg/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,31 @@ import (
"github.com/replicatedhq/replicated/pkg/types"
)

const cacheFileName = "replicated.cache"
const (
legacyCacheFileName = "replicated.cache"
cacheFileNameFormat = "replicated-%s.cache" // profile-specific cache
)

type Cache struct {
DefaultApp string `json:"default_app"`

LastAppRefresh *time.Time `json:"last_app_refresh"`
Apps []types.App `json:"apps"`

// profileName is the profile this cache is associated with
// Empty string means legacy/no profile
profileName string
}

func InitCache() (*Cache, error) {
// InitCache creates or loads a cache for the given profile name.
// If profileName is empty, uses legacy cache file.
func InitCache(profileName string) (*Cache, error) {
cacheDir, err := getCacheDir()
if err != nil {
return nil, errors.Wrap(err, "failed to get cache directory")
}

cacheFilePath := filepath.Join(cacheDir, cacheFileName)
cacheFilePath := getCacheFilePath(cacheDir, profileName)

// Try to load existing cache
cache, err := loadCache(cacheFilePath)
Expand All @@ -36,6 +45,7 @@ func InitCache() (*Cache, error) {
Apps: []types.App{},
LastAppRefresh: nil,
DefaultApp: "",
profileName: profileName,
}

// save it
Expand All @@ -46,18 +56,29 @@ func InitCache() (*Cache, error) {
} else {
return nil, errors.Wrap(err, "failed to load cache")
}
} else {
// Set the profile name on loaded cache
cache.profileName = profileName
}

return cache, nil
}

// getCacheFilePath returns the cache file path for the given profile
func getCacheFilePath(cacheDir, profileName string) string {
if profileName == "" {
return filepath.Join(cacheDir, legacyCacheFileName)
}
return filepath.Join(cacheDir, filepath.Clean(profileName)+".cache")
}

func (c *Cache) Save() error {
cacheDir, err := getCacheDir()
if err != nil {
return errors.Wrap(err, "failed to get cache directory")
}

cacheFilePath := filepath.Join(cacheDir, cacheFileName)
cacheFilePath := getCacheFilePath(cacheDir, c.profileName)

data, err := json.Marshal(c)
if err != nil {
Expand Down Expand Up @@ -138,3 +159,58 @@ func (c *Cache) ClearDefault(defaultType string) error {

return nil
}

// ClearAll removes all cached data from the current cache
func (c *Cache) ClearAll() error {
c.Apps = []types.App{}
c.DefaultApp = ""
c.LastAppRefresh = nil
return c.Save()
}

// DeleteCacheFile deletes the cache file for a specific profile
func DeleteCacheFile(profileName string) error {
cacheDir, err := getCacheDir()
if err != nil {
return errors.Wrap(err, "failed to get cache directory")
}

cacheFilePath := getCacheFilePath(cacheDir, profileName)

// It's not an error if the file doesn't exist
if err := os.Remove(cacheFilePath); err != nil && !os.IsNotExist(err) {
return errors.Wrap(err, "failed to delete cache file")
}

return nil
}

// DeleteAllCacheFiles removes all cache files (for logout)
func DeleteAllCacheFiles() error {
cacheDir, err := getCacheDir()
if err != nil {
return errors.Wrap(err, "failed to get cache directory")
}

// Read all files in cache directory
files, err := os.ReadDir(cacheDir)
if err != nil {
if os.IsNotExist(err) {
return nil // Cache directory doesn't exist, nothing to delete
}
return errors.Wrap(err, "failed to read cache directory")
}

// Delete all .cache files
for _, file := range files {
if !file.IsDir() && filepath.Ext(file.Name()) == ".cache" {
filePath := filepath.Join(cacheDir, file.Name())
if err := os.Remove(filePath); err != nil && !os.IsNotExist(err) {
// Log but don't fail on individual file deletion errors
continue
}
}
}

return nil
}
Loading