Skip to content

Commit 1b6de44

Browse files
authored
Merge pull request kubernetes#90676 from aubm/refactor-command-kubectl-create-deploy
Remove the dependency between create deploy command and generators
2 parents ec23b61 + 0cbb208 commit 1b6de44

File tree

3 files changed

+148
-132
lines changed

3 files changed

+148
-132
lines changed

staging/src/k8s.io/kubectl/pkg/cmd/create/BUILD

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ go_library(
2424
importpath = "k8s.io/kubectl/pkg/cmd/create",
2525
visibility = ["//build/visible_to:pkg_kubectl_cmd_create_CONSUMERS"],
2626
deps = [
27+
"//staging/src/k8s.io/api/apps/v1:go_default_library",
2728
"//staging/src/k8s.io/api/batch/v1:go_default_library",
2829
"//staging/src/k8s.io/api/batch/v1beta1:go_default_library",
2930
"//staging/src/k8s.io/api/core/v1:go_default_library",
@@ -33,11 +34,13 @@ go_library(
3334
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
3435
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
3536
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
37+
"//staging/src/k8s.io/apimachinery/pkg/util/rand:go_default_library",
3638
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
3739
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
3840
"//staging/src/k8s.io/cli-runtime/pkg/printers:go_default_library",
3941
"//staging/src/k8s.io/cli-runtime/pkg/resource:go_default_library",
4042
"//staging/src/k8s.io/client-go/dynamic:go_default_library",
43+
"//staging/src/k8s.io/client-go/kubernetes/typed/apps/v1:go_default_library",
4144
"//staging/src/k8s.io/client-go/kubernetes/typed/batch/v1:go_default_library",
4245
"//staging/src/k8s.io/client-go/kubernetes/typed/batch/v1beta1:go_default_library",
4346
"//staging/src/k8s.io/client-go/kubernetes/typed/rbac/v1:go_default_library",
@@ -95,7 +98,6 @@ go_test(
9598
"//staging/src/k8s.io/client-go/rest/fake:go_default_library",
9699
"//staging/src/k8s.io/kubectl/pkg/cmd/testing:go_default_library",
97100
"//staging/src/k8s.io/kubectl/pkg/cmd/util:go_default_library",
98-
"//staging/src/k8s.io/kubectl/pkg/generate/versioned:go_default_library",
99101
"//staging/src/k8s.io/kubectl/pkg/scheme:go_default_library",
100102
"//vendor/github.com/stretchr/testify/assert:go_default_library",
101103
],

staging/src/k8s.io/kubectl/pkg/cmd/create/create_deployment.go

Lines changed: 142 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,21 @@ limitations under the License.
1717
package create
1818

1919
import (
20-
"github.com/spf13/cobra"
20+
"context"
21+
"fmt"
22+
"strings"
2123

24+
"github.com/spf13/cobra"
25+
appsv1 "k8s.io/api/apps/v1"
26+
v1 "k8s.io/api/core/v1"
27+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28+
"k8s.io/apimachinery/pkg/runtime"
29+
utilrand "k8s.io/apimachinery/pkg/util/rand"
2230
"k8s.io/cli-runtime/pkg/genericclioptions"
31+
"k8s.io/cli-runtime/pkg/resource"
32+
appsv1client "k8s.io/client-go/kubernetes/typed/apps/v1"
2333
cmdutil "k8s.io/kubectl/pkg/cmd/util"
24-
"k8s.io/kubectl/pkg/generate"
25-
generateversioned "k8s.io/kubectl/pkg/generate/versioned"
34+
"k8s.io/kubectl/pkg/scheme"
2635
"k8s.io/kubectl/pkg/util/i18n"
2736
"k8s.io/kubectl/pkg/util/templates"
2837
)
@@ -38,22 +47,35 @@ var (
3847

3948
// DeploymentOpts is returned by NewCmdCreateDeployment
4049
type DeploymentOpts struct {
41-
CreateSubcommandOptions *CreateSubcommandOptions
50+
PrintFlags *genericclioptions.PrintFlags
51+
PrintObj func(obj runtime.Object) error
52+
53+
Name string
54+
Images []string
55+
Namespace string
56+
EnforceNamespace bool
57+
FieldManager string
58+
59+
Client appsv1client.AppsV1Interface
60+
DryRunStrategy cmdutil.DryRunStrategy
61+
DryRunVerifier *resource.DryRunVerifier
62+
63+
genericclioptions.IOStreams
4264
}
4365

4466
// NewCmdCreateDeployment is a macro command to create a new deployment.
4567
// This command is better known to users as `kubectl create deployment`.
46-
// Note that this command overlaps significantly with the `kubectl run` command.
4768
func NewCmdCreateDeployment(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
4869
options := &DeploymentOpts{
49-
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
70+
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
71+
IOStreams: ioStreams,
5072
}
5173

5274
cmd := &cobra.Command{
5375
Use: "deployment NAME --image=image [--dry-run=server|client|none]",
5476
DisableFlagsInUseLine: true,
5577
Aliases: []string{"deploy"},
56-
Short: i18n.T("Create a deployment with the specified name."),
78+
Short: deploymentLong,
5779
Long: deploymentLong,
5880
Example: deploymentExample,
5981
Run: func(cmd *cobra.Command, args []string) {
@@ -62,56 +84,16 @@ func NewCmdCreateDeployment(f cmdutil.Factory, ioStreams genericclioptions.IOStr
6284
},
6385
}
6486

65-
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
87+
options.PrintFlags.AddFlags(cmd)
6688

6789
cmdutil.AddApplyAnnotationFlags(cmd)
6890
cmdutil.AddValidateFlags(cmd)
6991
cmdutil.AddGeneratorFlags(cmd, "")
70-
cmd.Flags().StringSlice("image", []string{}, "Image name to run.")
71-
cmd.MarkFlagRequired("image")
72-
cmdutil.AddFieldManagerFlagVar(cmd, &options.CreateSubcommandOptions.FieldManager, "kubectl-create")
73-
return cmd
74-
}
75-
76-
// generatorFromName returns the appropriate StructuredGenerator based on the
77-
// generatorName. If the generatorName is unrecognized, then return (nil,
78-
// false).
79-
func generatorFromName(
80-
generatorName string,
81-
imageNames []string,
82-
deploymentName string,
83-
) (generate.StructuredGenerator, bool) {
84-
85-
switch generatorName {
86-
case generateversioned.DeploymentBasicAppsV1GeneratorName:
87-
generator := &generateversioned.DeploymentBasicAppsGeneratorV1{
88-
BaseDeploymentGenerator: generateversioned.BaseDeploymentGenerator{
89-
Name: deploymentName,
90-
Images: imageNames,
91-
},
92-
}
93-
return generator, true
94-
95-
case generateversioned.DeploymentBasicAppsV1Beta1GeneratorName:
96-
generator := &generateversioned.DeploymentBasicAppsGeneratorV1Beta1{
97-
BaseDeploymentGenerator: generateversioned.BaseDeploymentGenerator{
98-
Name: deploymentName,
99-
Images: imageNames,
100-
},
101-
}
102-
return generator, true
103-
104-
case generateversioned.DeploymentBasicV1Beta1GeneratorName:
105-
generator := &generateversioned.DeploymentBasicGeneratorV1{
106-
BaseDeploymentGenerator: generateversioned.BaseDeploymentGenerator{
107-
Name: deploymentName,
108-
Images: imageNames,
109-
},
110-
}
111-
return generator, true
112-
}
92+
cmd.Flags().StringSliceVar(&options.Images, "image", []string{}, "Image name to run.")
93+
_ = cmd.MarkFlagRequired("image")
94+
cmdutil.AddFieldManagerFlagVar(cmd, &options.FieldManager, "kubectl-create")
11395

114-
return nil, false
96+
return cmd
11597
}
11698

11799
// Complete completes all the options
@@ -120,37 +102,126 @@ func (o *DeploymentOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []
120102
if err != nil {
121103
return err
122104
}
105+
o.Name = name
123106

124-
clientset, err := f.KubernetesClientSet()
107+
clientConfig, err := f.ToRESTConfig()
108+
if err != nil {
109+
return err
110+
}
111+
o.Client, err = appsv1client.NewForConfig(clientConfig)
125112
if err != nil {
126113
return err
127114
}
128115

129-
generatorName := cmdutil.GetFlagString(cmd, "generator")
116+
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
117+
if err != nil {
118+
return err
119+
}
130120

131-
if len(generatorName) == 0 {
132-
generatorName = generateversioned.DeploymentBasicAppsV1GeneratorName
133-
generatorNameTemp, err := generateversioned.FallbackGeneratorNameIfNecessary(generatorName, clientset.Discovery(), o.CreateSubcommandOptions.ErrOut)
134-
if err != nil {
135-
return err
136-
}
137-
if generatorNameTemp != generatorName {
138-
cmdutil.Warning(o.CreateSubcommandOptions.ErrOut, generatorName, generatorNameTemp)
139-
} else {
140-
generatorName = generatorNameTemp
141-
}
121+
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
122+
if err != nil {
123+
return err
142124
}
125+
dynamicClient, err := f.DynamicClient()
126+
if err != nil {
127+
return err
128+
}
129+
discoveryClient, err := f.ToDiscoveryClient()
130+
if err != nil {
131+
return err
132+
}
133+
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
134+
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
143135

144-
imageNames := cmdutil.GetFlagStringSlice(cmd, "image")
145-
generator, ok := generatorFromName(generatorName, imageNames, name)
146-
if !ok {
147-
return errUnsupportedGenerator(cmd, generatorName)
136+
printer, err := o.PrintFlags.ToPrinter()
137+
if err != nil {
138+
return err
139+
}
140+
o.PrintObj = func(obj runtime.Object) error {
141+
return printer.PrintObj(obj, o.Out)
148142
}
149143

150-
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
144+
return nil
151145
}
152146

153147
// Run performs the execution of 'create deployment' sub command
154148
func (o *DeploymentOpts) Run() error {
155-
return o.CreateSubcommandOptions.Run()
149+
one := int32(1)
150+
labels := map[string]string{"app": o.Name}
151+
selector := metav1.LabelSelector{MatchLabels: labels}
152+
namespace := ""
153+
if o.EnforceNamespace {
154+
namespace = o.Namespace
155+
}
156+
157+
deploy := &appsv1.Deployment{
158+
TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String(), Kind: "Deployment"},
159+
ObjectMeta: metav1.ObjectMeta{
160+
Name: o.Name,
161+
Labels: labels,
162+
Namespace: namespace,
163+
},
164+
Spec: appsv1.DeploymentSpec{
165+
Replicas: &one,
166+
Selector: &selector,
167+
Template: v1.PodTemplateSpec{
168+
ObjectMeta: metav1.ObjectMeta{
169+
Labels: labels,
170+
},
171+
Spec: o.buildPodSpec(),
172+
},
173+
},
174+
}
175+
176+
if o.DryRunStrategy != cmdutil.DryRunClient {
177+
createOptions := metav1.CreateOptions{}
178+
if o.FieldManager != "" {
179+
createOptions.FieldManager = o.FieldManager
180+
}
181+
if o.DryRunStrategy == cmdutil.DryRunServer {
182+
if err := o.DryRunVerifier.HasSupport(deploy.GroupVersionKind()); err != nil {
183+
return err
184+
}
185+
createOptions.DryRun = []string{metav1.DryRunAll}
186+
}
187+
var err error
188+
deploy, err = o.Client.Deployments(o.Namespace).Create(context.TODO(), deploy, createOptions)
189+
if err != nil {
190+
return fmt.Errorf("failed to create deployment: %v", err)
191+
}
192+
}
193+
194+
return o.PrintObj(deploy)
195+
}
196+
197+
// buildPodSpec parses the image strings and assemble them into the Containers
198+
// of a PodSpec. This is all you need to create the PodSpec for a deployment.
199+
func (o *DeploymentOpts) buildPodSpec() v1.PodSpec {
200+
podSpec := v1.PodSpec{Containers: []v1.Container{}}
201+
for _, imageString := range o.Images {
202+
// Retain just the image name
203+
imageSplit := strings.Split(imageString, "/")
204+
name := imageSplit[len(imageSplit)-1]
205+
// Remove any tag or hash
206+
if strings.Contains(name, ":") {
207+
name = strings.Split(name, ":")[0]
208+
}
209+
if strings.Contains(name, "@") {
210+
name = strings.Split(name, "@")[0]
211+
}
212+
name = sanitizeAndUniquify(name)
213+
podSpec.Containers = append(podSpec.Containers, v1.Container{Name: name, Image: imageString})
214+
}
215+
return podSpec
216+
}
217+
218+
// sanitizeAndUniquify replaces characters like "." or "_" into "-" to follow DNS1123 rules.
219+
// Then add random suffix to make it uniquified.
220+
func sanitizeAndUniquify(name string) string {
221+
if strings.ContainsAny(name, "_.") {
222+
name = strings.Replace(name, "_", "-", -1)
223+
name = strings.Replace(name, ".", "-", -1)
224+
name = fmt.Sprintf("%s-%s", name, utilrand.String(5))
225+
}
226+
return name
156227
}

staging/src/k8s.io/kubectl/pkg/cmd/create/create_deployment_test.go

Lines changed: 3 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -29,64 +29,9 @@ import (
2929
"k8s.io/client-go/rest/fake"
3030
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
3131
cmdutil "k8s.io/kubectl/pkg/cmd/util"
32-
generateversioned "k8s.io/kubectl/pkg/generate/versioned"
3332
"k8s.io/kubectl/pkg/scheme"
3433
)
3534

36-
func Test_generatorFromName(t *testing.T) {
37-
const (
38-
nonsenseName = "not-a-real-generator-name"
39-
basicName = generateversioned.DeploymentBasicV1Beta1GeneratorName
40-
basicAppsV1Beta1Name = generateversioned.DeploymentBasicAppsV1Beta1GeneratorName
41-
basicAppsV1Name = generateversioned.DeploymentBasicAppsV1GeneratorName
42-
deploymentName = "deployment-name"
43-
)
44-
imageNames := []string{"image-1", "image-2"}
45-
46-
generator, ok := generatorFromName(nonsenseName, imageNames, deploymentName)
47-
assert.Nil(t, generator)
48-
assert.False(t, ok)
49-
50-
generator, ok = generatorFromName(basicName, imageNames, deploymentName)
51-
assert.True(t, ok)
52-
53-
{
54-
expectedGenerator := &generateversioned.DeploymentBasicGeneratorV1{
55-
BaseDeploymentGenerator: generateversioned.BaseDeploymentGenerator{
56-
Name: deploymentName,
57-
Images: imageNames,
58-
},
59-
}
60-
assert.Equal(t, expectedGenerator, generator)
61-
}
62-
63-
generator, ok = generatorFromName(basicAppsV1Beta1Name, imageNames, deploymentName)
64-
assert.True(t, ok)
65-
66-
{
67-
expectedGenerator := &generateversioned.DeploymentBasicAppsGeneratorV1Beta1{
68-
BaseDeploymentGenerator: generateversioned.BaseDeploymentGenerator{
69-
Name: deploymentName,
70-
Images: imageNames,
71-
},
72-
}
73-
assert.Equal(t, expectedGenerator, generator)
74-
}
75-
76-
generator, ok = generatorFromName(basicAppsV1Name, imageNames, deploymentName)
77-
assert.True(t, ok)
78-
79-
{
80-
expectedGenerator := &generateversioned.DeploymentBasicAppsGeneratorV1{
81-
BaseDeploymentGenerator: generateversioned.BaseDeploymentGenerator{
82-
Name: deploymentName,
83-
Images: imageNames,
84-
},
85-
}
86-
assert.Equal(t, expectedGenerator, generator)
87-
}
88-
}
89-
9035
func TestCreateDeployment(t *testing.T) {
9136
depName := "jonny-dep"
9237
tf := cmdtesting.NewTestFactory().WithNamespace("test")
@@ -139,11 +84,9 @@ func TestCreateDeploymentNoImage(t *testing.T) {
13984
cmd := NewCmdCreateDeployment(tf, ioStreams)
14085
cmd.Flags().Set("output", "name")
14186
options := &DeploymentOpts{
142-
CreateSubcommandOptions: &CreateSubcommandOptions{
143-
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
144-
DryRunStrategy: cmdutil.DryRunClient,
145-
IOStreams: ioStreams,
146-
},
87+
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
88+
DryRunStrategy: cmdutil.DryRunClient,
89+
IOStreams: ioStreams,
14790
}
14891

14992
err := options.Complete(tf, cmd, []string{depName})

0 commit comments

Comments
 (0)