cli/pkg/lifecycle/get_credentials.go (120 lines of code) (raw):
package lifecycle
import (
"context"
"fmt"
"strings"
gateway "cloud.google.com/go/gkeconnect/gateway/apiv1"
gatewaypb "cloud.google.com/go/gkeconnect/gateway/apiv1/gatewaypb"
log "github.com/sirupsen/logrus"
"google.golang.org/api/cloudresourcemanager/v1"
"google.golang.org/api/gkehub/v1"
"google.golang.org/api/option"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
// GenerateKubeConfig generates a kubeconfig with contexts for all clusters in the GKE Demo Environment.
func GenerateKubeConfig(fleetProjectId string) (*clientcmdapi.Config, error) {
// Create a GKE Hub client.
ctx := context.Background()
hubClient, err := gkehub.NewService(ctx, option.WithEndpoint("https://gkehub.googleapis.com/v1"))
if err != nil {
log.Errorf("Failed to create GKE Hub client: %v", err)
return nil, err
}
// Get a list of all GKE Fleet memberships in the project.
parent := fmt.Sprintf("projects/%s/locations/-", fleetProjectId)
req := hubClient.Projects.Locations.Memberships.List(parent)
memberships, err := req.Do()
if err != nil {
log.Errorf("Failed to list memberships: %v", err)
return nil, err
}
// Get the project number for the fleet project needed later in the generate credentials request
projectNumber, err := getProjectNumber(fleetProjectId)
if err != nil {
log.Errorf("Failed to get project number: %v", err)
return nil, err
}
// Create a new kubeconfig.
config := clientcmdapi.NewConfig()
// Keep track of failed memberships
failedMemberships := []string{}
// Generate credentials for each membership
for _, membership := range memberships.Resources {
membershipName := membership.Name
membershipLocation := extractLocation(membershipName)
// Create a Gateway Control Client.
endpoint := "connectgateway.googleapis.com"
if isRegionalMembership(membershipName) {
endpoint = membershipLocation + "-" + endpoint // Use regional endpoint
} else {
endpoint = "connectgateway.googleapis.com" // Use global endpoint
}
// Create a Gateway Control Client with the correct endpoint
ctx2 := context.Background()
gcc, err := gateway.NewGatewayControlRESTClient(ctx2, option.WithEndpoint(endpoint))
if err != nil {
log.Errorf("Failed to create gateway control client for %s: %v", membershipName, err)
failedMemberships = append(failedMemberships, membershipName)
continue // Skip to the next membership
}
defer gcc.Close()
log.Infof("Generating credentials for membership: %s", membershipName)
// Construct the correct membership name with project number
membershipName = fmt.Sprintf("projects/%s/locations/%s/memberships/%s",
projectNumber, membershipLocation, extractMembershipID(membership.Name))
// Generate credentials for each membership
req := &gatewaypb.GenerateCredentialsRequest{
Name: membershipName,
}
resp, err := gcc.GenerateCredentials(ctx, req)
if err != nil {
log.Errorf("Failed to generate credentials for membership %s: Endpoint: %s, Error: %v", membershipName, endpoint, err)
failedMemberships = append(failedMemberships, membershipName)
continue // Skip to the next membership
}
// Get the kubeconfig from the response
kc := resp.GetKubeconfig()
// Parse the kubeconfig
parsedConfig, err := clientcmd.Load(kc)
if err != nil {
log.Errorf("Failed to load kubeconfig for membership %s: %v", membershipName, err)
return nil, err
}
// Merge the parsed config into the main config
for key, context := range parsedConfig.Contexts {
config.Contexts[key] = context
}
for key, cluster := range parsedConfig.Clusters {
config.Clusters[key] = cluster
}
for key, authInfo := range parsedConfig.AuthInfos {
config.AuthInfos[key] = authInfo
}
}
// Write the kubeconfig to a file.
err = clientcmd.WriteToFile(*config, "kubeconfig")
if err != nil {
log.Errorf("Failed to write kubeconfig: %v", err)
return nil, err
}
if len(failedMemberships) > 0 {
log.Warnf("Failed to generate credentials for the following memberships: %v", failedMemberships)
} else {
log.Info("Kubeconfig generated successfully.")
}
return config, err
}
func isRegionalMembership(membership string) bool {
// A membership is regional if the membership.name string does not contain "locations/global"
return !strings.Contains(membership, "locations/global")
}
func extractLocation(path string) string {
parts := strings.Split(path, "/")
for i, part := range parts {
if part == "locations" && i+1 < len(parts) {
return parts[i+1]
}
}
return ""
}
func extractMembershipID(membershipName string) string {
parts := strings.Split(membershipName, "/")
return parts[len(parts)-1]
}
func getProjectNumber(projectID string) (string, error) {
ctx := context.Background()
crmService, err := cloudresourcemanager.NewService(ctx)
if err != nil {
return "", fmt.Errorf("failed to create Resource Manager client: %v", err)
}
project, err := crmService.Projects.Get(projectID).Do()
if err != nil {
return "", fmt.Errorf("failed to get project: %v", err)
}
return fmt.Sprintf("%d", project.ProjectNumber), nil
}