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  }