From 2fd38a7dc022c28f05403355061d521d8f124da3 Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Mon, 21 Dec 2015 00:37:49 -0500 Subject: [PATCH] Break kubectl from assuming details of codecs Most of the logic related to type and kind retrieval belongs in the codec, not in the various classes. Make it explicit that the codec should handle these details. Factory now returns a universal Decoder and a JSONEncoder to assist code in kubectl that needs to specifically deal with JSON serialization (apply, merge, patch, edit, jsonpath). Add comments to indicate the serialization is explicit in those places. These methods decode to internal and encode to the preferred API version as previous, although in the future they may be changed. React to removing Codec from version interfaces and RESTMapping by passing it in to all the places that it is needed. --- pkg/kubectl/apply.go | 25 ++++--- pkg/kubectl/cmd/annotate.go | 4 +- pkg/kubectl/cmd/apply.go | 11 +-- pkg/kubectl/cmd/autoscale.go | 11 ++- pkg/kubectl/cmd/clusterinfo.go | 8 +-- pkg/kubectl/cmd/cmd_test.go | 28 +++++--- pkg/kubectl/cmd/convert.go | 10 ++- pkg/kubectl/cmd/create.go | 14 ++-- pkg/kubectl/cmd/delete.go | 2 +- pkg/kubectl/cmd/describe.go | 4 +- pkg/kubectl/cmd/drain.go | 3 +- pkg/kubectl/cmd/edit.go | 24 ++++--- pkg/kubectl/cmd/expose.go | 15 ++-- pkg/kubectl/cmd/get.go | 9 +-- pkg/kubectl/cmd/get_test.go | 4 +- pkg/kubectl/cmd/label.go | 4 +- pkg/kubectl/cmd/logs.go | 6 +- pkg/kubectl/cmd/patch.go | 4 +- pkg/kubectl/cmd/replace.go | 10 +-- pkg/kubectl/cmd/rollingupdate.go | 12 ++-- pkg/kubectl/cmd/run.go | 16 +++-- pkg/kubectl/cmd/scale.go | 2 +- pkg/kubectl/cmd/stop.go | 2 +- pkg/kubectl/cmd/util/factory.go | 39 ++++++----- pkg/kubectl/cmd/util/factory_test.go | 2 +- pkg/kubectl/cmd/util/helpers.go | 44 ++---------- pkg/kubectl/cmd/util/helpers_test.go | 2 +- pkg/kubectl/custom_column_printer.go | 4 +- pkg/kubectl/resource/builder.go | 4 +- pkg/kubectl/resource/builder_test.go | 74 +++++++++++--------- pkg/kubectl/resource/helper.go | 5 +- pkg/kubectl/resource/helper_test.go | 2 - pkg/kubectl/resource/interfaces.go | 2 +- pkg/kubectl/resource/mapper.go | 46 ++++++------ pkg/kubectl/resource/result.go | 10 +-- pkg/kubectl/resource/visitor.go | 2 +- pkg/kubectl/resource_printer.go | 3 +- pkg/kubectl/resource_printer_test.go | 3 +- pkg/kubectl/sorting_printer_test.go | 6 +- pkg/registry/thirdpartyresourcedata/codec.go | 1 - 40 files changed, 245 insertions(+), 232 deletions(-) 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"