github.com/yrj2011/jx-test-infra@v0.0.0-20190529031832-7a2065ee98eb/prow/kube/client_test.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package kube 18 19 import ( 20 "bufio" 21 "crypto/rsa" 22 "crypto/tls" 23 "crypto/x509" 24 "encoding/base64" 25 "encoding/pem" 26 "fmt" 27 "io/ioutil" 28 "math/big" 29 "math/rand" 30 "net" 31 "net/http" 32 "net/http/httptest" 33 "os" 34 "reflect" 35 "testing" 36 "time" 37 38 "k8s.io/api/core/v1" 39 ) 40 41 func getClient(url string) *Client { 42 return &Client{ 43 baseURL: url, 44 client: &http.Client{}, 45 token: "abcd", 46 namespace: "ns", 47 } 48 } 49 50 func TestNamespace(t *testing.T) { 51 c1 := &Client{ 52 baseURL: "a", 53 namespace: "ns1", 54 } 55 c2 := c1.Namespace("ns2") 56 if c1 == c2 { 57 t.Error("Namespace modified in place.") 58 } 59 if c2.baseURL != c1.baseURL { 60 t.Error("Didn't copy over struct members.") 61 } 62 if c2.namespace != "ns2" { 63 t.Errorf("Got wrong namespace. Got %s, expected ns2", c2.namespace) 64 } 65 } 66 67 func TestSetHiddenReposProviderGet(t *testing.T) { 68 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 69 if r.Method != http.MethodGet { 70 t.Errorf("Bad method: %s", r.Method) 71 } 72 switch r.URL.Path { 73 case "/apis/prow.k8s.io/v1/namespaces/ns/prowjobs/pja": 74 fmt.Fprint(w, `{"spec": {"job": "a", "refs": {"org": "org", "repo": "repo"}}}`) 75 case "/apis/prow.k8s.io/v1/namespaces/ns/prowjobs/pjb": 76 fmt.Fprint(w, `{"spec": {"job": "b", "refs": {"org": "hidden-org", "repo": "repo"}}}`) 77 default: 78 t.Errorf("Bad request path: %s", r.URL.Path) 79 } 80 })) 81 defer ts.Close() 82 c := getClient(ts.URL) 83 c.SetHiddenReposProvider(func() []string { return []string{"hidden-org"} }, false) 84 pj, err := c.GetProwJob("pja") 85 if err != nil { 86 t.Errorf("Didn't expect error: %v", err) 87 } 88 if got, expected := pj.Spec.Job, "a"; got != expected { 89 t.Errorf("Expected returned prowjob to be job %q, but got %q.", expected, got) 90 } 91 92 pj, err = c.GetProwJob("pjb") 93 if err == nil { 94 t.Fatal("Expected error getting hidden prowjob, but did not receive an error.") 95 } 96 } 97 98 func TestHiddenReposProviderGet(t *testing.T) { 99 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 100 if r.Method != http.MethodGet { 101 t.Errorf("Bad method: %s", r.Method) 102 } 103 switch r.URL.Path { 104 case "/apis/prow.k8s.io/v1/namespaces/ns/prowjobs/pja": 105 fmt.Fprint(w, `{"spec": {"job": "a", "refs": {"org": "org", "repo": "repo"}}}`) 106 case "/apis/prow.k8s.io/v1/namespaces/ns/prowjobs/pjb": 107 fmt.Fprint(w, `{"spec": {"job": "b", "refs": {"org": "hidden-org", "repo": "repo"}}}`) 108 default: 109 t.Errorf("Bad request path: %s", r.URL.Path) 110 } 111 })) 112 defer ts.Close() 113 c := getClient(ts.URL) 114 c.SetHiddenReposProvider(func() []string { return []string{"hidden-org"} }, true) 115 pj, err := c.GetProwJob("pjb") 116 if err != nil { 117 t.Errorf("Didn't expect error: %v", err) 118 } 119 if got, expected := pj.Spec.Job, "b"; got != expected { 120 t.Errorf("Expected returned prowjob to be job %q, but got %q.", expected, got) 121 } 122 } 123 124 func TestSetHiddenReposProviderList(t *testing.T) { 125 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 126 if r.Method != http.MethodGet { 127 t.Errorf("Bad method: %s", r.Method) 128 } 129 if r.URL.Path != "/apis/prow.k8s.io/v1/namespaces/ns/prowjobs" { 130 t.Errorf("Bad request path: %s", r.URL.Path) 131 } 132 fmt.Fprint(w, `{"items": [{"spec": {"job": "a", "refs": {"org": "org", "repo": "hidden-repo"}}}, {"spec": {"job": "b", "refs": {"org": "org", "repo": "repo"}}}]}`) 133 })) 134 defer ts.Close() 135 c := getClient(ts.URL) 136 c.SetHiddenReposProvider(func() []string { return []string{"org/hidden-repo"} }, false) 137 pjs, err := c.ListProwJobs(EmptySelector) 138 if err != nil { 139 t.Errorf("Didn't expect error: %v", err) 140 } 141 if len(pjs) != 1 { 142 t.Fatalf("Expected one prowjobs, but got %v.", pjs) 143 } 144 if got, expected := pjs[0].Spec.Job, "b"; got != expected { 145 t.Errorf("Expected returned prowjob to be job %q, but got %q.", expected, got) 146 } 147 } 148 149 func TestHiddenReposProviderList(t *testing.T) { 150 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 151 if r.Method != http.MethodGet { 152 t.Errorf("Bad method: %s", r.Method) 153 } 154 if r.URL.Path != "/apis/prow.k8s.io/v1/namespaces/ns/prowjobs" { 155 t.Errorf("Bad request path: %s", r.URL.Path) 156 } 157 fmt.Fprint(w, `{"items": [{"spec": {"job": "a", "refs": {"org": "org", "repo": "hidden-repo"}}}, {"spec": {"job": "b", "refs": {"org": "org", "repo": "repo"}}}]}`) 158 })) 159 defer ts.Close() 160 c := getClient(ts.URL) 161 c.SetHiddenReposProvider(func() []string { return []string{"org/hidden-repo"} }, true) 162 pjs, err := c.ListProwJobs(EmptySelector) 163 if err != nil { 164 t.Errorf("Didn't expect error: %v", err) 165 } 166 if len(pjs) != 1 { 167 t.Fatalf("Expected one prowjobs, but got %v.", pjs) 168 } 169 if got, expected := pjs[0].Spec.Job, "a"; got != expected { 170 t.Errorf("Expected returned prowjob to be job %q, but got %q.", expected, got) 171 } 172 } 173 174 func TestListPods(t *testing.T) { 175 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 176 if r.Method != http.MethodGet { 177 t.Errorf("Bad method: %s", r.Method) 178 } 179 if r.URL.Path != "/api/v1/namespaces/ns/pods" { 180 t.Errorf("Bad request path: %s", r.URL.Path) 181 } 182 fmt.Fprint(w, `{"items": [{}, {}]}`) 183 })) 184 defer ts.Close() 185 c := getClient(ts.URL) 186 ps, err := c.ListPods(EmptySelector) 187 if err != nil { 188 t.Errorf("Didn't expect error: %v", err) 189 } 190 if len(ps) != 2 { 191 t.Error("Expected two pods.") 192 } 193 } 194 195 func TestDeletePod(t *testing.T) { 196 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 197 if r.Method != http.MethodDelete { 198 t.Errorf("Bad method: %s", r.Method) 199 } 200 if r.URL.Path != "/api/v1/namespaces/ns/pods/po" { 201 t.Errorf("Bad request path: %s", r.URL.Path) 202 } 203 })) 204 defer ts.Close() 205 c := getClient(ts.URL) 206 err := c.DeletePod("po") 207 if err != nil { 208 t.Errorf("Didn't expect error: %v", err) 209 } 210 } 211 212 func TestGetPod(t *testing.T) { 213 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 214 if r.Method != http.MethodGet { 215 t.Errorf("Bad method: %s", r.Method) 216 } 217 if r.URL.Path != "/api/v1/namespaces/ns/pods/po" { 218 t.Errorf("Bad request path: %s", r.URL.Path) 219 } 220 fmt.Fprint(w, `{"metadata": {"name": "abcd"}}`) 221 })) 222 defer ts.Close() 223 c := getClient(ts.URL) 224 po, err := c.GetPod("po") 225 if err != nil { 226 t.Errorf("Didn't expect error: %v", err) 227 } 228 if po.ObjectMeta.Name != "abcd" { 229 t.Errorf("Wrong name: %s", po.ObjectMeta.Name) 230 } 231 } 232 233 func TestCreatePod(t *testing.T) { 234 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 235 if r.Method != http.MethodPost { 236 t.Errorf("Bad method: %s", r.Method) 237 } 238 if r.URL.Path != "/api/v1/namespaces/ns/pods" { 239 t.Errorf("Bad request path: %s", r.URL.Path) 240 } 241 fmt.Fprint(w, `{"metadata": {"name": "abcd"}}`) 242 })) 243 defer ts.Close() 244 c := getClient(ts.URL) 245 po, err := c.CreatePod(v1.Pod{}) 246 if err != nil { 247 t.Errorf("Didn't expect error: %v", err) 248 } 249 if po.ObjectMeta.Name != "abcd" { 250 t.Errorf("Wrong name: %s", po.ObjectMeta.Name) 251 } 252 } 253 254 func TestCreateConfigMap(t *testing.T) { 255 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 256 if r.Method != http.MethodPost { 257 t.Errorf("Bad method: %s", r.Method) 258 } 259 if r.URL.Path != "/api/v1/namespaces/ns/configmaps" { 260 t.Errorf("Bad request path: %s", r.URL.Path) 261 } 262 fmt.Fprint(w, `{"metadata": {"name": "abcd"}}`) 263 })) 264 defer ts.Close() 265 c := getClient(ts.URL) 266 if _, err := c.CreateConfigMap(ConfigMap{}); err != nil { 267 t.Errorf("Didn't expect error: %v", err) 268 } 269 } 270 271 func TestReplaceConfigMap(t *testing.T) { 272 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 273 if r.Method != http.MethodPut { 274 t.Errorf("Bad method: %s", r.Method) 275 } 276 if r.URL.Path != "/api/v1/namespaces/ns/configmaps/config" { 277 t.Errorf("Bad request path: %s", r.URL.Path) 278 } 279 fmt.Fprint(w, `{"metadata": {"name": "abcd"}}`) 280 })) 281 defer ts.Close() 282 c := getClient(ts.URL) 283 if _, err := c.ReplaceConfigMap("config", ConfigMap{}); err != nil { 284 t.Errorf("Didn't expect error: %v", err) 285 } 286 } 287 288 // TestNewClient messes around with certs and keys and such to just make sure 289 // that our cert handling is done properly. We create root and client keys, 290 // then server and client certificates, then ensure that the client can talk 291 // to the server. 292 // See https://ericchiang.github.io/post/go-tls/ for implementation details. 293 func TestNewClient(t *testing.T) { 294 r := rand.New(rand.NewSource(42)) 295 rootKey, err := rsa.GenerateKey(r, 2048) 296 if err != nil { 297 t.Fatalf("Generating key: %v", err) 298 } 299 tmpl := &x509.Certificate{ 300 SerialNumber: big.NewInt(42), 301 SignatureAlgorithm: x509.SHA256WithRSA, 302 NotBefore: time.Now(), 303 NotAfter: time.Now().Add(time.Hour), 304 BasicConstraintsValid: true, 305 IsCA: true, 306 KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature, 307 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, 308 IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, 309 } 310 certDER, err := x509.CreateCertificate(r, tmpl, tmpl, &rootKey.PublicKey, rootKey) 311 if err != nil { 312 t.Fatalf("Creating cert: %v", err) 313 } 314 rootCert, err := x509.ParseCertificate(certDER) 315 if err != nil { 316 t.Fatalf("Parsing cert: %v", err) 317 } 318 rootCertPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) 319 rootKeyPEM := pem.EncodeToMemory(&pem.Block{ 320 Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rootKey), 321 }) 322 rootTLSCert, err := tls.X509KeyPair(rootCertPEM, rootKeyPEM) 323 if err != nil { 324 t.Fatalf("Creating KeyPair: %v", err) 325 } 326 327 clientKey, err := rsa.GenerateKey(r, 2048) 328 if err != nil { 329 t.Fatalf("Creating key: %v", err) 330 } 331 332 clientCertTmpl := &x509.Certificate{ 333 BasicConstraintsValid: true, 334 SerialNumber: big.NewInt(43), 335 SignatureAlgorithm: x509.SHA256WithRSA, 336 NotBefore: time.Now(), 337 NotAfter: time.Now().Add(time.Hour), 338 KeyUsage: x509.KeyUsageDigitalSignature, 339 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, 340 } 341 clientCertDER, err := x509.CreateCertificate(r, clientCertTmpl, rootCert, &clientKey.PublicKey, rootKey) 342 if err != nil { 343 t.Fatalf("Creating cert: %v", err) 344 } 345 clientCertPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: clientCertDER}) 346 clientKeyPEM := pem.EncodeToMemory(&pem.Block{ 347 Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(clientKey), 348 }) 349 350 certPool := x509.NewCertPool() 351 certPool.AppendCertsFromPEM(rootCertPEM) 352 353 clus := &Cluster{ 354 ClientCertificate: base64.StdEncoding.EncodeToString(clientCertPEM), 355 ClientKey: base64.StdEncoding.EncodeToString(clientKeyPEM), 356 ClusterCACertificate: base64.StdEncoding.EncodeToString(rootCertPEM), 357 } 358 s := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("{}")) })) 359 s.TLS = &tls.Config{ 360 Certificates: []tls.Certificate{rootTLSCert}, 361 ClientCAs: certPool, 362 ClientAuth: tls.RequireAndVerifyClientCert, 363 } 364 s.StartTLS() 365 defer s.Close() 366 clus.Endpoint = s.URL 367 cl, err := NewClient(clus, "default") 368 if err != nil { 369 t.Fatalf("Failed to create client: %v", err) 370 } 371 if _, err := cl.GetPod("p"); err != nil { 372 t.Fatalf("Failed to talk to server: %v", err) 373 } 374 } 375 376 type tempConfig struct { 377 file *os.File 378 writer *bufio.Writer 379 } 380 381 func newTempConfig() (*tempConfig, error) { 382 tempfile, err := ioutil.TempFile(os.TempDir(), "prow_kube_client_test") 383 if err != nil { 384 return nil, err 385 } 386 return &tempConfig{file: tempfile, writer: bufio.NewWriter(tempfile)}, nil 387 } 388 389 func (t *tempConfig) SetContent(content string) error { 390 // Clear file and reset writing offset 391 t.file.Truncate(0) 392 t.file.Seek(0, os.SEEK_SET) 393 t.writer.Reset(t.file) 394 if _, err := t.writer.WriteString(content); err != nil { 395 return err 396 } 397 if err := t.writer.Flush(); err != nil { 398 return err 399 } 400 return nil 401 } 402 403 func (t *tempConfig) Clean() { 404 t.file.Close() 405 os.Remove(t.file.Name()) 406 } 407 408 func TestClientMapFromFile(t *testing.T) { 409 newClient = func(c *Cluster, namespace string) (*Client, error) { 410 return &Client{baseURL: c.Endpoint}, nil 411 } 412 defer func() { newClient = NewClient }() 413 414 temp, err := newTempConfig() 415 if err != nil { 416 t.Fatalf("Failed to create temp file for test: %v", err) 417 } 418 defer temp.Clean() 419 420 testCases := []struct { 421 name string 422 configContents string 423 expectedMap map[string]*Client 424 }{ 425 { 426 name: "single cluster config", 427 configContents: `endpoint: "cluster1" 428 clientKey: "key1" 429 `, 430 expectedMap: map[string]*Client{ 431 DefaultClusterAlias: {baseURL: "cluster1"}, 432 }, 433 }, 434 { 435 name: "multi cluster config", 436 configContents: `"default": 437 endpoint: "cluster1" 438 clientKey: "key1" 439 "trusted": 440 endpoint: "cluster2" 441 clientKey: "key2" 442 `, 443 expectedMap: map[string]*Client{ 444 DefaultClusterAlias: {baseURL: "cluster1"}, 445 "trusted": {baseURL: "cluster2"}, 446 }, 447 }, 448 { 449 name: "multi cluster config missing 'default' key", 450 configContents: `"untrusted": 451 endpoint: "cluster1" 452 clientKey: "key1" 453 "trusted": 454 endpoint: "cluster2" 455 clientKey: "key2" 456 `, 457 expectedMap: nil, 458 }, 459 } 460 461 for _, tc := range testCases { 462 t.Logf("Running test scenario %q...", tc.name) 463 if err := temp.SetContent(tc.configContents); err != nil { 464 t.Fatalf("Error setting temp file contents: %v", err) 465 } 466 m, err := ClientMapFromFile(temp.file.Name(), "ns") 467 if err != nil && tc.expectedMap != nil { 468 t.Fatalf("Unexpected error loading config: %v.", err) 469 } else if err == nil && tc.expectedMap == nil { 470 t.Fatal("Expected an error loading the config, but did not receive one!") 471 } 472 if expect, got := tc.expectedMap, m; !reflect.DeepEqual(expect, got) { 473 t.Errorf("Expected cluster config to produce map %v, but got %v.", expect, got) 474 } 475 } 476 }