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  }