diff --git a/cmd/common/fetchService.go b/cmd/common/fetchService.go index 07ff4aa..2d1b37d 100644 --- a/cmd/common/fetchService.go +++ b/cmd/common/fetchService.go @@ -161,20 +161,24 @@ func FetchService(serviceName string, verb string, resourceName string, options if strings.HasPrefix(config.Environment, "local-") { hostPort = "localhost:50051" } else { - var envPrefix string - 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 strings.Contains(config.Environments[config.Environment].URL, "megazone.io") { + hostPort = fmt.Sprintf("%s.kr1.api.spaceone.megazone.io:443", convertServiceNameToEndpoint(serviceName)) + } else { + var envPrefix string + 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) - } + 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", convertServiceNameToEndpoint(serviceName), envPrefix) + hostPort = fmt.Sprintf("%s.api.%s.spaceone.dev:443", convertServiceNameToEndpoint(serviceName), envPrefix) + } } // Configure gRPC connection @@ -349,8 +353,8 @@ func loadConfig() (*Config, error) { // Handle token based on environment type if strings.HasSuffix(currentEnv, "-user") { - // For user environments, read from grant_token file - grantTokenPath := filepath.Join(home, ".cfctl", "cache", currentEnv, "grant_token") + // For user environments, read from access_token file (Actual token is grant_token) + grantTokenPath := filepath.Join(home, ".cfctl", "cache", currentEnv, "access_token") tokenBytes, err := os.ReadFile(grantTokenPath) if err == nil { envConfig.Token = strings.TrimSpace(string(tokenBytes)) @@ -375,6 +379,7 @@ func loadConfig() (*Config, error) { func fetchJSONResponse(config *Config, serviceName string, verb string, resourceName string, options *FetchOptions) ([]byte, error) { var conn *grpc.ClientConn var err error + var hostPort string if strings.HasPrefix(config.Environment, "local-") { conn, err = grpc.Dial("localhost:50051", grpc.WithInsecure(), @@ -386,20 +391,24 @@ func fetchJSONResponse(config *Config, serviceName string, verb string, resource return nil, fmt.Errorf("connection failed: unable to connect to local server: %v", err) } } else { - var envPrefix string - 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 strings.Contains(config.Environments[config.Environment].URL, "megazone.io") { + hostPort = fmt.Sprintf("%s.kr1.api.spaceone.megazone.io:443", convertServiceNameToEndpoint(serviceName)) + } else { + var envPrefix string + 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) - } + 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", convertServiceNameToEndpoint(serviceName), envPrefix) + hostPort = fmt.Sprintf("%s.api.%s.spaceone.dev:443", convertServiceNameToEndpoint(serviceName), envPrefix) + } tlsConfig := &tls.Config{ InsecureSkipVerify: false, diff --git a/cmd/common/helpers.go b/cmd/common/helpers.go index cd84732..2e0617c 100644 --- a/cmd/common/helpers.go +++ b/cmd/common/helpers.go @@ -93,7 +93,6 @@ func handleLocalEnvironment(serviceName string) (map[string][]string, error) { return nil, fmt.Errorf("only plugin service is supported in local environment") } - // local 환경의 plugin 서비스 endpoint 설정 conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure()) if err != nil { return nil, fmt.Errorf("failed to connect to local plugin service: %v", err) @@ -143,19 +142,26 @@ func fetchVerbResourceMap(serviceName string, config *Config) (map[string][]stri // 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 + var hostPort string + + if strings.Contains(envConfig.URL, "megazone.io") { + endpointServiceName := convertServiceNameToEndpoint(serviceName) + hostPort = fmt.Sprintf("%s.kr1.api.spaceone.megazone.io:443", endpointServiceName) + } else { + 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) - } + 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) + endpointServiceName := convertServiceNameToEndpoint(serviceName) + hostPort = fmt.Sprintf("%s.api.%s.spaceone.dev:443", endpointServiceName, envPrefix) + } tlsConfig := &tls.Config{ InsecureSkipVerify: false, diff --git a/cmd/other/apiResources.go b/cmd/other/apiResources.go index 93369fd..3a51353 100644 --- a/cmd/other/apiResources.go +++ b/cmd/other/apiResources.go @@ -3,11 +3,13 @@ package other import ( + "bytes" "context" "crypto/tls" "encoding/json" "fmt" "log" + "net/http" "os" "path/filepath" "sort" @@ -189,6 +191,53 @@ var ApiResourcesCmd = &cobra.Command{ } func FetchEndpointsMap(endpoint string) (map[string]string, error) { + if strings.Contains(endpoint, "megazone.io") { + payload := map[string]string{} + jsonPayload, err := json.Marshal(payload) + if err != nil { + return nil, err + } + + listEndpointsUrl := endpoint + "/endpoint/list" + req, err := http.NewRequest("POST", listEndpointsUrl, bytes.NewBuffer(jsonPayload)) + if err != nil { + return nil, err + } + + req.Header.Set("accept", "application/json") + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to fetch endpoints, status code: %d", resp.StatusCode) + } + + var result struct { + Results []struct { + Service string `json:"service"` + Endpoint string `json:"endpoint"` + } `json:"results"` + } + + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return nil, err + } + + // Convert to endpointsMap format + endpointsMap := make(map[string]string) + for _, service := range result.Results { + endpointsMap[service.Service] = service.Endpoint + } + + return endpointsMap, nil + } + // Parse the endpoint parts := strings.Split(endpoint, "://") if len(parts) != 2 { @@ -366,54 +415,54 @@ func fetchServiceResources(service, endpoint string, shortNamesMap map[string]st services := resp.GetListServicesResponse().Service registeredShortNames, err := listShortNames() - if err != nil { - return nil, fmt.Errorf("failed to load short names: %v", err) - } - - data := [][]string{} - for _, s := range services { - if strings.HasPrefix(s.Name, "grpc.reflection.v1alpha.") { - continue - } - resourceName := s.Name[strings.LastIndex(s.Name, ".")+1:] - verbs := getServiceMethods(client, s.Name) - - // Find all matching short names for this resource - verbsWithShortNames := make(map[string]string) - remainingVerbs := make([]string, 0) - - // Get service-specific short names - serviceShortNames := registeredShortNames[service] - if serviceMap, ok := serviceShortNames.(map[string]interface{}); ok { - for _, verb := range verbs { - hasShortName := false - for sn, cmd := range serviceMap { - if strings.Contains(cmd.(string), fmt.Sprintf("%s %s", verb, resourceName)) { - verbsWithShortNames[verb] = sn - hasShortName = true - break - } - } - if !hasShortName { - remainingVerbs = append(remainingVerbs, verb) - } - } - } else { - remainingVerbs = verbs - } - - // Add row for verbs without short names - if len(remainingVerbs) > 0 { - data = append(data, []string{service, resourceName, "", strings.Join(remainingVerbs, ", ")}) - } - - // Add separate rows for each verb with a short name - for verb, shortName := range verbsWithShortNames { - data = append(data, []string{service, resourceName, shortName, verb}) - } - } - - return data, nil + if err != nil { + return nil, fmt.Errorf("failed to load short names: %v", err) + } + + data := [][]string{} + for _, s := range services { + if strings.HasPrefix(s.Name, "grpc.reflection.v1alpha.") { + continue + } + resourceName := s.Name[strings.LastIndex(s.Name, ".")+1:] + verbs := getServiceMethods(client, s.Name) + + // Find all matching short names for this resource + verbsWithShortNames := make(map[string]string) + remainingVerbs := make([]string, 0) + + // Get service-specific short names + serviceShortNames := registeredShortNames[service] + if serviceMap, ok := serviceShortNames.(map[string]interface{}); ok { + for _, verb := range verbs { + hasShortName := false + for sn, cmd := range serviceMap { + if strings.Contains(cmd.(string), fmt.Sprintf("%s %s", verb, resourceName)) { + verbsWithShortNames[verb] = sn + hasShortName = true + break + } + } + if !hasShortName { + remainingVerbs = append(remainingVerbs, verb) + } + } + } else { + remainingVerbs = verbs + } + + // Add row for verbs without short names + if len(remainingVerbs) > 0 { + data = append(data, []string{service, resourceName, "", strings.Join(remainingVerbs, ", ")}) + } + + // Add separate rows for each verb with a short name + for verb, shortName := range verbsWithShortNames { + data = append(data, []string{service, resourceName, shortName, verb}) + } + } + + return data, nil } func getServiceMethods(client grpc_reflection_v1alpha.ServerReflectionClient, serviceName string) []string { diff --git a/cmd/other/login.go b/cmd/other/login.go index 0d9e2ac..3b8ddf8 100644 --- a/cmd/other/login.go +++ b/cmd/other/login.go @@ -1,6 +1,7 @@ package other import ( + "bytes" "context" "crypto/aes" "crypto/cipher" @@ -11,6 +12,7 @@ import ( "errors" "fmt" "io" + "net/http" "os" "path/filepath" "strconv" @@ -76,7 +78,7 @@ func executeLogin(cmd *cobra.Command, args []string) { return } - configPath := filepath.Join(homeDir, ".cfctl", "setting.toml") + configPath := filepath.Join(homeDir, ".cfctl", "setting.yaml") // Check if config file exists if _, err := os.Stat(configPath); os.IsNotExist(err) { @@ -87,7 +89,7 @@ func executeLogin(cmd *cobra.Command, args []string) { } viper.SetConfigFile(configPath) - viper.SetConfigType("toml") + viper.SetConfigType("yaml") if err := viper.ReadInConfig(); err != nil { pterm.Error.Printf("Failed to read config file: %v\n", err) return @@ -414,29 +416,203 @@ func executeUserLogin(currentEnv string) { homeDir, _ := os.UserHomeDir() // Get user_id from current environment mainViper := viper.New() - settingPath := filepath.Join(homeDir, ".cfctl", "setting.toml") + settingPath := filepath.Join(homeDir, ".cfctl", "setting.yaml") mainViper.SetConfigFile(settingPath) - mainViper.SetConfigType("toml") + mainViper.SetConfigType("yaml") if err := mainViper.ReadInConfig(); err != nil { pterm.Error.Printf("Failed to read config file: %v\n", err) exitWithError() } + if strings.Contains(baseUrl, "megazone.io") { + client := &http.Client{} + + // Check for existing user_id in config + userID := mainViper.GetString(fmt.Sprintf("environments.%s.user_id", currentEnv)) + var tempUserID string + if userID == "" { + userIDInput := pterm.DefaultInteractiveTextInput + tempUserID, _ = userIDInput.Show("Enter your User ID") + } else { + tempUserID = userID + pterm.Info.Printf("Logging in as: %s\n", userID) + } + + passwordInput := pterm.DefaultInteractiveTextInput.WithMask("*") + password, _ := passwordInput.Show("Enter your password") + + url := mainViper.GetString(fmt.Sprintf("environments.%s.url", currentEnv)) + if url == "" { + pterm.Error.Println("URL not found in configuration") + exitWithError() + } + + url = strings.TrimPrefix(url, "https://") + url = strings.TrimPrefix(url, "http://") + + parts := strings.Split(url, ".") + if len(parts) < 3 { + pterm.Error.Printf("Invalid URL format: %s\n", url) + exitWithError() + } + domainName := parts[0] + + domainPayload := map[string]string{"name": domainName} + jsonPayload, _ := json.Marshal(domainPayload) + + req, err := http.NewRequest("POST", baseUrl+"/domain/get-auth-info", bytes.NewBuffer(jsonPayload)) + if err != nil { + pterm.Error.Printf("Failed to create request: %v\n", err) + exitWithError() + } + req.Header.Set("Content-Type", "application/json") + + resp, err := client.Do(req) + if err != nil { + pterm.Error.Printf("Failed to fetch domain info: %v\n", err) + exitWithError() + } + defer resp.Body.Close() + + var result map[string]interface{} + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + pterm.Error.Printf("Failed to decode response: %v\n", err) + exitWithError() + } + + domainID, ok := result["domain_id"].(string) + if !ok { + pterm.Error.Println("Domain ID not found in response") + exitWithError() + } + + tokenPayload := map[string]interface{}{ + "credentials": map[string]string{ + "user_id": tempUserID, + "password": password, + }, + "auth_type": "LOCAL", + "domain_id": domainID, + } + + jsonPayload, _ = json.Marshal(tokenPayload) + req, _ = http.NewRequest("POST", baseUrl+"/token/issue", bytes.NewBuffer(jsonPayload)) + req.Header.Set("Content-Type", "application/json") + + resp, err = client.Do(req) + if err != nil { + pterm.Error.Printf("Failed to issue token: %v\n", err) + exitWithError() + } + defer resp.Body.Close() + + var tokenResult map[string]interface{} + if err := json.NewDecoder(resp.Body).Decode(&tokenResult); err != nil { + pterm.Error.Printf("Failed to decode token response: %v\n", err) + exitWithError() + } + + accessToken, ok := tokenResult["access_token"].(string) + if !ok { + pterm.Error.Println("Access token not found in response") + exitWithError() + } + + refreshToken, ok := tokenResult["refresh_token"].(string) + if !ok { + pterm.Error.Println("Refresh token not found in response") + exitWithError() + } + + existingAccessToken, existingRefreshToken, err := getValidTokens(currentEnv) + if err == nil && existingRefreshToken != "" && !isTokenExpired(existingRefreshToken) { + accessToken = existingAccessToken + refreshToken = existingRefreshToken + } else { + if userID == "" { + mainViper.Set(fmt.Sprintf("environments.%s.user_id", currentEnv), tempUserID) + if err := mainViper.WriteConfig(); err != nil { + pterm.Error.Printf("Failed to save user ID to config: %v\n", err) + exitWithError() + } + } + } + + // Extract domain name from environment + nameParts := strings.Split(currentEnv, "-") + if len(nameParts) < 2 { + pterm.Error.Println("Environment name format is invalid.") + exitWithError() + } + + // Create cache directory and save tokens + 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.Info.Printf("Logged in as %s\n", tempUserID) + + // Use the tokens to fetch workspaces and role + workspaces, err := fetchWorkspaces(baseUrl, accessToken) + if err != nil { + pterm.Error.Println("Failed to fetch workspaces:", err) + exitWithError() + } + + 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 select workspace + scope := determineScope(roleType, len(workspaces)) + var workspaceID string + if roleType == "DOMAIN_ADMIN" { + workspaceID = selectScopeOrWorkspace(workspaces, roleType) + if workspaceID == "0" { + scope = "DOMAIN" + workspaceID = "" + } else { + scope = "WORKSPACE" + } + } else { + workspaceID = selectWorkspaceOnly(workspaces) + scope = "WORKSPACE" + } + + // Grant new token using the refresh token + newAccessToken, err := grantToken(baseUrl, refreshToken, scope, domainID, workspaceID) + if err != nil { + pterm.Error.Println("Failed to retrieve new access token:", err) + exitWithError() + } + + // Save all tokens + 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, "access_token"), []byte(newAccessToken), 0600); err != nil { + pterm.Error.Printf("Failed to save access token: %v\n", err) + exitWithError() + } + + pterm.Success.Println("Successfully logged in and saved token.") + return + } + // Extract domain name from environment nameParts := strings.Split(currentEnv, "-") - if len(nameParts) < 3 { + if len(nameParts) < 2 { pterm.Error.Println("Environment name format is invalid.") exitWithError() } - name := nameParts[1] - - // Fetch Domain ID - domainID, err := fetchDomainID(baseUrl, name) - if err != nil { - pterm.Error.Println("Failed to fetch Domain ID:", err) - exitWithError() - } + name := nameParts[0] // Check for existing user_id in config userID := mainViper.GetString(fmt.Sprintf("environments.%s.user_id", currentEnv)) @@ -450,7 +626,14 @@ func executeUserLogin(currentEnv string) { pterm.Info.Printf("Logging in as: %s\n", userID) } - accessToken, refreshToken, newAccessToken, err := getValidTokens(currentEnv) + // Fetch Domain ID + domainID, err := fetchDomainID(baseUrl, name) + if err != nil { + pterm.Error.Println("Failed to fetch Domain ID:", err) + exitWithError() + } + + accessToken, refreshToken, err := getValidTokens(currentEnv) if err != nil || refreshToken == "" || isTokenExpired(refreshToken) { // Get new tokens with password password := promptPassword() @@ -509,25 +692,20 @@ func executeUserLogin(currentEnv string) { } // Grant new token using the refresh token - newAccessToken, err = grantToken(baseUrl, refreshToken, scope, domainID, workspaceID) + newAccessToken, err := grantToken(baseUrl, refreshToken, scope, domainID, workspaceID) if err != nil { pterm.Error.Println("Failed to retrieve new access token:", err) exitWithError() } // Save all tokens - 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() - } - 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(newAccessToken), 0600); err != nil { - pterm.Error.Printf("Failed to save grant token: %v\n", err) + if err := os.WriteFile(filepath.Join(envCacheDir, "access_token"), []byte(newAccessToken), 0600); err != nil { + pterm.Error.Printf("Failed to save access token: %v\n", err) exitWithError() } @@ -640,10 +818,10 @@ func saveCredentials(currentEnv, userID, encryptedPassword, accessToken, refresh } // Update main settings file - settingPath := filepath.Join(homeDir, ".cfctl", "setting.toml") + settingPath := filepath.Join(homeDir, ".cfctl", "setting.yaml") mainViper := viper.New() mainViper.SetConfigFile(settingPath) - mainViper.SetConfigType("toml") + mainViper.SetConfigType("yaml") if err := mainViper.ReadInConfig(); err != nil { pterm.Error.Printf("Failed to read config file: %v\n", err) @@ -749,9 +927,9 @@ func loadEnvironmentConfig() { exitWithError() } - settingPath := filepath.Join(homeDir, ".cfctl", "setting.toml") + settingPath := filepath.Join(homeDir, ".cfctl", "setting.yaml") viper.SetConfigFile(settingPath) - viper.SetConfigType("toml") + viper.SetConfigType("yaml") if err := viper.ReadInConfig(); err != nil { pterm.Error.Printf("Failed to read setting file: %v\n", err) @@ -820,7 +998,7 @@ func isTokenExpired(token string) bool { if exp, ok := claims["exp"].(float64); ok { return time.Now().Unix() > int64(exp) } - return true // exp 필드가 없거나 잘못된 형식이면 만료된 것으로 간주 + return true } func verifyToken(token string) bool { @@ -995,6 +1173,61 @@ func issueToken(baseUrl, userID, password, domainID string) (string, string, err } func fetchWorkspaces(baseUrl string, accessToken string) ([]map[string]interface{}, error) { + if strings.Contains(baseUrl, "megazone.io") { + payload := map[string]string{} + jsonPayload, err := json.Marshal(payload) + if err != nil { + return nil, err + } + + getWorkspacesUrl := baseUrl + "/user-profile/get-workspaces" + req, err := http.NewRequest("POST", getWorkspacesUrl, bytes.NewBuffer(jsonPayload)) + if err != nil { + return nil, err + } + + req.Header.Set("accept", "application/json") + req.Header.Set("Authorization", "Bearer "+accessToken) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + responseBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %v", err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to fetch workspaces, status code: %d", resp.StatusCode) + } + + var result map[string]interface{} + if err := json.Unmarshal(responseBody, &result); err != nil { + return nil, err + } + + workspaces, ok := result["results"].([]interface{}) + if !ok || len(workspaces) == 0 { + pterm.Warning.Println("There are no accessible workspaces. Ask your administrators or workspace owners for access.") + exitWithError() + } + + var workspaceList []map[string]interface{} + for _, workspace := range workspaces { + workspaceMap, ok := workspace.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("failed to parse workspace data") + } + workspaceList = append(workspaceList, workspaceMap) + } + + return workspaceList, nil + } + // Parse the endpoint parts := strings.Split(baseUrl, "://") if len(parts) != 2 { @@ -1098,6 +1331,52 @@ func fetchWorkspaces(baseUrl string, accessToken string) ([]map[string]interface } func fetchDomainIDAndRole(baseUrl string, accessToken string) (string, string, error) { + if strings.Contains(baseUrl, "megazone.io") { + payload := map[string]string{} + jsonPayload, err := json.Marshal(payload) + if err != nil { + return "", "", err + } + + getUserProfileUrl := baseUrl + "/user-profile/get" + req, err := http.NewRequest("POST", getUserProfileUrl, bytes.NewBuffer(jsonPayload)) + if err != nil { + return "", "", err + } + + req.Header.Set("accept", "application/json") + req.Header.Set("Authorization", "Bearer "+accessToken) + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return "", "", err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", "", fmt.Errorf("failed to fetch user profile, status code: %d", resp.StatusCode) + } + + var result map[string]interface{} + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return "", "", err + } + + domainID, ok := result["domain_id"].(string) + if !ok { + return "", "", fmt.Errorf("domain_id not found in response") + } + + roleType, ok := result["role_type"].(string) + if !ok { + return "", "", fmt.Errorf("role_type not found in response") + } + + return domainID, roleType, nil + } + // Parse the endpoint parts := strings.Split(baseUrl, "://") if len(parts) != 2 { @@ -1192,6 +1471,50 @@ func fetchDomainIDAndRole(baseUrl string, accessToken string) (string, string, e } func grantToken(baseUrl, refreshToken, scope, domainID, workspaceID string) (string, error) { + if strings.Contains(baseUrl, "megazone.io") { + payload := map[string]interface{}{ + "grant_type": "REFRESH_TOKEN", + "token": refreshToken, + "scope": scope, + "timeout": 10800, + "domain_id": domainID, + "workspace_id": workspaceID, + } + jsonPayload, err := json.Marshal(payload) + if err != nil { + return "", err + } + + req, err := http.NewRequest("POST", baseUrl+"/token/grant", bytes.NewBuffer(jsonPayload)) + if err != nil { + return "", err + } + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("status code: %d", resp.StatusCode) + } + + var result map[string]interface{} + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return "", err + } + + accessToken, ok := result["access_token"].(string) + if !ok { + return "", fmt.Errorf("access token not found in response") + } + + return accessToken, nil + } + // Parse the endpoint parts := strings.Split(baseUrl, "://") if len(parts) != 2 { @@ -1255,7 +1578,7 @@ func grantToken(baseUrl, refreshToken, scope, domainID, workspaceID string) (str reqMsg.SetFieldByName("scope", scopeEnum) reqMsg.SetFieldByName("token", refreshToken) - reqMsg.SetFieldByName("timeout", int32(21600)) + reqMsg.SetFieldByName("timeout", int32(10800)) reqMsg.SetFieldByName("domain_id", domainID) if workspaceID != "" { reqMsg.SetFieldByName("workspace_id", workspaceID) @@ -1657,10 +1980,10 @@ func readTokenFromFile(envDir, tokenType string) (string, error) { } // getValidTokens checks for existing valid tokens in the environment cache directory -func getValidTokens(currentEnv string) (accessToken, refreshToken, newAccessToken string, err error) { +func getValidTokens(currentEnv string) (accessToken, refreshToken string, err error) { homeDir, err := os.UserHomeDir() if err != nil { - return "", "", "", err + return "", "", err } envCacheDir := filepath.Join(homeDir, ".cfctl", "cache", currentEnv) @@ -1670,13 +1993,14 @@ func getValidTokens(currentEnv string) (accessToken, refreshToken, newAccessToke if err == nil { if exp, ok := claims["exp"].(float64); ok { if time.Now().Unix() < int64(exp) { - accessToken, _ = readTokenFromFile(envCacheDir, "access_token") - newAccessToken, _ = readTokenFromFile(envCacheDir, "grant_token") - return accessToken, refreshToken, newAccessToken, nil + if accessToken, err = readTokenFromFile(envCacheDir, "access_token"); err == nil { + return accessToken, refreshToken, nil + } + return accessToken, refreshToken, nil } } } } - return "", "", "", fmt.Errorf("no valid tokens found") + return "", "", fmt.Errorf("no valid tokens found") } diff --git a/cmd/other/setting.go b/cmd/other/setting.go index efbb846..f5a00f1 100644 --- a/cmd/other/setting.go +++ b/cmd/other/setting.go @@ -1180,27 +1180,25 @@ func constructEndpoint(baseURL string) (string, error) { } hostname := parsedURL.Hostname() - prefix := "" - // Determine the prefix based on the hostname switch { case strings.Contains(hostname, ".dev.spaceone.dev"): - prefix = "dev" + return fmt.Sprintf("grpc+ssl://identity.api.dev.spaceone.dev:443"), nil case strings.Contains(hostname, ".stg.spaceone.dev"): - prefix = "stg" - // TODO: After set up production - default: - return "", fmt.Errorf("unknown environment prefix in URL: %s", hostname) + return fmt.Sprintf("grpc+ssl://identity.api.stg.spaceone.dev:443"), nil } - // Extract the service from the hostname - service := strings.Split(hostname, ".")[0] - if service == "" { - return "", fmt.Errorf("unable to determine service from URL: %s", hostname) + if strings.Contains(hostname, "spaceone.megazone.io") { + region := "kr1" + if strings.Contains(hostname, "jp1.") { + region = "jp1" + } else if strings.Contains(hostname, "us1.") { + region = "us1" + } + + return fmt.Sprintf("https://console-v2.%s.api.spaceone.megazone.io/identity", region), nil } - // Construct the endpoint dynamically based on the service - newEndpoint := fmt.Sprintf("grpc+ssl://identity.api.%s.spaceone.dev:443", prefix) - return newEndpoint, nil + return "", fmt.Errorf("unknown environment in URL: %s", hostname) } func init() { diff --git a/cmd/root.go b/cmd/root.go index 8ebd802..7992f37 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -253,7 +253,11 @@ func addDynamicServiceCommands() error { } // Only show progress bar when actually fetching services - if len(os.Args) == 1 || (len(os.Args) > 1 && os.Args[1] != "setting") { + if len(os.Args) == 1 || (len(os.Args) > 1 && + os.Args[1] != "setting" && + os.Args[1] != "login" && + os.Args[1] != "api_resources" && + os.Args[1] != "short_name") { // Create progress bar progressbar, _ := pterm.DefaultProgressbar. WithTotal(4). @@ -421,7 +425,7 @@ func loadConfig() (*Config, error) { if strings.HasSuffix(currentEnv, "-user") { // For user environments, read from cache directory envCacheDir := filepath.Join(home, ".cfctl", "cache", currentEnv) - grantTokenPath := filepath.Join(envCacheDir, "grant_token") + grantTokenPath := filepath.Join(envCacheDir, "access_token") data, err := os.ReadFile(grantTokenPath) if err != nil { return nil, fmt.Errorf("no valid token found in cache")