func RunCreate(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *CreateOptions) error { schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir")) if err != nil { return err } cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err } mapper, typer := f.Object() r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). Schema(schema). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, options.Filenames...). Flatten(). Do() err = r.Err() if err != nil { return err } count := 0 err = r.Visit(func(info *resource.Info, err error) error { if err != nil { return err } if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info, f.JSONEncoder()); err != nil { return cmdutil.AddSourceToErr("creating", info.Source, err) } if cmdutil.ShouldRecord(cmd, info) { if err := cmdutil.RecordChangeCause(info.Object, f.Command()); err != nil { return cmdutil.AddSourceToErr("creating", info.Source, err) } } if err := createAndRefresh(info); err != nil { return cmdutil.AddSourceToErr("creating", info.Source, err) } count++ shortOutput := cmdutil.GetFlagString(cmd, "output") == "name" if !shortOutput { printObjectSpecificMessage(info.Object, out) } cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, "created") return nil }) if err != nil { return err } if count == 0 { return fmt.Errorf("no objects passed to create") } return nil }
func visitAnnotation(cmd *cobra.Command, f cmdutil.Factory, updates *resource.Info, resourceMapper *resource.Mapper, encoder runtime.Encoder) ([]runtime.Object, error) { mutatedObjects := []runtime.Object{} annotationVisitor := resource.NewFlattenListVisitor(updates, resourceMapper) // iterate through all items to apply annotations 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 }) return mutatedObjects, err }
func RunEdit(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, filenames []string) error { var printer kubectl.ResourcePrinter var ext string switch format := cmdutil.GetFlagString(cmd, "output"); format { case "json": printer = &kubectl.JSONPrinter{} ext = ".json" case "yaml": printer = &kubectl.YAMLPrinter{} ext = ".yaml" 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), Decoder: f.Decoder(true), } r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, filenames...). ResourceTypeOrNameArgs(true, args...). Latest(). Flatten(). Do() err = r.Err() if err != nil { return err } infos, err := r.Infos() if err != nil { return err } clientConfig, err := f.ClientConfig() if err != nil { return err } encoder := f.JSONEncoder() windowsLineEndings := cmdutil.GetFlagBool(cmd, "windows-line-endings") edit := editor.NewDefaultEditor(f.EditorEnvs()) defaultVersion, err := cmdutil.OutputVersion(cmd, clientConfig.GroupVersion) if err != nil { return err } results := editResults{} for { objs, err := resource.AsVersionedObjects(infos, defaultVersion.String(), encoder) if err != nil { return preservedFile(err, results.file, out) } // if input object is a list, traverse and edit each item one at a time for _, obj := range objs { // TODO: add an annotating YAML printer that can print inline comments on each field, // including descriptions or validation errors // generate the file to edit buf := &bytes.Buffer{} var w io.Writer = buf if windowsLineEndings { w = util.NewCRLFWriter(w) } if err := results.header.writeTo(w); err != nil { return preservedFile(err, results.file, out) } if err := printer.PrintObj(obj, w); err != nil { return preservedFile(err, results.file, out) } original := buf.Bytes() // launch the editor edited, file, err := edit.LaunchTempFile("kubectl-edit-", ext, buf) if err != nil { return preservedFile(err, results.file, out) } // 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)) lines, err := hasLines(bytes.NewBuffer(edited)) if err != nil { return preservedFile(err, file, out) } // Compare content without comments if bytes.Equal(stripComments(original), stripComments(edited)) { if len(results.edit) > 0 { preservedFile(nil, file, out) } else { os.Remove(file) } fmt.Fprintln(out, "Edit cancelled, no changes made.") continue } if !lines { if len(results.edit) > 0 { preservedFile(nil, file, out) } else { os.Remove(file) } fmt.Fprintln(out, "Edit cancelled, saved file was empty.") continue } results = editResults{ file: file, } // parse the edited file updates, err := resourceMapper.InfoForData(edited, "edited-file") if err != nil { return fmt.Errorf("The edited file had a syntax error: %v", err) } // put configuration annotation in "updates" if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), updates, encoder); err != nil { return preservedFile(err, file, out) } if cmdutil.ShouldRecord(cmd, updates) { err = cmdutil.RecordChangeCause(updates.Object, f.Command()) if err != nil { return err } } // encode updates back to "edited" since we'll only generate patch from "edited" if edited, err = runtime.Encode(encoder, updates.Object); err != nil { return preservedFile(err, file, out) } visitor := resource.NewFlattenListVisitor(updates, resourceMapper) // need to make sure the original namespace wasn't changed while editing if err = visitor.Visit(resource.RequireNamespace(cmdNamespace)); err != nil { return preservedFile(err, file, out) } // use strategic merge to create a patch originalJS, err := yaml.ToJSON(original) if err != nil { return preservedFile(err, file, out) } editedJS, err := yaml.ToJSON(edited) if err != nil { return preservedFile(err, file, out) } patch, err := strategicpatch.CreateStrategicMergePatch(originalJS, editedJS, obj) // TODO: change all jsonmerge to strategicpatch // for checking preconditions preconditions := []jsonmerge.PreconditionFunc{} if err != nil { glog.V(4).Infof("Unable to calculate diff, no merge is possible: %v", err) return preservedFile(err, file, out) } else { preconditions = append(preconditions, jsonmerge.RequireKeyUnchanged("apiVersion")) preconditions = append(preconditions, jsonmerge.RequireKeyUnchanged("kind")) preconditions = append(preconditions, jsonmerge.RequireMetadataKeyUnchanged("name")) results.version = defaultVersion } if hold, msg := jsonmerge.TestPreconditionsHold(patch, preconditions); !hold { fmt.Fprintf(out, "error: %s", msg) return preservedFile(nil, file, out) } err = visitor.Visit(func(info *resource.Info, err error) error { 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, "edited") return nil }) if err != nil { return preservedFile(err, file, out) } if results.retryable > 0 { fmt.Fprintf(out, "You can run `kubectl replace -f %s` to try this update again.\n", file) return errExit } if results.conflict > 0 { fmt.Fprintf(out, "You must update your local resource version and run `kubectl replace -f %s` to overwrite the remote changes.\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) } } } if len(results.edit) == 0 { return nil } // loop again and edit the remaining items infos = results.edit } return nil }
func RunAutoscale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *AutoscaleOptions) error { namespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err } // validate flags if err := validateFlags(cmd); err != nil { return err } mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace(). FilenameParam(enforceNamespace, options.Recursive, options.Filenames...). ResourceTypeOrNameArgs(false, args...). Flatten(). Do() infos, err := r.Infos() if err != nil { return err } if len(infos) > 1 { return fmt.Errorf("multiple resources provided: %v", args) } info := infos[0] mapping := info.ResourceMapping() if err := f.CanBeAutoscaled(mapping.GroupVersionKind.GroupKind()); err != nil { return err } // Get the generator, setup and validate all required parameters generatorName := cmdutil.GetFlagString(cmd, "generator") generators := f.Generators("autoscale") generator, found := generators[generatorName] if !found { return cmdutil.UsageError(cmd, fmt.Sprintf("generator %q not found.", generatorName)) } names := generator.ParamNames() params := kubectl.MakeParams(cmd, names) name := info.Name params["default-name"] = name params["scaleRef-kind"] = mapping.GroupVersionKind.Kind params["scaleRef-name"] = name params["scaleRef-apiVersion"] = mapping.GroupVersionKind.GroupVersion().String() if err = kubectl.ValidateParams(names, params); err != nil { return err } // Check for invalid flags used against the present generator. if err := kubectl.EnsureFlagsValid(cmd, generators, generatorName); err != nil { return err } // Generate new object object, err := generator.Generate(params) if err != nil { return err } resourceMapper := &resource.Mapper{ ObjectTyper: typer, RESTMapper: mapper, ClientMapper: resource.ClientMapperFunc(f.ClientForMapping), Decoder: f.Decoder(true), } hpa, err := resourceMapper.InfoForObject(object, nil) if err != nil { return err } if cmdutil.ShouldRecord(cmd, hpa) { if err := cmdutil.RecordChangeCause(hpa.Object, f.Command()); err != nil { return err } object = hpa.Object } // TODO: extract this flag to a central location, when such a location exists. if cmdutil.GetFlagBool(cmd, "dry-run") { return f.PrintObject(cmd, mapper, object, out) } if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), hpa, f.JSONEncoder()); err != nil { return err } object, err = resource.NewHelper(hpa.Client, hpa.Mapping).Create(namespace, false, object) if err != nil { return err } if len(cmdutil.GetFlagString(cmd, "output")) > 0 { return f.PrintObject(cmd, mapper, object, out) } cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, "autoscaled") return nil }
func RunApply(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *resource.FilenameOptions) error { shortOutput := cmdutil.GetFlagString(cmd, "output") == "name" schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir")) if err != nil { return err } cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err } mapper, typer := f.Object() r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). Schema(schema). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, options). Flatten(). Do() err = r.Err() if err != nil { return err } encoder := f.JSONEncoder() decoder := f.Decoder(false) count := 0 err = r.Visit(func(info *resource.Info, err error) error { // In this method, info.Object contains the object retrieved from the server // and info.VersionedObject contains the object decoded from the input source. if err != nil { return err } // Get the modified configuration of the object. Embed the result // as an annotation in the modified configuration, so that it will appear // in the patch sent to the server. modified, err := kubectl.GetModifiedConfiguration(info, true, encoder) if err != nil { return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving modified configuration from:\n%v\nfor:", info), info.Source, err) } if err := info.Get(); err != nil { if !errors.IsNotFound(err) { return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%v\nfrom server for:", info), info.Source, err) } // Create the resource if it doesn't exist // First, update the annotation used by kubectl apply if err := kubectl.CreateApplyAnnotation(info, encoder); err != nil { return cmdutil.AddSourceToErr("creating", info.Source, err) } if cmdutil.ShouldRecord(cmd, info) { if err := cmdutil.RecordChangeCause(info.Object, f.Command()); err != nil { return cmdutil.AddSourceToErr("creating", info.Source, err) } } // Then create the resource and skip the three-way merge if err := createAndRefresh(info); err != nil { return cmdutil.AddSourceToErr("creating", info.Source, err) } count++ cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, "created") return nil } overwrite := cmdutil.GetFlagBool(cmd, "overwrite") helper := resource.NewHelper(info.Client, info.Mapping) patcher := NewPatcher(encoder, decoder, info.Mapping, helper, overwrite) patchBytes, err := patcher.patch(info.Object, modified, info.Source, info.Namespace, info.Name) if err != nil { return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patchBytes, info), info.Source, err) } if cmdutil.ShouldRecord(cmd, info) { patch, err := cmdutil.ChangeResourcePatch(info, f.Command()) if err != nil { return err } _, err = helper.Patch(info.Namespace, info.Name, api.StrategicMergePatchType, patch) if err != nil { return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patch, info), info.Source, err) } } count++ cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, "configured") return nil }) if err != nil { return err } if count == 0 { return fmt.Errorf("no objects passed to apply") } return nil }
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, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). 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 { obj, err := info.Mapping.ConvertToVersion(info.Object, info.Mapping.GroupVersionKind.GroupVersion().String()) if err != nil { return err } name, namespace := info.Name, info.Namespace 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 } 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, outputObj, out) } cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, dataChangeMsg) return nil }) }
func RunEdit(f *cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args []string, options *EditOptions) error { var printer kubectl.ResourcePrinter var ext string switch format := cmdutil.GetFlagString(cmd, "output"); format { case "json": printer = &kubectl.JSONPrinter{} ext = ".json" case "yaml": printer = &kubectl.YAMLPrinter{} ext = ".yaml" 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(cmdutil.GetIncludeThirdPartyAPIs(cmd)) 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.Recursive, options.Filenames...). ResourceTypeOrNameArgs(true, args...). Flatten(). Latest(). Do() err = r.Err() if err != nil { return err } infos, err := r.Infos() 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 } originalObj, err := resource.AsVersionedObject(infos, false, defaultVersion, encoder) if err != nil { return err } var ( windowsLineEndings = cmdutil.GetFlagBool(cmd, "windows-line-endings") edit = editor.NewDefaultEditor(f.EditorEnvs()) results = editResults{} original = []byte{} edited = []byte{} file string ) containsError := false for { // infos mutates over time to be the list of things we've tried and failed to edit // this means that our overall list changes over time. objToEdit, err := resource.AsVersionedObject(infos, false, defaultVersion, encoder) if err != nil { return err } // generate the file to edit buf := &bytes.Buffer{} var w io.Writer = buf if windowsLineEndings { w = crlf.NewCRLFWriter(w) } if err := results.header.writeTo(w); err != nil { return preservedFile(err, results.file, errOut) } 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)) // 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, "skipped") return nil } patch, err := strategicpatch.CreateStrategicMergePatch(originalJS, editedJS, currOriginalObj) // TODO: change all jsonmerge to strategicpatch // for checking preconditions preconditions := []jsonmerge.PreconditionFunc{} if err != nil { glog.V(4).Infof("Unable to calculate diff, no merge is possible: %v", err) return err } else { preconditions = append(preconditions, jsonmerge.RequireKeyUnchanged("apiVersion")) preconditions = append(preconditions, jsonmerge.RequireKeyUnchanged("kind")) preconditions = append(preconditions, jsonmerge.RequireMetadataKeyUnchanged("name")) results.version = defaultVersion } if hold, msg := jsonmerge.TestPreconditionsHold(patch, preconditions); !hold { fmt.Fprintf(errOut, "error: %s", msg) return preservedFile(nil, file, errOut) } 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, "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 } }
func RunPatch(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, shortOutput bool, options *PatchOptions) error { cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err } patch := cmdutil.GetFlagString(cmd, "patch") if len(patch) == 0 { return cmdutil.UsageError(cmd, "Must specify -p to patch") } patchBytes, err := yaml.ToJSON([]byte(patch)) if err != nil { return fmt.Errorf("unable to parse %q: %v", patch, err) } mapper, typer := f.Object() r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, options.Filenames...). ResourceTypeOrNameArgs(false, args...). Flatten(). Do() err = r.Err() if err != nil { return err } infos, err := r.Infos() if err != nil { return err } if len(infos) > 1 { return fmt.Errorf("multiple resources provided") } info := infos[0] name, namespace := info.Name, info.Namespace mapping := info.ResourceMapping() client, err := f.ClientForMapping(mapping) if err != nil { return err } helper := resource.NewHelper(client, mapping) _, err = helper.Patch(namespace, name, api.StrategicMergePatchType, patchBytes) if err != nil { return err } if cmdutil.ShouldRecord(cmd, info) { patchBytes, err = cmdutil.ChangeResourcePatch(info, f.Command()) if err != nil { return err } _, err = helper.Patch(namespace, name, api.StrategicMergePatchType, patchBytes) if err != nil { return err } } cmdutil.PrintSuccess(mapper, shortOutput, out, "", name, "patched") return nil }
func forceReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, shortOutput bool, options *resource.FilenameOptions) error { schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir")) if err != nil { return err } cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err } for i, filename := range options.Filenames { if filename == "-" { tempDir, err := ioutil.TempDir("", "kubectl_replace_") if err != nil { return err } defer os.RemoveAll(tempDir) tempFilename := filepath.Join(tempDir, "resource.stdin") err = cmdutil.DumpReaderToFile(os.Stdin, tempFilename) if err != nil { return err } options.Filenames[i] = tempFilename } } mapper, typer, err := f.UnstructuredObject() if err != nil { return err } r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), runtime.UnstructuredJSONScheme). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, options). ResourceTypeOrNameArgs(false, args...).RequireObject(false). Flatten(). Do() err = r.Err() if err != nil { return err } //Replace will create a resource if it doesn't exist already, so ignore not found error ignoreNotFound := true timeout := cmdutil.GetFlagDuration(cmd, "timeout") // By default use a reaper to delete all related resources. if cmdutil.GetFlagBool(cmd, "cascade") { glog.Warningf("\"cascade\" is set, kubectl will delete and re-create all resources managed by this resource (e.g. Pods created by a ReplicationController). Consider using \"kubectl rolling-update\" if you want to update a ReplicationController together with its Pods.") err = ReapResult(r, f, out, cmdutil.GetFlagBool(cmd, "cascade"), ignoreNotFound, timeout, cmdutil.GetFlagInt(cmd, "grace-period"), shortOutput, mapper, false) } else { err = DeleteResult(r, out, ignoreNotFound, shortOutput, mapper) } if err != nil { return err } if timeout == 0 { timeout = kubectl.Timeout } r.Visit(func(info *resource.Info, err error) error { if err != nil { return err } return wait.PollImmediate(kubectl.Interval, timeout, func() (bool, error) { if err := info.Get(); !errors.IsNotFound(err) { return false, err } return true, nil }) }) r = resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), runtime.UnstructuredJSONScheme). Schema(schema). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, options). Flatten(). Do() err = r.Err() if err != nil { return err } count := 0 err = r.Visit(func(info *resource.Info, err error) error { if err != nil { return err } if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info, f.JSONEncoder()); err != nil { return err } if cmdutil.ShouldRecord(cmd, info) { if err := cmdutil.RecordChangeCause(info.Object, f.Command()); err != nil { return cmdutil.AddSourceToErr("replacing", info.Source, err) } } obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object) if err != nil { return err } count++ info.Refresh(obj, true) f.PrintObjectSpecificMessage(obj, out) cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, false, "replaced") return nil }) if err != nil { return err } if count == 0 { return fmt.Errorf("no objects passed to replace") } return nil }
func RunApply(f cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *ApplyOptions) error { shortOutput := cmdutil.GetFlagString(cmd, "output") == "name" schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir")) if err != nil { return err } cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err } if options.Prune { options.PruneResources, err = parsePruneResources(cmdutil.GetFlagStringArray(cmd, "prune-whitelist")) if err != nil { return err } } mapper, typer := f.Object() r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). Schema(schema). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, &options.FilenameOptions). SelectorParam(options.Selector). Flatten(). Do() err = r.Err() if err != nil { return err } dryRun := cmdutil.GetFlagBool(cmd, "dry-run") encoder := f.JSONEncoder() decoder := f.Decoder(false) visitedUids := sets.NewString() visitedNamespaces := sets.NewString() count := 0 err = r.Visit(func(info *resource.Info, err error) error { // In this method, info.Object contains the object retrieved from the server // and info.VersionedObject contains the object decoded from the input source. if err != nil { return err } if info.Namespaced() { visitedNamespaces.Insert(info.Namespace) } // Get the modified configuration of the object. Embed the result // as an annotation in the modified configuration, so that it will appear // in the patch sent to the server. modified, err := kubectl.GetModifiedConfiguration(info, true, encoder) if err != nil { return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving modified configuration from:\n%v\nfor:", info), info.Source, err) } if err := info.Get(); err != nil { if !errors.IsNotFound(err) { return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%v\nfrom server for:", info), info.Source, err) } // Create the resource if it doesn't exist // First, update the annotation used by kubectl apply if err := kubectl.CreateApplyAnnotation(info, encoder); err != nil { return cmdutil.AddSourceToErr("creating", info.Source, err) } if cmdutil.ShouldRecord(cmd, info) { if err := cmdutil.RecordChangeCause(info.Object, f.Command()); err != nil { return cmdutil.AddSourceToErr("creating", info.Source, err) } } if !dryRun { // Then create the resource and skip the three-way merge if err := createAndRefresh(info); err != nil { return cmdutil.AddSourceToErr("creating", info.Source, err) } if uid, err := info.Mapping.UID(info.Object); err != nil { return err } else { visitedUids.Insert(string(uid)) } } count++ cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, dryRun, "created") return nil } if !dryRun { overwrite := cmdutil.GetFlagBool(cmd, "overwrite") helper := resource.NewHelper(info.Client, info.Mapping) patcher := &patcher{ encoder: encoder, decoder: decoder, mapping: info.Mapping, helper: helper, clientsetFunc: f.ClientSet, overwrite: overwrite, backOff: clockwork.NewRealClock(), force: options.Force, cascade: options.Cascade, timeout: options.Timeout, gracePeriod: options.GracePeriod, } patchBytes, err := patcher.patch(info.Object, modified, info.Source, info.Namespace, info.Name) if err != nil { return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patchBytes, info), info.Source, err) } if cmdutil.ShouldRecord(cmd, info) { patch, err := cmdutil.ChangeResourcePatch(info, f.Command()) if err != nil { return err } _, err = helper.Patch(info.Namespace, info.Name, api.StrategicMergePatchType, patch) if err != nil { return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patch, info), info.Source, err) } } if uid, err := info.Mapping.UID(info.Object); err != nil { return err } else { visitedUids.Insert(string(uid)) } } count++ cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, dryRun, "configured") return nil }) if err != nil { return err } if count == 0 { return fmt.Errorf("no objects passed to apply") } if !options.Prune { return nil } selector, err := labels.Parse(options.Selector) if err != nil { return err } p := pruner{ mapper: mapper, clientFunc: f.ClientForMapping, clientsetFunc: f.ClientSet, selector: selector, visitedUids: visitedUids, cascade: options.Cascade, dryRun: dryRun, gracePeriod: options.GracePeriod, out: out, } namespacedRESTMappings, nonNamespacedRESTMappings, err := getRESTMappings(&(options.PruneResources)) if err != nil { return fmt.Errorf("error retrieving RESTMappings to prune: %v", err) } for n := range visitedNamespaces { for _, m := range namespacedRESTMappings { if err := p.prune(n, m, shortOutput); err != nil { return fmt.Errorf("error pruning namespaced object %v: %v", m.GroupVersionKind, err) } } } for _, m := range nonNamespacedRESTMappings { if err := p.prune(api.NamespaceNone, m, shortOutput); err != nil { return fmt.Errorf("error pruning nonNamespaced object %v: %v", m.GroupVersionKind, err) } } return nil }
// RunScale executes the scaling func RunScale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, shortOutput bool, options *resource.FilenameOptions) error { if len(os.Args) > 1 && os.Args[1] == "resize" { printDeprecationWarning("scale", "resize") } cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err } mapper, typer := f.Object() r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, options). ResourceTypeOrNameArgs(false, args...). Flatten(). Do() err = r.Err() if resource.IsUsageError(err) { return cmdutil.UsageError(cmd, err.Error()) } if err != nil { return err } count := cmdutil.GetFlagInt(cmd, "replicas") if count < 0 { return cmdutil.UsageError(cmd, "The --replicas=COUNT flag is required, and COUNT must be greater than or equal to 0") } infos := []*resource.Info{} err = r.Visit(func(info *resource.Info, err error) error { if err == nil { infos = append(infos, info) } return nil }) resourceVersion := cmdutil.GetFlagString(cmd, "resource-version") if len(resourceVersion) != 0 && len(infos) > 1 { return fmt.Errorf("cannot use --resource-version with multiple resources") } counter := 0 err = r.Visit(func(info *resource.Info, err error) error { if err != nil { return err } mapping := info.ResourceMapping() scaler, err := f.Scaler(mapping) if err != nil { return err } currentSize := cmdutil.GetFlagInt(cmd, "current-replicas") precondition := &kubectl.ScalePrecondition{Size: currentSize, ResourceVersion: resourceVersion} retry := kubectl.NewRetryParams(kubectl.Interval, kubectl.Timeout) var waitForReplicas *kubectl.RetryParams if timeout := cmdutil.GetFlagDuration(cmd, "timeout"); timeout != 0 { waitForReplicas = kubectl.NewRetryParams(kubectl.Interval, timeout) } if err := scaler.Scale(info.Namespace, info.Name, uint(count), precondition, retry, waitForReplicas); err != nil { return err } if cmdutil.ShouldRecord(cmd, info) { patchBytes, err := cmdutil.ChangeResourcePatch(info, f.Command()) if err != nil { return err } mapping := info.ResourceMapping() client, err := f.ClientForMapping(mapping) if err != nil { return err } helper := resource.NewHelper(client, mapping) _, err = helper.Patch(info.Namespace, info.Name, api.StrategicMergePatchType, patchBytes) if err != nil { return err } } counter++ cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, false, "scaled") return nil }) if err != nil { return err } if counter == 0 { return fmt.Errorf("no objects passed to scale") } return nil }
func RunPatch(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, shortOutput bool, options *PatchOptions) error { cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err } patchType := api.StrategicMergePatchType patchTypeString := strings.ToLower(cmdutil.GetFlagString(cmd, "type")) if len(patchTypeString) != 0 { ok := false patchType, ok = patchTypes[patchTypeString] if !ok { return cmdutil.UsageError(cmd, fmt.Sprintf("--type must be one of %v, not %q", sets.StringKeySet(patchTypes).List(), patchTypeString)) } } patch := cmdutil.GetFlagString(cmd, "patch") if len(patch) == 0 { return cmdutil.UsageError(cmd, "Must specify -p to patch") } patchBytes, err := yaml.ToJSON([]byte(patch)) if err != nil { return fmt.Errorf("unable to parse %q: %v", patch, err) } mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, options.Recursive, options.Filenames...). ResourceTypeOrNameArgs(false, args...). Flatten(). Do() err = r.Err() if err != nil { return err } count := 0 err = r.Visit(func(info *resource.Info, err error) error { if err != nil { return err } name, namespace := info.Name, info.Namespace mapping := info.ResourceMapping() client, err := f.ClientForMapping(mapping) if err != nil { return err } helper := resource.NewHelper(client, mapping) patchedObject, err := helper.Patch(namespace, name, patchType, patchBytes) if err != nil { return err } if cmdutil.ShouldRecord(cmd, info) { if err := cmdutil.RecordChangeCause(patchedObject, f.Command()); err == nil { // don't return an error on failure. The patch itself succeeded, its only the hint for that change that failed // don't bother checking for failures of this replace, because a failure to indicate the hint doesn't fail the command // also, don't force the replacement. If the replacement fails on a resourceVersion conflict, then it means this // record hint is likely to be invalid anyway, so avoid the bad hint resource.NewHelper(client, mapping).Replace(namespace, name, false, patchedObject) } } count++ cmdutil.PrintSuccess(mapper, shortOutput, out, "", name, "patched") return nil }) if err != nil { return err } if count == 0 { return fmt.Errorf("no objects passed to patch") } return nil }
func RunAutoscale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *resource.FilenameOptions) error { namespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err } // validate flags if err := validateFlags(cmd); err != nil { return err } mapper, typer := f.Object() r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace(). FilenameParam(enforceNamespace, options). ResourceTypeOrNameArgs(false, args...). Flatten(). Do() err = r.Err() if err != nil { return err } // Get the generator, setup and validate all required parameters generatorName := cmdutil.GetFlagString(cmd, "generator") generators := f.Generators("autoscale") generator, found := generators[generatorName] if !found { return cmdutil.UsageError(cmd, fmt.Sprintf("generator %q not found.", generatorName)) } names := generator.ParamNames() count := 0 err = r.Visit(func(info *resource.Info, err error) error { if err != nil { return err } mapping := info.ResourceMapping() if err := f.CanBeAutoscaled(mapping.GroupVersionKind.GroupKind()); err != nil { return err } name := info.Name params := kubectl.MakeParams(cmd, names) params["default-name"] = name params["scaleRef-kind"] = mapping.GroupVersionKind.Kind params["scaleRef-name"] = name params["scaleRef-apiVersion"] = mapping.GroupVersionKind.GroupVersion().String() if err = kubectl.ValidateParams(names, params); err != nil { return err } // Check for invalid flags used against the present generator. if err := kubectl.EnsureFlagsValid(cmd, generators, generatorName); err != nil { return err } // Generate new object object, err := generator.Generate(params) if err != nil { return err } resourceMapper := &resource.Mapper{ ObjectTyper: typer, RESTMapper: mapper, ClientMapper: resource.ClientMapperFunc(f.ClientForMapping), Decoder: f.Decoder(true), } hpa, err := resourceMapper.InfoForObject(object, nil) if err != nil { return err } if cmdutil.ShouldRecord(cmd, hpa) { if err := cmdutil.RecordChangeCause(hpa.Object, f.Command()); err != nil { return err } object = hpa.Object } if cmdutil.GetDryRunFlag(cmd) { return f.PrintObject(cmd, mapper, object, out) } if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), hpa, f.JSONEncoder()); err != nil { return err } object, err = resource.NewHelper(hpa.Client, hpa.Mapping).Create(namespace, false, object) if err != nil { return err } count++ if len(cmdutil.GetFlagString(cmd, "output")) > 0 { return f.PrintObject(cmd, mapper, object, out) } cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, cmdutil.GetDryRunFlag(cmd), "autoscaled") return nil }) if err != nil { return err } if count == 0 { return fmt.Errorf("no objects passed to autoscale") } 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 }) }
func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *PatchOptions) error { switch { case options.Local && len(args) != 0: return fmt.Errorf("cannot specify --local and server resources") } cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err } patchType := api.StrategicMergePatchType patchTypeString := strings.ToLower(cmdutil.GetFlagString(cmd, "type")) if len(patchTypeString) != 0 { ok := false patchType, ok = patchTypes[patchTypeString] if !ok { return cmdutil.UsageError(cmd, fmt.Sprintf("--type must be one of %v, not %q", sets.StringKeySet(patchTypes).List(), patchTypeString)) } } patch := cmdutil.GetFlagString(cmd, "patch") if len(patch) == 0 { return cmdutil.UsageError(cmd, "Must specify -p to patch") } patchBytes, err := yaml.ToJSON([]byte(patch)) if err != nil { return fmt.Errorf("unable to parse %q: %v", patch, err) } mapper, typer := f.Object() r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, &options.FilenameOptions). ResourceTypeOrNameArgs(false, args...). Flatten(). Do() err = r.Err() if err != nil { return err } count := 0 err = r.Visit(func(info *resource.Info, err error) error { if err != nil { return err } name, namespace := info.Name, info.Namespace mapping := info.ResourceMapping() client, err := f.ClientForMapping(mapping) if err != nil { return err } if !options.Local { helper := resource.NewHelper(client, mapping) _, err := helper.Patch(namespace, name, patchType, patchBytes) if err != nil { return err } if cmdutil.ShouldRecord(cmd, info) { // don't return an error on failure. The patch itself succeeded, its only the hint for that change that failed // don't bother checking for failures of this replace, because a failure to indicate the hint doesn't fail the command // also, don't force the replacement. If the replacement fails on a resourceVersion conflict, then it means this // record hint is likely to be invalid anyway, so avoid the bad hint patch, err := cmdutil.ChangeResourcePatch(info, f.Command()) if err == nil { helper.Patch(info.Namespace, info.Name, api.StrategicMergePatchType, patch) } } count++ if options.OutputFormat == "name" || len(options.OutputFormat) == 0 { cmdutil.PrintSuccess(mapper, options.OutputFormat == "name", out, "", name, false, "patched") } return nil } count++ patchedObj, err := api.Scheme.DeepCopy(info.VersionedObject) if err != nil { return err } originalObjJS, err := runtime.Encode(api.Codecs.LegacyCodec(mapping.GroupVersionKind.GroupVersion()), info.VersionedObject.(runtime.Object)) if err != nil { return err } originalPatchedObjJS, err := getPatchedJSON(patchType, originalObjJS, patchBytes, patchedObj.(runtime.Object)) if err != nil { return err } targetObj, err := runtime.Decode(api.Codecs.UniversalDecoder(), originalPatchedObjJS) if err != nil { return err } // TODO: if we ever want to go generic, this allows a clean -o yaml without trying to print columns or anything // rawExtension := &runtime.Unknown{ // Raw: originalPatchedObjJS, // } printer, err := f.PrinterForMapping(cmd, mapping, false) if err != nil { return err } if err := printer.PrintObj(targetObj, out); err != nil { return err } return nil }) if err != nil { return err } if count == 0 { return fmt.Errorf("no objects passed to patch") } return nil }
func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *ExposeOptions) error { namespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err } mapper, typer := f.Object(false) r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace(). FilenameParam(enforceNamespace, options.Recursive, options.Filenames...). ResourceTypeOrNameArgs(false, args...). Flatten(). Do() err = r.Err() if err != nil { return err } // Get the generator, setup and validate all required parameters generatorName := cmdutil.GetFlagString(cmd, "generator") generators := f.Generators("expose") generator, found := generators[generatorName] if !found { return cmdutil.UsageError(cmd, fmt.Sprintf("generator %q not found.", generatorName)) } names := generator.ParamNames() err = r.Visit(func(info *resource.Info, err error) error { if err != nil { return err } mapping := info.ResourceMapping() if err := f.CanBeExposed(mapping.GroupVersionKind.GroupKind()); err != nil { return err } params := kubectl.MakeParams(cmd, names) name := info.Name if len(name) > validation.DNS952LabelMaxLength { name = name[:validation.DNS952LabelMaxLength] } params["default-name"] = name // For objects that need a pod selector, derive it from the exposed object in case a user // didn't explicitly specify one via --selector if s, found := params["selector"]; found && kubectl.IsZero(s) { s, err := f.MapBasedSelectorForObject(info.Object) if err != nil { return cmdutil.UsageError(cmd, fmt.Sprintf("couldn't retrieve selectors via --selector flag or introspection: %s", err)) } params["selector"] = s } // For objects that need a port, derive it from the exposed object in case a user // didn't explicitly specify one via --port if port, found := params["port"]; found && kubectl.IsZero(port) { ports, err := f.PortsForObject(info.Object) if err != nil { return cmdutil.UsageError(cmd, fmt.Sprintf("couldn't find port via --port flag or introspection: %s", err)) } switch len(ports) { case 0: return cmdutil.UsageError(cmd, "couldn't find port via --port flag or introspection") case 1: params["port"] = ports[0] default: params["ports"] = strings.Join(ports, ",") } } // Always try to derive protocols from the exposed object, may use // different protocols for different ports. if _, found := params["protocol"]; found { protocolsMap, err := f.ProtocolsForObject(info.Object) if err != nil { return cmdutil.UsageError(cmd, fmt.Sprintf("couldn't find protocol via introspection: %s", err)) } if protocols := kubectl.MakeProtocols(protocolsMap); !kubectl.IsZero(protocols) { params["protocols"] = protocols } } if kubectl.IsZero(params["labels"]) { labels, err := f.LabelsForObject(info.Object) if err != nil { return err } params["labels"] = kubectl.MakeLabels(labels) } if err = kubectl.ValidateParams(names, params); err != nil { return err } // Check for invalid flags used against the present generator. if err := kubectl.EnsureFlagsValid(cmd, generators, generatorName); err != nil { return err } // Generate new object object, err := generator.Generate(params) if err != nil { return err } if inline := cmdutil.GetFlagString(cmd, "overrides"); len(inline) > 0 { codec := runtime.NewCodec(f.JSONEncoder(), f.Decoder(true)) object, err = cmdutil.Merge(codec, object, inline, mapping.GroupVersionKind.Kind) if err != nil { return err } } resourceMapper := &resource.Mapper{ ObjectTyper: typer, RESTMapper: mapper, ClientMapper: resource.ClientMapperFunc(f.ClientForMapping), Decoder: f.Decoder(true), } info, err = resourceMapper.InfoForObject(object, nil) if err != nil { return err } if cmdutil.ShouldRecord(cmd, info) { if err := cmdutil.RecordChangeCause(object, f.Command()); err != nil { return err } } info.Refresh(object, true) if cmdutil.GetDryRunFlag(cmd) { return f.PrintObject(cmd, mapper, object, out) } if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info, f.JSONEncoder()); err != nil { return err } // Serialize the object with the annotation applied. object, err = resource.NewHelper(info.Client, info.Mapping).Create(namespace, false, object) if err != nil { return err } if len(cmdutil.GetFlagString(cmd, "output")) > 0 { return f.PrintObject(cmd, mapper, object, out) } cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, "exposed") return nil }) if err != nil { return err } return nil }
func RunEdit(f *cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args []string, filenames []string) error { var printer kubectl.ResourcePrinter var ext string switch format := cmdutil.GetFlagString(cmd, "output"); format { case "json": printer = &kubectl.JSONPrinter{} ext = ".json" case "yaml": printer = &kubectl.YAMLPrinter{} ext = ".yaml" 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), Decoder: f.Decoder(true), } r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, filenames...). ResourceTypeOrNameArgs(true, args...). Latest(). Flatten(). Do() err = r.Err() if err != nil { return err } infos, err := r.Infos() 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 } objs, err := resource.AsVersionedObjects(infos, defaultVersion.String(), encoder) if err != nil { return err } var ( windowsLineEndings = cmdutil.GetFlagBool(cmd, "windows-line-endings") edit = editor.NewDefaultEditor(f.EditorEnvs()) results = editResults{} original = []byte{} edited = []byte{} file string ) outter: for i := range objs { obj := objs[i] // some bookkeeping results.header.flush() containsError := false for { // generate the file to edit buf := &bytes.Buffer{} var w io.Writer = buf if windowsLineEndings { w = util.NewCRLFWriter(w) } if err := results.header.writeTo(w); err != nil { return preservedFile(err, results.file, errOut) } if !containsError { if err := printer.PrintObj(obj, 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-", path.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.") continue outter } // 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)) // Compare content without comments if bytes.Equal(stripComments(original), stripComments(edited)) { os.Remove(file) fmt.Fprintln(errOut, "Edit cancelled, no changes made.") continue outter } 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.") continue outter } 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 // put configuration annotation in "updates" if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), updates, encoder); err != nil { return preservedFile(err, file, errOut) } if cmdutil.ShouldRecord(cmd, updates) { err = cmdutil.RecordChangeCause(updates.Object, f.Command()) if err != nil { return err } } editedCopy := edited if editedCopy, err = runtime.Encode(encoder, updates.Object); err != nil { return preservedFile(err, file, errOut) } visitor := resource.NewFlattenListVisitor(updates, resourceMapper) // need to make sure the original namespace wasn't changed while editing if err = visitor.Visit(resource.RequireNamespace(cmdNamespace)); err != nil { return preservedFile(err, file, errOut) } // use strategic merge to create a patch originalJS, err := yaml.ToJSON(original) if err != nil { return preservedFile(err, file, errOut) } editedJS, err := yaml.ToJSON(editedCopy) if err != nil { return preservedFile(err, file, errOut) } patch, err := strategicpatch.CreateStrategicMergePatch(originalJS, editedJS, obj) // TODO: change all jsonmerge to strategicpatch // for checking preconditions preconditions := []jsonmerge.PreconditionFunc{} if err != nil { glog.V(4).Infof("Unable to calculate diff, no merge is possible: %v", err) return preservedFile(err, file, errOut) } else { preconditions = append(preconditions, jsonmerge.RequireKeyUnchanged("apiVersion")) preconditions = append(preconditions, jsonmerge.RequireKeyUnchanged("kind")) preconditions = append(preconditions, jsonmerge.RequireMetadataKeyUnchanged("name")) results.version = defaultVersion } if hold, msg := jsonmerge.TestPreconditionsHold(patch, preconditions); !hold { fmt.Fprintf(errOut, "error: %s\n", msg) return preservedFile(nil, file, errOut) } errorMsg := "" err = visitor.Visit(func(info *resource.Info, err error) error { patched, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, api.StrategicMergePatchType, patch) if err != nil { errorMsg = results.addError(err, info) return err } info.Refresh(patched, true) cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, "edited") return nil }) if err == nil { os.Remove(file) continue outter } // 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.Fprintln(errOut, errorMsg) fmt.Fprintf(errOut, "You can run `%s replace -f %s` to try this update again.\n", path.Base(os.Args[0]), file) continue outter } if results.notfound > 0 { fmt.Fprintln(errOut, errorMsg) fmt.Fprintf(errOut, "The edits you made on deleted resources have been saved to %q\n", file) continue outter } // validation error containsError = true } } return nil }
func RunReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *resource.FilenameOptions) error { if len(os.Args) > 1 && os.Args[1] == "update" { printDeprecationWarning("replace", "update") } schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir")) if err != nil { return err } cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err } force := cmdutil.GetFlagBool(cmd, "force") if cmdutil.IsFilenameEmpty(options.Filenames) { return cmdutil.UsageError(cmd, "Must specify --filename to replace") } shortOutput := cmdutil.GetFlagString(cmd, "output") == "name" if force { return forceReplace(f, out, cmd, args, shortOutput, options) } if cmdutil.GetFlagInt(cmd, "grace-period") >= 0 { return fmt.Errorf("--grace-period must have --force specified") } if cmdutil.GetFlagDuration(cmd, "timeout") != 0 { return fmt.Errorf("--timeout must have --force specified") } mapper, typer := f.Object() r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). Schema(schema). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, options). Flatten(). Do() err = r.Err() if err != nil { return err } return r.Visit(func(info *resource.Info, err error) error { if err != nil { return err } if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info, f.JSONEncoder()); err != nil { return cmdutil.AddSourceToErr("replacing", info.Source, err) } if cmdutil.ShouldRecord(cmd, info) { if err := cmdutil.RecordChangeCause(info.Object, f.Command()); err != nil { return cmdutil.AddSourceToErr("replacing", info.Source, err) } } // Serialize the object with the annotation applied. obj, err := resource.NewHelper(info.Client, info.Mapping).Replace(info.Namespace, info.Name, true, info.Object) if err != nil { return cmdutil.AddSourceToErr("replacing", info.Source, err) } info.Refresh(obj, true) f.PrintObjectSpecificMessage(obj, out) cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, false, "replaced") return nil }) }
func RunApply(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *ApplyOptions) error { shortOutput := cmdutil.GetFlagString(cmd, "output") == "name" schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir")) if err != nil { return err } cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err } mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). Schema(schema). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, options.Recursive, options.Filenames...). Flatten(). Do() err = r.Err() if err != nil { return err } encoder := f.JSONEncoder() decoder := f.Decoder(false) count := 0 err = r.Visit(func(info *resource.Info, err error) error { // In this method, info.Object contains the object retrieved from the server // and info.VersionedObject contains the object decoded from the input source. if err != nil { return err } // Get the modified configuration of the object. Embed the result // as an annotation in the modified configuration, so that it will appear // in the patch sent to the server. modified, err := kubectl.GetModifiedConfiguration(info, true, encoder) if err != nil { return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving modified configuration from:\n%v\nfor:", info), info.Source, err) } if err := info.Get(); err != nil { if !errors.IsNotFound(err) { return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%v\nfrom server for:", info), info.Source, err) } // Create the resource if it doesn't exist // First, update the annotation used by kubectl apply if err := kubectl.CreateApplyAnnotation(info, encoder); err != nil { return cmdutil.AddSourceToErr("creating", info.Source, err) } if cmdutil.ShouldRecord(cmd, info) { if err := cmdutil.RecordChangeCause(info.Object, f.Command()); err != nil { return cmdutil.AddSourceToErr("creating", info.Source, err) } } // Then create the resource and skip the three-way merge if err := createAndRefresh(info); err != nil { return cmdutil.AddSourceToErr("creating", info.Source, err) } count++ cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, "created") return nil } // Serialize the current configuration of the object from the server. current, err := runtime.Encode(encoder, info.Object) if err != nil { return cmdutil.AddSourceToErr(fmt.Sprintf("serializing current configuration from:\n%v\nfor:", info), info.Source, err) } // Retrieve the original configuration of the object from the annotation. original, err := kubectl.GetOriginalConfiguration(info) if err != nil { return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving original configuration from:\n%v\nfor:", info), info.Source, err) } // Create the versioned struct from the original from the server for // strategic patch. // TODO: Move all structs in apply to use raw data. Can be done once // builder has a RawResult method which delivers raw data instead of // internal objects. versionedObject, _, err := decoder.Decode(current, nil, nil) if err != nil { return cmdutil.AddSourceToErr(fmt.Sprintf("converting encoded server-side object back to versioned struct:\n%v\nfor:", info), info.Source, err) } // Compute a three way strategic merge patch to send to server. patch, err := strategicpatch.CreateThreeWayMergePatch(original, modified, current, versionedObject, true) if err != nil { format := "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfrom:\n%v\nfor:" return cmdutil.AddSourceToErr(fmt.Sprintf(format, original, modified, current, info), info.Source, err) } helper := resource.NewHelper(info.Client, info.Mapping) _, err = helper.Patch(info.Namespace, info.Name, api.StrategicMergePatchType, patch) if err != nil { return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patch, info), info.Source, err) } if cmdutil.ShouldRecord(cmd, info) { patch, err = cmdutil.ChangeResourcePatch(info, f.Command()) if err != nil { return err } _, err = helper.Patch(info.Namespace, info.Name, api.StrategicMergePatchType, patch) if err != nil { return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patch, info), info.Source, err) } } count++ cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, "configured") return nil }) if err != nil { return err } if count == 0 { return fmt.Errorf("no objects passed to apply") } return nil }