github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/caas/kubernetes/clientconfig/k8s_test.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package clientconfig_test
     5  
     6  import (
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  
    11  	jc "github.com/juju/testing/checkers"
    12  	gc "gopkg.in/check.v1"
    13  	"k8s.io/client-go/tools/clientcmd"
    14  
    15  	"github.com/juju/juju/caas/kubernetes/clientconfig"
    16  	"github.com/juju/juju/cloud"
    17  	"github.com/juju/testing"
    18  )
    19  
    20  type k8sConfigSuite struct {
    21  	testing.IsolationSuite
    22  	dir string
    23  }
    24  
    25  var _ = gc.Suite(&k8sConfigSuite{})
    26  
    27  var (
    28  	prefixConfigYAML = `
    29  apiVersion: v1
    30  kind: Config
    31  clusters:
    32  - cluster:
    33      server: https://1.1.1.1:8888
    34      certificate-authority-data: QQ==
    35    name: the-cluster
    36  contexts:
    37  - context:
    38      cluster: the-cluster
    39      user: the-user
    40    name: the-context
    41  current-context: the-context
    42  preferences: {}
    43  users:
    44  `
    45  	emptyConfigYAML = `
    46  apiVersion: v1
    47  kind: Config
    48  clusters: []
    49  contexts: []
    50  current-context: ""
    51  preferences: {}
    52  users: []
    53  `
    54  
    55  	singleConfigYAML = prefixConfigYAML + `
    56  - name: the-user
    57    user:
    58      password: thepassword
    59      username: theuser
    60  `
    61  
    62  	multiConfigYAML = `
    63  apiVersion: v1
    64  kind: Config
    65  clusters:
    66  - cluster:
    67      server: https://1.1.1.1:8888
    68      certificate-authority-data: QQ==
    69    name: the-cluster
    70  - cluster:
    71      server: https://10.10.10.10:1010
    72    name: default-cluster
    73  - cluster:
    74      server: https://100.100.100.100:1010
    75      certificate-authority-data: QQ==
    76    name: second-cluster
    77  contexts:
    78  - context:
    79      cluster: the-cluster
    80      user: the-user
    81    name: the-context
    82  - context:
    83      cluster: second-cluster
    84      user: second-user
    85    name: second-context
    86  - context:
    87      cluster: default-cluster
    88      user: default-user
    89    name: default-context
    90  current-context: default-context
    91  preferences: {}
    92  users:
    93  - name: the-user
    94    user:
    95      client-certificate-data: QQ==
    96      client-key-data: Qg==
    97      token: tokenwithcerttoken
    98  - name: default-user
    99    user:
   100      password: defaultpassword
   101      username: defaultuser
   102  - name: second-user
   103    user:
   104      client-certificate-data: QQ==
   105      token: tokenwithcerttoken
   106  - name: third-user
   107    user:
   108      token: "atoken"
   109  - name: fourth-user
   110    user:
   111      client-certificate-data: QQ==
   112      client-key-data: Qg==
   113      token: "tokenwithcerttoken"
   114  - name: fifth-user
   115    user:
   116      client-certificate-data: QQ==
   117      client-key-data: Qg==
   118      username: "fifth-user"
   119      password: "userpasscertpass"
   120  `
   121  )
   122  
   123  func (s *k8sConfigSuite) SetUpTest(c *gc.C) {
   124  	s.IsolationSuite.SetUpTest(c)
   125  	s.dir = c.MkDir()
   126  }
   127  
   128  // writeTempKubeConfig writes yaml to a temp file and sets the
   129  // KUBECONFIG environment variable so that the clientconfig code reads
   130  // it instead of the default.
   131  // The caller must close and remove the returned file.
   132  func (s *k8sConfigSuite) writeTempKubeConfig(c *gc.C, filename string, data string) (*os.File, error) {
   133  	fullpath := filepath.Join(s.dir, filename)
   134  	err := ioutil.WriteFile(fullpath, []byte(data), 0644)
   135  	if err != nil {
   136  		c.Fatal(err.Error())
   137  	}
   138  	os.Setenv("KUBECONFIG", fullpath)
   139  
   140  	f, err := os.Open(fullpath)
   141  	return f, err
   142  }
   143  
   144  func (s *k8sConfigSuite) TestGetEmptyConfig(c *gc.C) {
   145  	s.assertNewK8sClientConfig(c, newK8sClientConfigTestCase{
   146  		title:              "get empty config",
   147  		configYamlContent:  emptyConfigYAML,
   148  		configYamlFileName: "emptyConfig",
   149  		expected: &clientconfig.ClientConfig{
   150  			Type:           "kubernetes",
   151  			Contexts:       map[string]clientconfig.Context{},
   152  			CurrentContext: "",
   153  			Clouds:         map[string]clientconfig.CloudConfig{},
   154  			Credentials:    map[string]cloud.Credential{},
   155  		}})
   156  }
   157  
   158  type newK8sClientConfigTestCase struct {
   159  	title, contextName, clusterName, configYamlContent, configYamlFileName string
   160  	expected                                                               *clientconfig.ClientConfig
   161  	errMatch                                                               string
   162  }
   163  
   164  func (s *k8sConfigSuite) assertNewK8sClientConfig(c *gc.C, testCase newK8sClientConfigTestCase) {
   165  	f, err := s.writeTempKubeConfig(c, testCase.configYamlFileName, testCase.configYamlContent)
   166  	defer f.Close()
   167  	c.Assert(err, jc.ErrorIsNil)
   168  
   169  	c.Logf("test: %s", testCase.title)
   170  	cfg, err := clientconfig.NewK8sClientConfig(f, testCase.contextName, testCase.clusterName, nil)
   171  	if testCase.errMatch != "" {
   172  		c.Check(err, gc.ErrorMatches, testCase.errMatch)
   173  	} else {
   174  		c.Check(err, jc.ErrorIsNil)
   175  		c.Check(cfg, jc.DeepEquals, testCase.expected)
   176  	}
   177  }
   178  
   179  func (s *k8sConfigSuite) TestGetSingleConfig(c *gc.C) {
   180  	cred := cloud.NewCredential(
   181  		cloud.UserPassAuthType,
   182  		map[string]string{"username": "theuser", "password": "thepassword"})
   183  	cred.Label = `kubernetes credential "the-user"`
   184  	s.assertNewK8sClientConfig(c, newK8sClientConfigTestCase{
   185  		title:              "assert single config",
   186  		configYamlContent:  singleConfigYAML,
   187  		configYamlFileName: "singleConfig",
   188  		expected: &clientconfig.ClientConfig{
   189  			Type: "kubernetes",
   190  			Contexts: map[string]clientconfig.Context{
   191  				"the-context": {
   192  					CloudName:      "the-cluster",
   193  					CredentialName: "the-user"}},
   194  			CurrentContext: "the-context",
   195  			Clouds: map[string]clientconfig.CloudConfig{
   196  				"the-cluster": {
   197  					Endpoint:   "https://1.1.1.1:8888",
   198  					Attributes: map[string]interface{}{"CAData": "A"}}},
   199  			Credentials: map[string]cloud.Credential{
   200  				"the-user": cred,
   201  			},
   202  		},
   203  	})
   204  }
   205  
   206  func (s *k8sConfigSuite) TestConfigErrors(c *gc.C) {
   207  	for _, v := range []newK8sClientConfigTestCase{
   208  		{
   209  			title: "notSupportedAuthProviderTypeConfig",
   210  			configYamlContent: `
   211  - name: the-user
   212    user:
   213      auth-provider:
   214        config:
   215          cmd-args: config config-helper --format=json
   216          cmd-path: /usr/lib/google-cloud-sdk/bin/gcloud
   217          expiry-key: '{.credential.token_expiry}'
   218          token-key: '{.credential.access_token}'
   219        name: gcp
   220  `,
   221  			errMatch: `failed to read credentials from kubernetes config: configuration for "the-user" not supported`,
   222  		},
   223  		{
   224  			title: "tokenWithUsernameInvalidConfig",
   225  			configYamlContent: `
   226  - name: the-user
   227    user:
   228      password: defaultpassword
   229      username: defaultuser
   230      token: nonEmptyToken
   231  `,
   232  			errMatch: `failed to read credentials from kubernetes config: AuthInfo: "the-user" with both Token and User/Pass not valid`,
   233  		},
   234  		{
   235  			title: "emptyTokenInvalidConfig",
   236  			configYamlContent: `
   237  - name: the-user
   238    user:
   239      client-certificate-data: QQ==
   240      client-key-data: CK==
   241  `,
   242  			errMatch: `failed to read credentials from kubernetes config: missing token for "the-user" with auth type "oauth2withcert" not valid`,
   243  		},
   244  		{
   245  			title: "emptyClientKeyDataAndEmptyTokenInvalidConfig",
   246  			configYamlContent: `
   247  - name: the-user
   248    user:
   249      client-certificate-data: QQ==
   250  `,
   251  			errMatch: `failed to read credentials from kubernetes config: configuration for "the-user" not supported`,
   252  		},
   253  	} {
   254  		v.configYamlFileName = v.title
   255  		v.configYamlContent = prefixConfigYAML + v.configYamlContent
   256  		s.assertNewK8sClientConfig(c, v)
   257  	}
   258  }
   259  
   260  func (s *k8sConfigSuite) TestGetMultiConfig(c *gc.C) {
   261  	firstCred := cloud.NewCredential(
   262  		cloud.UserPassAuthType,
   263  		map[string]string{"username": "defaultuser", "password": "defaultpassword"})
   264  	firstCred.Label = `kubernetes credential "default-user"`
   265  	theCred := cloud.NewCredential(
   266  		cloud.OAuth2WithCertAuthType,
   267  		map[string]string{"ClientCertificateData": "A", "ClientKeyData": "B", "Token": "tokenwithcerttoken"})
   268  	theCred.Label = `kubernetes credential "the-user"`
   269  	secondCred := cloud.NewCredential(
   270  		cloud.CertificateAuthType,
   271  		map[string]string{"ClientCertificateData": "A", "Token": "tokenwithcerttoken"})
   272  	secondCred.Label = `kubernetes credential "second-user"`
   273  
   274  	for i, v := range []newK8sClientConfigTestCase{
   275  		{
   276  			title:       "no cluster name specified, will select current cluster",
   277  			clusterName: "", // will use current context.
   278  			expected: &clientconfig.ClientConfig{
   279  				Type: "kubernetes",
   280  				Contexts: map[string]clientconfig.Context{
   281  					"default-context": {
   282  						CloudName:      "default-cluster",
   283  						CredentialName: "default-user"},
   284  				},
   285  				CurrentContext: "default-context",
   286  				Clouds: map[string]clientconfig.CloudConfig{
   287  					"default-cluster": {
   288  						Endpoint:   "https://10.10.10.10:1010",
   289  						Attributes: map[string]interface{}{"CAData": ""},
   290  					},
   291  				},
   292  				Credentials: map[string]cloud.Credential{
   293  					"default-user": firstCred,
   294  				},
   295  			},
   296  		},
   297  		{
   298  			title:       "select the-cluster",
   299  			clusterName: "the-cluster",
   300  			expected: &clientconfig.ClientConfig{
   301  				Type: "kubernetes",
   302  				Contexts: map[string]clientconfig.Context{
   303  					"the-context": {
   304  						CloudName:      "the-cluster",
   305  						CredentialName: "the-user"},
   306  				},
   307  				CurrentContext: "default-context",
   308  				Clouds: map[string]clientconfig.CloudConfig{
   309  					"the-cluster": {
   310  						Endpoint:   "https://1.1.1.1:8888",
   311  						Attributes: map[string]interface{}{"CAData": "A"}}},
   312  				Credentials: map[string]cloud.Credential{
   313  					"the-user": theCred,
   314  				},
   315  			},
   316  		},
   317  		{
   318  			title:       "select second-cluster",
   319  			clusterName: "second-cluster",
   320  			expected: &clientconfig.ClientConfig{
   321  				Type: "kubernetes",
   322  				Contexts: map[string]clientconfig.Context{
   323  					"second-context": {
   324  						CloudName:      "second-cluster",
   325  						CredentialName: "second-user"},
   326  				},
   327  				CurrentContext: "default-context",
   328  				Clouds: map[string]clientconfig.CloudConfig{
   329  					"second-cluster": {
   330  						Endpoint:   "https://100.100.100.100:1010",
   331  						Attributes: map[string]interface{}{"CAData": "A"}}},
   332  				Credentials: map[string]cloud.Credential{
   333  					"second-user": secondCred,
   334  				},
   335  			},
   336  		},
   337  		{
   338  			title:       "select the-context",
   339  			contextName: "the-context",
   340  			expected: &clientconfig.ClientConfig{
   341  				Type: "kubernetes",
   342  				Contexts: map[string]clientconfig.Context{
   343  					"the-context": {
   344  						CloudName:      "the-cluster",
   345  						CredentialName: "the-user"},
   346  				},
   347  				CurrentContext: "default-context",
   348  				Clouds: map[string]clientconfig.CloudConfig{
   349  					"the-cluster": {
   350  						Endpoint:   "https://1.1.1.1:8888",
   351  						Attributes: map[string]interface{}{"CAData": "A"}}},
   352  				Credentials: map[string]cloud.Credential{
   353  					"the-user": theCred,
   354  				},
   355  			},
   356  		},
   357  		{
   358  			title:       "select default-cluster",
   359  			clusterName: "default-cluster",
   360  			expected: &clientconfig.ClientConfig{
   361  				Type: "kubernetes",
   362  				Contexts: map[string]clientconfig.Context{
   363  					"default-context": {
   364  						CloudName:      "default-cluster",
   365  						CredentialName: "default-user"},
   366  				},
   367  				CurrentContext: "default-context",
   368  				Clouds: map[string]clientconfig.CloudConfig{
   369  					"default-cluster": {
   370  						Endpoint:   "https://10.10.10.10:1010",
   371  						Attributes: map[string]interface{}{"CAData": ""},
   372  					}},
   373  				Credentials: map[string]cloud.Credential{
   374  					"default-user": firstCred,
   375  				},
   376  			},
   377  		},
   378  	} {
   379  		c.Logf("testcase %v: %s", i, v.title)
   380  		v.configYamlFileName = "multiConfig"
   381  		v.configYamlContent = multiConfigYAML
   382  		s.assertNewK8sClientConfig(c, v)
   383  	}
   384  }
   385  
   386  // TestGetSingleConfigReadsFilePaths checks that we handle config
   387  // with certificate/key file paths the same as we do those with
   388  // the data inline.
   389  func (s *k8sConfigSuite) TestGetSingleConfigReadsFilePaths(c *gc.C) {
   390  
   391  	singleConfig, err := clientcmd.Load([]byte(singleConfigYAML))
   392  	c.Assert(err, jc.ErrorIsNil)
   393  
   394  	tempdir := c.MkDir()
   395  	divert := func(name string, data *[]byte, path *string) {
   396  		*path = filepath.Join(tempdir, name)
   397  		err := ioutil.WriteFile(*path, *data, 0644)
   398  		c.Assert(err, jc.ErrorIsNil)
   399  		*data = nil
   400  	}
   401  
   402  	for name, cluster := range singleConfig.Clusters {
   403  		divert(
   404  			"cluster-"+name+".ca",
   405  			&cluster.CertificateAuthorityData,
   406  			&cluster.CertificateAuthority,
   407  		)
   408  	}
   409  
   410  	for name, authInfo := range singleConfig.AuthInfos {
   411  		divert(
   412  			"auth-"+name+".cert",
   413  			&authInfo.ClientCertificateData,
   414  			&authInfo.ClientCertificate,
   415  		)
   416  		divert(
   417  			"auth-"+name+".key",
   418  			&authInfo.ClientKeyData,
   419  			&authInfo.ClientKey,
   420  		)
   421  	}
   422  
   423  	singleConfigWithPathsYAML, err := clientcmd.Write(*singleConfig)
   424  	c.Assert(err, jc.ErrorIsNil)
   425  
   426  	cred := cloud.NewCredential(
   427  		cloud.UserPassAuthType,
   428  		map[string]string{"username": "theuser", "password": "thepassword"})
   429  	cred.Label = `kubernetes credential "the-user"`
   430  	s.assertNewK8sClientConfig(c, newK8sClientConfigTestCase{
   431  		title:              "assert single config",
   432  		configYamlContent:  string(singleConfigWithPathsYAML),
   433  		configYamlFileName: "singleConfigWithPaths",
   434  		expected: &clientconfig.ClientConfig{
   435  			Type: "kubernetes",
   436  			Contexts: map[string]clientconfig.Context{
   437  				"the-context": {
   438  					CloudName:      "the-cluster",
   439  					CredentialName: "the-user"}},
   440  			CurrentContext: "the-context",
   441  			Clouds: map[string]clientconfig.CloudConfig{
   442  				"the-cluster": {
   443  					Endpoint:   "https://1.1.1.1:8888",
   444  					Attributes: map[string]interface{}{"CAData": "A"}}},
   445  			Credentials: map[string]cloud.Credential{
   446  				"the-user": cred,
   447  			},
   448  		},
   449  	})
   450  }