github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/prow/kube/client.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  	"bytes"
    21  	"crypto/tls"
    22  	"crypto/x509"
    23  	"encoding/base64"
    24  	"encoding/json"
    25  	"fmt"
    26  	"io"
    27  	"io/ioutil"
    28  	"net/http"
    29  	"strings"
    30  	"time"
    31  
    32  	"github.com/ghodss/yaml"
    33  )
    34  
    35  const (
    36  	inClusterBaseURL = "https://kubernetes.default"
    37  	maxRetries       = 8
    38  	retryDelay       = 2 * time.Second
    39  )
    40  
    41  type Logger interface {
    42  	Debugf(s string, v ...interface{})
    43  }
    44  
    45  // Client interacts with the Kubernetes api-server.
    46  type Client struct {
    47  	// If Logger is non-nil, log all method calls with it.
    48  	Logger Logger
    49  
    50  	baseURL   string
    51  	client    *http.Client
    52  	token     string
    53  	namespace string
    54  	fake      bool
    55  }
    56  
    57  // Namespace returns a copy of the client pointing at the specified namespace.
    58  func (c *Client) Namespace(ns string) *Client {
    59  	nc := *c
    60  	nc.namespace = ns
    61  	return &nc
    62  }
    63  
    64  func (c *Client) log(methodName string, args ...interface{}) {
    65  	if c.Logger == nil {
    66  		return
    67  	}
    68  	var as []string
    69  	for _, arg := range args {
    70  		as = append(as, fmt.Sprintf("%v", arg))
    71  	}
    72  	c.Logger.Debugf("%s(%s)", methodName, strings.Join(as, ", "))
    73  }
    74  
    75  type ConflictError struct {
    76  	e error
    77  }
    78  
    79  func (e ConflictError) Error() string {
    80  	return e.e.Error()
    81  }
    82  
    83  func NewConflictError(e error) ConflictError {
    84  	return ConflictError{e: e}
    85  }
    86  
    87  type UnprocessableEntityError struct {
    88  	e error
    89  }
    90  
    91  func (e UnprocessableEntityError) Error() string {
    92  	return e.e.Error()
    93  }
    94  
    95  func NewUnprocessableEntityError(e error) UnprocessableEntityError {
    96  	return UnprocessableEntityError{e: e}
    97  }
    98  
    99  type request struct {
   100  	method      string
   101  	path        string
   102  	query       map[string]string
   103  	requestBody interface{}
   104  }
   105  
   106  func (c *Client) request(r *request, ret interface{}) error {
   107  	out, err := c.requestRetry(r)
   108  	if err != nil {
   109  		return err
   110  	}
   111  	if ret != nil {
   112  		if err := json.Unmarshal(out, ret); err != nil {
   113  			return err
   114  		}
   115  	}
   116  	return nil
   117  }
   118  
   119  func (c *Client) retry(r *request) (*http.Response, error) {
   120  	var resp *http.Response
   121  	var err error
   122  	backoff := retryDelay
   123  	for retries := 0; retries < maxRetries; retries++ {
   124  		resp, err = c.doRequest(r.method, r.path, r.query, r.requestBody)
   125  		if err == nil {
   126  			if resp.StatusCode < 500 {
   127  				break
   128  			}
   129  			resp.Body.Close()
   130  		}
   131  
   132  		time.Sleep(backoff)
   133  		backoff *= 2
   134  	}
   135  	return resp, err
   136  }
   137  
   138  // Retry on transport failures. Does not retry on 500s.
   139  func (c *Client) requestRetryStream(r *request) (io.ReadCloser, error) {
   140  	if c.fake {
   141  		return nil, nil
   142  	}
   143  	resp, err := c.retry(r)
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  	if resp.StatusCode == 409 {
   148  		return nil, NewConflictError(fmt.Errorf("body cannot be streamed"))
   149  	} else if resp.StatusCode < 200 || resp.StatusCode > 299 {
   150  		return nil, fmt.Errorf("response has status \"%s\"", resp.Status)
   151  	}
   152  	return resp.Body, nil
   153  }
   154  
   155  // Retry on transport failures. Does not retry on 500s.
   156  func (c *Client) requestRetry(r *request) ([]byte, error) {
   157  	if c.fake {
   158  		return []byte("{}"), nil
   159  	}
   160  	resp, err := c.retry(r)
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	defer resp.Body.Close()
   166  	rb, err := ioutil.ReadAll(resp.Body)
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  	if resp.StatusCode == 409 {
   171  		return nil, NewConflictError(fmt.Errorf("body: %s", string(rb)))
   172  	} else if resp.StatusCode == 422 {
   173  		return nil, NewUnprocessableEntityError(fmt.Errorf("body: %s", string(rb)))
   174  	} else if resp.StatusCode < 200 || resp.StatusCode > 299 {
   175  		return nil, fmt.Errorf("response has status \"%s\" and body \"%s\"", resp.Status, string(rb))
   176  	}
   177  	return rb, nil
   178  }
   179  
   180  func (c *Client) doRequest(method, urlPath string, query map[string]string, body interface{}) (*http.Response, error) {
   181  	url := c.baseURL + urlPath
   182  	var buf io.Reader
   183  	if body != nil {
   184  		b, err := json.Marshal(body)
   185  		if err != nil {
   186  			return nil, err
   187  		}
   188  		buf = bytes.NewBuffer(b)
   189  	}
   190  	req, err := http.NewRequest(method, url, buf)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  	if c.token != "" {
   195  		req.Header.Set("Authorization", "Bearer "+c.token)
   196  	}
   197  	if method == http.MethodPatch {
   198  		req.Header.Set("Content-Type", "application/strategic-merge-patch+json")
   199  	} else {
   200  		req.Header.Set("Content-Type", "application/json")
   201  	}
   202  
   203  	q := req.URL.Query()
   204  	for k, v := range query {
   205  		q.Add(k, v)
   206  	}
   207  	req.URL.RawQuery = q.Encode()
   208  
   209  	return c.client.Do(req)
   210  }
   211  
   212  // NewFakeClient creates a client that doesn't do anything.
   213  func NewFakeClient() *Client {
   214  	return &Client{
   215  		namespace: "default",
   216  		fake:      true,
   217  	}
   218  }
   219  
   220  // NewClientInCluster creates a Client that works from within a pod.
   221  func NewClientInCluster(namespace string) (*Client, error) {
   222  	tokenFile := "/var/run/secrets/kubernetes.io/serviceaccount/token"
   223  	token, err := ioutil.ReadFile(tokenFile)
   224  	if err != nil {
   225  		return nil, err
   226  	}
   227  
   228  	rootCAFile := "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
   229  	certData, err := ioutil.ReadFile(rootCAFile)
   230  	if err != nil {
   231  		return nil, err
   232  	}
   233  
   234  	cp := x509.NewCertPool()
   235  	cp.AppendCertsFromPEM(certData)
   236  
   237  	tr := &http.Transport{
   238  		TLSClientConfig: &tls.Config{
   239  			MinVersion: tls.VersionTLS12,
   240  			RootCAs:    cp,
   241  		},
   242  	}
   243  	c := &http.Client{Transport: tr}
   244  	return &Client{
   245  		baseURL:   inClusterBaseURL,
   246  		client:    c,
   247  		token:     string(token),
   248  		namespace: namespace,
   249  	}, nil
   250  }
   251  
   252  // Cluster represents the information necessary to talk to a Kubernetes
   253  // master endpoint.
   254  type Cluster struct {
   255  	// The IP address of the cluster's master endpoint.
   256  	Endpoint string `yaml:"endpoint"`
   257  	// Base64-encoded public cert used by clients to authenticate to the
   258  	// cluster endpoint.
   259  	ClientCertificate string `yaml:"clientCertificate"`
   260  	// Base64-encoded private key used by clients..
   261  	ClientKey string `yaml:"clientKey"`
   262  	// Base64-encoded public certificate that is the root of trust for the
   263  	// cluster.
   264  	ClusterCACertificate string `yaml:"clusterCaCertificate"`
   265  }
   266  
   267  // NewClientFromFile reads a Cluster object at clusterPath and returns an
   268  // authenticated client using the keys within.
   269  func NewClientFromFile(clusterPath, namespace string) (*Client, error) {
   270  	data, err := ioutil.ReadFile(clusterPath)
   271  	if err != nil {
   272  		return nil, err
   273  	}
   274  	var c Cluster
   275  	if err := yaml.Unmarshal(data, &c); err != nil {
   276  		return nil, err
   277  	}
   278  	return NewClient(&c, namespace)
   279  }
   280  
   281  // NewClient returns an authenticated Client using the keys in the Cluster.
   282  func NewClient(c *Cluster, namespace string) (*Client, error) {
   283  	cc, err := base64.StdEncoding.DecodeString(c.ClientCertificate)
   284  	if err != nil {
   285  		return nil, err
   286  	}
   287  	ck, err := base64.StdEncoding.DecodeString(c.ClientKey)
   288  	if err != nil {
   289  		return nil, err
   290  	}
   291  	ca, err := base64.StdEncoding.DecodeString(c.ClusterCACertificate)
   292  	if err != nil {
   293  		return nil, err
   294  	}
   295  
   296  	cert, err := tls.X509KeyPair(cc, ck)
   297  	if err != nil {
   298  		return nil, err
   299  	}
   300  
   301  	cp := x509.NewCertPool()
   302  	cp.AppendCertsFromPEM(ca)
   303  
   304  	tr := &http.Transport{
   305  		TLSClientConfig: &tls.Config{
   306  			MinVersion:   tls.VersionTLS12,
   307  			Certificates: []tls.Certificate{cert},
   308  			RootCAs:      cp,
   309  		},
   310  	}
   311  	return &Client{
   312  		baseURL:   c.Endpoint,
   313  		client:    &http.Client{Transport: tr},
   314  		namespace: namespace,
   315  	}, nil
   316  }
   317  
   318  func labelsToSelector(labels map[string]string) string {
   319  	var sel []string
   320  	for k, v := range labels {
   321  		sel = append(sel, fmt.Sprintf("%s = %s", k, v))
   322  	}
   323  	return strings.Join(sel, ",")
   324  }
   325  
   326  func (c *Client) GetPod(name string) (Pod, error) {
   327  	c.log("GetPod", name)
   328  	var retPod Pod
   329  	err := c.request(&request{
   330  		method: http.MethodGet,
   331  		path:   fmt.Sprintf("/api/v1/namespaces/%s/pods/%s", c.namespace, name),
   332  	}, &retPod)
   333  	return retPod, err
   334  }
   335  
   336  func (c *Client) ListPods(labels map[string]string) ([]Pod, error) {
   337  	c.log("ListPods", labels)
   338  	var pl struct {
   339  		Items []Pod `json:"items"`
   340  	}
   341  	err := c.request(&request{
   342  		method: http.MethodGet,
   343  		path:   fmt.Sprintf("/api/v1/namespaces/%s/pods", c.namespace),
   344  		query:  map[string]string{"labelSelector": labelsToSelector(labels)},
   345  	}, &pl)
   346  	return pl.Items, err
   347  }
   348  
   349  func (c *Client) DeletePod(name string) error {
   350  	c.log("DeletePod", name)
   351  	return c.request(&request{
   352  		method: http.MethodDelete,
   353  		path:   fmt.Sprintf("/api/v1/namespaces/%s/pods/%s", c.namespace, name),
   354  	}, nil)
   355  }
   356  
   357  func (c *Client) CreateProwJob(j ProwJob) (ProwJob, error) {
   358  	c.log("CreateProwJob", j)
   359  	var retJob ProwJob
   360  	err := c.request(&request{
   361  		method:      http.MethodPost,
   362  		path:        fmt.Sprintf("/apis/prow.k8s.io/v1/namespaces/%s/prowjobs", c.namespace),
   363  		requestBody: &j,
   364  	}, &retJob)
   365  	return retJob, err
   366  }
   367  
   368  func (c *Client) GetProwJob(name string) (ProwJob, error) {
   369  	c.log("GetProwJob", name)
   370  	var pj ProwJob
   371  	err := c.request(&request{
   372  		method: http.MethodGet,
   373  		path:   fmt.Sprintf("/apis/prow.k8s.io/v1/namespaces/%s/prowjobs/%s", c.namespace, name),
   374  	}, &pj)
   375  	return pj, err
   376  }
   377  
   378  func (c *Client) ListProwJobs(labels map[string]string) ([]ProwJob, error) {
   379  	c.log("ListProwJobs", labels)
   380  	var jl struct {
   381  		Items []ProwJob `json:"items"`
   382  	}
   383  	err := c.request(&request{
   384  		method: http.MethodGet,
   385  		path:   fmt.Sprintf("/apis/prow.k8s.io/v1/namespaces/%s/prowjobs", c.namespace),
   386  		query:  map[string]string{"labelSelector": labelsToSelector(labels)},
   387  	}, &jl)
   388  	return jl.Items, err
   389  }
   390  
   391  func (c *Client) DeleteProwJob(name string) error {
   392  	c.log("DeleteProwJob", name)
   393  	return c.request(&request{
   394  		method: http.MethodDelete,
   395  		path:   fmt.Sprintf("/apis/prow.k8s.io/v1/namespaces/%s/prowjobs/%s", c.namespace, name),
   396  	}, nil)
   397  }
   398  
   399  func (c *Client) ReplaceProwJob(name string, job ProwJob) (ProwJob, error) {
   400  	c.log("ReplaceProwJob", name, job)
   401  	var retJob ProwJob
   402  	err := c.request(&request{
   403  		method:      http.MethodPut,
   404  		path:        fmt.Sprintf("/apis/prow.k8s.io/v1/namespaces/%s/prowjobs/%s", c.namespace, name),
   405  		requestBody: &job,
   406  	}, &retJob)
   407  	return retJob, err
   408  }
   409  
   410  func (c *Client) CreatePod(p Pod) (Pod, error) {
   411  	c.log("CreatePod", p)
   412  	var retPod Pod
   413  	err := c.request(&request{
   414  		method:      http.MethodPost,
   415  		path:        fmt.Sprintf("/api/v1/namespaces/%s/pods", c.namespace),
   416  		requestBody: &p,
   417  	}, &retPod)
   418  	return retPod, err
   419  }
   420  
   421  func (c *Client) GetLog(pod string) ([]byte, error) {
   422  	c.log("GetLog", pod)
   423  	return c.requestRetry(&request{
   424  		method: http.MethodGet,
   425  		path:   fmt.Sprintf("/api/v1/namespaces/%s/pods/%s/log", c.namespace, pod),
   426  	})
   427  }
   428  
   429  func (c *Client) GetLogStream(pod string, options map[string]string) (io.ReadCloser, error) {
   430  	c.log("GetLogStream", pod)
   431  	return c.requestRetryStream(&request{
   432  		method: http.MethodGet,
   433  		path:   fmt.Sprintf("/api/v1/namespaces/%s/pods/%s/log", c.namespace, pod),
   434  		query:  options,
   435  	})
   436  }
   437  
   438  func (c *Client) CreateConfigMap(content ConfigMap) (ConfigMap, error) {
   439  	c.log("CreateConfigMap")
   440  	var retConfigMap ConfigMap
   441  	err := c.request(&request{
   442  		method:      http.MethodPost,
   443  		path:        fmt.Sprintf("/api/v1/namespaces/%s/configmaps", c.namespace),
   444  		requestBody: &content,
   445  	}, &retConfigMap)
   446  
   447  	return retConfigMap, err
   448  }
   449  
   450  func (c *Client) ReplaceConfigMap(name string, config ConfigMap) (ConfigMap, error) {
   451  	c.log("ReplaceConfigMap", name)
   452  	var retConfigMap ConfigMap
   453  	err := c.request(&request{
   454  		method:      http.MethodPut,
   455  		path:        fmt.Sprintf("/api/v1/namespaces/%s/configmaps/%s", c.namespace, name),
   456  		requestBody: &config,
   457  	}, &retConfigMap)
   458  
   459  	return retConfigMap, err
   460  }