github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/caas/gke.go (about) 1 // Copyright 2019 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package caas 5 6 import ( 7 "fmt" 8 "io" 9 "os" 10 "strings" 11 12 "github.com/juju/cmd" 13 "github.com/juju/errors" 14 15 "github.com/juju/juju/caas/kubernetes/clientconfig" 16 "github.com/juju/juju/cmd/juju/interact" 17 ) 18 19 type gke struct { 20 CommandRunner 21 } 22 23 func newGKECluster() k8sCluster { 24 return &gke{CommandRunner: &defaultRunner{}} 25 } 26 27 func (g *gke) cloud() string { 28 return "gce" 29 } 30 31 func (g *gke) getKubeConfig(p *clusterParams) (io.ReadCloser, string, error) { 32 cmd := []string{ 33 "gcloud", "container", "clusters", "get-credentials", p.name, 34 } 35 qualifiedClusterName := "gke_" 36 if p.credential != "" { 37 cmd = append(cmd, "--account", p.credential) 38 } 39 if p.project != "" { 40 cmd = append(cmd, "--project", p.project) 41 qualifiedClusterName += p.project + "_" 42 } 43 if p.region != "" { 44 cmd = append(cmd, "--region", p.region) 45 qualifiedClusterName += p.region + "_" 46 } 47 qualifiedClusterName += p.name 48 49 kubeconfig := clientconfig.GetKubeConfigPath() 50 result, err := runCommand(g, cmd, kubeconfig) 51 if err != nil { 52 return nil, "", errors.Trace(err) 53 } 54 if result.Code != 0 { 55 return nil, "", errors.New(string(result.Stderr)) 56 } 57 rdr, err := os.Open(kubeconfig) 58 return rdr, qualifiedClusterName, err 59 } 60 61 func (g *gke) interactiveParams(ctxt *cmd.Context, p *clusterParams) (*clusterParams, error) { 62 errout := interact.NewErrWriter(ctxt.Stdout) 63 pollster := interact.New(ctxt.Stdin, ctxt.Stdout, errout) 64 65 var err error 66 if p.credential == "" { 67 p.credential, err = g.queryAccount(pollster) 68 if err != nil { 69 return nil, errors.Trace(err) 70 } 71 } 72 73 if p.project == "" { 74 p.project, err = g.queryProject(pollster, p.credential) 75 if err != nil { 76 return nil, errors.Trace(err) 77 } 78 } 79 80 if p.name == "" { 81 p.name, p.region, err = g.queryCluster(pollster, p.credential, p.project, p.region) 82 if err != nil { 83 return nil, errors.Trace(err) 84 } 85 } 86 return p, nil 87 } 88 89 func (g *gke) listAccounts() ([]string, string, error) { 90 cmd := []string{ 91 "gcloud", "auth", "list", "--format", "value\\(account,status\\)", 92 } 93 result, err := runCommand(g, cmd, "") 94 if err != nil { 95 return nil, "", errors.Trace(err) 96 } 97 if result.Code != 0 { 98 return nil, "", errors.New(string(result.Stderr)) 99 } 100 info := strings.Split(string(result.Stdout), "\n") 101 102 var accounts []string 103 var defaultAccount string 104 for _, line := range info { 105 parts := strings.Fields(line) 106 if len(parts) == 0 { 107 continue 108 } 109 accounts = append(accounts, parts[0]) 110 if len(parts) > 1 && parts[1] == "*" { 111 defaultAccount = parts[0] 112 } 113 } 114 return accounts, defaultAccount, nil 115 } 116 117 func (g *gke) queryAccount(pollster *interact.Pollster) (string, error) { 118 allAccounts, defaultAccount, err := g.listAccounts() 119 if err != nil { 120 return "", errors.Trace(err) 121 } 122 if len(allAccounts) == 0 { 123 return "", errors.New("no accounts have been set up.\n" + 124 "See gcloud help auth.'", 125 ) 126 } 127 if defaultAccount == "" { 128 defaultAccount = allAccounts[0] 129 } 130 account, err := pollster.Select(interact.List{ 131 Singular: "account", 132 Plural: "Available accounts", 133 Options: allAccounts, 134 Default: defaultAccount, 135 }) 136 return account, errors.Trace(err) 137 } 138 139 func (g *gke) listProjects(account string) ([]string, error) { 140 cmd := []string{ 141 "gcloud", "projects", "list", "--account", account, "--filter", "lifecycleState:ACTIVE", "--format", "value\\(projectId\\)", 142 } 143 result, err := runCommand(g, cmd, "") 144 if err != nil { 145 return nil, errors.Trace(err) 146 } 147 if result.Code != 0 { 148 return nil, errors.New(string(result.Stderr)) 149 } 150 return strings.Split(string(result.Stdout), "\n"), nil 151 } 152 153 func (g *gke) queryProject(pollster *interact.Pollster, account string) (string, error) { 154 allProjects, err := g.listProjects(account) 155 if err != nil { 156 return "", errors.Trace(err) 157 } 158 if len(allProjects) == 0 { 159 return "", errors.New("no projects have been set up.\n" + 160 "You can create a project using 'gcloud projects create'", 161 ) 162 } 163 project, err := pollster.Select(interact.List{ 164 Singular: "project", 165 Plural: "Available projects", 166 Options: allProjects, 167 Default: allProjects[0], 168 }) 169 return project, errors.Trace(err) 170 } 171 172 func (g *gke) listClusters(account, project, region string) (map[string]cluster, error) { 173 cmd := []string{ 174 "gcloud", "container", "clusters", "list", "--filter", "status:RUNNING", "--account", account, "--project", project, "--format", "value\\(name,zone\\)", 175 } 176 if region != "" { 177 cmd = append(cmd, "--region", region) 178 } 179 result, err := runCommand(g, cmd, "") 180 if err != nil { 181 return nil, errors.Trace(err) 182 } 183 if result.Code != 0 { 184 return nil, errors.New(string(result.Stderr)) 185 } 186 info := strings.Split(string(result.Stdout), "\n") 187 188 clusters := make(map[string]cluster) 189 for _, line := range info { 190 parts := strings.Fields(line) 191 if len(parts) == 0 { 192 continue 193 } 194 c := cluster{name: parts[0]} 195 if len(parts) > 1 { 196 c.region = parts[1] 197 } 198 clusters[c.name] = c 199 } 200 return clusters, nil 201 } 202 203 func (g *gke) queryCluster(pollster *interact.Pollster, account, project, region string) (string, string, error) { 204 allClustersByName, err := g.listClusters(account, project, region) 205 if err != nil { 206 return "", "", errors.Trace(err) 207 } 208 if len(allClustersByName) == 0 { 209 regionMsg := "" 210 if region != "" { 211 regionMsg = fmt.Sprintf(" in region %v", regionMsg) 212 } 213 return "", "", errors.New("no clusters have been set up%s.\n" + 214 "You can create a k8s cluster using 'gcloud container cluster create'", 215 ) 216 } 217 var clusterNamesAndRegions []string 218 clustersByNameRegion := make(map[string]cluster) 219 for n, c := range allClustersByName { 220 nr := n 221 if c.region != "" { 222 nr += " in " + c.region 223 } 224 clusterNamesAndRegions = append(clusterNamesAndRegions, nr) 225 clustersByNameRegion[nr] = c 226 } 227 cluster, err := pollster.Select(interact.List{ 228 Singular: "cluster", 229 Plural: "Available clusters", 230 Options: clusterNamesAndRegions, 231 Default: clusterNamesAndRegions[0], 232 }) 233 if err != nil { 234 return "", "", errors.Trace(err) 235 } 236 selected := clustersByNameRegion[cluster] 237 return selected.name, selected.region, nil 238 }