mirror of
https://github.com/kubernetes/kubectl.git
synced 2026-04-15 22:00:02 -04:00
The current help text wordings incorrectly suggests inference only works for RC/RS when it actually works for Pods and Deployments too Update help text to accurately reflect that selector inference works for any resource being exposed, as documented in main expose help. Kubernetes-commit: 5c8f26f48032899031760e5b75ad259d23d312b2
629 lines
21 KiB
Go
629 lines
21 KiB
Go
/*
|
|
Copyright 2014 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package expose
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/spf13/cobra"
|
|
"k8s.io/klog/v2"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/meta"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/util/intstr"
|
|
"k8s.io/apimachinery/pkg/util/validation"
|
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
|
"k8s.io/cli-runtime/pkg/genericiooptions"
|
|
"k8s.io/cli-runtime/pkg/printers"
|
|
"k8s.io/cli-runtime/pkg/resource"
|
|
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
|
"k8s.io/kubectl/pkg/polymorphichelpers"
|
|
"k8s.io/kubectl/pkg/scheme"
|
|
"k8s.io/kubectl/pkg/util"
|
|
"k8s.io/kubectl/pkg/util/completion"
|
|
"k8s.io/kubectl/pkg/util/i18n"
|
|
"k8s.io/kubectl/pkg/util/templates"
|
|
)
|
|
|
|
var (
|
|
exposeResources = i18n.T(`pod (po), service (svc), replicationcontroller (rc), deployment (deploy), replicaset (rs)`)
|
|
|
|
exposeLong = templates.LongDesc(i18n.T(`
|
|
Expose a resource as a new Kubernetes service.
|
|
|
|
Looks up a deployment, service, replica set, replication controller or pod by name and uses the selector
|
|
for that resource as the selector for a new service on the specified port. A deployment or replica set
|
|
will be exposed as a service only if its selector is convertible to a selector that service supports,
|
|
i.e. when the selector contains only the matchLabels component. Note that if no port is specified via
|
|
--port and the exposed resource has multiple ports, all will be re-used by the new service. Also if no
|
|
labels are specified, the new service will re-use the labels from the resource it exposes.
|
|
|
|
Possible resources include (case insensitive):
|
|
|
|
`) + exposeResources)
|
|
|
|
exposeExample = templates.Examples(i18n.T(`
|
|
# Create a service for a replicated nginx, which serves on port 80 and connects to the containers on port 8000
|
|
kubectl expose rc nginx --port=80 --target-port=8000
|
|
|
|
# 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
|
|
kubectl expose -f nginx-controller.yaml --port=80 --target-port=8000
|
|
|
|
# Create a service for a pod valid-pod, which serves on port 444 with the name "frontend"
|
|
kubectl expose pod valid-pod --port=444 --name=frontend
|
|
|
|
# Create a second service based on the above service, exposing the container port 8443 as port 443 with the name "nginx-https"
|
|
kubectl expose service nginx --port=443 --target-port=8443 --name=nginx-https
|
|
|
|
# Create a service for a replicated streaming application on port 4100 balancing UDP traffic and named 'video-stream'.
|
|
kubectl expose rc streamer --port=4100 --protocol=UDP --name=video-stream
|
|
|
|
# Create a service for a replicated nginx using replica set, which serves on port 80 and connects to the containers on port 8000
|
|
kubectl expose rs nginx --port=80 --target-port=8000
|
|
|
|
# Create a service for an nginx deployment, which serves on port 80 and connects to the containers on port 8000
|
|
kubectl expose deployment nginx --port=80 --target-port=8000`))
|
|
)
|
|
|
|
// ExposeServiceOptions holds the options for kubectl expose command
|
|
type ExposeServiceOptions struct {
|
|
cmdutil.OverrideOptions
|
|
|
|
FilenameOptions resource.FilenameOptions
|
|
RecordFlags *genericclioptions.RecordFlags
|
|
PrintFlags *genericclioptions.PrintFlags
|
|
PrintObj printers.ResourcePrinterFunc
|
|
|
|
Name string
|
|
DefaultName string
|
|
Selector string
|
|
// Port will be used if a user specifies --port OR the exposed object as one port
|
|
Port string
|
|
// Ports will be used iff a user doesn't specify --port AND the exposed object has multiple ports
|
|
Ports string
|
|
Labels string
|
|
ExternalIP string
|
|
LoadBalancerIP string
|
|
Type string
|
|
Protocol string
|
|
// Protocols will be used to keep port-protocol mapping derived from exposed object
|
|
Protocols string
|
|
TargetPort string
|
|
PortName string
|
|
SessionAffinity string
|
|
ClusterIP string
|
|
|
|
DryRunStrategy cmdutil.DryRunStrategy
|
|
EnforceNamespace bool
|
|
|
|
fieldManager string
|
|
|
|
CanBeExposed polymorphichelpers.CanBeExposedFunc
|
|
MapBasedSelectorForObject func(runtime.Object) (string, error)
|
|
PortsForObject polymorphichelpers.PortsForObjectFunc
|
|
ProtocolsForObject polymorphichelpers.MultiProtocolsWithForObjectFunc
|
|
|
|
Namespace string
|
|
Mapper meta.RESTMapper
|
|
|
|
Builder *resource.Builder
|
|
ClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error)
|
|
|
|
Recorder genericclioptions.Recorder
|
|
genericiooptions.IOStreams
|
|
}
|
|
|
|
// exposeServiceFlags is a struct that contains the user input flags to the command.
|
|
type ExposeServiceFlags struct {
|
|
cmdutil.OverrideOptions
|
|
PrintFlags *genericclioptions.PrintFlags
|
|
RecordFlags *genericclioptions.RecordFlags
|
|
|
|
fieldManager string
|
|
Protocol string
|
|
|
|
// Port will be used if a user specifies --port OR the exposed object as one port
|
|
Port string
|
|
Type string
|
|
LoadBalancerIP string
|
|
Selector string
|
|
Labels string
|
|
TargetPort string
|
|
ExternalIP string
|
|
Name string
|
|
SessionAffinity string
|
|
ClusterIP string
|
|
Recorder genericclioptions.Recorder
|
|
FilenameOptions resource.FilenameOptions
|
|
genericiooptions.IOStreams
|
|
}
|
|
|
|
func NewExposeFlags(ioStreams genericiooptions.IOStreams) *ExposeServiceFlags {
|
|
return &ExposeServiceFlags{
|
|
RecordFlags: genericclioptions.NewRecordFlags(),
|
|
PrintFlags: genericclioptions.NewPrintFlags("exposed").WithTypeSetter(scheme.Scheme),
|
|
|
|
Recorder: genericclioptions.NoopRecorder{},
|
|
IOStreams: ioStreams,
|
|
}
|
|
}
|
|
|
|
// NewCmdExposeService is a command to expose the service from user's input
|
|
func NewCmdExposeService(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
|
|
flags := NewExposeFlags(streams)
|
|
|
|
validArgs := []string{}
|
|
resources := regexp.MustCompile(`\s*,`).Split(exposeResources, -1)
|
|
for _, r := range resources {
|
|
validArgs = append(validArgs, strings.Fields(r)[0])
|
|
}
|
|
|
|
cmd := &cobra.Command{
|
|
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]",
|
|
DisableFlagsInUseLine: true,
|
|
Short: i18n.T("Take a replication controller, service, deployment or pod and expose it as a new Kubernetes service"),
|
|
Long: exposeLong,
|
|
Example: exposeExample,
|
|
ValidArgsFunction: completion.SpecifiedResourceTypeAndNameCompletionFunc(f, validArgs),
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
o, err := flags.ToOptions(cmd, args)
|
|
cmdutil.CheckErr(err)
|
|
cmdutil.CheckErr(o.Complete(f))
|
|
cmdutil.CheckErr(o.RunExpose(cmd, args))
|
|
},
|
|
}
|
|
|
|
flags.AddFlags(cmd)
|
|
return cmd
|
|
}
|
|
|
|
func (flags *ExposeServiceFlags) AddFlags(cmd *cobra.Command) {
|
|
flags.PrintFlags.AddFlags(cmd)
|
|
flags.RecordFlags.AddFlags(cmd)
|
|
|
|
cmd.Flags().StringVar(&flags.Protocol, "protocol", flags.Protocol, i18n.T("The network protocol for the service to be created. Default is 'TCP'."))
|
|
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"))
|
|
cmd.Flags().StringVar(&flags.Type, "type", flags.Type, i18n.T("Type for this service: ClusterIP, NodePort, LoadBalancer, or ExternalName. Default is 'ClusterIP'."))
|
|
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)."))
|
|
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 resource being exposed."))
|
|
cmd.Flags().StringVarP(&flags.Labels, "labels", "l", flags.Labels, "Labels to apply to the service created by this call.")
|
|
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."))
|
|
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."))
|
|
cmd.Flags().StringVar(&flags.Name, "name", flags.Name, i18n.T("The name for the newly created object."))
|
|
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'"))
|
|
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."))
|
|
|
|
cmdutil.AddFieldManagerFlagVar(cmd, &flags.fieldManager, "kubectl-expose")
|
|
flags.AddOverrideFlags(cmd)
|
|
|
|
cmdutil.AddDryRunFlag(cmd)
|
|
cmdutil.AddApplyAnnotationFlags(cmd)
|
|
|
|
usage := "identifying the resource to expose a service"
|
|
cmdutil.AddFilenameOptionFlags(cmd, &flags.FilenameOptions, usage)
|
|
}
|
|
|
|
func (flags *ExposeServiceFlags) ToOptions(cmd *cobra.Command, args []string) (*ExposeServiceOptions, error) {
|
|
dryRunStrategy, err := cmdutil.GetDryRunStrategy(cmd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cmdutil.PrintFlagsWithDryRunStrategy(flags.PrintFlags, dryRunStrategy)
|
|
printer, err := flags.PrintFlags.ToPrinter()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
flags.RecordFlags.Complete(cmd)
|
|
recorder, err := flags.RecordFlags.ToRecorder()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
e := &ExposeServiceOptions{
|
|
DryRunStrategy: dryRunStrategy,
|
|
PrintObj: printer.PrintObj,
|
|
Recorder: recorder,
|
|
IOStreams: flags.IOStreams,
|
|
fieldManager: flags.fieldManager,
|
|
PrintFlags: flags.PrintFlags,
|
|
RecordFlags: flags.RecordFlags,
|
|
FilenameOptions: flags.FilenameOptions,
|
|
Protocol: flags.Protocol,
|
|
Port: flags.Port,
|
|
Type: flags.Type,
|
|
LoadBalancerIP: flags.LoadBalancerIP,
|
|
Selector: flags.Selector,
|
|
Labels: flags.Labels,
|
|
TargetPort: flags.TargetPort,
|
|
ExternalIP: flags.ExternalIP,
|
|
Name: flags.Name,
|
|
SessionAffinity: flags.SessionAffinity,
|
|
ClusterIP: flags.ClusterIP,
|
|
OverrideOptions: flags.OverrideOptions,
|
|
}
|
|
return e, nil
|
|
}
|
|
|
|
// Complete loads data from the command line environment
|
|
func (o *ExposeServiceOptions) Complete(f cmdutil.Factory) error {
|
|
var err error
|
|
|
|
o.Builder = f.NewBuilder()
|
|
o.ClientForMapping = f.ClientForMapping
|
|
o.CanBeExposed = polymorphichelpers.CanBeExposedFn
|
|
o.MapBasedSelectorForObject = polymorphichelpers.MapBasedSelectorForObjectFn
|
|
o.ProtocolsForObject = polymorphichelpers.MultiProtocolsForObjectFn
|
|
o.PortsForObject = polymorphichelpers.PortsForObjectFn
|
|
|
|
o.Mapper, err = f.ToRESTMapper()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// RunExpose retrieves the Kubernetes Object from the API server and expose it to a
|
|
// Kubernetes Service
|
|
func (o *ExposeServiceOptions) RunExpose(cmd *cobra.Command, args []string) error {
|
|
r := o.Builder.
|
|
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
|
|
ContinueOnError().
|
|
NamespaceParam(o.Namespace).DefaultNamespace().
|
|
FilenameParam(o.EnforceNamespace, &o.FilenameOptions).
|
|
ResourceTypeOrNameArgs(false, args...).
|
|
Flatten().
|
|
Do()
|
|
err := r.Err()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = r.Visit(func(info *resource.Info, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mapping := info.ResourceMapping()
|
|
if err := o.CanBeExposed(mapping.GroupVersionKind.GroupKind()); err != nil {
|
|
return err
|
|
}
|
|
|
|
name := info.Name
|
|
if len(name) > validation.DNS1035LabelMaxLength {
|
|
name = name[:validation.DNS1035LabelMaxLength]
|
|
}
|
|
o.DefaultName = name
|
|
|
|
// For objects that need a pod selector, derive it from the exposed object in case a user
|
|
// didn't explicitly specify one via --selector
|
|
if len(o.Selector) == 0 {
|
|
s, err := o.MapBasedSelectorForObject(info.Object)
|
|
if err != nil {
|
|
return fmt.Errorf("couldn't retrieve selectors via --selector flag or introspection: %v", err)
|
|
}
|
|
o.Selector = s
|
|
}
|
|
|
|
isHeadlessService := o.ClusterIP == "None"
|
|
|
|
// For objects that need a port, derive it from the exposed object in case a user
|
|
// didn't explicitly specify one via --port
|
|
if len(o.Port) == 0 {
|
|
ports, err := o.PortsForObject(info.Object)
|
|
if err != nil {
|
|
return fmt.Errorf("couldn't find port via --port flag or introspection: %v", err)
|
|
}
|
|
switch len(ports) {
|
|
case 0:
|
|
if !isHeadlessService {
|
|
return fmt.Errorf("couldn't find port via --port flag or introspection")
|
|
}
|
|
case 1:
|
|
o.Port = ports[0]
|
|
default:
|
|
o.Ports = strings.Join(ports, ",")
|
|
}
|
|
}
|
|
|
|
// Always try to derive protocols from the exposed object, may use
|
|
// different protocols for different ports.
|
|
protocolsMap, err := o.ProtocolsForObject(info.Object)
|
|
if err != nil {
|
|
return fmt.Errorf("couldn't find protocol via introspection: %v", err)
|
|
}
|
|
if protocols := makeProtocols(protocolsMap); len(protocols) > 0 {
|
|
o.Protocols = protocols
|
|
}
|
|
|
|
if len(o.Labels) == 0 {
|
|
labels, err := meta.NewAccessor().Labels(info.Object)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
o.Labels = polymorphichelpers.MakeLabels(labels)
|
|
}
|
|
|
|
// Generate new object
|
|
service, err := o.createService()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
overrideService, err := o.NewOverrider(&corev1.Service{}).Apply(service)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := o.Recorder.Record(overrideService); err != nil {
|
|
klog.V(4).Infof("error recording current command: %v", err)
|
|
}
|
|
|
|
if o.DryRunStrategy == cmdutil.DryRunClient {
|
|
if meta, err := meta.Accessor(overrideService); err == nil && o.EnforceNamespace {
|
|
meta.SetNamespace(o.Namespace)
|
|
}
|
|
return o.PrintObj(overrideService, o.Out)
|
|
}
|
|
if err := util.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), overrideService, scheme.DefaultJSONEncoder()); err != nil {
|
|
return err
|
|
}
|
|
|
|
asUnstructured := &unstructured.Unstructured{}
|
|
if err := scheme.Scheme.Convert(overrideService, asUnstructured, nil); err != nil {
|
|
return err
|
|
}
|
|
gvks, _, err := unstructuredscheme.NewUnstructuredObjectTyper().ObjectKinds(asUnstructured)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
objMapping, err := o.Mapper.RESTMapping(gvks[0].GroupKind(), gvks[0].Version)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Serialize the object with the annotation applied.
|
|
client, err := o.ClientForMapping(objMapping)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
actualObject, err := resource.
|
|
NewHelper(client, objMapping).
|
|
DryRun(o.DryRunStrategy == cmdutil.DryRunServer).
|
|
WithFieldManager(o.fieldManager).
|
|
Create(o.Namespace, false, asUnstructured)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return o.PrintObj(actualObject, o.Out)
|
|
})
|
|
return err
|
|
}
|
|
|
|
func (o *ExposeServiceOptions) createService() (*corev1.Service, error) {
|
|
if len(o.Selector) == 0 {
|
|
return nil, fmt.Errorf("selector must be specified")
|
|
}
|
|
selector, err := parseLabels(o.Selector)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var labels map[string]string
|
|
if len(o.Labels) > 0 {
|
|
labels, err = parseLabels(o.Labels)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
name := o.Name
|
|
if len(name) == 0 {
|
|
name = o.DefaultName
|
|
if len(name) == 0 {
|
|
return nil, fmt.Errorf("name must be specified")
|
|
}
|
|
}
|
|
|
|
var portProtocolMap map[string][]string
|
|
if o.Protocols != "" {
|
|
portProtocolMap, err = parseProtocols(o.Protocols)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// ports takes precedence over port since it will be
|
|
// specified only when the user hasn't specified a port
|
|
// via --port and the exposed object has multiple ports.
|
|
var portString string
|
|
portString = o.Ports
|
|
if len(o.Ports) == 0 {
|
|
portString = o.Port
|
|
}
|
|
|
|
ports := []corev1.ServicePort{}
|
|
if len(portString) != 0 {
|
|
portStringSlice := strings.Split(portString, ",")
|
|
servicePortName := o.PortName
|
|
for i, stillPortString := range portStringSlice {
|
|
port, err := strconv.Atoi(stillPortString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
name := servicePortName
|
|
// If we are going to assign multiple ports to a service, we need to
|
|
// generate a different name for each one.
|
|
if len(portStringSlice) > 1 {
|
|
name = fmt.Sprintf("port-%d", i+1)
|
|
}
|
|
protocol := o.Protocol
|
|
|
|
switch {
|
|
case len(protocol) == 0 && len(portProtocolMap) == 0:
|
|
// Default to TCP, what the flag was doing previously.
|
|
protocol = "TCP"
|
|
case len(protocol) > 0 && len(portProtocolMap) > 0:
|
|
// User has specified the --protocol while exposing a multiprotocol resource
|
|
// We should stomp multiple protocols with the one specified ie. do nothing
|
|
case len(protocol) == 0 && len(portProtocolMap) > 0:
|
|
// no --protocol and we expose a multiprotocol resource
|
|
protocol = "TCP" // have the default so we can stay sane
|
|
if exposeProtocols, found := portProtocolMap[stillPortString]; found {
|
|
if len(exposeProtocols) == 1 {
|
|
protocol = exposeProtocols[0]
|
|
break
|
|
}
|
|
for _, exposeProtocol := range exposeProtocols {
|
|
name := fmt.Sprintf("port-%d-%s", i+1, strings.ToLower(exposeProtocol))
|
|
ports = append(ports, corev1.ServicePort{
|
|
Name: name,
|
|
Port: int32(port),
|
|
Protocol: corev1.Protocol(exposeProtocol),
|
|
})
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
ports = append(ports, corev1.ServicePort{
|
|
Name: name,
|
|
Port: int32(port),
|
|
Protocol: corev1.Protocol(protocol),
|
|
})
|
|
}
|
|
}
|
|
|
|
service := corev1.Service{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Labels: labels,
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Selector: selector,
|
|
Ports: ports,
|
|
},
|
|
}
|
|
targetPortString := o.TargetPort
|
|
if len(targetPortString) > 0 {
|
|
targetPort := intstr.Parse(targetPortString)
|
|
// Use the same target-port for every port
|
|
for i := range service.Spec.Ports {
|
|
service.Spec.Ports[i].TargetPort = targetPort
|
|
}
|
|
} else {
|
|
// If --target-port or --container-port haven't been specified, this
|
|
// should be the same as Port
|
|
for i := range service.Spec.Ports {
|
|
port := service.Spec.Ports[i].Port
|
|
service.Spec.Ports[i].TargetPort = intstr.FromInt32(port)
|
|
}
|
|
}
|
|
if len(o.ExternalIP) > 0 {
|
|
service.Spec.ExternalIPs = []string{o.ExternalIP}
|
|
}
|
|
if len(o.Type) != 0 {
|
|
service.Spec.Type = corev1.ServiceType(o.Type)
|
|
}
|
|
if service.Spec.Type == corev1.ServiceTypeLoadBalancer {
|
|
service.Spec.LoadBalancerIP = o.LoadBalancerIP
|
|
}
|
|
if len(o.SessionAffinity) != 0 {
|
|
switch corev1.ServiceAffinity(o.SessionAffinity) {
|
|
case corev1.ServiceAffinityNone:
|
|
service.Spec.SessionAffinity = corev1.ServiceAffinityNone
|
|
case corev1.ServiceAffinityClientIP:
|
|
service.Spec.SessionAffinity = corev1.ServiceAffinityClientIP
|
|
default:
|
|
return nil, fmt.Errorf("unknown session affinity: %s", o.SessionAffinity)
|
|
}
|
|
}
|
|
if len(o.ClusterIP) != 0 {
|
|
if o.ClusterIP == "None" {
|
|
service.Spec.ClusterIP = corev1.ClusterIPNone
|
|
} else {
|
|
service.Spec.ClusterIP = o.ClusterIP
|
|
}
|
|
}
|
|
return &service, nil
|
|
}
|
|
|
|
// parseLabels turns a string representation of a label set into a map[string]string
|
|
func parseLabels(labelSpec string) (map[string]string, error) {
|
|
if len(labelSpec) == 0 {
|
|
return nil, fmt.Errorf("no label spec passed")
|
|
}
|
|
labels := map[string]string{}
|
|
labelSpecs := strings.Split(labelSpec, ",")
|
|
for ix := range labelSpecs {
|
|
labelSpec := strings.Split(labelSpecs[ix], "=")
|
|
if len(labelSpec) != 2 {
|
|
return nil, fmt.Errorf("unexpected label spec: %s", labelSpecs[ix])
|
|
}
|
|
if len(labelSpec[0]) == 0 {
|
|
return nil, fmt.Errorf("unexpected empty label key")
|
|
}
|
|
labels[labelSpec[0]] = labelSpec[1]
|
|
}
|
|
return labels, nil
|
|
}
|
|
|
|
func makeProtocols(protocols map[string][]string) string {
|
|
var out []string
|
|
for key, value := range protocols {
|
|
for _, s := range value {
|
|
out = append(out, fmt.Sprintf("%s/%s", key, s))
|
|
}
|
|
}
|
|
return strings.Join(out, ",")
|
|
}
|
|
|
|
// parseProtocols turns a string representation of a protocols set into a map[string]string
|
|
func parseProtocols(protocols string) (map[string][]string, error) {
|
|
if len(protocols) == 0 {
|
|
return nil, fmt.Errorf("no protocols passed")
|
|
}
|
|
portProtocolMap := map[string][]string{}
|
|
protocolsSlice := strings.Split(protocols, ",")
|
|
for ix := range protocolsSlice {
|
|
portProtocol := strings.Split(protocolsSlice[ix], "/")
|
|
if len(portProtocol) != 2 {
|
|
return nil, fmt.Errorf("unexpected port protocol mapping: %s", protocolsSlice[ix])
|
|
}
|
|
if len(portProtocol[0]) == 0 {
|
|
return nil, fmt.Errorf("unexpected empty port")
|
|
}
|
|
if len(portProtocol[1]) == 0 {
|
|
return nil, fmt.Errorf("unexpected empty protocol")
|
|
}
|
|
port := portProtocol[0]
|
|
portProtocolMap[port] = append(portProtocolMap[port], portProtocol[1])
|
|
}
|
|
return portProtocolMap, nil
|
|
}
|