github.com/migueleliasweb/helm@v2.6.1+incompatible/pkg/kube/client.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors All rights reserved. 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 // import "k8s.io/helm/pkg/kube" 18 19 import ( 20 "bytes" 21 "encoding/json" 22 goerrors "errors" 23 "fmt" 24 "io" 25 "log" 26 "strings" 27 "time" 28 29 jsonpatch "github.com/evanphx/json-patch" 30 "k8s.io/apimachinery/pkg/api/errors" 31 "k8s.io/apimachinery/pkg/api/meta" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 34 "k8s.io/apimachinery/pkg/fields" 35 "k8s.io/apimachinery/pkg/labels" 36 "k8s.io/apimachinery/pkg/runtime" 37 "k8s.io/apimachinery/pkg/types" 38 "k8s.io/apimachinery/pkg/util/strategicpatch" 39 "k8s.io/apimachinery/pkg/watch" 40 "k8s.io/client-go/tools/clientcmd" 41 "k8s.io/kubernetes/pkg/api" 42 "k8s.io/kubernetes/pkg/api/helper" 43 "k8s.io/kubernetes/pkg/api/v1" 44 apps "k8s.io/kubernetes/pkg/apis/apps/v1beta1" 45 batchinternal "k8s.io/kubernetes/pkg/apis/batch" 46 batch "k8s.io/kubernetes/pkg/apis/batch/v1" 47 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" 48 conditions "k8s.io/kubernetes/pkg/client/unversioned" 49 "k8s.io/kubernetes/pkg/kubectl" 50 cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" 51 "k8s.io/kubernetes/pkg/kubectl/resource" 52 "k8s.io/kubernetes/pkg/printers" 53 ) 54 55 // ErrNoObjectsVisited indicates that during a visit operation, no matching objects were found. 56 var ErrNoObjectsVisited = goerrors.New("no objects visited") 57 58 // Client represents a client capable of communicating with the Kubernetes API. 59 type Client struct { 60 cmdutil.Factory 61 // SchemaCacheDir is the path for loading cached schema. 62 SchemaCacheDir string 63 64 Log func(string, ...interface{}) 65 } 66 67 // New creates a new Client. 68 func New(config clientcmd.ClientConfig) *Client { 69 return &Client{ 70 Factory: cmdutil.NewFactory(config), 71 SchemaCacheDir: clientcmd.RecommendedSchemaFile, 72 Log: func(_ string, _ ...interface{}) {}, 73 } 74 } 75 76 // ResourceActorFunc performs an action on a single resource. 77 type ResourceActorFunc func(*resource.Info) error 78 79 // Create creates Kubernetes resources from an io.reader. 80 // 81 // Namespace will set the namespace. 82 func (c *Client) Create(namespace string, reader io.Reader, timeout int64, shouldWait bool) error { 83 client, err := c.ClientSet() 84 if err != nil { 85 return err 86 } 87 if err := ensureNamespace(client, namespace); err != nil { 88 return err 89 } 90 c.Log("building resources from manifest") 91 infos, buildErr := c.BuildUnstructured(namespace, reader) 92 if buildErr != nil { 93 return buildErr 94 } 95 c.Log("creating %d resource(s)", len(infos)) 96 if err := perform(infos, createResource); err != nil { 97 return err 98 } 99 if shouldWait { 100 return c.waitForResources(time.Duration(timeout)*time.Second, infos) 101 } 102 return nil 103 } 104 105 func (c *Client) newBuilder(namespace string, reader io.Reader) *resource.Result { 106 schema, err := c.Validator(true, c.SchemaCacheDir) 107 if err != nil { 108 c.Log("warning: failed to load schema: %s", err) 109 } 110 return c.NewBuilder(true). 111 ContinueOnError(). 112 Schema(schema). 113 NamespaceParam(namespace). 114 DefaultNamespace(). 115 Stream(reader, ""). 116 Flatten(). 117 Do() 118 } 119 120 // BuildUnstructured validates for Kubernetes objects and returns unstructured infos. 121 func (c *Client) BuildUnstructured(namespace string, reader io.Reader) (Result, error) { 122 schema, err := c.Validator(true, c.SchemaCacheDir) 123 if err != nil { 124 c.Log("warning: failed to load schema: %s", err) 125 } 126 127 var result Result 128 b, err := c.NewUnstructuredBuilder(true) 129 if err != nil { 130 return result, err 131 } 132 result, err = b.ContinueOnError(). 133 Schema(schema). 134 NamespaceParam(namespace). 135 DefaultNamespace(). 136 Stream(reader, ""). 137 Flatten(). 138 Do().Infos() 139 return result, scrubValidationError(err) 140 } 141 142 // Build validates for Kubernetes objects and returns resource Infos from a io.Reader. 143 func (c *Client) Build(namespace string, reader io.Reader) (Result, error) { 144 var result Result 145 result, err := c.newBuilder(namespace, reader).Infos() 146 return result, scrubValidationError(err) 147 } 148 149 // Get gets Kubernetes resources as pretty-printed string. 150 // 151 // Namespace will set the namespace. 152 func (c *Client) Get(namespace string, reader io.Reader) (string, error) { 153 // Since we don't know what order the objects come in, let's group them by the types, so 154 // that when we print them, they come out looking good (headers apply to subgroups, etc.). 155 objs := make(map[string][]runtime.Object) 156 infos, err := c.BuildUnstructured(namespace, reader) 157 if err != nil { 158 return "", err 159 } 160 missing := []string{} 161 err = perform(infos, func(info *resource.Info) error { 162 c.Log("Doing get for %s: %q", info.Mapping.GroupVersionKind.Kind, info.Name) 163 if err := info.Get(); err != nil { 164 c.Log("WARNING: Failed Get for resource %q: %s", info.Name, err) 165 missing = append(missing, fmt.Sprintf("%v\t\t%s", info.Mapping.Resource, info.Name)) 166 return nil 167 } 168 169 // Use APIVersion/Kind as grouping mechanism. I'm not sure if you can have multiple 170 // versions per cluster, but this certainly won't hurt anything, so let's be safe. 171 gvk := info.ResourceMapping().GroupVersionKind 172 vk := gvk.Version + "/" + gvk.Kind 173 objs[vk] = append(objs[vk], info.Object) 174 return nil 175 }) 176 if err != nil { 177 return "", err 178 } 179 180 // Ok, now we have all the objects grouped by types (say, by v1/Pod, v1/Service, etc.), so 181 // spin through them and print them. Printer is cool since it prints the header only when 182 // an object type changes, so we can just rely on that. Problem is it doesn't seem to keep 183 // track of tab widths. 184 buf := new(bytes.Buffer) 185 p, _ := c.Printer(nil, printers.PrintOptions{}) 186 for t, ot := range objs { 187 if _, err = buf.WriteString("==> " + t + "\n"); err != nil { 188 return "", err 189 } 190 for _, o := range ot { 191 if err := p.PrintObj(o, buf); err != nil { 192 c.Log("failed to print object type %s, object: %q :\n %v", t, o, err) 193 return "", err 194 } 195 } 196 if _, err := buf.WriteString("\n"); err != nil { 197 return "", err 198 } 199 } 200 if len(missing) > 0 { 201 buf.WriteString("==> MISSING\nKIND\t\tNAME\n") 202 for _, s := range missing { 203 fmt.Fprintln(buf, s) 204 } 205 } 206 return buf.String(), nil 207 } 208 209 // Update reads in the current configuration and a target configuration from io.reader 210 // and creates resources that don't already exists, updates resources that have been modified 211 // in the target configuration and deletes resources from the current configuration that are 212 // not present in the target configuration. 213 // 214 // Namespace will set the namespaces. 215 func (c *Client) Update(namespace string, originalReader, targetReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error { 216 original, err := c.BuildUnstructured(namespace, originalReader) 217 if err != nil { 218 return fmt.Errorf("failed decoding reader into objects: %s", err) 219 } 220 221 c.Log("building resources from updated manifest") 222 target, err := c.BuildUnstructured(namespace, targetReader) 223 if err != nil { 224 return fmt.Errorf("failed decoding reader into objects: %s", err) 225 } 226 227 updateErrors := []string{} 228 229 c.Log("checking %d resources for changes", len(target)) 230 err = target.Visit(func(info *resource.Info, err error) error { 231 if err != nil { 232 return err 233 } 234 235 helper := resource.NewHelper(info.Client, info.Mapping) 236 if _, err := helper.Get(info.Namespace, info.Name, info.Export); err != nil { 237 if !errors.IsNotFound(err) { 238 return fmt.Errorf("Could not get information about the resource: %s", err) 239 } 240 241 // Since the resource does not exist, create it. 242 if err := createResource(info); err != nil { 243 return fmt.Errorf("failed to create resource: %s", err) 244 } 245 246 kind := info.Mapping.GroupVersionKind.Kind 247 c.Log("Created a new %s called %q\n", kind, info.Name) 248 return nil 249 } 250 251 originalInfo := original.Get(info) 252 if originalInfo == nil { 253 return fmt.Errorf("no resource with the name %q found", info.Name) 254 } 255 256 if err := updateResource(c, info, originalInfo.Object, force, recreate); err != nil { 257 c.Log("error updating the resource %q:\n\t %v", info.Name, err) 258 updateErrors = append(updateErrors, err.Error()) 259 } 260 261 return nil 262 }) 263 264 switch { 265 case err != nil: 266 return err 267 case len(updateErrors) != 0: 268 return fmt.Errorf(strings.Join(updateErrors, " && ")) 269 } 270 271 for _, info := range original.Difference(target) { 272 c.Log("Deleting %q in %s...", info.Name, info.Namespace) 273 if err := deleteResource(c, info); err != nil { 274 c.Log("Failed to delete %q, err: %s", info.Name, err) 275 } 276 } 277 if shouldWait { 278 return c.waitForResources(time.Duration(timeout)*time.Second, target) 279 } 280 return nil 281 } 282 283 // Delete deletes Kubernetes resources from an io.reader. 284 // 285 // Namespace will set the namespace. 286 func (c *Client) Delete(namespace string, reader io.Reader) error { 287 infos, err := c.BuildUnstructured(namespace, reader) 288 if err != nil { 289 return err 290 } 291 return perform(infos, func(info *resource.Info) error { 292 c.Log("Starting delete for %q %s", info.Name, info.Mapping.GroupVersionKind.Kind) 293 err := deleteResource(c, info) 294 return c.skipIfNotFound(err) 295 }) 296 } 297 298 func (c *Client) skipIfNotFound(err error) error { 299 if errors.IsNotFound(err) { 300 c.Log("%v", err) 301 return nil 302 } 303 return err 304 } 305 306 func (c *Client) watchTimeout(t time.Duration) ResourceActorFunc { 307 return func(info *resource.Info) error { 308 return c.watchUntilReady(t, info) 309 } 310 } 311 312 // WatchUntilReady watches the resource given in the reader, and waits until it is ready. 313 // 314 // This function is mainly for hook implementations. It watches for a resource to 315 // hit a particular milestone. The milestone depends on the Kind. 316 // 317 // For most kinds, it checks to see if the resource is marked as Added or Modified 318 // by the Kubernetes event stream. For some kinds, it does more: 319 // 320 // - Jobs: A job is marked "Ready" when it has successfully completed. This is 321 // ascertained by watching the Status fields in a job's output. 322 // 323 // Handling for other kinds will be added as necessary. 324 func (c *Client) WatchUntilReady(namespace string, reader io.Reader, timeout int64, shouldWait bool) error { 325 infos, err := c.Build(namespace, reader) 326 if err != nil { 327 return err 328 } 329 // For jobs, there's also the option to do poll c.Jobs(namespace).Get(): 330 // https://github.com/adamreese/kubernetes/blob/master/test/e2e/job.go#L291-L300 331 return perform(infos, c.watchTimeout(time.Duration(timeout)*time.Second)) 332 } 333 334 func perform(infos Result, fn ResourceActorFunc) error { 335 if len(infos) == 0 { 336 return ErrNoObjectsVisited 337 } 338 339 for _, info := range infos { 340 if err := fn(info); err != nil { 341 return err 342 } 343 } 344 return nil 345 } 346 347 func createResource(info *resource.Info) error { 348 obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object) 349 if err != nil { 350 return err 351 } 352 return info.Refresh(obj, true) 353 } 354 355 func deleteResource(c *Client, info *resource.Info) error { 356 reaper, err := c.Reaper(info.Mapping) 357 if err != nil { 358 // If there is no reaper for this resources, delete it. 359 if kubectl.IsNoSuchReaperError(err) { 360 return resource.NewHelper(info.Client, info.Mapping).Delete(info.Namespace, info.Name) 361 } 362 return err 363 } 364 c.Log("Using reaper for deleting %q", info.Name) 365 return reaper.Stop(info.Namespace, info.Name, 0, nil) 366 } 367 368 func createPatch(mapping *meta.RESTMapping, target, current runtime.Object) ([]byte, types.PatchType, error) { 369 oldData, err := json.Marshal(current) 370 if err != nil { 371 return nil, types.StrategicMergePatchType, fmt.Errorf("serializing current configuration: %s", err) 372 } 373 newData, err := json.Marshal(target) 374 if err != nil { 375 return nil, types.StrategicMergePatchType, fmt.Errorf("serializing target configuration: %s", err) 376 } 377 378 if helper.Semantic.DeepEqual(oldData, newData) { 379 return nil, types.StrategicMergePatchType, nil 380 } 381 382 // Get a versioned object 383 versionedObject, err := api.Scheme.New(mapping.GroupVersionKind) 384 switch { 385 case runtime.IsNotRegisteredError(err): 386 // fall back to generic JSON merge patch 387 patch, err := jsonpatch.CreateMergePatch(oldData, newData) 388 return patch, types.MergePatchType, err 389 case err != nil: 390 return nil, types.StrategicMergePatchType, fmt.Errorf("failed to get versionedObject: %s", err) 391 default: 392 patch, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, versionedObject) 393 return patch, types.StrategicMergePatchType, err 394 } 395 } 396 397 func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, force bool, recreate bool) error { 398 patch, patchType, err := createPatch(target.Mapping, target.Object, currentObj) 399 if err != nil { 400 return fmt.Errorf("failed to create patch: %s", err) 401 } 402 if patch == nil { 403 c.Log("Looks like there are no changes for %s %q", target.Mapping.GroupVersionKind.Kind, target.Name) 404 // This needs to happen to make sure that tiller has the latest info from the API 405 // Otherwise there will be no labels and other functions that use labels will panic 406 if err := target.Get(); err != nil { 407 return fmt.Errorf("error trying to refresh resource information: %v", err) 408 } 409 return nil 410 } 411 412 // send patch to server 413 helper := resource.NewHelper(target.Client, target.Mapping) 414 415 obj, err := helper.Patch(target.Namespace, target.Name, patchType, patch) 416 if err != nil { 417 kind := target.Mapping.GroupVersionKind.Kind 418 log.Printf("Cannot patch %s: %q (%v)", kind, target.Name, err) 419 420 if force { 421 // Attempt to delete... 422 if err := deleteResource(c, target); err != nil { 423 return err 424 } 425 log.Printf("Deleted %s: %q", kind, target.Name) 426 427 // ... and recreate 428 if err := createResource(target); err != nil { 429 return fmt.Errorf("Failed to recreate resource: %s", err) 430 } 431 log.Printf("Created a new %s called %q\n", kind, target.Name) 432 433 // No need to refresh the target, as we recreated the resource based 434 // on it. In addition, it might not exist yet and a call to `Refresh` 435 // may fail. 436 } else { 437 log.Print("Use --force to force recreation of the resource") 438 return err 439 } 440 } else { 441 // When patch succeeds without needing to recreate, refresh target. 442 target.Refresh(obj, true) 443 } 444 445 if !recreate { 446 return nil 447 } 448 449 versioned, err := c.AsVersionedObject(target.Object) 450 if runtime.IsNotRegisteredError(err) { 451 return nil 452 } 453 if err != nil { 454 return err 455 } 456 457 selector, err := getSelectorFromObject(versioned) 458 if err != nil { 459 return nil 460 } 461 462 client, err := c.ClientSet() 463 if err != nil { 464 return err 465 } 466 467 pods, err := client.Core().Pods(target.Namespace).List(metav1.ListOptions{ 468 FieldSelector: fields.Everything().String(), 469 LabelSelector: labels.Set(selector).AsSelector().String(), 470 }) 471 if err != nil { 472 return err 473 } 474 475 // Restart pods 476 for _, pod := range pods.Items { 477 c.Log("Restarting pod: %v/%v", pod.Namespace, pod.Name) 478 479 // Delete each pod for get them restarted with changed spec. 480 if err := client.Core().Pods(pod.Namespace).Delete(pod.Name, metav1.NewPreconditionDeleteOptions(string(pod.UID))); err != nil { 481 return err 482 } 483 } 484 return nil 485 } 486 487 func getSelectorFromObject(obj runtime.Object) (map[string]string, error) { 488 switch typed := obj.(type) { 489 case *v1.ReplicationController: 490 return typed.Spec.Selector, nil 491 case *v1beta1.ReplicaSet: 492 return typed.Spec.Selector.MatchLabels, nil 493 case *v1beta1.Deployment: 494 return typed.Spec.Selector.MatchLabels, nil 495 case *v1beta1.DaemonSet: 496 return typed.Spec.Selector.MatchLabels, nil 497 case *batch.Job: 498 return typed.Spec.Selector.MatchLabels, nil 499 case *apps.StatefulSet: 500 return typed.Spec.Selector.MatchLabels, nil 501 default: 502 return nil, fmt.Errorf("Unsupported kind when getting selector: %v", obj) 503 } 504 } 505 506 func (c *Client) watchUntilReady(timeout time.Duration, info *resource.Info) error { 507 w, err := resource.NewHelper(info.Client, info.Mapping).WatchSingle(info.Namespace, info.Name, info.ResourceVersion) 508 if err != nil { 509 return err 510 } 511 512 kind := info.Mapping.GroupVersionKind.Kind 513 c.Log("Watching for changes to %s %s with timeout of %v", kind, info.Name, timeout) 514 515 // What we watch for depends on the Kind. 516 // - For a Job, we watch for completion. 517 // - For all else, we watch until Ready. 518 // In the future, we might want to add some special logic for types 519 // like Ingress, Volume, etc. 520 521 _, err = watch.Until(timeout, w, func(e watch.Event) (bool, error) { 522 switch e.Type { 523 case watch.Added, watch.Modified: 524 // For things like a secret or a config map, this is the best indicator 525 // we get. We care mostly about jobs, where what we want to see is 526 // the status go into a good state. For other types, like ReplicaSet 527 // we don't really do anything to support these as hooks. 528 c.Log("Add/Modify event for %s: %v", info.Name, e.Type) 529 if kind == "Job" { 530 return c.waitForJob(e, info.Name) 531 } 532 return true, nil 533 case watch.Deleted: 534 c.Log("Deleted event for %s", info.Name) 535 return true, nil 536 case watch.Error: 537 // Handle error and return with an error. 538 c.Log("Error event for %s", info.Name) 539 return true, fmt.Errorf("Failed to deploy %s", info.Name) 540 default: 541 return false, nil 542 } 543 }) 544 return err 545 } 546 547 // AsVersionedObject converts a runtime.object to a versioned object. 548 func (c *Client) AsVersionedObject(obj runtime.Object) (runtime.Object, error) { 549 json, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj) 550 if err != nil { 551 return nil, err 552 } 553 versions := &runtime.VersionedObjects{} 554 err = runtime.DecodeInto(c.Decoder(true), json, versions) 555 return versions.First(), err 556 } 557 558 // waitForJob is a helper that waits for a job to complete. 559 // 560 // This operates on an event returned from a watcher. 561 func (c *Client) waitForJob(e watch.Event, name string) (bool, error) { 562 o, ok := e.Object.(*batchinternal.Job) 563 if !ok { 564 return true, fmt.Errorf("Expected %s to be a *batch.Job, got %T", name, e.Object) 565 } 566 567 for _, c := range o.Status.Conditions { 568 if c.Type == batchinternal.JobComplete && c.Status == api.ConditionTrue { 569 return true, nil 570 } else if c.Type == batchinternal.JobFailed && c.Status == api.ConditionTrue { 571 return true, fmt.Errorf("Job failed: %s", c.Reason) 572 } 573 } 574 575 c.Log("%s: Jobs active: %d, jobs failed: %d, jobs succeeded: %d", name, o.Status.Active, o.Status.Failed, o.Status.Succeeded) 576 return false, nil 577 } 578 579 // scrubValidationError removes kubectl info from the message. 580 func scrubValidationError(err error) error { 581 if err == nil { 582 return nil 583 } 584 const stopValidateMessage = "if you choose to ignore these errors, turn validation off with --validate=false" 585 586 if strings.Contains(err.Error(), stopValidateMessage) { 587 return goerrors.New(strings.Replace(err.Error(), "; "+stopValidateMessage, "", -1)) 588 } 589 return err 590 } 591 592 // WaitAndGetCompletedPodPhase waits up to a timeout until a pod enters a completed phase 593 // and returns said phase (PodSucceeded or PodFailed qualify). 594 func (c *Client) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) { 595 infos, err := c.Build(namespace, reader) 596 if err != nil { 597 return api.PodUnknown, err 598 } 599 info := infos[0] 600 601 kind := info.Mapping.GroupVersionKind.Kind 602 if kind != "Pod" { 603 return api.PodUnknown, fmt.Errorf("%s is not a Pod", info.Name) 604 } 605 606 if err := c.watchPodUntilComplete(timeout, info); err != nil { 607 return api.PodUnknown, err 608 } 609 610 if err := info.Get(); err != nil { 611 return api.PodUnknown, err 612 } 613 status := info.Object.(*api.Pod).Status.Phase 614 615 return status, nil 616 } 617 618 func (c *Client) watchPodUntilComplete(timeout time.Duration, info *resource.Info) error { 619 w, err := resource.NewHelper(info.Client, info.Mapping).WatchSingle(info.Namespace, info.Name, info.ResourceVersion) 620 if err != nil { 621 return err 622 } 623 624 c.Log("Watching pod %s for completion with timeout of %v", info.Name, timeout) 625 _, err = watch.Until(timeout, w, func(e watch.Event) (bool, error) { 626 return conditions.PodCompleted(e) 627 }) 628 629 return err 630 }