From 1fefbdeef905e5a7efe51b25d9e20b0c6946850b Mon Sep 17 00:00:00 2001 From: Youngjin Jo Date: Tue, 3 Dec 2024 12:44:16 +0900 Subject: [PATCH 01/12] refactor: remove cache config.yaml Signed-off-by: Youngjin Jo --- cmd/other/setting.go | 119 ++++++++----------------------------------- cmd/root.go | 32 ++++++------ 2 files changed, 37 insertions(+), 114 deletions(-) diff --git a/cmd/other/setting.go b/cmd/other/setting.go index 67719d7..8cf9277 100644 --- a/cmd/other/setting.go +++ b/cmd/other/setting.go @@ -30,8 +30,8 @@ import ( // SettingCmd represents the setting command var SettingCmd = &cobra.Command{ Use: "setting", - Short: "Manage cfctl setting files", - Long: `Manage setting files for cfctl. You can initialize, + Short: "Manage cfctl setting file", + Long: `Manage setting file for cfctl. You can initialize, switch environments, and display the current configuration.`, } @@ -90,27 +90,20 @@ var settingInitURLCmd = &cobra.Command{ } } - // Initialize the environment - if appFlag { - envName = fmt.Sprintf("%s-app", envName) - } else { - envName = fmt.Sprintf("%s-user", envName) - } - - // Update configuration + // Update configuration in main setting file updateSetting(envName, urlStr, map[bool]string{true: "app", false: "user"}[appFlag]) - // Update the current environment in the main setting + // Update the current environment mainV := viper.New() mainV.SetConfigFile(mainSettingPath) - // Create empty setting if it doesn't exist if err := mainV.ReadInConfig(); err != nil && !os.IsNotExist(err) { pterm.Error.Printf("Failed to read setting file: %v\n", err) return } - // Set the new environment as current + // Set the environment name with app/user suffix + envName = fmt.Sprintf("%s-%s", envName, map[bool]string{true: "app", false: "user"}[appFlag]) mainV.Set("environment", envName) if err := mainV.WriteConfig(); err != nil { @@ -369,7 +362,7 @@ var envCmd = &cobra.Command{ } if len(allEnvs) == 0 { - pterm.Println("No environments found in setting files") + pterm.Println("No environments found in setting file") return } @@ -790,40 +783,19 @@ func getBaseURL(v *viper.Viper) (string, error) { // getToken retrieves the token for the current environment. func getToken(v *viper.Viper) (string, error) { - home, _ := os.UserHomeDir() currentEnv := getCurrentEnvironment(v) if currentEnv == "" { return "", fmt.Errorf("no environment is set") } - // Check if the environment is app or user type - if strings.HasSuffix(currentEnv, "-app") { - // For app environments, check only in main setting - token := v.GetString(fmt.Sprintf("environments.%s.token", currentEnv)) - if token == "" { - return "", fmt.Errorf("no token found for app environment '%s' in %s/.cfctl/setting.yaml", currentEnv, home) - } - return token, nil - } else if strings.HasSuffix(currentEnv, "-user") { - // For user environments, check only in cache setting - cacheV := viper.New() - cachePath := filepath.Join(GetSettingDir(), "cache", "setting.yaml") - - if err := loadSetting(cacheV, cachePath); err != nil { - return "", fmt.Errorf("failed to load cache setting: %v", err) - } - - token := cacheV.GetString(fmt.Sprintf("environments.%s.token", currentEnv)) - if token == "" { - return "", fmt.Errorf("no token found for user environment '%s' in %s", currentEnv, cachePath) - } - return token, nil + token := v.GetString(fmt.Sprintf("environments.%s.token", currentEnv)) + if token == "" { + return "", fmt.Errorf("no token found for environment '%s'", currentEnv) } - - return "", fmt.Errorf("environment '%s' has invalid suffix (must end with -app or -user)", currentEnv) + return token, nil } -// GetSettingDir returns the directory where setting files are stored +// GetSettingDir returns the directory where setting file are stored func GetSettingDir() string { home, err := os.UserHomeDir() if err != nil { @@ -947,7 +919,7 @@ func parseEnvNameFromURL(urlStr string) (string, error) { return "", fmt.Errorf("URL does not match any known environment patterns") } -// updateSetting updates the configuration files based on the environment type +// updateSetting updates the configuration files func updateSetting(envName, urlStr, settingType string) { settingDir := GetSettingDir() mainSettingPath := filepath.Join(settingDir, "setting.yaml") @@ -963,14 +935,16 @@ func updateSetting(envName, urlStr, settingType string) { } } - // Handle app type settinguration - if settingType == "app" && urlStr != "" { + if urlStr != "" { endpoint, err := constructEndpoint(urlStr) if err != nil { pterm.Error.Printf("Failed to construct endpoint: %v\n", err) return } + // Append -app or -user to the environment name + envName = fmt.Sprintf("%s-%s", envName, settingType) + mainV.Set(fmt.Sprintf("environments.%s.endpoint", envName), endpoint) mainV.Set(fmt.Sprintf("environments.%s.proxy", envName), true) mainV.Set(fmt.Sprintf("environments.%s.token", envName), "") @@ -981,53 +955,6 @@ func updateSetting(envName, urlStr, settingType string) { } } - // Handle user type configuration - if settingType == "user" { - cacheDir := filepath.Join(settingDir, "cache") - if err := os.MkdirAll(cacheDir, 0755); err != nil { - pterm.Error.Printf("Failed to create cache directory: %v\n", err) - return - } - - cacheSettingPath := filepath.Join(cacheDir, "setting.yaml") - - // Create cache setting file if it doesn't exist - if _, err := os.Stat(cacheSettingPath); os.IsNotExist(err) { - initialSetting := []byte("environments:\n") - if err := os.WriteFile(cacheSettingPath, initialSetting, 0644); err != nil { - pterm.Error.Printf("Failed to create cache setting file: %v\n", err) - return - } - } - - cacheV := viper.New() - cacheV.SetConfigFile(cacheSettingPath) - - if err := cacheV.ReadInConfig(); err != nil { - if !os.IsNotExist(err) { - pterm.Error.Printf("Error reading cache setting: %v\n", err) - return - } - } - - if urlStr != "" { - endpoint, err := constructEndpoint(urlStr) - if err != nil { - pterm.Error.Printf("Failed to construct endpoint: %v\n", err) - return - } - - cacheV.Set(fmt.Sprintf("environments.%s.endpoint", envName), endpoint) - cacheV.Set(fmt.Sprintf("environments.%s.proxy", envName), true) - cacheV.Set(fmt.Sprintf("environments.%s.token", envName), "") - } - - if err := cacheV.WriteConfig(); err != nil { - pterm.Error.Printf("Failed to write cache setting: %v\n", err) - return - } - } - pterm.Success.Printf("Environment '%s' successfully initialized.\n", envName) } @@ -1098,10 +1025,6 @@ func constructEndpoint(baseURL string) (string, error) { } func init() { - settingDir := GetSettingDir() - settingPath := filepath.Join(settingDir, "setting.yaml") - cacheSettingPath := filepath.Join(settingDir, "cache", "setting.yaml") - SettingCmd.AddCommand(settingInitCmd) SettingCmd.AddCommand(envCmd) SettingCmd.AddCommand(showCmd) @@ -1112,12 +1035,12 @@ func init() { settingInitCmd.Flags().StringP("environment", "e", "", "Override environment name") settingInitURLCmd.Flags().StringP("url", "u", "", "URL for the environment") - settingInitURLCmd.Flags().Bool("app", false, fmt.Sprintf("Initialize as application settinguration (setting stored at %s)", settingPath)) - settingInitURLCmd.Flags().Bool("user", false, fmt.Sprintf("Initialize as user-specific settinguration (setting stored at %s)", cacheSettingPath)) + settingInitURLCmd.Flags().Bool("app", false, "Initialize as application configuration") + settingInitURLCmd.Flags().Bool("user", false, "Initialize as user-specific configuration") settingInitLocalCmd.Flags().StringP("name", "n", "", "Local environment name for the environment") - settingInitLocalCmd.Flags().Bool("app", false, fmt.Sprintf("Initialize as application settinguration (setting stored at %s)", settingPath)) - settingInitLocalCmd.Flags().Bool("user", false, fmt.Sprintf("Initialize as user-specific settinguration (setting stored at %s)", cacheSettingPath)) + settingInitLocalCmd.Flags().Bool("app", false, "Initialize as application configuration") + settingInitLocalCmd.Flags().Bool("user", false, "Initialize as user-specific configuration") envCmd.Flags().StringP("switch", "s", "", "Switch to a different environment") envCmd.Flags().StringP("remove", "r", "", "Remove an environment") diff --git a/cmd/root.go b/cmd/root.go index c2788bf..0263e43 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -92,14 +92,14 @@ func init() { // showInitializationGuide displays a helpful message when configuration is missing func showInitializationGuide(originalErr error) { // Only show error message for commands that require configuration - if len(os.Args) >= 2 && (os.Args[1] == "config" || + if len(os.Args) >= 2 && (os.Args[1] == "setting" || os.Args[1] == "login" || os.Args[1] == "api-resources") { // Add api-resources to skip list return } pterm.Warning.Printf("No valid configuration found.\n") - pterm.Info.Println("Please run 'cfctl config init' to set up your configuration.") + pterm.Info.Println("Please run 'cfctl setting init' to set up your configuration.") pterm.Info.Println("After initialization, run 'cfctl login' to authenticate.") } @@ -118,7 +118,7 @@ func addDynamicServiceCommands() error { if err == nil { // Store in memory for subsequent calls cachedEndpointsMap = endpoints - + // Create commands using cached endpoints for serviceName := range endpoints { cmd := createServiceCommand(serviceName) @@ -128,12 +128,12 @@ func addDynamicServiceCommands() error { } // If no cache available, fetch dynamically (this is slow path) - config, err := loadConfig() + setting, err := loadConfig() if err != nil { - return fmt.Errorf("failed to load config: %v", err) + return fmt.Errorf("failed to load setting: %v", err) } - endpoint := config.Endpoint + endpoint := setting.Endpoint if !strings.Contains(endpoint, "identity") { parts := strings.Split(endpoint, "://") if len(parts) == 2 { @@ -219,30 +219,30 @@ func saveEndpointsCache(endpoints map[string]string) error { return os.WriteFile(filepath.Join(cacheDir, "endpoints.yaml"), data, 0644) } -// loadConfig loads configuration from both main and cache config files +// loadConfig loads configuration from both main and cache setting files func loadConfig() (*Config, error) { home, err := os.UserHomeDir() if err != nil { return nil, fmt.Errorf("unable to find home directory: %v", err) } - configFile := filepath.Join(home, ".cfctl", "config.yaml") - cacheConfigFile := filepath.Join(home, ".cfctl", "cache", "config.yaml") + settingFile := filepath.Join(home, ".cfctl", "setting.yaml") + cacheConfigFile := filepath.Join(home, ".cfctl", "cache", "setting.yaml") - // Try to read main config first + // Try to read main setting first mainV := viper.New() - mainV.SetConfigFile(configFile) + mainV.SetConfigFile(settingFile) mainConfigErr := mainV.ReadInConfig() if mainConfigErr != nil { - return nil, fmt.Errorf("failed to read config file") + return nil, fmt.Errorf("failed to read setting file") } var currentEnv string var endpoint string var token string - // Main config exists, try to get environment + // Main setting exists, try to get environment currentEnv = mainV.GetString("environment") if currentEnv != "" { envConfig := mainV.Sub(fmt.Sprintf("environments.%s", currentEnv)) @@ -252,18 +252,18 @@ func loadConfig() (*Config, error) { } } - // If main config doesn't have what we need, try cache config + // If main setting doesn't have what we need, try cache setting if endpoint == "" || token == "" { cacheV := viper.New() cacheV.SetConfigFile(cacheConfigFile) if err := cacheV.ReadInConfig(); err == nil { - // If no current environment set, try to get it from cache config + // If no current environment set, try to get it from cache setting if currentEnv == "" { currentEnv = cacheV.GetString("environment") } - // Try to get environment config from cache + // Try to get environment setting from cache if currentEnv != "" { envConfig := cacheV.Sub(fmt.Sprintf("environments.%s", currentEnv)) if envConfig != nil { From 8f5d6cd15adeeedc452d4ef3c411b993815889e7 Mon Sep 17 00:00:00 2001 From: Youngjin Jo Date: Tue, 3 Dec 2024 13:53:37 +0900 Subject: [PATCH 02/12] feat: add endpoints to the specific environment of cache directory Signed-off-by: Youngjin Jo --- cmd/other/setting.go | 229 ++++++++++++++++++++++++++++--------------- cmd/root.go | 181 +++++++++++++++++++--------------- go.mod | 3 +- go.sum | 2 + 4 files changed, 258 insertions(+), 157 deletions(-) diff --git a/cmd/other/setting.go b/cmd/other/setting.go index 8cf9277..42de2cf 100644 --- a/cmd/other/setting.go +++ b/cmd/other/setting.go @@ -1,5 +1,3 @@ -// config.go - package other import ( @@ -21,10 +19,10 @@ import ( "google.golang.org/grpc/credentials" "google.golang.org/grpc/reflection/grpc_reflection_v1alpha" + "github.com/pelletier/go-toml/v2" "github.com/pterm/pterm" "github.com/spf13/cobra" "github.com/spf13/viper" - "gopkg.in/yaml.v2" ) // SettingCmd represents the setting command @@ -80,10 +78,11 @@ var settingInitURLCmd = &cobra.Command{ return } - // Initialize setting.yaml if it doesn't exist - mainSettingPath := filepath.Join(settingDir, "setting.yaml") + // Initialize setting.toml if it doesn't exist + mainSettingPath := filepath.Join(settingDir, "setting.toml") if _, err := os.Stat(mainSettingPath); os.IsNotExist(err) { - initialSetting := []byte("environments:\n") + // Initial TOML structure + initialSetting := []byte("environments = {}\n") if err := os.WriteFile(mainSettingPath, initialSetting, 0644); err != nil { pterm.Error.Printf("Failed to create setting file: %v\n", err) return @@ -96,6 +95,7 @@ var settingInitURLCmd = &cobra.Command{ // Update the current environment mainV := viper.New() mainV.SetConfigFile(mainSettingPath) + mainV.SetConfigType("toml") if err := mainV.ReadInConfig(); err != nil && !os.IsNotExist(err) { pterm.Error.Printf("Failed to read setting file: %v\n", err) @@ -147,10 +147,11 @@ var settingInitLocalCmd = &cobra.Command{ return } - // Initialize setting.yaml if it doesn't exist - mainSettingPath := filepath.Join(settingDir, "setting.yaml") + // Initialize setting.toml if it doesn't exist + mainSettingPath := filepath.Join(settingDir, "setting.toml") if _, err := os.Stat(mainSettingPath); os.IsNotExist(err) { - initialSetting := []byte("environments:\n") + // Initial TOML structure + initialSetting := []byte("environments = {}\n") if err := os.WriteFile(mainSettingPath, initialSetting, 0644); err != nil { pterm.Error.Printf("Failed to create setting file: %v\n", err) return @@ -163,12 +164,13 @@ var settingInitLocalCmd = &cobra.Command{ updateLocalSetting(envName, "app", mainSettingPath) } else { envName = fmt.Sprintf("%s-user", localEnv) - updateLocalSetting(envName, "user", filepath.Join(settingDir, "cache", "setting.yaml")) + updateLocalSetting(envName, "user", filepath.Join(settingDir, "cache", "setting.toml")) } // Update the current environment in the main setting mainV := viper.New() mainV.SetConfigFile(mainSettingPath) + mainV.SetConfigType("toml") // Read the setting file if err := mainV.ReadInConfig(); err != nil { @@ -223,8 +225,8 @@ var envCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { // Set paths for app and user configurations settingDir := GetSettingDir() - appSettingPath := filepath.Join(settingDir, "setting.yaml") - userSettingPath := filepath.Join(settingDir, "cache", "setting.yaml") + appSettingPath := filepath.Join(settingDir, "setting.toml") + userSettingPath := filepath.Join(settingDir, "cache", "setting.toml") // Create separate Viper instances appV := viper.New() @@ -263,8 +265,8 @@ var envCmd = &cobra.Command{ if _, existsApp := appEnvMap[switchEnv]; !existsApp { if _, existsUser := userEnvMap[switchEnv]; !existsUser { home, _ := os.UserHomeDir() - pterm.Error.Printf("Environment '%s' not found in either %s/.cfctl/setting.yaml or %s/.cfctl/cache/setting.yaml\n", - switchEnv, home, home) + pterm.Error.Printf("Environment '%s' not found in %s/.cfctl/setting.toml", + switchEnv, home) return } } @@ -273,7 +275,7 @@ var envCmd = &cobra.Command{ appV.Set("environment", switchEnv) if err := appV.WriteConfig(); err != nil { - pterm.Error.Printf("Failed to update environment in setting.yaml: %v", err) + pterm.Error.Printf("Failed to update environment in setting.toml: %v", err) return } @@ -298,8 +300,8 @@ var envCmd = &cobra.Command{ targetSettingPath = userSettingPath } else { home, _ := os.UserHomeDir() - pterm.Error.Printf("Environment '%s' not found in either %s/.cfctl/setting.yaml or %s/.cfctl/cache/setting.yaml\n", - removeEnv, home, home) + pterm.Error.Printf("Environment '%s' not found in %s/.cfctl/setting.toml", + switchEnv, home) return } @@ -325,10 +327,10 @@ var envCmd = &cobra.Command{ if currentEnv == removeEnv { appV.Set("environment", "") if err := appV.WriteConfig(); err != nil { - pterm.Error.Printf("Failed to update environment in setting.yaml: %v", err) + pterm.Error.Printf("Failed to update environment in setting.toml: %v", err) return } - pterm.Info.WithShowLineNumber(false).Println("Cleared current environment in setting.yaml") + pterm.Info.WithShowLineNumber(false).Println("Cleared current environment in setting.toml") } // Display success message @@ -394,8 +396,8 @@ var showCmd = &cobra.Command{ Short: "Display the current cfctl configuration", Run: func(cmd *cobra.Command, args []string) { settingDir := GetSettingDir() - appSettingPath := filepath.Join(settingDir, "setting.yaml") - userSettingPath := filepath.Join(settingDir, "cache", "setting.yaml") + appSettingPath := filepath.Join(settingDir, "setting.toml") + userSettingPath := filepath.Join(settingDir, "cache", "setting.toml") // Create separate Viper instances appV := viper.New() @@ -440,10 +442,10 @@ var showCmd = &cobra.Command{ log.Fatalf("Error formatting output as JSON: %v", err) } fmt.Println(string(data)) - case "yaml": - data, err := yaml.Marshal(envSetting) + case "toml": + data, err := toml.Marshal(envSetting) if err != nil { - log.Fatalf("Error formatting output as YAML: %v", err) + log.Fatalf("Error formatting output as TOML: %v", err) } fmt.Println(string(data)) default: @@ -467,7 +469,10 @@ Available Services are fetched dynamically from the backend.`, appV := viper.New() // Load app configuration - settingPath := filepath.Join(GetSettingDir(), "setting.yaml") + settingPath := filepath.Join(GetSettingDir(), "setting.toml") + appV.SetConfigFile(settingPath) + appV.SetConfigType("toml") + if err := loadSetting(appV, settingPath); err != nil { pterm.Error.Println(err) return @@ -475,7 +480,62 @@ Available Services are fetched dynamically from the backend.`, token, err := getToken(appV) if err != nil { - pterm.Error.Println("Error retrieving token:", err) + currentEnv := getCurrentEnvironment(appV) + if strings.HasSuffix(currentEnv, "-app") { + // Parse environment name to extract service name and environment + parts := strings.Split(currentEnv, "-") + if len(parts) >= 3 { + envPrefix := parts[0] // dev, stg + serviceName := parts[1] // cloudone, spaceone, etc. + url := fmt.Sprintf("https://%s.console.%s.spaceone.dev", serviceName, envPrefix) + settingPath := filepath.Join(GetSettingDir(), "setting.toml") + + // Create header for the error message + //pterm.DefaultHeader.WithBackgroundStyle(pterm.NewStyle(pterm.BgRed)).WithMargin(10).Println("Token Not Found") + pterm.DefaultBox. + WithTitle("Token Not Found"). + WithTitleTopCenter(). + WithBoxStyle(pterm.NewStyle(pterm.FgWhite)). + WithRightPadding(1). + WithLeftPadding(1). + WithTopPadding(0). + WithBottomPadding(0). + Println("Please follow the instructions below to obtain an App Token.") + + // Create a styled box with instructions + boxContent := fmt.Sprintf(`Please follow these steps to obtain an App Token: + +1. Visit %s +2. Go to Admin page or Workspace page +3. Navigate to the App page +4. Click [Create] button +5. Copy the generated App Token +6. Update your settings: + Path: %s + Environment: %s + Field: "token"`, + pterm.FgLightCyan.Sprint(url), + pterm.FgLightYellow.Sprint(settingPath), + pterm.FgLightGreen.Sprint(currentEnv)) + + // Print the box with instructions + pterm.DefaultBox. + WithTitle("Setup Instructions"). + WithTitleTopCenter(). + WithBoxStyle(pterm.NewStyle(pterm.FgLightBlue)). + // WithTextAlignment(pterm.TextAlignLeft). + Println(boxContent) + + // Print additional help message + pterm.Info.Println("After updating the token, please try your command again.") + + return + } + } else if strings.HasSuffix(currentEnv, "-user") { + pterm.Error.Printf("No token found for environment '%s'. Please run 'cfctl login' to authenticate.\n", currentEnv) + } else { + pterm.Error.Println("Error retrieving token:", err) + } return } @@ -523,15 +583,16 @@ Available Services are fetched dynamically from the backend.`, cacheV := viper.New() // Load app configuration (for getting current environment) - settingPath := filepath.Join(GetSettingDir(), "setting.yaml") - if err := loadSetting(appV, settingPath); err != nil { - pterm.Error.Println(err) - return - } + settingPath := filepath.Join(GetSettingDir(), "setting.toml") + appV.SetConfigFile(settingPath) + appV.SetConfigType("toml") // Load cache configuration - cachePath := filepath.Join(GetSettingDir(), "cache", "setting.yaml") - if err := loadSetting(cacheV, cachePath); err != nil { + cachePath := filepath.Join(GetSettingDir(), "cache", "setting.toml") + cacheV.SetConfigFile(cachePath) + cacheV.SetConfigType("toml") + + if err := loadSetting(appV, settingPath); err != nil { pterm.Error.Println(err) return } @@ -567,12 +628,12 @@ Available Services are fetched dynamically from the backend.`, } if err := appV.WriteConfig(); err != nil { - pterm.Error.Printf("Failed to update setting.yaml: %v\n", err) + pterm.Error.Printf("Failed to update setting.toml: %v\n", err) return } } else { // Update endpoint in cache setting for user environments - cachePath := filepath.Join(GetSettingDir(), "cache", "setting.yaml") + cachePath := filepath.Join(GetSettingDir(), "cache", "setting.toml") if err := loadSetting(cacheV, cachePath); err != nil { pterm.Error.Println(err) return @@ -586,7 +647,7 @@ Available Services are fetched dynamically from the backend.`, } if err := cacheV.WriteConfig(); err != nil { - pterm.Error.Printf("Failed to update cache/setting.yaml: %v\n", err) + pterm.Error.Printf("Failed to update cache/setting.toml: %v\n", err) return } } @@ -764,18 +825,8 @@ func getBaseURL(v *viper.Viper) (string, error) { baseURL := v.GetString(fmt.Sprintf("environments.%s.endpoint", currentEnv)) if baseURL == "" { - cacheV := viper.New() - cachePath := filepath.Join(GetSettingDir(), "cache", "setting.yaml") + return "", fmt.Errorf("no endpoint found for environment '%s' in setting.toml", currentEnv) - if err := loadSetting(cacheV, cachePath); err != nil { - return "", fmt.Errorf("failed to load cache setting: %v", err) - } - - baseURL = cacheV.GetString(fmt.Sprintf("environments.%s.endpoint", currentEnv)) - } - - if baseURL == "" { - return "", fmt.Errorf("no endpoint found for environment '%s' in either setting.yaml or cache/setting.yaml", currentEnv) } return baseURL, nil @@ -815,38 +866,37 @@ func loadSetting(v *viper.Viper, settingPath string) error { // Set the setting file v.SetConfigFile(settingPath) + v.SetConfigType("toml") - // Set the setting type explicitly to YAML - v.SetConfigFile("yaml") - - // Check if the setting file exists - if _, err := os.Stat(settingPath); os.IsNotExist(err) { - // Initialize with default values - v.Set("environments", map[string]interface{}{}) - v.Set("environment", "") + // Read the setting file + if err := v.ReadInConfig(); err != nil { + if os.IsNotExist(err) { + // Initialize with default values if file doesn't exist + defaultSettings := map[string]interface{}{ + "environments": map[string]interface{}{}, + "environment": "", + } - // Convert to YAML with 2-space indentation - setting := map[string]interface{}{ - "environments": map[string]interface{}{}, - "environment": "", - } + // Convert to TOML + data, err := toml.Marshal(defaultSettings) + if err != nil { + return fmt.Errorf("failed to marshal default settings: %w", err) + } - data, err := yaml.Marshal(setting) - if err != nil { - return fmt.Errorf("failed to marshal setting: %w", err) - } + // Write the default settings to file + if err := os.WriteFile(settingPath, data, 0644); err != nil { + return fmt.Errorf("failed to write default settings: %w", err) + } - // Write the default setting to the file - if err := os.WriteFile(settingPath, data, 0644); err != nil { - return fmt.Errorf("failed to create setting file '%s': %w", settingPath, err) + // Read the newly created file + if err := v.ReadInConfig(); err != nil { + return fmt.Errorf("failed to read newly created setting file: %w", err) + } + } else { + return fmt.Errorf("failed to read setting file: %w", err) } } - // Read the setting file - if err := v.ReadInConfig(); err != nil { - return fmt.Errorf("failed to read setting file '%s': %w", settingPath, err) - } - return nil } @@ -857,21 +907,21 @@ func getCurrentEnvironment(v *viper.Viper) string { // updateGlobalSetting prints a success message for global setting update func updateGlobalSetting() { - settingPath := filepath.Join(GetSettingDir(), "setting.yaml") + settingPath := filepath.Join(GetSettingDir(), "setting.toml") v := viper.New() v.SetConfigFile(settingPath) if err := v.ReadInConfig(); err != nil { if os.IsNotExist(err) { - pterm.Success.WithShowLineNumber(false).Printfln("Global setting updated with existing environments. (default: %s/setting.yaml)", GetSettingDir()) + pterm.Success.WithShowLineNumber(false).Printfln("Global setting updated with existing environments. (default: %s/setting.toml)", GetSettingDir()) return } pterm.Warning.Printf("Warning: Could not read global setting: %v\n", err) return } - pterm.Success.WithShowLineNumber(false).Printfln("Global setting updated with existing environments. (default: %s/setting.yaml)", GetSettingDir()) + pterm.Success.WithShowLineNumber(false).Printfln("Global setting updated with existing environments. (default: %s/setting.toml)", GetSettingDir()) } // parseEnvNameFromURL parses environment name from the given URL and validates based on URL structure @@ -922,12 +972,14 @@ func parseEnvNameFromURL(urlStr string) (string, error) { // updateSetting updates the configuration files func updateSetting(envName, urlStr, settingType string) { settingDir := GetSettingDir() - mainSettingPath := filepath.Join(settingDir, "setting.yaml") + mainSettingPath := filepath.Join(settingDir, "setting.toml") // Initialize main viper instance mainV := viper.New() mainV.SetConfigFile(mainSettingPath) + mainV.SetConfigType("toml") + // Read existing configuration file if err := mainV.ReadInConfig(); err != nil { if !os.IsNotExist(err) { pterm.Error.Printf("Error reading setting file: %v\n", err) @@ -935,6 +987,11 @@ func updateSetting(envName, urlStr, settingType string) { } } + // Initialize environments if not exists + if !mainV.IsSet("environments") { + mainV.Set("environments", make(map[string]interface{})) + } + if urlStr != "" { endpoint, err := constructEndpoint(urlStr) if err != nil { @@ -945,10 +1002,24 @@ func updateSetting(envName, urlStr, settingType string) { // Append -app or -user to the environment name envName = fmt.Sprintf("%s-%s", envName, settingType) - mainV.Set(fmt.Sprintf("environments.%s.endpoint", envName), endpoint) - mainV.Set(fmt.Sprintf("environments.%s.proxy", envName), true) - mainV.Set(fmt.Sprintf("environments.%s.token", envName), "") + // Get environments map + environments := mainV.GetStringMap("environments") + if environments == nil { + environments = make(map[string]interface{}) + } + + // Add new environment configuration + environments[envName] = map[string]interface{}{ + "endpoint": endpoint, + "proxy": true, + "token": "", + } + + // Update entire configuration + mainV.Set("environments", environments) + mainV.Set("environment", envName) + // Save configuration file if err := mainV.WriteConfig(); err != nil { pterm.Error.Printf("Failed to write setting: %v\n", err) return diff --git a/cmd/root.go b/cmd/root.go index 0263e43..4db739d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -11,9 +11,9 @@ import ( "github.com/cloudforet-io/cfctl/cmd/common" "github.com/cloudforet-io/cfctl/cmd/other" + "github.com/BurntSushi/toml" "github.com/pterm/pterm" "github.com/spf13/cobra" - "gopkg.in/yaml.v3" ) var cfgFile string @@ -113,21 +113,47 @@ func addDynamicServiceCommands() error { return nil } - // Try to load endpoints from file cache - endpoints, err := loadCachedEndpoints() - if err == nil { - // Store in memory for subsequent calls - cachedEndpointsMap = endpoints + // Try to load endpoints from environment-specific cache + home, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("unable to find home directory: %v", err) + } - // Create commands using cached endpoints - for serviceName := range endpoints { - cmd := createServiceCommand(serviceName) - rootCmd.AddCommand(cmd) + // Get current environment from main setting file + mainV := viper.New() + mainV.SetConfigFile(filepath.Join(home, ".cfctl", "setting.toml")) + mainV.SetConfigType("toml") + if err := mainV.ReadInConfig(); err != nil { + return fmt.Errorf("failed to read setting file: %v", err) + } + + currentEnv := mainV.GetString("environment") + if currentEnv == "" { + return fmt.Errorf("no environment set") + } + + // Try to load endpoints from environment-specific cache + envCacheDir := filepath.Join(home, ".cfctl", "cache", currentEnv) + cacheFile := filepath.Join(envCacheDir, "endpoints.toml") + + data, err := os.ReadFile(cacheFile) + if err == nil { + // Parse cached endpoints from TOML + var endpoints map[string]string + if err := toml.Unmarshal(data, &endpoints); err == nil { + // Store in memory for subsequent calls + cachedEndpointsMap = endpoints + + // Create commands using cached endpoints + for serviceName := range endpoints { + cmd := createServiceCommand(serviceName) + rootCmd.AddCommand(cmd) + } + return nil } - return nil } - // If no cache available, fetch dynamically (this is slow path) + // If no cache available or cache is invalid, fetch dynamically (this is slow path) setting, err := loadConfig() if err != nil { return fmt.Errorf("failed to load setting: %v", err) @@ -150,7 +176,7 @@ func addDynamicServiceCommands() error { return fmt.Errorf("failed to fetch services: %v", err) } - // Store in both memory and file cache + // Store in both memory and environment-specific cache cachedEndpointsMap = endpointsMap if err := saveEndpointsCache(endpointsMap); err != nil { fmt.Fprintf(os.Stderr, "Warning: Failed to cache endpoints: %v\n", err) @@ -166,13 +192,27 @@ func addDynamicServiceCommands() error { } func clearEndpointsCache() { - cachedEndpointsMap = nil home, err := os.UserHomeDir() if err != nil { return } - cacheFile := filepath.Join(home, ".cfctl", "cache", "endpoints.yaml") - os.Remove(cacheFile) + + mainV := viper.New() + mainV.SetConfigFile(filepath.Join(home, ".cfctl", "setting.toml")) + mainV.SetConfigType("toml") + if err := mainV.ReadInConfig(); err != nil { + return + } + + currentEnv := mainV.GetString("environment") + if currentEnv == "" { + return + } + + // Remove environment-specific cache directory + envCacheDir := filepath.Join(home, ".cfctl", "cache", currentEnv) + os.RemoveAll(envCacheDir) + cachedEndpointsMap = nil } func loadCachedEndpoints() (map[string]string, error) { @@ -181,16 +221,35 @@ func loadCachedEndpoints() (map[string]string, error) { return nil, err } - // Read from cache file - cacheFile := filepath.Join(home, ".cfctl", "cache", "endpoints.yaml") + // Get current environment from main setting file + mainV := viper.New() + mainV.SetConfigFile(filepath.Join(home, ".cfctl", "setting.toml")) + mainV.SetConfigType("toml") + if err := mainV.ReadInConfig(); err != nil { + return nil, err + } + + currentEnv := mainV.GetString("environment") + if currentEnv == "" { + return nil, fmt.Errorf("no environment set") + } + + // Create environment-specific cache directory + envCacheDir := filepath.Join(home, ".cfctl", "cache", currentEnv) + if err := os.MkdirAll(envCacheDir, 0755); err != nil { + return nil, err + } + + // Read from environment-specific cache file + cacheFile := filepath.Join(envCacheDir, "endpoints.toml") data, err := os.ReadFile(cacheFile) if err != nil { return nil, err } - // Parse cached endpoints + // Parse cached endpoints from TOML var endpoints map[string]string - if err := yaml.Unmarshal(data, &endpoints); err != nil { + if err := toml.Unmarshal(data, &endpoints); err != nil { return nil, err } @@ -203,20 +262,33 @@ func saveEndpointsCache(endpoints map[string]string) error { return err } - // Ensure cache directory exists - cacheDir := filepath.Join(home, ".cfctl", "cache") - if err := os.MkdirAll(cacheDir, 0755); err != nil { + // Get current environment from main setting file + mainV := viper.New() + mainV.SetConfigFile(filepath.Join(home, ".cfctl", "setting.toml")) + mainV.SetConfigType("toml") + if err := mainV.ReadInConfig(); err != nil { + return err + } + + currentEnv := mainV.GetString("environment") + if currentEnv == "" { + return fmt.Errorf("no environment set") + } + + // Create environment-specific cache directory + envCacheDir := filepath.Join(home, ".cfctl", "cache", currentEnv) + if err := os.MkdirAll(envCacheDir, 0755); err != nil { return err } - // Marshal endpoints to YAML format - data, err := yaml.Marshal(endpoints) + // Marshal endpoints to TOML format + data, err := toml.Marshal(endpoints) if err != nil { return err } - // Write to cache file - return os.WriteFile(filepath.Join(cacheDir, "endpoints.yaml"), data, 0644) + // Write to environment-specific cache file + return os.WriteFile(filepath.Join(envCacheDir, "endpoints.toml"), data, 0644) } // loadConfig loads configuration from both main and cache setting files @@ -226,12 +298,13 @@ func loadConfig() (*Config, error) { return nil, fmt.Errorf("unable to find home directory: %v", err) } - settingFile := filepath.Join(home, ".cfctl", "setting.yaml") - cacheConfigFile := filepath.Join(home, ".cfctl", "cache", "setting.yaml") + // Only use main setting file + settingFile := filepath.Join(home, ".cfctl", "setting.toml") - // Try to read main setting first + // Try to read main setting mainV := viper.New() mainV.SetConfigFile(settingFile) + mainV.SetConfigType("toml") mainConfigErr := mainV.ReadInConfig() if mainConfigErr != nil { @@ -242,7 +315,7 @@ func loadConfig() (*Config, error) { var endpoint string var token string - // Main setting exists, try to get environment + // Get configuration from main setting only currentEnv = mainV.GetString("environment") if currentEnv != "" { envConfig := mainV.Sub(fmt.Sprintf("environments.%s", currentEnv)) @@ -252,52 +325,6 @@ func loadConfig() (*Config, error) { } } - // If main setting doesn't have what we need, try cache setting - if endpoint == "" || token == "" { - cacheV := viper.New() - - cacheV.SetConfigFile(cacheConfigFile) - if err := cacheV.ReadInConfig(); err == nil { - // If no current environment set, try to get it from cache setting - if currentEnv == "" { - currentEnv = cacheV.GetString("environment") - } - - // Try to get environment setting from cache - if currentEnv != "" { - envConfig := cacheV.Sub(fmt.Sprintf("environments.%s", currentEnv)) - if envConfig != nil { - if endpoint == "" { - endpoint = envConfig.GetString("endpoint") - } - if token == "" { - token = envConfig.GetString("token") - } - } - } - - // If still no environment, try to find first user environment - if currentEnv == "" { - envs := cacheV.GetStringMap("environments") - for env := range envs { - if strings.HasSuffix(env, "-user") { - currentEnv = env - envConfig := cacheV.Sub(fmt.Sprintf("environments.%s", currentEnv)) - if envConfig != nil { - if endpoint == "" { - endpoint = envConfig.GetString("endpoint") - } - if token == "" { - token = envConfig.GetString("token") - } - break - } - } - } - } - } - } - if endpoint == "" { return nil, fmt.Errorf("no endpoint found in configuration") } diff --git a/go.mod b/go.mod index 01cc449..c9d44a6 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,11 @@ go 1.23.1 require ( github.com/AlecAivazis/survey/v2 v2.3.7 + github.com/BurntSushi/toml v1.4.0 github.com/atotto/clipboard v0.1.4 github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 github.com/jhump/protoreflect v1.17.0 + github.com/pelletier/go-toml/v2 v2.2.2 github.com/pterm/pterm v0.12.79 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 @@ -39,7 +41,6 @@ require ( github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect diff --git a/go.sum b/go.sum index 4ffe438..d2bf245 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs= atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU= github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs= github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8= github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII= From 526af0db843566a8b3515d0730a3a59d0d58d040 Mon Sep 17 00:00:00 2001 From: Youngjin Jo Date: Tue, 3 Dec 2024 13:57:13 +0900 Subject: [PATCH 03/12] chore: remove setting.toml of cache directory Signed-off-by: Youngjin Jo --- cmd/root.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 4db739d..9904d66 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -234,16 +234,19 @@ func loadCachedEndpoints() (map[string]string, error) { return nil, fmt.Errorf("no environment set") } - // Create environment-specific cache directory + // Only create endpoints.toml in the environment-specific cache directory envCacheDir := filepath.Join(home, ".cfctl", "cache", currentEnv) - if err := os.MkdirAll(envCacheDir, 0755); err != nil { - return nil, err - } - - // Read from environment-specific cache file cacheFile := filepath.Join(envCacheDir, "endpoints.toml") + + // Read from environment-specific cache file data, err := os.ReadFile(cacheFile) if err != nil { + if !os.IsNotExist(err) { + // Create directory only if we need to write the cache file + if err := os.MkdirAll(envCacheDir, 0755); err != nil { + return nil, err + } + } return nil, err } From 5ffd20cb73c5bc6d5ae307c208ec3348f3d3bdc2 Mon Sep 17 00:00:00 2001 From: Youngjin Jo Date: Tue, 3 Dec 2024 14:08:30 +0900 Subject: [PATCH 04/12] refactor: update api-resources for toml Signed-off-by: Youngjin Jo --- cmd/other/apiResources.go | 116 ++++++++++++++------------------------ 1 file changed, 43 insertions(+), 73 deletions(-) diff --git a/cmd/other/apiResources.go b/cmd/other/apiResources.go index 9bc92af..1a8efdc 100644 --- a/cmd/other/apiResources.go +++ b/cmd/other/apiResources.go @@ -14,6 +14,7 @@ import ( "strings" "sync" + "github.com/BurntSushi/toml" "github.com/jhump/protoreflect/dynamic" "github.com/jhump/protoreflect/grpcreflect" @@ -32,6 +33,27 @@ import ( var endpoints string +func loadEndpointsFromCache(currentEnv string) (map[string]string, error) { + home, err := os.UserHomeDir() + if err != nil { + return nil, fmt.Errorf("unable to find home directory: %v", err) + } + + // Read from environment-specific cache file + cacheFile := filepath.Join(home, ".cfctl", "cache", currentEnv, "endpoints.toml") + data, err := os.ReadFile(cacheFile) + if err != nil { + return nil, err + } + + var endpoints map[string]string + if err := toml.Unmarshal(data, &endpoints); err != nil { + return nil, err + } + + return endpoints, nil +} + var ApiResourcesCmd = &cobra.Command{ Use: "api-resources", Short: "Displays supported API resources", @@ -41,93 +63,41 @@ var ApiResourcesCmd = &cobra.Command{ log.Fatalf("Unable to find home directory: %v", err) } - configFile := filepath.Join(home, ".cfctl", "config.yaml") - cacheConfigFile := filepath.Join(home, ".cfctl", "cache", "config.yaml") - - var currentEnv string - var envConfig *viper.Viper - - // Try to read main config first + settingPath := filepath.Join(home, ".cfctl", "setting.toml") + + // Read main setting file mainV := viper.New() - mainV.SetConfigFile(configFile) + mainV.SetConfigFile(settingPath) + mainV.SetConfigType("toml") mainConfigErr := mainV.ReadInConfig() + var currentEnv string + var envConfig map[string]interface{} + if mainConfigErr == nil { - // Main config exists, try to get environment currentEnv = mainV.GetString("environment") if currentEnv != "" { - envConfig = mainV.Sub(fmt.Sprintf("environments.%s", currentEnv)) - } - } - - // If main config doesn't exist or has no environment set, try cache config - if mainConfigErr != nil || currentEnv == "" || envConfig == nil { - cacheV := viper.New() - cacheV.SetConfigFile(cacheConfigFile) - if err := cacheV.ReadInConfig(); err != nil { - log.Fatalf("Error reading both config files:\nMain config: %v\nCache config: %v", mainConfigErr, err) - } - - // If no current environment set, try to get it from cache config - if currentEnv == "" { - currentEnv = cacheV.GetString("environment") - } - - // If still no environment, try to find first user environment - if currentEnv == "" { - envs := cacheV.GetStringMap("environments") - for env := range envs { - if strings.HasSuffix(env, "-user") { - currentEnv = env - break - } - } - } - - // Try to get environment config from cache if not found in main config - if envConfig == nil && currentEnv != "" { - envConfig = cacheV.Sub(fmt.Sprintf("environments.%s", currentEnv)) + envConfig = mainV.GetStringMap(fmt.Sprintf("environments.%s", currentEnv)) } } if envConfig == nil { - // Try one more time with both configs - mainV := viper.New() - mainV.SetConfigFile(configFile) - if err := mainV.ReadInConfig(); err == nil { - envConfig = mainV.Sub(fmt.Sprintf("environments.%s", currentEnv)) - } - - if envConfig == nil { - cacheV := viper.New() - cacheV.SetConfigFile(cacheConfigFile) - if err := cacheV.ReadInConfig(); err == nil { - envConfig = cacheV.Sub(fmt.Sprintf("environments.%s", currentEnv)) - } - } - - if envConfig == nil { - log.Fatalf("No configuration found for environment '%s' in either config file", currentEnv) - } + log.Fatalf("No configuration found for environment '%s'", currentEnv) } - endpoint := envConfig.GetString("endpoint") - - if !strings.Contains(endpoint, "identity") { - parts := strings.Split(endpoint, "://") - if len(parts) == 2 { - hostParts := strings.Split(parts[1], ".") - if len(hostParts) >= 4 { - env := hostParts[2] // dev or stg - endpoint = fmt.Sprintf("grpc+ssl://identity.api.%s.spaceone.dev:443", env) - } + // Try to load endpoints from cache first + endpointsMap, err := loadEndpointsFromCache(currentEnv) + if err != nil { + // If cache loading fails, fall back to fetching from identity service + endpoint, ok := envConfig["endpoint"].(string) + if !ok || endpoint == "" { + log.Fatalf("No endpoint found for environment '%s'", currentEnv) } - } - // Fetch endpointsMap dynamically - endpointsMap, err := FetchEndpointsMap(endpoint) - if err != nil { - log.Fatalf("Failed to fetch endpointsMap from '%s': %v", endpoint, err) + endpointsMap, err = FetchEndpointsMap(endpoint) + if err != nil { + log.Fatalf("Failed to fetch endpointsMap from '%s': %v", endpoint, err) + } } // Load short names configuration From d39b6cd29b9974415fcb7e2dc10eaa856d0d32ad Mon Sep 17 00:00:00 2001 From: Youngjin Jo Date: Tue, 3 Dec 2024 15:43:32 +0900 Subject: [PATCH 05/12] refactor: change from config.yaml to setting.toml when login Signed-off-by: Youngjin Jo --- cmd/other/login.go | 63 +++++++-------- cmd/root.go | 198 ++++++++++++++++++++++++++++++++------------- 2 files changed, 170 insertions(+), 91 deletions(-) diff --git a/cmd/other/login.go b/cmd/other/login.go index 3cb1a4c..1388b84 100644 --- a/cmd/other/login.go +++ b/cmd/other/login.go @@ -76,17 +76,18 @@ func executeLogin(cmd *cobra.Command, args []string) { return } - configPath := filepath.Join(homeDir, ".cfctl", "config.yaml") + configPath := filepath.Join(homeDir, ".cfctl", "setting.toml") // Check if config file exists if _, err := os.Stat(configPath); os.IsNotExist(err) { pterm.Error.Println("No valid configuration found.") - pterm.Info.Println("Please run 'cfctl config init' to set up your configuration.") + pterm.Info.Println("Please run 'cfctl setting init' to set up your configuration.") pterm.Info.Println("After initialization, run 'cfctl login' to authenticate.") return } viper.SetConfigFile(configPath) + viper.SetConfigType("toml") // Explicitly set config type if err := viper.ReadInConfig(); err != nil { pterm.Error.Printf("Failed to read config file: %v\n", err) return @@ -399,7 +400,7 @@ func getTokenDisplayName(claims map[string]interface{}) string { } func executeUserLogin(currentEnv string) { - loadEnvironmentConfig() // Load the environment-specific configuration + loadEnvironmentConfig() baseUrl := providedUrl if baseUrl == "" { @@ -415,8 +416,9 @@ func executeUserLogin(currentEnv string) { } cacheViper := viper.New() - cacheConfigPath := filepath.Join(homeDir, ".cfctl", "cache", "config.yaml") + cacheConfigPath := filepath.Join(homeDir, ".cfctl", "cache", "setting.toml") cacheViper.SetConfigFile(cacheConfigPath) + cacheViper.SetConfigType("toml") var userID string var password string @@ -486,7 +488,7 @@ func executeUserLogin(currentEnv string) { // Proceed with domain ID fetching and token issuance mainViper := viper.New() - mainViper.SetConfigFile(filepath.Join(homeDir, ".cfctl", "config.yaml")) + mainViper.SetConfigFile(filepath.Join(homeDir, ".cfctl", "setting.toml")) if err := mainViper.ReadInConfig(); err != nil { pterm.Error.Println("Failed to read main config file:", err) exitWithError() @@ -848,9 +850,10 @@ func saveCredentials(currentEnv, userID, password, token string) { return } - cacheConfigPath := filepath.Join(cacheDir, "config.yaml") + cacheConfigPath := filepath.Join(cacheDir, "setting.toml") cacheViper := viper.New() cacheViper.SetConfigFile(cacheConfigPath) + cacheViper.SetConfigType("toml") if err := cacheViper.ReadInConfig(); err != nil && !os.IsNotExist(err) { pterm.Error.Printf("Failed to read cache config: %v\n", err) @@ -972,49 +975,37 @@ func loadEnvironmentConfig() { exitWithError() } - mainConfigPath := filepath.Join(homeDir, ".cfctl", "config.yaml") - cacheConfigPath := filepath.Join(homeDir, ".cfctl", "cache", "config.yaml") + settingPath := filepath.Join(homeDir, ".cfctl", "setting.toml") + viper.SetConfigFile(settingPath) + viper.SetConfigType("toml") - viper.SetConfigFile(mainConfigPath) if err := viper.ReadInConfig(); err != nil { - pterm.Error.Println("Failed to read config.yaml:", err) + pterm.Error.Printf("Failed to read setting file: %v\n", err) exitWithError() } - currentEnvironment := viper.GetString("environment") - if currentEnvironment == "" { - pterm.Error.Println("No environment specified in config.yaml") + currentEnv := viper.GetString("environment") + if currentEnv == "" { + pterm.Error.Println("No environment selected") exitWithError() } - configFound := false - for _, configPath := range []string{mainConfigPath, cacheConfigPath} { - v := viper.New() - v.SetConfigFile(configPath) - if err := v.ReadInConfig(); err == nil { - endpointKey := fmt.Sprintf("environments.%s.endpoint", currentEnvironment) - tokenKey := fmt.Sprintf("environments.%s.token", currentEnvironment) - - if providedUrl == "" { - providedUrl = v.GetString(endpointKey) - } - - if token := v.GetString(tokenKey); token != "" { - viper.Set("token", token) - } + v := viper.New() + v.SetConfigFile(settingPath) + if err := v.ReadInConfig(); err == nil { + endpointKey := fmt.Sprintf("environments.%s.endpoint", currentEnv) + tokenKey := fmt.Sprintf("environments.%s.token", currentEnv) - if providedUrl != "" { - configFound = true - } + if providedUrl == "" { + providedUrl = v.GetString(endpointKey) } - } - if !configFound { - pterm.Error.Printf("No endpoint found for the current environment '%s'\n", currentEnvironment) - exitWithError() + if token := v.GetString(tokenKey); token != "" { + viper.Set("token", token) + } } - isProxyEnabled := viper.GetBool(fmt.Sprintf("environments.%s.proxy", currentEnvironment)) + isProxyEnabled := viper.GetBool(fmt.Sprintf("environments.%s.proxy", currentEnv)) containsIdentity := strings.Contains(strings.ToLower(providedUrl), "identity") if !isProxyEnabled && !containsIdentity { diff --git a/cmd/root.go b/cmd/root.go index 9904d66..b41eea8 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -91,69 +91,112 @@ func init() { // showInitializationGuide displays a helpful message when configuration is missing func showInitializationGuide(originalErr error) { - // Only show error message for commands that require configuration + // Skip showing guide for certain commands if len(os.Args) >= 2 && (os.Args[1] == "setting" || os.Args[1] == "login" || - os.Args[1] == "api-resources") { // Add api-resources to skip list + os.Args[1] == "api-resources") { return } - pterm.Warning.Printf("No valid configuration found.\n") - pterm.Info.Println("Please run 'cfctl setting init' to set up your configuration.") - pterm.Info.Println("After initialization, run 'cfctl login' to authenticate.") -} - -func addDynamicServiceCommands() error { - // If we already have in-memory cache, use it - if cachedEndpointsMap != nil { - for serviceName := range cachedEndpointsMap { - cmd := createServiceCommand(serviceName) - rootCmd.AddCommand(cmd) - } - return nil - } - - // Try to load endpoints from environment-specific cache + // Get current environment from setting file home, err := os.UserHomeDir() if err != nil { - return fmt.Errorf("unable to find home directory: %v", err) + pterm.Error.Printf("Unable to find home directory: %v\n", err) + return } - // Get current environment from main setting file + settingFile := filepath.Join(home, ".cfctl", "setting.toml") mainV := viper.New() - mainV.SetConfigFile(filepath.Join(home, ".cfctl", "setting.toml")) + mainV.SetConfigFile(settingFile) mainV.SetConfigType("toml") + if err := mainV.ReadInConfig(); err != nil { - return fmt.Errorf("failed to read setting file: %v", err) + pterm.Warning.Printf("No valid configuration found.\n") + pterm.Info.Println("Please run 'cfctl setting init' to set up your configuration.") + return } currentEnv := mainV.GetString("environment") if currentEnv == "" { - return fmt.Errorf("no environment set") + pterm.Warning.Printf("No environment selected.\n") + pterm.Info.Println("Please run 'cfctl setting init' to set up your configuration.") + return } - // Try to load endpoints from environment-specific cache - envCacheDir := filepath.Join(home, ".cfctl", "cache", currentEnv) - cacheFile := filepath.Join(envCacheDir, "endpoints.toml") - - data, err := os.ReadFile(cacheFile) + // Parse environment name to extract service name and environment + parts := strings.Split(currentEnv, "-") + if len(parts) >= 3 { + envPrefix := parts[0] // dev, stg + serviceName := parts[1] // cloudone, spaceone, etc. + url := fmt.Sprintf("https://%s.console.%s.spaceone.dev", serviceName, envPrefix) + + if strings.HasSuffix(currentEnv, "-app") { + // Show app token guide + pterm.DefaultBox. + WithTitle("Token Not Found"). + WithTitleTopCenter(). + WithBoxStyle(pterm.NewStyle(pterm.FgWhite)). + WithRightPadding(1). + WithLeftPadding(1). + WithTopPadding(0). + WithBottomPadding(0). + Println("Please follow the instructions below to obtain an App Token.") + + boxContent := fmt.Sprintf(`Please follow these steps to obtain an App Token: + +1. Visit %s +2. Go to Admin page or Workspace page +3. Navigate to the App page +4. Click [Create] button +5. Copy the generated App Token +6. Update your settings: + Path: %s + Environment: %s + Field: "token"`, + pterm.FgLightCyan.Sprint(url), + pterm.FgLightYellow.Sprint(settingFile), + pterm.FgLightGreen.Sprint(currentEnv)) + + pterm.DefaultBox. + WithTitle("Setup Instructions"). + WithTitleTopCenter(). + WithBoxStyle(pterm.NewStyle(pterm.FgLightBlue)). + Println(boxContent) + + pterm.Info.Println("After updating the token, please try your command again.") + } else { + // Show user login guide + pterm.Warning.Printf("Authentication required.\n") + pterm.Info.Println("Please run 'cfctl login' to authenticate.") + } + } +} + +func addDynamicServiceCommands() error { + // If we already have in-memory cache, use it + if cachedEndpointsMap != nil { + for serviceName := range cachedEndpointsMap { + cmd := createServiceCommand(serviceName) + rootCmd.AddCommand(cmd) + } + return nil + } + + // Try to load endpoints from file cache + endpoints, err := loadCachedEndpoints() if err == nil { - // Parse cached endpoints from TOML - var endpoints map[string]string - if err := toml.Unmarshal(data, &endpoints); err == nil { - // Store in memory for subsequent calls - cachedEndpointsMap = endpoints - - // Create commands using cached endpoints - for serviceName := range endpoints { - cmd := createServiceCommand(serviceName) - rootCmd.AddCommand(cmd) - } - return nil + // Store in memory for subsequent calls + cachedEndpointsMap = endpoints + + // Create commands using cached endpoints + for serviceName := range endpoints { + cmd := createServiceCommand(serviceName) + rootCmd.AddCommand(cmd) } + return nil } - // If no cache available or cache is invalid, fetch dynamically (this is slow path) + // If no cache available, fetch dynamically (this is slow path) setting, err := loadConfig() if err != nil { return fmt.Errorf("failed to load setting: %v", err) @@ -176,7 +219,7 @@ func addDynamicServiceCommands() error { return fmt.Errorf("failed to fetch services: %v", err) } - // Store in both memory and environment-specific cache + // Store in both memory and file cache cachedEndpointsMap = endpointsMap if err := saveEndpointsCache(endpointsMap); err != nil { fmt.Fprintf(os.Stderr, "Warning: Failed to cache endpoints: %v\n", err) @@ -234,19 +277,16 @@ func loadCachedEndpoints() (map[string]string, error) { return nil, fmt.Errorf("no environment set") } - // Only create endpoints.toml in the environment-specific cache directory + // Create environment-specific cache directory envCacheDir := filepath.Join(home, ".cfctl", "cache", currentEnv) - cacheFile := filepath.Join(envCacheDir, "endpoints.toml") - + if err := os.MkdirAll(envCacheDir, 0755); err != nil { + return nil, err + } + // Read from environment-specific cache file + cacheFile := filepath.Join(envCacheDir, "endpoints.toml") data, err := os.ReadFile(cacheFile) if err != nil { - if !os.IsNotExist(err) { - // Create directory only if we need to write the cache file - if err := os.MkdirAll(envCacheDir, 0755); err != nil { - return nil, err - } - } return nil, err } @@ -301,13 +341,14 @@ func loadConfig() (*Config, error) { return nil, fmt.Errorf("unable to find home directory: %v", err) } - // Only use main setting file + // Change file extension from .yaml to .toml settingFile := filepath.Join(home, ".cfctl", "setting.toml") + cacheConfigFile := filepath.Join(home, ".cfctl", "cache", "setting.toml") - // Try to read main setting + // Try to read main setting first mainV := viper.New() mainV.SetConfigFile(settingFile) - mainV.SetConfigType("toml") + mainV.SetConfigType("toml") // Explicitly set config type to TOML mainConfigErr := mainV.ReadInConfig() if mainConfigErr != nil { @@ -318,7 +359,7 @@ func loadConfig() (*Config, error) { var endpoint string var token string - // Get configuration from main setting only + // Main setting exists, try to get environment currentEnv = mainV.GetString("environment") if currentEnv != "" { envConfig := mainV.Sub(fmt.Sprintf("environments.%s", currentEnv)) @@ -328,6 +369,53 @@ func loadConfig() (*Config, error) { } } + // If main setting doesn't have what we need, try cache setting + if endpoint == "" || token == "" { + cacheV := viper.New() + cacheV.SetConfigFile(cacheConfigFile) + cacheV.SetConfigType("toml") // Explicitly set config type to TOML + + if err := cacheV.ReadInConfig(); err == nil { + // If no current environment set, try to get it from cache setting + if currentEnv == "" { + currentEnv = cacheV.GetString("environment") + } + + // Try to get environment setting from cache + if currentEnv != "" { + envConfig := cacheV.Sub(fmt.Sprintf("environments.%s", currentEnv)) + if envConfig != nil { + if endpoint == "" { + endpoint = envConfig.GetString("endpoint") + } + if token == "" { + token = envConfig.GetString("token") + } + } + } + + // If still no environment, try to find first user environment + if currentEnv == "" { + envs := cacheV.GetStringMap("environments") + for env := range envs { + if strings.HasSuffix(env, "-user") { + currentEnv = env + envConfig := cacheV.Sub(fmt.Sprintf("environments.%s", currentEnv)) + if envConfig != nil { + if endpoint == "" { + endpoint = envConfig.GetString("endpoint") + } + if token == "" { + token = envConfig.GetString("token") + } + break + } + } + } + } + } + } + if endpoint == "" { return nil, fmt.Errorf("no endpoint found in configuration") } From 1771d211150ca18ff1ab8a2693f5cdb38f9c00e1 Mon Sep 17 00:00:00 2001 From: Youngjin Jo Date: Tue, 3 Dec 2024 15:51:54 +0900 Subject: [PATCH 06/12] chore: set grant token timeout from a day to 6 hours Signed-off-by: Youngjin Jo --- cmd/other/login.go | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/cmd/other/login.go b/cmd/other/login.go index 1388b84..e49e429 100644 --- a/cmd/other/login.go +++ b/cmd/other/login.go @@ -87,7 +87,7 @@ func executeLogin(cmd *cobra.Command, args []string) { } viper.SetConfigFile(configPath) - viper.SetConfigType("toml") // Explicitly set config type + viper.SetConfigType("toml") if err := viper.ReadInConfig(); err != nil { pterm.Error.Printf("Failed to read config file: %v\n", err) return @@ -101,14 +101,17 @@ func executeLogin(cmd *cobra.Command, args []string) { // Check if it's an app environment if strings.HasSuffix(currentEnv, "-app") { - if err := executeAppLogin(currentEnv); err != nil { - pterm.Error.Printf("Login failed: %v\n", err) - return - } - } else { - // Execute normal user login - executeUserLogin(currentEnv) + pterm.DefaultBox.WithTitle("App Environment Detected"). + WithTitleTopCenter(). + WithRightPadding(4). + WithLeftPadding(4). + WithBoxStyle(pterm.NewStyle(pterm.FgYellow)). + Println("Login command is not available for app environments.\nPlease use the app token directly in your configuration file.") + return } + + // Execute normal user login + executeUserLogin(currentEnv) } type TokenInfo struct { @@ -561,6 +564,7 @@ func executeUserLogin(currentEnv string) { // Save the new credentials to the configuration file saveCredentials(currentEnv, userID, encryptedPassword, newAccessToken) + fmt.Println(newAccessToken) fmt.Println() pterm.Success.Println("Successfully logged in and saved token.") @@ -1481,7 +1485,7 @@ func grantToken(baseUrl, refreshToken, scope, domainID, workspaceID string) (str reqMsg.SetFieldByName("scope", scopeEnum) reqMsg.SetFieldByName("token", refreshToken) - reqMsg.SetFieldByName("timeout", int32(86400)) + reqMsg.SetFieldByName("timeout", int32(21600)) reqMsg.SetFieldByName("domain_id", domainID) if workspaceID != "" { reqMsg.SetFieldByName("workspace_id", workspaceID) From b25cc40fd7d52970c3336ed0324854da265f202f Mon Sep 17 00:00:00 2001 From: Youngjin Jo Date: Tue, 3 Dec 2024 15:52:13 +0900 Subject: [PATCH 07/12] chore: remove print Signed-off-by: Youngjin Jo --- cmd/other/login.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/other/login.go b/cmd/other/login.go index e49e429..5f74106 100644 --- a/cmd/other/login.go +++ b/cmd/other/login.go @@ -564,7 +564,6 @@ func executeUserLogin(currentEnv string) { // Save the new credentials to the configuration file saveCredentials(currentEnv, userID, encryptedPassword, newAccessToken) - fmt.Println(newAccessToken) fmt.Println() pterm.Success.Println("Successfully logged in and saved token.") From 8714938ae50c7343e40c5293f45b2ee0101a5a02 Mon Sep 17 00:00:00 2001 From: Youngjin Jo Date: Tue, 3 Dec 2024 16:14:36 +0900 Subject: [PATCH 08/12] refactor: save tokens to files Signed-off-by: Youngjin Jo --- cmd/other/login.go | 79 ++++++++++++---------------------------------- 1 file changed, 21 insertions(+), 58 deletions(-) diff --git a/cmd/other/login.go b/cmd/other/login.go index 5f74106..163aeed 100644 --- a/cmd/other/login.go +++ b/cmd/other/login.go @@ -563,9 +563,8 @@ func executeUserLogin(currentEnv string) { } // Save the new credentials to the configuration file - saveCredentials(currentEnv, userID, encryptedPassword, newAccessToken) + saveCredentials(currentEnv, userID, encryptedPassword, accessToken, refreshToken, newAccessToken) - fmt.Println() pterm.Success.Println("Successfully logged in and saved token.") } @@ -840,79 +839,43 @@ type UserCredentials struct { } // saveCredentials saves the user's credentials to the configuration -func saveCredentials(currentEnv, userID, password, token string) { +func saveCredentials(currentEnv, userID, password, accessToken, refreshToken, grantToken string) { homeDir, err := os.UserHomeDir() if err != nil { pterm.Error.Printf("Failed to get user home directory: %v\n", err) return } - cacheDir := filepath.Join(homeDir, ".cfctl", "cache") - if err := os.MkdirAll(cacheDir, 0700); err != nil { + // Create environment-specific cache directory + envCacheDir := filepath.Join(homeDir, ".cfctl", "cache", currentEnv) + if err := os.MkdirAll(envCacheDir, 0700); err != nil { pterm.Error.Printf("Failed to create cache directory: %v\n", err) return } - cacheConfigPath := filepath.Join(cacheDir, "setting.toml") - cacheViper := viper.New() - cacheViper.SetConfigFile(cacheConfigPath) - cacheViper.SetConfigType("toml") - - if err := cacheViper.ReadInConfig(); err != nil && !os.IsNotExist(err) { - pterm.Error.Printf("Failed to read cache config: %v\n", err) + // Save access token + accessTokenPath := filepath.Join(envCacheDir, "access_token") + if err := os.WriteFile(accessTokenPath, []byte(accessToken), 0600); err != nil { + pterm.Error.Printf("Failed to save access token: %v\n", err) return } - envPath := fmt.Sprintf("environments.%s", currentEnv) - envSettings := cacheViper.GetStringMap(envPath) - if envSettings == nil { - envSettings = make(map[string]interface{}) - } - - // Save token at the root level of the environment - envSettings["token"] = token - - var users []UserCredentials - if existingUsers, ok := envSettings["users"]; ok { - if userList, ok := existingUsers.([]interface{}); ok { - for _, u := range userList { - if userMap, ok := u.(map[string]interface{}); ok { - user := UserCredentials{ - UserID: userMap["userid"].(string), - Password: userMap["password"].(string), - Token: userMap["token"].(string), - } - users = append(users, user) - } - } - } - } - - // Update existing user or add new user - userExists := false - for i, user := range users { - if user.UserID == userID { - users[i].Password = password - users[i].Token = token - userExists = true - break + // Save refresh token (if available) + if refreshToken != "" { + refreshTokenPath := filepath.Join(envCacheDir, "refresh_token") + if err := os.WriteFile(refreshTokenPath, []byte(refreshToken), 0600); err != nil { + pterm.Error.Printf("Failed to save refresh token: %v\n", err) + return } } - if !userExists { - newUser := UserCredentials{ - UserID: userID, - Password: password, - Token: token, + // Save grant token (if available) + if grantToken != "" { + grantTokenPath := filepath.Join(envCacheDir, "grant_token") + if err := os.WriteFile(grantTokenPath, []byte(grantToken), 0600); err != nil { + pterm.Error.Printf("Failed to save grant token: %v\n", err) + return } - users = append(users, newUser) - } - - envSettings["users"] = users - cacheViper.Set(envPath, envSettings) - - if err := cacheViper.WriteConfig(); err != nil { - pterm.Error.Printf("Failed to save user credentials: %v\n", err) } } From 7f31a41ca7ccbf0e2c948376a6179cd66c4226cd Mon Sep 17 00:00:00 2001 From: Youngjin Jo Date: Tue, 3 Dec 2024 16:44:42 +0900 Subject: [PATCH 09/12] refactor: check expiration date Signed-off-by: Youngjin Jo --- cmd/other/login.go | 76 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 17 deletions(-) diff --git a/cmd/other/login.go b/cmd/other/login.go index 163aeed..b3bca8f 100644 --- a/cmd/other/login.go +++ b/cmd/other/login.go @@ -411,21 +411,31 @@ func executeUserLogin(currentEnv string) { exitWithError() } - // Get the home directory homeDir, err := os.UserHomeDir() if err != nil { pterm.Error.Println("Failed to get user home directory:", err) exitWithError() } + var ( + userID string + password string + accessToken string + refreshToken string + encryptedPassword string + ) + + // Try to get valid tokens from cache first + if err != nil { + pterm.Error.Println("Failed to get user home directory:", err) + exitWithError() + } + cacheViper := viper.New() cacheConfigPath := filepath.Join(homeDir, ".cfctl", "cache", "setting.toml") cacheViper.SetConfigFile(cacheConfigPath) cacheViper.SetConfigType("toml") - var userID string - var password string - if err := cacheViper.ReadInConfig(); err == nil { usersField := cacheViper.Get("environments." + currentEnv + ".users") if usersField != nil { @@ -445,7 +455,7 @@ func executeUserLogin(currentEnv string) { // Selected existing user selectedUser := users[userSelection-1].(map[string]interface{}) userID = selectedUser["userid"].(string) - encryptedPassword := selectedUser["password"].(string) + encryptedPassword = selectedUser["password"].(string) token := selectedUser["token"].(string) // Check if token is still valid @@ -513,33 +523,31 @@ func executeUserLogin(currentEnv string) { exitWithError() } - // Attempt to issue token - accessToken, refreshToken, err := issueToken(baseUrl, userID, password, domainID) + // Issue new tokens + accessToken, refreshToken, err = getValidTokens(currentEnv) if err != nil { - pterm.Error.Println("Failed to retrieve token:", err) - exitWithError() + accessToken, refreshToken, err = issueToken(baseUrl, userID, password, domainID) } - // Encrypt password before saving - encryptedPassword, err := encrypt(password) + // Encrypt password + encryptedPassword, err = encrypt(password) if err != nil { pterm.Error.Printf("Failed to encrypt password: %v\n", err) exitWithError() } + // Use the tokens (either from cache or newly issued) workspaces, err := fetchWorkspaces(baseUrl, accessToken) if err != nil { pterm.Error.Println("Failed to fetch workspaces:", err) } - // Fetch Domain ID and Role Type domainID, roleType, err := fetchDomainIDAndRole(baseUrl, accessToken) if err != nil { pterm.Error.Println("Failed to fetch Domain ID and Role Type:", err) exitWithError() } - // Determine scope and workspace scope := determineScope(roleType, len(workspaces)) var workspaceID string if roleType == "DOMAIN_ADMIN" { @@ -555,15 +563,15 @@ func executeUserLogin(currentEnv string) { scope = "WORKSPACE" } - // Grant new token - newAccessToken, err := grantToken(baseUrl, refreshToken, scope, domainID, workspaceID) + // Grant new token using the refresh token + grantToken, err := grantToken(baseUrl, refreshToken, scope, domainID, workspaceID) if err != nil { pterm.Error.Println("Failed to retrieve new access token:", err) exitWithError() } - // Save the new credentials to the configuration file - saveCredentials(currentEnv, userID, encryptedPassword, accessToken, refreshToken, newAccessToken) + // Save all tokens + saveCredentials(currentEnv, userID, encryptedPassword, accessToken, refreshToken, grantToken) pterm.Success.Println("Successfully logged in and saved token.") } @@ -1837,3 +1845,37 @@ func clearInvalidTokens(currentEnv string) error { viper.Set(envPath, envSettings) return viper.WriteConfig() } + +// readTokenFromFile reads a token from the specified file in the environment cache directory +func readTokenFromFile(envDir, tokenType string) (string, error) { + tokenPath := filepath.Join(envDir, tokenType) + data, err := os.ReadFile(tokenPath) + if err != nil { + return "", err + } + return string(data), nil +} + +// getValidTokens checks for existing valid tokens in the environment cache directory +func getValidTokens(currentEnv string) (accessToken, refreshToken string, err error) { + homeDir, err := os.UserHomeDir() + if err != nil { + return "", "", err + } + + envCacheDir := filepath.Join(homeDir, ".cfctl", "cache", currentEnv) + + // Try to read and validate access token + if accessToken, err = readTokenFromFile(envCacheDir, "access_token"); err == nil { + if !isTokenExpired(accessToken) { + // Try to read refresh token only if access token is valid + if refreshToken, err = readTokenFromFile(envCacheDir, "refresh_token"); err == nil { + if !isTokenExpired(refreshToken) { + return accessToken, refreshToken, nil + } + } + } + } + + return "", "", fmt.Errorf("no valid tokens found") +} From c83d841f08dce7aa6501c75af8ef83295180f436 Mon Sep 17 00:00:00 2001 From: Youngjin Jo Date: Tue, 3 Dec 2024 17:19:18 +0900 Subject: [PATCH 10/12] refactor: remove token field when initialize a user Signed-off-by: Youngjin Jo --- cmd/other/setting.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cmd/other/setting.go b/cmd/other/setting.go index 42de2cf..c72aec0 100644 --- a/cmd/other/setting.go +++ b/cmd/other/setting.go @@ -1009,12 +1009,18 @@ func updateSetting(envName, urlStr, settingType string) { } // Add new environment configuration - environments[envName] = map[string]interface{}{ + envConfig := map[string]interface{}{ "endpoint": endpoint, "proxy": true, - "token": "", } + // Only add token field for app configuration + if settingType == "app" { + envConfig["token"] = "" + } + + environments[envName] = envConfig + // Update entire configuration mainV.Set("environments", environments) mainV.Set("environment", envName) From b8d6c08c828cad22f9b00e36b7cf1e6a09c2c7bc Mon Sep 17 00:00:00 2001 From: Youngjin Jo Date: Tue, 3 Dec 2024 17:32:28 +0900 Subject: [PATCH 11/12] refactor: add user_id to setting.toml when user log in Signed-off-by: Youngjin Jo --- cmd/other/login.go | 49 ++++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/cmd/other/login.go b/cmd/other/login.go index b3bca8f..132151c 100644 --- a/cmd/other/login.go +++ b/cmd/other/login.go @@ -847,42 +847,57 @@ type UserCredentials struct { } // saveCredentials saves the user's credentials to the configuration -func saveCredentials(currentEnv, userID, password, accessToken, refreshToken, grantToken string) { +func saveCredentials(currentEnv, userID, encryptedPassword, accessToken, refreshToken, grantToken string) { homeDir, err := os.UserHomeDir() if err != nil { - pterm.Error.Printf("Failed to get user home directory: %v\n", err) - return + pterm.Error.Println("Failed to get home directory:", err) + exitWithError() + } + + // Update main settings file + settingPath := filepath.Join(homeDir, ".cfctl", "setting.toml") + mainViper := viper.New() + mainViper.SetConfigFile(settingPath) + mainViper.SetConfigType("toml") + + if err := mainViper.ReadInConfig(); err != nil { + pterm.Error.Printf("Failed to read config file: %v\n", err) + exitWithError() } - // Create environment-specific cache directory + // Save user_id to environment settings + envPath := fmt.Sprintf("environments.%s.user_id", currentEnv) + mainViper.Set(envPath, userID) + + if err := mainViper.WriteConfig(); err != nil { + pterm.Error.Printf("Failed to save config file: %v\n", err) + exitWithError() + } + + // Create cache directory envCacheDir := filepath.Join(homeDir, ".cfctl", "cache", currentEnv) if err := os.MkdirAll(envCacheDir, 0700); err != nil { pterm.Error.Printf("Failed to create cache directory: %v\n", err) - return + exitWithError() } - // Save access token - accessTokenPath := filepath.Join(envCacheDir, "access_token") - if err := os.WriteFile(accessTokenPath, []byte(accessToken), 0600); err != nil { + // Save tokens to cache + if err := os.WriteFile(filepath.Join(envCacheDir, "access_token"), []byte(accessToken), 0600); err != nil { pterm.Error.Printf("Failed to save access token: %v\n", err) - return + exitWithError() } - // Save refresh token (if available) if refreshToken != "" { - refreshTokenPath := filepath.Join(envCacheDir, "refresh_token") - if err := os.WriteFile(refreshTokenPath, []byte(refreshToken), 0600); err != nil { + if err := os.WriteFile(filepath.Join(envCacheDir, "refresh_token"), []byte(refreshToken), 0600); err != nil { pterm.Error.Printf("Failed to save refresh token: %v\n", err) - return + exitWithError() } } - // Save grant token (if available) if grantToken != "" { - grantTokenPath := filepath.Join(envCacheDir, "grant_token") - if err := os.WriteFile(grantTokenPath, []byte(grantToken), 0600); err != nil { + if err := os.WriteFile(filepath.Join(envCacheDir, "grant_token"), []byte(grantToken), 0600); err != nil { pterm.Error.Printf("Failed to save grant token: %v\n", err) - return + exitWithError() } } } From 2a54a416f601a6780f5135e6f7c675b492caa7c6 Mon Sep 17 00:00:00 2001 From: Youngjin Jo Date: Tue, 3 Dec 2024 17:38:27 +0900 Subject: [PATCH 12/12] refactor: remove multi user log in and use user_id of toml Signed-off-by: Youngjin Jo --- cmd/other/login.go | 148 +++++++++++++-------------------------------- 1 file changed, 43 insertions(+), 105 deletions(-) diff --git a/cmd/other/login.go b/cmd/other/login.go index 132151c..aa5e22a 100644 --- a/cmd/other/login.go +++ b/cmd/other/login.go @@ -417,99 +417,31 @@ func executeUserLogin(currentEnv string) { exitWithError() } - var ( - userID string - password string - accessToken string - refreshToken string - encryptedPassword string - ) - - // Try to get valid tokens from cache first - if err != nil { - pterm.Error.Println("Failed to get user home directory:", err) - exitWithError() - } - - cacheViper := viper.New() - cacheConfigPath := filepath.Join(homeDir, ".cfctl", "cache", "setting.toml") - cacheViper.SetConfigFile(cacheConfigPath) - cacheViper.SetConfigType("toml") - - if err := cacheViper.ReadInConfig(); err == nil { - usersField := cacheViper.Get("environments." + currentEnv + ".users") - if usersField != nil { - users, ok := usersField.([]interface{}) - if !ok { - pterm.Error.Println("Failed to load users correctly.") - exitWithError() - } + // Get user_id from current environment + mainViper := viper.New() + settingPath := filepath.Join(homeDir, ".cfctl", "setting.toml") + mainViper.SetConfigFile(settingPath) + mainViper.SetConfigType("toml") - if len(users) > 0 { - pterm.Info.Println("Select an account to login or add a new user:") - - // Display user selection including "Add new user" option - userSelection := promptUserSelection(len(users), users) - - if userSelection <= len(users) { - // Selected existing user - selectedUser := users[userSelection-1].(map[string]interface{}) - userID = selectedUser["userid"].(string) - encryptedPassword = selectedUser["password"].(string) - token := selectedUser["token"].(string) - - // Check if token is still valid - if !isTokenExpired(token) { - // Use stored password - decryptedPassword, err := decrypt(encryptedPassword) - if err != nil { - pterm.Error.Printf("Failed to decrypt password: %v\n", err) - exitWithError() - } - password = decryptedPassword - pterm.Success.Printf("Using saved credentials for %s\n", userID) - } else { - // Token expired, ask for password again - password = promptPassword() - // Verify the password matches - decryptedPassword, err := decrypt(encryptedPassword) - if err != nil { - pterm.Error.Printf("Failed to decrypt password: %v\n", err) - exitWithError() - } - if password != decryptedPassword { - pterm.Error.Println("Password does not match.") - exitWithError() - } - } - } else { - // Selected to add new user - userID, password = promptCredentials() - } - } else { - // No existing users, prompt for new credentials - userID, password = promptCredentials() - } - } else { - // Users field doesn't exist, prompt for new credentials - userID, password = promptCredentials() - } - } else { - // Configuration cannot be read, prompt for new credentials - userID, password = promptCredentials() + if err := mainViper.ReadInConfig(); err != nil { + pterm.Error.Printf("Failed to read config file: %v\n", err) + exitWithError() } - // Proceed with domain ID fetching and token issuance - mainViper := viper.New() - mainViper.SetConfigFile(filepath.Join(homeDir, ".cfctl", "setting.toml")) - if err := mainViper.ReadInConfig(); err != nil { - pterm.Error.Println("Failed to read main config file:", err) + userID := mainViper.GetString(fmt.Sprintf("environments.%s.user_id", currentEnv)) + if userID == "" { + pterm.Error.Println("No user ID found in current environment configuration.") exitWithError() } + // Display the current user ID + pterm.Info.Printf("Logged in as: %s\n", userID) + + // Prompt for password + password := promptPassword() + // Extract the middle part of the environment name for `name` - currentEnvironment := mainViper.GetString("environment") - nameParts := strings.Split(currentEnvironment, "-") + nameParts := strings.Split(currentEnv, "-") if len(nameParts) < 3 { pterm.Error.Println("Environment name format is invalid.") exitWithError() @@ -524,22 +456,17 @@ func executeUserLogin(currentEnv string) { } // Issue new tokens - accessToken, refreshToken, err = getValidTokens(currentEnv) + accessToken, refreshToken, err := issueToken(baseUrl, userID, password, domainID) if err != nil { - accessToken, refreshToken, err = issueToken(baseUrl, userID, password, domainID) - } - - // Encrypt password - encryptedPassword, err = encrypt(password) - if err != nil { - pterm.Error.Printf("Failed to encrypt password: %v\n", err) + pterm.Error.Printf("Failed to issue token: %v\n", err) exitWithError() } - // Use the tokens (either from cache or newly issued) + // Use the tokens workspaces, err := fetchWorkspaces(baseUrl, accessToken) if err != nil { pterm.Error.Println("Failed to fetch workspaces:", err) + exitWithError() } domainID, roleType, err := fetchDomainIDAndRole(baseUrl, accessToken) @@ -570,18 +497,29 @@ func executeUserLogin(currentEnv string) { exitWithError() } - // Save all tokens - saveCredentials(currentEnv, userID, encryptedPassword, accessToken, refreshToken, grantToken) + // Save tokens to cache + envCacheDir := filepath.Join(homeDir, ".cfctl", "cache", currentEnv) + if err := os.MkdirAll(envCacheDir, 0700); err != nil { + pterm.Error.Printf("Failed to create cache directory: %v\n", err) + exitWithError() + } - pterm.Success.Println("Successfully logged in and saved token.") -} + if err := os.WriteFile(filepath.Join(envCacheDir, "access_token"), []byte(accessToken), 0600); err != nil { + pterm.Error.Printf("Failed to save access token: %v\n", err) + exitWithError() + } -// Prompt for user credentials if they aren't saved -func promptCredentials() (string, string) { - userId, _ := pterm.DefaultInteractiveTextInput.Show("Enter your user ID") - passwordInput := pterm.DefaultInteractiveTextInput.WithMask("*") - password, _ := passwordInput.Show("Enter your password") - return userId, password + if err := os.WriteFile(filepath.Join(envCacheDir, "refresh_token"), []byte(refreshToken), 0600); err != nil { + pterm.Error.Printf("Failed to save refresh token: %v\n", err) + exitWithError() + } + + if err := os.WriteFile(filepath.Join(envCacheDir, "grant_token"), []byte(grantToken), 0600); err != nil { + pterm.Error.Printf("Failed to save grant token: %v\n", err) + exitWithError() + } + + pterm.Success.Println("Successfully logged in and saved token.") } // Prompt for password when token is expired