Skip to content

Commit

Permalink
Merge pull request #103 from yjinjo/master
Browse files Browse the repository at this point in the history
Handle grpc connection for local and static mode
  • Loading branch information
yjinjo authored Jan 2, 2025
2 parents 0626c20 + 5d77104 commit 0c4f013
Show file tree
Hide file tree
Showing 5 changed files with 244 additions and 73 deletions.
40 changes: 30 additions & 10 deletions cmd/common/fetchService.go
Original file line number Diff line number Diff line change
Expand Up @@ -427,19 +427,39 @@ func fetchJSONResponse(config *Config, serviceName string, verb string, resource
}
} else {
if !hasIdentityService {
urlParts := strings.Split(apiEndpoint, "//")
if len(urlParts) != 2 {
return nil, fmt.Errorf("invalid API endpoint format: %s", apiEndpoint)
}
// Handle gRPC+SSL protocol directly
if strings.HasPrefix(config.Environments[config.Environment].Endpoint, "grpc+ssl://") {
endpoint := config.Environments[config.Environment].Endpoint
parts := strings.Split(endpoint, "/")
endpoint = strings.Join(parts[:len(parts)-1], "/")
parts = strings.Split(endpoint, "://")
if len(parts) != 2 {
return nil, fmt.Errorf("invalid endpoint format: %s", endpoint)
}

domainParts := strings.Split(urlParts[1], ".")
if len(domainParts) < 4 {
return nil, fmt.Errorf("invalid domain format in API endpoint: %s", apiEndpoint)
}
hostParts := strings.Split(parts[1], ".")
if len(hostParts) < 4 {
return nil, fmt.Errorf("invalid endpoint format: %s", endpoint)
}

domainParts[0] = convertServiceNameToEndpoint(serviceName)
// Replace service name
hostParts[0] = convertServiceNameToEndpoint(serviceName)
hostPort = strings.Join(hostParts, ".")
} else {
// Original HTTP/HTTPS handling
urlParts := strings.Split(apiEndpoint, "//")
if len(urlParts) != 2 {
return nil, fmt.Errorf("invalid API endpoint format: %s", apiEndpoint)
}

hostPort = strings.Join(domainParts, ".") + ":443"
domainParts := strings.Split(urlParts[1], ".")
if len(domainParts) < 4 {
return nil, fmt.Errorf("invalid domain format in API endpoint: %s", apiEndpoint)
}

domainParts[0] = convertServiceNameToEndpoint(serviceName)
hostPort = strings.Join(domainParts, ".") + ":443"
}
} else {
trimmedEndpoint := strings.TrimPrefix(identityEndpoint, "grpc+ssl://")
parts := strings.Split(trimmedEndpoint, ".")
Expand Down
33 changes: 33 additions & 0 deletions cmd/other/apiResources.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,39 @@ func FetchEndpointsMap(endpoint string) (map[string]string, error) {
}

if !hasIdentityService {
// Handle gRPC+SSL protocol directly
if strings.HasPrefix(endpoint, "grpc+ssl://") {
// Parse the endpoint
parts := strings.Split(endpoint, "/")
endpoint = strings.Join(parts[:len(parts)-1], "/")
parts = strings.Split(endpoint, "://")
if len(parts) != 2 {
return nil, fmt.Errorf("invalid endpoint format: %s", endpoint)
}

hostParts := strings.Split(parts[1], ".")
svc := hostParts[0]
baseDomain := strings.Join(hostParts[1:], ".")

// Configure TLS
tlsConfig := &tls.Config{
InsecureSkipVerify: false,
}
opts := []grpc.DialOption{
grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)),
}

//If current service is not identity, modify hostPort to use identity service
if svc != "identity" {
hostPort := fmt.Sprintf("identity.%s", baseDomain)
endpoints, err := invokeGRPCEndpointList(hostPort, opts)
if err != nil {
return nil, fmt.Errorf("failed to get endpoints from gRPC: %v", err)
}
return endpoints, nil
}
}

payload := map[string]string{}
jsonPayload, err := json.Marshal(payload)
if err != nil {
Expand Down
31 changes: 29 additions & 2 deletions cmd/other/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,13 @@ func executeUserLogin(currentEnv string) {
scope = "WORKSPACE"
}

// Grant new token using the refresh token
newAccessToken, err := grantToken("", identityEndpoint, hasIdentityService, refreshToken, scope, domainID, workspaceID)
if err != nil {
pterm.Error.Println("Failed to retrieve new access token:", err)
exitWithError()
}

// Create cache directory
envCacheDir := filepath.Join(homeDir, ".cfctl", "cache", currentEnv)
if err := os.MkdirAll(envCacheDir, 0700); err != nil {
Expand All @@ -710,7 +717,7 @@ func executeUserLogin(currentEnv string) {
exitWithError()
}

if err := os.WriteFile(filepath.Join(envCacheDir, "access_token"), []byte(accessToken), 0600); err != nil {
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()
}
Expand All @@ -721,6 +728,12 @@ func executeUserLogin(currentEnv string) {

// GetAPIEndpoint fetches the actual API endpoint from the config endpoint
func GetAPIEndpoint(endpoint string) (string, error) {
// Handle gRPC+SSL protocol
if strings.HasPrefix(endpoint, "grpc+ssl://") {
// For gRPC+SSL endpoints, return as is since it's already in the correct format
return endpoint, nil
}

// Remove protocol prefix if exists
endpoint = strings.TrimPrefix(endpoint, "https://")
endpoint = strings.TrimPrefix(endpoint, "http://")
Expand Down Expand Up @@ -759,6 +772,20 @@ func GetAPIEndpoint(endpoint string) (string, error) {

// GetIdentityEndpoint fetches the identity service endpoint from the API endpoint
func GetIdentityEndpoint(apiEndpoint string) (string, bool, error) {
// If the endpoint is already gRPC+SSL
if strings.HasPrefix(apiEndpoint, "grpc+ssl://") {
// Check if it contains 'identity'
containsIdentity := strings.Contains(apiEndpoint, "identity")

// Remove /v1 suffix if present
if idx := strings.Index(apiEndpoint, "/v"); idx != -1 {
apiEndpoint = apiEndpoint[:idx]
}

return apiEndpoint, containsIdentity, nil
}

// Original HTTP/HTTPS handling logic
endpointListURL := fmt.Sprintf("%s/identity/endpoint/list", apiEndpoint)

payload := map[string]string{}
Expand Down Expand Up @@ -1062,7 +1089,7 @@ func loadEnvironmentConfig() {
"Please enable proxy mode and set identity endpoint first.")

pterm.DefaultBox.WithBoxStyle(pterm.NewStyle(pterm.FgCyan)).
Println("$ cfctl config endpoint -s identity\n" +
Println("$ cfctl setting endpoint -s identity\n" +
"$ cfctl login")

exitWithError()
Expand Down
75 changes: 59 additions & 16 deletions cmd/other/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
"google.golang.org/grpc/credentials/insecure"
"log"
"net/http"
"net/url"
Expand All @@ -15,6 +14,8 @@ import (
"regexp"
"strings"

"google.golang.org/grpc/credentials/insecure"

"gopkg.in/yaml.v3"

"github.com/jhump/protoreflect/dynamic"
Expand Down Expand Up @@ -53,16 +54,21 @@ var settingInitCmd = &cobra.Command{
cfctl setting init endpoint http://localhost:8080 --app
cfctl setting init endpoint http://localhost:8080 --user
or
cfctl setting init local`,
cfctl setting init static grpc://localhost:50051
cfctl setting init static grpc+ssl://inventory.-`,
}

// settingInitLocalCmd represents the setting init local command
var settingInitLocalCmd = &cobra.Command{
Use: "local",
Short: "Initialize local environment setting",
Long: `Initialize a local environment setting with default configuration.`,
Args: cobra.NoArgs,
// settingInitStaticCmd represents the setting init direct command
var settingInitStaticCmd = &cobra.Command{
Use: "static [endpoint]",
Short: "Initialize static connection to a local or service endpoint",
Long: `Initialize configuration with a static service endpoint.
This is useful for development or when connecting directly to specific service endpoints.`,
Example: ` cfctl setting init static grpc://localhost:50051
cfctl setting init static grpc+ssl://inventory-`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
endpoint := args[0]
settingDir := GetSettingDir()
if err := os.MkdirAll(settingDir, 0755); err != nil {
pterm.Error.Printf("Failed to create setting directory: %v\n", err)
Expand All @@ -74,14 +80,20 @@ var settingInitLocalCmd = &cobra.Command{
v.SetConfigFile(mainSettingPath)
v.SetConfigType("yaml")

// Check if local environment already exists
envName, err := parseEnvNameFromURL(endpoint)
if err != nil {
pterm.Error.Printf("Failed to parse environment name: %v\n", err)
return
}

// Check if environment already exists
if err := v.ReadInConfig(); err == nil {
environments := v.GetStringMap("environments")
if existingEnv, exists := environments["local"]; exists {
if existingEnv, exists := environments[envName]; exists {
currentConfig, _ := yaml.Marshal(map[string]interface{}{
"environment": "local",
"environment": envName,
"environments": map[string]interface{}{
"local": existingEnv,
envName: existingEnv,
},
})

Expand All @@ -91,7 +103,7 @@ var settingInitLocalCmd = &cobra.Command{
WithLeftPadding(4).
WithBoxStyle(pterm.NewStyle(pterm.FgYellow))

confirmBox.Println("Environment 'local' already exists.\nDo you want to overwrite it?")
confirmBox.Println(fmt.Sprintf("Environment '%s' already exists.\nDo you want to overwrite it?", envName))

pterm.Info.Println("Current configuration:")
fmt.Println(string(currentConfig))
Expand All @@ -102,13 +114,14 @@ var settingInitLocalCmd = &cobra.Command{
response = strings.ToLower(strings.TrimSpace(response))

if response != "y" {
pterm.Info.Println("Operation cancelled. Environment 'local' remains unchanged.")
pterm.Info.Printf("Operation cancelled. Environment '%s' remains unchanged.\n", envName)
return
}
}
}

updateSetting("local", "grpc://localhost:50051", "")
updateSetting(envName, endpoint, "")
pterm.Success.Printf("Successfully initialized direct connection to %s\n", endpoint)
},
}

Expand Down Expand Up @@ -480,6 +493,17 @@ You can either specify a new endpoint URL directly or use the service-based endp
if listFlag {
token, err := getToken(appV)
if err != nil {
if strings.HasSuffix(currentEnv, "-user") {
pterm.DefaultBox.WithTitle("Authentication Required").
WithTitleTopCenter().
WithBoxStyle(pterm.NewStyle(pterm.FgLightCyan)).
WithRightPadding(4).
WithLeftPadding(4).
Println("Please login to SpaceONE Console first.\n" +
"Run the following command to authenticate:\n\n" +
"$ cfctl login")
return
}
pterm.Error.Println("Error retrieving token:", err)
return
}
Expand Down Expand Up @@ -1201,11 +1225,17 @@ func updateGlobalSetting() {
}

func parseEnvNameFromURL(urlStr string) (string, error) {
isGRPC := strings.HasPrefix(urlStr, "grpc://") || strings.HasPrefix(urlStr, "grpc+ssl://")

urlStr = strings.TrimPrefix(urlStr, "https://")
urlStr = strings.TrimPrefix(urlStr, "http://")
urlStr = strings.TrimPrefix(urlStr, "grpc://")
urlStr = strings.TrimPrefix(urlStr, "grpc+ssl://")

if isGRPC {
return "local", nil
}

if strings.Contains(urlStr, "localhost") {
return "local", nil
}
Expand Down Expand Up @@ -1271,6 +1301,19 @@ func updateSetting(envName, endpoint string, envSuffix string) {
envKey := fmt.Sprintf("environments.%s.endpoint", fullEnvName)
v.Set(envKey, endpoint)

// Set proxy based on endpoint type
proxyKey := fmt.Sprintf("environments.%s.proxy", fullEnvName)
if strings.HasPrefix(endpoint, "grpc://") || strings.HasPrefix(endpoint, "grpc+ssl://") {
// Check if endpoint contains 'identity'
if strings.Contains(strings.ToLower(endpoint), "identity") {
v.Set(proxyKey, true)
} else {
v.Set(proxyKey, false)
}
} else {
v.Set(proxyKey, true)
}

// Set additional configurations based on environment type
if envName == "local" {
// Local environment settings (only for pure 'local', not 'local-user' or 'local-app')
Expand Down Expand Up @@ -1378,7 +1421,7 @@ func init() {
SettingCmd.AddCommand(settingEndpointCmd)
SettingCmd.AddCommand(showCmd)
settingInitCmd.AddCommand(settingInitEndpointCmd)
settingInitCmd.AddCommand(settingInitLocalCmd)
settingInitCmd.AddCommand(settingInitStaticCmd)

settingInitEndpointCmd.Flags().Bool("app", false, "Initialize as application configuration")
settingInitEndpointCmd.Flags().Bool("user", false, "Initialize as user-specific configuration")
Expand Down
Loading

0 comments on commit 0c4f013

Please sign in to comment.