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 9edbd6fa73..3149bcb29d 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 3f82db4ff5..95869a3d53 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 a13165fc82..ef63b04009 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 b8a14827fa..6647db0118 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 a5adb2d33d..c76e28f7c6 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 2fff13e9b9..4db40989c6 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 694a005cd9..e7e04d5b99 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 6b887a0762..10b30ba4ef 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 6bac41cdec..94973d6e58 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 7fa4dedf19..3ea83ecdc4 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 db09620e1e..8c07a18281 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 3503620604..e98407bf84 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 382a6c098e..0546971316 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 e4c3f009b2..284e355505 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 7cedaf5178..d8ba37e2b9 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 d2952d201f..23f7b60564 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 5f1b47c6a5..369d41d8a9 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 9a971d7297..b537adaa18 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 a076e91f9a..226e5c434d 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 fb11b9a3cf..bf4015b6e6 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 a87efcee0d..8affcbda87 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 18e9fedbff..2637fa2b0a 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 c6673fa481..6069866171 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 66f9ebfab1..3d52e44eba 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 7818e7b904..be9484fae3 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 67e3b01c01..7232749346 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 3c878b4a01..598c3e961e 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 7d91691f73..431a114c74 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 a9dd31267e..61b0f11318 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 2ee364b05c..1fad662b63 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 85f7bc1946..fcce5b0cae 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 2bdc977b0f..541daf880f 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 3d64609984..54d20dfbff 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 2c61325bdd..0839ca4a8e 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 be6b4d5e05..8d726ab7b7 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 5054a72385..a187919a41 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 637fc1d7ba..974cff0dfc 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 3dc43870b7..a60eae5929 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 d62acacf07..f1d1d0c65c 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 9293af0f2c..33ee109452 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"