From 63bfda6b22db8a8ecdd09db9a23ca444c5e86a88 Mon Sep 17 00:00:00 2001 From: Youngjin Jo Date: Wed, 15 Jan 2025 14:12:03 +0900 Subject: [PATCH] feat: add internal flag Signed-off-by: Youngjin Jo --- cmd/other/setting.go | 134 +++++++++++++++++++++++++++++++++++- pkg/transport/reflection.go | 16 +++++ pkg/transport/service.go | 2 + 3 files changed, 149 insertions(+), 3 deletions(-) diff --git a/cmd/other/setting.go b/cmd/other/setting.go index 93b5c01..b537174 100644 --- a/cmd/other/setting.go +++ b/cmd/other/setting.go @@ -143,7 +143,6 @@ This is useful for development or when connecting directly to specific service e } pterm.Success.Printf("Successfully initialized direct connection to %s\n", endpoint) - updateSetting(envName, endpoint, "") if err := v.ReadInConfig(); err == nil { v.Set(fmt.Sprintf("environments.%s.proxy", envName), false) if err := v.WriteConfig(); err != nil { @@ -151,6 +150,8 @@ This is useful for development or when connecting directly to specific service e return } } + + updateSetting(envName, endpoint, "", false) }, } @@ -253,8 +254,10 @@ var settingInitProxyCmd = &cobra.Command{ } } + internalFlag, _ := cmd.Flags().GetBool("internal") + // Update configuration - updateSetting(envName, endpointStr, envSuffix) + updateSetting(envName, endpointStr, envSuffix, internalFlag) }, } @@ -1344,7 +1347,7 @@ func isIPAddress(host string) bool { } // updateSetting updates the configuration files -func updateSetting(envName, endpoint, envSuffix string) { +func updateSetting(envName, endpoint, envSuffix string, internal bool) { settingDir := GetSettingDir() mainSettingPath := filepath.Join(settingDir, "setting.yaml") @@ -1358,6 +1361,16 @@ func updateSetting(envName, endpoint, envSuffix string) { // Set environment v.Set("environment", envName) + if internal { + // Get internal endpoint + internalEndpoint, err := getInternalEndpoint(endpoint) + if err != nil { + pterm.Error.Printf("Failed to get internal endpoint: %v\n", err) + return + } + endpoint = internalEndpoint + } + // Handle protocol for endpoint if !strings.Contains(endpoint, "://") { if strings.Contains(endpoint, "localhost") || strings.Contains(endpoint, "127.0.0.1") { @@ -1399,6 +1412,120 @@ func updateSetting(envName, endpoint, envSuffix string) { pterm.Info.Printf("Configuration saved to: %s\n", mainSettingPath) } +func getInternalEndpoint(endpoint string) (string, error) { + if strings.HasPrefix(endpoint, "grpc://") || strings.HasPrefix(endpoint, "grpc+ssl://") { + // Handle gRPC endpoint + conn, err := transport.GetGrpcConnection(endpoint) + if err != nil { + return "", fmt.Errorf("failed to connect to gRPC server: %v", err) + } + defer conn.Close() + + client := grpc_reflection_v1alpha.NewServerReflectionClient(conn) + refClient := grpcreflect.NewClient(context.Background(), client) + defer refClient.Reset() + + serviceName := "spaceone.api.identity.v2.Endpoint" + methodName := "list" + + serviceDesc, err := refClient.ResolveService(serviceName) + if err != nil { + return "", fmt.Errorf("failed to resolve service: %v", err) + } + + methodDesc := serviceDesc.FindMethodByName(methodName) + if methodDesc == nil { + return "", fmt.Errorf("method not found: %s", methodName) + } + + reqMsg := dynamic.NewMessage(methodDesc.GetInputType()) + reqMsg.SetField(reqMsg.GetMessageDescriptor().FindFieldByName("service"), "identity") + reqMsg.SetField(reqMsg.GetMessageDescriptor().FindFieldByName("endpoint_type"), "INTERNAL") + reqMsg.SetField(reqMsg.GetMessageDescriptor().FindFieldByName("query"), map[string]interface{}{}) + + respMsg := dynamic.NewMessage(methodDesc.GetOutputType()) + fullMethod := fmt.Sprintf("/%s/%s", serviceName, methodName) + + if err := conn.Invoke(context.Background(), fullMethod, reqMsg, respMsg); err != nil { + return "", fmt.Errorf("failed to invoke method: %v", err) + } + + results, err := respMsg.TryGetField(respMsg.GetMessageDescriptor().FindFieldByName("results")) + if err != nil { + return "", fmt.Errorf("failed to get results: %v", err) + } + + resultsSlice := results.([]interface{}) + if len(resultsSlice) > 0 { + result := resultsSlice[0].(*dynamic.Message) + if internalEndpoint, err := result.TryGetField(result.GetMessageDescriptor().FindFieldByName("internal_endpoint")); err == nil { + return internalEndpoint.(string), nil + } + } + } else { + // Handle REST endpoint + // 1. First get the console API endpoint from config + client := &http.Client{} + configResp, err := client.Get(endpoint + "/config/production.json") + if err != nil { + return "", fmt.Errorf("failed to get config: %v", err) + } + defer configResp.Body.Close() + + var config struct { + ConsoleAPIV2 struct { + Endpoint string `json:"ENDPOINT"` + } `json:"CONSOLE_API_V2"` + } + + if err := json.NewDecoder(configResp.Body).Decode(&config); err != nil { + return "", fmt.Errorf("failed to decode config: %v", err) + } + + // 2. Then use the console API endpoint to get internal endpoint + reqBody := map[string]interface{}{ + "service": "identity", + "endpoint_type": "INTERNAL", + "query": map[string]interface{}{}, + } + + jsonBody, err := json.Marshal(reqBody) + if err != nil { + return "", fmt.Errorf("failed to marshal request body: %v", err) + } + + req, err := http.NewRequest("POST", config.ConsoleAPIV2.Endpoint+"/identity/endpoint/list", bytes.NewBuffer(jsonBody)) + if err != nil { + return "", fmt.Errorf("failed to create request: %v", err) + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("accept", "application/json") + + resp, err := client.Do(req) + if err != nil { + return "", fmt.Errorf("failed to send request: %v", err) + } + defer resp.Body.Close() + + var result struct { + Results []struct { + InternalEndpoint string `json:"internal_endpoint"` + } `json:"results"` + } + + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return "", fmt.Errorf("failed to decode response: %v", err) + } + + if len(result.Results) > 0 { + return result.Results[0].InternalEndpoint, nil + } + } + + return "", fmt.Errorf("internal endpoint not found") +} + // convertToStringMap converts map[interface{}]interface{} to map[string]interface{} func convertToStringMap(m map[interface{}]interface{}) map[string]interface{} { result := make(map[string]interface{}) @@ -1442,6 +1569,7 @@ func init() { settingInitProxyCmd.Flags().Bool("app", false, "Initialize as application configuration") settingInitProxyCmd.Flags().Bool("user", false, "Initialize as user-specific configuration") + settingInitProxyCmd.Flags().Bool("internal", false, "Use internal endpoint for the environment") envCmd.Flags().StringP("switch", "s", "", "Switch to a different environment") envCmd.Flags().StringP("remove", "r", "", "Remove an environment") diff --git a/pkg/transport/reflection.go b/pkg/transport/reflection.go index 00719ea..73d039a 100644 --- a/pkg/transport/reflection.go +++ b/pkg/transport/reflection.go @@ -56,6 +56,22 @@ func ListGRPCServices(endpoint string) ([]string, error) { return listServices(conn) } +// GetGrpcConnection establishes a gRPC connection with the specified endpoint +func GetGrpcConnection(endpoint string) (*grpc.ClientConn, error) { + parsedURL, err := url.Parse(endpoint) + if err != nil { + return nil, fmt.Errorf("failed to parse endpoint: %v", err) + } + + host := parsedURL.Hostname() + port := parsedURL.Port() + if port == "" { + port = "443" + } + + return dialGRPC(endpoint, host, port) +} + // CheckIdentityProxyAvailable checks if the given gRPC endpoint can be used as an identity proxy // by verifying the presence of both Endpoint and Token services. // These services are required for the identity service to act as a proxy. diff --git a/pkg/transport/service.go b/pkg/transport/service.go index e90fde5..02b58b0 100644 --- a/pkg/transport/service.go +++ b/pkg/transport/service.go @@ -187,12 +187,14 @@ func FetchService(serviceName string, verb string, resourceName string, options hostPort = strings.TrimPrefix(config.Environments[config.Environment].Endpoint, "grpc://") } else { apiEndpoint, err = configs.GetAPIEndpoint(config.Environments[config.Environment].Endpoint) + fmt.Println(apiEndpoint) if err != nil { pterm.Error.Printf("Failed to get API endpoint: %v\n", err) os.Exit(1) } // Get identity service endpoint identityEndpoint, hasIdentityService, err = configs.GetIdentityEndpoint(apiEndpoint) + fmt.Println(identityEndpoint) if err != nil { pterm.Error.Printf("Failed to get identity endpoint: %v\n", err) os.Exit(1)