1
16
17 package create
18
19 import (
20 "context"
21 "fmt"
22 "strings"
23
24 "github.com/spf13/cobra"
25
26 appsv1 "k8s.io/api/apps/v1"
27 corev1 "k8s.io/api/core/v1"
28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29 "k8s.io/apimachinery/pkg/runtime"
30 utilrand "k8s.io/apimachinery/pkg/util/rand"
31 "k8s.io/cli-runtime/pkg/genericclioptions"
32 "k8s.io/cli-runtime/pkg/genericiooptions"
33 appsv1client "k8s.io/client-go/kubernetes/typed/apps/v1"
34 cmdutil "k8s.io/kubectl/pkg/cmd/util"
35 "k8s.io/kubectl/pkg/scheme"
36 "k8s.io/kubectl/pkg/util"
37 "k8s.io/kubectl/pkg/util/i18n"
38 "k8s.io/kubectl/pkg/util/templates"
39 )
40
41 var (
42 deploymentLong = templates.LongDesc(i18n.T(`
43 Create a deployment with the specified name.`))
44
45 deploymentExample = templates.Examples(i18n.T(`
46 # Create a deployment named my-dep that runs the busybox image
47 kubectl create deployment my-dep --image=busybox
48
49 # Create a deployment with a command
50 kubectl create deployment my-dep --image=busybox -- date
51
52 # Create a deployment named my-dep that runs the nginx image with 3 replicas
53 kubectl create deployment my-dep --image=nginx --replicas=3
54
55 # Create a deployment named my-dep that runs the busybox image and expose port 5701
56 kubectl create deployment my-dep --image=busybox --port=5701
57
58 # Create a deployment named my-dep that runs multiple containers
59 kubectl create deployment my-dep --image=busybox:latest --image=ubuntu:latest --image=nginx`))
60 )
61
62
63 type CreateDeploymentOptions struct {
64 PrintFlags *genericclioptions.PrintFlags
65
66 PrintObj func(obj runtime.Object) error
67
68 Name string
69 Images []string
70 Port int32
71 Replicas int32
72 Command []string
73 Namespace string
74 EnforceNamespace bool
75 FieldManager string
76 CreateAnnotation bool
77
78 Client appsv1client.AppsV1Interface
79 DryRunStrategy cmdutil.DryRunStrategy
80 ValidationDirective string
81
82 genericiooptions.IOStreams
83 }
84
85
86 func NewCreateDeploymentOptions(ioStreams genericiooptions.IOStreams) *CreateDeploymentOptions {
87 return &CreateDeploymentOptions{
88 Port: -1,
89 Replicas: 1,
90 PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
91 IOStreams: ioStreams,
92 }
93 }
94
95
96
97 func NewCmdCreateDeployment(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
98 o := NewCreateDeploymentOptions(ioStreams)
99 cmd := &cobra.Command{
100 Use: "deployment NAME --image=image -- [COMMAND] [args...]",
101 DisableFlagsInUseLine: true,
102 Aliases: []string{"deploy"},
103 Short: i18n.T("Create a deployment with the specified name"),
104 Long: deploymentLong,
105 Example: deploymentExample,
106 Run: func(cmd *cobra.Command, args []string) {
107 cmdutil.CheckErr(o.Complete(f, cmd, args))
108 cmdutil.CheckErr(o.Validate())
109 cmdutil.CheckErr(o.Run())
110 },
111 }
112
113 o.PrintFlags.AddFlags(cmd)
114
115 cmdutil.AddApplyAnnotationFlags(cmd)
116 cmdutil.AddValidateFlags(cmd)
117 cmdutil.AddDryRunFlag(cmd)
118 cmd.Flags().StringSliceVar(&o.Images, "image", o.Images, "Image names to run. A deployment can have multiple images set for multi-container pod.")
119 cmd.MarkFlagRequired("image")
120 cmd.Flags().Int32Var(&o.Port, "port", o.Port, "The containerPort that this deployment exposes.")
121 cmd.Flags().Int32VarP(&o.Replicas, "replicas", "r", o.Replicas, "Number of replicas to create. Default is 1.")
122 cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
123
124 return cmd
125 }
126
127
128 func (o *CreateDeploymentOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
129 name, err := NameFromCommandArgs(cmd, args)
130 if err != nil {
131 return err
132 }
133 o.Name = name
134 if len(args) > 1 {
135 o.Command = args[1:]
136 }
137
138 clientConfig, err := f.ToRESTConfig()
139 if err != nil {
140 return err
141 }
142 o.Client, err = appsv1client.NewForConfig(clientConfig)
143 if err != nil {
144 return err
145 }
146
147 o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
148 if err != nil {
149 return err
150 }
151
152 o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
153
154 o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
155 if err != nil {
156 return err
157 }
158 cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
159
160 printer, err := o.PrintFlags.ToPrinter()
161 if err != nil {
162 return err
163 }
164 o.PrintObj = func(obj runtime.Object) error {
165 return printer.PrintObj(obj, o.Out)
166 }
167
168 o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
169 if err != nil {
170 return err
171 }
172
173 return nil
174 }
175
176
177 func (o *CreateDeploymentOptions) Validate() error {
178 if len(o.Images) > 1 && len(o.Command) > 0 {
179 return fmt.Errorf("cannot specify multiple --image options and command")
180 }
181 return nil
182 }
183
184
185 func (o *CreateDeploymentOptions) Run() error {
186 deploy := o.createDeployment()
187
188 if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, deploy, scheme.DefaultJSONEncoder()); err != nil {
189 return err
190 }
191
192 if o.DryRunStrategy != cmdutil.DryRunClient {
193 createOptions := metav1.CreateOptions{}
194 if o.FieldManager != "" {
195 createOptions.FieldManager = o.FieldManager
196 }
197 createOptions.FieldValidation = o.ValidationDirective
198 if o.DryRunStrategy == cmdutil.DryRunServer {
199 createOptions.DryRun = []string{metav1.DryRunAll}
200 }
201 var err error
202 deploy, err = o.Client.Deployments(o.Namespace).Create(context.TODO(), deploy, createOptions)
203 if err != nil {
204 return fmt.Errorf("failed to create deployment: %v", err)
205 }
206 }
207
208 return o.PrintObj(deploy)
209 }
210
211 func (o *CreateDeploymentOptions) createDeployment() *appsv1.Deployment {
212 labels := map[string]string{"app": o.Name}
213 selector := metav1.LabelSelector{MatchLabels: labels}
214 namespace := ""
215 if o.EnforceNamespace {
216 namespace = o.Namespace
217 }
218
219 deploy := &appsv1.Deployment{
220 TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String(), Kind: "Deployment"},
221 ObjectMeta: metav1.ObjectMeta{
222 Name: o.Name,
223 Labels: labels,
224 Namespace: namespace,
225 },
226 Spec: appsv1.DeploymentSpec{
227 Replicas: &o.Replicas,
228 Selector: &selector,
229 Template: corev1.PodTemplateSpec{
230 ObjectMeta: metav1.ObjectMeta{
231 Labels: labels,
232 },
233 Spec: o.buildPodSpec(),
234 },
235 },
236 }
237
238 if o.Port >= 0 && len(deploy.Spec.Template.Spec.Containers) > 0 {
239 deploy.Spec.Template.Spec.Containers[0].Ports = []corev1.ContainerPort{{ContainerPort: o.Port}}
240 }
241 return deploy
242 }
243
244
245
246 func (o *CreateDeploymentOptions) buildPodSpec() corev1.PodSpec {
247 podSpec := corev1.PodSpec{Containers: []corev1.Container{}}
248 for _, imageString := range o.Images {
249
250 imageSplit := strings.Split(imageString, "/")
251 name := imageSplit[len(imageSplit)-1]
252
253 if strings.Contains(name, ":") {
254 name = strings.Split(name, ":")[0]
255 }
256 if strings.Contains(name, "@") {
257 name = strings.Split(name, "@")[0]
258 }
259 name = sanitizeAndUniquify(name)
260 podSpec.Containers = append(podSpec.Containers, corev1.Container{
261 Name: name,
262 Image: imageString,
263 Command: o.Command,
264 })
265 }
266 return podSpec
267 }
268
269
270
271 func sanitizeAndUniquify(name string) string {
272 if strings.ContainsAny(name, "_.") {
273 name = strings.Replace(name, "_", "-", -1)
274 name = strings.Replace(name, ".", "-", -1)
275 name = fmt.Sprintf("%s-%s", name, utilrand.String(5))
276 }
277 return name
278 }
279
View as plain text