...

Source file src/k8s.io/kubectl/pkg/cmd/create/create_service.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  	"strconv"
    23  	"strings"
    24  
    25  	"github.com/spf13/cobra"
    26  
    27  	corev1 "k8s.io/api/core/v1"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/runtime"
    30  	"k8s.io/apimachinery/pkg/util/intstr"
    31  	"k8s.io/apimachinery/pkg/util/validation"
    32  	"k8s.io/cli-runtime/pkg/genericclioptions"
    33  	"k8s.io/cli-runtime/pkg/genericiooptions"
    34  	corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
    35  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    36  	"k8s.io/kubectl/pkg/scheme"
    37  	"k8s.io/kubectl/pkg/util"
    38  	"k8s.io/kubectl/pkg/util/i18n"
    39  	"k8s.io/kubectl/pkg/util/templates"
    40  	utilsnet "k8s.io/utils/net"
    41  )
    42  
    43  // NewCmdCreateService is a macro command to create a new service
    44  func NewCmdCreateService(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
    45  	cmd := &cobra.Command{
    46  		Use:     "service",
    47  		Aliases: []string{"svc"},
    48  		Short:   i18n.T("Create a service using a specified subcommand"),
    49  		Long:    i18n.T("Create a service using a specified subcommand."),
    50  		Run:     cmdutil.DefaultSubCommandRun(ioStreams.ErrOut),
    51  	}
    52  	cmd.AddCommand(NewCmdCreateServiceClusterIP(f, ioStreams))
    53  	cmd.AddCommand(NewCmdCreateServiceNodePort(f, ioStreams))
    54  	cmd.AddCommand(NewCmdCreateServiceLoadBalancer(f, ioStreams))
    55  	cmd.AddCommand(NewCmdCreateServiceExternalName(f, ioStreams))
    56  
    57  	return cmd
    58  }
    59  
    60  // ServiceOptions holds the options for 'create service' sub command
    61  type ServiceOptions struct {
    62  	PrintFlags *genericclioptions.PrintFlags
    63  	PrintObj   func(obj runtime.Object) error
    64  
    65  	Name         string
    66  	TCP          []string
    67  	Type         corev1.ServiceType
    68  	ClusterIP    string
    69  	NodePort     int
    70  	ExternalName string
    71  
    72  	FieldManager     string
    73  	CreateAnnotation bool
    74  	Namespace        string
    75  	EnforceNamespace bool
    76  
    77  	Client              corev1client.CoreV1Interface
    78  	DryRunStrategy      cmdutil.DryRunStrategy
    79  	ValidationDirective string
    80  	genericiooptions.IOStreams
    81  }
    82  
    83  // NewServiceOptions creates a ServiceOptions struct
    84  func NewServiceOptions(ioStreams genericiooptions.IOStreams, serviceType corev1.ServiceType) *ServiceOptions {
    85  	return &ServiceOptions{
    86  		PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
    87  		IOStreams:  ioStreams,
    88  		Type:       serviceType,
    89  	}
    90  }
    91  
    92  // Complete completes all the required options
    93  func (o *ServiceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
    94  	name, err := NameFromCommandArgs(cmd, args)
    95  	if err != nil {
    96  		return err
    97  	}
    98  	o.Name = name
    99  
   100  	clientConfig, err := f.ToRESTConfig()
   101  	if err != nil {
   102  		return err
   103  	}
   104  	o.Client, err = corev1client.NewForConfig(clientConfig)
   105  	if err != nil {
   106  		return err
   107  	}
   108  
   109  	o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
   110  	if err != nil {
   111  		return err
   112  	}
   113  
   114  	o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
   115  	if err != nil {
   116  		return err
   117  	}
   118  	cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
   119  
   120  	printer, err := o.PrintFlags.ToPrinter()
   121  	if err != nil {
   122  		return err
   123  	}
   124  
   125  	o.PrintObj = func(obj runtime.Object) error {
   126  		return printer.PrintObj(obj, o.Out)
   127  	}
   128  
   129  	o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
   130  	if err != nil {
   131  		return err
   132  	}
   133  
   134  	return nil
   135  }
   136  
   137  // Validate if the options are valid
   138  func (o *ServiceOptions) Validate() error {
   139  	if o.ClusterIP == corev1.ClusterIPNone && o.Type != corev1.ServiceTypeClusterIP {
   140  		return fmt.Errorf("ClusterIP=None can only be used with ClusterIP service type")
   141  	}
   142  	if o.ClusterIP != corev1.ClusterIPNone && len(o.TCP) == 0 && o.Type != corev1.ServiceTypeExternalName {
   143  		return fmt.Errorf("at least one tcp port specifier must be provided")
   144  	}
   145  	if o.Type == corev1.ServiceTypeExternalName {
   146  		if errs := validation.IsDNS1123Subdomain(o.ExternalName); len(errs) != 0 {
   147  			return fmt.Errorf("invalid service external name %s", o.ExternalName)
   148  		}
   149  	}
   150  	return nil
   151  }
   152  
   153  func (o *ServiceOptions) createService() (*corev1.Service, error) {
   154  	ports := []corev1.ServicePort{}
   155  	for _, tcpString := range o.TCP {
   156  		port, targetPort, err := parsePorts(tcpString)
   157  		if err != nil {
   158  			return nil, err
   159  		}
   160  
   161  		portName := strings.Replace(tcpString, ":", "-", -1)
   162  		ports = append(ports, corev1.ServicePort{
   163  			Name:       portName,
   164  			Port:       port,
   165  			TargetPort: targetPort,
   166  			Protocol:   corev1.Protocol("TCP"),
   167  			NodePort:   int32(o.NodePort),
   168  		})
   169  	}
   170  
   171  	// setup default label and selector
   172  	labels := map[string]string{}
   173  	labels["app"] = o.Name
   174  	selector := map[string]string{}
   175  	selector["app"] = o.Name
   176  
   177  	namespace := ""
   178  	if o.EnforceNamespace {
   179  		namespace = o.Namespace
   180  	}
   181  
   182  	service := corev1.Service{
   183  		ObjectMeta: metav1.ObjectMeta{
   184  			Name:      o.Name,
   185  			Labels:    labels,
   186  			Namespace: namespace,
   187  		},
   188  		Spec: corev1.ServiceSpec{
   189  			Type:         o.Type,
   190  			Selector:     selector,
   191  			Ports:        ports,
   192  			ExternalName: o.ExternalName,
   193  		},
   194  	}
   195  	if len(o.ClusterIP) > 0 {
   196  		service.Spec.ClusterIP = o.ClusterIP
   197  	}
   198  	return &service, nil
   199  }
   200  
   201  // Run the service command
   202  func (o *ServiceOptions) Run() error {
   203  	service, err := o.createService()
   204  	if err != nil {
   205  		return err
   206  	}
   207  
   208  	if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, service, scheme.DefaultJSONEncoder()); err != nil {
   209  		return err
   210  	}
   211  
   212  	if o.DryRunStrategy != cmdutil.DryRunClient {
   213  		createOptions := metav1.CreateOptions{}
   214  		if o.FieldManager != "" {
   215  			createOptions.FieldManager = o.FieldManager
   216  		}
   217  		createOptions.FieldValidation = o.ValidationDirective
   218  		if o.DryRunStrategy == cmdutil.DryRunServer {
   219  			createOptions.DryRun = []string{metav1.DryRunAll}
   220  		}
   221  		var err error
   222  		service, err = o.Client.Services(o.Namespace).Create(context.TODO(), service, createOptions)
   223  		if err != nil {
   224  			return fmt.Errorf("failed to create %s service: %v", o.Type, err)
   225  		}
   226  	}
   227  	return o.PrintObj(service)
   228  }
   229  
   230  var (
   231  	serviceClusterIPLong = templates.LongDesc(i18n.T(`
   232      Create a ClusterIP service with the specified name.`))
   233  
   234  	serviceClusterIPExample = templates.Examples(i18n.T(`
   235      # Create a new ClusterIP service named my-cs
   236      kubectl create service clusterip my-cs --tcp=5678:8080
   237  
   238      # Create a new ClusterIP service named my-cs (in headless mode)
   239      kubectl create service clusterip my-cs --clusterip="None"`))
   240  )
   241  
   242  // NewCmdCreateServiceClusterIP is a command to create a ClusterIP service
   243  func NewCmdCreateServiceClusterIP(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
   244  	o := NewServiceOptions(ioStreams, corev1.ServiceTypeClusterIP)
   245  
   246  	cmd := &cobra.Command{
   247  		Use:                   "clusterip NAME [--tcp=<port>:<targetPort>] [--dry-run=server|client|none]",
   248  		DisableFlagsInUseLine: true,
   249  		Short:                 i18n.T("Create a ClusterIP service"),
   250  		Long:                  serviceClusterIPLong,
   251  		Example:               serviceClusterIPExample,
   252  		Run: func(cmd *cobra.Command, args []string) {
   253  			cmdutil.CheckErr(o.Complete(f, cmd, args))
   254  			cmdutil.CheckErr(o.Validate())
   255  			cmdutil.CheckErr(o.Run())
   256  		},
   257  	}
   258  
   259  	o.PrintFlags.AddFlags(cmd)
   260  
   261  	cmdutil.AddApplyAnnotationFlags(cmd)
   262  	cmdutil.AddValidateFlags(cmd)
   263  	cmd.Flags().StringSliceVar(&o.TCP, "tcp", o.TCP, "Port pairs can be specified as '<port>:<targetPort>'.")
   264  	cmd.Flags().StringVar(&o.ClusterIP, "clusterip", o.ClusterIP, i18n.T("Assign your own ClusterIP or set to 'None' for a 'headless' service (no loadbalancing)."))
   265  	cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
   266  	cmdutil.AddDryRunFlag(cmd)
   267  
   268  	return cmd
   269  }
   270  
   271  var (
   272  	serviceNodePortLong = templates.LongDesc(i18n.T(`
   273      Create a NodePort service with the specified name.`))
   274  
   275  	serviceNodePortExample = templates.Examples(i18n.T(`
   276      # Create a new NodePort service named my-ns
   277      kubectl create service nodeport my-ns --tcp=5678:8080`))
   278  )
   279  
   280  // NewCmdCreateServiceNodePort is a macro command for creating a NodePort service
   281  func NewCmdCreateServiceNodePort(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
   282  	o := NewServiceOptions(ioStreams, corev1.ServiceTypeNodePort)
   283  
   284  	cmd := &cobra.Command{
   285  		Use:                   "nodeport NAME [--tcp=port:targetPort] [--dry-run=server|client|none]",
   286  		DisableFlagsInUseLine: true,
   287  		Short:                 i18n.T("Create a NodePort service"),
   288  		Long:                  serviceNodePortLong,
   289  		Example:               serviceNodePortExample,
   290  		Run: func(cmd *cobra.Command, args []string) {
   291  			cmdutil.CheckErr(o.Complete(f, cmd, args))
   292  			cmdutil.CheckErr(o.Validate())
   293  			cmdutil.CheckErr(o.Run())
   294  		},
   295  	}
   296  
   297  	o.PrintFlags.AddFlags(cmd)
   298  
   299  	cmdutil.AddApplyAnnotationFlags(cmd)
   300  	cmdutil.AddValidateFlags(cmd)
   301  	cmd.Flags().IntVar(&o.NodePort, "node-port", o.NodePort, "Port used to expose the service on each node in a cluster.")
   302  	cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
   303  	cmd.Flags().StringSliceVar(&o.TCP, "tcp", o.TCP, "Port pairs can be specified as '<port>:<targetPort>'.")
   304  	cmdutil.AddDryRunFlag(cmd)
   305  	return cmd
   306  }
   307  
   308  var (
   309  	serviceLoadBalancerLong = templates.LongDesc(i18n.T(`
   310      Create a LoadBalancer service with the specified name.`))
   311  
   312  	serviceLoadBalancerExample = templates.Examples(i18n.T(`
   313      # Create a new LoadBalancer service named my-lbs
   314      kubectl create service loadbalancer my-lbs --tcp=5678:8080`))
   315  )
   316  
   317  // NewCmdCreateServiceLoadBalancer is a macro command for creating a LoadBalancer service
   318  func NewCmdCreateServiceLoadBalancer(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
   319  	o := NewServiceOptions(ioStreams, corev1.ServiceTypeLoadBalancer)
   320  
   321  	cmd := &cobra.Command{
   322  		Use:                   "loadbalancer NAME [--tcp=port:targetPort] [--dry-run=server|client|none]",
   323  		DisableFlagsInUseLine: true,
   324  		Short:                 i18n.T("Create a LoadBalancer service"),
   325  		Long:                  serviceLoadBalancerLong,
   326  		Example:               serviceLoadBalancerExample,
   327  		Run: func(cmd *cobra.Command, args []string) {
   328  			cmdutil.CheckErr(o.Complete(f, cmd, args))
   329  			cmdutil.CheckErr(o.Validate())
   330  			cmdutil.CheckErr(o.Run())
   331  		},
   332  	}
   333  
   334  	o.PrintFlags.AddFlags(cmd)
   335  
   336  	cmdutil.AddApplyAnnotationFlags(cmd)
   337  	cmdutil.AddValidateFlags(cmd)
   338  	cmd.Flags().StringSliceVar(&o.TCP, "tcp", o.TCP, "Port pairs can be specified as '<port>:<targetPort>'.")
   339  	cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
   340  	cmdutil.AddDryRunFlag(cmd)
   341  	return cmd
   342  }
   343  
   344  var (
   345  	serviceExternalNameLong = templates.LongDesc(i18n.T(`
   346  	Create an ExternalName service with the specified name.
   347  
   348  	ExternalName service references to an external DNS address instead of
   349  	only pods, which will allow application authors to reference services
   350  	that exist off platform, on other clusters, or locally.`))
   351  
   352  	serviceExternalNameExample = templates.Examples(i18n.T(`
   353  	# Create a new ExternalName service named my-ns
   354  	kubectl create service externalname my-ns --external-name bar.com`))
   355  )
   356  
   357  // NewCmdCreateServiceExternalName is a macro command for creating an ExternalName service
   358  func NewCmdCreateServiceExternalName(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
   359  	o := NewServiceOptions(ioStreams, corev1.ServiceTypeExternalName)
   360  
   361  	cmd := &cobra.Command{
   362  		Use:                   "externalname NAME --external-name external.name [--dry-run=server|client|none]",
   363  		DisableFlagsInUseLine: true,
   364  		Short:                 i18n.T("Create an ExternalName service"),
   365  		Long:                  serviceExternalNameLong,
   366  		Example:               serviceExternalNameExample,
   367  		Run: func(cmd *cobra.Command, args []string) {
   368  			cmdutil.CheckErr(o.Complete(f, cmd, args))
   369  			cmdutil.CheckErr(o.Validate())
   370  			cmdutil.CheckErr(o.Run())
   371  		},
   372  	}
   373  
   374  	o.PrintFlags.AddFlags(cmd)
   375  
   376  	cmdutil.AddApplyAnnotationFlags(cmd)
   377  	cmdutil.AddValidateFlags(cmd)
   378  	cmd.Flags().StringSliceVar(&o.TCP, "tcp", o.TCP, "Port pairs can be specified as '<port>:<targetPort>'.")
   379  	cmd.Flags().StringVar(&o.ExternalName, "external-name", o.ExternalName, i18n.T("External name of service"))
   380  	cmd.MarkFlagRequired("external-name")
   381  	cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
   382  	cmdutil.AddDryRunFlag(cmd)
   383  	return cmd
   384  }
   385  
   386  func parsePorts(portString string) (int32, intstr.IntOrString, error) {
   387  	portStringSlice := strings.Split(portString, ":")
   388  
   389  	port, err := utilsnet.ParsePort(portStringSlice[0], true)
   390  	if err != nil {
   391  		return 0, intstr.FromInt32(0), err
   392  	}
   393  
   394  	if len(portStringSlice) == 1 {
   395  		port32 := int32(port)
   396  		return port32, intstr.FromInt32(port32), nil
   397  	}
   398  
   399  	var targetPort intstr.IntOrString
   400  	if portNum, err := strconv.Atoi(portStringSlice[1]); err != nil {
   401  		if errs := validation.IsValidPortName(portStringSlice[1]); len(errs) != 0 {
   402  			return 0, intstr.FromInt32(0), fmt.Errorf(strings.Join(errs, ","))
   403  		}
   404  		targetPort = intstr.FromString(portStringSlice[1])
   405  	} else {
   406  		if errs := validation.IsValidPortNum(portNum); len(errs) != 0 {
   407  			return 0, intstr.FromInt32(0), fmt.Errorf(strings.Join(errs, ","))
   408  		}
   409  		targetPort = intstr.FromInt32(int32(portNum))
   410  	}
   411  	return int32(port), targetPort, nil
   412  }
   413  

View as plain text