// RunEdit contains all the necessary functionality for the OpenShift cli edit command func RunEdit(fullName string, f *clientcmd.Factory, out io.Writer, cmd *cobra.Command, args []string, filenames util.StringList) 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, explicit, err := f.DefaultNamespace() if err != nil { return err } mapper, typer := f.Object() rmap := &resource.Mapper{ ObjectTyper: typer, RESTMapper: mapper, ClientMapper: f.ClientMapperForCommand(), } b := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(explicit, filenames...). //SelectorParam(selector). ResourceTypeOrNameArgs(true, args...). Latest() if err != nil { return err } clientConfig, err := f.ClientConfig() if err != nil { return err } r := b.Flatten().Do() infos, err := r.Infos() if err != nil { return err } defaultVersion := cmdutil.OutputVersion(cmd, clientConfig.Version) results := editResults{} for { obj, err := resource.AsVersionedObject(infos, false, defaultVersion) if err != nil { return preservedFile(err, results.file, cmd.Out()) } // 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{} if err := results.header.WriteTo(buf); err != nil { return preservedFile(err, results.file, cmd.Out()) } if err := printer.PrintObj(obj, buf); err != nil { return preservedFile(err, results.file, cmd.Out()) } original := buf.Bytes() // launch the editor edit := editor.NewDefaultEditor() edited, file, err := edit.LaunchTempFile("oc-edit-", ext, buf) if err != nil { return preservedFile(err, results.file, cmd.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, cmd.Out()) } if bytes.Equal(original, edited) { if len(results.edit) > 0 { preservedFile(nil, file, cmd.Out()) } else { os.Remove(file) } fmt.Fprintln(cmd.Out(), "Edit cancelled, no changes made.") return nil } if !lines { if len(results.edit) > 0 { preservedFile(nil, file, cmd.Out()) } else { os.Remove(file) } fmt.Fprintln(cmd.Out(), "Edit cancelled, saved file was empty.") return nil } results = editResults{ file: file, } // parse the edited file updates, err := rmap.InfoForData(edited, "edited-file") if err != nil { results.header.reasons = append(results.header.reasons, editReason{ head: fmt.Sprintf("The edited file had a syntax error: %v", err), }) continue } visitor := resource.NewFlattenListVisitor(updates, rmap) // 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, cmd.Out()) } // attempt to calculate a delta for merging conflicts delta, err := jsonmerge.NewDelta(original, edited) if err != nil { glog.V(4).Infof("Unable to calculate diff, no merge is possible: %v", err) delta = nil } else { delta.AddPreconditions(jsonmerge.RequireKeyUnchanged("apiVersion")) results.delta = delta results.version = defaultVersion } err = visitor.Visit(func(info *resource.Info) error { data, err := info.Mapping.Codec.Encode(info.Object) if err != nil { return err } updated, err := resource.NewHelper(info.Client, info.Mapping).Replace(info.Namespace, info.Name, false, data) if err != nil { fmt.Fprintln(cmd.Out(), results.AddError(err, info)) return nil } info.Refresh(updated, true) fmt.Fprintf(out, "%s/%s\n", info.Mapping.Resource, info.Name) return nil }) if err != nil { return preservedFile(err, file, cmd.Out()) } if results.retryable > 0 { fmt.Fprintf(cmd.Out(), "You can run `%s update -f %s` to try this update again.\n", fullName, file) return errExit } if results.conflict > 0 { fmt.Fprintf(cmd.Out(), "You must update your local resource version and run `%s update -f %s` to overwrite the remote changes.\n", fullName, file) return errExit } if len(results.edit) == 0 { if results.notfound == 0 { os.Remove(file) } else { fmt.Fprintf(cmd.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 nil }
// RunEdit contains all the necessary functionality for the OpenShift cli edit command. func (o *EditOptions) RunEdit() error { r := o.builder.Flatten().Do() results := editResults{} infos, err := r.Infos() if err != nil { return err } for { obj, err := resource.AsVersionedObject(infos, false, o.version) if err != nil { return preservedFile(err, results.file, o.out) } // 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{} if _, err := results.header.WriteTo(buf); err != nil { return preservedFile(err, results.file, o.out) } if err := o.printer.PrintObj(obj, buf); err != nil { return preservedFile(err, results.file, o.out) } original := buf.Bytes() // launch the editor edit := editor.NewDefaultEditor() edited, file, err := edit.LaunchTempFile("oc-edit-", o.ext, buf) if err != nil { return preservedFile(err, results.file, o.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, o.out) } if bytes.Equal(original, edited) { if len(results.edit) > 0 { preservedFile(nil, file, o.out) } else { os.Remove(file) } fmt.Fprintln(o.out, "Edit cancelled, no changes made.") return nil } if !lines { if len(results.edit) > 0 { preservedFile(nil, file, o.out) } else { os.Remove(file) } fmt.Fprintln(o.out, "Edit cancelled, saved file was empty.") return nil } results = editResults{ file: file, } // parse the edited file updates, err := o.rmap.InfoForData(edited, "edited-file") if err != nil { results.header.reasons = append(results.header.reasons, editReason{ head: fmt.Sprintf("The edited file had a syntax error: %v", err), }) continue } visitor := resource.NewFlattenListVisitor(updates, o.rmap) // need to make sure the original namespace wasn't changed while editing if err = visitor.Visit(resource.RequireNamespace(o.namespace)); err != nil { return preservedFile(err, file, o.out) } // attempt to calculate a delta for merging conflicts delta, err := jsonmerge.NewDelta(original, edited) if err != nil { glog.V(4).Infof("Unable to calculate diff, no merge is possible: %v", err) delta = nil } else { delta.AddPreconditions(jsonmerge.RequireKeyUnchanged("apiVersion")) results.delta = delta results.version = o.version } err = visitor.Visit(func(info *resource.Info, err error) error { if err != nil { return err } data, err := info.Mapping.Codec.Encode(info.Object) if err != nil { return err } updated, err := resource.NewHelper(info.Client, info.Mapping).Replace(info.Namespace, info.Name, false, data) if err != nil { fmt.Fprintln(o.out, results.AddError(err, info)) return nil } info.Refresh(updated, true) fmt.Fprintf(o.out, "%s/%s\n", info.Mapping.Resource, info.Name) return nil }) if err != nil { return preservedFile(err, file, o.out) } if results.retryable > 0 { fmt.Fprintf(o.out, "You can run `%s update -f %s` to try this update again.\n", o.fullName, file) return errExit } if results.conflict > 0 { fmt.Fprintf(o.out, "You must update your local resource version and run `%s update -f %s` to overwrite the remote changes.\n", o.fullName, file) return errExit } if len(results.edit) == 0 { if results.notfound == 0 { os.Remove(file) } else { fmt.Fprintf(o.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 } }