/
gcloud.go
161 lines (148 loc) · 5.64 KB
/
gcloud.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
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
}