diff --git a/cmd/common/apiResources.go b/cmd/common/apiResources.go index 6f7ebb9..0c6c47f 100644 --- a/cmd/common/apiResources.go +++ b/cmd/common/apiResources.go @@ -75,14 +75,29 @@ func ListAPIResources(serviceName string) error { } func getServiceEndpoint(config *Config, serviceName string) (string, error) { + envConfig := config.Environments[config.Environment] + if envConfig.URL == "" { + return "", fmt.Errorf("URL not found in environment config") + } + + // Parse URL to get environment + urlParts := strings.Split(envConfig.URL, ".") + if len(urlParts) < 4 { + return "", fmt.Errorf("invalid URL format: %s", envConfig.URL) + } + var envPrefix string - if strings.HasPrefix(config.Environment, "dev-") { - envPrefix = "dev" - } else if strings.HasPrefix(config.Environment, "stg-") { - envPrefix = "stg" - } else { - return "", fmt.Errorf("unsupported environment prefix") + for i, part := range urlParts { + if part == "console" && i+1 < len(urlParts) { + envPrefix = urlParts[i+1] // Get the part after "console" (dev or stg) + break + } + } + + if envPrefix == "" { + return "", fmt.Errorf("environment prefix not found in URL: %s", envConfig.URL) } + endpoint := fmt.Sprintf("grpc+ssl://%s.api.%s.spaceone.dev:443", serviceName, envPrefix) return endpoint, nil } @@ -122,16 +137,16 @@ func fetchServiceResources(serviceName, endpoint string, shortNamesMap map[strin return nil, fmt.Errorf("failed to list services: %v", err) } - // Load short names from setting.toml + // Load short names from setting.yaml home, err := os.UserHomeDir() if err != nil { return nil, fmt.Errorf("failed to get home directory: %v", err) } - settingPath := filepath.Join(home, ".cfctl", "setting.toml") + settingPath := filepath.Join(home, ".cfctl", "setting.yaml") v := viper.New() v.SetConfigFile(settingPath) - v.SetConfigType("toml") + v.SetConfigType("yaml") serviceShortNames := make(map[string]string) if err := v.ReadInConfig(); err == nil { diff --git a/cmd/common/fetchService.go b/cmd/common/fetchService.go index a7cec2e..c0be6fb 100644 --- a/cmd/common/fetchService.go +++ b/cmd/common/fetchService.go @@ -33,16 +33,16 @@ import ( "gopkg.in/yaml.v3" ) -// Config structure to parse environment files -type Config struct { - Environment string `yaml:"environment"` - Environments map[string]Environment `yaml:"environments"` -} - type Environment struct { Endpoint string `yaml:"endpoint"` - Proxy string `yaml:"proxy"` + Proxy string `yaml:"proxy"` Token string `yaml:"token"` + URL string `yaml:"url"` +} + +type Config struct { + Environment string `yaml:"environment"` + Environments map[string]Environment `yaml:"environments"` } // FetchService handles the execution of gRPC commands for all services @@ -54,8 +54,8 @@ func FetchService(serviceName string, verb string, resourceName string, options // Read configuration file mainViper := viper.New() - mainViper.SetConfigFile(filepath.Join(homeDir, ".cfctl", "setting.toml")) - mainViper.SetConfigType("toml") + mainViper.SetConfigFile(filepath.Join(homeDir, ".cfctl", "setting.yaml")) + mainViper.SetConfigType("yaml") if err := mainViper.ReadInConfig(); err != nil { return nil, fmt.Errorf("failed to read configuration file. Please run 'cfctl login' first") } @@ -158,12 +158,19 @@ func FetchService(serviceName string, verb string, resourceName string, options // Get hostPort based on environment prefix var envPrefix string - if strings.HasPrefix(config.Environment, "dev-") { - envPrefix = "dev" - } else if strings.HasPrefix(config.Environment, "stg-") { - envPrefix = "stg" + urlParts := strings.Split(config.Environments[config.Environment].URL, ".") + for i, part := range urlParts { + if part == "console" && i+1 < len(urlParts) { + envPrefix = urlParts[i+1] // Get the part after "console" (dev or stg) + break + } + } + + if envPrefix == "" { + return nil, fmt.Errorf("environment prefix not found in URL: %s", config.Environments[config.Environment].URL) } - hostPort := fmt.Sprintf("%s.api.%s.spaceone.dev:443", serviceName, envPrefix) + + hostPort := fmt.Sprintf("%s.api.%s.spaceone.dev:443", convertServiceNameToEndpoint(serviceName), envPrefix) // Configure gRPC connection var conn *grpc.ClientConn @@ -316,9 +323,9 @@ func loadConfig() (*Config, error) { // Load main configuration file mainV := viper.New() - mainConfigPath := filepath.Join(home, ".cfctl", "setting.toml") + mainConfigPath := filepath.Join(home, ".cfctl", "setting.yaml") mainV.SetConfigFile(mainConfigPath) - mainV.SetConfigType("toml") + mainV.SetConfigType("yaml") if err := mainV.ReadInConfig(); err != nil { return nil, fmt.Errorf("failed to read config file: %v", err) } @@ -332,6 +339,7 @@ func loadConfig() (*Config, error) { envConfig := &Environment{ Endpoint: mainV.GetString(fmt.Sprintf("environments.%s.endpoint", currentEnv)), Proxy: mainV.GetString(fmt.Sprintf("environments.%s.proxy", currentEnv)), + URL: mainV.GetString(fmt.Sprintf("environments.%s.url", currentEnv)), } // Handle token based on environment type @@ -374,12 +382,20 @@ func fetchJSONResponse(config *Config, serviceName string, verb string, resource } } else { var envPrefix string - if strings.HasPrefix(config.Environment, "dev-") { - envPrefix = "dev" - } else if strings.HasPrefix(config.Environment, "stg-") { - envPrefix = "stg" + urlParts := strings.Split(config.Environments[config.Environment].URL, ".") + for i, part := range urlParts { + if part == "console" && i+1 < len(urlParts) { + envPrefix = urlParts[i+1] + break + } + } + + if envPrefix == "" { + return nil, fmt.Errorf("environment prefix not found in URL: %s", config.Environments[config.Environment].URL) } - hostPort := fmt.Sprintf("%s.api.%s.spaceone.dev:443", serviceName, envPrefix) + + hostPort := fmt.Sprintf("%s.api.%s.spaceone.dev:443", convertServiceNameToEndpoint(serviceName), envPrefix) + tlsConfig := &tls.Config{ InsecureSkipVerify: false, } diff --git a/cmd/common/fetchVerb.go b/cmd/common/fetchVerb.go index a7a4118..a21843c 100644 --- a/cmd/common/fetchVerb.go +++ b/cmd/common/fetchVerb.go @@ -54,31 +54,7 @@ func AddVerbCommands(parentCmd *cobra.Command, serviceName string, groupID strin verbCmd := &cobra.Command{ Use: currentVerb + " ", Short: shortDesc, - Long: fmt.Sprintf(`Supported %d resources for %s command. - -%s - -%s`, - len(resources), - currentVerb, - pterm.DefaultBox.WithTitle("Interactive Mode").WithTitleTopCenter().Sprint( - func() string { - str, _ := pterm.DefaultBulletList.WithItems([]pterm.BulletListItem{ - {Level: 0, Text: "Required parameters will be prompted if not provided"}, - {Level: 0, Text: "Missing parameters will be requested interactively"}, - {Level: 0, Text: "Just follow the prompts to fill in the required fields"}, - }).Srender() - return str - }()), - pterm.DefaultBox.WithTitle("Example").WithTitleTopCenter().Sprint( - fmt.Sprintf("List resources:\n"+ - " $ cfctl %s list \n\n"+ - "List and sort by field:\n"+ - " $ cfctl %s list -s name\n"+ - " $ cfctl %s list -s created_at\n\n"+ - "Watch for changes:\n"+ - " $ cfctl %s list -w", - serviceName, serviceName, serviceName, serviceName))), + Long: fmt.Sprintf("Supported %d resources for %s command.", len(resources), currentVerb), Args: cobra.ArbitraryArgs, // Allow any number of arguments RunE: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { @@ -101,10 +77,6 @@ func AddVerbCommands(parentCmd *cobra.Command, serviceName string, groupID strin if err != nil { return err } - apiVersion, err := cmd.Flags().GetString("api-version") - if err != nil { - return err - } outputFormat, err := cmd.Flags().GetString("output") if err != nil { return err @@ -127,7 +99,6 @@ func AddVerbCommands(parentCmd *cobra.Command, serviceName string, groupID strin Parameters: parameters, JSONParameter: jsonParameter, FileParameter: fileParameter, - APIVersion: apiVersion, OutputFormat: outputFormat, CopyToClipboard: copyToClipboard, SortBy: sortBy, @@ -171,7 +142,6 @@ func AddVerbCommands(parentCmd *cobra.Command, serviceName string, groupID strin verbCmd.Flags().StringArrayP("parameter", "p", []string{}, "Input Parameter (-p = -p ...)") verbCmd.Flags().StringP("json-parameter", "j", "", "JSON type parameter") verbCmd.Flags().StringP("file-parameter", "f", "", "YAML file parameter") - verbCmd.Flags().StringP("api-version", "v", "v1", "API Version") verbCmd.Flags().StringP("output", "o", "yaml", "Output format (yaml, json, table, csv)") verbCmd.Flags().BoolP("copy", "y", false, "Copy the output to the clipboard (copies any output format)") @@ -180,31 +150,7 @@ func AddVerbCommands(parentCmd *cobra.Command, serviceName string, groupID strin // Update example for list command if currentVerb == "list" { - verbCmd.Long = fmt.Sprintf(`Supported %d resources for %s command. - -%s - -%s`, - len(resources), - currentVerb, - pterm.DefaultBox.WithTitle("Interactive Mode").WithTitleTopCenter().Sprint( - func() string { - str, _ := pterm.DefaultBulletList.WithItems([]pterm.BulletListItem{ - {Level: 0, Text: "Required parameters will be prompted if not provided"}, - {Level: 0, Text: "Missing parameters will be requested interactively"}, - {Level: 0, Text: "Just follow the prompts to fill in the required fields"}, - }).Srender() - return str - }()), - pterm.DefaultBox.WithTitle("Example").WithTitleTopCenter().Sprint( - fmt.Sprintf("List resources:\n"+ - " $ cfctl %s list \n\n"+ - "List and sort by field:\n"+ - " $ cfctl %s list -s name\n"+ - " $ cfctl %s list -s created_at\n\n"+ - "Watch for changes:\n"+ - " $ cfctl %s list -w", - serviceName, serviceName, serviceName, serviceName))) + verbCmd.Long = fmt.Sprintf("Supported %d resources for %s command.", len(resources), currentVerb) } parentCmd.AddCommand(verbCmd) diff --git a/cmd/common/helpers.go b/cmd/common/helpers.go index 33f5d3e..5c0cbf0 100644 --- a/cmd/common/helpers.go +++ b/cmd/common/helpers.go @@ -6,8 +6,12 @@ import ( "context" "crypto/tls" "fmt" + "gopkg.in/yaml.v3" + "os" + "path/filepath" "sort" "strings" + "time" "github.com/spf13/cobra" @@ -27,47 +31,87 @@ func convertServiceNameToEndpoint(serviceName string) string { return strings.ReplaceAll(serviceName, "_", "-") } -// BuildVerbResourceMap builds a mapping from verbs to resources for a given service + func BuildVerbResourceMap(serviceName string) (map[string][]string, error) { + // Try to load from cache first + home, err := os.UserHomeDir() + if err != nil { + return nil, fmt.Errorf("failed to get home directory: %v", err) + } + config, err := loadConfig() if err != nil { return nil, fmt.Errorf("failed to load config: %v", err) } - var conn *grpc.ClientConn - var refClient *grpcreflect.Client - - if strings.HasPrefix(config.Environment, "local-") { - conn, err = grpc.Dial("localhost:50051", grpc.WithInsecure()) - if err != nil { - return nil, fmt.Errorf("local connection failed: %v", err) - } - } else { - var envPrefix string - if strings.HasPrefix(config.Environment, "dev-") { - envPrefix = "dev" - } else if strings.HasPrefix(config.Environment, "stg-") { - envPrefix = "stg" - } else { - return nil, fmt.Errorf("unsupported environment prefix") + cacheDir := filepath.Join(home, ".cfctl", "cache", config.Environment) + cacheFile := filepath.Join(cacheDir, fmt.Sprintf("%s_verbs.yaml", serviceName)) + + // Check if cache exists and is fresh (less than 1 hour old) + if info, err := os.Stat(cacheFile); err == nil { + if time.Since(info.ModTime()) < time.Hour { + data, err := os.ReadFile(cacheFile) + if err == nil { + verbResourceMap := make(map[string][]string) + if err := yaml.Unmarshal(data, &verbResourceMap); err == nil { + return verbResourceMap, nil + } + } } + } - endpointServiceName := convertServiceNameToEndpoint(serviceName) - hostPort := fmt.Sprintf("%s.api.%s.spaceone.dev:443", endpointServiceName, envPrefix) + // Cache miss or expired, fetch from server + verbResourceMap, err := fetchVerbResourceMap(serviceName, config) + if err != nil { + return nil, err + } - tlsConfig := &tls.Config{ - InsecureSkipVerify: false, + // Save to cache + if err := os.MkdirAll(cacheDir, 0755); err == nil { + data, err := yaml.Marshal(verbResourceMap) + if err == nil { + os.WriteFile(cacheFile, data, 0644) } - creds := credentials.NewTLS(tlsConfig) - conn, err = grpc.Dial(hostPort, grpc.WithTransportCredentials(creds)) - if err != nil { - return nil, fmt.Errorf("connection failed: %v", err) + } + + return verbResourceMap, nil +} + +func fetchVerbResourceMap(serviceName string, config *Config) (map[string][]string, error) { + envConfig := config.Environments[config.Environment] + if envConfig.URL == "" { + return nil, fmt.Errorf("URL not found in environment config") + } + + // Parse URL to get environment + urlParts := strings.Split(envConfig.URL, ".") + var envPrefix string + for i, part := range urlParts { + if part == "console" && i+1 < len(urlParts) { + envPrefix = urlParts[i+1] + break } } + + if envPrefix == "" { + return nil, fmt.Errorf("environment prefix not found in URL: %s", envConfig.URL) + } + + endpointServiceName := convertServiceNameToEndpoint(serviceName) + hostPort := fmt.Sprintf("%s.api.%s.spaceone.dev:443", endpointServiceName, envPrefix) + + tlsConfig := &tls.Config{ + InsecureSkipVerify: false, + } + creds := credentials.NewTLS(tlsConfig) + conn, err := grpc.Dial(hostPort, grpc.WithTransportCredentials(creds)) + if err != nil { + return nil, fmt.Errorf("connection failed: %v", err) + } defer conn.Close() - ctx := metadata.AppendToOutgoingContext(context.Background(), "token", config.Environments[config.Environment].Token) - refClient = grpcreflect.NewClient(ctx, grpc_reflection_v1alpha.NewServerReflectionClient(conn)) + ctx := metadata.AppendToOutgoingContext(context.Background(), "token", envConfig.Token) + refClient := grpcreflect.NewClient(ctx, grpc_reflection_v1alpha.NewServerReflectionClient(conn)) defer refClient.Reset() services, err := refClient.ListServices() @@ -75,13 +119,8 @@ func BuildVerbResourceMap(serviceName string) (map[string][]string, error) { return nil, fmt.Errorf("failed to list services: %v", err) } - verbResourceMap := make(map[string]map[string]struct{}) - + verbResourceMap := make(map[string][]string) for _, s := range services { - if strings.HasPrefix(s, "grpc.reflection.") { - continue - } - if !strings.Contains(s, fmt.Sprintf(".%s.", serviceName)) { continue } @@ -91,29 +130,18 @@ func BuildVerbResourceMap(serviceName string) (map[string][]string, error) { continue } - parts := strings.Split(s, ".") - resourceName := parts[len(parts)-1] - + resourceName := s[strings.LastIndex(s, ".")+1:] for _, method := range serviceDesc.GetMethods() { verb := method.GetName() - if verbResourceMap[verb] == nil { - verbResourceMap[verb] = make(map[string]struct{}) + if resources, ok := verbResourceMap[verb]; ok { + verbResourceMap[verb] = append(resources, resourceName) + } else { + verbResourceMap[verb] = []string{resourceName} } - verbResourceMap[verb][resourceName] = struct{}{} } } - result := make(map[string][]string) - for verb, resourcesSet := range verbResourceMap { - resources := make([]string, 0, len(resourcesSet)) - for resource := range resourcesSet { - resources = append(resources, resource) - } - sort.Strings(resources) - result[verb] = resources - } - - return result, nil + return verbResourceMap, nil } // CustomParentHelpFunc customizes the help output for the parent command diff --git a/cmd/other/apiResources.go b/cmd/other/apiResources.go index 6794b8c..93369fd 100644 --- a/cmd/other/apiResources.go +++ b/cmd/other/apiResources.go @@ -14,7 +14,6 @@ import ( "strings" "sync" - "github.com/BurntSushi/toml" "github.com/jhump/protoreflect/dynamic" "github.com/jhump/protoreflect/grpcreflect" @@ -40,14 +39,14 @@ func loadEndpointsFromCache(currentEnv string) (map[string]string, error) { } // Read from environment-specific cache file - cacheFile := filepath.Join(home, ".cfctl", "cache", currentEnv, "endpoints.toml") + cacheFile := filepath.Join(home, ".cfctl", "cache", currentEnv, "endpoints.yaml") data, err := os.ReadFile(cacheFile) if err != nil { return nil, err } var endpoints map[string]string - if err := toml.Unmarshal(data, &endpoints); err != nil { + if err := yaml.Unmarshal(data, &endpoints); err != nil { return nil, err } @@ -63,12 +62,12 @@ var ApiResourcesCmd = &cobra.Command{ log.Fatalf("Unable to find home directory: %v", err) } - settingPath := filepath.Join(home, ".cfctl", "setting.toml") + settingPath := filepath.Join(home, ".cfctl", "setting.yaml") // Read main setting file mainV := viper.New() mainV.SetConfigFile(settingPath) - mainV.SetConfigType("toml") + mainV.SetConfigType("yaml") mainConfigErr := mainV.ReadInConfig() var currentEnv string diff --git a/cmd/other/setting.go b/cmd/other/setting.go index bf1f494..ca1f6f5 100644 --- a/cmd/other/setting.go +++ b/cmd/other/setting.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "encoding/json" "fmt" + "gopkg.in/yaml.v3" "log" "net/url" "os" @@ -19,7 +20,6 @@ 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" @@ -29,8 +29,8 @@ import ( var SettingCmd = &cobra.Command{ Use: "setting", Short: "Manage cfctl setting file", - Long: `Manage setting file for cfctl. You can initialize, -switch environments, and display the current configuration.`, + Long: `Manage setting file for cfctl. +You can initialize, switch environments, and display the current configuration.`, } // settingInitCmd initializes a new environment configuration @@ -67,7 +67,7 @@ var settingInitURLCmd = &cobra.Command{ envName, err := parseEnvNameFromURL(urlStr) if err != nil { - pterm.Error.Println("Invalid URL:", err) + pterm.Error.Printf("Failed to parse environment name from URL: %v\n", err) return } @@ -78,20 +78,20 @@ var settingInitURLCmd = &cobra.Command{ return } - // Initialize setting.toml if it doesn't exist - mainSettingPath := filepath.Join(settingDir, "setting.toml") + mainSettingPath := filepath.Join(settingDir, "setting.yaml") // Check if environment already exists v := viper.New() v.SetConfigFile(mainSettingPath) - v.SetConfigType("toml") + v.SetConfigType("yaml") + + envSuffix := map[bool]string{true: "app", false: "user"}[appFlag] + fullEnvName := fmt.Sprintf("%s-%s", envName, envSuffix) if err := v.ReadInConfig(); err == nil { // File exists and can be read - envSuffix := map[bool]string{true: "app", false: "user"}[appFlag] - fullEnvName := fmt.Sprintf("%s-%s", envName, envSuffix) - - if envConfig := v.GetStringMap(fmt.Sprintf("environments.%s", fullEnvName)); envConfig != nil { + environments := v.GetStringMap("environments") + if _, exists := environments[fullEnvName]; exists { // Environment exists, ask for confirmation confirmBox := pterm.DefaultBox.WithTitle("Environment Already Exists"). WithTitleTopCenter(). @@ -113,96 +113,30 @@ var settingInitURLCmd = &cobra.Command{ } } - // Update configuration in main setting file - updateSetting(envName, urlStr, map[bool]string{true: "app", false: "user"}[appFlag]) - - // 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) - return - } - - // 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 { - pterm.Error.Printf("Failed to update current environment: %v\n", err) - return - } - - pterm.Success.Printf("Switched to '%s' environment.\n", envName) + // Update configuration + updateSetting(envName, urlStr, envSuffix) }, } // settingInitLocalCmd initializes configuration with a local environment var settingInitLocalCmd = &cobra.Command{ Use: "local", - Short: "Initialize configuration with a local environment", - Long: `Specify a local environment name to initialize the configuration.`, - Args: cobra.NoArgs, - Example: ` cfctl setting init local -n [domain] --app --dev - or - cfctl setting init local -n [domain] --user --stg, - or - cfctl setting init local -n [plugin_name] --plugin`, - Run: func(cmd *cobra.Command, args []string) { - localEnv, _ := cmd.Flags().GetString("name") + Short: "Initialize local environment setting", + Long: `Initialize a local environment setting for cfctl.`, + RunE: func(cmd *cobra.Command, args []string) error { appFlag, _ := cmd.Flags().GetBool("app") userFlag, _ := cmd.Flags().GetBool("user") - devFlag, _ := cmd.Flags().GetBool("dev") - stgFlag, _ := cmd.Flags().GetBool("stg") - pluginFlag, _ := cmd.Flags().GetBool("plugin") - - // Step 1: Check name flag - if localEnv == "" { - pterm.Error.Println("The --name flag is required.") - // Show only name flag in help - cmd.SetUsageFunc(func(cmd *cobra.Command) error { - fmt.Printf("\nUsage:\n cfctl setting init local [flags]\n\n") - fmt.Printf("Flags:\n") - fmt.Printf(" -n, --name string Local environment name for the environment\n") - fmt.Printf(" -h, --help help for local\n") - return nil - }) - cmd.Help() - return - } - // Step 2: Check type flags (app/user/plugin) - if !appFlag && !userFlag && !pluginFlag { - pterm.Error.Println("You must specify either --app, --user, or --plugin flag.") - // Show only type flags in help - cmd.SetUsageFunc(func(cmd *cobra.Command) error { - fmt.Printf("\nUsage:\n cfctl setting init local --name %s [flags]\n\n", localEnv) - fmt.Printf("Flags:\n") - fmt.Printf(" --app Initialize as application configuration\n") - fmt.Printf(" --user Initialize as user-specific configuration\n") - fmt.Printf(" --plugin Initialize as plugin configuration\n") - return nil - }) - cmd.Help() - return + // Validate that either app or user flag is provided + if !appFlag && !userFlag { + pterm.Error.Println("You must specify either --app or --user flag.") + return fmt.Errorf("missing required flag") } - // Step 3: For app/user configs, check environment type - if (appFlag || userFlag) && !devFlag && !stgFlag { - pterm.Error.Println("You must specify either --dev or --stg flag.") - // Show only environment flags in help - cmd.SetUsageFunc(func(cmd *cobra.Command) error { - fmt.Printf("\nUsage:\n cfctl setting init local --name %s --%s [flags]\n\n", - localEnv, map[bool]string{true: "app", false: "user"}[appFlag]) - fmt.Printf("Flags:\n") - fmt.Printf(" --dev Initialize as development environment\n") - fmt.Printf(" --stg Initialize as staging environment\n") - return nil - }) - cmd.Help() - return + // Validate that not both flags are provided + if appFlag && userFlag { + pterm.Error.Println("Cannot use both --app and --user flags together.") + return fmt.Errorf("conflicting flags") } // Plugin flag takes precedence @@ -214,62 +148,108 @@ var settingInitLocalCmd = &cobra.Command{ // Rest of the existing implementation... settingDir := GetSettingDir() if err := os.MkdirAll(settingDir, 0755); err != nil { - pterm.Error.Printf("Failed to create setting directory: %v\n", err) - return + return fmt.Errorf("failed to create setting directory: %v", err) } - // Initialize setting.toml if it doesn't exist - mainSettingPath := filepath.Join(settingDir, "setting.toml") - if _, err := os.Stat(mainSettingPath); os.IsNotExist(err) { - // 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 - } - } + mainSettingPath := filepath.Join(settingDir, "setting.yaml") - envPrefix := "" - if devFlag { - envPrefix = "dev" - } else if stgFlag { - envPrefix = "stg" - } + // Basic local environment + envName := "local" - var envName string + // Add app/user suffix based on flag if appFlag { - envName = fmt.Sprintf("local-%s-%s-app", envPrefix, localEnv) + envName = fmt.Sprintf("%s-app", envName) } else { - envName = fmt.Sprintf("local-%s-%s-user", envPrefix, localEnv) + envName = fmt.Sprintf("%s-user", envName) } + // Initialize or update the settings + v := viper.New() + v.SetConfigFile(mainSettingPath) + v.SetConfigType("yaml") + + // Create initial configuration + envConfig := map[string]interface{}{ + "endpoint": "grpc://localhost:50051", + "url": "http://localhost:8080", + } + + // Add specific fields based on configuration type if appFlag { - updateLocalSetting(envName, "app", mainSettingPath) - } else { - updateLocalSetting(envName, "user", mainSettingPath) + envConfig["token"] = "" } - // Update the current environment in the main setting - mainV := viper.New() - mainV.SetConfigFile(mainSettingPath) - mainV.SetConfigType("toml") + if err := v.ReadInConfig(); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("error reading setting: %v", err) + } - // Read the setting file - if err := mainV.ReadInConfig(); err != nil { - pterm.Error.Printf("Failed to read setting file: %v\n", err) - return + // Set environment configuration + v.Set(fmt.Sprintf("environments.%s", envName), envConfig) + v.Set("environment", envName) + + if err := v.WriteConfig(); err != nil { + if os.IsNotExist(err) { + if err := v.SafeWriteConfig(); err != nil { + return fmt.Errorf("failed to create setting file: %v", err) + } + } else { + return fmt.Errorf("failed to write setting: %v", err) + } + } + + pterm.Success.Printf("Environment '%s' successfully initialized.\n", envName) + return nil + }, +} + +func initializePluginSetting(pluginName string) { + // Add 'local-' prefix to plugin name + envName := fmt.Sprintf("local-%s", pluginName) + + settingDir := GetSettingDir() + if err := os.MkdirAll(settingDir, 0755); err != nil { + pterm.Error.Printf("Failed to create setting directory: %v\n", err) + return + } + + mainSettingPath := filepath.Join(settingDir, "setting.yaml") + if _, err := os.Stat(mainSettingPath); os.IsNotExist(err) { + // Initial YAML structure + initialSetting := map[string]interface{}{ + "environments": map[string]interface{}{}, } - // Set the new environment as current - mainV.Set("environment", envName) + v := viper.New() + v.SetConfigFile(mainSettingPath) + v.SetConfigType("yaml") + v.Set("environments", initialSetting["environments"]) - if err := mainV.WriteConfig(); err != nil { - pterm.Error.Printf("Failed to update current environment: %v\n", err) + if err := v.WriteConfig(); err != nil { + pterm.Error.Printf("Failed to create setting file: %v\n", err) return } + } - pterm.Success.Printf("Switched to '%s' environment.\n", envName) - }, + v := viper.New() + v.SetConfigFile(mainSettingPath) + v.SetConfigType("yaml") + + if err := v.ReadInConfig(); err != nil && !os.IsNotExist(err) { + pterm.Error.Printf("Error reading setting: %v\n", err) + return + } + + // Set environment configuration using the prefixed name + v.Set(fmt.Sprintf("environments.%s.endpoint", envName), "grpc://localhost:50051") + v.Set(fmt.Sprintf("environments.%s.token", envName), "NO TOKEN") + v.Set("environment", envName) + + if err := v.WriteConfig(); err != nil { + pterm.Error.Printf("Failed to write setting: %v\n", err) + return + } + + pterm.Success.Printf("Plugin environment '%s' successfully initialized.\n", envName) } func initializePluginSetting(pluginName string) { @@ -350,7 +330,7 @@ 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.toml") + appSettingPath := filepath.Join(settingDir, "setting.yaml") // Create separate Viper instances appV := viper.New() @@ -380,7 +360,7 @@ var envCmd = &cobra.Command{ if _, existsApp := appEnvMap[switchEnv]; !existsApp { home, _ := os.UserHomeDir() - pterm.Error.Printf("Environment '%s' not found in %s/.cfctl/setting.toml", + pterm.Error.Printf("Environment '%s' not found in %s/.cfctl/setting.yaml", switchEnv, home) return } @@ -389,7 +369,7 @@ var envCmd = &cobra.Command{ appV.Set("environment", switchEnv) if err := appV.WriteConfig(); err != nil { - pterm.Error.Printf("Failed to update environment in setting.toml: %v", err) + pterm.Error.Printf("Failed to update environment in setting.yaml: %v", err) return } @@ -410,7 +390,7 @@ var envCmd = &cobra.Command{ targetSettingPath = appSettingPath } else { home, _ := os.UserHomeDir() - pterm.Error.Printf("Environment '%s' not found in %s/.cfctl/setting.toml", + pterm.Error.Printf("Environment '%s' not found in %s/.cfctl/setting.yaml", switchEnv, home) return } @@ -437,10 +417,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.toml: %v", err) + pterm.Error.Printf("Failed to update environment in setting.yaml: %v", err) return } - pterm.Info.WithShowLineNumber(false).Println("Cleared current environment in setting.toml") + pterm.Info.WithShowLineNumber(false).Println("Cleared current environment in setting.yaml") } // Display success message @@ -498,8 +478,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.toml") - userSettingPath := filepath.Join(settingDir, "cache", "setting.toml") + appSettingPath := filepath.Join(settingDir, "setting.yaml") + userSettingPath := filepath.Join(settingDir, "cache", "setting.yaml") // Create separate Viper instances appV := viper.New() @@ -538,10 +518,10 @@ var showCmd = &cobra.Command{ log.Fatalf("Error formatting output as JSON: %v", err) } fmt.Println(string(data)) - case "toml": - data, err := toml.Marshal(envSetting) + case "yaml": + data, err := yaml.Marshal(envSetting) if err != nil { - log.Fatalf("Error formatting output as TOML: %v", err) + log.Fatalf("Error formatting output as yaml: %v", err) } fmt.Println(string(data)) default: @@ -565,9 +545,9 @@ Available Services are fetched dynamically from the backend.`, appV := viper.New() // Load app configuration - settingPath := filepath.Join(GetSettingDir(), "setting.toml") + settingPath := filepath.Join(GetSettingDir(), "setting.yaml") appV.SetConfigFile(settingPath) - appV.SetConfigType("toml") + appV.SetConfigType("yaml") if err := loadSetting(appV, settingPath); err != nil { pterm.Error.Println(err) @@ -584,7 +564,7 @@ Available Services are fetched dynamically from the backend.`, 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") + settingPath := filepath.Join(GetSettingDir(), "setting.yaml") // Create header for the error message //pterm.DefaultHeader.WithBackgroundStyle(pterm.NewStyle(pterm.BgRed)).WithMargin(10).Println("Token Not Found") @@ -679,14 +659,14 @@ Available Services are fetched dynamically from the backend.`, cacheV := viper.New() // Load app configuration (for getting current environment) - settingPath := filepath.Join(GetSettingDir(), "setting.toml") + settingPath := filepath.Join(GetSettingDir(), "setting.yaml") appV.SetConfigFile(settingPath) - appV.SetConfigType("toml") + appV.SetConfigType("yaml") // Load cache configuration - cachePath := filepath.Join(GetSettingDir(), "cache", "setting.toml") + cachePath := filepath.Join(GetSettingDir(), "cache", "setting.yaml") cacheV.SetConfigFile(cachePath) - cacheV.SetConfigType("toml") + cacheV.SetConfigType("yaml") if err := loadSetting(appV, settingPath); err != nil { pterm.Error.Println(err) @@ -724,12 +704,12 @@ Available Services are fetched dynamically from the backend.`, } if err := appV.WriteConfig(); err != nil { - pterm.Error.Printf("Failed to update setting.toml: %v\n", err) + pterm.Error.Printf("Failed to update setting.yaml: %v\n", err) return } } else { // Update endpoint in cache setting for user environments - cachePath := filepath.Join(GetSettingDir(), "cache", "setting.toml") + cachePath := filepath.Join(GetSettingDir(), "cache", "setting.yaml") if err := loadSetting(cacheV, cachePath); err != nil { pterm.Error.Println(err) return @@ -743,7 +723,7 @@ Available Services are fetched dynamically from the backend.`, } if err := cacheV.WriteConfig(); err != nil { - pterm.Error.Printf("Failed to update cache/setting.toml: %v\n", err) + pterm.Error.Printf("Failed to update cache/setting.yaml: %v\n", err) return } } @@ -762,11 +742,11 @@ This command only works with app environments (-app suffix).`, Run: func(cmd *cobra.Command, args []string) { // Load current environment configuration file settingDir := GetSettingDir() - settingPath := filepath.Join(settingDir, "setting.toml") + settingPath := filepath.Join(settingDir, "setting.yaml") v := viper.New() v.SetConfigFile(settingPath) - v.SetConfigType("toml") + v.SetConfigType("yaml") if err := v.ReadInConfig(); err != nil { pterm.Error.Printf("Failed to read setting file: %v\n", err) @@ -969,7 +949,7 @@ func getBaseURL(v *viper.Viper) (string, error) { baseURL := v.GetString(fmt.Sprintf("environments.%s.endpoint", currentEnv)) if baseURL == "" { - return "", fmt.Errorf("no endpoint found for environment '%s' in setting.toml", currentEnv) + return "", fmt.Errorf("no endpoint found for environment '%s' in setting.yaml", currentEnv) } @@ -1010,7 +990,7 @@ func loadSetting(v *viper.Viper, settingPath string) error { // Set the setting file v.SetConfigFile(settingPath) - v.SetConfigType("toml") + v.SetConfigType("yaml") // Read the setting file if err := v.ReadInConfig(); err != nil { @@ -1021,14 +1001,12 @@ func loadSetting(v *viper.Viper, settingPath string) error { "environment": "", } - // Convert to TOML - data, err := toml.Marshal(defaultSettings) - if err != nil { - return fmt.Errorf("failed to marshal default settings: %w", err) + // Write the default settings to file + if err := v.MergeConfigMap(defaultSettings); err != nil { + return fmt.Errorf("failed to merge default settings: %w", err) } - // Write the default settings to file - if err := os.WriteFile(settingPath, data, 0644); err != nil { + if err := v.WriteConfig(); err != nil { return fmt.Errorf("failed to write default settings: %w", err) } @@ -1051,24 +1029,23 @@ func getCurrentEnvironment(v *viper.Viper) string { // updateGlobalSetting prints a success message for global setting update func updateGlobalSetting() { - settingPath := filepath.Join(GetSettingDir(), "setting.toml") + settingPath := filepath.Join(GetSettingDir(), "setting.yaml") 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.toml)", GetSettingDir()) + pterm.Success.WithShowLineNumber(false).Printfln("Global setting updated with existing environments. (default: %s/setting.yaml)", 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.toml)", GetSettingDir()) + pterm.Success.WithShowLineNumber(false).Printfln("Global setting updated with existing environments. (default: %s/setting.yaml)", GetSettingDir()) } -// parseEnvNameFromURL parses environment name from the given URL and validates based on URL structure func parseEnvNameFromURL(urlStr string) (string, error) { if !strings.Contains(urlStr, "://") { urlStr = "https://" + urlStr @@ -1084,7 +1061,7 @@ func parseEnvNameFromURL(urlStr string) (string, error) { re := regexp.MustCompile(`^(.*?)\.spaceone`) matches := re.FindStringSubmatch(hostname) if len(matches) == 2 { - return fmt.Sprintf("prd-%s", matches[1]), nil + return matches[1], nil } } @@ -1093,7 +1070,7 @@ func parseEnvNameFromURL(urlStr string) (string, error) { re := regexp.MustCompile(`(.*)\.console\.dev\.spaceone\.dev`) matches := re.FindStringSubmatch(hostname) if len(matches) == 2 { - return fmt.Sprintf("dev-%s", matches[1]), nil + return matches[1], nil } pterm.Error.WithShowLineNumber(false).Println("Invalid URL format for dev environment. Expected format: '.console.dev.spaceone.dev'") return "", fmt.Errorf("invalid dev URL format") @@ -1104,7 +1081,7 @@ func parseEnvNameFromURL(urlStr string) (string, error) { re := regexp.MustCompile(`(.*)\.console\.stg\.spaceone\.dev`) matches := re.FindStringSubmatch(hostname) if len(matches) == 2 { - return fmt.Sprintf("stg-%s", matches[1]), nil + return matches[1], nil } pterm.Error.WithShowLineNumber(false).Println("Invalid URL format for stg environment. Expected format: '.console.stg.spaceone.dev'") return "", fmt.Errorf("invalid stg URL format") @@ -1116,26 +1093,30 @@ func parseEnvNameFromURL(urlStr string) (string, error) { // updateSetting updates the configuration files func updateSetting(envName, urlStr, settingType string) { settingDir := GetSettingDir() - mainSettingPath := filepath.Join(settingDir, "setting.toml") + mainSettingPath := filepath.Join(settingDir, "setting.yaml") + + // Initialize viper instance + v := viper.New() + v.SetConfigFile(mainSettingPath) + v.SetConfigType("yaml") - // Initialize main viper instance - mainV := viper.New() - mainV.SetConfigFile(mainSettingPath) - mainV.SetConfigType("toml") + // Create directory if it doesn't exist + if err := os.MkdirAll(settingDir, 0755); err != nil { + pterm.Error.Printf("Failed to create setting directory: %v\n", err) + return + } - // Read existing configuration file - if err := mainV.ReadInConfig(); err != nil { - if !os.IsNotExist(err) { + // Read existing configuration or create new one + if err := v.ReadInConfig(); err != nil { + if os.IsNotExist(err) { + // Initialize with empty environments map + v.Set("environments", map[string]interface{}{}) + } else { pterm.Error.Printf("Error reading setting file: %v\n", err) return } } - // 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 { @@ -1146,16 +1127,11 @@ func updateSetting(envName, urlStr, settingType string) { // Append -app or -user to the environment name envName = fmt.Sprintf("%s-%s", envName, settingType) - // Get environments map - environments := mainV.GetStringMap("environments") - if environments == nil { - environments = make(map[string]interface{}) - } - // Add new environment configuration envConfig := map[string]interface{}{ "endpoint": endpoint, "proxy": true, + "url": fmt.Sprintf("https://%s", urlStr), } // Only add token field for app configuration @@ -1163,16 +1139,22 @@ func updateSetting(envName, urlStr, settingType string) { envConfig["token"] = "" } - environments[envName] = envConfig - - // Update entire configuration - mainV.Set("environments", environments) - mainV.Set("environment", envName) + // Update configuration + v.Set(fmt.Sprintf("environments.%s", envName), envConfig) + v.Set("environment", envName) - // Save configuration file - if err := mainV.WriteConfig(); err != nil { - pterm.Error.Printf("Failed to write setting: %v\n", err) - return + // Save configuration + if err := v.WriteConfig(); err != nil { + if os.IsNotExist(err) { + // Try to create the file if it doesn't exist + if err := v.SafeWriteConfig(); err != nil { + pterm.Error.Printf("Failed to create setting file: %v\n", err) + return + } + } else { + pterm.Error.Printf("Failed to write setting: %v\n", err) + return + } } } @@ -1260,18 +1242,14 @@ func init() { 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, "Initialize as application configuration") settingInitLocalCmd.Flags().Bool("user", false, "Initialize as user-specific configuration") - settingInitLocalCmd.Flags().Bool("dev", false, "Initialize as development environment") - settingInitLocalCmd.Flags().Bool("stg", false, "Initialize as staging environment") - settingInitLocalCmd.Flags().Bool("plugin", false, "Initialize as plugin configuration") envCmd.Flags().StringP("switch", "s", "", "Switch to a different environment") envCmd.Flags().StringP("remove", "r", "", "Remove an environment") envCmd.Flags().BoolP("list", "l", false, "List available environments") - showCmd.Flags().StringP("output", "o", "toml", "Output format (toml/json)") + showCmd.Flags().StringP("output", "o", "yaml", "Output format (yaml/json)") settingEndpointCmd.Flags().StringP("service", "s", "", "Service to set the endpoint for") diff --git a/cmd/root.go b/cmd/root.go index be74c50..c802550 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,6 +3,8 @@ package cmd import ( "context" "fmt" + "gopkg.in/yaml.v3" + "log" "os" "path/filepath" "strings" @@ -14,7 +16,6 @@ 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" "google.golang.org/grpc" @@ -54,9 +55,9 @@ func Execute() { // Check if the first argument is a service name and second is a short name v := viper.New() if home, err := os.UserHomeDir(); err == nil { - settingPath := filepath.Join(home, ".cfctl", "setting.toml") + settingPath := filepath.Join(home, ".cfctl", "setting.yaml") v.SetConfigFile(settingPath) - v.SetConfigType("toml") + v.SetConfigType("yaml") if err := v.ReadInConfig(); err == nil { serviceName := args[0] @@ -84,12 +85,20 @@ func init() { } rootCmd.AddGroup(AvailableCommands) + done := make(chan bool) go func() { - if _, err := loadCachedEndpoints(); err == nil { - return + if endpoints, err := loadCachedEndpoints(); err == nil { + cachedEndpointsMap = endpoints } + done <- true }() + select { + case <-done: + case <-time.After(50 * time.Millisecond): + fmt.Fprintf(os.Stderr, "Warning: Cache loading timed out\n") + } + if len(os.Args) > 1 && os.Args[1] == "__complete" { pterm.DisableColor() } @@ -128,6 +137,14 @@ func init() { cmd.GroupID = "other" } } + + home, err := os.UserHomeDir() + if err != nil { + log.Fatalf("Unable to find home directory: %v", err) + } + viper.AddConfigPath(filepath.Join(home, ".cfctl")) + viper.SetConfigName("setting") + viper.SetConfigType("yaml") } // showInitializationGuide displays a helpful message when configuration is missing @@ -146,10 +163,10 @@ func showInitializationGuide(originalErr error) { return } - settingFile := filepath.Join(home, ".cfctl", "setting.toml") + settingFile := filepath.Join(home, ".cfctl", "setting.yaml") mainV := viper.New() mainV.SetConfigFile(settingFile) - mainV.SetConfigType("toml") + mainV.SetConfigType("yaml") if err := mainV.ReadInConfig(); err != nil { pterm.Warning.Printf("No valid configuration found.\n") @@ -164,32 +181,23 @@ func showInitializationGuide(originalErr error) { return } - // Check if token exists for the current environment - envConfig := mainV.Sub(fmt.Sprintf("environments.%s", currentEnv)) - if envConfig != nil && envConfig.GetString("token") != "" { - // Token exists, no need to show guide - return - } - - // Parse environment name to extract service name and environment - parts := strings.Split(currentEnv, "-") - if len(parts) >= 3 { - var url string - if parts[0] == "local" { - if len(parts) >= 4 { - envPrefix := parts[1] // dev - serviceName := parts[2] // cloudone - url = fmt.Sprintf("https://%s.console.%s.spaceone.dev\n"+ - " Note: If you're running a local console server,\n"+ - " you can also access it at http://localhost:8080", serviceName, envPrefix) + // Check if current environment is app type and token is empty + if strings.HasSuffix(currentEnv, "-app") { + envConfig := mainV.Sub(fmt.Sprintf("environments.%s", currentEnv)) + if envConfig == nil || envConfig.GetString("token") == "" { + // Get URL from environment config + url := envConfig.GetString("url") + if url == "" { + // Fallback URL if not specified in config + parts := strings.Split(currentEnv, "-") + if len(parts) >= 2 { + serviceName := parts[0] // cloudone, spaceone, etc. + url = fmt.Sprintf("https://%s.console.dev.spaceone.dev", serviceName) + } else { + url = "https://console.spaceone.dev" + } } - } else { - envPrefix := parts[0] // dev - serviceName := parts[1] // cloudone - url = fmt.Sprintf("https://%s.console.%s.spaceone.dev", serviceName, envPrefix) - } - if strings.HasSuffix(currentEnv, "-app") { pterm.DefaultBox. WithTitle("Token Not Found"). WithTitleTopCenter(). @@ -222,11 +230,11 @@ func showInitializationGuide(originalErr error) { Println(boxContent) pterm.Info.Println("After updating the token, please try your command again.") - } else { - pterm.Warning.Printf("Authentication required.\n") - pterm.Info.Println("To see Available Commands, please authenticate first:") - pterm.Info.Println("$ cfctl login") } + } else if strings.HasSuffix(currentEnv, "-user") { + pterm.Warning.Printf("Authentication required.\n") + pterm.Info.Println("To see Available Commands, please authenticate first:") + pterm.Info.Println("$ cfctl login") } } @@ -328,53 +336,32 @@ func addDynamicServiceCommands() error { return nil } -func clearEndpointsCache() { + +func loadCachedEndpoints() (map[string]string, error) { home, err := os.UserHomeDir() if err != nil { - return - } - - 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 + return nil, err } - // Remove environment-specific cache directory - envCacheDir := filepath.Join(home, ".cfctl", "cache", currentEnv) - os.RemoveAll(envCacheDir) - cachedEndpointsMap = nil -} - -func loadCachedEndpoints() (map[string]string, error) { - home, err := os.UserHomeDir() + settingFile := filepath.Join(home, ".cfctl", "setting.yaml") + settingData, err := os.ReadFile(settingFile) if err != nil { return nil, err } - // 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 { + var settings struct { + Environment string `yaml:"environment"` + } + + if err := yaml.Unmarshal(settingData, &settings); err != nil { return nil, err } - currentEnv := mainV.GetString("environment") - if currentEnv == "" { + if settings.Environment == "" { return nil, fmt.Errorf("no environment set") } - // Create environment-specific cache directory - envCacheDir := filepath.Join(home, ".cfctl", "cache", currentEnv) - - cacheFile := filepath.Join(envCacheDir, "endpoints.toml") + cacheFile := filepath.Join(home, ".cfctl", "cache", settings.Environment, "endpoints.yaml") data, err := os.ReadFile(cacheFile) if err != nil { return nil, err @@ -390,7 +377,7 @@ func loadCachedEndpoints() (map[string]string, error) { } var endpoints map[string]string - if err := toml.Unmarshal(data, &endpoints); err != nil { + if err := yaml.Unmarshal(data, &endpoints); err != nil { return nil, err } @@ -405,8 +392,8 @@ func saveEndpointsCache(endpoints map[string]string) error { // Get current environment from main setting file mainV := viper.New() - mainV.SetConfigFile(filepath.Join(home, ".cfctl", "setting.toml")) - mainV.SetConfigType("toml") + mainV.SetConfigFile(filepath.Join(home, ".cfctl", "setting.yaml")) + mainV.SetConfigType("yaml") if err := mainV.ReadInConfig(); err != nil { return err } @@ -422,12 +409,12 @@ func saveEndpointsCache(endpoints map[string]string) error { return err } - data, err := toml.Marshal(endpoints) + data, err := yaml.Marshal(endpoints) if err != nil { return err } - return os.WriteFile(filepath.Join(envCacheDir, "endpoints.toml"), data, 0644) + return os.WriteFile(filepath.Join(envCacheDir, "endpoints.yaml"), data, 0644) } // loadConfig loads configuration from both main and cache setting files @@ -437,12 +424,12 @@ func loadConfig() (*Config, error) { return nil, fmt.Errorf("unable to find home directory: %v", err) } - settingFile := filepath.Join(home, ".cfctl", "setting.toml") + settingFile := filepath.Join(home, ".cfctl", "setting.yaml") // Read main setting file mainV := viper.New() mainV.SetConfigFile(settingFile) - mainV.SetConfigType("toml") + mainV.SetConfigType("yaml") if err := mainV.ReadInConfig(); err != nil { return nil, fmt.Errorf("failed to read setting file") } @@ -475,7 +462,6 @@ func loadConfig() (*Config, error) { } token = string(data) } else if strings.HasSuffix(currentEnv, "-app") { - // For app environments, read from setting.toml token = envConfig.GetString("token") if token == "" { return nil, fmt.Errorf("no token found in configuration") @@ -495,74 +481,8 @@ func createServiceCommand(serviceName string) *cobra.Command { cmd := &cobra.Command{ Use: serviceName, Short: fmt.Sprintf("Interact with the %s service", serviceName), - Long: fmt.Sprintf(`Use this command to interact with the %s service. - -%s - -%s`, - serviceName, - pterm.DefaultBox.WithTitle("Interactive Mode").WithTitleTopCenter().Sprint( - func() string { - str, _ := pterm.DefaultBulletList.WithItems([]pterm.BulletListItem{ - {Level: 0, Text: "Required parameters will be prompted if not provided"}, - {Level: 0, Text: "Missing parameters will be requested interactively"}, - {Level: 0, Text: "Just follow the prompts to fill in the required fields"}, - }).Srender() - return str - }()), - pterm.DefaultBox.WithTitle("Example").WithTitleTopCenter().Sprint( - fmt.Sprintf("Instead of:\n"+ - " $ cfctl %s -p key=value\n\n"+ - "You can simply run:\n"+ - " $ cfctl %s \n\n"+ - "The tool will interactively prompt for the required parameters.", - serviceName, serviceName))), + Long: fmt.Sprintf("Use this command to interact with the %s service.", serviceName), GroupID: "available", - RunE: func(cmd *cobra.Command, args []string) error { - // If no args provided, show available verbs - if len(args) == 0 { - common.PrintAvailableVerbs(cmd) - return nil - } - - // Process command arguments - if len(args) < 2 { - return cmd.Help() - } - - verb := args[0] - resource := args[1] - - // Create options from remaining args - options := &common.FetchOptions{ - Parameters: make([]string, 0), - } - - // Process remaining args as parameters - for i := 2; i < len(args); i++ { - if strings.HasPrefix(args[i], "--") { - paramName := strings.TrimPrefix(args[i], "--") - if i+1 < len(args) && !strings.HasPrefix(args[i+1], "--") { - options.Parameters = append(options.Parameters, fmt.Sprintf("%s=%s", paramName, args[i+1])) - i++ - } - } - } - - // Call FetchService with the processed arguments - result, err := common.FetchService(serviceName, verb, resource, options) - if err != nil { - pterm.Error.Printf("Failed to execute command: %v\n", err) - return err - } - - if result != nil { - // The result will be printed by FetchService if needed - return nil - } - - return nil - }, } cmd.AddGroup(&cobra.Group{ diff --git a/go.mod b/go.mod index c192451..9829fcd 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,9 @@ 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 @@ -41,6 +39,7 @@ 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 588c243..335061d 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,6 @@ 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=