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 }