Remove the dependency between create namespace command and generators (#96556)

* Remove the dependency between create namespace command and generators

* Update create_namespace.go

format the file

rename "kruntime" package to "runtime"

remove the reliance of generators

replace dynamic client with typed client

rename "options" to "o" in "NewNamespaceOptions" fun for better reading and comparison with other create cmd

remove Namespace and EnforceNamespace from NamespaceOptions

remove Mapper from NamespaceOptions

refactory the "Run" fun

refactory the "Run" fun

Update create_namespace.go and create_namespace_test.go

* Update create_namespace.go and create_namespace_test.go

* fix createNamespace function

* fix createNamespace function

* fix createNamespace function

* remove the wrong comment in NamespaceOptions

* add validate operation for cobra.Command

* add some unit tests

* add some unit tests

* remove the call of Validate() from createNamespace() and update return type of createNamespace()

* update test suite for the new createNamespace()

Kubernetes-commit: 6990d75625b6aaa32c1aa5a99a174775868263bc
This commit is contained in:
CaiyuhangC 2021-02-06 00:44:52 +08:00 committed by Kubernetes Publisher
parent b5589ab246
commit 1fa4fffb96
5 changed files with 148 additions and 61 deletions

2
Godeps/Godeps.json generated
View File

@ -944,7 +944,7 @@
},
{
"ImportPath": "k8s.io/component-base",
"Rev": "81d9ea233619"
"Rev": "cc3f82d8eb26"
},
{
"ImportPath": "k8s.io/component-helpers",

4
go.mod
View File

@ -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
)

2
go.sum
View File

@ -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=

View File

@ -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
}

View File

@ -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",
},
},
},
}
}),
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)
}
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())
})
}
}