// applyPatch reads the latest version of the object, writes it to version, then attempts to merge // the changes onto it without conflict. If a conflict occurs jsonmerge.IsConflicting(err) is // true. The info object is mutated func applyPatch(delta *jsonmerge.Delta, info *resource.Info, version string) error { if err := info.Get(); err != nil { return patchError{err} } obj, err := resource.AsVersionedObject([]*resource.Info{info}, false, version) if err != nil { return patchError{err} } data, err := info.Mapping.Codec.Encode(obj) if err != nil { return patchError{err} } merged, err := delta.Apply(data) if err != nil { return patchError{err} } updated, err := resource.NewHelper(info.Client, info.Mapping).Replace(info.Namespace, info.Name, false, merged) if err != nil { return err } info.Refresh(updated, true) return nil }
func changeObjectsVersion(items []kruntime.Object) { if errs := kruntime.DecodeList(items, api.Scheme); len(errs) > 0 { log.Fatalf("Unable to decode Template objects: %v", errs) } for i, obj := range items { _, kind, err := api.Scheme.ObjectVersionAndKind(obj) if err != nil { glog.Infof("Template.Objects[%d]: Unable to determine version and kind: %v", i, err) continue } mapping, err := latest.RESTMapper.RESTMapping(kind, *outputVersion) if err != nil { glog.Infof("Template.Objects[%d]: Unable to get REST mappings: %v", err) continue } info := resource.Info{Object: obj, Mapping: mapping} outputObj, err := resource.AsVersionedObject([]*resource.Info{&info}, false, *outputVersion) if err != nil { glog.Infof("Template.Objects[%d]: Unable to convert: %v", err) continue } items[i] = outputObj } }
func (v *VolumeOptions) RunVolume(args []string) error { b := resource.NewBuilder(v.Mapper, v.Typer, resource.ClientMapperFunc(v.RESTClientFactory)). ContinueOnError(). NamespaceParam(v.DefaultNamespace).DefaultNamespace(). FilenameParam(v.Filenames...). SelectorParam(v.Selector). ResourceTypeOrNameArgs(v.All, args...). Flatten() one := false infos, err := b.Do().IntoSingular(&one).Infos() if err != nil { return err } skipped := 0 for _, info := range infos { ok, err := v.UpdatePodSpecForObject(info.Object, func(spec *kapi.PodSpec) error { var e error switch { case v.Add: e = v.addVolumeToSpec(spec) case v.Remove: e = v.removeVolumeFromSpec(spec) case v.List: e = v.listVolumeForSpec(spec, info) } return e }) if !ok { skipped++ continue } if err != nil { fmt.Fprintf(v.Writer, "error: %s/%s %v\n", info.Mapping.Resource, info.Name, err) continue } } if one && skipped == len(infos) { return fmt.Errorf("the %s %s is not a pod or does not have a pod template", infos[0].Mapping.Resource, infos[0].Name) } if v.List { return nil } objects, err := resource.AsVersionedObject(infos, false, v.OutputVersion) if err != nil { return err } if len(v.Output) != 0 { p, _, err := kubectl.GetPrinter(v.Output, "") if err != nil { return err } return p.PrintObj(objects, v.Writer) } failed := false for _, info := range infos { data, err := info.Mapping.Codec.Encode(info.Object) if err != nil { fmt.Fprintf(v.Writer, "Error: %v\n", err) failed = true continue } obj, err := resource.NewHelper(info.Client, info.Mapping).Update(info.Namespace, info.Name, true, data) if err != nil { handlePodUpdateError(v.Writer, err, "volume") failed = true continue } info.Refresh(obj, true) fmt.Fprintf(v.Writer, "%s/%s\n", info.Mapping.Resource, info.Name) } if failed { return errExit } return nil }
// RunEnv contains all the necessary functionality for the OpenShift cli env command func RunEnv(f *clientcmd.Factory, in io.Reader, out io.Writer, cmd *cobra.Command, args []string, envParams, filenames kutil.StringList) error { resources, envArgs := []string{}, []string{} first := true for _, s := range args { isEnv := strings.Contains(s, "=") || strings.HasSuffix(s, "-") switch { case first && isEnv: first = false fallthrough case !first && isEnv: envArgs = append(envArgs, s) case first && !isEnv: resources = append(resources, s) case !first && !isEnv: return cmdutil.UsageError(cmd, "all resources must be specified before environment changes: %s", s) } } if len(filenames) == 0 && len(resources) < 1 { return cmdutil.UsageError(cmd, "one or more resources must be specified as <resource> <name> or <resource>/<name>") } containerMatch := cmdutil.GetFlagString(cmd, "containers") list := cmdutil.GetFlagBool(cmd, "list") selector := cmdutil.GetFlagString(cmd, "selector") all := cmdutil.GetFlagBool(cmd, "all") //overwrite := cmdutil.GetFlagBool(cmd, "overwrite") resourceVersion := cmdutil.GetFlagString(cmd, "resource-version") outputFormat := cmdutil.GetFlagString(cmd, "output") if list && len(outputFormat) > 0 { return cmdutil.UsageError(cmd, "--list and --output may not be specified together") } clientConfig, err := f.ClientConfig() if err != nil { return err } outputVersion := cmdutil.OutputVersion(cmd, clientConfig.Version) cmdNamespace, err := f.DefaultNamespace() if err != nil { return err } env, remove, err := parseEnv(append(envParams, envArgs...), in) if err != nil { return err } mapper, typer := f.Object() b := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(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 cmdutil.UsageError(cmd, "--resource-version may only be used with a single resource") } 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("the %s %s is not a pod or does not have a pod template", infos[0].Mapping.Resource, infos[0].Name) } if list { return nil } objects, err := resource.AsVersionedObject(infos, false, outputVersion) if err != nil { return err } if len(outputFormat) != 0 { p, _, err := kubectl.GetPrinter(outputFormat, "") if err != nil { return err } return p.PrintObj(objects, out) } failed := false for _, info := range infos { data, err := info.Mapping.Codec.Encode(info.Object) if err != nil { fmt.Fprintf(cmd.Out(), "Error: %v\n", err) failed = true continue } obj, err := resource.NewHelper(info.Client, info.Mapping).Update(info.Namespace, info.Name, true, data) if err != nil { handlePodUpdateError(cmd.Out(), err, "environment variables") failed = true continue } info.Refresh(obj, true) fmt.Fprintf(out, "%s/%s\n", info.Mapping.Resource, info.Name) } if failed { return errExit } return nil }
// 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, 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(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 }
// RunGet implements the generic Get command // TODO: convert all direct flag accessors to a struct and pass that instead of cmd func RunGet(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error { selector := cmdutil.GetFlagString(cmd, "selector") allNamespaces := cmdutil.GetFlagBool(cmd, "all-namespaces") mapper, typer := f.Object() cmdNamespace, _, err := f.DefaultNamespace() if err != nil { return err } if len(args) == 0 { fmt.Fprint(out, ` You must specify the type of resource to get. Valid resource types include: * pods (aka 'po') * replicationcontrollers (aka 'rc') * services * nodes (aka 'no') * events (aka 'ev') * secrets * limits * persistentVolumes (aka 'pv') * persistentVolumeClaims (aka 'pvc') * quota `) return errors.New("Required resource not specified.") } // handle watch separately since we cannot watch multiple resource types isWatch, isWatchOnly := cmdutil.GetFlagBool(cmd, "watch"), cmdutil.GetFlagBool(cmd, "watch-only") if isWatch || isWatchOnly { r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces). SelectorParam(selector). ResourceTypeOrNameArgs(true, args...). SingleResourceType(). Do() if err != nil { return err } mapping, err := r.ResourceMapping() if err != nil { return err } printer, err := f.PrinterForMapping(cmd, mapping, allNamespaces) if err != nil { return err } obj, err := r.Object() if err != nil { return err } rv, err := mapping.MetadataAccessor.ResourceVersion(obj) if err != nil { return err } // print the current object if !isWatchOnly { if err := printer.PrintObj(obj, out); err != nil { return fmt.Errorf("unable to output the provided object: %v", err) } } // print watched changes w, err := r.Watch(rv) if err != nil { return err } kubectl.WatchLoop(w, func(e watch.Event) error { return printer.PrintObj(e.Object, out) }) return nil } b := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces). SelectorParam(selector). ResourceTypeOrNameArgs(true, args...). ContinueOnError(). Latest() printer, generic, err := cmdutil.PrinterForCommand(cmd) if err != nil { return err } if generic { clientConfig, err := f.ClientConfig() if err != nil { return err } defaultVersion := clientConfig.Version singular := false r := b.Flatten().Do() infos, err := r.IntoSingular(&singular).Infos() if err != nil { return err } // the outermost object will be converted to the output-version, but inner // objects can use their mappings version := cmdutil.OutputVersion(cmd, defaultVersion) obj, err := resource.AsVersionedObject(infos, !singular, version) if err != nil { return err } return printer.PrintObj(obj, out) } // use the default printer for each object return b.Do().Visit(func(r *resource.Info) error { printer, err := f.PrinterForMapping(cmd, r.Mapping, allNamespaces) if err != nil { return err } return printer.PrintObj(r.Object, out) }) }
func RunExport(f *clientcmd.Factory, exporter Exporter, in io.Reader, out io.Writer, cmd *cobra.Command, args []string, filenames util.StringList) error { selector := cmdutil.GetFlagString(cmd, "selector") all := cmdutil.GetFlagBool(cmd, "all") exact := cmdutil.GetFlagBool(cmd, "exact") asTemplate := cmdutil.GetFlagString(cmd, "as-template") raw := cmdutil.GetFlagBool(cmd, "raw") if exact && raw { return cmdutil.UsageError(cmd, "--exact and --raw may not both be specified") } clientConfig, err := f.ClientConfig() if err != nil { return err } outputVersion := cmdutil.OutputVersion(cmd, clientConfig.Version) cmdNamespace, explicit, err := f.DefaultNamespace() if err != nil { return err } mapper, typer := f.Object() b := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(explicit, filenames...). SelectorParam(selector). ResourceTypeOrNameArgs(all, args...). Flatten() one := false infos, err := b.Do().IntoSingular(&one).Infos() if err != nil { return err } if len(infos) == 0 { return fmt.Errorf("no resources found - nothing to export") } if !raw { newInfos := []*resource.Info{} errs := []error{} for _, info := range infos { if err := exporter.Export(info.Object, exact); err != nil { if err == ErrExportOmit { continue } errs = append(errs, err) } newInfos = append(newInfos, info) } if len(errs) > 0 { return utilerrors.NewAggregate(errs) } infos = newInfos } var result runtime.Object if len(asTemplate) > 0 { objects, err := resource.AsVersionedObjects(infos, outputVersion) if err != nil { return err } template := &templateapi.Template{ Objects: objects, } template.Name = asTemplate result, err = kapi.Scheme.ConvertToVersion(template, outputVersion) if err != nil { return err } } else { object, err := resource.AsVersionedObject(infos, !one, outputVersion) if err != nil { return err } result = object } // use YAML as the default format outputFormat := cmdutil.GetFlagString(cmd, "output") templateFile := cmdutil.GetFlagString(cmd, "template") if len(outputFormat) == 0 && len(templateFile) != 0 { outputFormat = "template" } if len(outputFormat) == 0 { outputFormat = "yaml" } p, _, err := kubectl.GetPrinter(outputFormat, templateFile) if err != nil { return err } return p.PrintObj(result, out) }
// RunGet implements the generic Get command // TODO: convert all direct flag accessors to a struct and pass that instead of cmd func RunGet(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error { selector := cmdutil.GetFlagString(cmd, "selector") mapper, typer := f.Object() cmdNamespace, err := f.DefaultNamespace() if err != nil { return err } // handle watch separately since we cannot watch multiple resource types isWatch, isWatchOnly := cmdutil.GetFlagBool(cmd, "watch"), cmdutil.GetFlagBool(cmd, "watch-only") if isWatch || isWatchOnly { r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). NamespaceParam(cmdNamespace).DefaultNamespace(). SelectorParam(selector). ResourceTypeOrNameArgs(true, args...). SingleResourceType(). Do() if err != nil { return err } mapping, err := r.ResourceMapping() if err != nil { return err } printer, err := f.PrinterForMapping(cmd, mapping) if err != nil { return err } obj, err := r.Object() if err != nil { return err } rv, err := mapping.MetadataAccessor.ResourceVersion(obj) if err != nil { return err } // print the current object if !isWatchOnly { if err := printer.PrintObj(obj, out); err != nil { return fmt.Errorf("unable to output the provided object: %v", err) } } // print watched changes w, err := r.Watch(rv) if err != nil { return err } kubectl.WatchLoop(w, func(e watch.Event) error { return printer.PrintObj(e.Object, out) }) return nil } b := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). NamespaceParam(cmdNamespace).DefaultNamespace(). SelectorParam(selector). ResourceTypeOrNameArgs(true, args...). ContinueOnError(). Latest() printer, generic, err := cmdutil.PrinterForCommand(cmd) if err != nil { return err } if generic { clientConfig, err := f.ClientConfig() if err != nil { return err } defaultVersion := clientConfig.Version singular := false r := b.Flatten().Do() infos, err := r.IntoSingular(&singular).Infos() if err != nil { return err } // the outermost object will be converted to the output-version, but inner // objects can use their mappings version := cmdutil.OutputVersion(cmd, defaultVersion) obj, err := resource.AsVersionedObject(infos, !singular, version) if err != nil { return err } return printer.PrintObj(obj, out) } // use the default printer for each object return b.Do().Visit(func(r *resource.Info) error { printer, err := f.PrinterForMapping(cmd, r.Mapping) if err != nil { return err } return printer.PrintObj(r.Object, out) }) }