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 }