...

Source file src/k8s.io/kubectl/pkg/cmd/create/create_deployment.go

Documentation: k8s.io/kubectl/pkg/cmd/create

     1  /*
     2  Copyright 2016 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    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  // CreateDeploymentOptions is returned by NewCmdCreateDeployment
    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  // NewCreateDeploymentOptions returns an initialized CreateDeploymentOptions instance
    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  // NewCmdCreateDeployment is a macro command to create a new deployment.
    96  // This command is better known to users as `kubectl create deployment`.
    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  // Complete completes all the options
   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  // Validate makes sure there is no discrepency in provided option values
   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  // Run performs the execution of 'create deployment' sub command
   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  // buildPodSpec parses the image strings and assemble them into the Containers
   245  // of a PodSpec. This is all you need to create the PodSpec for a deployment.
   246  func (o *CreateDeploymentOptions) buildPodSpec() corev1.PodSpec {
   247  	podSpec := corev1.PodSpec{Containers: []corev1.Container{}}
   248  	for _, imageString := range o.Images {
   249  		// Retain just the image name
   250  		imageSplit := strings.Split(imageString, "/")
   251  		name := imageSplit[len(imageSplit)-1]
   252  		// Remove any tag or hash
   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  // sanitizeAndUniquify replaces characters like "." or "_" into "-" to follow DNS1123 rules.
   270  // Then add random suffix to make it uniquified.
   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