mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-27 21:47:07 +00:00
Merge pull request #106824 from lauchokyip/removeExposeGenerator
Remove generator dependency of expose.go
This commit is contained in:
commit
d429c9820e
@ -17,7 +17,9 @@ limitations under the License.
|
|||||||
package expose
|
package expose
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -25,16 +27,17 @@ import (
|
|||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"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"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
"k8s.io/apimachinery/pkg/util/validation"
|
"k8s.io/apimachinery/pkg/util/validation"
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||||
"k8s.io/cli-runtime/pkg/printers"
|
"k8s.io/cli-runtime/pkg/printers"
|
||||||
"k8s.io/cli-runtime/pkg/resource"
|
"k8s.io/cli-runtime/pkg/resource"
|
||||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||||
"k8s.io/kubectl/pkg/generate"
|
"k8s.io/kubectl/pkg/generate"
|
||||||
generateversioned "k8s.io/kubectl/pkg/generate/versioned"
|
|
||||||
"k8s.io/kubectl/pkg/polymorphichelpers"
|
"k8s.io/kubectl/pkg/polymorphichelpers"
|
||||||
"k8s.io/kubectl/pkg/scheme"
|
"k8s.io/kubectl/pkg/scheme"
|
||||||
"k8s.io/kubectl/pkg/util"
|
"k8s.io/kubectl/pkg/util"
|
||||||
@ -83,6 +86,7 @@ var (
|
|||||||
kubectl expose deployment nginx --port=80 --target-port=8000`))
|
kubectl expose deployment nginx --port=80 --target-port=8000`))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ExposeServiceOptions holds the options for kubectl expose command
|
||||||
type ExposeServiceOptions struct {
|
type ExposeServiceOptions struct {
|
||||||
cmdutil.OverrideOptions
|
cmdutil.OverrideOptions
|
||||||
|
|
||||||
@ -91,13 +95,31 @@ type ExposeServiceOptions struct {
|
|||||||
PrintFlags *genericclioptions.PrintFlags
|
PrintFlags *genericclioptions.PrintFlags
|
||||||
PrintObj printers.ResourcePrinterFunc
|
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
|
DryRunStrategy cmdutil.DryRunStrategy
|
||||||
DryRunVerifier *resource.QueryParamVerifier
|
DryRunVerifier *resource.QueryParamVerifier
|
||||||
EnforceNamespace bool
|
EnforceNamespace bool
|
||||||
|
|
||||||
fieldManager string
|
fieldManager string
|
||||||
|
|
||||||
Generators func(string) map[string]generate.Generator
|
|
||||||
CanBeExposed polymorphichelpers.CanBeExposedFunc
|
CanBeExposed polymorphichelpers.CanBeExposedFunc
|
||||||
MapBasedSelectorForObject func(runtime.Object) (string, error)
|
MapBasedSelectorForObject func(runtime.Object) (string, error)
|
||||||
PortsForObject polymorphichelpers.PortsForObjectFunc
|
PortsForObject polymorphichelpers.PortsForObjectFunc
|
||||||
@ -113,6 +135,8 @@ type ExposeServiceOptions struct {
|
|||||||
genericclioptions.IOStreams
|
genericclioptions.IOStreams
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewExposeServiceOptions creates a new ExposeServiceOptions and return a pointer to the
|
||||||
|
// struct
|
||||||
func NewExposeServiceOptions(ioStreams genericclioptions.IOStreams) *ExposeServiceOptions {
|
func NewExposeServiceOptions(ioStreams genericclioptions.IOStreams) *ExposeServiceOptions {
|
||||||
return &ExposeServiceOptions{
|
return &ExposeServiceOptions{
|
||||||
RecordFlags: genericclioptions.NewRecordFlags(),
|
RecordFlags: genericclioptions.NewRecordFlags(),
|
||||||
@ -123,6 +147,7 @@ func NewExposeServiceOptions(ioStreams genericclioptions.IOStreams) *ExposeServi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewCmdExposeService is a command to expose the service from user's input
|
||||||
func NewCmdExposeService(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
|
func NewCmdExposeService(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
|
||||||
o := NewExposeServiceOptions(streams)
|
o := NewExposeServiceOptions(streams)
|
||||||
|
|
||||||
@ -148,20 +173,17 @@ func NewCmdExposeService(f cmdutil.Factory, streams genericclioptions.IOStreams)
|
|||||||
o.RecordFlags.AddFlags(cmd)
|
o.RecordFlags.AddFlags(cmd)
|
||||||
o.PrintFlags.AddFlags(cmd)
|
o.PrintFlags.AddFlags(cmd)
|
||||||
|
|
||||||
cmd.Flags().String("generator", "service/v2", i18n.T("The name of the API generator to use. There are 2 generators: 'service/v1' and 'service/v2'. The only difference between them is that service port in v1 is named 'default', while it is left unnamed in v2. Default is 'service/v2'."))
|
cmd.Flags().StringVar(&o.Protocol, "protocol", o.Protocol, i18n.T("The network protocol for the service to be created. Default is 'TCP'."))
|
||||||
cmd.Flags().String("protocol", "", i18n.T("The network protocol for the service to be created. Default is 'TCP'."))
|
cmd.Flags().StringVar(&o.Port, "port", o.Port, i18n.T("The port that the service should serve on. Copied from the resource being exposed, if unspecified"))
|
||||||
cmd.Flags().String("port", "", i18n.T("The port that the service should serve on. Copied from the resource being exposed, if unspecified"))
|
cmd.Flags().StringVar(&o.Type, "type", o.Type, i18n.T("Type for this service: ClusterIP, NodePort, LoadBalancer, or ExternalName. Default is 'ClusterIP'."))
|
||||||
cmd.Flags().String("type", "", i18n.T("Type for this service: ClusterIP, NodePort, LoadBalancer, or ExternalName. Default is 'ClusterIP'."))
|
cmd.Flags().StringVar(&o.LoadBalancerIP, "load-balancer-ip", o.LoadBalancerIP, i18n.T("IP to assign to the LoadBalancer. If empty, an ephemeral IP will be created and used (cloud-provider specific)."))
|
||||||
cmd.Flags().String("load-balancer-ip", "", i18n.T("IP to assign to the LoadBalancer. If empty, an ephemeral IP will be created and used (cloud-provider specific)."))
|
cmd.Flags().StringVar(&o.Selector, "selector", o.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.)"))
|
||||||
cmd.Flags().String("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.)"))
|
cmd.Flags().StringVarP(&o.Labels, "labels", "l", o.Labels, "Labels to apply to the service created by this call.")
|
||||||
cmd.Flags().StringP("labels", "l", "", "Labels to apply to the service created by this call.")
|
cmd.Flags().StringVar(&o.TargetPort, "target-port", o.TargetPort, i18n.T("Name or number for the port on the container that the service should direct traffic to. Optional."))
|
||||||
cmd.Flags().String("container-port", "", i18n.T("Synonym for --target-port"))
|
cmd.Flags().StringVar(&o.ExternalIP, "external-ip", o.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().MarkDeprecated("container-port", "--container-port will be removed in the future, please use --target-port instead")
|
cmd.Flags().StringVar(&o.Name, "name", o.Name, i18n.T("The name for the newly created object."))
|
||||||
cmd.Flags().String("target-port", "", i18n.T("Name or number for the port on the container that the service should direct traffic to. Optional."))
|
cmd.Flags().StringVar(&o.SessionAffinity, "session-affinity", o.SessionAffinity, i18n.T("If non-empty, set the session affinity for the service to this; legal values: 'None', 'ClientIP'"))
|
||||||
cmd.Flags().String("external-ip", "", 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(&o.ClusterIP, "cluster-ip", o.ClusterIP, i18n.T("ClusterIP to be assigned to the service. Leave empty to auto-allocate, or set to 'None' to create a headless service."))
|
||||||
cmd.Flags().String("name", "", i18n.T("The name for the newly created object."))
|
|
||||||
cmd.Flags().String("session-affinity", "", i18n.T("If non-empty, set the session affinity for the service to this; legal values: 'None', 'ClientIP'"))
|
|
||||||
cmd.Flags().String("cluster-ip", "", 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, &o.fieldManager, "kubectl-expose")
|
cmdutil.AddFieldManagerFlagVar(cmd, &o.fieldManager, "kubectl-expose")
|
||||||
o.AddOverrideFlags(cmd)
|
o.AddOverrideFlags(cmd)
|
||||||
|
|
||||||
@ -172,6 +194,7 @@ func NewCmdExposeService(f cmdutil.Factory, streams genericclioptions.IOStreams)
|
|||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Complete loads data from the command line environment
|
||||||
func (o *ExposeServiceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
|
func (o *ExposeServiceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
|
||||||
var err error
|
var err error
|
||||||
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
|
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
|
||||||
@ -197,7 +220,6 @@ func (o *ExposeServiceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) e
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
o.Generators = generateversioned.GeneratorFn
|
|
||||||
o.Builder = f.NewBuilder()
|
o.Builder = f.NewBuilder()
|
||||||
o.ClientForMapping = f.ClientForMapping
|
o.ClientForMapping = f.ClientForMapping
|
||||||
o.CanBeExposed = polymorphichelpers.CanBeExposedFn
|
o.CanBeExposed = polymorphichelpers.CanBeExposedFn
|
||||||
@ -218,6 +240,8 @@ func (o *ExposeServiceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) e
|
|||||||
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 {
|
func (o *ExposeServiceOptions) RunExpose(cmd *cobra.Command, args []string) error {
|
||||||
r := o.Builder.
|
r := o.Builder.
|
||||||
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
|
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
|
||||||
@ -229,18 +253,9 @@ func (o *ExposeServiceOptions) RunExpose(cmd *cobra.Command, args []string) erro
|
|||||||
Do()
|
Do()
|
||||||
err := r.Err()
|
err := r.Err()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cmdutil.UsageErrorf(cmd, err.Error())
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the generator, setup and validate all required parameters
|
|
||||||
generatorName := cmdutil.GetFlagString(cmd, "generator")
|
|
||||||
generators := o.Generators("expose")
|
|
||||||
generator, found := generators[generatorName]
|
|
||||||
if !found {
|
|
||||||
return cmdutil.UsageErrorf(cmd, "generator %q not found.", generatorName)
|
|
||||||
}
|
|
||||||
names := generator.ParamNames()
|
|
||||||
|
|
||||||
err = r.Visit(func(info *resource.Info, err error) error {
|
err = r.Visit(func(info *resource.Info, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -251,98 +266,90 @@ func (o *ExposeServiceOptions) RunExpose(cmd *cobra.Command, args []string) erro
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
params := generate.MakeParams(cmd, names)
|
|
||||||
name := info.Name
|
name := info.Name
|
||||||
if len(name) > validation.DNS1035LabelMaxLength {
|
if len(name) > validation.DNS1035LabelMaxLength {
|
||||||
name = name[:validation.DNS1035LabelMaxLength]
|
name = name[:validation.DNS1035LabelMaxLength]
|
||||||
}
|
}
|
||||||
params["default-name"] = name
|
o.DefaultName = name
|
||||||
|
|
||||||
// For objects that need a pod selector, derive it from the exposed object in case a user
|
// For objects that need a pod selector, derive it from the exposed object in case a user
|
||||||
// didn't explicitly specify one via --selector
|
// didn't explicitly specify one via --selector
|
||||||
if s, found := params["selector"]; found && generate.IsZero(s) {
|
if len(o.Selector) == 0 {
|
||||||
s, err := o.MapBasedSelectorForObject(info.Object)
|
s, err := o.MapBasedSelectorForObject(info.Object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cmdutil.UsageErrorf(cmd, "couldn't retrieve selectors via --selector flag or introspection: %v", err)
|
return fmt.Errorf("couldn't retrieve selectors via --selector flag or introspection: %v", err)
|
||||||
}
|
}
|
||||||
params["selector"] = s
|
o.Selector = s
|
||||||
}
|
}
|
||||||
|
|
||||||
isHeadlessService := params["cluster-ip"] == "None"
|
isHeadlessService := o.ClusterIP == "None"
|
||||||
|
|
||||||
// For objects that need a port, derive it from the exposed object in case a user
|
// For objects that need a port, derive it from the exposed object in case a user
|
||||||
// didn't explicitly specify one via --port
|
// didn't explicitly specify one via --port
|
||||||
if port, found := params["port"]; found && generate.IsZero(port) {
|
if len(o.Port) == 0 {
|
||||||
ports, err := o.PortsForObject(info.Object)
|
ports, err := o.PortsForObject(info.Object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cmdutil.UsageErrorf(cmd, "couldn't find port via --port flag or introspection: %v", err)
|
return fmt.Errorf("couldn't find port via --port flag or introspection: %v", err)
|
||||||
}
|
}
|
||||||
switch len(ports) {
|
switch len(ports) {
|
||||||
case 0:
|
case 0:
|
||||||
if !isHeadlessService {
|
if !isHeadlessService {
|
||||||
return cmdutil.UsageErrorf(cmd, "couldn't find port via --port flag or introspection")
|
return fmt.Errorf("couldn't find port via --port flag or introspection")
|
||||||
}
|
}
|
||||||
case 1:
|
case 1:
|
||||||
params["port"] = ports[0]
|
o.Port = ports[0]
|
||||||
default:
|
default:
|
||||||
params["ports"] = strings.Join(ports, ",")
|
o.Ports = strings.Join(ports, ",")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always try to derive protocols from the exposed object, may use
|
// Always try to derive protocols from the exposed object, may use
|
||||||
// different protocols for different ports.
|
// different protocols for different ports.
|
||||||
if _, found := params["protocol"]; found {
|
|
||||||
protocolsMap, err := o.ProtocolsForObject(info.Object)
|
protocolsMap, err := o.ProtocolsForObject(info.Object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cmdutil.UsageErrorf(cmd, "couldn't find protocol via introspection: %v", err)
|
return fmt.Errorf("couldn't find protocol via introspection: %v", err)
|
||||||
}
|
}
|
||||||
if protocols := generate.MakeProtocols(protocolsMap); !generate.IsZero(protocols) {
|
if protocols := generate.MakeProtocols(protocolsMap); !generate.IsZero(protocols) {
|
||||||
params["protocols"] = protocols
|
o.Protocols = protocols
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if generate.IsZero(params["labels"]) {
|
if len(o.Labels) == 0 {
|
||||||
labels, err := meta.NewAccessor().Labels(info.Object)
|
labels, err := meta.NewAccessor().Labels(info.Object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
params["labels"] = polymorphichelpers.MakeLabels(labels)
|
o.Labels = polymorphichelpers.MakeLabels(labels)
|
||||||
}
|
|
||||||
if err = generate.ValidateParams(names, params); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Check for invalid flags used against the present generator.
|
|
||||||
if err := generate.EnsureFlagsValid(cmd, generators, generatorName); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate new object
|
// Generate new object
|
||||||
object, err := generator.Generate(params)
|
service, err := o.createService()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
object, err = o.NewOverrider(&corev1.Service{}).Apply(object)
|
overrideService, err := o.NewOverrider(&corev1.Service{}).Apply(service)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := o.Recorder.Record(object); err != nil {
|
if err := o.Recorder.Record(overrideService); err != nil {
|
||||||
klog.V(4).Infof("error recording current command: %v", err)
|
klog.V(4).Infof("error recording current command: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.DryRunStrategy == cmdutil.DryRunClient {
|
if o.DryRunStrategy == cmdutil.DryRunClient {
|
||||||
if meta, err := meta.Accessor(object); err == nil && o.EnforceNamespace {
|
if meta, err := meta.Accessor(overrideService); err == nil && o.EnforceNamespace {
|
||||||
meta.SetNamespace(o.Namespace)
|
meta.SetNamespace(o.Namespace)
|
||||||
}
|
}
|
||||||
return o.PrintObj(object, o.Out)
|
return o.PrintObj(overrideService, o.Out)
|
||||||
}
|
}
|
||||||
if err := util.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), object, scheme.DefaultJSONEncoder()); err != nil {
|
if err := util.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), overrideService, scheme.DefaultJSONEncoder()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
asUnstructured := &unstructured.Unstructured{}
|
asUnstructured := &unstructured.Unstructured{}
|
||||||
if err := scheme.Scheme.Convert(object, asUnstructured, nil); err != nil {
|
if err := scheme.Scheme.Convert(overrideService, asUnstructured, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
gvks, _, err := unstructuredscheme.NewUnstructuredObjectTyper().ObjectKinds(asUnstructured)
|
gvks, _, err := unstructuredscheme.NewUnstructuredObjectTyper().ObjectKinds(asUnstructured)
|
||||||
@ -379,3 +386,186 @@ func (o *ExposeServiceOptions) RunExpose(cmd *cobra.Command, args []string) erro
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 exposeProtocol, found := portProtocolMap[stillPortString]; found {
|
||||||
|
protocol = exposeProtocol
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
var targetPort intstr.IntOrString
|
||||||
|
if portNum, err := strconv.Atoi(targetPortString); err != nil {
|
||||||
|
targetPort = intstr.FromString(targetPortString)
|
||||||
|
} else {
|
||||||
|
targetPort = intstr.FromInt(portNum)
|
||||||
|
}
|
||||||
|
// 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.FromInt(int(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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
portProtocolMap[portProtocol[0]] = portProtocol[1]
|
||||||
|
}
|
||||||
|
return portProtocolMap, nil
|
||||||
|
}
|
||||||
|
@ -21,7 +21,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
@ -856,3 +858,878 @@ status:
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGenerateService(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
selector string
|
||||||
|
name string
|
||||||
|
port string
|
||||||
|
protocol string
|
||||||
|
protocols string
|
||||||
|
targetPort string
|
||||||
|
clusterIP string
|
||||||
|
labels string
|
||||||
|
externalIP string
|
||||||
|
serviceType string
|
||||||
|
sessionAffinity string
|
||||||
|
setup func(t *testing.T, exposeServiceOptions *ExposeServiceOptions) func()
|
||||||
|
|
||||||
|
expected *corev1.Service
|
||||||
|
expectErr string
|
||||||
|
}{
|
||||||
|
"test1": {
|
||||||
|
selector: "foo=bar,baz=blah",
|
||||||
|
name: "test",
|
||||||
|
port: "80",
|
||||||
|
protocol: "TCP",
|
||||||
|
targetPort: "1234",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "blah",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
Port: 80,
|
||||||
|
Protocol: "TCP",
|
||||||
|
TargetPort: intstr.FromInt(1234),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test2": {
|
||||||
|
selector: "foo=bar,baz=blah",
|
||||||
|
name: "test",
|
||||||
|
port: "80",
|
||||||
|
protocol: "UDP",
|
||||||
|
targetPort: "foobar",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "blah",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
Port: 80,
|
||||||
|
Protocol: "UDP",
|
||||||
|
TargetPort: intstr.FromString("foobar"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test3": {
|
||||||
|
selector: "foo=bar,baz=blah",
|
||||||
|
labels: "key1=value1,key2=value2",
|
||||||
|
name: "test",
|
||||||
|
port: "80",
|
||||||
|
protocol: "TCP",
|
||||||
|
targetPort: "1234",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"key1": "value1",
|
||||||
|
"key2": "value2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "blah",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
Port: 80,
|
||||||
|
Protocol: "TCP",
|
||||||
|
TargetPort: intstr.FromInt(1234),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test4": {
|
||||||
|
selector: "foo=bar,baz=blah",
|
||||||
|
name: "test",
|
||||||
|
port: "80",
|
||||||
|
protocol: "UDP",
|
||||||
|
externalIP: "1.2.3.4",
|
||||||
|
targetPort: "foobar",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "blah",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
Port: 80,
|
||||||
|
Protocol: "UDP",
|
||||||
|
TargetPort: intstr.FromString("foobar"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExternalIPs: []string{"1.2.3.4"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test5": {
|
||||||
|
selector: "foo=bar,baz=blah",
|
||||||
|
name: "test",
|
||||||
|
port: "80",
|
||||||
|
protocol: "UDP",
|
||||||
|
externalIP: "1.2.3.4",
|
||||||
|
serviceType: "LoadBalancer",
|
||||||
|
targetPort: "foobar",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "blah",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
Port: 80,
|
||||||
|
Protocol: "UDP",
|
||||||
|
TargetPort: intstr.FromString("foobar"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: corev1.ServiceTypeLoadBalancer,
|
||||||
|
ExternalIPs: []string{"1.2.3.4"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test6": {
|
||||||
|
selector: "foo=bar,baz=blah",
|
||||||
|
name: "test",
|
||||||
|
port: "80",
|
||||||
|
protocol: "UDP",
|
||||||
|
targetPort: "foobar",
|
||||||
|
serviceType: string(corev1.ServiceTypeNodePort),
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "blah",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
Port: 80,
|
||||||
|
Protocol: "UDP",
|
||||||
|
TargetPort: intstr.FromString("foobar"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: corev1.ServiceTypeNodePort,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test7": {
|
||||||
|
selector: "foo=bar,baz=blah",
|
||||||
|
name: "test",
|
||||||
|
port: "80",
|
||||||
|
protocol: "UDP",
|
||||||
|
targetPort: "foobar",
|
||||||
|
serviceType: string(corev1.ServiceTypeNodePort),
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "blah",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
Port: 80,
|
||||||
|
Protocol: "UDP",
|
||||||
|
TargetPort: intstr.FromString("foobar"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: corev1.ServiceTypeNodePort,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test8": {
|
||||||
|
selector: "foo=bar,baz=blah",
|
||||||
|
name: "test",
|
||||||
|
port: "80",
|
||||||
|
protocol: "TCP",
|
||||||
|
targetPort: "1234",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "blah",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
Port: 80,
|
||||||
|
Protocol: "TCP",
|
||||||
|
TargetPort: intstr.FromInt(1234),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test9": {
|
||||||
|
selector: "foo=bar,baz=blah",
|
||||||
|
name: "test",
|
||||||
|
port: "80",
|
||||||
|
protocol: "TCP",
|
||||||
|
sessionAffinity: "ClientIP",
|
||||||
|
targetPort: "1234",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "blah",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
Port: 80,
|
||||||
|
Protocol: "TCP",
|
||||||
|
TargetPort: intstr.FromInt(1234),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SessionAffinity: corev1.ServiceAffinityClientIP,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test10": {
|
||||||
|
selector: "foo=bar,baz=blah",
|
||||||
|
name: "test",
|
||||||
|
port: "80",
|
||||||
|
protocol: "TCP",
|
||||||
|
clusterIP: "10.10.10.10",
|
||||||
|
targetPort: "1234",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "blah",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
|
||||||
|
Port: 80,
|
||||||
|
Protocol: "TCP",
|
||||||
|
TargetPort: intstr.FromInt(1234),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ClusterIP: "10.10.10.10",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test11": {
|
||||||
|
selector: "foo=bar,baz=blah",
|
||||||
|
name: "test",
|
||||||
|
port: "80",
|
||||||
|
protocol: "TCP",
|
||||||
|
clusterIP: "None",
|
||||||
|
targetPort: "1234",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "blah",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
|
||||||
|
Port: 80,
|
||||||
|
Protocol: "TCP",
|
||||||
|
TargetPort: intstr.FromInt(1234),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ClusterIP: corev1.ClusterIPNone,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test12": {
|
||||||
|
selector: "foo=bar",
|
||||||
|
name: "test",
|
||||||
|
port: "80,443",
|
||||||
|
protocol: "TCP",
|
||||||
|
targetPort: "foobar",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
Name: "port-1",
|
||||||
|
Port: 80,
|
||||||
|
Protocol: corev1.ProtocolTCP,
|
||||||
|
TargetPort: intstr.FromString("foobar"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "port-2",
|
||||||
|
Port: 443,
|
||||||
|
Protocol: corev1.ProtocolTCP,
|
||||||
|
TargetPort: intstr.FromString("foobar"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test13": {
|
||||||
|
selector: "foo=bar",
|
||||||
|
name: "test",
|
||||||
|
port: "80,443",
|
||||||
|
protocol: "UDP",
|
||||||
|
targetPort: "1234",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
Name: "port-1",
|
||||||
|
Port: 80,
|
||||||
|
Protocol: corev1.ProtocolUDP,
|
||||||
|
TargetPort: intstr.FromInt(1234),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "port-2",
|
||||||
|
Port: 443,
|
||||||
|
Protocol: corev1.ProtocolUDP,
|
||||||
|
TargetPort: intstr.FromInt(1234),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test14": {
|
||||||
|
selector: "foo=bar",
|
||||||
|
name: "test",
|
||||||
|
port: "80,443",
|
||||||
|
protocol: "TCP",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
Name: "port-1",
|
||||||
|
Port: 80,
|
||||||
|
Protocol: corev1.ProtocolTCP,
|
||||||
|
TargetPort: intstr.FromInt(80),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "port-2",
|
||||||
|
Port: 443,
|
||||||
|
Protocol: corev1.ProtocolTCP,
|
||||||
|
TargetPort: intstr.FromInt(443),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test15": {
|
||||||
|
selector: "foo=bar",
|
||||||
|
name: "test",
|
||||||
|
port: "80,8080",
|
||||||
|
protocols: "8080/UDP",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
Name: "port-1",
|
||||||
|
Port: 80,
|
||||||
|
Protocol: corev1.ProtocolTCP,
|
||||||
|
TargetPort: intstr.FromInt(80),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "port-2",
|
||||||
|
Port: 8080,
|
||||||
|
Protocol: corev1.ProtocolUDP,
|
||||||
|
TargetPort: intstr.FromInt(8080),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test16": {
|
||||||
|
selector: "foo=bar",
|
||||||
|
name: "test",
|
||||||
|
port: "80,8080,8081",
|
||||||
|
protocols: "8080/UDP,8081/TCP",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
Name: "port-1",
|
||||||
|
Port: 80,
|
||||||
|
Protocol: corev1.ProtocolTCP,
|
||||||
|
TargetPort: intstr.FromInt(80),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "port-2",
|
||||||
|
Port: 8080,
|
||||||
|
Protocol: corev1.ProtocolUDP,
|
||||||
|
TargetPort: intstr.FromInt(8080),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "port-3",
|
||||||
|
Port: 8081,
|
||||||
|
Protocol: corev1.ProtocolTCP,
|
||||||
|
TargetPort: intstr.FromInt(8081),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test17": {
|
||||||
|
selector: "foo=bar,baz=blah",
|
||||||
|
name: "test",
|
||||||
|
protocol: "TCP",
|
||||||
|
clusterIP: "None",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "blah",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{},
|
||||||
|
ClusterIP: corev1.ClusterIPNone,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test18": {
|
||||||
|
selector: "foo=bar",
|
||||||
|
name: "test",
|
||||||
|
clusterIP: "None",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{},
|
||||||
|
ClusterIP: corev1.ClusterIPNone,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test19": {
|
||||||
|
selector: "foo=bar,baz=blah",
|
||||||
|
name: "test",
|
||||||
|
port: "80",
|
||||||
|
protocol: "SCTP",
|
||||||
|
targetPort: "1234",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "blah",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
|
||||||
|
Port: 80,
|
||||||
|
Protocol: "SCTP",
|
||||||
|
TargetPort: intstr.FromInt(1234),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test20": {
|
||||||
|
selector: "foo=bar,baz=blah",
|
||||||
|
labels: "key1=value1,key2=value2",
|
||||||
|
name: "test",
|
||||||
|
port: "80",
|
||||||
|
protocol: "SCTP",
|
||||||
|
targetPort: "1234",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"key1": "value1",
|
||||||
|
"key2": "value2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "blah",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
|
||||||
|
Port: 80,
|
||||||
|
Protocol: "SCTP",
|
||||||
|
TargetPort: intstr.FromInt(1234),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test21": {
|
||||||
|
selector: "foo=bar,baz=blah",
|
||||||
|
name: "test",
|
||||||
|
port: "80",
|
||||||
|
protocol: "SCTP",
|
||||||
|
targetPort: "1234",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "blah",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
|
||||||
|
Port: 80,
|
||||||
|
Protocol: "SCTP",
|
||||||
|
TargetPort: intstr.FromInt(1234),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test22": {
|
||||||
|
selector: "foo=bar,baz=blah",
|
||||||
|
name: "test",
|
||||||
|
port: "80",
|
||||||
|
protocol: "SCTP",
|
||||||
|
sessionAffinity: "ClientIP",
|
||||||
|
targetPort: "1234",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "blah",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
Port: 80,
|
||||||
|
Protocol: "SCTP",
|
||||||
|
TargetPort: intstr.FromInt(1234),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SessionAffinity: corev1.ServiceAffinityClientIP,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test23": {
|
||||||
|
selector: "foo=bar,baz=blah",
|
||||||
|
name: "test",
|
||||||
|
port: "80",
|
||||||
|
protocol: "SCTP",
|
||||||
|
clusterIP: "10.10.10.10",
|
||||||
|
targetPort: "1234",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "blah",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
Port: 80,
|
||||||
|
Protocol: "SCTP",
|
||||||
|
TargetPort: intstr.FromInt(1234),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ClusterIP: "10.10.10.10",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test24": {
|
||||||
|
selector: "foo=bar,baz=blah",
|
||||||
|
name: "test",
|
||||||
|
port: "80",
|
||||||
|
protocol: "SCTP",
|
||||||
|
clusterIP: "None",
|
||||||
|
targetPort: "1234",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "blah",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
|
||||||
|
Port: 80,
|
||||||
|
Protocol: "SCTP",
|
||||||
|
TargetPort: intstr.FromInt(1234),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ClusterIP: corev1.ClusterIPNone,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test25": {
|
||||||
|
selector: "foo=bar",
|
||||||
|
name: "test",
|
||||||
|
port: "80,443",
|
||||||
|
protocol: "SCTP",
|
||||||
|
targetPort: "foobar",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
Name: "port-1",
|
||||||
|
Port: 80,
|
||||||
|
Protocol: corev1.ProtocolSCTP,
|
||||||
|
TargetPort: intstr.FromString("foobar"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "port-2",
|
||||||
|
Port: 443,
|
||||||
|
Protocol: corev1.ProtocolSCTP,
|
||||||
|
TargetPort: intstr.FromString("foobar"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test26": {
|
||||||
|
selector: "foo=bar",
|
||||||
|
name: "test",
|
||||||
|
port: "80,443",
|
||||||
|
protocol: "SCTP",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
Name: "port-1",
|
||||||
|
Port: 80,
|
||||||
|
Protocol: corev1.ProtocolSCTP,
|
||||||
|
TargetPort: intstr.FromInt(80),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "port-2",
|
||||||
|
Port: 443,
|
||||||
|
Protocol: corev1.ProtocolSCTP,
|
||||||
|
TargetPort: intstr.FromInt(443),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test27": {
|
||||||
|
selector: "foo=bar",
|
||||||
|
name: "test",
|
||||||
|
port: "80,8080",
|
||||||
|
protocols: "8080/SCTP",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
Name: "port-1",
|
||||||
|
Port: 80,
|
||||||
|
Protocol: corev1.ProtocolTCP,
|
||||||
|
TargetPort: intstr.FromInt(80),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "port-2",
|
||||||
|
Port: 8080,
|
||||||
|
Protocol: corev1.ProtocolSCTP,
|
||||||
|
TargetPort: intstr.FromInt(8080),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test28": {
|
||||||
|
selector: "foo=bar",
|
||||||
|
name: "test",
|
||||||
|
port: "80,8080,8081,8082",
|
||||||
|
protocols: "8080/UDP,8081/TCP,8082/SCTP",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{
|
||||||
|
{
|
||||||
|
Name: "port-1",
|
||||||
|
Port: 80,
|
||||||
|
Protocol: corev1.ProtocolTCP,
|
||||||
|
TargetPort: intstr.FromInt(80),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "port-2",
|
||||||
|
Port: 8080,
|
||||||
|
Protocol: corev1.ProtocolUDP,
|
||||||
|
TargetPort: intstr.FromInt(8080),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "port-3",
|
||||||
|
Port: 8081,
|
||||||
|
Protocol: corev1.ProtocolTCP,
|
||||||
|
TargetPort: intstr.FromInt(8081),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "port-4",
|
||||||
|
Port: 8082,
|
||||||
|
Protocol: corev1.ProtocolSCTP,
|
||||||
|
TargetPort: intstr.FromInt(8082),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test 29": {
|
||||||
|
selector: "foo=bar,baz=blah",
|
||||||
|
name: "test",
|
||||||
|
protocol: "SCTP",
|
||||||
|
targetPort: "1234",
|
||||||
|
clusterIP: "None",
|
||||||
|
expected: &corev1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Selector: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "blah",
|
||||||
|
},
|
||||||
|
Ports: []corev1.ServicePort{},
|
||||||
|
ClusterIP: corev1.ClusterIPNone,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"check selector": {
|
||||||
|
name: "test",
|
||||||
|
protocol: "SCTP",
|
||||||
|
targetPort: "1234",
|
||||||
|
clusterIP: "None",
|
||||||
|
expectErr: `selector must be specified`,
|
||||||
|
},
|
||||||
|
"check name": {
|
||||||
|
selector: "foo=bar,baz=blah",
|
||||||
|
protocol: "SCTP",
|
||||||
|
targetPort: "1234",
|
||||||
|
clusterIP: "None",
|
||||||
|
expectErr: `name must be specified`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, test := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
exposeServiceOptions := ExposeServiceOptions{
|
||||||
|
Selector: test.selector,
|
||||||
|
Name: test.name,
|
||||||
|
Protocol: test.protocol,
|
||||||
|
Protocols: test.protocols,
|
||||||
|
Port: test.port,
|
||||||
|
ClusterIP: test.clusterIP,
|
||||||
|
TargetPort: test.targetPort,
|
||||||
|
Labels: test.labels,
|
||||||
|
ExternalIP: test.externalIP,
|
||||||
|
Type: test.serviceType,
|
||||||
|
SessionAffinity: test.sessionAffinity,
|
||||||
|
}
|
||||||
|
|
||||||
|
service, err := exposeServiceOptions.createService()
|
||||||
|
if test.expectErr == "" {
|
||||||
|
require.NoError(t, err)
|
||||||
|
if !apiequality.Semantic.DeepEqual(service, test.expected) {
|
||||||
|
t.Errorf("\nexpected:\n%#v\ngot:\n%#v", test.expected, service)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
require.EqualError(t, err, test.expectErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user