diff --git a/pkg/kubectl/cmd/BUILD b/pkg/kubectl/cmd/BUILD index 72071bf9ff072..81dc8dcbbfc05 100644 --- a/pkg/kubectl/cmd/BUILD +++ b/pkg/kubectl/cmd/BUILD @@ -102,6 +102,7 @@ go_library( "//vendor/github.com/renstrom/dedent:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", + "//vendor/k8s.io/api/autoscaling/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/policy/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", @@ -129,7 +130,9 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library", "//vendor/k8s.io/client-go/discovery:go_default_library", + "//vendor/k8s.io/client-go/dynamic:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/typed/autoscaling/v1:go_default_library", "//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/scale:go_default_library", diff --git a/pkg/kubectl/cmd/apply.go b/pkg/kubectl/cmd/apply.go index 5d0a14b0b9abb..12cfa204f7b3b 100644 --- a/pkg/kubectl/cmd/apply.go +++ b/pkg/kubectl/cmd/apply.go @@ -38,6 +38,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/dynamic" scaleclient "k8s.io/client-go/scale" oapi "k8s.io/kube-openapi/pkg/util/proto" api "k8s.io/kubernetes/pkg/apis/core" @@ -285,8 +286,13 @@ func (o *ApplyOptions) Run(f cmdutil.Factory, cmd *cobra.Command) error { return err } + mapper, err := f.RESTMapper() + if err != nil { + return err + } + if o.Prune { - o.PruneResources, err = parsePruneResources(r.Mapper().RESTMapper, o.PruneWhitelist) + o.PruneResources, err = parsePruneResources(mapper, o.PruneWhitelist) if err != nil { return err } @@ -297,7 +303,6 @@ func (o *ApplyOptions) Run(f cmdutil.Factory, cmd *cobra.Command) error { encoder := scheme.DefaultJSONEncoder() deserializer := scheme.Codecs.UniversalDeserializer() - mapper := r.Mapper().RESTMapper visitedUids := sets.NewString() visitedNamespaces := sets.NewString() @@ -382,12 +387,16 @@ func (o *ApplyOptions) Run(f cmdutil.Factory, cmd *cobra.Command) error { return err } helper := resource.NewHelper(info.Client, info.Mapping) + dynamicClient, err := f.DynamicClient() + if err != nil { + return err + } patcher := &patcher{ encoder: encoder, decoder: deserializer, mapping: info.Mapping, helper: helper, - clientFunc: f.UnstructuredClientForMapping, + dynamicClient: dynamicClient, clientsetFunc: f.ClientSet, overwrite: o.Overwrite, backOff: clockwork.NewRealClock(), @@ -470,9 +479,14 @@ func (o *ApplyOptions) Run(f cmdutil.Factory, cmd *cobra.Command) error { return nil } + dynamicClient, err := f.DynamicClient() + if err != nil { + return err + } + p := pruner{ mapper: mapper, - clientFunc: f.UnstructuredClientForMapping, + dynamicClient: dynamicClient, clientsetFunc: f.ClientSet, labelSelector: o.Selector, @@ -560,7 +574,7 @@ func getRESTMappings(mapper meta.RESTMapper, pruneResources *[]pruneResource) (n type pruner struct { mapper meta.RESTMapper - clientFunc resource.ClientMapperFunc + dynamicClient dynamic.DynamicInterface clientsetFunc func() (internalclientset.Interface, error) visitedUids sets.String @@ -577,24 +591,17 @@ type pruner struct { } func (p *pruner) prune(f cmdutil.Factory, namespace string, mapping *meta.RESTMapping, includeUninitialized bool) error { - c, err := p.clientFunc(mapping) - if err != nil { - return err - } - - objList, err := resource.NewHelper(c, mapping).List( - namespace, - mapping.GroupVersionKind.Version, - false, - &metav1.ListOptions{ + objList, err := p.dynamicClient.Resource(mapping.GroupVersionKind.GroupVersion().WithResource(mapping.Resource)). + Namespace(namespace). + List(metav1.ListOptions{ LabelSelector: p.labelSelector, FieldSelector: p.fieldSelector, IncludeUninitialized: includeUninitialized, - }, - ) + }) if err != nil { return err } + objs, err := meta.ExtractList(objList) if err != nil { return err @@ -635,20 +642,12 @@ func (p *pruner) prune(f cmdutil.Factory, namespace string, mapping *meta.RESTMa } func (p *pruner) delete(namespace, name string, mapping *meta.RESTMapping, scaleClient scaleclient.ScalesGetter) error { - c, err := p.clientFunc(mapping) - if err != nil { - return err - } - - return runDelete(namespace, name, mapping, c, nil, p.cascade, p.gracePeriod, p.clientsetFunc, scaleClient) + return runDelete(namespace, name, mapping, p.dynamicClient, p.cascade, p.gracePeriod, p.clientsetFunc, scaleClient) } -func runDelete(namespace, name string, mapping *meta.RESTMapping, c resource.RESTClient, helper *resource.Helper, cascade bool, gracePeriod int, clientsetFunc func() (internalclientset.Interface, error), scaleClient scaleclient.ScalesGetter) error { +func runDelete(namespace, name string, mapping *meta.RESTMapping, c dynamic.DynamicInterface, cascade bool, gracePeriod int, clientsetFunc func() (internalclientset.Interface, error), scaleClient scaleclient.ScalesGetter) error { if !cascade { - if helper == nil { - helper = resource.NewHelper(c, mapping) - } - return helper.Delete(namespace, name) + return c.Resource(mapping.GroupVersionKind.GroupVersion().WithResource(mapping.Resource)).Namespace(namespace).Delete(name, nil) } cs, err := clientsetFunc() if err != nil { @@ -659,7 +658,7 @@ func runDelete(namespace, name string, mapping *meta.RESTMapping, c resource.RES if _, ok := err.(*kubectl.NoSuchReaperError); !ok { return err } - return resource.NewHelper(c, mapping).Delete(namespace, name) + return c.Resource(mapping.GroupVersionKind.GroupVersion().WithResource(mapping.Resource)).Namespace(namespace).Delete(name, nil) } var options *metav1.DeleteOptions if gracePeriod >= 0 { @@ -672,11 +671,7 @@ func runDelete(namespace, name string, mapping *meta.RESTMapping, c resource.RES } func (p *patcher) delete(namespace, name string) error { - c, err := p.clientFunc(p.mapping) - if err != nil { - return err - } - return runDelete(namespace, name, p.mapping, c, p.helper, p.cascade, p.gracePeriod, p.clientsetFunc, p.scaleClient) + return runDelete(namespace, name, p.mapping, p.dynamicClient, p.cascade, p.gracePeriod, p.clientsetFunc, p.scaleClient) } type patcher struct { @@ -685,7 +680,7 @@ type patcher struct { mapping *meta.RESTMapping helper *resource.Helper - clientFunc resource.ClientMapperFunc + dynamicClient dynamic.DynamicInterface clientsetFunc func() (internalclientset.Interface, error) overwrite bool diff --git a/pkg/kubectl/cmd/attach.go b/pkg/kubectl/cmd/attach.go index ced12cd3fcd8e..569cfec92451e 100644 --- a/pkg/kubectl/cmd/attach.go +++ b/pkg/kubectl/cmd/attach.go @@ -146,7 +146,7 @@ func (p *AttachOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, argsIn [ } builder := f.NewBuilder(). - Internal(legacyscheme.Scheme). + WithScheme(legacyscheme.Scheme). NamespaceParam(namespace).DefaultNamespace() switch len(argsIn) { diff --git a/pkg/kubectl/cmd/auth/reconcile.go b/pkg/kubectl/cmd/auth/reconcile.go index 6df674ee5804b..26dd8069c4d43 100644 --- a/pkg/kubectl/cmd/auth/reconcile.go +++ b/pkg/kubectl/cmd/auth/reconcile.go @@ -96,7 +96,7 @@ func (o *ReconcileOptions) Complete(cmd *cobra.Command, f cmdutil.Factory, args } r := f.NewBuilder(). - Internal(legacyscheme.Scheme). + WithScheme(legacyscheme.Scheme). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace(). FilenameParam(enforceNamespace, options). diff --git a/pkg/kubectl/cmd/autoscale.go b/pkg/kubectl/cmd/autoscale.go index e1a0d61c5185a..a4a4aba2c0914 100644 --- a/pkg/kubectl/cmd/autoscale.go +++ b/pkg/kubectl/cmd/autoscale.go @@ -22,8 +22,10 @@ import ( "github.com/golang/glog" "github.com/spf13/cobra" + autoscalingv1 "k8s.io/api/autoscaling/v1" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime/schema" + autoscalingv1client "k8s.io/client-go/kubernetes/typed/autoscaling/v1" "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" @@ -70,11 +72,11 @@ type AutoscaleOptions struct { namespace string dryRun bool builder *resource.Builder - mapper meta.RESTMapper canBeAutoscaled func(kind schema.GroupKind) error - clientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error) generatorFunc func(string, *meta.RESTMapping) (kubectl.StructuredGenerator, error) + HPAClient autoscalingv1client.HorizontalPodAutoscalersGetter + genericclioptions.IOStreams } @@ -132,12 +134,6 @@ func (o *AutoscaleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args o.createAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag) o.builder = f.NewBuilder() o.canBeAutoscaled = f.CanBeAutoscaled - o.mapper, err = f.RESTMapper() - if err != nil { - return err - } - - o.clientForMapping = f.ClientForMapping o.args = args o.RecordFlags.Complete(f.Command(cmd, false)) @@ -146,6 +142,12 @@ func (o *AutoscaleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args return err } + kubeClient, err := f.KubernetesClientSet() + if err != nil { + return err + } + o.HPAClient = kubeClient.AutoscalingV1() + // get the generator o.generatorFunc = func(name string, mapping *meta.RESTMapping) (kubectl.StructuredGenerator, error) { switch o.Generator { @@ -199,7 +201,7 @@ func (o *AutoscaleOptions) Validate() error { func (o *AutoscaleOptions) Run() error { r := o.builder. - Internal(legacyscheme.Scheme). + WithScheme(legacyscheme.Scheme). ContinueOnError(). NamespaceParam(o.namespace).DefaultNamespace(). FilenameParam(o.enforceNamespace, o.FilenameOptions). @@ -231,20 +233,14 @@ func (o *AutoscaleOptions) Run() error { if err != nil { return err } - - resourceMapper := &resource.Mapper{ - RESTMapper: o.mapper, - ClientMapper: resource.ClientMapperFunc(o.clientForMapping), - Decoder: cmdutil.InternalVersionDecoder(), + hpa, ok := object.(*autoscalingv1.HorizontalPodAutoscaler) + if !ok { + return fmt.Errorf("generator made %T, not autoscalingv1.HorizontalPodAutoscaler", object) } - hpa, err := resourceMapper.InfoForObject(object, legacyscheme.Scheme, nil) - if err != nil { - return err - } - if err := o.Recorder.Record(hpa.Object); err != nil { + + if err := o.Recorder.Record(hpa); err != nil { glog.V(4).Infof("error recording current command: %v", err) } - object = hpa.Object if o.dryRun { count++ @@ -253,14 +249,14 @@ func (o *AutoscaleOptions) Run() error { if err != nil { return err } - return printer.PrintObj(cmdutil.AsDefaultVersionedOrOriginal(hpa.Object, hpa.Mapping), o.Out) + return printer.PrintObj(hpa, o.Out) } - if err := kubectl.CreateOrUpdateAnnotation(o.createAnnotation, hpa.Object, cmdutil.InternalVersionJSONEncoder()); err != nil { + if err := kubectl.CreateOrUpdateAnnotation(o.createAnnotation, hpa, cmdutil.InternalVersionJSONEncoder()); err != nil { return err } - _, err = resource.NewHelper(hpa.Client, hpa.Mapping).Create(o.namespace, false, object) + actualHPA, err := o.HPAClient.HorizontalPodAutoscalers(o.namespace).Create(hpa) if err != nil { return err } @@ -270,7 +266,7 @@ func (o *AutoscaleOptions) Run() error { if err != nil { return err } - return printer.PrintObj(cmdutil.AsDefaultVersionedOrOriginal(info.Object, hpa.Mapping), o.Out) + return printer.PrintObj(actualHPA, o.Out) }) if err != nil { return err diff --git a/pkg/kubectl/cmd/certificates.go b/pkg/kubectl/cmd/certificates.go index d7357cba955f6..169381a48a647 100644 --- a/pkg/kubectl/cmd/certificates.go +++ b/pkg/kubectl/cmd/certificates.go @@ -204,7 +204,7 @@ func (o *CertificateOptions) RunCertificateDeny(force bool) error { func (options *CertificateOptions) modifyCertificateCondition(builder *resource.Builder, clientSet internalclientset.Interface, force bool, modify func(csr *certificates.CertificateSigningRequest) (*certificates.CertificateSigningRequest, bool)) error { var found int r := builder. - Internal(legacyscheme.Scheme). + WithScheme(legacyscheme.Scheme). ContinueOnError(). FilenameParam(false, &options.FilenameOptions). ResourceNames("certificatesigningrequest", options.csrNames...). diff --git a/pkg/kubectl/cmd/clusterinfo.go b/pkg/kubectl/cmd/clusterinfo.go index b908ca47704f5..bd30c721955ee 100644 --- a/pkg/kubectl/cmd/clusterinfo.go +++ b/pkg/kubectl/cmd/clusterinfo.go @@ -96,7 +96,7 @@ func (o *ClusterInfoOptions) Run() error { // TODO use generalized labels once they are implemented (#341) b := o.Builder. - Internal(legacyscheme.Scheme). + WithScheme(legacyscheme.Scheme). NamespaceParam(o.Namespace).DefaultNamespace(). LabelSelectorParam("kubernetes.io/cluster-service=true"). ResourceTypeOrNameArgs(false, []string{"services"}...). diff --git a/pkg/kubectl/cmd/convert.go b/pkg/kubectl/cmd/convert.go index 7dc2cd4dfc0fe..264895023ca7c 100644 --- a/pkg/kubectl/cmd/convert.go +++ b/pkg/kubectl/cmd/convert.go @@ -129,7 +129,7 @@ func (o *ConvertOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) (err er // build the builder o.builder = f.NewBuilder(). - Internal(scheme.Scheme). + WithScheme(scheme.Scheme). LocalParam(o.local) if !o.local { schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate")) diff --git a/pkg/kubectl/cmd/create/BUILD b/pkg/kubectl/cmd/create/BUILD index 53a84bd9ea8ea..b14ab4ff9dda9 100644 --- a/pkg/kubectl/cmd/create/BUILD +++ b/pkg/kubectl/cmd/create/BUILD @@ -41,6 +41,7 @@ go_library( "//vendor/k8s.io/api/rbac/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", diff --git a/pkg/kubectl/cmd/create/create.go b/pkg/kubectl/cmd/create/create.go index e1dfc5b866bde..8506481abb26a 100644 --- a/pkg/kubectl/cmd/create/create.go +++ b/pkg/kubectl/cmd/create/create.go @@ -29,6 +29,7 @@ import ( "github.com/golang/glog" "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" kruntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/kubernetes/pkg/api/legacyscheme" @@ -404,29 +405,29 @@ func RunCreateSubcommand(f cmdutil.Factory, options *CreateSubcommandOptions) er if err != nil { return err } - client, err := f.ClientForMapping(mapping) - if err != nil { + + if err := kubectl.CreateOrUpdateAnnotation(options.CreateAnnotation, obj, cmdutil.InternalVersionJSONEncoder()); err != nil { return err } - resourceMapper := &resource.Mapper{ - RESTMapper: mapper, - ClientMapper: resource.ClientMapperFunc(f.ClientForMapping), + + asUnstructured := &unstructured.Unstructured{} + if err := legacyscheme.Scheme.Convert(obj, asUnstructured, nil); err != nil { + return err } - info, err := resourceMapper.InfoForObject(obj, legacyscheme.Scheme, nil) + dynamicClient, err := f.DynamicClient() if err != nil { return err } - if err := kubectl.CreateOrUpdateAnnotation(options.CreateAnnotation, info.Object, cmdutil.InternalVersionJSONEncoder()); err != nil { - return err + if mapping.Scope.Name() == meta.RESTScopeNameRoot { + namespace = "" } - - obj, err = resource.NewHelper(client, mapping).Create(namespace, false, info.Object) + actualObject, err := dynamicClient.Resource(mapping.GroupVersionKind.GroupVersion().WithResource(mapping.Resource)).Namespace(namespace).Create(asUnstructured) if err != nil { return err } // ensure we pass a versioned object to the printer - obj = cmdutil.AsDefaultVersionedOrOriginal(info.Object, info.Mapping) + obj = actualObject } else { if meta, err := meta.Accessor(obj); err == nil && nsOverriden { meta.SetNamespace(namespace) diff --git a/pkg/kubectl/cmd/create/create_quota_test.go b/pkg/kubectl/cmd/create/create_quota_test.go index f0b3b1619d419..c9bf1333eb540 100644 --- a/pkg/kubectl/cmd/create/create_quota_test.go +++ b/pkg/kubectl/cmd/create/create_quota_test.go @@ -17,41 +17,16 @@ limitations under the License. package create import ( - "net/http" "testing" "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api/legacyscheme" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" "k8s.io/kubernetes/pkg/kubectl/genericclioptions" - "k8s.io/kubernetes/pkg/kubectl/scheme" ) func TestCreateQuota(t *testing.T) { resourceQuotaObject := &v1.ResourceQuota{} resourceQuotaObject.Name = "my-quota" - tf := cmdtesting.NewTestFactory() - defer tf.Cleanup() - - codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...) - ns := legacyscheme.Codecs - - tf.Client = &fake.RESTClient{ - GroupVersion: schema.GroupVersion{Version: "v1"}, - NegotiatedSerializer: ns, - Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { - switch p, m := req.URL.Path, req.Method; { - case p == "/namespaces/test/resourcequotas" && m == "POST": - return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, resourceQuotaObject)}, nil - default: - t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) - return nil, nil - } - }), - } - tf.Namespace = "test" tests := map[string]struct { flags []string @@ -75,14 +50,21 @@ func TestCreateQuota(t *testing.T) { }, } for name, test := range tests { - ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdCreateQuota(tf, ioStreams) - cmd.Flags().Parse(test.flags) - cmd.Flags().Set("output", "name") - cmd.Run(cmd, []string{resourceQuotaObject.Name}) + t.Run(name, func(t *testing.T) { + tf := cmdtesting.NewTestFactory() + defer tf.Cleanup() - if buf.String() != test.expectedOutput { - t.Errorf("%s: expected output: %s, but got: %s", name, test.expectedOutput, buf.String()) - } + tf.Namespace = "test" + + ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams() + cmd := NewCmdCreateQuota(tf, ioStreams) + cmd.Flags().Parse(test.flags) + cmd.Flags().Set("output", "name") + cmd.Run(cmd, []string{resourceQuotaObject.Name}) + + if buf.String() != test.expectedOutput { + t.Errorf("%s: expected output: %s, but got: %s", name, test.expectedOutput, buf.String()) + } + }) } } diff --git a/pkg/kubectl/cmd/delete.go b/pkg/kubectl/cmd/delete.go index b27072c1819b2..e19848f309fa0 100644 --- a/pkg/kubectl/cmd/delete.go +++ b/pkg/kubectl/cmd/delete.go @@ -174,7 +174,11 @@ func (o *DeleteOptions) Complete(f cmdutil.Factory, out, errOut io.Writer, args return err } o.Result = r - o.Mapper = r.Mapper().RESTMapper + + o.Mapper, err = f.RESTMapper() + if err != nil { + return err + } // Set up writer o.Out = out diff --git a/pkg/kubectl/cmd/drain.go b/pkg/kubectl/cmd/drain.go index 8ae562744de3d..d4ceeab0f5c7c 100644 --- a/pkg/kubectl/cmd/drain.go +++ b/pkg/kubectl/cmd/drain.go @@ -282,7 +282,7 @@ func (o *DrainOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st } builder := f.NewBuilder(). - Internal(legacyscheme.Scheme). + WithScheme(legacyscheme.Scheme). NamespaceParam(o.Namespace).DefaultNamespace(). ResourceNames("nodes", args...). SingleResourceType(). diff --git a/pkg/kubectl/cmd/edit_test.go b/pkg/kubectl/cmd/edit_test.go index 78fda659ad3c9..e0445d279e17d 100644 --- a/pkg/kubectl/cmd/edit_test.go +++ b/pkg/kubectl/cmd/edit_test.go @@ -32,7 +32,7 @@ import ( yaml "gopkg.in/yaml.v2" - "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/rest/fake" @@ -211,12 +211,12 @@ func TestEdit(t *testing.T) { tf := cmdtesting.NewTestFactory() defer tf.Cleanup() - tf.UnstructuredClientForMappingFunc = func(mapping *meta.RESTMapping) (resource.RESTClient, error) { + tf.UnstructuredClientForMappingFunc = func(gv schema.GroupVersion) (resource.RESTClient, error) { versionedAPIPath := "" - if mapping.GroupVersionKind.Group == "" { - versionedAPIPath = "/api/" + mapping.GroupVersionKind.Version + if gv.Group == "" { + versionedAPIPath = "/api/" + gv.Version } else { - versionedAPIPath = "/apis/" + mapping.GroupVersionKind.Group + "/" + mapping.GroupVersionKind.Version + versionedAPIPath = "/apis/" + gv.Group + "/" + gv.Version } return &fake.RESTClient{ VersionedAPIPath: versionedAPIPath, diff --git a/pkg/kubectl/cmd/expose.go b/pkg/kubectl/cmd/expose.go index cf356e58b0012..574e9f6c43e90 100644 --- a/pkg/kubectl/cmd/expose.go +++ b/pkg/kubectl/cmd/expose.go @@ -24,9 +24,12 @@ import ( "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/client-go/dynamic" "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" @@ -97,7 +100,8 @@ type ExposeServiceOptions struct { Namespace string Mapper meta.RESTMapper - Builder *resource.Builder + DynamicClient dynamic.DynamicInterface + Builder *resource.Builder Recorder genericclioptions.Recorder genericclioptions.IOStreams @@ -180,6 +184,11 @@ func (o *ExposeServiceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) e return err } + o.DynamicClient, err = f.DynamicClient() + if err != nil { + return err + } + o.Generators = f.Generators o.Builder = f.NewBuilder() o.CanBeExposed = f.CanBeExposed @@ -203,7 +212,7 @@ func (o *ExposeServiceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) e func (o *ExposeServiceOptions) RunExpose(cmd *cobra.Command, args []string) error { r := o.Builder. - Internal(legacyscheme.Scheme). + WithScheme(legacyscheme.Scheme). ContinueOnError(). NamespaceParam(o.Namespace).DefaultNamespace(). FilenameParam(o.EnforceNamespace, &o.FilenameOptions). @@ -313,33 +322,36 @@ func (o *ExposeServiceOptions) RunExpose(cmd *cobra.Command, args []string) erro } } - resourceMapper := &resource.Mapper{ - RESTMapper: o.Mapper, - ClientMapper: resource.ClientMapperFunc(o.ClientForMapping), - Decoder: cmdutil.InternalVersionDecoder(), - } - info, err = resourceMapper.InfoForObject(object, legacyscheme.Scheme, nil) - if err != nil { - return err - } if err := o.Recorder.Record(object); err != nil { glog.V(4).Infof("error recording current command: %v", err) } - info.Refresh(object, true) + if o.DryRun { return o.PrintObj(object, o.Out) } - if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info.Object, cmdutil.InternalVersionJSONEncoder()); err != nil { + if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), object, cmdutil.InternalVersionJSONEncoder()); err != nil { return err } + asUnstructured := &unstructured.Unstructured{} + if err := legacyscheme.Scheme.Convert(object, asUnstructured, nil); err != nil { + return err + } + gvks, _, err := unstructuredscheme.NewUnstructuredObjectTyper().ObjectKinds(asUnstructured) + if err != nil { + return err + } + objMapping, err := o.Mapper.RESTMapping(gvks[0].GroupKind(), gvks[0].Version) + if err != nil { + return err + } // Serialize the object with the annotation applied. - object, err = resource.NewHelper(info.Client, info.Mapping).Create(o.Namespace, false, object) + actualObject, err := o.DynamicClient.Resource(objMapping.GroupVersionKind.GroupVersion().WithResource(objMapping.Resource)).Namespace(o.Namespace).Create(asUnstructured) if err != nil { return err } - return o.PrintObj(cmdutil.AsDefaultVersionedOrOriginal(info.Object, info.Mapping), o.Out) + return o.PrintObj(actualObject, o.Out) }) if err != nil { return err diff --git a/pkg/kubectl/cmd/expose_test.go b/pkg/kubectl/cmd/expose_test.go index c021fe77a6f4a..7b1b9d4af8832 100644 --- a/pkg/kubectl/cmd/expose_test.go +++ b/pkg/kubectl/cmd/expose_test.go @@ -480,8 +480,6 @@ func TestRunExposeService(t *testing.T) { switch p, m := req.URL.Path, req.Method; { case p == test.calls[m] && m == "GET": return &http.Response{StatusCode: test.status, Header: defaultHeader(), Body: objBody(codec, test.input)}, nil - case p == test.calls[m] && m == "POST": - return &http.Response{StatusCode: test.status, Header: defaultHeader(), Body: objBody(codec, test.output)}, nil default: t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) return nil, nil diff --git a/pkg/kubectl/cmd/logs.go b/pkg/kubectl/cmd/logs.go index 810d7a862cfcc..7b07806b04fc5 100644 --- a/pkg/kubectl/cmd/logs.go +++ b/pkg/kubectl/cmd/logs.go @@ -202,7 +202,7 @@ func (o *LogsOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.Comm if o.Object == nil { builder := f.NewBuilder(). - Internal(legacyscheme.Scheme). + WithScheme(legacyscheme.Scheme). NamespaceParam(o.Namespace).DefaultNamespace(). SingleResourceType() if o.ResourceArg != "" { diff --git a/pkg/kubectl/cmd/portforward.go b/pkg/kubectl/cmd/portforward.go index e2fa114c42639..5f0043104a4ec 100644 --- a/pkg/kubectl/cmd/portforward.go +++ b/pkg/kubectl/cmd/portforward.go @@ -180,7 +180,7 @@ func (o *PortForwardOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, arg } builder := f.NewBuilder(). - Internal(legacyscheme.Scheme). + WithScheme(legacyscheme.Scheme). ContinueOnError(). NamespaceParam(o.Namespace).DefaultNamespace() diff --git a/pkg/kubectl/cmd/rollout/rollout_history.go b/pkg/kubectl/cmd/rollout/rollout_history.go index a454acecba143..e42f3b8a4fb29 100644 --- a/pkg/kubectl/cmd/rollout/rollout_history.go +++ b/pkg/kubectl/cmd/rollout/rollout_history.go @@ -82,7 +82,7 @@ func RunHistory(f cmdutil.Factory, cmd *cobra.Command, out io.Writer, args []str } r := f.NewBuilder(). - Internal(legacyscheme.Scheme). + WithScheme(legacyscheme.Scheme). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, options). ResourceTypeOrNameArgs(true, args...). diff --git a/pkg/kubectl/cmd/rollout/rollout_pause.go b/pkg/kubectl/cmd/rollout/rollout_pause.go index 1640dda4d714d..eb4a4b1662e32 100644 --- a/pkg/kubectl/cmd/rollout/rollout_pause.go +++ b/pkg/kubectl/cmd/rollout/rollout_pause.go @@ -111,7 +111,7 @@ func (o *PauseConfig) CompletePause(f cmdutil.Factory, cmd *cobra.Command, args } r := f.NewBuilder(). - Internal(legacyscheme.Scheme). + WithScheme(legacyscheme.Scheme). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, &o.FilenameOptions). ResourceTypeOrNameArgs(true, args...). diff --git a/pkg/kubectl/cmd/rollout/rollout_resume.go b/pkg/kubectl/cmd/rollout/rollout_resume.go index d4b79e023e5eb..ee28ef9db9303 100644 --- a/pkg/kubectl/cmd/rollout/rollout_resume.go +++ b/pkg/kubectl/cmd/rollout/rollout_resume.go @@ -119,7 +119,7 @@ func (o *ResumeConfig) CompleteResume(f cmdutil.Factory, cmd *cobra.Command, arg } r := f.NewBuilder(). - Internal(legacyscheme.Scheme). + WithScheme(legacyscheme.Scheme). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, &o.FilenameOptions). ResourceTypeOrNameArgs(true, args...). diff --git a/pkg/kubectl/cmd/rollout/rollout_status.go b/pkg/kubectl/cmd/rollout/rollout_status.go index 5a0b27b0b25c9..e6e46a4422092 100644 --- a/pkg/kubectl/cmd/rollout/rollout_status.go +++ b/pkg/kubectl/cmd/rollout/rollout_status.go @@ -124,7 +124,7 @@ func (o *RolloutStatusOptions) Validate(cmd *cobra.Command, args []string) error func (o *RolloutStatusOptions) Run() error { r := o.Builder. - Internal(legacyscheme.Scheme). + WithScheme(legacyscheme.Scheme). NamespaceParam(o.Namespace).DefaultNamespace(). FilenameParam(o.EnforceNamespace, o.FilenameOptions). ResourceTypeOrNameArgs(true, o.BuilderArgs...). diff --git a/pkg/kubectl/cmd/rollout/rollout_undo.go b/pkg/kubectl/cmd/rollout/rollout_undo.go index 45aaf1703844d..b8c07afc4b1b2 100644 --- a/pkg/kubectl/cmd/rollout/rollout_undo.go +++ b/pkg/kubectl/cmd/rollout/rollout_undo.go @@ -127,7 +127,7 @@ func (o *UndoOptions) CompleteUndo(f cmdutil.Factory, cmd *cobra.Command, out io } r := f.NewBuilder(). - Internal(legacyscheme.Scheme). + WithScheme(legacyscheme.Scheme). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, &o.FilenameOptions). ResourceTypeOrNameArgs(true, args...). diff --git a/pkg/kubectl/cmd/run.go b/pkg/kubectl/cmd/run.go index 1285974fb8227..92eeabe89d55d 100644 --- a/pkg/kubectl/cmd/run.go +++ b/pkg/kubectl/cmd/run.go @@ -20,6 +20,7 @@ import ( "fmt" "io" + "k8s.io/client-go/dynamic" "k8s.io/kubernetes/pkg/printers" "github.com/docker/distribution/reference" @@ -40,6 +41,7 @@ import ( cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/util/interrupt" uexec "k8s.io/utils/exec" @@ -90,10 +92,8 @@ var ( ) type RunObject struct { - Versioned runtime.Object - Object runtime.Object - Kind string - Mapping *meta.RESTMapping + Object runtime.Object + Mapping *meta.RESTMapping } type RunOptions struct { @@ -107,6 +107,8 @@ type RunOptions struct { PrintObj func(runtime.Object) error Recorder genericclioptions.Recorder + DynamicClient dynamic.DynamicInterface + ArgsLenAtDash int Attach bool Expose bool @@ -197,6 +199,11 @@ func (o *RunOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error { return err } + o.DynamicClient, err = f.DynamicClient() + if err != nil { + return err + } + o.ArgsLenAtDash = cmd.ArgsLenAtDash() o.DryRun = cmdutil.GetFlagBool(cmd, "dry-run") o.Expose = cmdutil.GetFlagBool(cmd, "expose") @@ -438,7 +445,7 @@ func (o *RunOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e } if runObject != nil { - if err := o.PrintObj(runObject.Versioned); err != nil { + if err := o.PrintObj(runObject.Object); err != nil { return err } } @@ -458,7 +465,7 @@ func (o *RunOptions) removeCreatedObjects(f cmdutil.Factory, createdObjects []*R return err } r := f.NewBuilder(). - Internal(legacyscheme.Scheme). + WithScheme(legacyscheme.Scheme). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace(). ResourceNames(obj.Mapping.Resource.Resource+"."+obj.Mapping.Resource.Group, name). @@ -620,7 +627,7 @@ func (o *RunOptions) generateService(f cmdutil.Factory, cmd *cobra.Command, serv return nil, err } - if err := o.PrintObj(runObject.Versioned); err != nil { + if err := o.PrintObj(runObject.Object); err != nil { return nil, err } // separate yaml objects @@ -648,60 +655,45 @@ func (o *RunOptions) createGeneratedObject(f cmdutil.Factory, cmd *cobra.Command return nil, err } // run has compiled knowledge of the thing is is creating - groupVersionKinds, _, err := legacyscheme.Scheme.ObjectKinds(obj) + gvks, _, err := scheme.Scheme.ObjectKinds(obj) + if err != nil { + return nil, err + } + mapping, err := mapper.RESTMapping(gvks[0].GroupKind(), gvks[0].Version) if err != nil { return nil, err } - groupVersionKind := groupVersionKinds[0] if len(overrides) > 0 { - codec := runtime.NewCodec(cmdutil.InternalVersionJSONEncoder(), cmdutil.InternalVersionDecoder()) + codec := runtime.NewCodec(scheme.DefaultJSONEncoder(), scheme.Codecs.UniversalDecoder(scheme.Registry.RegisteredGroupVersions()...)) obj, err = cmdutil.Merge(codec, obj, overrides) if err != nil { return nil, err } } - mapping, err := mapper.RESTMapping(groupVersionKind.GroupKind(), groupVersionKind.Version) - if err != nil { - return nil, err - } - client, err := f.ClientForMapping(mapping) - if err != nil { - return nil, err - } - if err := o.Recorder.Record(obj); err != nil { glog.V(4).Infof("error recording current command: %v", err) } - versioned := obj + actualObj := obj if !o.DryRun { - resourceMapper := &resource.Mapper{ - RESTMapper: mapper, - ClientMapper: resource.ClientMapperFunc(f.ClientForMapping), - Decoder: cmdutil.InternalVersionDecoder(), - } - info, err := resourceMapper.InfoForObject(obj, legacyscheme.Scheme, nil) - if err != nil { + if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), obj, scheme.DefaultJSONEncoder()); err != nil { return nil, err } - - if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info.Object, cmdutil.InternalVersionJSONEncoder()); err != nil { + client, err := f.ClientForMapping(mapping) + if err != nil { return nil, err } - - obj, err = resource.NewHelper(client, mapping).Create(namespace, false, info.Object) + actualObj, err = resource.NewHelper(client, mapping).Create(namespace, false, obj) if err != nil { return nil, err } - - versioned = cmdutil.AsDefaultVersionedOrOriginal(info.Object, info.Mapping) } + actualObj = cmdutil.AsDefaultVersionedOrOriginal(actualObj, mapping) + return &RunObject{ - Versioned: versioned, - Object: obj, - Kind: groupVersionKind.Kind, - Mapping: mapping, + Object: actualObj, + Mapping: mapping, }, nil } diff --git a/pkg/kubectl/cmd/set/BUILD b/pkg/kubectl/cmd/set/BUILD index a7dd1844c993c..3e989609f6b01 100644 --- a/pkg/kubectl/cmd/set/BUILD +++ b/pkg/kubectl/cmd/set/BUILD @@ -33,7 +33,6 @@ go_library( "//vendor/github.com/spf13/cobra:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", diff --git a/pkg/kubectl/cmd/set/set_env.go b/pkg/kubectl/cmd/set/set_env.go index ef2a5c61123dd..db9d56c9c0558 100644 --- a/pkg/kubectl/cmd/set/set_env.go +++ b/pkg/kubectl/cmd/set/set_env.go @@ -241,7 +241,7 @@ func (o *EnvOptions) RunEnv() error { if len(o.From) != 0 { b := o.builder(). - Internal(legacyscheme.Scheme). + WithScheme(legacyscheme.Scheme). LocalParam(o.Local). ContinueOnError(). NamespaceParam(o.namespace).DefaultNamespace(). @@ -309,7 +309,7 @@ func (o *EnvOptions) RunEnv() error { } b := o.builder(). - Internal(legacyscheme.Scheme). + WithScheme(legacyscheme.Scheme). LocalParam(o.Local). ContinueOnError(). NamespaceParam(o.namespace).DefaultNamespace(). diff --git a/pkg/kubectl/cmd/set/set_image.go b/pkg/kubectl/cmd/set/set_image.go index e3096aa7537a4..2e32735127640 100644 --- a/pkg/kubectl/cmd/set/set_image.go +++ b/pkg/kubectl/cmd/set/set_image.go @@ -161,7 +161,7 @@ func (o *SetImageOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [ includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false) builder := f.NewBuilder(). - Internal(legacyscheme.Scheme). + WithScheme(legacyscheme.Scheme). LocalParam(o.Local). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). diff --git a/pkg/kubectl/cmd/set/set_resources.go b/pkg/kubectl/cmd/set/set_resources.go index ceb57798d662a..a15b32e30fe5d 100644 --- a/pkg/kubectl/cmd/set/set_resources.go +++ b/pkg/kubectl/cmd/set/set_resources.go @@ -173,7 +173,7 @@ func (o *SetResourcesOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, ar includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false) builder := f.NewBuilder(). - Internal(legacyscheme.Scheme). + WithScheme(legacyscheme.Scheme). LocalParam(o.Local). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). diff --git a/pkg/kubectl/cmd/set/set_selector.go b/pkg/kubectl/cmd/set/set_selector.go index 6c21fb58bb915..f7c75ae2c6e86 100644 --- a/pkg/kubectl/cmd/set/set_selector.go +++ b/pkg/kubectl/cmd/set/set_selector.go @@ -146,7 +146,7 @@ func (o *SetSelectorOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, arg includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false) o.builder = f.NewBuilder(). - Internal(legacyscheme.Scheme). + WithScheme(legacyscheme.Scheme). LocalParam(o.local). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). diff --git a/pkg/kubectl/cmd/set/set_serviceaccount.go b/pkg/kubectl/cmd/set/set_serviceaccount.go index 217bfff776eb7..781f3f17bdd17 100644 --- a/pkg/kubectl/cmd/set/set_serviceaccount.go +++ b/pkg/kubectl/cmd/set/set_serviceaccount.go @@ -152,7 +152,7 @@ func (o *SetServiceAccountOptions) Complete(f cmdutil.Factory, cmd *cobra.Comman resources := args[:len(args)-1] includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false) builder := f.NewBuilder(). - Internal(legacyscheme.Scheme). + WithScheme(legacyscheme.Scheme). LocalParam(o.local). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). diff --git a/pkg/kubectl/cmd/set/set_subject.go b/pkg/kubectl/cmd/set/set_subject.go index fa6a49bf84008..e9ee174dfee34 100644 --- a/pkg/kubectl/cmd/set/set_subject.go +++ b/pkg/kubectl/cmd/set/set_subject.go @@ -138,7 +138,7 @@ func (o *SubjectOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [] includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false) builder := f.NewBuilder(). - Internal(legacyscheme.Scheme). + WithScheme(legacyscheme.Scheme). LocalParam(o.Local). ContinueOnError(). NamespaceParam(o.namespace).DefaultNamespace(). diff --git a/pkg/kubectl/cmd/taint.go b/pkg/kubectl/cmd/taint.go index 945519b8b268f..84b381aa80592 100644 --- a/pkg/kubectl/cmd/taint.go +++ b/pkg/kubectl/cmd/taint.go @@ -172,7 +172,7 @@ func (o *TaintOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st return cmdutil.UsageErrorf(cmd, err.Error()) } o.builder = f.NewBuilder(). - Internal(legacyscheme.Scheme). + WithScheme(legacyscheme.Scheme). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace() if o.selector != "" { diff --git a/pkg/kubectl/cmd/testing/BUILD b/pkg/kubectl/cmd/testing/BUILD index f1348755b0a22..4140652d79976 100644 --- a/pkg/kubectl/cmd/testing/BUILD +++ b/pkg/kubectl/cmd/testing/BUILD @@ -24,11 +24,12 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta/testrestmapper:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", "//vendor/k8s.io/client-go/discovery:go_default_library", + "//vendor/k8s.io/client-go/dynamic:go_default_library", + "//vendor/k8s.io/client-go/dynamic/fake:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/rest/fake:go_default_library", diff --git a/pkg/kubectl/cmd/testing/fake.go b/pkg/kubectl/cmd/testing/fake.go index aacb908762f23..574478ef72de4 100644 --- a/pkg/kubectl/cmd/testing/fake.go +++ b/pkg/kubectl/cmd/testing/fake.go @@ -30,11 +30,12 @@ import ( "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta/testrestmapper" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/client-go/discovery" + "k8s.io/client-go/dynamic" + fakedynamic "k8s.io/client-go/dynamic/fake" "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" @@ -241,10 +242,11 @@ type TestFactory struct { Namespace string ClientConfigVal *restclient.Config CommandVal string + FakeDynamicClient *fakedynamic.FakeDynamicClient tempConfigFile *os.File - UnstructuredClientForMappingFunc func(mapping *meta.RESTMapping) (resource.RESTClient, error) + UnstructuredClientForMappingFunc resource.FakeClientFunc OpenAPISchemaFunc func() (openapi.Resources, error) } @@ -253,8 +255,9 @@ func NewTestFactory() *TestFactory { // to avoid polluting an existing user config. config, configFile := defaultFakeClientConfig() return &TestFactory{ - Factory: cmdutil.NewFactory(config), - tempConfigFile: configFile, + Factory: cmdutil.NewFactory(config), + FakeDynamicClient: fakedynamic.NewSimpleDynamicClient(legacyscheme.Scheme), + tempConfigFile: configFile, } } @@ -309,7 +312,7 @@ func (f *TestFactory) ClientForMapping(mapping *meta.RESTMapping) (resource.REST func (f *TestFactory) UnstructuredClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error) { if f.UnstructuredClientForMappingFunc != nil { - return f.UnstructuredClientForMappingFunc(mapping) + return f.UnstructuredClientForMappingFunc(mapping.GroupVersionKind.GroupVersion()) } return f.UnstructuredClient, nil } @@ -340,17 +343,17 @@ func (f *TestFactory) Command(*cobra.Command, bool) string { func (f *TestFactory) NewBuilder() *resource.Builder { mapper, err := f.RESTMapper() - return resource.NewBuilder( - &resource.Mapper{ - RESTMapper: mapper, - ClientMapper: resource.ClientMapperFunc(f.ClientForMapping), - Decoder: cmdutil.InternalVersionDecoder(), - }, - &resource.Mapper{ - RESTMapper: mapper, - ClientMapper: resource.ClientMapperFunc(f.UnstructuredClientForMapping), - Decoder: unstructured.UnstructuredJSONScheme, + return resource.NewFakeBuilder( + func(version schema.GroupVersion) (resource.RESTClient, error) { + if f.UnstructuredClientForMappingFunc != nil { + return f.UnstructuredClientForMappingFunc(version) + } + if f.UnstructuredClient != nil { + return f.UnstructuredClient, nil + } + return f.Client, nil }, + mapper, f.CategoryExpander(), ).AddError(err) } @@ -402,6 +405,13 @@ func (f *TestFactory) ClientSet() (internalclientset.Interface, error) { return clientset, nil } +func (f *TestFactory) DynamicClient() (dynamic.DynamicInterface, error) { + if f.FakeDynamicClient != nil { + return f.FakeDynamicClient, nil + } + return f.Factory.DynamicClient() +} + func (f *TestFactory) RESTClient() (*restclient.RESTClient, error) { // Swap out the HTTP client out of the client with the fake's version. fakeClient := f.Client.(*fake.RESTClient) diff --git a/pkg/kubectl/cmd/util/editor/editoptions.go b/pkg/kubectl/cmd/util/editor/editoptions.go index d9dc1247fbed0..9a0f0cd11b0ec 100644 --- a/pkg/kubectl/cmd/util/editor/editoptions.go +++ b/pkg/kubectl/cmd/util/editor/editoptions.go @@ -66,7 +66,6 @@ type EditOptions struct { cmdutil.ValidateOptions - ResourceMapper *resource.Mapper OriginalResult *resource.Result EditMode EditMode diff --git a/pkg/kubectl/cmd/util/factory.go b/pkg/kubectl/cmd/util/factory.go index 38ee9742cc33a..d4a2356f17a8f 100644 --- a/pkg/kubectl/cmd/util/factory.go +++ b/pkg/kubectl/cmd/util/factory.go @@ -33,6 +33,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/discovery" + "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" scaleclient "k8s.io/client-go/scale" @@ -92,6 +93,9 @@ type ClientAccessFactory interface { // ClientSet gives you back an internal, generated clientset ClientSet() (internalclientset.Interface, error) + // DynamicClient returns a dynamic client ready for use + DynamicClient() (dynamic.DynamicInterface, error) + // KubernetesClientSet gives you back an external clientset KubernetesClientSet() (*kubernetes.Clientset, error) diff --git a/pkg/kubectl/cmd/util/factory_builder.go b/pkg/kubectl/cmd/util/factory_builder.go index 472f89b5ef924..166659470db09 100644 --- a/pkg/kubectl/cmd/util/factory_builder.go +++ b/pkg/kubectl/cmd/util/factory_builder.go @@ -22,7 +22,6 @@ import ( "os" "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/client-go/dynamic" scaleclient "k8s.io/client-go/scale" "k8s.io/kubernetes/pkg/kubectl" @@ -46,24 +45,12 @@ func NewBuilderFactory(clientAccessFactory ClientAccessFactory, objectMappingFac // NewBuilder returns a new resource builder for structured api objects. func (f *ring2Factory) NewBuilder() *resource.Builder { - clientMapperFunc := resource.ClientMapperFunc(f.objectMappingFactory.ClientForMapping) mapper, mapperErr := f.objectMappingFactory.RESTMapper() - unstructuredClientMapperFunc := resource.ClientMapperFunc(f.objectMappingFactory.UnstructuredClientForMapping) - categoryExpander := f.objectMappingFactory.CategoryExpander() - return resource.NewBuilder( - &resource.Mapper{ - RESTMapper: mapper, - ClientMapper: clientMapperFunc, - Decoder: InternalVersionDecoder(), - }, - &resource.Mapper{ - RESTMapper: mapper, - ClientMapper: unstructuredClientMapperFunc, - Decoder: unstructured.UnstructuredJSONScheme, - }, + f.clientAccessFactory.ClientConfig, + mapper, categoryExpander, ).AddError(mapperErr) } diff --git a/pkg/kubectl/cmd/util/factory_client_access.go b/pkg/kubectl/cmd/util/factory_client_access.go index bcc018f3609e6..dfa09151eb2d1 100644 --- a/pkg/kubectl/cmd/util/factory_client_access.go +++ b/pkg/kubectl/cmd/util/factory_client_access.go @@ -52,6 +52,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" utilflag "k8s.io/apiserver/pkg/util/flag" "k8s.io/client-go/discovery" + "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" @@ -199,6 +200,13 @@ func (f *ring0Factory) ClientSet() (internalclientset.Interface, error) { return internalclientset.NewForConfig(clientConfig) } +func (f *ring0Factory) DynamicClient() (dynamic.DynamicInterface, error) { + clientConfig, err := f.ClientConfig() + if err != nil { + return nil, err + } + return dynamic.NewForConfig(clientConfig) +} func (f *ring0Factory) checkMatchingServerVersion() error { f.checkServerVersion.Do(func() { if !f.requireMatchedServerVersion { diff --git a/pkg/kubectl/cmd/util/factory_test.go b/pkg/kubectl/cmd/util/factory_test.go index 6f0154926b25c..e936a2d980010 100644 --- a/pkg/kubectl/cmd/util/factory_test.go +++ b/pkg/kubectl/cmd/util/factory_test.go @@ -25,7 +25,6 @@ import ( "k8s.io/api/core/v1" apiequality "k8s.io/apimachinery/pkg/api/equality" - "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta/testrestmapper" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -36,7 +35,6 @@ import ( manualfake "k8s.io/client-go/rest/fake" testcore "k8s.io/client-go/testing" "k8s.io/kubernetes/pkg/api/legacyscheme" - "k8s.io/kubernetes/pkg/api/testapi" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" "k8s.io/kubernetes/pkg/controller" @@ -442,10 +440,10 @@ func TestMakePortsString(t *testing.T) { } } -func fakeClient() resource.ClientMapper { - return resource.ClientMapperFunc(func(*meta.RESTMapping) (resource.RESTClient, error) { +func fakeClient() resource.FakeClientFunc { + return func(version schema.GroupVersion) (resource.RESTClient, error) { return &manualfake.RESTClient{}, nil - }) + } } func TestDiscoveryReplaceAliases(t *testing.T) { @@ -473,15 +471,7 @@ func TestDiscoveryReplaceAliases(t *testing.T) { ds := &fakeDiscoveryClient{} mapper := NewShortcutExpander(testrestmapper.TestOnlyStaticRESTMapper(legacyscheme.Registry, legacyscheme.Scheme), ds) - b := resource.NewBuilder( - &resource.Mapper{ - RESTMapper: mapper, - ClientMapper: fakeClient(), - Decoder: testapi.Default.Codec(), - }, - nil, - categories.LegacyCategoryExpander, - ) + b := resource.NewFakeBuilder(fakeClient(), mapper, categories.LegacyCategoryExpander) for _, test := range tests { replaced := b.ReplaceAliases(test.arg) diff --git a/pkg/kubectl/resource/BUILD b/pkg/kubectl/resource/BUILD index c227a2edeac50..a2df4dc864f5b 100644 --- a/pkg/kubectl/resource/BUILD +++ b/pkg/kubectl/resource/BUILD @@ -8,6 +8,7 @@ go_library( name = "go_default_library", srcs = [ "builder.go", + "client.go", "doc.go", "helper.go", "interfaces.go", @@ -29,16 +30,19 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library", "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", + "//vendor/k8s.io/client-go/dynamic:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", ], ) @@ -56,10 +60,10 @@ go_test( ], embed = [":go_default_library"], deps = [ - "//pkg/api/legacyscheme:go_default_library", "//pkg/apis/core/install:go_default_library", "//pkg/kubectl/categories:go_default_library", "//pkg/kubectl/scheme:go_default_library", + "//vendor/github.com/davecgh/go-spew/spew:go_default_library", "//vendor/github.com/ghodss/yaml:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/pkg/kubectl/resource/builder.go b/pkg/kubectl/resource/builder.go index 0f0b53c905da2..f56fe6f03be91 100644 --- a/pkg/kubectl/resource/builder.go +++ b/pkg/kubectl/resource/builder.go @@ -26,10 +26,12 @@ import ( "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/kubernetes/pkg/kubectl/categories" @@ -48,14 +50,22 @@ type Builder struct { categoryExpander categories.CategoryExpander // mapper is set explicitly by resource builders - mapper *Mapper - internal *Mapper - unstructured *Mapper + mapper *mapper + internal *mapper + unstructured *mapper + + // clientConfigFn is a function to produce a client, *if* you need one + clientConfigFn ClientConfigFunc + + restMapper meta.RESTMapper // objectTyper is statically determinant per-command invocation based on your internal or unstructured choice // it does not ever need to rely upon discovery. objectTyper runtime.ObjectTyper + // codecFactory describes which codecs you want to use + codecFactory *serializer.CodecFactory + // local indicates that we cannot make server calls local bool @@ -96,6 +106,9 @@ type Builder struct { export bool schema validation.Schema + + // fakeClientFn is used for testing + fakeClientFn FakeClientFunc } var missingResourceError = fmt.Errorf(`You must provide one or more resources by argument or filename. @@ -128,13 +141,22 @@ type resourceTuple struct { Name string } +type FakeClientFunc func(version schema.GroupVersion) (RESTClient, error) + +func NewFakeBuilder(fakeClientFn FakeClientFunc, restMapper meta.RESTMapper, categoryExpander categories.CategoryExpander) *Builder { + ret := NewBuilder(nil, restMapper, categoryExpander) + ret.fakeClientFn = fakeClientFn + return ret +} + // NewBuilder creates a builder that operates on generic objects. At least one of // internal or unstructured must be specified. // TODO: Add versioned client (although versioned is still lossy) -func NewBuilder(internal, unstructured *Mapper, categoryExpander categories.CategoryExpander) *Builder { +// TODO remove internal and unstructured mapper and instead have them set the negotiated serializer for use in the client +func NewBuilder(clientConfigFn ClientConfigFunc, restMapper meta.RESTMapper, categoryExpander categories.CategoryExpander) *Builder { return &Builder{ - internal: internal, - unstructured: unstructured, + clientConfigFn: clientConfigFn, + restMapper: restMapper, categoryExpander: categoryExpander, requireObject: true, } @@ -194,39 +216,39 @@ func (b *Builder) FilenameParam(enforceNamespace bool, filenameOptions *Filename // reads and then writes an object. Use this mode in preference to Internal unless you // are working with Go types directly. func (b *Builder) Unstructured() *Builder { - if b.unstructured == nil { - b.errs = append(b.errs, fmt.Errorf("no unstructured mapper provided")) - return b - } - if b.mapper != nil && b.mapper != b.unstructured { + if b.mapper != nil { b.errs = append(b.errs, fmt.Errorf("another mapper was already selected, cannot use unstructured types")) return b } - b.mapper = b.unstructured - b.mapper.localFn = b.isLocal - b.objectTyper = unstructuredscheme.NewUnstructuredObjectTyper() + b.mapper = &mapper{ + localFn: b.isLocal, + restMapper: b.restMapper, + clientFn: b.getClient, + decoder: unstructured.UnstructuredJSONScheme, + } + return b } -// Internal updates the builder so that it will convert objects off the wire -// into the internal form if necessary. Using internal types is lossy - fields added -// to the server will not be seen by the client code and may result in failure. Only -// use this mode when working offline, or when generating patches to send to the server. -// Use Unstructured if you are reading an object and performing a POST or PUT. -func (b *Builder) Internal(typer runtime.ObjectTyper) *Builder { - if b.internal == nil { - b.errs = append(b.errs, fmt.Errorf("no internal mapper provided")) - return b - } - if b.mapper != nil && b.mapper != b.internal { +// WithScheme uses the scheme to manage typing, conversion (optional), and decoding. If decodingVersions +// is empty, then you can end up with internal types. You have been warned. +func (b *Builder) WithScheme(scheme *runtime.Scheme, decodingVersions ...schema.GroupVersion) *Builder { + if b.mapper != nil { b.errs = append(b.errs, fmt.Errorf("another mapper was already selected, cannot use internal types")) return b } - b.mapper = b.internal - b.mapper.localFn = b.isLocal + b.objectTyper = scheme + codecFactory := serializer.NewCodecFactory(scheme) + b.codecFactory = &codecFactory + + b.mapper = &mapper{ + localFn: b.isLocal, + restMapper: b.restMapper, + clientFn: b.getClient, + decoder: b.codecFactory.UniversalDecoder(decodingVersions...), + } - b.objectTyper = typer return b } @@ -241,9 +263,6 @@ func (b *Builder) LocalParam(local bool) *Builder { // Local will avoid asking the server for results. func (b *Builder) Local() *Builder { b.local = true - mapper := *b.mapper - mapper.ClientMapper = DisabledClientForMapping{ClientMapper: mapper.ClientMapper} - b.mapper = &mapper return b } @@ -252,7 +271,7 @@ func (b *Builder) isLocal() bool { } // Mapper returns a copy of the current mapper. -func (b *Builder) Mapper() *Mapper { +func (b *Builder) Mapper() *mapper { mapper := *b.mapper return &mapper } @@ -636,13 +655,13 @@ func (b *Builder) mappingFor(resourceOrKindArg string) (*meta.RESTMapping, error fullySpecifiedGVR, groupResource := schema.ParseResourceArg(resourceOrKindArg) gvk := schema.GroupVersionKind{} if fullySpecifiedGVR != nil { - gvk, _ = b.mapper.RESTMapper.KindFor(*fullySpecifiedGVR) + gvk, _ = b.mapper.restMapper.KindFor(*fullySpecifiedGVR) } if gvk.Empty() { - gvk, _ = b.mapper.RESTMapper.KindFor(groupResource.WithVersion("")) + gvk, _ = b.mapper.restMapper.KindFor(groupResource.WithVersion("")) } if !gvk.Empty() { - return b.mapper.RESTMapper.RESTMapping(gvk.GroupKind(), gvk.Version) + return b.mapper.restMapper.RESTMapping(gvk.GroupKind(), gvk.Version) } fullySpecifiedGVK, groupKind := schema.ParseKindArg(resourceOrKindArg) @@ -652,12 +671,12 @@ func (b *Builder) mappingFor(resourceOrKindArg string) (*meta.RESTMapping, error } if !fullySpecifiedGVK.Empty() { - if mapping, err := b.mapper.RESTMapper.RESTMapping(fullySpecifiedGVK.GroupKind(), fullySpecifiedGVK.Version); err == nil { + if mapping, err := b.mapper.restMapper.RESTMapping(fullySpecifiedGVK.GroupKind(), fullySpecifiedGVK.Version); err == nil { return mapping, nil } } - mapping, err := b.mapper.RESTMapper.RESTMapping(groupKind, gvk.Version) + mapping, err := b.mapper.restMapper.RESTMapping(groupKind, gvk.Version) if err != nil { // if we error out here, it is because we could not match a resource or a kind // for the given argument. To maintain consistency with previous behavior, @@ -782,7 +801,7 @@ func (b *Builder) visitBySelector() *Result { visitors := []Visitor{} for _, mapping := range mappings { - client, err := b.mapper.ClientMapper.ClientForMapping(mapping) + client, err := b.getClient(mapping.GroupVersionKind.GroupVersion()) if err != nil { result.err = err return result @@ -803,6 +822,18 @@ func (b *Builder) visitBySelector() *Result { return result } +func (b *Builder) getClient(gv schema.GroupVersion) (RESTClient, error) { + if b.fakeClientFn != nil { + return b.fakeClientFn(gv) + } + + if b.codecFactory != nil { + return b.clientConfigFn.clientForGroupVersion(gv, b.codecFactory) + } + + return b.clientConfigFn.unstructuredClientForGroupVersion(gv) +} + func (b *Builder) visitByResource() *Result { // if b.singleItemImplied is false, this could be by default, so double-check length // of resourceTuples to determine if in fact it is singleItemImplied or not @@ -832,7 +863,7 @@ func (b *Builder) visitByResource() *Result { if _, ok := clients[s]; ok { continue } - client, err := b.mapper.ClientMapper.ClientForMapping(mapping) + client, err := b.getClient(mapping.GroupVersionKind.GroupVersion()) if err != nil { result.err = err return result @@ -910,7 +941,7 @@ func (b *Builder) visitByName() *Result { } mapping := mappings[0] - client, err := b.mapper.ClientMapper.ClientForMapping(mapping) + client, err := b.getClient(mapping.GroupVersionKind.GroupVersion()) if err != nil { result.err = err return result diff --git a/pkg/kubectl/resource/builder_test.go b/pkg/kubectl/resource/builder_test.go index 3a3e57f1e85a5..0d115f4d5ab93 100644 --- a/pkg/kubectl/resource/builder_test.go +++ b/pkg/kubectl/resource/builder_test.go @@ -47,11 +47,11 @@ import ( "k8s.io/client-go/rest/fake" restclientwatch "k8s.io/client-go/rest/watch" utiltesting "k8s.io/client-go/util/testing" - "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/kubectl/categories" "k8s.io/kubernetes/pkg/kubectl/scheme" // install the pod scheme into the legacy scheme for test typer resolution + "github.com/davecgh/go-spew/spew" _ "k8s.io/kubernetes/pkg/apis/core/install" ) @@ -76,14 +76,14 @@ func watchBody(events ...watch.Event) string { return buf.String() } -func fakeClient() ClientMapper { - return ClientMapperFunc(func(*meta.RESTMapping) (RESTClient, error) { +func fakeClient() FakeClientFunc { + return func(version schema.GroupVersion) (RESTClient, error) { return &fake.RESTClient{}, nil - }) + } } -func fakeClientWith(testName string, t *testing.T, data map[string]string) ClientMapper { - return ClientMapperFunc(func(*meta.RESTMapping) (RESTClient, error) { +func fakeClientWith(testName string, t *testing.T, data map[string]string) FakeClientFunc { + return func(version schema.GroupVersion) (RESTClient, error) { return &fake.RESTClient{ GroupVersion: corev1GV, NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}, @@ -106,7 +106,7 @@ func fakeClientWith(testName string, t *testing.T, data map[string]string) Clien }, nil }), }, nil - }) + } } func testData() (*v1.PodList, *v1.ServiceList) { @@ -272,16 +272,9 @@ func newDefaultBuilder() *Builder { return newDefaultBuilderWith(fakeClient()) } -func newDefaultBuilderWith(client ClientMapper) *Builder { - return NewBuilder( - &Mapper{ - RESTMapper: restmapper, - ClientMapper: client, - Decoder: corev1Codec, - }, - nil, - categories.LegacyCategoryExpander, - ).Internal(legacyscheme.Scheme) +func newDefaultBuilderWith(fakeClientFn FakeClientFunc) *Builder { + return NewFakeBuilder(fakeClientFn, restmapper, categories.LegacyCategoryExpander). + WithScheme(scheme.Scheme, scheme.Registry.RegisteredGroupVersions()...) } func TestPathBuilderAndVersionedObjectNotDefaulted(t *testing.T) { @@ -407,11 +400,11 @@ func TestPathBuilderWithMultiple(t *testing.T) { switch test.object.(type) { case *v1.Pod: if _, ok := v.Object.(*v1.Pod); !ok || v.Name != test.expectedNames[i] || v.Namespace != "test" { - t.Errorf("unexpected info: %#v", v) + t.Errorf("unexpected info: %v", spew.Sdump(v.Object)) } case *v1.ReplicationController: if _, ok := v.Object.(*v1.ReplicationController); !ok || v.Name != test.expectedNames[i] || v.Namespace != "test" { - t.Errorf("unexpected info: %#v", v) + t.Errorf("unexpected info: %v", spew.Sdump(v.Object)) } } } diff --git a/pkg/kubectl/resource/client.go b/pkg/kubectl/resource/client.go new file mode 100644 index 0000000000000..aa1d232a9aa8f --- /dev/null +++ b/pkg/kubectl/resource/client.go @@ -0,0 +1,59 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resource + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" +) + +// TODO require negotiatedSerializer. leaving it optional lets us plumb current behavior and deal with the difference after major plumbing is complete +func (clientConfigFn ClientConfigFunc) clientForGroupVersion(gv schema.GroupVersion, negotiatedSerializer runtime.NegotiatedSerializer) (RESTClient, error) { + cfg, err := clientConfigFn() + if err != nil { + return nil, err + } + if negotiatedSerializer != nil { + cfg.ContentConfig.NegotiatedSerializer = negotiatedSerializer + } + cfg.GroupVersion = &gv + if len(gv.Group) == 0 { + cfg.APIPath = "/api" + } else { + cfg.APIPath = "/apis" + } + + return rest.RESTClientFor(cfg) +} + +func (clientConfigFn ClientConfigFunc) unstructuredClientForGroupVersion(gv schema.GroupVersion) (RESTClient, error) { + cfg, err := clientConfigFn() + if err != nil { + return nil, err + } + cfg.ContentConfig = dynamic.ContentConfig() + cfg.GroupVersion = &gv + if len(gv.Group) == 0 { + cfg.APIPath = "/api" + } else { + cfg.APIPath = "/apis" + } + + return rest.RESTClientFor(cfg) +} diff --git a/pkg/kubectl/resource/interfaces.go b/pkg/kubectl/resource/interfaces.go index 383649e57733c..359a014846c42 100644 --- a/pkg/kubectl/resource/interfaces.go +++ b/pkg/kubectl/resource/interfaces.go @@ -17,38 +17,24 @@ limitations under the License. package resource import ( - "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/types" - client "k8s.io/client-go/rest" + "k8s.io/client-go/rest" ) -type RESTMapperFunc func() (meta.RESTMapper, error) +type ClientConfigFunc func() (*rest.Config, error) // RESTClient is a client helper for dealing with RESTful resources // in a generic way. type RESTClient interface { - Get() *client.Request - Post() *client.Request - Patch(types.PatchType) *client.Request - Delete() *client.Request - Put() *client.Request -} - -// ClientMapper abstracts retrieving a Client for mapped objects. -type ClientMapper interface { - ClientForMapping(mapping *meta.RESTMapping) (RESTClient, error) -} - -// ClientMapperFunc implements ClientMapper for a function -type ClientMapperFunc func(mapping *meta.RESTMapping) (RESTClient, error) - -// ClientForMapping implements ClientMapper -func (f ClientMapperFunc) ClientForMapping(mapping *meta.RESTMapping) (RESTClient, error) { - return f(mapping) + Get() *rest.Request + Post() *rest.Request + Patch(types.PatchType) *rest.Request + Delete() *rest.Request + Put() *rest.Request } // RequestTransform is a function that is given a chance to modify the outgoing request. -type RequestTransform func(*client.Request) +type RequestTransform func(*rest.Request) // NewClientWithOptions wraps the provided RESTClient and invokes each transform on each // newly created request. @@ -61,26 +47,26 @@ type clientOptions struct { transforms []RequestTransform } -func (c *clientOptions) modify(req *client.Request) *client.Request { +func (c *clientOptions) modify(req *rest.Request) *rest.Request { for _, transform := range c.transforms { transform(req) } return req } -func (c *clientOptions) Get() *client.Request { +func (c *clientOptions) Get() *rest.Request { return c.modify(c.c.Get()) } -func (c *clientOptions) Post() *client.Request { +func (c *clientOptions) Post() *rest.Request { return c.modify(c.c.Post()) } -func (c *clientOptions) Patch(t types.PatchType) *client.Request { +func (c *clientOptions) Patch(t types.PatchType) *rest.Request { return c.modify(c.c.Patch(t)) } -func (c *clientOptions) Delete() *client.Request { +func (c *clientOptions) Delete() *rest.Request { return c.modify(c.c.Delete()) } -func (c *clientOptions) Put() *client.Request { +func (c *clientOptions) Put() *rest.Request { return c.modify(c.c.Put()) } diff --git a/pkg/kubectl/resource/mapper.go b/pkg/kubectl/resource/mapper.go index c188a19f8d992..0453e5423727a 100644 --- a/pkg/kubectl/resource/mapper.go +++ b/pkg/kubectl/resource/mapper.go @@ -27,20 +27,20 @@ import ( // Mapper is a convenience struct for holding references to the interfaces // needed to create Info for arbitrary objects. -type Mapper struct { +type mapper struct { // localFn indicates the call can't make server requests localFn func() bool - RESTMapper meta.RESTMapper - ClientMapper ClientMapper - Decoder runtime.Decoder + restMapper meta.RESTMapper + clientFn func(version schema.GroupVersion) (RESTClient, error) + decoder 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) { - obj, gvk, err := m.Decoder.Decode(data, nil, nil) +func (m *mapper) infoForData(data []byte, source string) (*Info, error) { + obj, gvk, err := m.decoder.Decode(data, nil, nil) if err != nil { return nil, fmt.Errorf("unable to decode %q: %v", source, err) } @@ -59,13 +59,13 @@ func (m *Mapper) InfoForData(data []byte, source string) (*Info, error) { } if m.localFn == nil || !m.localFn() { - mapping, err := m.RESTMapper.RESTMapping(gvk.GroupKind(), gvk.Version) + mapping, err := m.restMapper.RESTMapping(gvk.GroupKind(), gvk.Version) if err != nil { return nil, fmt.Errorf("unable to recognize %q: %v", source, err) } ret.Mapping = mapping - client, err := m.ClientMapper.ClientForMapping(mapping) + client, err := m.clientFn(gvk.GroupVersion()) if err != nil { return nil, fmt.Errorf("unable to connect to a server to handle %q: %v", mapping.Resource, err) } @@ -78,7 +78,7 @@ func (m *Mapper) InfoForData(data []byte, source string) (*Info, error) { // InfoForObject creates an Info object for the given Object. An error is returned // if the object cannot be introspected. Name and namespace will be set into Info // if the mapping's MetadataAccessor can retrieve them. -func (m *Mapper) InfoForObject(obj runtime.Object, typer runtime.ObjectTyper, preferredGVKs []schema.GroupVersionKind) (*Info, error) { +func (m *mapper) infoForObject(obj runtime.Object, typer runtime.ObjectTyper, preferredGVKs []schema.GroupVersionKind) (*Info, error) { groupVersionKinds, _, err := typer.ObjectKinds(obj) if err != nil { return nil, fmt.Errorf("unable to get type info from the object %q: %v", reflect.TypeOf(obj), err) @@ -101,13 +101,13 @@ func (m *Mapper) InfoForObject(obj runtime.Object, typer runtime.ObjectTyper, pr } if m.localFn == nil || !m.localFn() { - mapping, err := m.RESTMapper.RESTMapping(gvk.GroupKind(), gvk.Version) + mapping, err := m.restMapper.RESTMapping(gvk.GroupKind(), gvk.Version) if err != nil { return nil, fmt.Errorf("unable to recognize %v", err) } ret.Mapping = mapping - client, err := m.ClientMapper.ClientForMapping(mapping) + client, err := m.clientFn(gvk.GroupVersion()) if err != nil { return nil, fmt.Errorf("unable to connect to a server to handle %q: %v", mapping.Resource, err) } @@ -152,13 +152,3 @@ func preferredObjectKind(possibilities []schema.GroupVersionKind, preferences [] // Just pick the first return possibilities[0] } - -// 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 -} diff --git a/pkg/kubectl/resource/result.go b/pkg/kubectl/resource/result.go index 57f66e125d1ff..b8722afe69762 100644 --- a/pkg/kubectl/resource/result.go +++ b/pkg/kubectl/resource/result.go @@ -42,7 +42,7 @@ type Result struct { singleItemImplied bool targetsSingleItems bool - mapper *Mapper + mapper *mapper ignoreErrors []utilerrors.Matcher // populated by a call to Infos @@ -77,7 +77,7 @@ func (r *Result) IgnoreErrors(fns ...ErrMatchFunc) *Result { } // Mapper returns a copy of the builder's mapper. -func (r *Result) Mapper() *Mapper { +func (r *Result) Mapper() *mapper { return r.mapper } diff --git a/pkg/kubectl/resource/visitor.go b/pkg/kubectl/resource/visitor.go index bfe8be21d2a81..20550940ebf7a 100644 --- a/pkg/kubectl/resource/visitor.go +++ b/pkg/kubectl/resource/visitor.go @@ -372,12 +372,12 @@ func (v ContinueOnErrorVisitor) Visit(fn VisitorFunc) error { type FlattenListVisitor struct { visitor Visitor typer runtime.ObjectTyper - mapper *Mapper + mapper *mapper } // NewFlattenListVisitor creates a visitor that will expand list style runtime.Objects // into individual items and then visit them individually. -func NewFlattenListVisitor(v Visitor, typer runtime.ObjectTyper, mapper *Mapper) Visitor { +func NewFlattenListVisitor(v Visitor, typer runtime.ObjectTyper, mapper *mapper) Visitor { return FlattenListVisitor{v, typer, mapper} } @@ -393,7 +393,7 @@ func (v FlattenListVisitor) Visit(fn VisitorFunc) error { if err != nil { return fn(info, nil) } - if errs := runtime.DecodeList(items, v.mapper.Decoder); len(errs) > 0 { + if errs := runtime.DecodeList(items, v.mapper.decoder); len(errs) > 0 { return utilerrors.NewAggregate(errs) } @@ -404,7 +404,7 @@ func (v FlattenListVisitor) Visit(fn VisitorFunc) error { } for i := range items { - item, err := v.mapper.InfoForObject(items[i], v.typer, preferredGVKs) + item, err := v.mapper.infoForObject(items[i], v.typer, preferredGVKs) if err != nil { return err } @@ -433,7 +433,7 @@ func ignoreFile(path string, extensions []string) bool { } // FileVisitorForSTDIN return a special FileVisitor just for STDIN -func FileVisitorForSTDIN(mapper *Mapper, schema validation.Schema) Visitor { +func FileVisitorForSTDIN(mapper *mapper, schema validation.Schema) Visitor { return &FileVisitor{ Path: constSTDINstr, StreamVisitor: NewStreamVisitor(nil, mapper, constSTDINstr, schema), @@ -443,7 +443,7 @@ func FileVisitorForSTDIN(mapper *Mapper, schema validation.Schema) Visitor { // ExpandPathsToFileVisitors will return a slice of FileVisitors that will handle files from the provided path. // After FileVisitors open the files, they will pass an io.Reader to a StreamVisitor to do the reading. (stdin // is also taken care of). Paths argument also accepts a single file, and will return a single visitor -func ExpandPathsToFileVisitors(mapper *Mapper, paths string, recursive bool, extensions []string, schema validation.Schema) ([]Visitor, error) { +func ExpandPathsToFileVisitors(mapper *mapper, paths string, recursive bool, extensions []string, schema validation.Schema) ([]Visitor, error) { var visitors []Visitor err := filepath.Walk(paths, func(path string, fi os.FileInfo, err error) error { if err != nil { @@ -510,17 +510,17 @@ func (v *FileVisitor) Visit(fn VisitorFunc) error { // a stream decoder method on runtime.Codec to properly handle this. type StreamVisitor struct { io.Reader - *Mapper + *mapper Source string Schema validation.Schema } // NewStreamVisitor is a helper function that is useful when we want to change the fields of the struct but keep calls the same. -func NewStreamVisitor(r io.Reader, mapper *Mapper, source string, schema validation.Schema) *StreamVisitor { +func NewStreamVisitor(r io.Reader, mapper *mapper, source string, schema validation.Schema) *StreamVisitor { return &StreamVisitor{ Reader: r, - Mapper: mapper, + mapper: mapper, Source: source, Schema: schema, } @@ -545,7 +545,7 @@ func (v *StreamVisitor) Visit(fn VisitorFunc) error { if err := ValidateSchema(ext.Raw, v.Schema); err != nil { return fmt.Errorf("error validating %q: %v", v.Source, err) } - info, err := v.InfoForData(ext.Raw, v.Source) + info, err := v.infoForData(ext.Raw, v.Source) if err != nil { if fnErr := fn(info, err); fnErr != nil { return fnErr