func (s scaleUpdater) Update(annotator *ScaleAnnotater, obj runtime.Object, scale *kextapi.Scale) error { var ( err error patchBytes, originalObj, newObj []byte ) originalObj, err = runtime.Encode(s.encoder, obj) if err != nil { return err } switch typedObj := obj.(type) { case *deployapi.DeploymentConfig: if typedObj.Annotations == nil { typedObj.Annotations = make(map[string]string) } annotator.ChangeAnnotations(typedObj.Spec.Replicas, typedObj.Annotations) typedObj.Spec.Replicas = scale.Spec.Replicas newObj, err = runtime.Encode(s.encoder, typedObj) if err != nil { return err } patchBytes, err = strategicpatch.CreateTwoWayMergePatch(originalObj, newObj, &deployapiv1.DeploymentConfig{}) if err != nil { return err } _, err = s.dcGetter.DeploymentConfigs(s.namespace).Patch(typedObj.Name, kapi.StrategicMergePatchType, patchBytes) case *kapi.ReplicationController: if typedObj.Annotations == nil { typedObj.Annotations = make(map[string]string) } annotator.ChangeAnnotations(typedObj.Spec.Replicas, typedObj.Annotations) typedObj.Spec.Replicas = scale.Spec.Replicas newObj, err = runtime.Encode(s.encoder, typedObj) if err != nil { return err } patchBytes, err = strategicpatch.CreateTwoWayMergePatch(originalObj, newObj, &kapiv1.ReplicationController{}) if err != nil { return err } _, err = s.rcGetter.ReplicationControllers(s.namespace).Patch(typedObj.Name, kapi.StrategicMergePatchType, patchBytes) } return err }
// CalculatePatches calls the mutation function on each provided info object, and generates a strategic merge patch for // the changes in the object. Encoder must be able to encode the info into the appropriate destination type. If mutateFn // returns false, the object is not included in the final list of patches. func CalculatePatches(infos []*resource.Info, encoder runtime.Encoder, mutateFn func(*resource.Info) (bool, error)) []*Patch { var patches []*Patch for _, info := range infos { patch := &Patch{Info: info} patch.Before, patch.Err = runtime.Encode(encoder, info.Object) ok, err := mutateFn(info) if !ok { continue } if err != nil { patch.Err = err } patches = append(patches, patch) if patch.Err != nil { continue } patch.After, patch.Err = runtime.Encode(encoder, info.Object) if patch.Err != nil { continue } // TODO: should be via New versioned, err := info.Mapping.ConvertToVersion(info.Object, info.Mapping.GroupVersionKind.GroupVersion()) if err != nil { patch.Err = err continue } patch.Patch, patch.Err = strategicpatch.CreateTwoWayMergePatch(patch.Before, patch.After, versioned) } return patches }
// PatchNodeStatus patches node status. func PatchNodeStatus(c clientset.Interface, nodeName types.NodeName, oldNode *v1.Node, newNode *v1.Node) (*v1.Node, error) { oldData, err := json.Marshal(oldNode) if err != nil { return nil, fmt.Errorf("failed to marshal old node %#v for node %q: %v", oldNode, nodeName, err) } // Reset spec to make sure only patch for Status or ObjectMeta is generated. // Note that we don't reset ObjectMeta here, because: // 1. This aligns with Nodes().UpdateStatus(). // 2. Some component does use this to update node annotations. newNode.Spec = oldNode.Spec newData, err := json.Marshal(newNode) if err != nil { return nil, fmt.Errorf("failed to marshal new node %#v for node %q: %v", newNode, nodeName, err) } patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, v1.Node{}) if err != nil { return nil, fmt.Errorf("failed to create patch for node %q: %v", nodeName, err) } updatedNode, err := c.Core().Nodes().Patch(string(nodeName), types.StrategicMergePatchType, patchBytes, "status") if err != nil { return nil, fmt.Errorf("failed to patch status %q for node %q: %v", patchBytes, nodeName, err) } return updatedNode, nil }
// RunAnnotate does the work func (o AnnotateOptions) RunAnnotate() error { r := o.builder.Do() if err := r.Err(); err != nil { return err } return r.Visit(func(info *resource.Info, err error) error { if err != nil { return err } name, namespace, obj := info.Name, info.Namespace, info.Object oldData, err := json.Marshal(obj) if err != nil { return err } // If we should record change-cause, add it to new annotations if cmdutil.ContainsChangeCause(info) || o.recordChangeCause { o.newAnnotations[kubectl.ChangeCauseAnnotation] = o.changeCause } if err := o.updateAnnotations(obj); err != nil { return err } newData, err := json.Marshal(obj) if err != nil { return err } patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj) createdPatch := err == nil if err != nil { glog.V(2).Infof("couldn't compute patch: %v", err) } mapping := info.ResourceMapping() client, err := o.f.ClientForMapping(mapping) if err != nil { return err } helper := resource.NewHelper(client, mapping) var outputObj runtime.Object if createdPatch { outputObj, err = helper.Patch(namespace, name, api.StrategicMergePatchType, patchBytes) } else { outputObj, err = helper.Replace(namespace, name, false, obj) } if err != nil { return err } outputFormat := cmdutil.GetFlagString(o.cmd, "output") if outputFormat != "" { return o.f.PrintObject(o.cmd, outputObj, o.out) } mapper, _ := o.f.Object() cmdutil.PrintSuccess(mapper, false, o.out, info.Mapping.Resource, info.Name, "annotated") return nil }) }
// diff creates a patch. func diff(original, modified runtime.Object) (patch []byte, err error) { origBytes, err := json.Marshal(original) if err != nil { return nil, err } modBytes, err := json.Marshal(modified) if err != nil { return nil, err } return strategicpatch.CreateTwoWayMergePatch(origBytes, modBytes, original) }
func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, recreate bool) error { encoder := api.Codecs.LegacyCodec(registered.EnabledVersions()...) original, err := runtime.Encode(encoder, currentObj) if err != nil { return err } modified, err := runtime.Encode(encoder, target.Object) if err != nil { return err } if api.Semantic.DeepEqual(original, modified) { return ErrAlreadyExists{target.Name} } patch, err := strategicpatch.CreateTwoWayMergePatch(original, modified, currentObj) if err != nil { return err } // send patch to server helper := resource.NewHelper(target.Client, target.Mapping) _, err = helper.Patch(target.Namespace, target.Name, api.StrategicMergePatchType, patch) if err != nil { return err } if recreate { kind := target.Mapping.GroupVersionKind.Kind client, _ := c.ClientSet() switch kind { case "ReplicationController": rc := currentObj.(*v1.ReplicationController) err = recreatePods(client, target.Namespace, rc.Spec.Selector) case "DaemonSet": daemonSet := currentObj.(*v1beta1.DaemonSet) err = recreatePods(client, target.Namespace, daemonSet.Spec.Selector.MatchLabels) case "StatefulSet": petSet := currentObj.(*apps.StatefulSet) err = recreatePods(client, target.Namespace, petSet.Spec.Selector.MatchLabels) case "ReplicaSet": replicaSet := currentObj.(*v1beta1.ReplicaSet) err = recreatePods(client, target.Namespace, replicaSet.Spec.Selector.MatchLabels) } } return err }
// ChangeResourcePatch creates a strategic merge patch between the origin input resource info // and the annotated with change-cause input resource info. func ChangeResourcePatch(info *resource.Info, changeCause string) ([]byte, error) { oldData, err := json.Marshal(info.Object) if err != nil { return nil, err } if err := RecordChangeCause(info.Object, changeCause); err != nil { return nil, err } newData, err := json.Marshal(info.Object) if err != nil { return nil, err } return strategicpatch.CreateTwoWayMergePatch(oldData, newData, info.Object) }
// CalculatePatches calls the mutation function on each provided info object, and generates a strategic merge patch for // the changes in the object. Encoder must be able to encode the info into the appropriate destination type. If mutateFn // returns false, the object is not included in the final list of patches. // If local is true, it will be default to use SMPatchVersionLatest to calculate a patch without contacting the server to // get the server supported SMPatchVersion. If you are using a patch's Patch field generated in local mode, be careful. // If local is false, it will talk to the server to check which StategicMergePatchVersion to use. func CalculatePatches(f cmdutil.Factory, infos []*resource.Info, encoder runtime.Encoder, local bool, mutateFn func(*resource.Info) (bool, error)) []*Patch { var patches []*Patch smPatchVersion := strategicpatch.SMPatchVersionLatest var err error if !local { smPatchVersion, err = cmdutil.GetServerSupportedSMPatchVersionFromFactory(f) if err != nil { return patches } } for _, info := range infos { patch := &Patch{Info: info} patch.Before, patch.Err = runtime.Encode(encoder, info.Object) if patch.Err != nil { patches = append(patches, patch) continue } ok, err := mutateFn(info) if err != nil { patch.Err = err patches = append(patches, patch) continue } if !ok { continue } patches = append(patches, patch) if patch.Err != nil { continue } patch.After, patch.Err = runtime.Encode(encoder, info.Object) if patch.Err != nil { continue } // TODO: should be via New versioned, err := info.Mapping.ConvertToVersion(info.Object, info.Mapping.GroupVersionKind.GroupVersion()) if err != nil { patch.Err = err continue } patch.Patch, patch.Err = strategicpatch.CreateTwoWayMergePatch(patch.Before, patch.After, versioned, smPatchVersion) } return patches }
// ChangeResourcePatch creates a strategic merge patch between the origin input resource info // and the annotated with change-cause input resource info. func ChangeResourcePatch(info *resource.Info, changeCause string) ([]byte, error) { oldData, err := json.Marshal(info.Object) if err != nil { return nil, err } if err := RecordChangeCause(info.Object, changeCause); err != nil { return nil, err } newData, err := json.Marshal(info.Object) if err != nil { return nil, err } // Using SMPatchVersion_1_5, since RecordChangeCause() just update the annotation which is a map[string]string return strategicpatch.CreateTwoWayMergePatch(oldData, newData, info.Object, strategicpatch.SMPatchVersion_1_5) }
// RunAnnotate does the work func (o AnnotateOptions) RunAnnotate() error { r := o.builder.Do() if err := r.Err(); err != nil { return err } return r.Visit(func(info *resource.Info, err error) error { if err != nil { return err } name, namespace, obj := info.Name, info.Namespace, info.Object oldData, err := json.Marshal(obj) if err != nil { return err } if err := o.updateAnnotations(obj); err != nil { return err } newData, err := json.Marshal(obj) if err != nil { return err } patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj) if err != nil { return err } mapping := info.ResourceMapping() client, err := o.f.RESTClient(mapping) if err != nil { return err } helper := resource.NewHelper(client, mapping) outputObj, err := helper.Patch(namespace, name, api.StrategicMergePatchType, patchBytes) if err != nil { return err } outputFormat := cmdutil.GetFlagString(o.cmd, "output") if outputFormat != "" { return o.f.PrintObject(o.cmd, outputObj, o.out) } mapper, _ := o.f.Object() cmdutil.PrintSuccess(mapper, false, o.out, info.Mapping.Resource, info.Name, "annotated") return nil }) }
func diffBuildSpec(newer buildapi.BuildSpec, older buildapi.BuildSpec) (string, error) { codec := kapi.Codecs.LegacyCodec(v1.SchemeGroupVersion) newerObj := &buildapi.Build{Spec: newer} olderObj := &buildapi.Build{Spec: older} newerJSON, err := runtime.Encode(codec, newerObj) if err != nil { return "", fmt.Errorf("error encoding newer: %v", err) } olderJSON, err := runtime.Encode(codec, olderObj) if err != nil { return "", fmt.Errorf("error encoding older: %v", err) } patch, err := strategicpatch.CreateTwoWayMergePatch(olderJSON, newerJSON, &v1.Build{}) if err != nil { return "", fmt.Errorf("error creating a strategic patch: %v", err) } return string(patch), nil }
// eventObserve records the event, and determines if its frequency should update func (e *eventLogger) eventObserve(newEvent *v1.Event) (*v1.Event, []byte, error) { var ( patch []byte err error ) key := getEventKey(newEvent) eventCopy := *newEvent event := &eventCopy e.Lock() defer e.Unlock() lastObservation := e.lastEventObservationFromCache(key) // we have seen this event before, so we must prepare a patch if lastObservation.count > 0 { // update the event based on the last observation so patch will work as desired event.Name = lastObservation.name event.ResourceVersion = lastObservation.resourceVersion event.FirstTimestamp = lastObservation.firstTimestamp event.Count = int32(lastObservation.count) + 1 eventCopy2 := *event eventCopy2.Count = 0 eventCopy2.LastTimestamp = metav1.NewTime(time.Unix(0, 0)) newData, _ := json.Marshal(event) oldData, _ := json.Marshal(eventCopy2) patch, err = strategicpatch.CreateTwoWayMergePatch(oldData, newData, event) } // record our new observation e.cache.Add( key, eventLog{ count: int(event.Count), firstTimestamp: event.FirstTimestamp, name: event.Name, resourceVersion: event.ResourceVersion, }, ) return event, patch, err }
// patchObj patches calculates a patch between the given new object and the existing marshaled object func patchObj(obj runtime.Object, metadata meta.Object, oldData []byte, mapping *meta.RESTMapping, f *clientcmd.Factory) (runtime.Object, error) { newData, err := json.Marshal(obj) if err != nil { return nil, err } patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj) if err != nil { return nil, err } client, err := f.ClientForMapping(mapping) if err != nil { return nil, err } helper := resource.NewHelper(client, mapping) return helper.Patch(metadata.GetNamespace(), metadata.GetName(), kapi.StrategicMergePatchType, patchBytes) }
// ChangeResourcePatch creates a strategic merge patch between the origin input resource info // and the annotated with change-cause input resource info. func ChangeResourcePatch(info *resource.Info, changeCause string) ([]byte, error) { // Get a versioned object obj, err := info.Mapping.ConvertToVersion(info.Object, info.Mapping.GroupVersionKind.GroupVersion()) if err != nil { return nil, err } oldData, err := json.Marshal(obj) if err != nil { return nil, err } if err := RecordChangeCause(obj, changeCause); err != nil { return nil, err } newData, err := json.Marshal(obj) if err != nil { return nil, err } return strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj) }
// CalculatePatch calls the mutation function on the provided info object, and generates a strategic merge patch for // the changes in the object. Encoder must be able to encode the info into the appropriate destination type. // This function returns whether the mutation function made any change in the original object. func CalculatePatch(patch *Patch, encoder runtime.Encoder, mutateFn patchFn) bool { patch.Before, patch.Err = runtime.Encode(encoder, patch.Info.Object) patch.After, patch.Err = mutateFn(patch.Info) if patch.Err != nil { return true } if patch.After == nil { return false } // TODO: should be via New versioned, err := patch.Info.Mapping.ConvertToVersion(patch.Info.Object, patch.Info.Mapping.GroupVersionKind.GroupVersion()) if err != nil { patch.Err = err return true } patch.Patch, patch.Err = strategicpatch.CreateTwoWayMergePatch(patch.Before, patch.After, versioned) return true }
// RunAnnotate does the work func (o AnnotateOptions) RunAnnotate(f *cmdutil.Factory) error { r := o.builder.Do() if err := r.Err(); err != nil { return err } return r.Visit(func(info *resource.Info, err error) error { if err != nil { return err } name, namespace, obj := info.Name, info.Namespace, info.Object oldData, err := json.Marshal(obj) if err != nil { return err } if err := o.updateAnnotations(obj); err != nil { return err } newData, err := json.Marshal(obj) if err != nil { return err } patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj) if err != nil { return err } mapping := info.ResourceMapping() client, err := f.RESTClient(mapping) if err != nil { return err } helper := resource.NewHelper(client, mapping) _, err = helper.Patch(namespace, name, api.StrategicMergePatchType, patchBytes) return err }) }
// RunEnv contains all the necessary functionality for the OpenShift cli env command // TODO: refactor to share the common "patch resource" pattern of probe func RunEnv(f *clientcmd.Factory, in io.Reader, out io.Writer, cmd *cobra.Command, args []string, envParams, filenames []string) error { resources, envArgs, ok := cmdutil.SplitEnvironmentFromResources(args) if !ok { return kcmdutil.UsageError(cmd, "all resources must be specified before environment changes: %s", strings.Join(args, " ")) } if len(filenames) == 0 && len(resources) < 1 { return kcmdutil.UsageError(cmd, "one or more resources must be specified as <resource> <name> or <resource>/<name>") } containerMatch := kcmdutil.GetFlagString(cmd, "containers") list := kcmdutil.GetFlagBool(cmd, "list") selector := kcmdutil.GetFlagString(cmd, "selector") all := kcmdutil.GetFlagBool(cmd, "all") //overwrite := kcmdutil.GetFlagBool(cmd, "overwrite") resourceVersion := kcmdutil.GetFlagString(cmd, "resource-version") outputFormat := kcmdutil.GetFlagString(cmd, "output") if list && len(outputFormat) > 0 { return kcmdutil.UsageError(cmd, "--list and --output may not be specified together") } clientConfig, err := f.ClientConfig() if err != nil { return err } cmdNamespace, explicit, err := f.DefaultNamespace() if err != nil { return err } env, remove, err := cmdutil.ParseEnv(append(envParams, envArgs...), in) if err != nil { return err } mapper, typer := f.Object() b := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), kapi.Codecs.UniversalDecoder()). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(explicit, filenames...). SelectorParam(selector). ResourceTypeOrNameArgs(all, resources...). Flatten() one := false infos, err := b.Do().IntoSingular(&one).Infos() if err != nil { return err } // only apply resource version locking on a single resource if !one && len(resourceVersion) > 0 { return kcmdutil.UsageError(cmd, "--resource-version may only be used with a single resource") } // Keep a copy of the original objects prior to updating their environment. // Used in constructing the patch(es) that will be applied in the server. oldObjects, err := resource.AsVersionedObjects(infos, clientConfig.GroupVersion.String(), kapi.Codecs.LegacyCodec(*clientConfig.GroupVersion)) if err != nil { return err } if len(oldObjects) != len(infos) { return fmt.Errorf("could not convert all objects to API version %q", clientConfig.GroupVersion) } oldData := make([][]byte, len(infos)) for i := range oldObjects { old, err := json.Marshal(oldObjects[i]) if err != nil { return err } oldData[i] = old } skipped := 0 for _, info := range infos { ok, err := f.UpdatePodSpecForObject(info.Object, func(spec *kapi.PodSpec) error { containers, _ := selectContainers(spec.Containers, containerMatch) if len(containers) == 0 { fmt.Fprintf(cmd.Out(), "warning: %s/%s does not have any containers matching %q\n", info.Mapping.Resource, info.Name, containerMatch) return nil } for _, c := range containers { c.Env = updateEnv(c.Env, env, remove) if list { fmt.Fprintf(out, "# %s %s, container %s\n", info.Mapping.Resource, info.Name, c.Name) for _, env := range c.Env { // if env.ValueFrom != nil && env.ValueFrom.FieldRef != nil { // fmt.Fprintf(cmd.Out(), "%s= # calculated from pod %s %s\n", env.Name, env.ValueFrom.FieldRef.FieldPath, env.ValueFrom.FieldRef.APIVersion) // continue // } fmt.Fprintf(out, "%s=%s\n", env.Name, env.Value) } } } return nil }) if !ok { skipped++ continue } if err != nil { fmt.Fprintf(cmd.Out(), "error: %s/%s %v\n", info.Mapping.Resource, info.Name, err) continue } } if one && skipped == len(infos) { return fmt.Errorf("%s/%s is not a pod or does not have a pod template", infos[0].Mapping.Resource, infos[0].Name) } if list { return nil } if len(outputFormat) != 0 { outputVersion, err := kcmdutil.OutputVersion(cmd, clientConfig.GroupVersion) if err != nil { return err } objects, err := resource.AsVersionedObjects(infos, outputVersion.String(), kapi.Codecs.LegacyCodec(outputVersion)) if err != nil { return err } if len(objects) != len(infos) { return fmt.Errorf("could not convert all objects to API version %q", outputVersion) } p, _, err := kubectl.GetPrinter(outputFormat, "") if err != nil { return err } for _, object := range objects { if err := p.PrintObj(object, out); err != nil { return err } } return nil } objects, err := resource.AsVersionedObjects(infos, clientConfig.GroupVersion.String(), kapi.Codecs.LegacyCodec(*clientConfig.GroupVersion)) if err != nil { return err } if len(objects) != len(infos) { return fmt.Errorf("could not convert all objects to API version %q", clientConfig.GroupVersion) } failed := false for i, info := range infos { newData, err := json.Marshal(objects[i]) if err != nil { return err } patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData[i], newData, objects[i]) if err != nil { return err } obj, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, kapi.StrategicMergePatchType, patchBytes) if err != nil { handlePodUpdateError(cmd.Out(), err, "environment variables") failed = true continue } info.Refresh(obj, true) shortOutput := kcmdutil.GetFlagString(cmd, "output") == "name" kcmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, "updated") } if failed { return cmdutil.ErrExit } return nil }
// RunEnv contains all the necessary functionality for the OpenShift cli env command // TODO: refactor to share the common "patch resource" pattern of probe func RunEnv(f *clientcmd.Factory, in io.Reader, out, errout io.Writer, cmd *cobra.Command, args []string, envParams, filenames []string) error { resources, envArgs, ok := cmdutil.SplitEnvironmentFromResources(args) if !ok { return kcmdutil.UsageError(cmd, "all resources must be specified before environment changes: %s", strings.Join(args, " ")) } if len(filenames) == 0 && len(resources) < 1 { return kcmdutil.UsageError(cmd, "one or more resources must be specified as <resource> <name> or <resource>/<name>") } containerMatch := kcmdutil.GetFlagString(cmd, "containers") list := kcmdutil.GetFlagBool(cmd, "list") resolve := kcmdutil.GetFlagBool(cmd, "resolve") selector := kcmdutil.GetFlagString(cmd, "selector") all := kcmdutil.GetFlagBool(cmd, "all") overwrite := kcmdutil.GetFlagBool(cmd, "overwrite") resourceVersion := kcmdutil.GetFlagString(cmd, "resource-version") outputFormat := kcmdutil.GetFlagString(cmd, "output") from := kcmdutil.GetFlagString(cmd, "from") prefix := kcmdutil.GetFlagString(cmd, "prefix") if list && len(outputFormat) > 0 { return kcmdutil.UsageError(cmd, "--list and --output may not be specified together") } clientConfig, err := f.ClientConfig() if err != nil { return err } cmdNamespace, explicit, err := f.DefaultNamespace() if err != nil { return err } cmdutil.WarnAboutCommaSeparation(errout, envParams, "--env") env, remove, err := cmdutil.ParseEnv(append(envParams, envArgs...), in) if err != nil { return err } if len(from) != 0 { mapper, typer := f.Object(false) b := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), kapi.Codecs.UniversalDecoder()). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(explicit, false, filenames...). SelectorParam(selector). ResourceTypeOrNameArgs(all, from). Flatten() one := false infos, err := b.Do().IntoSingular(&one).Infos() if err != nil { return err } for _, info := range infos { switch from := info.Object.(type) { case *kapi.Secret: for key := range from.Data { envVar := kapi.EnvVar{ Name: keyToEnvName(key), ValueFrom: &kapi.EnvVarSource{ SecretKeyRef: &kapi.SecretKeySelector{ LocalObjectReference: kapi.LocalObjectReference{ Name: from.Name, }, Key: key, }, }, } env = append(env, envVar) } case *kapi.ConfigMap: for key := range from.Data { envVar := kapi.EnvVar{ Name: keyToEnvName(key), ValueFrom: &kapi.EnvVarSource{ ConfigMapKeyRef: &kapi.ConfigMapKeySelector{ LocalObjectReference: kapi.LocalObjectReference{ Name: from.Name, }, Key: key, }, }, } env = append(env, envVar) } default: return fmt.Errorf("unsupported resource specified in --from") } } } if len(prefix) != 0 { for i := range env { env[i].Name = fmt.Sprintf("%s%s", prefix, env[i].Name) } } mapper, typer := f.Object(false) b := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), kapi.Codecs.UniversalDecoder()). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(explicit, false, filenames...). SelectorParam(selector). ResourceTypeOrNameArgs(all, resources...). Flatten() one := false infos, err := b.Do().IntoSingular(&one).Infos() if err != nil { return err } // only apply resource version locking on a single resource if !one && len(resourceVersion) > 0 { return kcmdutil.UsageError(cmd, "--resource-version may only be used with a single resource") } // Keep a copy of the original objects prior to updating their environment. // Used in constructing the patch(es) that will be applied in the server. gv := *clientConfig.GroupVersion oldObjects, err := resource.AsVersionedObjects(infos, gv, kapi.Codecs.LegacyCodec(gv)) if err != nil { return err } if len(oldObjects) != len(infos) { return fmt.Errorf("could not convert all objects to API version %q", clientConfig.GroupVersion) } oldData := make([][]byte, len(infos)) for i := range oldObjects { old, err := json.Marshal(oldObjects[i]) if err != nil { return err } oldData[i] = old } skipped := 0 errored := []*resource.Info{} for _, info := range infos { ok, err := f.UpdatePodSpecForObject(info.Object, func(spec *kapi.PodSpec) error { resolutionErrorsEncountered := false containers, _ := selectContainers(spec.Containers, containerMatch) if len(containers) == 0 { fmt.Fprintf(errout, "warning: %s/%s does not have any containers matching %q\n", info.Mapping.Resource, info.Name, containerMatch) return nil } for _, c := range containers { if !overwrite { if err := validateNoOverwrites(c.Env, env); err != nil { errored = append(errored, info) return err } } c.Env = updateEnv(c.Env, env, remove) if list { resolveErrors := map[string][]string{} store := newResourceStore() fmt.Fprintf(out, "# %s %s, container %s\n", info.Mapping.Resource, info.Name, c.Name) for _, env := range c.Env { // Print the simple value if env.ValueFrom == nil { fmt.Fprintf(out, "%s=%s\n", env.Name, env.Value) continue } // Print the reference version if !resolve { fmt.Fprintf(out, "# %s from %s\n", env.Name, getEnvVarRefString(env.ValueFrom)) continue } value, err := getEnvVarRefValue(f, store, env.ValueFrom, info.Object, c) // Print the resolved value if err == nil { fmt.Fprintf(out, "%s=%s\n", env.Name, value) continue } // Print the reference version and save the resolve error fmt.Fprintf(out, "# %s from %s\n", env.Name, getEnvVarRefString(env.ValueFrom)) errString := err.Error() resolveErrors[errString] = append(resolveErrors[errString], env.Name) resolutionErrorsEncountered = true } // Print any resolution errors errs := []string{} for err, vars := range resolveErrors { sort.Strings(vars) errs = append(errs, fmt.Sprintf("error retrieving reference for %s: %v", strings.Join(vars, ", "), err)) } sort.Strings(errs) for _, err := range errs { fmt.Fprintln(errout, err) } } } if resolutionErrorsEncountered { errored = append(errored, info) return errors.New("failed to retrieve valueFrom references") } return nil }) if !ok { // This is a fallback function for objects that don't have pod spec. ok, err = f.UpdateObjectEnvironment(info.Object, func(vars *[]kapi.EnvVar) error { if vars == nil { return fmt.Errorf("no environment variables provided") } if !overwrite { if err := validateNoOverwrites(*vars, env); err != nil { errored = append(errored, info) return err } } *vars = updateEnv(*vars, env, remove) if list { fmt.Fprintf(out, "# %s %s\n", info.Mapping.Resource, info.Name) for _, env := range *vars { fmt.Fprintf(out, "%s=%s\n", env.Name, env.Value) } } return nil }) if !ok { skipped++ continue } } if err != nil { fmt.Fprintf(errout, "error: %s/%s %v\n", info.Mapping.Resource, info.Name, err) continue } } if one && skipped == len(infos) { return fmt.Errorf("%s/%s is not a pod or does not have a pod template", infos[0].Mapping.Resource, infos[0].Name) } if len(errored) == len(infos) { return cmdutil.ErrExit } if list { return nil } if len(outputFormat) > 0 { return f.PrintResourceInfos(cmd, infos, out) } objects, err := resource.AsVersionedObjects(infos, gv, kapi.Codecs.LegacyCodec(gv)) if err != nil { return err } if len(objects) != len(infos) { return fmt.Errorf("could not convert all objects to API version %q", clientConfig.GroupVersion) } failed := false updates: for i, info := range infos { for _, erroredInfo := range errored { if info == erroredInfo { continue updates } } newData, err := json.Marshal(objects[i]) if err != nil { return err } patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData[i], newData, objects[i]) if err != nil { return err } obj, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, kapi.StrategicMergePatchType, patchBytes) if err != nil { handlePodUpdateError(errout, err, "environment variables") failed = true continue } info.Refresh(obj, true) // make sure arguments to set or replace environment variables are set // before returning a successful message if len(env) == 0 && len(envArgs) == 0 { return fmt.Errorf("at least one environment variable must be provided") } shortOutput := kcmdutil.GetFlagString(cmd, "output") == "name" kcmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, false, "updated") } if failed { return cmdutil.ErrExit } return nil }
func RunEdit(f *cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args []string, options *resource.FilenameOptions) error { var printer kubectl.ResourcePrinter var ext string var addHeader bool switch format := cmdutil.GetFlagString(cmd, "output"); format { case "json": printer = &kubectl.JSONPrinter{} ext = ".json" addHeader = false case "yaml": printer = &kubectl.YAMLPrinter{} ext = ".yaml" addHeader = true default: return cmdutil.UsageError(cmd, "The flag 'output' must be one of yaml|json") } cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err } mapper, typer := f.Object() resourceMapper := &resource.Mapper{ ObjectTyper: typer, RESTMapper: mapper, ClientMapper: resource.ClientMapperFunc(f.ClientForMapping), // NB: we use `f.Decoder(false)` to get a plain deserializer for // the resourceMapper, since it's used to read in edits and // we don't want to convert into the internal version when // reading in edits (this would cause us to potentially try to // compare two different GroupVersions). Decoder: f.Decoder(false), } r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, options). ResourceTypeOrNameArgs(true, args...). ContinueOnError(). Flatten(). Latest(). Do() err = r.Err() if err != nil { return err } clientConfig, err := f.ClientConfig() if err != nil { return err } encoder := f.JSONEncoder() defaultVersion, err := cmdutil.OutputVersion(cmd, clientConfig.GroupVersion) if err != nil { return err } var ( windowsLineEndings = cmdutil.GetFlagBool(cmd, "windows-line-endings") edit = editor.NewDefaultEditor(f.EditorEnvs()) ) err = r.Visit(func(info *resource.Info, err error) error { var ( results = editResults{} original = []byte{} edited = []byte{} file string ) containsError := false for { infos := []*resource.Info{info} originalObj, err := resource.AsVersionedObject(infos, false, defaultVersion, encoder) if err != nil { return err } objToEdit := originalObj // generate the file to edit buf := &bytes.Buffer{} var w io.Writer = buf if windowsLineEndings { w = crlf.NewCRLFWriter(w) } if addHeader { results.header.writeTo(w) } if !containsError { if err := printer.PrintObj(objToEdit, w); err != nil { return preservedFile(err, results.file, errOut) } original = buf.Bytes() } else { // In case of an error, preserve the edited file. // Remove the comments (header) from it since we already // have included the latest header in the buffer above. buf.Write(manualStrip(edited)) } // launch the editor editedDiff := edited edited, file, err = edit.LaunchTempFile(fmt.Sprintf("%s-edit-", filepath.Base(os.Args[0])), ext, buf) if err != nil { return preservedFile(err, results.file, errOut) } if bytes.Equal(stripComments(editedDiff), stripComments(edited)) { // Ugly hack right here. We will hit this either (1) when we try to // save the same changes we tried to save in the previous iteration // which means our changes are invalid or (2) when we exit the second // time. The second case is more usual so we can probably live with it. // TODO: A less hacky fix would be welcome :) fmt.Fprintln(errOut, "Edit cancelled, no valid changes were saved.") return nil } // cleanup any file from the previous pass if len(results.file) > 0 { os.Remove(results.file) } glog.V(4).Infof("User edited:\n%s", string(edited)) // Apply validation schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir")) if err != nil { return preservedFile(err, file, errOut) } err = schema.ValidateBytes(stripComments(edited)) if err != nil { return preservedFile(err, file, errOut) } // Compare content without comments if bytes.Equal(stripComments(original), stripComments(edited)) { os.Remove(file) fmt.Fprintln(errOut, "Edit cancelled, no changes made.") return nil } lines, err := hasLines(bytes.NewBuffer(edited)) if err != nil { return preservedFile(err, file, errOut) } if !lines { os.Remove(file) fmt.Fprintln(errOut, "Edit cancelled, saved file was empty.") return nil } results = editResults{ file: file, } // parse the edited file updates, err := resourceMapper.InfoForData(edited, "edited-file") if err != nil { // syntax error containsError = true results.header.reasons = append(results.header.reasons, editReason{head: fmt.Sprintf("The edited file had a syntax error: %v", err)}) continue } // not a syntax error as it turns out... containsError = false namespaceVisitor := resource.NewFlattenListVisitor(updates, resourceMapper) // need to make sure the original namespace wasn't changed while editing if err = namespaceVisitor.Visit(resource.RequireNamespace(cmdNamespace)); err != nil { return preservedFile(err, file, errOut) } mutatedObjects := []runtime.Object{} annotationVisitor := resource.NewFlattenListVisitor(updates, resourceMapper) // iterate through all items to apply annotations if err = annotationVisitor.Visit(func(info *resource.Info, incomingErr error) error { // put configuration annotation in "updates" if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info, encoder); err != nil { return err } if cmdutil.ShouldRecord(cmd, info) { if err := cmdutil.RecordChangeCause(info.Object, f.Command()); err != nil { return err } } mutatedObjects = append(mutatedObjects, info.Object) return nil }); err != nil { return preservedFile(err, file, errOut) } // if we mutated a list in the visitor, persist the changes on the overall object if meta.IsListType(updates.Object) { meta.SetList(updates.Object, mutatedObjects) } patchVisitor := resource.NewFlattenListVisitor(updates, resourceMapper) err = patchVisitor.Visit(func(info *resource.Info, incomingErr error) error { currOriginalObj := originalObj // if we're editing a list, then navigate the list to find the item that we're currently trying to edit if meta.IsListType(originalObj) { currOriginalObj = nil editObjUID, err := meta.NewAccessor().UID(info.Object) if err != nil { return err } listItems, err := meta.ExtractList(originalObj) if err != nil { return err } // iterate through the list to find the item with the matching UID for i := range listItems { originalObjUID, err := meta.NewAccessor().UID(listItems[i]) if err != nil { return err } if editObjUID == originalObjUID { currOriginalObj = listItems[i] break } } if currOriginalObj == nil { return fmt.Errorf("no original object found for %#v", info.Object) } } originalSerialization, err := runtime.Encode(encoder, currOriginalObj) if err != nil { return err } editedSerialization, err := runtime.Encode(encoder, info.Object) if err != nil { return err } // compute the patch on a per-item basis // use strategic merge to create a patch originalJS, err := yaml.ToJSON(originalSerialization) if err != nil { return err } editedJS, err := yaml.ToJSON(editedSerialization) if err != nil { return err } if reflect.DeepEqual(originalJS, editedJS) { // no edit, so just skip it. cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, false, "skipped") return nil } preconditions := []strategicpatch.PreconditionFunc{strategicpatch.RequireKeyUnchanged("apiVersion"), strategicpatch.RequireKeyUnchanged("kind"), strategicpatch.RequireMetadataKeyUnchanged("name")} patch, err := strategicpatch.CreateTwoWayMergePatch(originalJS, editedJS, currOriginalObj, preconditions...) if err != nil { glog.V(4).Infof("Unable to calculate diff, no merge is possible: %v", err) if strategicpatch.IsPreconditionFailed(err) { return preservedFile(nil, file, errOut) } return err } results.version = defaultVersion patched, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, api.StrategicMergePatchType, patch) if err != nil { fmt.Fprintln(out, results.addError(err, info)) return nil } info.Refresh(patched, true) cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, false, "edited") return nil }) if err != nil { return preservedFile(err, results.file, errOut) } // Handle all possible errors // // 1. retryable: propose kubectl replace -f // 2. notfound: indicate the location of the saved configuration of the deleted resource // 3. invalid: retry those on the spot by looping ie. reloading the editor if results.retryable > 0 { fmt.Fprintf(errOut, "You can run `%s replace -f %s` to try this update again.\n", filepath.Base(os.Args[0]), file) return errExit } if results.notfound > 0 { fmt.Fprintf(errOut, "The edits you made on deleted resources have been saved to %q\n", file) return errExit } if len(results.edit) == 0 { if results.notfound == 0 { os.Remove(file) } else { fmt.Fprintf(out, "The edits you made on deleted resources have been saved to %q\n", file) } return nil } // loop again and edit the remaining items infos = results.edit } }) return err }
// RunAnnotate does the work func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) error { namespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err } changeCause := f.Command() mapper, typer := f.Object() b := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace(). FilenameParam(enforceNamespace, &o.FilenameOptions). Flatten() if !o.local { b = b.SelectorParam(o.selector). ResourceTypeOrNameArgs(o.all, o.resources...). Latest() } r := b.Do() if err := r.Err(); err != nil { return err } var singularResource bool r.IntoSingular(&singularResource) // only apply resource version locking on a single resource. // we must perform this check after o.builder.Do() as // []o.resources can not not accurately return the proper number // of resources when they are not passed in "resource/name" format. if !singularResource && len(o.resourceVersion) > 0 { return fmt.Errorf("--resource-version may only be used with a single resource") } return r.Visit(func(info *resource.Info, err error) error { if err != nil { return err } var outputObj runtime.Object obj, err := cmdutil.MaybeConvertObject(info.Object, info.Mapping.GroupVersionKind.GroupVersion(), info.Mapping) if err != nil { return err } if o.dryrun || o.local { if err := o.updateAnnotations(obj); err != nil { return err } outputObj = obj } else { name, namespace := info.Name, info.Namespace oldData, err := json.Marshal(obj) if err != nil { return err } // If we should record change-cause, add it to new annotations if cmdutil.ContainsChangeCause(info) || o.recordChangeCause { o.newAnnotations[kubectl.ChangeCauseAnnotation] = changeCause } if err := o.updateAnnotations(obj); err != nil { return err } newData, err := json.Marshal(obj) if err != nil { return err } patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj) createdPatch := err == nil if err != nil { glog.V(2).Infof("couldn't compute patch: %v", err) } mapping := info.ResourceMapping() client, err := f.ClientForMapping(mapping) if err != nil { return err } helper := resource.NewHelper(client, mapping) if createdPatch { outputObj, err = helper.Patch(namespace, name, api.StrategicMergePatchType, patchBytes) } else { outputObj, err = helper.Replace(namespace, name, false, obj) } if err != nil { return err } } if o.outputFormat != "" { return f.PrintObject(cmd, mapper, outputObj, o.out) } cmdutil.PrintSuccess(mapper, false, o.out, info.Mapping.Resource, info.Name, o.dryrun, "annotated") return nil }) }
// RunLabel does the work func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error { cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err } changeCause := f.Command() mapper, typer := f.Object() b := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, &o.FilenameOptions). Flatten() if !o.local { b = b.SelectorParam(o.selector). ResourceTypeOrNameArgs(o.all, o.resources...). Latest() } one := false r := b.Do().IntoSingular(&one) if err := r.Err(); err != nil { return err } smPatchVersion := strategicpatch.SMPatchVersionLatest if !o.local { smPatchVersion, err = cmdutil.GetServerSupportedSMPatchVersionFromFactory(f) if err != nil { return err } } // only apply resource version locking on a single resource if !one && len(o.resourceVersion) > 0 { return fmt.Errorf("--resource-version may only be used with a single resource") } // TODO: support bulk generic output a la Get return r.Visit(func(info *resource.Info, err error) error { if err != nil { return err } var outputObj runtime.Object dataChangeMsg := "not labeled" if o.dryrun || o.local { err = labelFunc(info.Object, o.overwrite, o.resourceVersion, o.newLabels, o.removeLabels) if err != nil { return err } outputObj = info.Object } else { obj, err := cmdutil.MaybeConvertObject(info.Object, info.Mapping.GroupVersionKind.GroupVersion(), info.Mapping) if err != nil { return err } name, namespace := info.Name, info.Namespace oldData, err := json.Marshal(obj) if err != nil { return err } accessor, err := meta.Accessor(obj) if err != nil { return err } for _, label := range o.removeLabels { if _, ok := accessor.GetLabels()[label]; !ok { fmt.Fprintf(o.out, "label %q not found.\n", label) } } if err := labelFunc(obj, o.overwrite, o.resourceVersion, o.newLabels, o.removeLabels); err != nil { return err } if cmdutil.ShouldRecord(cmd, info) { if err := cmdutil.RecordChangeCause(obj, changeCause); err != nil { return err } } newData, err := json.Marshal(obj) if err != nil { return err } if !reflect.DeepEqual(oldData, newData) { dataChangeMsg = "labeled" } patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj, smPatchVersion) createdPatch := err == nil if err != nil { glog.V(2).Infof("couldn't compute patch: %v", err) } mapping := info.ResourceMapping() client, err := f.ClientForMapping(mapping) if err != nil { return err } helper := resource.NewHelper(client, mapping) if createdPatch { outputObj, err = helper.Patch(namespace, name, api.StrategicMergePatchType, patchBytes) } else { outputObj, err = helper.Replace(namespace, name, false, obj) } if err != nil { return err } } if o.outputFormat != "" { return f.PrintObject(cmd, mapper, outputObj, o.out) } cmdutil.PrintSuccess(mapper, false, o.out, info.Mapping.Resource, info.Name, o.dryrun, dataChangeMsg) return nil }) }
// RunTaint does the work func (o TaintOptions) RunTaint() error { r := o.builder.Do() if err := r.Err(); err != nil { return err } return r.Visit(func(info *resource.Info, err error) error { if err != nil { return err } obj, err := info.Mapping.ConvertToVersion(info.Object, info.Mapping.GroupVersionKind.GroupVersion()) if err != nil { return err } name, namespace := info.Name, info.Namespace oldData, err := json.Marshal(obj) if err != nil { return err } if err := o.updateTaints(obj); err != nil { return err } newData, err := json.Marshal(obj) if err != nil { return err } // Defaulting to SMPatchVersion_1_5 is safe, since we don't update list of primitives. patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj, strategicpatch.SMPatchVersion_1_5) createdPatch := err == nil if err != nil { glog.V(2).Infof("couldn't compute patch: %v", err) } mapping := info.ResourceMapping() client, err := o.f.ClientForMapping(mapping) if err != nil { return err } helper := resource.NewHelper(client, mapping) var outputObj runtime.Object if createdPatch { outputObj, err = helper.Patch(namespace, name, api.StrategicMergePatchType, patchBytes) } else { outputObj, err = helper.Replace(namespace, name, false, obj) } if err != nil { return err } mapper, _ := o.f.Object() outputFormat := cmdutil.GetFlagString(o.cmd, "output") if outputFormat != "" { return o.f.PrintObject(o.cmd, mapper, outputObj, o.out) } cmdutil.PrintSuccess(mapper, false, o.out, info.Mapping.Resource, info.Name, false, "tainted") return nil }) }
// patchResource divides PatchResource for easier unit testing func patchResource( ctx request.Context, admit updateAdmissionFunc, timeout time.Duration, versionedObj runtime.Object, patcher rest.Patcher, name string, patchType types.PatchType, patchJS []byte, namer ScopeNamer, copier runtime.ObjectCopier, resource schema.GroupVersionResource, codec runtime.Codec, ) (runtime.Object, error) { namespace := request.NamespaceValue(ctx) var ( originalObjJS []byte originalPatchedObjJS []byte originalObjMap map[string]interface{} originalPatchMap map[string]interface{} lastConflictErr error ) // applyPatch is called every time GuaranteedUpdate asks for the updated object, // and is given the currently persisted object as input. applyPatch := func(_ request.Context, _, currentObject runtime.Object) (runtime.Object, error) { // Make sure we actually have a persisted currentObject if hasUID, err := hasUID(currentObject); err != nil { return nil, err } else if !hasUID { return nil, errors.NewNotFound(resource.GroupResource(), name) } switch { case originalObjJS == nil && originalObjMap == nil: // first time through, // 1. apply the patch // 2. save the original and patched to detect whether there were conflicting changes on retries objToUpdate := patcher.New() // For performance reasons, in case of strategicpatch, we avoid json // marshaling and unmarshaling and operate just on map[string]interface{}. // In case of other patch types, we still have to operate on JSON // representations. switch patchType { case types.JSONPatchType, types.MergePatchType: originalJS, patchedJS, err := patchObjectJSON(patchType, codec, currentObject, patchJS, objToUpdate, versionedObj) if err != nil { return nil, err } originalObjJS, originalPatchedObjJS = originalJS, patchedJS case types.StrategicMergePatchType: originalMap, patchMap, err := strategicPatchObject(codec, currentObject, patchJS, objToUpdate, versionedObj) if err != nil { return nil, err } originalObjMap, originalPatchMap = originalMap, patchMap } if err := checkName(objToUpdate, name, namespace, namer); err != nil { return nil, err } return objToUpdate, nil default: // on a conflict, // 1. build a strategic merge patch from originalJS and the patchedJS. Different patch types can // be specified, but a strategic merge patch should be expressive enough handle them. Build the // patch with this type to handle those cases. // 2. build a strategic merge patch from originalJS and the currentJS // 3. ensure no conflicts between the two patches // 4. apply the #1 patch to the currentJS object // TODO: This should be one-step conversion that doesn't require // json marshaling and unmarshaling once #39017 is fixed. data, err := runtime.Encode(codec, currentObject) if err != nil { return nil, err } currentObjMap := make(map[string]interface{}) if err := json.Unmarshal(data, ¤tObjMap); err != nil { return nil, err } var currentPatchMap map[string]interface{} if originalObjMap != nil { var err error currentPatchMap, err = strategicpatch.CreateTwoWayMergeMapPatch(originalObjMap, currentObjMap, versionedObj) if err != nil { return nil, err } } else { if originalPatchMap == nil { // Compute original patch, if we already didn't do this in previous retries. originalPatch, err := strategicpatch.CreateTwoWayMergePatch(originalObjJS, originalPatchedObjJS, versionedObj) if err != nil { return nil, err } originalPatchMap = make(map[string]interface{}) if err := json.Unmarshal(originalPatch, &originalPatchMap); err != nil { return nil, err } } // Compute current patch. currentObjJS, err := runtime.Encode(codec, currentObject) if err != nil { return nil, err } currentPatch, err := strategicpatch.CreateTwoWayMergePatch(originalObjJS, currentObjJS, versionedObj) if err != nil { return nil, err } currentPatchMap = make(map[string]interface{}) if err := json.Unmarshal(currentPatch, ¤tPatchMap); err != nil { return nil, err } } hasConflicts, err := strategicpatch.HasConflicts(originalPatchMap, currentPatchMap) if err != nil { return nil, err } if hasConflicts { if glog.V(4) { diff1, _ := json.Marshal(currentPatchMap) diff2, _ := json.Marshal(originalPatchMap) glog.Infof("patchResource failed for resource %s, because there is a meaningful conflict.\n diff1=%v\n, diff2=%v\n", name, diff1, diff2) } // Return the last conflict error we got if we have one if lastConflictErr != nil { return nil, lastConflictErr } // Otherwise manufacture one of our own return nil, errors.NewConflict(resource.GroupResource(), name, nil) } objToUpdate := patcher.New() if err := applyPatchToObject(codec, currentObjMap, originalPatchMap, objToUpdate, versionedObj); err != nil { return nil, err } return objToUpdate, nil } } // applyAdmission is called every time GuaranteedUpdate asks for the updated object, // and is given the currently persisted object and the patched object as input. applyAdmission := func(ctx request.Context, patchedObject runtime.Object, currentObject runtime.Object) (runtime.Object, error) { return patchedObject, admit(patchedObject, currentObject) } updatedObjectInfo := rest.DefaultUpdatedObjectInfo(nil, copier, applyPatch, applyAdmission) return finishRequest(timeout, func() (runtime.Object, error) { updateObject, _, updateErr := patcher.Update(ctx, name, updatedObjectInfo) for i := 0; i < maxRetryWhenPatchConflicts && (errors.IsConflict(updateErr)); i++ { lastConflictErr = updateErr updateObject, _, updateErr = patcher.Update(ctx, name, updatedObjectInfo) } return updateObject, updateErr }) }
func RunLabel(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *LabelOptions) error { resources, labelArgs, err := cmdutil.GetResourcesAndPairs(args, "label") if err != nil { return err } if len(resources) < 1 && len(options.Filenames) == 0 { return cmdutil.UsageError(cmd, "one or more resources must be specified as <resource> <name> or <resource>/<name>") } if len(labelArgs) < 1 { return cmdutil.UsageError(cmd, "at least one label update is required") } selector := cmdutil.GetFlagString(cmd, "selector") all := cmdutil.GetFlagBool(cmd, "all") overwrite := cmdutil.GetFlagBool(cmd, "overwrite") resourceVersion := cmdutil.GetFlagString(cmd, "resource-version") cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err } lbls, remove, err := parseLabels(labelArgs) if err != nil { return cmdutil.UsageError(cmd, err.Error()) } mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) b := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, options.Recursive, options.Filenames...). SelectorParam(selector). ResourceTypeOrNameArgs(all, resources...). Flatten(). Latest() one := false r := b.Do().IntoSingular(&one) if err := r.Err(); err != nil { return err } // only apply resource version locking on a single resource if !one && len(resourceVersion) > 0 { return cmdutil.UsageError(cmd, "--resource-version may only be used with a single resource") } // TODO: support bulk generic output a la Get return r.Visit(func(info *resource.Info, err error) error { if err != nil { return err } var outputObj runtime.Object dataChangeMsg := "not labeled" if cmdutil.GetDryRunFlag(cmd) { err = labelFunc(info.Object, overwrite, resourceVersion, lbls, remove) if err != nil { return err } outputObj = info.Object } else { obj, err := cmdutil.MaybeConvertObject(info.Object, info.Mapping.GroupVersionKind.GroupVersion(), info.Mapping) if err != nil { return err } name, namespace := info.Name, info.Namespace oldData, err := json.Marshal(obj) if err != nil { return err } accessor, err := meta.Accessor(obj) if err != nil { return err } for _, label := range remove { if _, ok := accessor.GetLabels()[label]; !ok { fmt.Fprintf(out, "label %q not found.\n", label) } } if err := labelFunc(obj, overwrite, resourceVersion, lbls, remove); err != nil { return err } if cmdutil.ShouldRecord(cmd, info) { if err := cmdutil.RecordChangeCause(obj, f.Command()); err != nil { return err } } newData, err := json.Marshal(obj) if err != nil { return err } if !reflect.DeepEqual(oldData, newData) { dataChangeMsg = "labeled" } patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj) createdPatch := err == nil if err != nil { glog.V(2).Infof("couldn't compute patch: %v", err) } mapping := info.ResourceMapping() client, err := f.ClientForMapping(mapping) if err != nil { return err } helper := resource.NewHelper(client, mapping) if createdPatch { outputObj, err = helper.Patch(namespace, name, api.StrategicMergePatchType, patchBytes) } else { outputObj, err = helper.Replace(namespace, name, false, obj) } if err != nil { return err } } outputFormat := cmdutil.GetFlagString(cmd, "output") if outputFormat != "" { return f.PrintObject(cmd, mapper, outputObj, out) } cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, dataChangeMsg) return nil }) }
// RunAnnotate does the work func (o AnnotateOptions) RunAnnotate() error { r := o.builder.Do() if err := r.Err(); err != nil { return err } clientConfig, err := o.f.ClientConfig() if err != nil { return err } // TODO: get the negotiated version per group negotiatedVersion := "" if clientConfig.GroupVersion != nil { negotiatedVersion = clientConfig.GroupVersion.String() } return r.Visit(func(info *resource.Info, err error) error { if err != nil { return err } // if the resource can't be converted to the negotiated version, AsVersionedObject falls back to the info.Mapping.GroupVersion obj, err := resource.AsVersionedObject([]*resource.Info{info}, false, negotiatedVersion) if err != nil { return err } name, namespace := info.Name, info.Namespace oldData, err := json.Marshal(obj) if err != nil { return err } if err := o.updateAnnotations(obj); err != nil { return err } newData, err := json.Marshal(obj) if err != nil { return err } patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj) createdPatch := err == nil if err != nil { glog.V(2).Infof("couldn't compute patch: %v", err) } mapping := info.ResourceMapping() client, err := o.f.RESTClient(mapping) if err != nil { return err } helper := resource.NewHelper(client, mapping) var outputObj runtime.Object if createdPatch { outputObj, err = helper.Patch(namespace, name, api.StrategicMergePatchType, patchBytes) } else { outputObj, err = helper.Replace(namespace, name, false, obj) } if err != nil { return err } outputFormat := cmdutil.GetFlagString(o.cmd, "output") if outputFormat != "" { return o.f.PrintObject(o.cmd, outputObj, o.out) } mapper, _ := o.f.Object() cmdutil.PrintSuccess(mapper, false, o.out, info.Mapping.Resource, info.Name, "annotated") return nil }) }
func (tc *patchTestCase) Run(t *testing.T) { t.Logf("Starting test %s", tc.name) namespace := tc.startingPod.Namespace name := tc.startingPod.Name codec := testapi.Default.Codec() admit := tc.admit if admit == nil { admit = func(updatedObject runtime.Object, currentObject runtime.Object) error { return nil } } testPatcher := &testPatcher{} testPatcher.t = t testPatcher.startingPod = tc.startingPod testPatcher.updatePod = tc.updatePod ctx := request.NewDefaultContext() ctx = request.WithNamespace(ctx, namespace) namer := &testNamer{namespace, name} copier := runtime.ObjectCopier(api.Scheme) resource := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"} versionedObj := &v1.Pod{} for _, patchType := range []types.PatchType{types.JSONPatchType, types.MergePatchType, types.StrategicMergePatchType} { // TODO SUPPORT THIS! if patchType == types.JSONPatchType { continue } t.Logf("Working with patchType %v", patchType) originalObjJS, err := runtime.Encode(codec, tc.startingPod) if err != nil { t.Errorf("%s: unexpected error: %v", tc.name, err) return } changedJS, err := runtime.Encode(codec, tc.changedPod) if err != nil { t.Errorf("%s: unexpected error: %v", tc.name, err) return } patch := []byte{} switch patchType { case types.JSONPatchType: continue case types.StrategicMergePatchType: patch, err = strategicpatch.CreateTwoWayMergePatch(originalObjJS, changedJS, versionedObj) if err != nil { t.Errorf("%s: unexpected error: %v", tc.name, err) return } case types.MergePatchType: patch, err = jsonpatch.CreateMergePatch(originalObjJS, changedJS) if err != nil { t.Errorf("%s: unexpected error: %v", tc.name, err) return } } resultObj, err := patchResource(ctx, admit, 1*time.Second, versionedObj, testPatcher, name, patchType, patch, namer, copier, resource, codec) if len(tc.expectedError) != 0 { if err == nil || err.Error() != tc.expectedError { t.Errorf("%s: expected error %v, but got %v", tc.name, tc.expectedError, err) return } } else { if err != nil { t.Errorf("%s: unexpected error: %v", tc.name, err) return } } if tc.expectedPod == nil { if resultObj != nil { t.Errorf("%s: unexpected result: %v", tc.name, resultObj) } return } resultPod := resultObj.(*api.Pod) // roundtrip to get defaulting expectedJS, err := runtime.Encode(codec, tc.expectedPod) if err != nil { t.Errorf("%s: unexpected error: %v", tc.name, err) return } expectedObj, err := runtime.Decode(codec, expectedJS) if err != nil { t.Errorf("%s: unexpected error: %v", tc.name, err) return } reallyExpectedPod := expectedObj.(*api.Pod) if !reflect.DeepEqual(*reallyExpectedPod, *resultPod) { t.Errorf("%s mismatch: %v\n", tc.name, diff.ObjectGoPrintDiff(reallyExpectedPod, resultPod)) return } } }
func RunLabel(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *LabelOptions) error { resources, labelArgs := []string{}, []string{} first := true for _, s := range args { isLabel := strings.Contains(s, "=") || strings.HasSuffix(s, "-") switch { case first && isLabel: first = false fallthrough case !first && isLabel: labelArgs = append(labelArgs, s) case first && !isLabel: resources = append(resources, s) case !first && !isLabel: return cmdutil.UsageError(cmd, "all resources must be specified before label changes: %s", s) } } if len(resources) < 1 && len(options.Filenames) == 0 { return cmdutil.UsageError(cmd, "one or more resources must be specified as <resource> <name> or <resource>/<name>") } if len(labelArgs) < 1 { return cmdutil.UsageError(cmd, "at least one label update is required") } selector := cmdutil.GetFlagString(cmd, "selector") all := cmdutil.GetFlagBool(cmd, "all") overwrite := cmdutil.GetFlagBool(cmd, "overwrite") resourceVersion := cmdutil.GetFlagString(cmd, "resource-version") cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err } lbls, remove, err := parseLabels(labelArgs) if err != nil { return cmdutil.UsageError(cmd, err.Error()) } mapper, typer := f.Object() b := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, options.Filenames...). SelectorParam(selector). ResourceTypeOrNameArgs(all, resources...). Flatten(). Latest() one := false r := b.Do().IntoSingular(&one) if err := r.Err(); err != nil { return err } // only apply resource version locking on a single resource if !one && len(resourceVersion) > 0 { return cmdutil.UsageError(cmd, "--resource-version may only be used with a single resource") } // TODO: support bulk generic output a la Get return r.Visit(func(info *resource.Info, err error) error { if err != nil { return err } var outputObj runtime.Object dataChangeMsg := "not labeled" if cmdutil.GetFlagBool(cmd, "dry-run") { err = labelFunc(info.Object, overwrite, resourceVersion, lbls, remove) if err != nil { return err } outputObj = info.Object } else { name, namespace, obj := info.Name, info.Namespace, info.Object oldData, err := json.Marshal(obj) if err != nil { return err } meta, err := api.ObjectMetaFor(obj) for _, label := range remove { if _, ok := meta.Labels[label]; !ok { fmt.Fprintf(out, "label %q not found.\n", label) } } if err := labelFunc(obj, overwrite, resourceVersion, lbls, remove); err != nil { return err } newData, err := json.Marshal(obj) if err != nil { return err } if !reflect.DeepEqual(oldData, newData) { dataChangeMsg = "labeled" } patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj) if err != nil { return err } mapping := info.ResourceMapping() client, err := f.RESTClient(mapping) if err != nil { return err } helper := resource.NewHelper(client, mapping) outputObj, err = helper.Patch(namespace, name, api.StrategicMergePatchType, patchBytes) if err != nil { return err } } outputFormat := cmdutil.GetFlagString(cmd, "output") if outputFormat != "" { return f.PrintObject(cmd, outputObj, out) } cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, dataChangeMsg) return nil }) }
// RunAnnotate does the work func (o AnnotateOptions) RunAnnotate() error { r := o.builder.Do() if err := r.Err(); err != nil { return err } var singularResource bool r.IntoSingular(&singularResource) // only apply resource version locking on a single resource. // we must perform this check after o.builder.Do() as // []o.resources can not not accurately return the proper number // of resources when they are not passed in "resource/name" format. if !singularResource && len(o.resourceVersion) > 0 { return fmt.Errorf("--resource-version may only be used with a single resource") } return r.Visit(func(info *resource.Info, err error) error { if err != nil { return err } obj, err := cmdutil.MaybeConvertObject(info.Object, info.Mapping.GroupVersionKind.GroupVersion(), info.Mapping) if err != nil { return err } name, namespace := info.Name, info.Namespace oldData, err := json.Marshal(obj) if err != nil { return err } // If we should record change-cause, add it to new annotations if cmdutil.ContainsChangeCause(info) || o.recordChangeCause { o.newAnnotations[kubectl.ChangeCauseAnnotation] = o.changeCause } if err := o.updateAnnotations(obj); err != nil { return err } newData, err := json.Marshal(obj) if err != nil { return err } patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj) createdPatch := err == nil if err != nil { glog.V(2).Infof("couldn't compute patch: %v", err) } mapping := info.ResourceMapping() client, err := o.f.ClientForMapping(mapping) if err != nil { return err } helper := resource.NewHelper(client, mapping) var outputObj runtime.Object if createdPatch { outputObj, err = helper.Patch(namespace, name, api.StrategicMergePatchType, patchBytes) } else { outputObj, err = helper.Replace(namespace, name, false, obj) } if err != nil { return err } mapper, _ := o.f.Object() outputFormat := cmdutil.GetFlagString(o.cmd, "output") if outputFormat != "" { return o.f.PrintObject(o.cmd, mapper, outputObj, o.out) } cmdutil.PrintSuccess(mapper, false, o.out, info.Mapping.Resource, info.Name, false, "annotated") return nil }) }
func (nsu *nodeStatusUpdater) UpdateNodeStatuses() error { // TODO: investigate right behavior if nodeName is empty // kubernetes/kubernetes/issues/37777 nodesToUpdate := nsu.actualStateOfWorld.GetVolumesToReportAttached() for nodeName, attachedVolumes := range nodesToUpdate { nodeObj, exists, err := nsu.nodeInformer.GetStore().GetByKey(string(nodeName)) if nodeObj == nil || !exists || err != nil { // If node does not exist, its status cannot be updated, log error and // reset flag statusUpdateNeeded back to true to indicate this node status // needs to be updated again glog.V(2).Infof( "Could not update node status. Failed to find node %q in NodeInformer cache. %v", nodeName, err) nsu.actualStateOfWorld.SetNodeStatusUpdateNeeded(nodeName) continue } clonedNode, err := api.Scheme.DeepCopy(nodeObj) if err != nil { return fmt.Errorf("error cloning node %q: %v", nodeName, err) } node, ok := clonedNode.(*v1.Node) if !ok || node == nil { return fmt.Errorf( "failed to cast %q object %#v to Node", nodeName, clonedNode) } // TODO: Change to pkg/util/node.UpdateNodeStatus. oldData, err := json.Marshal(node) if err != nil { return fmt.Errorf( "failed to Marshal oldData for node %q. %v", nodeName, err) } node.Status.VolumesAttached = attachedVolumes newData, err := json.Marshal(node) if err != nil { return fmt.Errorf( "failed to Marshal newData for node %q. %v", nodeName, err) } patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, node) if err != nil { return fmt.Errorf( "failed to CreateTwoWayMergePatch for node %q. %v", nodeName, err) } _, err = nsu.kubeClient.Core().Nodes().PatchStatus(string(nodeName), patchBytes) if err != nil { // If update node status fails, reset flag statusUpdateNeeded back to true // to indicate this node status needs to be updated again nsu.actualStateOfWorld.SetNodeStatusUpdateNeeded(nodeName) return fmt.Errorf( "failed to kubeClient.Core().Nodes().Patch for node %q. %v", nodeName, err) } glog.V(2).Infof( "Updating status for node %q succeeded. patchBytes: %q VolumesAttached: %v", nodeName, string(patchBytes), node.Status.VolumesAttached) } return nil }
func visitToPatch(originalObj runtime.Object, updates *resource.Info, mapper meta.RESTMapper, resourceMapper *resource.Mapper, encoder runtime.Encoder, out, errOut io.Writer, defaultVersion unversioned.GroupVersion, results *editResults, file string) error { patchVisitor := resource.NewFlattenListVisitor(updates, resourceMapper) err := patchVisitor.Visit(func(info *resource.Info, incomingErr error) error { currOriginalObj := originalObj // if we're editing a list, then navigate the list to find the item that we're currently trying to edit if meta.IsListType(originalObj) { currOriginalObj = nil editObjUID, err := meta.NewAccessor().UID(info.Object) if err != nil { return err } listItems, err := meta.ExtractList(originalObj) if err != nil { return err } // iterate through the list to find the item with the matching UID for i := range listItems { originalObjUID, err := meta.NewAccessor().UID(listItems[i]) if err != nil { return err } if editObjUID == originalObjUID { currOriginalObj = listItems[i] break } } if currOriginalObj == nil { return fmt.Errorf("no original object found for %#v", info.Object) } } originalSerialization, err := runtime.Encode(encoder, currOriginalObj) if err != nil { return err } editedSerialization, err := runtime.Encode(encoder, info.Object) if err != nil { return err } // compute the patch on a per-item basis // use strategic merge to create a patch originalJS, err := yaml.ToJSON(originalSerialization) if err != nil { return err } editedJS, err := yaml.ToJSON(editedSerialization) if err != nil { return err } if reflect.DeepEqual(originalJS, editedJS) { // no edit, so just skip it. cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, false, "skipped") return nil } preconditions := []strategicpatch.PreconditionFunc{strategicpatch.RequireKeyUnchanged("apiVersion"), strategicpatch.RequireKeyUnchanged("kind"), strategicpatch.RequireMetadataKeyUnchanged("name")} patch, err := strategicpatch.CreateTwoWayMergePatch(originalJS, editedJS, currOriginalObj, preconditions...) if err != nil { glog.V(4).Infof("Unable to calculate diff, no merge is possible: %v", err) if strategicpatch.IsPreconditionFailed(err) { return fmt.Errorf("%s", "At least one of apiVersion, kind and name was changed") } return err } results.version = defaultVersion patched, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, api.StrategicMergePatchType, patch) if err != nil { fmt.Fprintln(out, results.addError(err, info)) return nil } info.Refresh(patched, true) cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, false, "edited") return nil }) return err }