diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 4c8535e6..9c8f2b68 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -944,7 +944,7 @@ }, { "ImportPath": "k8s.io/component-base", - "Rev": "81d9ea233619" + "Rev": "cc3f82d8eb26" }, { "ImportPath": "k8s.io/component-helpers", diff --git a/go.mod b/go.mod index b79b7289..28d25518 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( k8s.io/apimachinery v0.0.0-20210202200849-9e39a13d2cab k8s.io/cli-runtime v0.0.0-20210202202902-984374fbd3bd k8s.io/client-go v0.0.0-20210203041231-93ce9718ffcd - k8s.io/component-base v0.0.0-20210202201701-81d9ea233619 + k8s.io/component-base v0.0.0-20210205131209-cc3f82d8eb26 k8s.io/component-helpers v0.0.0-20210202201756-e42f75bf9b21 k8s.io/klog/v2 v2.5.0 k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd @@ -54,7 +54,7 @@ replace ( k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20210202202902-984374fbd3bd k8s.io/client-go => k8s.io/client-go v0.0.0-20210203041231-93ce9718ffcd k8s.io/code-generator => k8s.io/code-generator v0.0.0-20210202200712-b6eef682227f - k8s.io/component-base => k8s.io/component-base v0.0.0-20210202201701-81d9ea233619 + k8s.io/component-base => k8s.io/component-base v0.0.0-20210205131209-cc3f82d8eb26 k8s.io/component-helpers => k8s.io/component-helpers v0.0.0-20210202201756-e42f75bf9b21 k8s.io/metrics => k8s.io/metrics v0.0.0-20210202202759-ef93df266531 ) diff --git a/go.sum b/go.sum index 04cd543e..96b20415 100644 --- a/go.sum +++ b/go.sum @@ -641,7 +641,7 @@ k8s.io/apimachinery v0.0.0-20210202200849-9e39a13d2cab/go.mod h1:usCLrfBNFPxV+np k8s.io/cli-runtime v0.0.0-20210202202902-984374fbd3bd/go.mod h1:vN4I0m+pP/HOTtiHkCpNvUcrRPPu0/FU8aUKMibl7d4= k8s.io/client-go v0.0.0-20210203041231-93ce9718ffcd/go.mod h1:mJubEonhU7Puf25vxba4hDwWZKKp6ErQbXGPi9sLXhU= k8s.io/code-generator v0.0.0-20210202200712-b6eef682227f/go.mod h1:O7FXIFFMbeLstjVDD1gKtnexuIo2JF8jkudWpXyjVeo= -k8s.io/component-base v0.0.0-20210202201701-81d9ea233619/go.mod h1:usKilGzmoexy3tmMYXYM2r6QFIS+drbtnot8qjqIXe8= +k8s.io/component-base v0.0.0-20210205131209-cc3f82d8eb26/go.mod h1:LVHaG2+LhDg6yQ7C6Tcbs0HCyZvHNLE19fS6qlaWBcQ= k8s.io/component-helpers v0.0.0-20210202201756-e42f75bf9b21/go.mod h1:8OBU1/nmDh8pjPoisMrILN3oAmgF9879iToT+jg9vjU= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= diff --git a/pkg/cmd/create/create_namespace.go b/pkg/cmd/create/create_namespace.go index 49a4b3c9..cb7fdb7e 100644 --- a/pkg/cmd/create/create_namespace.go +++ b/pkg/cmd/create/create_namespace.go @@ -17,12 +17,20 @@ limitations under the License. package create import ( + "context" + "fmt" "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/cli-runtime/pkg/resource" + coreclient "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util" "k8s.io/cli-runtime/pkg/genericclioptions" cmdutil "k8s.io/kubectl/pkg/cmd/util" - "k8s.io/kubectl/pkg/generate" - generateversioned "k8s.io/kubectl/pkg/generate/versioned" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" ) @@ -36,16 +44,37 @@ var ( kubectl create namespace my-namespace`)) ) -// NamespaceOpts is the options for 'create namespace' sub command -type NamespaceOpts struct { - CreateSubcommandOptions *CreateSubcommandOptions +// NamespaceOptions is the options for 'create namespace' sub command +type NamespaceOptions struct { + // PrintFlags holds options necessary for obtaining a printer + PrintFlags *genericclioptions.PrintFlags + // Name of resource being created + Name string + + DryRunStrategy cmdutil.DryRunStrategy + DryRunVerifier *resource.DryRunVerifier + CreateAnnotation bool + FieldManager string + + Client *coreclient.CoreV1Client + + PrintObj func(obj runtime.Object) error + + genericclioptions.IOStreams +} + +// NewNamespaceOptions creates a new *NamespaceOptions with sane defaults +func NewNamespaceOptions(ioStreams genericclioptions.IOStreams) *NamespaceOptions { + return &NamespaceOptions{ + PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme), + IOStreams: ioStreams, + } } // NewCmdCreateNamespace is a macro command to create a new namespace func NewCmdCreateNamespace(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { - options := &NamespaceOpts{ - CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams), - } + + o := NewNamespaceOptions(ioStreams) cmd := &cobra.Command{ Use: "namespace NAME [--dry-run=server|client|none]", @@ -55,40 +84,104 @@ func NewCmdCreateNamespace(f cmdutil.Factory, ioStreams genericclioptions.IOStre Long: namespaceLong, Example: namespaceExample, Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(options.Complete(f, cmd, args)) - cmdutil.CheckErr(options.Run()) + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate()) + cmdutil.CheckErr(o.Run()) }, } - options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd) + o.PrintFlags.AddFlags(cmd) cmdutil.AddApplyAnnotationFlags(cmd) cmdutil.AddValidateFlags(cmd) - cmdutil.AddGeneratorFlags(cmd, generateversioned.NamespaceV1GeneratorName) - cmdutil.AddFieldManagerFlagVar(cmd, &options.CreateSubcommandOptions.FieldManager, "kubectl-create") + cmdutil.AddDryRunFlag(cmd) + cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create") return cmd } // Complete completes all the required options -func (o *NamespaceOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { +func (o *NamespaceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { name, err := NameFromCommandArgs(cmd, args) if err != nil { return err } - var generator generate.StructuredGenerator - switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName { - case generateversioned.NamespaceV1GeneratorName: - generator = &generateversioned.NamespaceGeneratorV1{Name: name} - default: - return errUnsupportedGenerator(cmd, generatorName) + restConfig, err := f.ToRESTConfig() + if err != nil { + return err + } + o.Client, err = coreclient.NewForConfig(restConfig) + if err != nil { + return err } - return o.CreateSubcommandOptions.Complete(f, cmd, args, generator) + o.Name = name + o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd) + if err != nil { + return err + } + dynamicClient, err := f.DynamicClient() + if err != nil { + return err + } + discoveryClient, err := f.ToDiscoveryClient() + if err != nil { + return err + } + o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient) + o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag) + cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + o.PrintObj = func(obj runtime.Object) error { + return printer.PrintObj(obj, o.Out) + } + return nil } // Run calls the CreateSubcommandOptions.Run in NamespaceOpts instance -func (o *NamespaceOpts) Run() error { - return o.CreateSubcommandOptions.Run() +func (o *NamespaceOptions) Run() error { + namespace := o.createNamespace() + if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, namespace, scheme.DefaultJSONEncoder()); err != nil { + return err + } + + if o.DryRunStrategy != cmdutil.DryRunClient { + createOptions := metav1.CreateOptions{} + if o.FieldManager != "" { + createOptions.FieldManager = o.FieldManager + } + if o.DryRunStrategy == cmdutil.DryRunServer { + if err := o.DryRunVerifier.HasSupport(namespace.GroupVersionKind()); err != nil { + return err + } + createOptions.DryRun = []string{metav1.DryRunAll} + } + var err error + namespace, err = o.Client.Namespaces().Create(context.TODO(), namespace, createOptions) + if err != nil { + return err + } + } + return o.PrintObj(namespace) +} + +// createNamespace outputs a namespace object using the configured fields +func (o *NamespaceOptions) createNamespace() *corev1.Namespace { + namespace := &corev1.Namespace{ + TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String(), Kind: "Namespace"}, + ObjectMeta: metav1.ObjectMeta{Name: o.Name}, + } + return namespace +} + +// Validate validates required fields are set to support structured generation +func (o *NamespaceOptions) Validate() error { + if len(o.Name) == 0 { + return fmt.Errorf("name must be specified") + } + return nil } diff --git a/pkg/cmd/create/create_namespace_test.go b/pkg/cmd/create/create_namespace_test.go index c8581d93..9589cfc7 100644 --- a/pkg/cmd/create/create_namespace_test.go +++ b/pkg/cmd/create/create_namespace_test.go @@ -17,45 +17,39 @@ limitations under the License. package create import ( - "net/http" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "testing" - "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/client-go/rest/fake" - cmdtesting "k8s.io/kubectl/pkg/cmd/testing" - "k8s.io/kubectl/pkg/scheme" + apiequality "k8s.io/apimachinery/pkg/api/equality" ) func TestCreateNamespace(t *testing.T) { - namespaceObject := &v1.Namespace{} - namespaceObject.Name = "my-namespace" - tf := cmdtesting.NewTestFactory() - defer tf.Cleanup() - - codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) - ns := scheme.Codecs.WithoutConversion() - - 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" && m == "POST": - return &http.Response{StatusCode: http.StatusCreated, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, namespaceObject)}, nil - default: - t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) - return nil, nil - } - }), + tests := map[string]struct { + options *NamespaceOptions + expected *corev1.Namespace + }{ + "success_create": { + options: &NamespaceOptions{ + Name: "my-namespace", + }, + expected: &corev1.Namespace{ + TypeMeta: metav1.TypeMeta{ + Kind: "Namespace", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-namespace", + }, + }, + }, } - ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams() - cmd := NewCmdCreateNamespace(tf, ioStreams) - cmd.Flags().Set("output", "name") - cmd.Run(cmd, []string{namespaceObject.Name}) - expectedOutput := "namespace/" + namespaceObject.Name + "\n" - if buf.String() != expectedOutput { - t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String()) + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + namespace := tc.options.createNamespace() + if !apiequality.Semantic.DeepEqual(namespace, tc.expected) { + t.Errorf("expected:\n%#v\ngot:\n%#v", tc.expected, namespace) + } + }) } }