diff --git a/pkg/kubectl/apply.go b/pkg/kubectl/apply.go index 9edbd6fa737..3149bcb29da 100644 --- a/pkg/kubectl/apply.go +++ b/pkg/kubectl/apply.go @@ -21,6 +21,7 @@ import ( "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/runtime" ) type debugError interface { @@ -80,7 +81,7 @@ func SetOriginalConfiguration(info *resource.Info, original []byte) error { // If annotate is true, it embeds the result as an anotation in the modified // configuration. If an object was read from the command input, it will use that // version of the object. Otherwise, it will use the version from the server. -func GetModifiedConfiguration(info *resource.Info, annotate bool) ([]byte, error) { +func GetModifiedConfiguration(info *resource.Info, annotate bool, codec runtime.Encoder) ([]byte, error) { // First serialize the object without the annotation to prevent recursion, // then add that serialization to it as the annotation and serialize it again. var modified []byte @@ -100,6 +101,8 @@ func GetModifiedConfiguration(info *resource.Info, annotate bool) ([]byte, error original := annotations[LastAppliedConfigAnnotation] delete(annotations, LastAppliedConfigAnnotation) accessor.SetAnnotations(annotations) + // TODO: this needs to be abstracted - there should be no assumption that versioned object + // can be marshalled to JSON. modified, err = json.Marshal(info.VersionedObject) if err != nil { return nil, err @@ -108,6 +111,8 @@ func GetModifiedConfiguration(info *resource.Info, annotate bool) ([]byte, error if annotate { annotations[LastAppliedConfigAnnotation] = string(modified) accessor.SetAnnotations(annotations) + // TODO: this needs to be abstracted - there should be no assumption that versioned object + // can be marshalled to JSON. modified, err = json.Marshal(info.VersionedObject) if err != nil { return nil, err @@ -136,7 +141,7 @@ func GetModifiedConfiguration(info *resource.Info, annotate bool) ([]byte, error return nil, err } - modified, err = info.Mapping.Codec.Encode(info.Object) + modified, err = runtime.Encode(codec, info.Object) if err != nil { return nil, err } @@ -147,7 +152,7 @@ func GetModifiedConfiguration(info *resource.Info, annotate bool) ([]byte, error return nil, err } - modified, err = info.Mapping.Codec.Encode(info.Object) + modified, err = runtime.Encode(codec, info.Object) if err != nil { return nil, err } @@ -165,17 +170,17 @@ func GetModifiedConfiguration(info *resource.Info, annotate bool) ([]byte, error // UpdateApplyAnnotation calls CreateApplyAnnotation if the last applied // configuration annotation is already present. Otherwise, it does nothing. -func UpdateApplyAnnotation(info *resource.Info) error { +func UpdateApplyAnnotation(info *resource.Info, codec runtime.Encoder) error { if original, err := GetOriginalConfiguration(info); err != nil || len(original) <= 0 { return err } - return CreateApplyAnnotation(info) + return CreateApplyAnnotation(info, codec) } // CreateApplyAnnotation gets the modified configuration of the object, // without embedding it again, and then sets it on the object as the annotation. -func CreateApplyAnnotation(info *resource.Info) error { - modified, err := GetModifiedConfiguration(info, false) +func CreateApplyAnnotation(info *resource.Info, codec runtime.Encoder) error { + modified, err := GetModifiedConfiguration(info, false, codec) if err != nil { return err } @@ -184,9 +189,9 @@ func CreateApplyAnnotation(info *resource.Info) error { // Create the annotation used by kubectl apply only when createAnnotation is true // Otherwise, only update the annotation when it already exists -func CreateOrUpdateAnnotation(createAnnotation bool, info *resource.Info) error { +func CreateOrUpdateAnnotation(createAnnotation bool, info *resource.Info, codec runtime.Encoder) error { if createAnnotation { - return CreateApplyAnnotation(info) + return CreateApplyAnnotation(info, codec) } - return UpdateApplyAnnotation(info) + return UpdateApplyAnnotation(info, codec) } diff --git a/pkg/kubectl/cmd/annotate.go b/pkg/kubectl/cmd/annotate.go index 3f82db4ff52..95869a3d534 100644 --- a/pkg/kubectl/cmd/annotate.go +++ b/pkg/kubectl/cmd/annotate.go @@ -151,7 +151,7 @@ func (o *AnnotateOptions) Complete(f *cmdutil.Factory, out io.Writer, cmd *cobra } mapper, typer := f.Object() - o.builder = resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). + o.builder = resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace(). FilenameParam(enforceNamespace, o.filenames...). @@ -211,7 +211,7 @@ func (o AnnotateOptions) RunAnnotate() error { } mapping := info.ResourceMapping() - client, err := o.f.RESTClient(mapping) + client, err := o.f.ClientForMapping(mapping) if err != nil { return err } diff --git a/pkg/kubectl/cmd/apply.go b/pkg/kubectl/cmd/apply.go index a13165fc823..ef63b040092 100644 --- a/pkg/kubectl/cmd/apply.go +++ b/pkg/kubectl/cmd/apply.go @@ -27,6 +27,7 @@ import ( "k8s.io/kubernetes/pkg/kubectl" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/util/strategicpatch" ) @@ -92,7 +93,7 @@ func RunApply(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *Ap } mapper, typer := f.Object() - r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). + r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). Schema(schema). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). @@ -104,6 +105,8 @@ func RunApply(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *Ap return err } + encoder := f.JSONEncoder() + count := 0 err = r.Visit(func(info *resource.Info, err error) error { // In this method, info.Object contains the object retrieved from the server @@ -115,7 +118,7 @@ func RunApply(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *Ap // 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) + 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) } @@ -126,7 +129,7 @@ func RunApply(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *Ap } // Create the resource if it doesn't exist // First, update the annotation used by kubectl apply - if err := kubectl.CreateApplyAnnotation(info); err != nil { + if err := kubectl.CreateApplyAnnotation(info, encoder); err != nil { return cmdutil.AddSourceToErr("creating", info.Source, err) } // Then create the resource and skip the three-way merge @@ -139,7 +142,7 @@ func RunApply(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *Ap } // Serialize the current configuration of the object from the server. - current, err := info.Mapping.Codec.Encode(info.Object) + 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) } diff --git a/pkg/kubectl/cmd/autoscale.go b/pkg/kubectl/cmd/autoscale.go index b8a14827faa..6647db01180 100644 --- a/pkg/kubectl/cmd/autoscale.go +++ b/pkg/kubectl/cmd/autoscale.go @@ -79,7 +79,7 @@ func RunAutoscale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args [] } mapper, typer := f.Object() - r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). + r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace(). FilenameParam(enforceNamespace, filenames...). @@ -129,7 +129,12 @@ func RunAutoscale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args [] return err } - resourceMapper := &resource.Mapper{ObjectTyper: typer, RESTMapper: mapper, ClientMapper: f.ClientMapperForCommand()} + resourceMapper := &resource.Mapper{ + ObjectTyper: typer, + RESTMapper: mapper, + ClientMapper: resource.ClientMapperFunc(f.ClientForMapping), + Decoder: f.Decoder(true), + } hpa, err := resourceMapper.InfoForObject(object) if err != nil { return err @@ -139,7 +144,7 @@ func RunAutoscale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args [] return f.PrintObject(cmd, object, out) } - if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), hpa); err != nil { + if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), hpa, f.JSONEncoder()); err != nil { return err } diff --git a/pkg/kubectl/cmd/clusterinfo.go b/pkg/kubectl/cmd/clusterinfo.go index a5adb2d33da..c76e28f7c63 100644 --- a/pkg/kubectl/cmd/clusterinfo.go +++ b/pkg/kubectl/cmd/clusterinfo.go @@ -45,25 +45,25 @@ func NewCmdClusterInfo(f *cmdutil.Factory, out io.Writer) *cobra.Command { return cmd } -func RunClusterInfo(factory *cmdutil.Factory, out io.Writer, cmd *cobra.Command) error { +func RunClusterInfo(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command) error { if len(os.Args) > 1 && os.Args[1] == "clusterinfo" { printDeprecationWarning("cluster-info", "clusterinfo") } - client, err := factory.ClientConfig() + client, err := f.ClientConfig() if err != nil { return err } printService(out, "Kubernetes master", client.Host) - mapper, typer := factory.Object() + mapper, typer := f.Object() cmdNamespace := cmdutil.GetFlagString(cmd, "namespace") if cmdNamespace == "" { cmdNamespace = api.NamespaceSystem } // TODO use generalized labels once they are implemented (#341) - b := resource.NewBuilder(mapper, typer, factory.ClientMapperForCommand()). + b := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). NamespaceParam(cmdNamespace).DefaultNamespace(). SelectorParam("kubernetes.io/cluster-service=true"). ResourceTypeOrNameArgs(false, []string{"services"}...). diff --git a/pkg/kubectl/cmd/cmd_test.go b/pkg/kubectl/cmd/cmd_test.go index 2fff13e9b91..4db40989c67 100644 --- a/pkg/kubectl/cmd/cmd_test.go +++ b/pkg/kubectl/cmd/cmd_test.go @@ -38,6 +38,7 @@ import ( cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/runtime/serializer" "k8s.io/kubernetes/pkg/util" ) @@ -100,22 +101,21 @@ func versionErrIfFalse(b bool) error { } var validVersion = testapi.Default.GroupVersion().Version -var internalGV = unversioned.GroupVersion{Group: "apitest", Version: ""} +var internalGV = unversioned.GroupVersion{Group: "apitest", Version: runtime.APIVersionInternal} var unlikelyGV = unversioned.GroupVersion{Group: "apitest", Version: "unlikelyversion"} var validVersionGV = unversioned.GroupVersion{Group: "apitest", Version: validVersion} func newExternalScheme() (*runtime.Scheme, meta.RESTMapper, runtime.Codec) { scheme := runtime.NewScheme() - scheme.AddInternalGroupVersion(internalGV) scheme.AddKnownTypeWithName(internalGV.WithKind("Type"), &internalType{}) scheme.AddKnownTypeWithName(unlikelyGV.WithKind("Type"), &externalType{}) //This tests that kubectl will not confuse the external scheme with the internal scheme, even when they accidentally have versions of the same name. scheme.AddKnownTypeWithName(validVersionGV.WithKind("Type"), &ExternalType2{}) - codec := runtime.CodecFor(scheme, unlikelyGV) + codecs := serializer.NewCodecFactory(scheme) + codec := codecs.LegacyCodec(unlikelyGV) mapper := meta.NewDefaultRESTMapper([]unversioned.GroupVersion{unlikelyGV, validVersionGV}, func(version unversioned.GroupVersion) (*meta.VersionInterfaces, error) { return &meta.VersionInterfaces{ - Codec: runtime.CodecFor(scheme, version), ObjectConvertor: scheme, MetadataAccessor: meta.NewAccessor(), }, versionErrIfFalse(version == validVersionGV || version == unlikelyGV) @@ -183,9 +183,15 @@ func NewTestFactory() (*cmdutil.Factory, *testFactory, runtime.Codec) { Object: func() (meta.RESTMapper, runtime.ObjectTyper) { return t.Mapper, t.Typer }, - RESTClient: func(*meta.RESTMapping) (resource.RESTClient, error) { + ClientForMapping: func(*meta.RESTMapping) (resource.RESTClient, error) { return t.Client, t.Err }, + Decoder: func(bool) runtime.Decoder { + return codec + }, + JSONEncoder: func() runtime.Encoder { + return codec + }, Describer: func(*meta.RESTMapping) (kubectl.Describer, error) { return t.Describer, t.Err }, @@ -209,7 +215,7 @@ func NewMixedFactory(apiClient resource.RESTClient) (*cmdutil.Factory, *testFact f.Object = func() (meta.RESTMapper, runtime.ObjectTyper) { return meta.MultiRESTMapper{t.Mapper, testapi.Default.RESTMapper()}, runtime.MultiObjectTyper{t.Typer, api.Scheme} } - f.RESTClient = func(m *meta.RESTMapping) (resource.RESTClient, error) { + f.ClientForMapping = func(m *meta.RESTMapping) (resource.RESTClient, error) { if m.ObjectConvertor == api.Scheme { return apiClient, t.Err } @@ -235,9 +241,15 @@ func NewAPIFactory() (*cmdutil.Factory, *testFactory, runtime.Codec) { c.ExtensionsClient.Client = fakeClient.Client return c, t.Err }, - RESTClient: func(*meta.RESTMapping) (resource.RESTClient, error) { + ClientForMapping: func(*meta.RESTMapping) (resource.RESTClient, error) { return t.Client, t.Err }, + Decoder: func(bool) runtime.Decoder { + return testapi.Default.Codec() + }, + JSONEncoder: func() runtime.Encoder { + return testapi.Default.Codec() + }, Describer: func(*meta.RESTMapping) (kubectl.Describer, error) { return t.Describer, t.Err }, @@ -303,7 +315,7 @@ func stringBody(body string) io.ReadCloser { // mapping := &meta.RESTMapping{ // APIVersion: version, // } -// c, err := f.RESTClient(mapping) +// c, err := f.ClientForMapping(mapping) // if err != nil { // t.Errorf("unexpected error: %v", err) // } diff --git a/pkg/kubectl/cmd/convert.go b/pkg/kubectl/cmd/convert.go index 694a005cd95..e7e04d5b99d 100644 --- a/pkg/kubectl/cmd/convert.go +++ b/pkg/kubectl/cmd/convert.go @@ -26,6 +26,7 @@ import ( "k8s.io/kubernetes/pkg/kubectl" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/runtime" "github.com/spf13/cobra" ) @@ -87,6 +88,7 @@ type ConvertOptions struct { filenames []string local bool + encoder runtime.Encoder out io.Writer printer kubectl.ResourcePrinter @@ -105,11 +107,12 @@ func (o *ConvertOptions) Complete(f *cmdutil.Factory, out io.Writer, cmd *cobra. // build the builder mapper, typer := f.Object() + clientMapper := resource.ClientMapperFunc(f.ClientForMapping) if o.local { fmt.Fprintln(out, "running in local mode...") - o.builder = resource.NewBuilder(mapper, typer, f.NilClientMapperForCommand()) + o.builder = resource.NewBuilder(mapper, typer, resource.DisabledClientForMapping{clientMapper}, f.Decoder(true)) } else { - o.builder = resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()) + o.builder = resource.NewBuilder(mapper, typer, clientMapper, f.Decoder(true)) schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir")) if err != nil { return err @@ -136,6 +139,7 @@ func (o *ConvertOptions) Complete(f *cmdutil.Factory, out io.Writer, cmd *cobra. outputFormat = "template" } } + o.encoder = f.JSONEncoder() o.printer, _, err = kubectl.GetPrinter(outputFormat, templateFile) if err != nil { return err @@ -151,7 +155,7 @@ func (o *ConvertOptions) RunConvert() error { return err } - objects, err := resource.AsVersionedObject(infos, false, o.outputVersion.String()) + objects, err := resource.AsVersionedObject(infos, false, o.outputVersion.String(), o.encoder) if err != nil { return err } diff --git a/pkg/kubectl/cmd/create.go b/pkg/kubectl/cmd/create.go index 6b887a07627..10b30ba4ef3 100644 --- a/pkg/kubectl/cmd/create.go +++ b/pkg/kubectl/cmd/create.go @@ -99,7 +99,7 @@ func RunCreate(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *C } mapper, typer := f.Object() - r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). + r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). Schema(schema). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). @@ -116,7 +116,7 @@ func RunCreate(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *C if err != nil { return err } - if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info); err != nil { + if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info, f.JSONEncoder()); err != nil { return cmdutil.AddSourceToErr("creating", info.Source, err) } @@ -218,16 +218,20 @@ func RunCreateSubcommand(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, if err != nil { return err } - client, err := f.RESTClient(mapping) + client, err := f.ClientForMapping(mapping) if err != nil { return err } - resourceMapper := &resource.Mapper{ObjectTyper: typer, RESTMapper: mapper, ClientMapper: f.ClientMapperForCommand()} + resourceMapper := &resource.Mapper{ + ObjectTyper: typer, + RESTMapper: mapper, + ClientMapper: resource.ClientMapperFunc(f.ClientForMapping), + } info, err := resourceMapper.InfoForObject(obj) if err != nil { return err } - if err := kubectl.UpdateApplyAnnotation(info); err != nil { + if err := kubectl.UpdateApplyAnnotation(info, f.JSONEncoder()); err != nil { return err } if !options.DryRun { diff --git a/pkg/kubectl/cmd/delete.go b/pkg/kubectl/cmd/delete.go index 6bac41cdec6..94973d6e584 100644 --- a/pkg/kubectl/cmd/delete.go +++ b/pkg/kubectl/cmd/delete.go @@ -108,7 +108,7 @@ func RunDelete(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str } deleteAll := cmdutil.GetFlagBool(cmd, "all") mapper, typer := f.Object() - r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). + r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, options.Filenames...). diff --git a/pkg/kubectl/cmd/describe.go b/pkg/kubectl/cmd/describe.go index 7fa4dedf196..3ea83ecdc4f 100644 --- a/pkg/kubectl/cmd/describe.go +++ b/pkg/kubectl/cmd/describe.go @@ -106,7 +106,7 @@ func RunDescribe(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []s } mapper, typer := f.Object() - r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). + r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, options.Filenames...). @@ -147,7 +147,7 @@ func RunDescribe(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []s } func DescribeMatchingResources(mapper meta.RESTMapper, typer runtime.ObjectTyper, f *cmdutil.Factory, namespace, rsrc, prefix string, out io.Writer, originalError error) error { - r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). + r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). NamespaceParam(namespace).DefaultNamespace(). ResourceTypeOrNameArgs(true, rsrc). SingleResourceType(). diff --git a/pkg/kubectl/cmd/drain.go b/pkg/kubectl/cmd/drain.go index db09620e1e1..8c07a18281a 100644 --- a/pkg/kubectl/cmd/drain.go +++ b/pkg/kubectl/cmd/drain.go @@ -209,8 +209,7 @@ func (o *DrainOptions) getPodsForDeletion() ([]api.Pod, error) { if found { // Now verify that the specified creator actually exists. var sr api.SerializedReference - err := api.Scheme.DecodeInto([]byte(creatorRef), &sr) - if err != nil { + if err := runtime.DecodeInto(o.factory.Decoder(true), []byte(creatorRef), &sr); err != nil { return pods, err } if sr.Reference.Kind == "ReplicationController" { diff --git a/pkg/kubectl/cmd/edit.go b/pkg/kubectl/cmd/edit.go index 35036206043..e98407bf846 100644 --- a/pkg/kubectl/cmd/edit.go +++ b/pkg/kubectl/cmd/edit.go @@ -23,7 +23,7 @@ import ( "fmt" "io" "os" - "runtime" + gruntime "runtime" "strings" "k8s.io/kubernetes/pkg/api" @@ -34,6 +34,7 @@ import ( "k8s.io/kubernetes/pkg/kubectl/cmd/util/editor" "k8s.io/kubernetes/pkg/kubectl/cmd/util/jsonmerge" "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/util" "k8s.io/kubernetes/pkg/util/strategicpatch" "k8s.io/kubernetes/pkg/util/yaml" @@ -94,7 +95,7 @@ func NewCmdEdit(f *cmdutil.Factory, out io.Writer) *cobra.Command { kubectl.AddJsonFilenameFlag(cmd, &filenames, usage) cmd.Flags().StringP("output", "o", "yaml", "Output format. One of: yaml|json.") cmd.Flags().String("output-version", "", "Output the formatted object with the given version (default api-version).") - cmd.Flags().Bool("windows-line-endings", runtime.GOOS == "windows", "Use Windows line-endings (default Unix line-endings)") + cmd.Flags().Bool("windows-line-endings", gruntime.GOOS == "windows", "Use Windows line-endings (default Unix line-endings)") cmdutil.AddApplyAnnotationFlags(cmd) return cmd } @@ -119,13 +120,14 @@ func RunEdit(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin } mapper, typer := f.Object() - rmap := &resource.Mapper{ + resourceMapper := &resource.Mapper{ ObjectTyper: typer, RESTMapper: mapper, - ClientMapper: f.ClientMapperForCommand(), + ClientMapper: resource.ClientMapperFunc(f.ClientForMapping), + Decoder: f.Decoder(true), } - r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). + r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, filenames...). ResourceTypeOrNameArgs(true, args...). @@ -147,6 +149,8 @@ func RunEdit(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin return err } + encoder := f.JSONEncoder() + windowsLineEndings := cmdutil.GetFlagBool(cmd, "windows-line-endings") edit := editor.NewDefaultEditor(f.EditorEnvs()) defaultVersion, err := cmdutil.OutputVersion(cmd, clientConfig.GroupVersion) @@ -155,7 +159,7 @@ func RunEdit(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin } results := editResults{} for { - objs, err := resource.AsVersionedObjects(infos, defaultVersion.String()) + objs, err := resource.AsVersionedObjects(infos, defaultVersion.String(), encoder) if err != nil { return preservedFile(err, results.file, out) } @@ -219,21 +223,21 @@ func RunEdit(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin } // parse the edited file - updates, err := rmap.InfoForData(edited, "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); err != nil { + if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), updates, encoder); err != nil { return preservedFile(err, file, out) } // encode updates back to "edited" since we'll only generate patch from "edited" - if edited, err = updates.Mapping.Codec.Encode(updates.Object); err != nil { + if edited, err = runtime.Encode(encoder, updates.Object); err != nil { return preservedFile(err, file, out) } - visitor := resource.NewFlattenListVisitor(updates, rmap) + 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 { diff --git a/pkg/kubectl/cmd/expose.go b/pkg/kubectl/cmd/expose.go index 382a6c098e4..05469713164 100644 --- a/pkg/kubectl/cmd/expose.go +++ b/pkg/kubectl/cmd/expose.go @@ -26,6 +26,7 @@ import ( "k8s.io/kubernetes/pkg/kubectl" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/util/validation" ) @@ -104,7 +105,7 @@ func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str } mapper, typer := f.Object() - r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). + r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace(). FilenameParam(enforceNamespace, options.Filenames...). @@ -192,13 +193,19 @@ func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str } if inline := cmdutil.GetFlagString(cmd, "overrides"); len(inline) > 0 { - object, err = cmdutil.Merge(object, inline, mapping.GroupVersionKind.Kind) + 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: f.ClientMapperForCommand()} + resourceMapper := &resource.Mapper{ + ObjectTyper: typer, + RESTMapper: mapper, + ClientMapper: resource.ClientMapperFunc(f.ClientForMapping), + Decoder: f.Decoder(true), + } info, err = resourceMapper.InfoForObject(object) if err != nil { return err @@ -207,7 +214,7 @@ func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str if cmdutil.GetFlagBool(cmd, "dry-run") { return f.PrintObject(cmd, object, out) } - if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info); err != nil { + if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info, f.JSONEncoder()); err != nil { return err } diff --git a/pkg/kubectl/cmd/get.go b/pkg/kubectl/cmd/get.go index e4c3f009b2e..284e3555052 100644 --- a/pkg/kubectl/cmd/get.go +++ b/pkg/kubectl/cmd/get.go @@ -137,7 +137,7 @@ func RunGet(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string // 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()). + r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces). FilenameParam(enforceNamespace, options.Filenames...). SelectorParam(selector). @@ -192,7 +192,7 @@ func RunGet(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string return nil } - b := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). + b := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces). FilenameParam(enforceNamespace, options.Filenames...). SelectorParam(selector). @@ -224,7 +224,7 @@ func RunGet(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string if err != nil { return err } - obj, err := resource.AsVersionedObject(infos, !singular, version.String()) + obj, err := resource.AsVersionedObject(infos, !singular, version.String(), f.JSONEncoder()) if err != nil { return err } @@ -244,7 +244,8 @@ func RunGet(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string sorting, err := cmd.Flags().GetString("sort-by") var sorter *kubectl.RuntimeSort if err == nil && len(sorting) > 0 { - if sorter, err = kubectl.SortObjects(objs, sorting); err != nil { + // TODO: questionable + if sorter, err = kubectl.SortObjects(f.Decoder(true), objs, sorting); err != nil { return err } } diff --git a/pkg/kubectl/cmd/get_test.go b/pkg/kubectl/cmd/get_test.go index 7cedaf51786..d8ba37e2b91 100644 --- a/pkg/kubectl/cmd/get_test.go +++ b/pkg/kubectl/cmd/get_test.go @@ -177,7 +177,7 @@ func TestGetUnknownSchemaObjectListGeneric(t *testing.T) { }, } for k, test := range testCases { - apiCodec := runtime.CodecFor(api.Scheme, *testapi.Default.GroupVersion()) + apiCodec := testapi.Default.Codec() regularClient := &fake.RESTClient{ Codec: apiCodec, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { @@ -482,7 +482,7 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) { if err != nil { t.Fatalf("unexpected error: %v", err) } - if errs := runtime.DecodeList(list, api.Scheme); len(errs) > 0 { + if errs := runtime.DecodeList(list, codec); len(errs) > 0 { t.Fatalf("unexpected error: %v", errs) } if err := meta.SetList(out, list); err != nil { diff --git a/pkg/kubectl/cmd/label.go b/pkg/kubectl/cmd/label.go index d2952d201fa..23f7b605644 100644 --- a/pkg/kubectl/cmd/label.go +++ b/pkg/kubectl/cmd/label.go @@ -201,7 +201,7 @@ func RunLabel(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri return cmdutil.UsageError(cmd, err.Error()) } mapper, typer := f.Object() - b := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). + b := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, options.Filenames...). @@ -264,7 +264,7 @@ func RunLabel(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri } mapping := info.ResourceMapping() - client, err := f.RESTClient(mapping) + client, err := f.ClientForMapping(mapping) if err != nil { return err } diff --git a/pkg/kubectl/cmd/logs.go b/pkg/kubectl/cmd/logs.go index 5f1b47c6a52..369d41d8a96 100644 --- a/pkg/kubectl/cmd/logs.go +++ b/pkg/kubectl/cmd/logs.go @@ -59,6 +59,7 @@ type LogsOptions struct { Mapper meta.RESTMapper Typer runtime.ObjectTyper ClientMapper resource.ClientMapper + Decoder runtime.Decoder LogsForObject func(object, options runtime.Object) (*client.Request, error) @@ -151,7 +152,8 @@ func (o *LogsOptions) Complete(f *cmdutil.Factory, out io.Writer, cmd *cobra.Com o.Options = logOptions o.Mapper, o.Typer = f.Object() - o.ClientMapper = f.ClientMapperForCommand() + o.Decoder = f.Decoder(true) + o.ClientMapper = resource.ClientMapperFunc(f.ClientForMapping) o.LogsForObject = f.LogsForObject o.Out = out @@ -176,7 +178,7 @@ func (o LogsOptions) Validate() error { // RunLogs retrieves a pod log func (o LogsOptions) RunLogs() (int64, error) { - infos, err := resource.NewBuilder(o.Mapper, o.Typer, o.ClientMapper). + infos, err := resource.NewBuilder(o.Mapper, o.Typer, o.ClientMapper, o.Decoder). NamespaceParam(o.Namespace).DefaultNamespace(). ResourceNames("pods", o.ResourceArg). SingleResourceType(). diff --git a/pkg/kubectl/cmd/patch.go b/pkg/kubectl/cmd/patch.go index 9a971d72978..b537adaa186 100644 --- a/pkg/kubectl/cmd/patch.go +++ b/pkg/kubectl/cmd/patch.go @@ -92,7 +92,7 @@ func RunPatch(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri } mapper, typer := f.Object() - r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). + r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, options.Filenames...). @@ -114,7 +114,7 @@ func RunPatch(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri info := infos[0] name, namespace := info.Name, info.Namespace mapping := info.ResourceMapping() - client, err := f.RESTClient(mapping) + client, err := f.ClientForMapping(mapping) if err != nil { return err } diff --git a/pkg/kubectl/cmd/replace.go b/pkg/kubectl/cmd/replace.go index a076e91f9ab..226e5c434d0 100644 --- a/pkg/kubectl/cmd/replace.go +++ b/pkg/kubectl/cmd/replace.go @@ -112,7 +112,7 @@ func RunReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []st } mapper, typer := f.Object() - r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). + r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). Schema(schema). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). @@ -129,7 +129,7 @@ func RunReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []st return err } - if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info); err != nil { + if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info, f.JSONEncoder()); err != nil { return cmdutil.AddSourceToErr("replacing", info.Source, err) } @@ -174,7 +174,7 @@ func forceReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args [] } mapper, typer := f.Object() - r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). + r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, options.Filenames...). @@ -198,7 +198,7 @@ func forceReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args [] return err } - r = resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). + r = resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). Schema(schema). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). @@ -216,7 +216,7 @@ func forceReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args [] return err } - if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info); err != nil { + if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info, f.JSONEncoder()); err != nil { return err } diff --git a/pkg/kubectl/cmd/rollingupdate.go b/pkg/kubectl/cmd/rollingupdate.go index fb11b9a3cf7..bf4015b6e62 100644 --- a/pkg/kubectl/cmd/rollingupdate.go +++ b/pkg/kubectl/cmd/rollingupdate.go @@ -29,7 +29,6 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/meta" - "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/kubectl" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -198,7 +197,7 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg return err } - request := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). + request := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). Schema(schema). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, filename). @@ -385,12 +384,9 @@ func isReplicasDefaulted(info *resource.Info) bool { // was unable to recover versioned info return false } - - switch info.Mapping.GroupVersionKind.GroupVersion() { - case unversioned.GroupVersion{Version: "v1"}: - if rc, ok := info.VersionedObject.(*v1.ReplicationController); ok { - return rc.Spec.Replicas == nil - } + switch t := info.VersionedObject.(type) { + case *v1.ReplicationController: + return t.Spec.Replicas == nil } return false } diff --git a/pkg/kubectl/cmd/run.go b/pkg/kubectl/cmd/run.go index a87efcee0d6..8affcbda870 100644 --- a/pkg/kubectl/cmd/run.go +++ b/pkg/kubectl/cmd/run.go @@ -236,7 +236,7 @@ func Run(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cob return err } _, typer := f.Object() - r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). + r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace(). ResourceNames(mapping.Resource, name). @@ -409,7 +409,8 @@ func createGeneratedObject(f *cmdutil.Factory, cmd *cobra.Command, generator kub } if len(overrides) > 0 { - obj, err = cmdutil.Merge(obj, overrides, groupVersionKind.Kind) + codec := runtime.NewCodec(f.JSONEncoder(), f.Decoder(true)) + obj, err = cmdutil.Merge(codec, obj, overrides, groupVersionKind.Kind) if err != nil { return nil, "", nil, nil, err } @@ -419,20 +420,25 @@ func createGeneratedObject(f *cmdutil.Factory, cmd *cobra.Command, generator kub if err != nil { return nil, "", nil, nil, err } - client, err := f.RESTClient(mapping) + client, err := f.ClientForMapping(mapping) if err != nil { return nil, "", nil, nil, err } // TODO: extract this flag to a central location, when such a location exists. if !cmdutil.GetFlagBool(cmd, "dry-run") { - resourceMapper := &resource.Mapper{ObjectTyper: typer, RESTMapper: mapper, ClientMapper: f.ClientMapperForCommand()} + resourceMapper := &resource.Mapper{ + ObjectTyper: typer, + RESTMapper: mapper, + ClientMapper: resource.ClientMapperFunc(f.ClientForMapping), + Decoder: f.Decoder(true), + } info, err := resourceMapper.InfoForObject(obj) if err != nil { return nil, "", nil, nil, err } - if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info); err != nil { + if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info, f.JSONEncoder()); err != nil { return nil, "", nil, nil, err } diff --git a/pkg/kubectl/cmd/scale.go b/pkg/kubectl/cmd/scale.go index 18e9fedbffd..2637fa2b0af 100644 --- a/pkg/kubectl/cmd/scale.go +++ b/pkg/kubectl/cmd/scale.go @@ -105,7 +105,7 @@ func RunScale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri } mapper, typer := f.Object() - r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). + r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, options.Filenames...). diff --git a/pkg/kubectl/cmd/stop.go b/pkg/kubectl/cmd/stop.go index c6673fa481e..6069866171a 100644 --- a/pkg/kubectl/cmd/stop.go +++ b/pkg/kubectl/cmd/stop.go @@ -85,7 +85,7 @@ func RunStop(f *cmdutil.Factory, cmd *cobra.Command, args []string, out io.Write } mapper, typer := f.Object() - r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). + r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). ResourceTypeOrNameArgs(false, args...). diff --git a/pkg/kubectl/cmd/util/factory.go b/pkg/kubectl/cmd/util/factory.go index 66f9ebfab12..3d52e44eba1 100644 --- a/pkg/kubectl/cmd/util/factory.go +++ b/pkg/kubectl/cmd/util/factory.go @@ -45,6 +45,7 @@ import ( "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/runtime/serializer/json" "k8s.io/kubernetes/pkg/util" ) @@ -63,13 +64,19 @@ type Factory struct { // Returns interfaces for dealing with arbitrary runtime.Objects. Object func() (meta.RESTMapper, runtime.ObjectTyper) + // Returns interfaces for decoding objects - if toInternal is set, decoded objects will be converted + // into their internal form (if possible). Eventually the internal form will be removed as an option, + // and only versioned objects will be returned. + Decoder func(toInternal bool) runtime.Decoder + // Returns an encoder capable of encoding a provided object into JSON in the default desired version. + JSONEncoder func() runtime.Encoder // Returns a client for accessing Kubernetes resources or an error. Client func() (*client.Client, error) // Returns a client.Config for accessing the Kubernetes server. ClientConfig func() (*client.Config, error) // Returns a RESTClient for working with the specified RESTMapping or an error. This is intended // for working with arbitrary resources and is not guaranteed to point to a Kubernetes APIServer. - RESTClient func(mapping *meta.RESTMapping) (resource.RESTClient, error) + ClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error) // Returns a Describer for displaying the specified RESTMapping type or an error. Describer func(mapping *meta.RESTMapping) (kubectl.Describer, error) // Returns a Printer for formatting objects of the given type or an error. @@ -185,7 +192,7 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory { ClientConfig: func() (*client.Config, error) { return clients.ClientConfigForVersion(nil) }, - RESTClient: func(mapping *meta.RESTMapping) (resource.RESTClient, error) { + ClientForMapping: func(mapping *meta.RESTMapping) (resource.RESTClient, error) { mappingVersion := mapping.GroupVersionKind.GroupVersion() client, err := clients.ClientForVersion(&mappingVersion) if err != nil { @@ -210,6 +217,15 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory { } return nil, fmt.Errorf("no description has been implemented for %q", mapping.GroupVersionKind.Kind) }, + Decoder: func(toInternal bool) runtime.Decoder { + if toInternal { + return api.Codecs.UniversalDecoder() + } + return api.Codecs.UniversalDeserializer() + }, + JSONEncoder: func() runtime.Encoder { + return api.Codecs.LegacyCodec(registered.EnabledVersions()...) + }, Printer: func(mapping *meta.RESTMapping, noHeaders, withNamespace bool, wide bool, showAll bool, absoluteTimestamps bool, columnLabels []string) (kubectl.ResourcePrinter, error) { return kubectl.NewHumanReadablePrinter(noHeaders, withNamespace, wide, showAll, absoluteTimestamps, columnLabels), nil }, @@ -531,7 +547,7 @@ func getSchemaAndValidate(c schemaClient, data []byte, prefix, groupVersion, cac } func (c *clientSwaggerSchema) ValidateBytes(data []byte) error { - gvk, err := runtime.UnstructuredJSONScheme.DataKind(data) + gvk, err := json.DefaultMetaFactory.Interpret(data) if err != nil { return err } @@ -663,24 +679,9 @@ func (f *Factory) PrinterForMapping(cmd *cobra.Command, mapping *meta.RESTMappin return printer, nil } -// ClientMapperForCommand returns a ClientMapper for the factory. -func (f *Factory) ClientMapperForCommand() resource.ClientMapper { - return resource.ClientMapperFunc(func(mapping *meta.RESTMapping) (resource.RESTClient, error) { - return f.RESTClient(mapping) - }) -} - -// NilClientMapperForCommand returns a ClientMapper which always returns nil. -// When command is running locally and client isn't needed, this mapper can be parsed to NewBuilder. -func (f *Factory) NilClientMapperForCommand() resource.ClientMapper { - return resource.ClientMapperFunc(func(mapping *meta.RESTMapping) (resource.RESTClient, error) { - return nil, nil - }) -} - // One stop shopping for a Builder func (f *Factory) NewBuilder() *resource.Builder { mapper, typer := f.Object() - return resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()) + return resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)) } diff --git a/pkg/kubectl/cmd/util/factory_test.go b/pkg/kubectl/cmd/util/factory_test.go index 7818e7b9049..be9484fae3c 100644 --- a/pkg/kubectl/cmd/util/factory_test.go +++ b/pkg/kubectl/cmd/util/factory_test.go @@ -245,7 +245,7 @@ func TestValidateCachesSchema(t *testing.T) { os.RemoveAll(dir) obj := &api.Pod{} - data, err := testapi.Default.Codec().Encode(obj) + data, err := runtime.Encode(testapi.Default.Codec(), obj) if err != nil { t.Errorf("unexpected error: %v", err) t.FailNow() diff --git a/pkg/kubectl/cmd/util/helpers.go b/pkg/kubectl/cmd/util/helpers.go index 67e3b01c018..72327493460 100644 --- a/pkg/kubectl/cmd/util/helpers.go +++ b/pkg/kubectl/cmd/util/helpers.go @@ -18,7 +18,6 @@ package util import ( "bytes" - "encoding/json" "fmt" "io" "io/ioutil" @@ -28,10 +27,8 @@ import ( "strings" "time" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/unversioned" - "k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/resource" @@ -371,38 +368,11 @@ func ReadConfigDataFromLocation(location string) ([]byte, error) { } } -func Merge(dst runtime.Object, fragment, kind string) (runtime.Object, error) { - // Ok, this is a little hairy, we'd rather not force the user to specify a kind for their JSON - // So we pull it into a map, add the Kind field, and then reserialize. - // We also pull the apiVersion for proper parsing - var intermediate interface{} - if err := json.Unmarshal([]byte(fragment), &intermediate); err != nil { - return nil, err - } - dataMap, ok := intermediate.(map[string]interface{}) - if !ok { - return nil, fmt.Errorf("Expected a map, found something else: %s", fragment) - } - version, found := dataMap["apiVersion"] - if !found { - return nil, fmt.Errorf("Inline JSON requires an apiVersion field") - } - versionString, ok := version.(string) - if !ok { - return nil, fmt.Errorf("apiVersion must be a string") - } - groupVersion, err := unversioned.ParseGroupVersion(versionString) - if err != nil { - return nil, err - } - - i, err := registered.GroupOrDie(api.GroupName).InterfacesFor(groupVersion) - if err != nil { - return nil, err - } - +// Merge requires JSON serialization +// TODO: merge assumes JSON serialization, and does not properly abstract API retrieval +func Merge(codec runtime.Codec, dst runtime.Object, fragment, kind string) (runtime.Object, error) { // encode dst into versioned json and apply fragment directly too it - target, err := i.Codec.Encode(dst) + target, err := runtime.Encode(codec, dst) if err != nil { return nil, err } @@ -410,7 +380,7 @@ func Merge(dst runtime.Object, fragment, kind string) (runtime.Object, error) { if err != nil { return nil, err } - out, err := i.Codec.Decode(patched) + out, err := runtime.Decode(codec, patched) if err != nil { return nil, err } @@ -443,7 +413,7 @@ func DumpReaderToFile(reader io.Reader, filename string) error { } // UpdateObject updates resource object with updateFn -func UpdateObject(info *resource.Info, updateFn func(runtime.Object) error) (runtime.Object, error) { +func UpdateObject(info *resource.Info, codec runtime.Codec, updateFn func(runtime.Object) error) (runtime.Object, error) { helper := resource.NewHelper(info.Client, info.Mapping) if err := updateFn(info.Object); err != nil { @@ -451,7 +421,7 @@ func UpdateObject(info *resource.Info, updateFn func(runtime.Object) error) (run } // Update the annotation used by kubectl apply - if err := kubectl.UpdateApplyAnnotation(info); err != nil { + if err := kubectl.UpdateApplyAnnotation(info, codec); err != nil { return nil, err } diff --git a/pkg/kubectl/cmd/util/helpers_test.go b/pkg/kubectl/cmd/util/helpers_test.go index 3c878b4a01c..598c3e961e2 100644 --- a/pkg/kubectl/cmd/util/helpers_test.go +++ b/pkg/kubectl/cmd/util/helpers_test.go @@ -183,7 +183,7 @@ func TestMerge(t *testing.T) { } for i, test := range tests { - out, err := Merge(test.obj, test.fragment, test.kind) + out, err := Merge(testapi.Default.Codec(), test.obj, test.fragment, test.kind) if !test.expectErr { if err != nil { t.Errorf("testcase[%d], unexpected error: %v", i, err) diff --git a/pkg/kubectl/custom_column_printer.go b/pkg/kubectl/custom_column_printer.go index 7d91691f734..431a114c748 100644 --- a/pkg/kubectl/custom_column_printer.go +++ b/pkg/kubectl/custom_column_printer.go @@ -26,7 +26,6 @@ import ( "strings" "text/tabwriter" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/util/jsonpath" @@ -152,6 +151,7 @@ type Column struct { // of data from templates specified in the `Columns` array type CustomColumnsPrinter struct { Columns []Column + Decoder runtime.Decoder } func (s *CustomColumnsPrinter) PrintObj(obj runtime.Object, out io.Writer) error { @@ -192,7 +192,7 @@ func (s *CustomColumnsPrinter) printOneObject(obj runtime.Object, parsers []*jso switch u := obj.(type) { case *runtime.Unknown: var err error - if obj, err = api.Codec.Decode(u.RawJSON); err != nil { + if obj, _, err = s.Decoder.Decode(u.RawJSON, nil, nil); err != nil { return err } } diff --git a/pkg/kubectl/resource/builder.go b/pkg/kubectl/resource/builder.go index a9dd31267e2..61b0f113184 100644 --- a/pkg/kubectl/resource/builder.go +++ b/pkg/kubectl/resource/builder.go @@ -80,9 +80,9 @@ type resourceTuple struct { } // NewBuilder creates a builder that operates on generic objects. -func NewBuilder(mapper meta.RESTMapper, typer runtime.ObjectTyper, clientMapper ClientMapper) *Builder { +func NewBuilder(mapper meta.RESTMapper, typer runtime.ObjectTyper, clientMapper ClientMapper, decoder runtime.Decoder) *Builder { return &Builder{ - mapper: &Mapper{typer, mapper, clientMapper}, + mapper: &Mapper{typer, mapper, clientMapper, decoder}, requireObject: true, } } diff --git a/pkg/kubectl/resource/builder_test.go b/pkg/kubectl/resource/builder_test.go index 2ee364b05c5..1fad662b63f 100644 --- a/pkg/kubectl/resource/builder_test.go +++ b/pkg/kubectl/resource/builder_test.go @@ -34,6 +34,7 @@ import ( "k8s.io/kubernetes/pkg/api/testapi" apitesting "k8s.io/kubernetes/pkg/api/testing" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/client/unversioned/fake" "k8s.io/kubernetes/pkg/runtime" utilerrors "k8s.io/kubernetes/pkg/util/errors" @@ -177,9 +178,9 @@ func (v *testVisitor) Objects() []runtime.Object { return objects } -func TestPathBuilder(t *testing.T) { - b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient()). - FilenameParam(false, "../../../examples/guestbook/redis-master-controller.yaml") +func TestPathBuilderAndVersionedObjectNotDefaulted(t *testing.T) { + b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). + FilenameParam(false, "../../../docs/user-guide/update-demo/kitten-rc.yaml") test := &testVisitor{} singular := false @@ -190,9 +191,14 @@ func TestPathBuilder(t *testing.T) { } info := test.Infos[0] - if info.Name != "redis-master" || info.Namespace != "" || info.Object == nil { + if info.Name != "update-demo-kitten" || info.Namespace != "" || info.Object == nil { t.Errorf("unexpected info: %#v", info) } + version, ok := info.VersionedObject.(*v1.ReplicationController) + // versioned object does not have defaulting applied + if info.VersionedObject == nil || !ok || version.Spec.Replicas != nil { + t.Errorf("unexpected versioned object: %#v", info.VersionedObject) + } } func TestNodeBuilder(t *testing.T) { @@ -212,7 +218,7 @@ func TestNodeBuilder(t *testing.T) { w.Write([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), node))) }() - b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient()). + b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). NamespaceParam("test").Stream(r, "STDIN") test := &testVisitor{} @@ -228,7 +234,7 @@ func TestNodeBuilder(t *testing.T) { } func TestPathBuilderWithMultiple(t *testing.T) { - b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient()). + b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). FilenameParam(false, "../../../examples/guestbook/redis-master-controller.yaml"). FilenameParam(false, "../../../examples/pod"). NamespaceParam("test").DefaultNamespace() @@ -252,7 +258,7 @@ func TestPathBuilderWithMultiple(t *testing.T) { } func TestDirectoryBuilder(t *testing.T) { - b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient()). + b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). FilenameParam(false, "../../../examples/guestbook"). NamespaceParam("test").DefaultNamespace() @@ -283,7 +289,7 @@ func TestNamespaceOverride(t *testing.T) { // TODO: Uncomment when fix #19254 // defer s.Close() - b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient()). + b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). FilenameParam(false, s.URL). NamespaceParam("test") @@ -294,7 +300,7 @@ func TestNamespaceOverride(t *testing.T) { t.Fatalf("unexpected response: %v %#v", err, test.Infos) } - b = NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient()). + b = NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). FilenameParam(true, s.URL). NamespaceParam("test") @@ -314,7 +320,7 @@ func TestURLBuilder(t *testing.T) { // TODO: Uncomment when fix #19254 // defer s.Close() - b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient()). + b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). FilenameParam(false, s.URL). NamespaceParam("test") @@ -339,7 +345,7 @@ func TestURLBuilderRequireNamespace(t *testing.T) { // TODO: Uncomment when fix #19254 // defer s.Close() - b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient()). + b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). FilenameParam(false, s.URL). NamespaceParam("test").RequireNamespace() @@ -356,7 +362,7 @@ func TestResourceByName(t *testing.T) { pods, _ := testData() b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClientWith("", t, map[string]string{ "/namespaces/test/pods/foo": runtime.EncodeOrDie(testapi.Default.Codec(), &pods.Items[0]), - })). + }), testapi.Default.Codec()). NamespaceParam("test") test := &testVisitor{} @@ -392,7 +398,7 @@ func TestMultipleResourceByTheSameName(t *testing.T) { "/namespaces/test/pods/baz": runtime.EncodeOrDie(testapi.Default.Codec(), &pods.Items[1]), "/namespaces/test/services/foo": runtime.EncodeOrDie(testapi.Default.Codec(), &svcs.Items[0]), "/namespaces/test/services/baz": runtime.EncodeOrDie(testapi.Default.Codec(), &svcs.Items[0]), - })). + }), testapi.Default.Codec()). NamespaceParam("test") test := &testVisitor{} @@ -422,7 +428,7 @@ func TestResourceNames(t *testing.T) { b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClientWith("", t, map[string]string{ "/namespaces/test/pods/foo": runtime.EncodeOrDie(testapi.Default.Codec(), &pods.Items[0]), "/namespaces/test/services/baz": runtime.EncodeOrDie(testapi.Default.Codec(), &svc.Items[0]), - })). + }), testapi.Default.Codec()). NamespaceParam("test") test := &testVisitor{} @@ -446,7 +452,7 @@ func TestResourceNames(t *testing.T) { } func TestResourceByNameWithoutRequireObject(t *testing.T) { - b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClientWith("", t, map[string]string{})). + b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClientWith("", t, map[string]string{}), testapi.Default.Codec()). NamespaceParam("test") test := &testVisitor{} @@ -482,7 +488,7 @@ func TestResourceByNameAndEmptySelector(t *testing.T) { pods, _ := testData() b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClientWith("", t, map[string]string{ "/namespaces/test/pods/foo": runtime.EncodeOrDie(testapi.Default.Codec(), &pods.Items[0]), - })). + }), testapi.Default.Codec()). NamespaceParam("test"). SelectorParam(""). ResourceTypeOrNameArgs(true, "pods", "foo") @@ -511,7 +517,7 @@ func TestSelector(t *testing.T) { b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClientWith("", t, map[string]string{ "/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(testapi.Default.Codec(), pods), "/namespaces/test/services?" + labelKey + "=a%3Db": runtime.EncodeOrDie(testapi.Default.Codec(), svc), - })). + }), testapi.Default.Codec()). SelectorParam("a=b"). NamespaceParam("test"). Flatten() @@ -539,7 +545,7 @@ func TestSelector(t *testing.T) { } func TestSelectorRequiresKnownTypes(t *testing.T) { - b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient()). + b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). SelectorParam("a=b"). NamespaceParam("test"). ResourceTypes("unknown") @@ -550,7 +556,7 @@ func TestSelectorRequiresKnownTypes(t *testing.T) { } func TestSingleResourceType(t *testing.T) { - b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient()). + b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). SelectorParam("a=b"). SingleResourceType(). ResourceTypeOrNameArgs(true, "pods,services") @@ -620,7 +626,7 @@ func TestResourceTuple(t *testing.T) { } } - b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClientWith(k, t, expectedRequests)). + b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClientWith(k, t, expectedRequests), testapi.Default.Codec()). NamespaceParam("test").DefaultNamespace(). ResourceTypeOrNameArgs(true, testCase.args...).RequireObject(requireObject) @@ -651,7 +657,7 @@ func TestResourceTuple(t *testing.T) { func TestStream(t *testing.T) { r, pods, rc := streamTestData() - b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient()). + b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). NamespaceParam("test").Stream(r, "STDIN").Flatten() test := &testVisitor{} @@ -668,7 +674,7 @@ func TestStream(t *testing.T) { func TestYAMLStream(t *testing.T) { r, pods, rc := streamYAMLTestData() - b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient()). + b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). NamespaceParam("test").Stream(r, "STDIN").Flatten() test := &testVisitor{} @@ -685,7 +691,7 @@ func TestYAMLStream(t *testing.T) { func TestMultipleObject(t *testing.T) { r, pods, svc := streamTestData() - obj, err := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient()). + obj, err := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). NamespaceParam("test").Stream(r, "STDIN").Flatten(). Do().Object() @@ -707,7 +713,7 @@ func TestMultipleObject(t *testing.T) { func TestContinueOnErrorVisitor(t *testing.T) { r, _, _ := streamTestData() - req := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient()). + req := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). ContinueOnError(). NamespaceParam("test").Stream(r, "STDIN").Flatten(). Do() @@ -736,7 +742,7 @@ func TestContinueOnErrorVisitor(t *testing.T) { } func TestSingularObject(t *testing.T) { - obj, err := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient()). + obj, err := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). NamespaceParam("test").DefaultNamespace(). FilenameParam(false, "../../../examples/guestbook/redis-master-controller.yaml"). Flatten(). @@ -756,7 +762,7 @@ func TestSingularObject(t *testing.T) { } func TestSingularObjectNoExtension(t *testing.T) { - obj, err := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient()). + obj, err := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). NamespaceParam("test").DefaultNamespace(). FilenameParam(false, "../../../examples/pod"). Flatten(). @@ -778,7 +784,7 @@ func TestSingularObjectNoExtension(t *testing.T) { func TestSingularRootScopedObject(t *testing.T) { node := &api.Node{ObjectMeta: api.ObjectMeta{Name: "test"}, Spec: api.NodeSpec{ExternalID: "test"}} r := streamTestObject(node) - infos, err := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient()). + infos, err := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). NamespaceParam("test").DefaultNamespace(). Stream(r, "STDIN"). Flatten(). @@ -805,7 +811,7 @@ func TestListObject(t *testing.T) { labelKey := unversioned.LabelSelectorQueryParam(testapi.Default.GroupVersion().String()) b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClientWith("", t, map[string]string{ "/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(testapi.Default.Codec(), pods), - })). + }), testapi.Default.Codec()). SelectorParam("a=b"). NamespaceParam("test"). ResourceTypeOrNameArgs(true, "pods"). @@ -839,7 +845,7 @@ func TestListObjectWithDifferentVersions(t *testing.T) { obj, err := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClientWith("", t, map[string]string{ "/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(testapi.Default.Codec(), pods), "/namespaces/test/services?" + labelKey + "=a%3Db": runtime.EncodeOrDie(testapi.Default.Codec(), svc), - })). + }), testapi.Default.Codec()). SelectorParam("a=b"). NamespaceParam("test"). ResourceTypeOrNameArgs(true, "pods,services"). @@ -867,7 +873,7 @@ func TestWatch(t *testing.T) { Type: watch.Added, Object: &svc.Items[0], }), - })). + }), testapi.Default.Codec()). NamespaceParam("test").DefaultNamespace(). FilenameParam(false, "../../../examples/guestbook/redis-master-service.yaml").Flatten(). Do().Watch("12") @@ -894,7 +900,7 @@ func TestWatch(t *testing.T) { } func TestWatchMultipleError(t *testing.T) { - _, err := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient()). + _, err := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). NamespaceParam("test").DefaultNamespace(). FilenameParam(false, "../../../examples/guestbook/redis-master-controller.yaml").Flatten(). FilenameParam(false, "../../../examples/guestbook/redis-master-controller.yaml").Flatten(). @@ -921,7 +927,7 @@ func TestLatest(t *testing.T) { "/namespaces/test/pods/foo": runtime.EncodeOrDie(testapi.Default.Codec(), newPod), "/namespaces/test/pods/bar": runtime.EncodeOrDie(testapi.Default.Codec(), newPod2), "/namespaces/test/services/baz": runtime.EncodeOrDie(testapi.Default.Codec(), newSvc), - })). + }), testapi.Default.Codec()). NamespaceParam("other").Stream(r, "STDIN").Flatten().Latest() test := &testVisitor{} @@ -953,7 +959,7 @@ func TestReceiveMultipleErrors(t *testing.T) { w2.Write([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), &svc.Items[0]))) }() - b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient()). + b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). Stream(r, "1").Stream(r2, "2"). ContinueOnError() @@ -997,7 +1003,7 @@ func TestReplaceAliases(t *testing.T) { }, } - b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient()) + b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()) for _, test := range tests { replaced := b.replaceAliases(test.arg) diff --git a/pkg/kubectl/resource/helper.go b/pkg/kubectl/resource/helper.go index 85f7bc1946c..fcce5b0cae6 100644 --- a/pkg/kubectl/resource/helper.go +++ b/pkg/kubectl/resource/helper.go @@ -33,8 +33,6 @@ type Helper struct { Resource string // A RESTClient capable of mutating this resource. RESTClient RESTClient - // A codec for decoding and encoding objects of this resource type. - Codec runtime.Codec // An interface for reading or writing the resource version of this // type. Versioner runtime.ResourceVersioner @@ -45,9 +43,8 @@ type Helper struct { // NewHelper creates a Helper from a ResourceMapping func NewHelper(client RESTClient, mapping *meta.RESTMapping) *Helper { return &Helper{ - RESTClient: client, Resource: mapping.Resource, - Codec: mapping.Codec, + RESTClient: client, Versioner: mapping.MetadataAccessor, NamespaceScoped: mapping.Scope.Name() == meta.RESTScopeNameNamespace, } diff --git a/pkg/kubectl/resource/helper_test.go b/pkg/kubectl/resource/helper_test.go index 2bdc977b0ff..541daf880f4 100644 --- a/pkg/kubectl/resource/helper_test.go +++ b/pkg/kubectl/resource/helper_test.go @@ -189,7 +189,6 @@ func TestHelperCreate(t *testing.T) { } modifier := &Helper{ RESTClient: client, - Codec: testapi.Default.Codec(), Versioner: testapi.Default.MetadataAccessor(), NamespaceScoped: true, } @@ -441,7 +440,6 @@ func TestHelperReplace(t *testing.T) { } modifier := &Helper{ RESTClient: client, - Codec: testapi.Default.Codec(), Versioner: testapi.Default.MetadataAccessor(), NamespaceScoped: true, } diff --git a/pkg/kubectl/resource/interfaces.go b/pkg/kubectl/resource/interfaces.go index 3d64609984a..54d20dfbfff 100644 --- a/pkg/kubectl/resource/interfaces.go +++ b/pkg/kubectl/resource/interfaces.go @@ -32,7 +32,7 @@ type RESTClient interface { Put() *client.Request } -// ClientMapper retrieves a client object for a given mapping +// ClientMapper abstracts retrieving a Client for mapped objects. type ClientMapper interface { ClientForMapping(mapping *meta.RESTMapping) (RESTClient, error) } diff --git a/pkg/kubectl/resource/mapper.go b/pkg/kubectl/resource/mapper.go index 2c61325bddc..0839ca4a8ea 100644 --- a/pkg/kubectl/resource/mapper.go +++ b/pkg/kubectl/resource/mapper.go @@ -20,69 +20,63 @@ import ( "fmt" "reflect" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/meta" - "k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/runtime" - "k8s.io/kubernetes/pkg/util/yaml" ) +// DisabledClientForMapping allows callers to avoid allowing remote calls when handling +// resources. +type DisabledClientForMapping struct { + ClientMapper +} + +func (f DisabledClientForMapping) ClientForMapping(mapping *meta.RESTMapping) (RESTClient, error) { + return nil, nil +} + // Mapper is a convenience struct for holding references to the three interfaces // needed to create Info for arbitrary objects. type Mapper struct { runtime.ObjectTyper meta.RESTMapper ClientMapper + runtime.Decoder } // InfoForData creates an Info object for the given data. An error is returned // if any of the decoding or client lookup steps fail. Name and namespace will be // set into Info if the mapping's MetadataAccessor can retrieve them. func (m *Mapper) InfoForData(data []byte, source string) (*Info, error) { - json, err := yaml.ToJSON(data) + versions := &runtime.VersionedObjects{} + _, gvk, err := m.Decode(data, nil, versions) if err != nil { - return nil, fmt.Errorf("unable to parse %q: %v", source, err) - } - data = json - gvk, err := runtime.UnstructuredJSONScheme.DataKind(data) - if err != nil { - return nil, fmt.Errorf("unable to get type info from %q: %v", source, err) - } - if ok := registered.IsEnabledVersion(gvk.GroupVersion()); !ok { - return nil, fmt.Errorf("API version %q in %q isn't supported, only supports API versions %q", gvk.GroupVersion().String(), source, registered.EnabledVersions()) - } - if gvk.Kind == "" { - return nil, fmt.Errorf("kind not set in %q", source) + return nil, fmt.Errorf("unable to decode %q: %v", source, err) } mapping, err := m.RESTMapping(gvk.GroupKind(), gvk.Version) if err != nil { return nil, fmt.Errorf("unable to recognize %q: %v", source, err) } - obj, err := mapping.Codec.Decode(data) - if err != nil { - return nil, fmt.Errorf("unable to load %q: %v", source, err) - } + client, err := m.ClientForMapping(mapping) if err != nil { return nil, fmt.Errorf("unable to connect to a server to handle %q: %v", mapping.Resource, err) } + // TODO: decoding the version object is convenient, but questionable. This is used by apply + // and rolling-update today, but both of those cases should probably be requesting the raw + // object and performing their own decoding. + obj, versioned := versions.Last(), versions.First() name, _ := mapping.MetadataAccessor.Name(obj) namespace, _ := mapping.MetadataAccessor.Namespace(obj) resourceVersion, _ := mapping.MetadataAccessor.ResourceVersion(obj) - var versionedObject interface{} - - if vo, _, err := api.Scheme.Raw().DecodeToVersionedObject(data); err == nil { - versionedObject = vo - } return &Info{ Mapping: mapping, Client: client, Namespace: namespace, Name: name, Source: source, - VersionedObject: versionedObject, + VersionedObject: versioned, Object: obj, ResourceVersion: resourceVersion, }, nil diff --git a/pkg/kubectl/resource/result.go b/pkg/kubectl/resource/result.go index be6b4d5e051..8d726ab7b73 100644 --- a/pkg/kubectl/resource/result.go +++ b/pkg/kubectl/resource/result.go @@ -210,8 +210,8 @@ func (r *Result) Watch(resourceVersion string) (watch.Interface, error) { // the objects as children, or if only a single Object is present, as that object. The provided // version will be preferred as the conversion target, but the Object's mapping version will be // used if that version is not present. -func AsVersionedObject(infos []*Info, forceList bool, version string) (runtime.Object, error) { - objects, err := AsVersionedObjects(infos, version) +func AsVersionedObject(infos []*Info, forceList bool, version string, encoder runtime.Encoder) (runtime.Object, error) { + objects, err := AsVersionedObjects(infos, version, encoder) if err != nil { return nil, err } @@ -233,19 +233,21 @@ func AsVersionedObject(infos []*Info, forceList bool, version string) (runtime.O // AsVersionedObjects converts a list of infos into versioned objects. The provided // version will be preferred as the conversion target, but the Object's mapping version will be // used if that version is not present. -func AsVersionedObjects(infos []*Info, version string) ([]runtime.Object, error) { +func AsVersionedObjects(infos []*Info, version string, encoder runtime.Encoder) ([]runtime.Object, error) { objects := []runtime.Object{} for _, info := range infos { if info.Object == nil { continue } + // TODO: use info.VersionedObject as the value? + // objects that are not part of api.Scheme must be converted to JSON // TODO: convert to map[string]interface{}, attach to runtime.Unknown? if len(version) > 0 { if _, err := api.Scheme.ObjectKind(info.Object); runtime.IsNotRegisteredError(err) { // TODO: ideally this would encode to version, but we don't expose multiple codecs here. - data, err := info.Mapping.Codec.Encode(info.Object) + data, err := runtime.Encode(encoder, info.Object) if err != nil { return nil, err } diff --git a/pkg/kubectl/resource/visitor.go b/pkg/kubectl/resource/visitor.go index 5054a723852..a187919a414 100644 --- a/pkg/kubectl/resource/visitor.go +++ b/pkg/kubectl/resource/visitor.go @@ -345,7 +345,7 @@ func (v FlattenListVisitor) Visit(fn VisitorFunc) error { if errs := runtime.DecodeList(items, struct { runtime.ObjectTyper runtime.Decoder - }{v.Mapper, info.Mapping.Codec}); len(errs) > 0 { + }{v.Mapper, v.Mapper.Decoder}); len(errs) > 0 { return utilerrors.NewAggregate(errs) } for i := range items { diff --git a/pkg/kubectl/resource_printer.go b/pkg/kubectl/resource_printer.go index 637fc1d7ba7..974cff0dfc4 100644 --- a/pkg/kubectl/resource_printer.go +++ b/pkg/kubectl/resource_printer.go @@ -33,7 +33,6 @@ import ( "github.com/ghodss/yaml" "github.com/golang/glog" "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/latest" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apis/extensions" @@ -68,7 +67,7 @@ func GetPrinter(format, formatArgument string) (ResourcePrinter, bool, error) { case "name": printer = &NamePrinter{ Typer: runtime.ObjectTyperToTyper(api.Scheme), - Decoder: latest.Codecs.UniversalDecoder(), + Decoder: api.Codecs.UniversalDecoder(), } case "template", "go-template": if len(formatArgument) == 0 { diff --git a/pkg/kubectl/resource_printer_test.go b/pkg/kubectl/resource_printer_test.go index 3dc43870b75..a60eae59292 100644 --- a/pkg/kubectl/resource_printer_test.go +++ b/pkg/kubectl/resource_printer_test.go @@ -27,7 +27,6 @@ import ( "time" "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/latest" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/v1" @@ -466,7 +465,7 @@ func TestPrinters(t *testing.T) { "jsonpath": jsonpathPrinter, "name": &NamePrinter{ Typer: runtime.ObjectTyperToTyper(api.Scheme), - Decoder: latest.Codecs.UniversalDecoder(), + Decoder: api.Codecs.UniversalDecoder(), }, } objects := map[string]runtime.Object{ diff --git a/pkg/kubectl/sorting_printer_test.go b/pkg/kubectl/sorting_printer_test.go index d62acacf07c..f1d1d0c65cb 100644 --- a/pkg/kubectl/sorting_printer_test.go +++ b/pkg/kubectl/sorting_printer_test.go @@ -20,13 +20,13 @@ import ( "reflect" "testing" - "k8s.io/kubernetes/pkg/api/latest" + internal "k8s.io/kubernetes/pkg/api" api "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/runtime" ) func encodeOrDie(obj runtime.Object) []byte { - data, err := runtime.Encode(latest.Codecs.LegacyCodec(api.SchemeGroupVersion), obj) + data, err := runtime.Encode(internal.Codecs.LegacyCodec(api.SchemeGroupVersion), obj) if err != nil { panic(err.Error()) } @@ -224,7 +224,7 @@ func TestSortingPrinter(t *testing.T) { }, } for _, test := range tests { - sort := &SortingPrinter{SortField: test.field, Decoder: latest.Codecs.UniversalDecoder()} + sort := &SortingPrinter{SortField: test.field, Decoder: internal.Codecs.UniversalDecoder()} if err := sort.sortObj(test.obj); err != nil { t.Errorf("unexpected error: %v (%s)", err, test.name) continue diff --git a/pkg/registry/thirdpartyresourcedata/codec.go b/pkg/registry/thirdpartyresourcedata/codec.go index 9293af0f2cd..33ee1094520 100644 --- a/pkg/registry/thirdpartyresourcedata/codec.go +++ b/pkg/registry/thirdpartyresourcedata/codec.go @@ -25,7 +25,6 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/meta" - "k8s.io/kubernetes/pkg/api/registered" "k8s.io/kubernetes/pkg/api/unversioned" apiutil "k8s.io/kubernetes/pkg/api/util" "k8s.io/kubernetes/pkg/apimachinery/registered"