gke-deploy/services/gcloud.go (140 lines of code) (raw):

package services import ( "context" "encoding/base64" "fmt" "os" "os/exec" "path/filepath" "strings" "golang.org/x/oauth2/google" "google.golang.org/api/container/v1" "google.golang.org/api/option" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd/api" ) var ( googleScopes = []string{ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/userinfo.email"} kubeArgs = []string{"--use_application_default_credentials"} ) const ( gkeContextFormat = "gke_%s_%s_%s" gkeResourceNameFormat = "projects/%s/locations/%s/clusters/%s" kubeApiVersion = "client.authentication.k8s.io/v1beta1" kubeCommand = "gke-gcloud-auth-plugin" kubeInstallHint = `Install gke-gcloud-auth-plugin for use with kubectl by following https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke` kubeProvideClusterInfo = true ) // Gcloud implements the GcloudService interface. type Gcloud struct { printCommands bool } // NewGcloud returns a new Gcloud object. func NewGcloud(ctx context.Context, printCommands bool) (*Gcloud, error) { if _, err := exec.LookPath("gcloud"); err != nil { return nil, err } return &Gcloud{ printCommands, }, nil } // NewGcloudGoClient returns a new Gcloud object. func NewGcloudGoClient(ctx context.Context, printCommands bool) *Gcloud { return &Gcloud{ printCommands, } } func (g *Gcloud) ContainerClustersGetCredentials(ctx context.Context, clusterName, clusterLocation, clusterProject string) error { if _, err := runCommand(ctx, g.printCommands, "gcloud", "container", "clusters", "get-credentials", clusterName, fmt.Sprintf("--zone=%s", clusterLocation), fmt.Sprintf("--project=%s", clusterProject), "--quiet"); err != nil { return fmt.Errorf("command to get cluster credentials failed: %v", err) } return nil } // ContainerClustersGetCredentialsGoClient uses the go client libraries to get cluster credentials and generate the kubeconfig file for kubectl. // The kubectl authenticates using the accessToken instead of the google-gke-auth-plugin (which depends on gcloud). func (g *Gcloud) ContainerClustersGetCredentialsGoClient(ctx context.Context, clusterName, clusterLocation, clusterProject string) error { dirname, err := os.UserHomeDir() if err != nil { return fmt.Errorf("failed to get the user's home directory: %v", err) } path := filepath.Join(dirname, ".kube") err = os.MkdirAll(path, 0755) if err != nil { return fmt.Errorf("failed to make directory %s: %v", path, err) } kubeConfigFile := filepath.Join(path, "config") if err := getK8sClusterConfigs(ctx, clusterProject, clusterLocation, clusterName, kubeConfigFile); err != nil { return fmt.Errorf("failed to initialize gke cluster config: %v", err) } return nil } // ConfigGetValue calls `gcloud config get-value <property>` and returns stdout. func (g *Gcloud) ConfigGetValue(ctx context.Context, property string) (string, error) { out, err := runCommand(ctx, g.printCommands, "gcloud", "config", "get-value", property, "--quiet") if err != nil { return "", fmt.Errorf("command to get property value failed: %v", err) } return strings.TrimSpace(out), nil } // getK8sClusterConfigs uses the go client libraries to authenticate against the cluster and generate the kubeconfig file // instead of the gcloud CLI. func getK8sClusterConfigs(ctx context.Context, clusterProject, clusterLocation, clusterName, kubeConfigFile string) error { fullClusterName := fmt.Sprintf(gkeResourceNameFormat, clusterProject, clusterLocation, clusterName) fmt.Printf("Full Cluster: %s\n", fullClusterName) ts, err := google.DefaultTokenSource(ctx, googleScopes...) if err != nil { return fmt.Errorf("failed to create google token source: %v", err) } options := []option.ClientOption{option.WithTokenSource(ts)} userAgent := os.Getenv("GOOGLE_APIS_USER_AGENT") if userAgent != "" { options = append(options, option.WithUserAgent(userAgent)) } svc, err := container.NewService(ctx, options...) if err != nil { return fmt.Errorf("failed to initialize gke client: %v", err) } cluster, err := svc.Projects.Locations.Clusters.Get(fullClusterName).Do() if err != nil { return fmt.Errorf("failed to list clusters: %w", err) } fmt.Printf("Cluster's Endpoint is: %s\n", cluster.Endpoint) name := fmt.Sprintf(gkeContextFormat, clusterProject, cluster.Zone, cluster.Name) kubeConfig := &api.Config{ APIVersion: "v1", Kind: "Config", Clusters: map[string]*api.Cluster{}, AuthInfos: map[string]*api.AuthInfo{}, Contexts: map[string]*api.Context{}, CurrentContext: name, } _, err = os.Stat(kubeConfigFile) if os.IsNotExist(err) { } else { conf, err := clientcmd.LoadFromFile(kubeConfigFile) if err != nil { return fmt.Errorf("failed to load kubeConfig from file %s : %v", kubeConfigFile, err) } kubeConfig = conf } cert, err := base64.StdEncoding.DecodeString(cluster.MasterAuth.ClusterCaCertificate) if err != nil { return fmt.Errorf("failed to decode the cluster certificate: %v", err) } kubeConfig.Clusters[name] = &api.Cluster{ CertificateAuthorityData: cert, Server: "https://" + cluster.Endpoint, } kubeConfig.Contexts[name] = &api.Context{ Cluster: name, AuthInfo: name, } kubeConfig.AuthInfos[name] = &api.AuthInfo{ Exec: &api.ExecConfig{ APIVersion: kubeApiVersion, Command: kubeCommand, Args: kubeArgs, InstallHint: kubeInstallHint, ProvideClusterInfo: kubeProvideClusterInfo, }, } if err := clientcmd.WriteToFile(*kubeConfig, kubeConfigFile); err != nil { return fmt.Errorf("failed to write kubeConfig to file %s: %v", kubeConfigFile, err) } return nil }