Merge pull request #114909 from aimuz/fix-114402
fix: kubectl expose fails for apps with same-port, different-protocol
This commit is contained in:
		@@ -38,7 +38,6 @@ import (
 | 
				
			|||||||
	"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/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"
 | 
				
			||||||
@@ -123,7 +122,7 @@ type ExposeServiceOptions struct {
 | 
				
			|||||||
	CanBeExposed              polymorphichelpers.CanBeExposedFunc
 | 
						CanBeExposed              polymorphichelpers.CanBeExposedFunc
 | 
				
			||||||
	MapBasedSelectorForObject func(runtime.Object) (string, error)
 | 
						MapBasedSelectorForObject func(runtime.Object) (string, error)
 | 
				
			||||||
	PortsForObject            polymorphichelpers.PortsForObjectFunc
 | 
						PortsForObject            polymorphichelpers.PortsForObjectFunc
 | 
				
			||||||
	ProtocolsForObject        func(runtime.Object) (map[string]string, error)
 | 
						ProtocolsForObject        polymorphichelpers.MultiProtocolsWithForObjectFunc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Namespace string
 | 
						Namespace string
 | 
				
			||||||
	Mapper    meta.RESTMapper
 | 
						Mapper    meta.RESTMapper
 | 
				
			||||||
@@ -276,7 +275,7 @@ func (o *ExposeServiceOptions) Complete(f cmdutil.Factory) error {
 | 
				
			|||||||
	o.ClientForMapping = f.ClientForMapping
 | 
						o.ClientForMapping = f.ClientForMapping
 | 
				
			||||||
	o.CanBeExposed = polymorphichelpers.CanBeExposedFn
 | 
						o.CanBeExposed = polymorphichelpers.CanBeExposedFn
 | 
				
			||||||
	o.MapBasedSelectorForObject = polymorphichelpers.MapBasedSelectorForObjectFn
 | 
						o.MapBasedSelectorForObject = polymorphichelpers.MapBasedSelectorForObjectFn
 | 
				
			||||||
	o.ProtocolsForObject = polymorphichelpers.ProtocolsForObjectFn
 | 
						o.ProtocolsForObject = polymorphichelpers.MultiProtocolsForObjectFn
 | 
				
			||||||
	o.PortsForObject = polymorphichelpers.PortsForObjectFn
 | 
						o.PortsForObject = polymorphichelpers.PortsForObjectFn
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	o.Mapper, err = f.ToRESTMapper()
 | 
						o.Mapper, err = f.ToRESTMapper()
 | 
				
			||||||
@@ -361,7 +360,7 @@ func (o *ExposeServiceOptions) RunExpose(cmd *cobra.Command, args []string) erro
 | 
				
			|||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return fmt.Errorf("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 := makeProtocols(protocolsMap); len(protocols) > 0 {
 | 
				
			||||||
			o.Protocols = protocols
 | 
								o.Protocols = protocols
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -375,13 +374,11 @@ func (o *ExposeServiceOptions) RunExpose(cmd *cobra.Command, args []string) erro
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// Generate new object
 | 
							// Generate new object
 | 
				
			||||||
		service, err := o.createService()
 | 
							service, err := o.createService()
 | 
				
			||||||
 | 
					 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		overrideService, err := o.NewOverrider(&corev1.Service{}).Apply(service)
 | 
							overrideService, err := o.NewOverrider(&corev1.Service{}).Apply(service)
 | 
				
			||||||
 | 
					 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -455,7 +452,7 @@ func (o *ExposeServiceOptions) createService() (*corev1.Service, error) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var portProtocolMap map[string]string
 | 
						var portProtocolMap map[string][]string
 | 
				
			||||||
	if o.Protocols != "" {
 | 
						if o.Protocols != "" {
 | 
				
			||||||
		portProtocolMap, err = parseProtocols(o.Protocols)
 | 
							portProtocolMap, err = parseProtocols(o.Protocols)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
@@ -499,8 +496,20 @@ func (o *ExposeServiceOptions) createService() (*corev1.Service, error) {
 | 
				
			|||||||
			case len(protocol) == 0 && len(portProtocolMap) > 0:
 | 
								case len(protocol) == 0 && len(portProtocolMap) > 0:
 | 
				
			||||||
				// no --protocol and we expose a multiprotocol resource
 | 
									// no --protocol and we expose a multiprotocol resource
 | 
				
			||||||
				protocol = "TCP" // have the default so we can stay sane
 | 
									protocol = "TCP" // have the default so we can stay sane
 | 
				
			||||||
				if exposeProtocol, found := portProtocolMap[stillPortString]; found {
 | 
									if exposeProtocols, found := portProtocolMap[stillPortString]; found {
 | 
				
			||||||
					protocol = exposeProtocol
 | 
										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{
 | 
								ports = append(ports, corev1.ServicePort{
 | 
				
			||||||
@@ -590,12 +599,22 @@ func parseLabels(labelSpec string) (map[string]string, error) {
 | 
				
			|||||||
	return labels, nil
 | 
						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
 | 
					// parseProtocols turns a string representation of a protocols set into a map[string]string
 | 
				
			||||||
func parseProtocols(protocols string) (map[string]string, error) {
 | 
					func parseProtocols(protocols string) (map[string][]string, error) {
 | 
				
			||||||
	if len(protocols) == 0 {
 | 
						if len(protocols) == 0 {
 | 
				
			||||||
		return nil, fmt.Errorf("no protocols passed")
 | 
							return nil, fmt.Errorf("no protocols passed")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	portProtocolMap := map[string]string{}
 | 
						portProtocolMap := map[string][]string{}
 | 
				
			||||||
	protocolsSlice := strings.Split(protocols, ",")
 | 
						protocolsSlice := strings.Split(protocols, ",")
 | 
				
			||||||
	for ix := range protocolsSlice {
 | 
						for ix := range protocolsSlice {
 | 
				
			||||||
		portProtocol := strings.Split(protocolsSlice[ix], "/")
 | 
							portProtocol := strings.Split(protocolsSlice[ix], "/")
 | 
				
			||||||
@@ -608,7 +627,8 @@ func parseProtocols(protocols string) (map[string]string, error) {
 | 
				
			|||||||
		if len(portProtocol[1]) == 0 {
 | 
							if len(portProtocol[1]) == 0 {
 | 
				
			||||||
			return nil, fmt.Errorf("unexpected empty protocol")
 | 
								return nil, fmt.Errorf("unexpected empty protocol")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		portProtocolMap[portProtocol[0]] = portProtocol[1]
 | 
							port := portProtocol[0]
 | 
				
			||||||
 | 
							portProtocolMap[port] = append(portProtocolMap[port], portProtocol[1])
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return portProtocolMap, nil
 | 
						return portProtocolMap, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1688,6 +1688,40 @@ func TestGenerateService(t *testing.T) {
 | 
				
			|||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							// Fixed #114402 kubectl expose fails for apps with same-port, different-protocol
 | 
				
			||||||
 | 
							"test #114402": {
 | 
				
			||||||
 | 
								selector:  "foo=bar,baz=blah",
 | 
				
			||||||
 | 
								name:      "test",
 | 
				
			||||||
 | 
								clusterIP: "None",
 | 
				
			||||||
 | 
								protocols: "53/TCP,53/UDP",
 | 
				
			||||||
 | 
								port:      "53",
 | 
				
			||||||
 | 
								expected: &corev1.Service{
 | 
				
			||||||
 | 
									ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
										Name: "test",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Spec: corev1.ServiceSpec{
 | 
				
			||||||
 | 
										Selector: map[string]string{
 | 
				
			||||||
 | 
											"foo": "bar",
 | 
				
			||||||
 | 
											"baz": "blah",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Ports: []corev1.ServicePort{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:       "port-1-tcp",
 | 
				
			||||||
 | 
												Port:       53,
 | 
				
			||||||
 | 
												Protocol:   corev1.ProtocolTCP,
 | 
				
			||||||
 | 
												TargetPort: intstr.FromInt(53),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:       "port-1-udp",
 | 
				
			||||||
 | 
												Port:       53,
 | 
				
			||||||
 | 
												Protocol:   corev1.ProtocolUDP,
 | 
				
			||||||
 | 
												TargetPort: intstr.FromInt(53),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										ClusterIP: corev1.ClusterIPNone,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		"check selector": {
 | 
							"check selector": {
 | 
				
			||||||
			name:       "test",
 | 
								name:       "test",
 | 
				
			||||||
			protocol:   "SCTP",
 | 
								protocol:   "SCTP",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -67,11 +67,21 @@ var MapBasedSelectorForObjectFn MapBasedSelectorForObjectFunc = mapBasedSelector
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// ProtocolsForObjectFunc will call the provided function on the protocols for the object,
 | 
					// ProtocolsForObjectFunc will call the provided function on the protocols for the object,
 | 
				
			||||||
// return nil-map if no protocols for the object, or return an error.
 | 
					// return nil-map if no protocols for the object, or return an error.
 | 
				
			||||||
 | 
					// Deprecated: use PortsProtocolsForObjectFunc instead.
 | 
				
			||||||
 | 
					// When the same port has different protocols, data will be lost
 | 
				
			||||||
type ProtocolsForObjectFunc func(object runtime.Object) (map[string]string, error)
 | 
					type ProtocolsForObjectFunc func(object runtime.Object) (map[string]string, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ProtocolsForObjectFn gives a way to easily override the function for unit testing if needed
 | 
					// ProtocolsForObjectFn gives a way to easily override the function for unit testing if needed
 | 
				
			||||||
 | 
					// Deprecated: use MultiProtocolsForObjectFn instead.
 | 
				
			||||||
var ProtocolsForObjectFn ProtocolsForObjectFunc = protocolsForObject
 | 
					var ProtocolsForObjectFn ProtocolsForObjectFunc = protocolsForObject
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MultiProtocolsWithForObjectFunc will call the provided function on the protocols for the object,
 | 
				
			||||||
 | 
					// return nil-map if no protocols for the object, or return an error.
 | 
				
			||||||
 | 
					type MultiProtocolsWithForObjectFunc func(object runtime.Object) (map[string][]string, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MultiProtocolsForObjectFn gives a way to easily override the function for unit testing if needed
 | 
				
			||||||
 | 
					var MultiProtocolsForObjectFn MultiProtocolsWithForObjectFunc = multiProtocolsForObject
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// PortsForObjectFunc returns the ports associated with the provided object
 | 
					// PortsForObjectFunc returns the ports associated with the provided object
 | 
				
			||||||
type PortsForObjectFunc func(object runtime.Object) ([]string, error)
 | 
					type PortsForObjectFunc func(object runtime.Object) ([]string, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,95 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2018 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 polymorphichelpers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						appsv1 "k8s.io/api/apps/v1"
 | 
				
			||||||
 | 
						appsv1beta1 "k8s.io/api/apps/v1beta1"
 | 
				
			||||||
 | 
						appsv1beta2 "k8s.io/api/apps/v1beta2"
 | 
				
			||||||
 | 
						corev1 "k8s.io/api/core/v1"
 | 
				
			||||||
 | 
						extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/runtime"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func multiProtocolsForObject(object runtime.Object) (map[string][]string, error) {
 | 
				
			||||||
 | 
						// TODO: replace with a swagger schema based approach (identify pod selector via schema introspection)
 | 
				
			||||||
 | 
						switch t := object.(type) {
 | 
				
			||||||
 | 
						case *corev1.ReplicationController:
 | 
				
			||||||
 | 
							return getMultiProtocols(t.Spec.Template.Spec), nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						case *corev1.Pod:
 | 
				
			||||||
 | 
							return getMultiProtocols(t.Spec), nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						case *corev1.Service:
 | 
				
			||||||
 | 
							return getServiceMultiProtocols(t.Spec), nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						case *extensionsv1beta1.Deployment:
 | 
				
			||||||
 | 
							return getMultiProtocols(t.Spec.Template.Spec), nil
 | 
				
			||||||
 | 
						case *appsv1.Deployment:
 | 
				
			||||||
 | 
							return getMultiProtocols(t.Spec.Template.Spec), nil
 | 
				
			||||||
 | 
						case *appsv1beta2.Deployment:
 | 
				
			||||||
 | 
							return getMultiProtocols(t.Spec.Template.Spec), nil
 | 
				
			||||||
 | 
						case *appsv1beta1.Deployment:
 | 
				
			||||||
 | 
							return getMultiProtocols(t.Spec.Template.Spec), nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						case *extensionsv1beta1.ReplicaSet:
 | 
				
			||||||
 | 
							return getMultiProtocols(t.Spec.Template.Spec), nil
 | 
				
			||||||
 | 
						case *appsv1.ReplicaSet:
 | 
				
			||||||
 | 
							return getMultiProtocols(t.Spec.Template.Spec), nil
 | 
				
			||||||
 | 
						case *appsv1beta2.ReplicaSet:
 | 
				
			||||||
 | 
							return getMultiProtocols(t.Spec.Template.Spec), nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("cannot extract protocols from %T", object)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getMultiProtocols(spec corev1.PodSpec) map[string][]string {
 | 
				
			||||||
 | 
						result := make(map[string][]string)
 | 
				
			||||||
 | 
						var protocol corev1.Protocol
 | 
				
			||||||
 | 
						for _, container := range spec.Containers {
 | 
				
			||||||
 | 
							for _, port := range container.Ports {
 | 
				
			||||||
 | 
								// Empty protocol must be defaulted (TCP)
 | 
				
			||||||
 | 
								protocol = corev1.ProtocolTCP
 | 
				
			||||||
 | 
								if len(port.Protocol) > 0 {
 | 
				
			||||||
 | 
									protocol = port.Protocol
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								p := strconv.Itoa(int(port.ContainerPort))
 | 
				
			||||||
 | 
								result[p] = append(result[p], string(protocol))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return result
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Extracts the protocols exposed by a service from the given service spec.
 | 
				
			||||||
 | 
					func getServiceMultiProtocols(spec corev1.ServiceSpec) map[string][]string {
 | 
				
			||||||
 | 
						result := make(map[string][]string)
 | 
				
			||||||
 | 
						var protocol corev1.Protocol
 | 
				
			||||||
 | 
						for _, servicePort := range spec.Ports {
 | 
				
			||||||
 | 
							// Empty protocol must be defaulted (TCP)
 | 
				
			||||||
 | 
							protocol = corev1.ProtocolTCP
 | 
				
			||||||
 | 
							if len(servicePort.Protocol) > 0 {
 | 
				
			||||||
 | 
								protocol = servicePort.Protocol
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							p := strconv.Itoa(int(servicePort.Port))
 | 
				
			||||||
 | 
							result[p] = append(result[p], string(protocol))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return result
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,213 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2018 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 polymorphichelpers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						corev1 "k8s.io/api/core/v1"
 | 
				
			||||||
 | 
						extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/runtime"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestMultiProtocolsForObject(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name      string
 | 
				
			||||||
 | 
							object    runtime.Object
 | 
				
			||||||
 | 
							expectErr bool
 | 
				
			||||||
 | 
							expected  map[string][]string
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "pod with TCP protocol",
 | 
				
			||||||
 | 
								object: &corev1.Pod{
 | 
				
			||||||
 | 
									Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
										Containers: []corev1.Container{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Ports: []corev1.ContainerPort{
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														ContainerPort: 101,
 | 
				
			||||||
 | 
														Protocol:      "TCP",
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: map[string][]string{"101": {"TCP"}},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							// No protocol--should default to TCP.
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "pod with no protocol",
 | 
				
			||||||
 | 
								object: &corev1.Pod{
 | 
				
			||||||
 | 
									Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
										Containers: []corev1.Container{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Ports: []corev1.ContainerPort{
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														ContainerPort: 101,
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: map[string][]string{"101": {"TCP"}},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "pod with same-port,different-protocol",
 | 
				
			||||||
 | 
								object: &corev1.Pod{
 | 
				
			||||||
 | 
									Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
										Containers: []corev1.Container{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Ports: []corev1.ContainerPort{
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														ContainerPort: 101,
 | 
				
			||||||
 | 
														Protocol:      "TCP",
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														ContainerPort: 101,
 | 
				
			||||||
 | 
														Protocol:      "UDP",
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: map[string][]string{"101": {"TCP", "UDP"}},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "service with TCP protocol",
 | 
				
			||||||
 | 
								object: &corev1.Service{
 | 
				
			||||||
 | 
									Spec: corev1.ServiceSpec{
 | 
				
			||||||
 | 
										Ports: []corev1.ServicePort{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Port:     101,
 | 
				
			||||||
 | 
												Protocol: "TCP",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: map[string][]string{"101": {"TCP"}},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							// No protocol for service port--default to TCP
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "service with no protocol",
 | 
				
			||||||
 | 
								object: &corev1.Service{
 | 
				
			||||||
 | 
									Spec: corev1.ServiceSpec{
 | 
				
			||||||
 | 
										Ports: []corev1.ServicePort{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Port: 101,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: map[string][]string{"101": {"TCP"}},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "replication with TCP protocol",
 | 
				
			||||||
 | 
								object: &corev1.ReplicationController{
 | 
				
			||||||
 | 
									Spec: corev1.ReplicationControllerSpec{
 | 
				
			||||||
 | 
										Template: &corev1.PodTemplateSpec{
 | 
				
			||||||
 | 
											Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
												Containers: []corev1.Container{
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														Ports: []corev1.ContainerPort{
 | 
				
			||||||
 | 
															{
 | 
				
			||||||
 | 
																ContainerPort: 101,
 | 
				
			||||||
 | 
																Protocol:      "TCP",
 | 
				
			||||||
 | 
															},
 | 
				
			||||||
 | 
														},
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: map[string][]string{"101": {"TCP"}},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "deployment with TCP protocol",
 | 
				
			||||||
 | 
								object: &extensionsv1beta1.Deployment{
 | 
				
			||||||
 | 
									Spec: extensionsv1beta1.DeploymentSpec{
 | 
				
			||||||
 | 
										Template: corev1.PodTemplateSpec{
 | 
				
			||||||
 | 
											Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
												Containers: []corev1.Container{
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														Ports: []corev1.ContainerPort{
 | 
				
			||||||
 | 
															{
 | 
				
			||||||
 | 
																ContainerPort: 101,
 | 
				
			||||||
 | 
																Protocol:      "TCP",
 | 
				
			||||||
 | 
															},
 | 
				
			||||||
 | 
														},
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: map[string][]string{"101": {"TCP"}},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "replicaset with TCP protocol",
 | 
				
			||||||
 | 
								object: &extensionsv1beta1.ReplicaSet{
 | 
				
			||||||
 | 
									Spec: extensionsv1beta1.ReplicaSetSpec{
 | 
				
			||||||
 | 
										Template: corev1.PodTemplateSpec{
 | 
				
			||||||
 | 
											Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
												Containers: []corev1.Container{
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														Ports: []corev1.ContainerPort{
 | 
				
			||||||
 | 
															{
 | 
				
			||||||
 | 
																ContainerPort: 101,
 | 
				
			||||||
 | 
																Protocol:      "TCP",
 | 
				
			||||||
 | 
															},
 | 
				
			||||||
 | 
														},
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: map[string][]string{"101": {"TCP"}},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:      "unsupported object",
 | 
				
			||||||
 | 
								object:    &corev1.Node{},
 | 
				
			||||||
 | 
								expectErr: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, test := range tests {
 | 
				
			||||||
 | 
							t.Run(test.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								actual, err := multiProtocolsForObject(test.object)
 | 
				
			||||||
 | 
								if test.expectErr {
 | 
				
			||||||
 | 
									if err == nil {
 | 
				
			||||||
 | 
										t.Error("unexpected non-error")
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if !test.expectErr && err != nil {
 | 
				
			||||||
 | 
									t.Errorf("unexpected error: %v", err)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if !reflect.DeepEqual(actual, test.expected) {
 | 
				
			||||||
 | 
									t.Errorf("expected ports %v, but got %v", test.expected, actual)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -60,19 +60,31 @@ func portsForObject(object runtime.Object) ([]string, error) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getPorts(spec corev1.PodSpec) []string {
 | 
					func getPorts(spec corev1.PodSpec) []string {
 | 
				
			||||||
	result := []string{}
 | 
						var result []string
 | 
				
			||||||
 | 
						exists := map[string]struct{}{}
 | 
				
			||||||
	for _, container := range spec.Containers {
 | 
						for _, container := range spec.Containers {
 | 
				
			||||||
		for _, port := range container.Ports {
 | 
							for _, port := range container.Ports {
 | 
				
			||||||
			result = append(result, strconv.Itoa(int(port.ContainerPort)))
 | 
								// remove duplicate ports
 | 
				
			||||||
 | 
								key := strconv.Itoa(int(port.ContainerPort))
 | 
				
			||||||
 | 
								if _, ok := exists[key]; !ok {
 | 
				
			||||||
 | 
									exists[key] = struct{}{}
 | 
				
			||||||
 | 
									result = append(result, key)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return result
 | 
						return result
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getServicePorts(spec corev1.ServiceSpec) []string {
 | 
					func getServicePorts(spec corev1.ServiceSpec) []string {
 | 
				
			||||||
	result := []string{}
 | 
						var result []string
 | 
				
			||||||
 | 
						exists := map[string]struct{}{}
 | 
				
			||||||
	for _, servicePort := range spec.Ports {
 | 
						for _, servicePort := range spec.Ports {
 | 
				
			||||||
		result = append(result, strconv.Itoa(int(servicePort.Port)))
 | 
							// remove duplicate ports
 | 
				
			||||||
 | 
							key := strconv.Itoa(int(servicePort.Port))
 | 
				
			||||||
 | 
							if _, ok := exists[key]; !ok {
 | 
				
			||||||
 | 
								exists[key] = struct{}{}
 | 
				
			||||||
 | 
								result = append(result, key)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return result
 | 
						return result
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,6 +30,7 @@ func TestPortsForObject(t *testing.T) {
 | 
				
			|||||||
	tests := []struct {
 | 
						tests := []struct {
 | 
				
			||||||
		object    runtime.Object
 | 
							object    runtime.Object
 | 
				
			||||||
		expectErr bool
 | 
							expectErr bool
 | 
				
			||||||
 | 
							expected  []string
 | 
				
			||||||
	}{
 | 
						}{
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			object: &corev1.Pod{
 | 
								object: &corev1.Pod{
 | 
				
			||||||
@@ -45,6 +46,74 @@ func TestPortsForObject(t *testing.T) {
 | 
				
			|||||||
					},
 | 
										},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								expected: []string{"101"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								object: &corev1.Pod{
 | 
				
			||||||
 | 
									Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
										Containers: []corev1.Container{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Ports: []corev1.ContainerPort{
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														ContainerPort: 101,
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														ContainerPort: 102,
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: []string{"101", "102"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								object: &corev1.Pod{
 | 
				
			||||||
 | 
									Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
										Containers: []corev1.Container{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Ports: []corev1.ContainerPort{
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														ContainerPort: 101,
 | 
				
			||||||
 | 
														Protocol:      corev1.ProtocolTCP,
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														ContainerPort: 101,
 | 
				
			||||||
 | 
														Protocol:      corev1.ProtocolUDP,
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														ContainerPort: 102,
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: []string{"101", "102"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								object: &corev1.Pod{
 | 
				
			||||||
 | 
									Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
										Containers: []corev1.Container{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Ports: []corev1.ContainerPort{
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														ContainerPort: 101,
 | 
				
			||||||
 | 
														Protocol:      corev1.ProtocolTCP,
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														ContainerPort: 102,
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														ContainerPort: 101,
 | 
				
			||||||
 | 
														Protocol:      corev1.ProtocolUDP,
 | 
				
			||||||
 | 
													},
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: []string{"101", "102"},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			object: &corev1.Service{
 | 
								object: &corev1.Service{
 | 
				
			||||||
@@ -56,6 +125,7 @@ func TestPortsForObject(t *testing.T) {
 | 
				
			|||||||
					},
 | 
										},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								expected: []string{"101"},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			object: &corev1.ReplicationController{
 | 
								object: &corev1.ReplicationController{
 | 
				
			||||||
@@ -75,6 +145,7 @@ func TestPortsForObject(t *testing.T) {
 | 
				
			|||||||
					},
 | 
										},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								expected: []string{"101"},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			object: &extensionsv1beta1.Deployment{
 | 
								object: &extensionsv1beta1.Deployment{
 | 
				
			||||||
@@ -94,6 +165,7 @@ func TestPortsForObject(t *testing.T) {
 | 
				
			|||||||
					},
 | 
										},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								expected: []string{"101"},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			object: &extensionsv1beta1.ReplicaSet{
 | 
								object: &extensionsv1beta1.ReplicaSet{
 | 
				
			||||||
@@ -113,13 +185,13 @@ func TestPortsForObject(t *testing.T) {
 | 
				
			|||||||
					},
 | 
										},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								expected: []string{"101"},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			object:    &corev1.Node{},
 | 
								object:    &corev1.Node{},
 | 
				
			||||||
			expectErr: true,
 | 
								expectErr: true,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	expectedPorts := []string{"101"}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, test := range tests {
 | 
						for _, test := range tests {
 | 
				
			||||||
		actual, err := portsForObject(test.object)
 | 
							actual, err := portsForObject(test.object)
 | 
				
			||||||
@@ -133,8 +205,8 @@ func TestPortsForObject(t *testing.T) {
 | 
				
			|||||||
			t.Errorf("unexpected error: %v", err)
 | 
								t.Errorf("unexpected error: %v", err)
 | 
				
			||||||
			continue
 | 
								continue
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if !reflect.DeepEqual(actual, expectedPorts) {
 | 
							if !reflect.DeepEqual(actual, test.expected) {
 | 
				
			||||||
			t.Errorf("expected ports %v, but got %v", expectedPorts, actual)
 | 
								t.Errorf("expected ports %v, but got %v", test.expected, actual)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user