...

Source file src/k8s.io/kubectl/pkg/cmd/expose/expose.go

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

     1  /*
     2  Copyright 2014 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 expose
    18  
    19  import (
    20  	"fmt"
    21  	"regexp"
    22  	"strconv"
    23  	"strings"
    24  
    25  	"github.com/spf13/cobra"
    26  	"k8s.io/klog/v2"
    27  
    28  	corev1 "k8s.io/api/core/v1"
    29  	"k8s.io/apimachinery/pkg/api/meta"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    32  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme"
    33  	"k8s.io/apimachinery/pkg/runtime"
    34  	"k8s.io/apimachinery/pkg/util/intstr"
    35  	"k8s.io/apimachinery/pkg/util/validation"
    36  	"k8s.io/cli-runtime/pkg/genericclioptions"
    37  	"k8s.io/cli-runtime/pkg/genericiooptions"
    38  	"k8s.io/cli-runtime/pkg/printers"
    39  	"k8s.io/cli-runtime/pkg/resource"
    40  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    41  	"k8s.io/kubectl/pkg/polymorphichelpers"
    42  	"k8s.io/kubectl/pkg/scheme"
    43  	"k8s.io/kubectl/pkg/util"
    44  	"k8s.io/kubectl/pkg/util/completion"
    45  	"k8s.io/kubectl/pkg/util/i18n"
    46  	"k8s.io/kubectl/pkg/util/templates"
    47  )
    48  
    49  var (
    50  	exposeResources = i18n.T(`pod (po), service (svc), replicationcontroller (rc), deployment (deploy), replicaset (rs)`)
    51  
    52  	exposeLong = templates.LongDesc(i18n.T(`
    53  		Expose a resource as a new Kubernetes service.
    54  
    55  		Looks up a deployment, service, replica set, replication controller or pod by name and uses the selector
    56  		for that resource as the selector for a new service on the specified port. A deployment or replica set
    57  		will be exposed as a service only if its selector is convertible to a selector that service supports,
    58  		i.e. when the selector contains only the matchLabels component. Note that if no port is specified via
    59  		--port and the exposed resource has multiple ports, all will be re-used by the new service. Also if no
    60  		labels are specified, the new service will re-use the labels from the resource it exposes.
    61  
    62  		Possible resources include (case insensitive):
    63  
    64  		`) + exposeResources)
    65  
    66  	exposeExample = templates.Examples(i18n.T(`
    67  		# Create a service for a replicated nginx, which serves on port 80 and connects to the containers on port 8000
    68  		kubectl expose rc nginx --port=80 --target-port=8000
    69  
    70  		# Create a service for a replication controller identified by type and name specified in "nginx-controller.yaml", which serves on port 80 and connects to the containers on port 8000
    71  		kubectl expose -f nginx-controller.yaml --port=80 --target-port=8000
    72  
    73  		# Create a service for a pod valid-pod, which serves on port 444 with the name "frontend"
    74  		kubectl expose pod valid-pod --port=444 --name=frontend
    75  
    76  		# Create a second service based on the above service, exposing the container port 8443 as port 443 with the name "nginx-https"
    77  		kubectl expose service nginx --port=443 --target-port=8443 --name=nginx-https
    78  
    79  		# Create a service for a replicated streaming application on port 4100 balancing UDP traffic and named 'video-stream'.
    80  		kubectl expose rc streamer --port=4100 --protocol=UDP --name=video-stream
    81  
    82  		# Create a service for a replicated nginx using replica set, which serves on port 80 and connects to the containers on port 8000
    83  		kubectl expose rs nginx --port=80 --target-port=8000
    84  
    85  		# Create a service for an nginx deployment, which serves on port 80 and connects to the containers on port 8000
    86  		kubectl expose deployment nginx --port=80 --target-port=8000`))
    87  )
    88  
    89  // ExposeServiceOptions holds the options for kubectl expose command
    90  type ExposeServiceOptions struct {
    91  	cmdutil.OverrideOptions
    92  
    93  	FilenameOptions resource.FilenameOptions
    94  	RecordFlags     *genericclioptions.RecordFlags
    95  	PrintFlags      *genericclioptions.PrintFlags
    96  	PrintObj        printers.ResourcePrinterFunc
    97  
    98  	Name        string
    99  	DefaultName string
   100  	Selector    string
   101  	// Port will be used if a user specifies --port OR the exposed object as one port
   102  	Port string
   103  	// Ports will be used iff a user doesn't specify --port AND the exposed object has multiple ports
   104  	Ports          string
   105  	Labels         string
   106  	ExternalIP     string
   107  	LoadBalancerIP string
   108  	Type           string
   109  	Protocol       string
   110  	// Protocols will be used to keep port-protocol mapping derived from exposed object
   111  	Protocols       string
   112  	TargetPort      string
   113  	PortName        string
   114  	SessionAffinity string
   115  	ClusterIP       string
   116  
   117  	DryRunStrategy   cmdutil.DryRunStrategy
   118  	EnforceNamespace bool
   119  
   120  	fieldManager string
   121  
   122  	CanBeExposed              polymorphichelpers.CanBeExposedFunc
   123  	MapBasedSelectorForObject func(runtime.Object) (string, error)
   124  	PortsForObject            polymorphichelpers.PortsForObjectFunc
   125  	ProtocolsForObject        polymorphichelpers.MultiProtocolsWithForObjectFunc
   126  
   127  	Namespace string
   128  	Mapper    meta.RESTMapper
   129  
   130  	Builder          *resource.Builder
   131  	ClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error)
   132  
   133  	Recorder genericclioptions.Recorder
   134  	genericiooptions.IOStreams
   135  }
   136  
   137  // exposeServiceFlags is a struct that contains the user input flags to the command.
   138  type ExposeServiceFlags struct {
   139  	cmdutil.OverrideOptions
   140  	PrintFlags  *genericclioptions.PrintFlags
   141  	RecordFlags *genericclioptions.RecordFlags
   142  
   143  	fieldManager string
   144  	Protocol     string
   145  
   146  	// Port will be used if a user specifies --port OR the exposed object as one port
   147  	Port            string
   148  	Type            string
   149  	LoadBalancerIP  string
   150  	Selector        string
   151  	Labels          string
   152  	TargetPort      string
   153  	ExternalIP      string
   154  	Name            string
   155  	SessionAffinity string
   156  	ClusterIP       string
   157  	Recorder        genericclioptions.Recorder
   158  	FilenameOptions resource.FilenameOptions
   159  	genericiooptions.IOStreams
   160  }
   161  
   162  func NewExposeFlags(ioStreams genericiooptions.IOStreams) *ExposeServiceFlags {
   163  	return &ExposeServiceFlags{
   164  		RecordFlags: genericclioptions.NewRecordFlags(),
   165  		PrintFlags:  genericclioptions.NewPrintFlags("exposed").WithTypeSetter(scheme.Scheme),
   166  
   167  		Recorder:  genericclioptions.NoopRecorder{},
   168  		IOStreams: ioStreams,
   169  	}
   170  }
   171  
   172  // NewCmdExposeService is a command to expose the service from user's input
   173  func NewCmdExposeService(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
   174  	flags := NewExposeFlags(streams)
   175  
   176  	validArgs := []string{}
   177  	resources := regexp.MustCompile(`\s*,`).Split(exposeResources, -1)
   178  	for _, r := range resources {
   179  		validArgs = append(validArgs, strings.Fields(r)[0])
   180  	}
   181  
   182  	cmd := &cobra.Command{
   183  		Use:                   "expose (-f FILENAME | TYPE NAME) [--port=port] [--protocol=TCP|UDP|SCTP] [--target-port=number-or-name] [--name=name] [--external-ip=external-ip-of-service] [--type=type]",
   184  		DisableFlagsInUseLine: true,
   185  		Short:                 i18n.T("Take a replication controller, service, deployment or pod and expose it as a new Kubernetes service"),
   186  		Long:                  exposeLong,
   187  		Example:               exposeExample,
   188  		ValidArgsFunction:     completion.SpecifiedResourceTypeAndNameCompletionFunc(f, validArgs),
   189  		Run: func(cmd *cobra.Command, args []string) {
   190  			o, err := flags.ToOptions(cmd, args)
   191  			cmdutil.CheckErr(err)
   192  			cmdutil.CheckErr(o.Complete(f))
   193  			cmdutil.CheckErr(o.RunExpose(cmd, args))
   194  		},
   195  	}
   196  
   197  	flags.AddFlags(cmd)
   198  	return cmd
   199  }
   200  
   201  func (flags *ExposeServiceFlags) AddFlags(cmd *cobra.Command) {
   202  	flags.PrintFlags.AddFlags(cmd)
   203  	flags.RecordFlags.AddFlags(cmd)
   204  
   205  	cmd.Flags().StringVar(&flags.Protocol, "protocol", flags.Protocol, i18n.T("The network protocol for the service to be created. Default is 'TCP'."))
   206  	cmd.Flags().StringVar(&flags.Port, "port", flags.Port, i18n.T("The port that the service should serve on. Copied from the resource being exposed, if unspecified"))
   207  	cmd.Flags().StringVar(&flags.Type, "type", flags.Type, i18n.T("Type for this service: ClusterIP, NodePort, LoadBalancer, or ExternalName. Default is 'ClusterIP'."))
   208  	cmd.Flags().StringVar(&flags.LoadBalancerIP, "load-balancer-ip", flags.LoadBalancerIP, i18n.T("IP to assign to the LoadBalancer. If empty, an ephemeral IP will be created and used (cloud-provider specific)."))
   209  	cmd.Flags().StringVar(&flags.Selector, "selector", flags.Selector, i18n.T("A label selector to use for this service. Only equality-based selector requirements are supported. If empty (the default) infer the selector from the replication controller or replica set.)"))
   210  	cmd.Flags().StringVarP(&flags.Labels, "labels", "l", flags.Labels, "Labels to apply to the service created by this call.")
   211  	cmd.Flags().StringVar(&flags.TargetPort, "target-port", flags.TargetPort, i18n.T("Name or number for the port on the container that the service should direct traffic to. Optional."))
   212  	cmd.Flags().StringVar(&flags.ExternalIP, "external-ip", flags.ExternalIP, i18n.T("Additional external IP address (not managed by Kubernetes) to accept for the service. If this IP is routed to a node, the service can be accessed by this IP in addition to its generated service IP."))
   213  	cmd.Flags().StringVar(&flags.Name, "name", flags.Name, i18n.T("The name for the newly created object."))
   214  	cmd.Flags().StringVar(&flags.SessionAffinity, "session-affinity", flags.SessionAffinity, i18n.T("If non-empty, set the session affinity for the service to this; legal values: 'None', 'ClientIP'"))
   215  	cmd.Flags().StringVar(&flags.ClusterIP, "cluster-ip", flags.ClusterIP, i18n.T("ClusterIP to be assigned to the service. Leave empty to auto-allocate, or set to 'None' to create a headless service."))
   216  
   217  	cmdutil.AddFieldManagerFlagVar(cmd, &flags.fieldManager, "kubectl-expose")
   218  	flags.AddOverrideFlags(cmd)
   219  
   220  	cmdutil.AddDryRunFlag(cmd)
   221  	cmdutil.AddApplyAnnotationFlags(cmd)
   222  
   223  	usage := "identifying the resource to expose a service"
   224  	cmdutil.AddFilenameOptionFlags(cmd, &flags.FilenameOptions, usage)
   225  }
   226  
   227  func (flags *ExposeServiceFlags) ToOptions(cmd *cobra.Command, args []string) (*ExposeServiceOptions, error) {
   228  	dryRunStratergy, err := cmdutil.GetDryRunStrategy(cmd)
   229  	if err != nil {
   230  		return nil, err
   231  	}
   232  
   233  	cmdutil.PrintFlagsWithDryRunStrategy(flags.PrintFlags, dryRunStratergy)
   234  	printer, err := flags.PrintFlags.ToPrinter()
   235  	if err != nil {
   236  		return nil, err
   237  	}
   238  
   239  	flags.RecordFlags.Complete(cmd)
   240  	recorder, err := flags.RecordFlags.ToRecorder()
   241  	if err != nil {
   242  		return nil, err
   243  	}
   244  
   245  	e := &ExposeServiceOptions{
   246  		DryRunStrategy:  dryRunStratergy,
   247  		PrintObj:        printer.PrintObj,
   248  		Recorder:        recorder,
   249  		IOStreams:       flags.IOStreams,
   250  		fieldManager:    flags.fieldManager,
   251  		PrintFlags:      flags.PrintFlags,
   252  		RecordFlags:     flags.RecordFlags,
   253  		FilenameOptions: flags.FilenameOptions,
   254  		Protocol:        flags.Protocol,
   255  		Port:            flags.Port,
   256  		Type:            flags.Type,
   257  		LoadBalancerIP:  flags.LoadBalancerIP,
   258  		Selector:        flags.Selector,
   259  		Labels:          flags.Labels,
   260  		TargetPort:      flags.TargetPort,
   261  		ExternalIP:      flags.ExternalIP,
   262  		Name:            flags.Name,
   263  		SessionAffinity: flags.SessionAffinity,
   264  		ClusterIP:       flags.ClusterIP,
   265  		OverrideOptions: flags.OverrideOptions,
   266  	}
   267  	return e, nil
   268  }
   269  
   270  // Complete loads data from the command line environment
   271  func (o *ExposeServiceOptions) Complete(f cmdutil.Factory) error {
   272  	var err error
   273  
   274  	o.Builder = f.NewBuilder()
   275  	o.ClientForMapping = f.ClientForMapping
   276  	o.CanBeExposed = polymorphichelpers.CanBeExposedFn
   277  	o.MapBasedSelectorForObject = polymorphichelpers.MapBasedSelectorForObjectFn
   278  	o.ProtocolsForObject = polymorphichelpers.MultiProtocolsForObjectFn
   279  	o.PortsForObject = polymorphichelpers.PortsForObjectFn
   280  
   281  	o.Mapper, err = f.ToRESTMapper()
   282  	if err != nil {
   283  		return err
   284  	}
   285  
   286  	o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
   287  	if err != nil {
   288  		return err
   289  	}
   290  
   291  	return err
   292  }
   293  
   294  // RunExpose retrieves the Kubernetes Object from the API server and expose it to a
   295  // Kubernetes Service
   296  func (o *ExposeServiceOptions) RunExpose(cmd *cobra.Command, args []string) error {
   297  	r := o.Builder.
   298  		WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
   299  		ContinueOnError().
   300  		NamespaceParam(o.Namespace).DefaultNamespace().
   301  		FilenameParam(o.EnforceNamespace, &o.FilenameOptions).
   302  		ResourceTypeOrNameArgs(false, args...).
   303  		Flatten().
   304  		Do()
   305  	err := r.Err()
   306  	if err != nil {
   307  		return err
   308  	}
   309  
   310  	err = r.Visit(func(info *resource.Info, err error) error {
   311  		if err != nil {
   312  			return err
   313  		}
   314  
   315  		mapping := info.ResourceMapping()
   316  		if err := o.CanBeExposed(mapping.GroupVersionKind.GroupKind()); err != nil {
   317  			return err
   318  		}
   319  
   320  		name := info.Name
   321  		if len(name) > validation.DNS1035LabelMaxLength {
   322  			name = name[:validation.DNS1035LabelMaxLength]
   323  		}
   324  		o.DefaultName = name
   325  
   326  		// For objects that need a pod selector, derive it from the exposed object in case a user
   327  		// didn't explicitly specify one via --selector
   328  		if len(o.Selector) == 0 {
   329  			s, err := o.MapBasedSelectorForObject(info.Object)
   330  			if err != nil {
   331  				return fmt.Errorf("couldn't retrieve selectors via --selector flag or introspection: %v", err)
   332  			}
   333  			o.Selector = s
   334  		}
   335  
   336  		isHeadlessService := o.ClusterIP == "None"
   337  
   338  		// For objects that need a port, derive it from the exposed object in case a user
   339  		// didn't explicitly specify one via --port
   340  		if len(o.Port) == 0 {
   341  			ports, err := o.PortsForObject(info.Object)
   342  			if err != nil {
   343  				return fmt.Errorf("couldn't find port via --port flag or introspection: %v", err)
   344  			}
   345  			switch len(ports) {
   346  			case 0:
   347  				if !isHeadlessService {
   348  					return fmt.Errorf("couldn't find port via --port flag or introspection")
   349  				}
   350  			case 1:
   351  				o.Port = ports[0]
   352  			default:
   353  				o.Ports = strings.Join(ports, ",")
   354  			}
   355  		}
   356  
   357  		// Always try to derive protocols from the exposed object, may use
   358  		// different protocols for different ports.
   359  		protocolsMap, err := o.ProtocolsForObject(info.Object)
   360  		if err != nil {
   361  			return fmt.Errorf("couldn't find protocol via introspection: %v", err)
   362  		}
   363  		if protocols := makeProtocols(protocolsMap); len(protocols) > 0 {
   364  			o.Protocols = protocols
   365  		}
   366  
   367  		if len(o.Labels) == 0 {
   368  			labels, err := meta.NewAccessor().Labels(info.Object)
   369  			if err != nil {
   370  				return err
   371  			}
   372  			o.Labels = polymorphichelpers.MakeLabels(labels)
   373  		}
   374  
   375  		// Generate new object
   376  		service, err := o.createService()
   377  		if err != nil {
   378  			return err
   379  		}
   380  
   381  		overrideService, err := o.NewOverrider(&corev1.Service{}).Apply(service)
   382  		if err != nil {
   383  			return err
   384  		}
   385  
   386  		if err := o.Recorder.Record(overrideService); err != nil {
   387  			klog.V(4).Infof("error recording current command: %v", err)
   388  		}
   389  
   390  		if o.DryRunStrategy == cmdutil.DryRunClient {
   391  			if meta, err := meta.Accessor(overrideService); err == nil && o.EnforceNamespace {
   392  				meta.SetNamespace(o.Namespace)
   393  			}
   394  			return o.PrintObj(overrideService, o.Out)
   395  		}
   396  		if err := util.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), overrideService, scheme.DefaultJSONEncoder()); err != nil {
   397  			return err
   398  		}
   399  
   400  		asUnstructured := &unstructured.Unstructured{}
   401  		if err := scheme.Scheme.Convert(overrideService, asUnstructured, nil); err != nil {
   402  			return err
   403  		}
   404  		gvks, _, err := unstructuredscheme.NewUnstructuredObjectTyper().ObjectKinds(asUnstructured)
   405  		if err != nil {
   406  			return err
   407  		}
   408  		objMapping, err := o.Mapper.RESTMapping(gvks[0].GroupKind(), gvks[0].Version)
   409  		if err != nil {
   410  			return err
   411  		}
   412  		// Serialize the object with the annotation applied.
   413  		client, err := o.ClientForMapping(objMapping)
   414  		if err != nil {
   415  			return err
   416  		}
   417  		actualObject, err := resource.
   418  			NewHelper(client, objMapping).
   419  			DryRun(o.DryRunStrategy == cmdutil.DryRunServer).
   420  			WithFieldManager(o.fieldManager).
   421  			Create(o.Namespace, false, asUnstructured)
   422  		if err != nil {
   423  			return err
   424  		}
   425  		return o.PrintObj(actualObject, o.Out)
   426  	})
   427  	return err
   428  }
   429  
   430  func (o *ExposeServiceOptions) createService() (*corev1.Service, error) {
   431  	if len(o.Selector) == 0 {
   432  		return nil, fmt.Errorf("selector must be specified")
   433  	}
   434  	selector, err := parseLabels(o.Selector)
   435  	if err != nil {
   436  		return nil, err
   437  	}
   438  
   439  	var labels map[string]string
   440  	if len(o.Labels) > 0 {
   441  		labels, err = parseLabels(o.Labels)
   442  		if err != nil {
   443  			return nil, err
   444  		}
   445  	}
   446  
   447  	name := o.Name
   448  	if len(name) == 0 {
   449  		name = o.DefaultName
   450  		if len(name) == 0 {
   451  			return nil, fmt.Errorf("name must be specified")
   452  		}
   453  	}
   454  
   455  	var portProtocolMap map[string][]string
   456  	if o.Protocols != "" {
   457  		portProtocolMap, err = parseProtocols(o.Protocols)
   458  		if err != nil {
   459  			return nil, err
   460  		}
   461  	}
   462  
   463  	// ports takes precedence over port since it will be
   464  	// specified only when the user hasn't specified a port
   465  	// via --port and the exposed object has multiple ports.
   466  	var portString string
   467  	portString = o.Ports
   468  	if len(o.Ports) == 0 {
   469  		portString = o.Port
   470  	}
   471  
   472  	ports := []corev1.ServicePort{}
   473  	if len(portString) != 0 {
   474  		portStringSlice := strings.Split(portString, ",")
   475  		servicePortName := o.PortName
   476  		for i, stillPortString := range portStringSlice {
   477  			port, err := strconv.Atoi(stillPortString)
   478  			if err != nil {
   479  				return nil, err
   480  			}
   481  			name := servicePortName
   482  			// If we are going to assign multiple ports to a service, we need to
   483  			// generate a different name for each one.
   484  			if len(portStringSlice) > 1 {
   485  				name = fmt.Sprintf("port-%d", i+1)
   486  			}
   487  			protocol := o.Protocol
   488  
   489  			switch {
   490  			case len(protocol) == 0 && len(portProtocolMap) == 0:
   491  				// Default to TCP, what the flag was doing previously.
   492  				protocol = "TCP"
   493  			case len(protocol) > 0 && len(portProtocolMap) > 0:
   494  				// User has specified the --protocol while exposing a multiprotocol resource
   495  				// We should stomp multiple protocols with the one specified ie. do nothing
   496  			case len(protocol) == 0 && len(portProtocolMap) > 0:
   497  				// no --protocol and we expose a multiprotocol resource
   498  				protocol = "TCP" // have the default so we can stay sane
   499  				if exposeProtocols, found := portProtocolMap[stillPortString]; found {
   500  					if len(exposeProtocols) == 1 {
   501  						protocol = exposeProtocols[0]
   502  						break
   503  					}
   504  					for _, exposeProtocol := range exposeProtocols {
   505  						name := fmt.Sprintf("port-%d-%s", i+1, strings.ToLower(exposeProtocol))
   506  						ports = append(ports, corev1.ServicePort{
   507  							Name:     name,
   508  							Port:     int32(port),
   509  							Protocol: corev1.Protocol(exposeProtocol),
   510  						})
   511  					}
   512  					continue
   513  				}
   514  			}
   515  			ports = append(ports, corev1.ServicePort{
   516  				Name:     name,
   517  				Port:     int32(port),
   518  				Protocol: corev1.Protocol(protocol),
   519  			})
   520  		}
   521  	}
   522  
   523  	service := corev1.Service{
   524  		ObjectMeta: metav1.ObjectMeta{
   525  			Name:   name,
   526  			Labels: labels,
   527  		},
   528  		Spec: corev1.ServiceSpec{
   529  			Selector: selector,
   530  			Ports:    ports,
   531  		},
   532  	}
   533  	targetPortString := o.TargetPort
   534  	if len(targetPortString) > 0 {
   535  		targetPort := intstr.Parse(targetPortString)
   536  		// Use the same target-port for every port
   537  		for i := range service.Spec.Ports {
   538  			service.Spec.Ports[i].TargetPort = targetPort
   539  		}
   540  	} else {
   541  		// If --target-port or --container-port haven't been specified, this
   542  		// should be the same as Port
   543  		for i := range service.Spec.Ports {
   544  			port := service.Spec.Ports[i].Port
   545  			service.Spec.Ports[i].TargetPort = intstr.FromInt32(port)
   546  		}
   547  	}
   548  	if len(o.ExternalIP) > 0 {
   549  		service.Spec.ExternalIPs = []string{o.ExternalIP}
   550  	}
   551  	if len(o.Type) != 0 {
   552  		service.Spec.Type = corev1.ServiceType(o.Type)
   553  	}
   554  	if service.Spec.Type == corev1.ServiceTypeLoadBalancer {
   555  		service.Spec.LoadBalancerIP = o.LoadBalancerIP
   556  	}
   557  	if len(o.SessionAffinity) != 0 {
   558  		switch corev1.ServiceAffinity(o.SessionAffinity) {
   559  		case corev1.ServiceAffinityNone:
   560  			service.Spec.SessionAffinity = corev1.ServiceAffinityNone
   561  		case corev1.ServiceAffinityClientIP:
   562  			service.Spec.SessionAffinity = corev1.ServiceAffinityClientIP
   563  		default:
   564  			return nil, fmt.Errorf("unknown session affinity: %s", o.SessionAffinity)
   565  		}
   566  	}
   567  	if len(o.ClusterIP) != 0 {
   568  		if o.ClusterIP == "None" {
   569  			service.Spec.ClusterIP = corev1.ClusterIPNone
   570  		} else {
   571  			service.Spec.ClusterIP = o.ClusterIP
   572  		}
   573  	}
   574  	return &service, nil
   575  }
   576  
   577  // parseLabels turns a string representation of a label set into a map[string]string
   578  func parseLabels(labelSpec string) (map[string]string, error) {
   579  	if len(labelSpec) == 0 {
   580  		return nil, fmt.Errorf("no label spec passed")
   581  	}
   582  	labels := map[string]string{}
   583  	labelSpecs := strings.Split(labelSpec, ",")
   584  	for ix := range labelSpecs {
   585  		labelSpec := strings.Split(labelSpecs[ix], "=")
   586  		if len(labelSpec) != 2 {
   587  			return nil, fmt.Errorf("unexpected label spec: %s", labelSpecs[ix])
   588  		}
   589  		if len(labelSpec[0]) == 0 {
   590  			return nil, fmt.Errorf("unexpected empty label key")
   591  		}
   592  		labels[labelSpec[0]] = labelSpec[1]
   593  	}
   594  	return labels, nil
   595  }
   596  
   597  func makeProtocols(protocols map[string][]string) string {
   598  	var out []string
   599  	for key, value := range protocols {
   600  		for _, s := range value {
   601  			out = append(out, fmt.Sprintf("%s/%s", key, s))
   602  		}
   603  	}
   604  	return strings.Join(out, ",")
   605  }
   606  
   607  // parseProtocols turns a string representation of a protocols set into a map[string]string
   608  func parseProtocols(protocols string) (map[string][]string, error) {
   609  	if len(protocols) == 0 {
   610  		return nil, fmt.Errorf("no protocols passed")
   611  	}
   612  	portProtocolMap := map[string][]string{}
   613  	protocolsSlice := strings.Split(protocols, ",")
   614  	for ix := range protocolsSlice {
   615  		portProtocol := strings.Split(protocolsSlice[ix], "/")
   616  		if len(portProtocol) != 2 {
   617  			return nil, fmt.Errorf("unexpected port protocol mapping: %s", protocolsSlice[ix])
   618  		}
   619  		if len(portProtocol[0]) == 0 {
   620  			return nil, fmt.Errorf("unexpected empty port")
   621  		}
   622  		if len(portProtocol[1]) == 0 {
   623  			return nil, fmt.Errorf("unexpected empty protocol")
   624  		}
   625  		port := portProtocol[0]
   626  		portProtocolMap[port] = append(portProtocolMap[port], portProtocol[1])
   627  	}
   628  	return portProtocolMap, nil
   629  }
   630  

View as plain text