...

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

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

     1  /*
     2  Copyright 2015 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package expose
    18  
    19  import (
    20  	"net/http"
    21  	"strings"
    22  	"testing"
    23  
    24  	"github.com/stretchr/testify/require"
    25  	corev1 "k8s.io/api/core/v1"
    26  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/runtime/schema"
    30  	"k8s.io/apimachinery/pkg/util/intstr"
    31  	"k8s.io/cli-runtime/pkg/genericiooptions"
    32  	"k8s.io/client-go/rest/fake"
    33  	cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
    34  	"k8s.io/kubectl/pkg/scheme"
    35  )
    36  
    37  func TestRunExposeService(t *testing.T) {
    38  	tests := []struct {
    39  		name     string
    40  		args     []string
    41  		ns       string
    42  		calls    map[string]string
    43  		input    runtime.Object
    44  		flags    map[string]string
    45  		output   runtime.Object
    46  		expected string
    47  		status   int
    48  	}{
    49  		{
    50  			name: "expose-service-from-service-no-selector-defined",
    51  			args: []string{"service", "baz"},
    52  			ns:   "test",
    53  			calls: map[string]string{
    54  				"GET":  "/namespaces/test/services/baz",
    55  				"POST": "/namespaces/test/services",
    56  			},
    57  			input: &corev1.Service{
    58  				ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
    59  				Spec: corev1.ServiceSpec{
    60  					Selector: map[string]string{"app": "go"},
    61  				},
    62  			},
    63  			flags: map[string]string{"protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test"},
    64  			output: &corev1.Service{
    65  				ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
    66  				Spec: corev1.ServiceSpec{
    67  					Ports: []corev1.ServicePort{
    68  						{
    69  							Protocol:   corev1.ProtocolUDP,
    70  							Port:       14,
    71  							TargetPort: intstr.FromInt32(14),
    72  						},
    73  					},
    74  					Selector: map[string]string{"app": "go"},
    75  				},
    76  			},
    77  			expected: "service/foo exposed",
    78  			status:   200,
    79  		},
    80  		{
    81  			name: "expose-service-from-service",
    82  			args: []string{"service", "baz"},
    83  			ns:   "test",
    84  			calls: map[string]string{
    85  				"GET":  "/namespaces/test/services/baz",
    86  				"POST": "/namespaces/test/services",
    87  			},
    88  			input: &corev1.Service{
    89  				ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
    90  				Spec: corev1.ServiceSpec{
    91  					Selector: map[string]string{"app": "go"},
    92  				},
    93  			},
    94  			flags: map[string]string{"selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test"},
    95  			output: &corev1.Service{
    96  				ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
    97  				Spec: corev1.ServiceSpec{
    98  					Ports: []corev1.ServicePort{
    99  						{
   100  							Protocol:   corev1.ProtocolUDP,
   101  							Port:       14,
   102  							TargetPort: intstr.FromInt32(14),
   103  						},
   104  					},
   105  					Selector: map[string]string{"func": "stream"},
   106  				},
   107  			},
   108  			expected: "service/foo exposed",
   109  			status:   200,
   110  		},
   111  		{
   112  			name: "no-name-passed-from-the-cli",
   113  			args: []string{"service", "mayor"},
   114  			ns:   "default",
   115  			calls: map[string]string{
   116  				"GET":  "/namespaces/default/services/mayor",
   117  				"POST": "/namespaces/default/services",
   118  			},
   119  			input: &corev1.Service{
   120  				ObjectMeta: metav1.ObjectMeta{Name: "mayor", Namespace: "default", ResourceVersion: "12"},
   121  				Spec: corev1.ServiceSpec{
   122  					Selector: map[string]string{"run": "this"},
   123  				},
   124  			},
   125  			// No --name flag specified below. Service will use the rc's name passed via the 'default-name' parameter
   126  			flags: map[string]string{"selector": "run=this", "port": "80", "labels": "runas=amayor"},
   127  			output: &corev1.Service{
   128  				ObjectMeta: metav1.ObjectMeta{Name: "mayor", Namespace: "", Labels: map[string]string{"runas": "amayor"}},
   129  				Spec: corev1.ServiceSpec{
   130  					Ports: []corev1.ServicePort{
   131  						{
   132  							Protocol:   corev1.ProtocolTCP,
   133  							Port:       80,
   134  							TargetPort: intstr.FromInt32(80),
   135  						},
   136  					},
   137  					Selector: map[string]string{"run": "this"},
   138  				},
   139  			},
   140  			expected: "service/mayor exposed",
   141  			status:   200,
   142  		},
   143  		{
   144  			name: "expose-service",
   145  			args: []string{"service", "baz"},
   146  			ns:   "test",
   147  			calls: map[string]string{
   148  				"GET":  "/namespaces/test/services/baz",
   149  				"POST": "/namespaces/test/services",
   150  			},
   151  			input: &corev1.Service{
   152  				ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
   153  				Spec: corev1.ServiceSpec{
   154  					Selector: map[string]string{"app": "go"},
   155  				},
   156  			},
   157  			flags: map[string]string{"selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test", "type": "LoadBalancer", "dry-run": "client"},
   158  			output: &corev1.Service{
   159  				ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
   160  				Spec: corev1.ServiceSpec{
   161  					Ports: []corev1.ServicePort{
   162  						{
   163  							Protocol:   corev1.ProtocolUDP,
   164  							Port:       14,
   165  							TargetPort: intstr.FromInt32(14),
   166  						},
   167  					},
   168  					Selector: map[string]string{"func": "stream"},
   169  					Type:     corev1.ServiceTypeLoadBalancer,
   170  				},
   171  			},
   172  			expected: "service/foo exposed (dry run)",
   173  			status:   200,
   174  		},
   175  		{
   176  			name: "expose-affinity-service",
   177  			args: []string{"service", "baz"},
   178  			ns:   "test",
   179  			calls: map[string]string{
   180  				"GET":  "/namespaces/test/services/baz",
   181  				"POST": "/namespaces/test/services",
   182  			},
   183  			input: &corev1.Service{
   184  				ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
   185  				Spec: corev1.ServiceSpec{
   186  					Selector: map[string]string{"app": "go"},
   187  				},
   188  			},
   189  			flags: map[string]string{"selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test", "type": "LoadBalancer", "session-affinity": "ClientIP", "dry-run": "client"},
   190  			output: &corev1.Service{
   191  				ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
   192  				Spec: corev1.ServiceSpec{
   193  					Ports: []corev1.ServicePort{
   194  						{
   195  							Protocol:   corev1.ProtocolUDP,
   196  							Port:       14,
   197  							TargetPort: intstr.FromInt32(14),
   198  						},
   199  					},
   200  					Selector:        map[string]string{"func": "stream"},
   201  					Type:            corev1.ServiceTypeLoadBalancer,
   202  					SessionAffinity: corev1.ServiceAffinityClientIP,
   203  				},
   204  			},
   205  			expected: "service/foo exposed (dry run)",
   206  			status:   200,
   207  		},
   208  		{
   209  			name: "expose-service-cluster-ip",
   210  			args: []string{"service", "baz"},
   211  			ns:   "test",
   212  			calls: map[string]string{
   213  				"GET":  "/namespaces/test/services/baz",
   214  				"POST": "/namespaces/test/services",
   215  			},
   216  			input: &corev1.Service{
   217  				ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
   218  				Spec: corev1.ServiceSpec{
   219  					Selector: map[string]string{"app": "go"},
   220  				},
   221  			},
   222  			flags: map[string]string{"selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test", "cluster-ip": "10.10.10.10", "dry-run": "client"},
   223  			output: &corev1.Service{
   224  				ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
   225  				Spec: corev1.ServiceSpec{
   226  					Ports: []corev1.ServicePort{
   227  						{
   228  							Protocol:   corev1.ProtocolUDP,
   229  							Port:       14,
   230  							TargetPort: intstr.FromInt32(14),
   231  						},
   232  					},
   233  					Selector:  map[string]string{"func": "stream"},
   234  					ClusterIP: "10.10.10.10",
   235  				},
   236  			},
   237  			expected: "service/foo exposed (dry run)",
   238  			status:   200,
   239  		},
   240  		{
   241  			name: "expose-headless-service",
   242  			args: []string{"service", "baz"},
   243  			ns:   "test",
   244  			calls: map[string]string{
   245  				"GET":  "/namespaces/test/services/baz",
   246  				"POST": "/namespaces/test/services",
   247  			},
   248  			input: &corev1.Service{
   249  				ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
   250  				Spec: corev1.ServiceSpec{
   251  					Selector: map[string]string{"app": "go"},
   252  				},
   253  			},
   254  			flags: map[string]string{"selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test", "cluster-ip": "None", "dry-run": "client"},
   255  			output: &corev1.Service{
   256  				ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
   257  				Spec: corev1.ServiceSpec{
   258  					Ports: []corev1.ServicePort{
   259  						{
   260  							Protocol:   corev1.ProtocolUDP,
   261  							Port:       14,
   262  							TargetPort: intstr.FromInt32(14),
   263  						},
   264  					},
   265  					Selector:  map[string]string{"func": "stream"},
   266  					ClusterIP: corev1.ClusterIPNone,
   267  				},
   268  			},
   269  			expected: "service/foo exposed (dry run)",
   270  			status:   200,
   271  		},
   272  		{
   273  			name: "expose-headless-service-no-port",
   274  			args: []string{"service", "baz"},
   275  			ns:   "test",
   276  			calls: map[string]string{
   277  				"GET":  "/namespaces/test/services/baz",
   278  				"POST": "/namespaces/test/services",
   279  			},
   280  			input: &corev1.Service{
   281  				ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
   282  				Spec: corev1.ServiceSpec{
   283  					Selector: map[string]string{"app": "go"},
   284  				},
   285  			},
   286  			flags: map[string]string{"selector": "func=stream", "name": "foo", "labels": "svc=test", "cluster-ip": "None", "dry-run": "client"},
   287  			output: &corev1.Service{
   288  				ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
   289  				Spec: corev1.ServiceSpec{
   290  					Ports:     []corev1.ServicePort{},
   291  					Selector:  map[string]string{"func": "stream"},
   292  					ClusterIP: corev1.ClusterIPNone,
   293  				},
   294  			},
   295  			expected: "service/foo exposed (dry run)",
   296  			status:   200,
   297  		},
   298  		{
   299  			name: "expose-from-file",
   300  			args: []string{},
   301  			ns:   "test",
   302  			calls: map[string]string{
   303  				"GET":  "/namespaces/test/services/redis-master",
   304  				"POST": "/namespaces/test/services",
   305  			},
   306  			input: &corev1.Service{
   307  				ObjectMeta: metav1.ObjectMeta{Name: "redis-master", Namespace: "test", ResourceVersion: "12"},
   308  				Spec: corev1.ServiceSpec{
   309  					Selector: map[string]string{"app": "go"},
   310  				},
   311  			},
   312  			flags: map[string]string{"filename": "../../../testdata/redis-master-service.yaml", "selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test", "dry-run": "client"},
   313  			output: &corev1.Service{
   314  				ObjectMeta: metav1.ObjectMeta{Name: "foo", Labels: map[string]string{"svc": "test"}},
   315  				Spec: corev1.ServiceSpec{
   316  					Ports: []corev1.ServicePort{
   317  						{
   318  							Protocol:   corev1.ProtocolUDP,
   319  							Port:       14,
   320  							TargetPort: intstr.FromInt32(14),
   321  						},
   322  					},
   323  					Selector: map[string]string{"func": "stream"},
   324  				},
   325  			},
   326  			expected: "service/foo exposed (dry run)",
   327  			status:   200,
   328  		},
   329  		{
   330  			name: "truncate-name",
   331  			args: []string{"pod", "a-name-that-is-toooo-big-for-a-service-because-it-can-only-handle-63-characters"},
   332  			ns:   "test",
   333  			calls: map[string]string{
   334  				"GET":  "/namespaces/test/pods/a-name-that-is-toooo-big-for-a-service-because-it-can-only-handle-63-characters",
   335  				"POST": "/namespaces/test/services",
   336  			},
   337  			input: &corev1.Pod{
   338  				ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
   339  			},
   340  			flags: map[string]string{"selector": "svc=frompod", "port": "90", "labels": "svc=frompod", "generator": "service/v2"},
   341  			output: &corev1.Service{
   342  				ObjectMeta: metav1.ObjectMeta{Name: "a-name-that-is-toooo-big-for-a-service-because-it-can-only-handle-63-characters"[:63], Namespace: "", Labels: map[string]string{"svc": "frompod"}},
   343  				Spec: corev1.ServiceSpec{
   344  					Ports: []corev1.ServicePort{
   345  						{
   346  							Protocol:   corev1.ProtocolTCP,
   347  							Port:       90,
   348  							TargetPort: intstr.FromInt32(90),
   349  						},
   350  					},
   351  					Selector: map[string]string{"svc": "frompod"},
   352  				},
   353  			},
   354  			expected: "service/a-name-that-is-toooo-big-for-a-service-because-it-can-only-hand exposed",
   355  			status:   200,
   356  		},
   357  		{
   358  			name: "expose-multiport-object",
   359  			args: []string{"service", "foo"},
   360  			ns:   "test",
   361  			calls: map[string]string{
   362  				"GET":  "/namespaces/test/services/foo",
   363  				"POST": "/namespaces/test/services",
   364  			},
   365  			input: &corev1.Service{
   366  				ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "multiport"}},
   367  				Spec: corev1.ServiceSpec{
   368  					Ports: []corev1.ServicePort{
   369  						{
   370  							Protocol:   corev1.ProtocolTCP,
   371  							Port:       80,
   372  							TargetPort: intstr.FromInt32(80),
   373  						},
   374  						{
   375  							Protocol:   corev1.ProtocolTCP,
   376  							Port:       443,
   377  							TargetPort: intstr.FromInt32(443),
   378  						},
   379  					},
   380  				},
   381  			},
   382  			flags: map[string]string{"selector": "svc=fromfoo", "generator": "service/v2", "name": "fromfoo", "dry-run": "client"},
   383  			output: &corev1.Service{
   384  				ObjectMeta: metav1.ObjectMeta{Name: "fromfoo", Namespace: "", Labels: map[string]string{"svc": "multiport"}},
   385  				Spec: corev1.ServiceSpec{
   386  					Ports: []corev1.ServicePort{
   387  						{
   388  							Name:       "port-1",
   389  							Protocol:   corev1.ProtocolTCP,
   390  							Port:       80,
   391  							TargetPort: intstr.FromInt32(80),
   392  						},
   393  						{
   394  							Name:       "port-2",
   395  							Protocol:   corev1.ProtocolTCP,
   396  							Port:       443,
   397  							TargetPort: intstr.FromInt32(443),
   398  						},
   399  					},
   400  					Selector: map[string]string{"svc": "fromfoo"},
   401  				},
   402  			},
   403  			expected: "service/fromfoo exposed (dry run)",
   404  			status:   200,
   405  		},
   406  		{
   407  			name: "expose-multiprotocol-object",
   408  			args: []string{"service", "foo"},
   409  			ns:   "test",
   410  			calls: map[string]string{
   411  				"GET":  "/namespaces/test/services/foo",
   412  				"POST": "/namespaces/test/services",
   413  			},
   414  			input: &corev1.Service{
   415  				ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "multiport"}},
   416  				Spec: corev1.ServiceSpec{
   417  					Ports: []corev1.ServicePort{
   418  						{
   419  							Protocol:   corev1.ProtocolTCP,
   420  							Port:       80,
   421  							TargetPort: intstr.FromInt32(80),
   422  						},
   423  						{
   424  							Protocol:   corev1.ProtocolUDP,
   425  							Port:       8080,
   426  							TargetPort: intstr.FromInt32(8080),
   427  						},
   428  						{
   429  							Protocol:   corev1.ProtocolUDP,
   430  							Port:       8081,
   431  							TargetPort: intstr.FromInt32(8081),
   432  						},
   433  						{
   434  							Protocol:   corev1.ProtocolSCTP,
   435  							Port:       8082,
   436  							TargetPort: intstr.FromInt32(8082),
   437  						},
   438  					},
   439  				},
   440  			},
   441  			flags: map[string]string{"selector": "svc=fromfoo", "generator": "service/v2", "name": "fromfoo", "dry-run": "client"},
   442  			output: &corev1.Service{
   443  				ObjectMeta: metav1.ObjectMeta{Name: "fromfoo", Namespace: "", Labels: map[string]string{"svc": "multiport"}},
   444  				Spec: corev1.ServiceSpec{
   445  					Ports: []corev1.ServicePort{
   446  						{
   447  							Name:       "port-1",
   448  							Protocol:   corev1.ProtocolTCP,
   449  							Port:       80,
   450  							TargetPort: intstr.FromInt32(80),
   451  						},
   452  						{
   453  							Name:       "port-2",
   454  							Protocol:   corev1.ProtocolUDP,
   455  							Port:       8080,
   456  							TargetPort: intstr.FromInt32(8080),
   457  						},
   458  						{
   459  							Name:       "port-3",
   460  							Protocol:   corev1.ProtocolUDP,
   461  							Port:       8081,
   462  							TargetPort: intstr.FromInt32(8081),
   463  						},
   464  						{
   465  							Name:       "port-4",
   466  							Protocol:   corev1.ProtocolSCTP,
   467  							Port:       8082,
   468  							TargetPort: intstr.FromInt32(8082),
   469  						},
   470  					},
   471  					Selector: map[string]string{"svc": "fromfoo"},
   472  				},
   473  			},
   474  			expected: "service/fromfoo exposed (dry run)",
   475  			status:   200,
   476  		},
   477  		{
   478  			name: "expose-service-from-service-no-selector-defined-sctp",
   479  			args: []string{"service", "baz"},
   480  			ns:   "test",
   481  			calls: map[string]string{
   482  				"GET":  "/namespaces/test/services/baz",
   483  				"POST": "/namespaces/test/services",
   484  			},
   485  			input: &corev1.Service{
   486  				ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
   487  				Spec: corev1.ServiceSpec{
   488  					Selector: map[string]string{"app": "go"},
   489  				},
   490  			},
   491  			flags: map[string]string{"protocol": "SCTP", "port": "14", "name": "foo", "labels": "svc=test"},
   492  			output: &corev1.Service{
   493  				ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
   494  				Spec: corev1.ServiceSpec{
   495  					Ports: []corev1.ServicePort{
   496  						{
   497  							Protocol:   corev1.ProtocolSCTP,
   498  							Port:       14,
   499  							TargetPort: intstr.FromInt32(14),
   500  						},
   501  					},
   502  					Selector: map[string]string{"app": "go"},
   503  				},
   504  			},
   505  			expected: "service/foo exposed",
   506  			status:   200,
   507  		},
   508  		{
   509  			name: "expose-service-from-service-sctp",
   510  			args: []string{"service", "baz"},
   511  			ns:   "test",
   512  			calls: map[string]string{
   513  				"GET":  "/namespaces/test/services/baz",
   514  				"POST": "/namespaces/test/services",
   515  			},
   516  			input: &corev1.Service{
   517  				ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
   518  				Spec: corev1.ServiceSpec{
   519  					Selector: map[string]string{"app": "go"},
   520  				},
   521  			},
   522  			flags: map[string]string{"selector": "func=stream", "protocol": "SCTP", "port": "14", "name": "foo", "labels": "svc=test"},
   523  			output: &corev1.Service{
   524  				ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
   525  				Spec: corev1.ServiceSpec{
   526  					Ports: []corev1.ServicePort{
   527  						{
   528  							Protocol:   corev1.ProtocolSCTP,
   529  							Port:       14,
   530  							TargetPort: intstr.FromInt32(14),
   531  						},
   532  					},
   533  					Selector: map[string]string{"func": "stream"},
   534  				},
   535  			},
   536  			expected: "service/foo exposed",
   537  			status:   200,
   538  		},
   539  		{
   540  			name: "expose-service-cluster-ip-sctp",
   541  			args: []string{"service", "baz"},
   542  			ns:   "test",
   543  			calls: map[string]string{
   544  				"GET":  "/namespaces/test/services/baz",
   545  				"POST": "/namespaces/test/services",
   546  			},
   547  			input: &corev1.Service{
   548  				ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
   549  				Spec: corev1.ServiceSpec{
   550  					Selector: map[string]string{"app": "go"},
   551  				},
   552  			},
   553  			flags: map[string]string{"selector": "func=stream", "protocol": "SCTP", "port": "14", "name": "foo", "labels": "svc=test", "cluster-ip": "10.10.10.10", "dry-run": "client"},
   554  			output: &corev1.Service{
   555  				ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
   556  				Spec: corev1.ServiceSpec{
   557  					Ports: []corev1.ServicePort{
   558  						{
   559  							Protocol:   corev1.ProtocolSCTP,
   560  							Port:       14,
   561  							TargetPort: intstr.FromInt32(14),
   562  						},
   563  					},
   564  					Selector:  map[string]string{"func": "stream"},
   565  					ClusterIP: "10.10.10.10",
   566  				},
   567  			},
   568  			expected: "service/foo exposed (dry run)",
   569  			status:   200,
   570  		},
   571  		{
   572  			name: "expose-headless-service-sctp",
   573  			args: []string{"service", "baz"},
   574  			ns:   "test",
   575  			calls: map[string]string{
   576  				"GET":  "/namespaces/test/services/baz",
   577  				"POST": "/namespaces/test/services",
   578  			},
   579  			input: &corev1.Service{
   580  				ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
   581  				Spec: corev1.ServiceSpec{
   582  					Selector: map[string]string{"app": "go"},
   583  				},
   584  			},
   585  			flags: map[string]string{"selector": "func=stream", "protocol": "SCTP", "port": "14", "name": "foo", "labels": "svc=test", "cluster-ip": "None", "dry-run": "client"},
   586  			output: &corev1.Service{
   587  				ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
   588  				Spec: corev1.ServiceSpec{
   589  					Ports: []corev1.ServicePort{
   590  						{
   591  							Protocol:   corev1.ProtocolSCTP,
   592  							Port:       14,
   593  							TargetPort: intstr.FromInt32(14),
   594  						},
   595  					},
   596  					Selector:  map[string]string{"func": "stream"},
   597  					ClusterIP: corev1.ClusterIPNone,
   598  				},
   599  			},
   600  			expected: "service/foo exposed (dry run)",
   601  			status:   200,
   602  		},
   603  		{
   604  			name: "namespace-yaml",
   605  			args: []string{"service", "baz"},
   606  			ns:   "testns",
   607  			calls: map[string]string{
   608  				"GET":  "/namespaces/testns/services/baz",
   609  				"POST": "/namespaces/testns/services",
   610  			},
   611  			input: &corev1.Service{
   612  				ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "testns", ResourceVersion: "12"},
   613  				Spec: corev1.ServiceSpec{
   614  					Selector: map[string]string{"app": "go"},
   615  				},
   616  			},
   617  			flags: map[string]string{"selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test", "type": "LoadBalancer", "dry-run": "client", "output": "yaml"},
   618  			output: &corev1.Service{
   619  				ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
   620  				Spec: corev1.ServiceSpec{
   621  					Ports: []corev1.ServicePort{
   622  						{
   623  							Protocol:   corev1.ProtocolUDP,
   624  							Port:       14,
   625  							TargetPort: intstr.FromInt32(14),
   626  						},
   627  					},
   628  					Selector: map[string]string{"func": "stream"},
   629  					Type:     corev1.ServiceTypeLoadBalancer,
   630  				},
   631  			},
   632  			expected: "namespace: testns",
   633  			status:   200,
   634  		},
   635  	}
   636  
   637  	for _, test := range tests {
   638  		t.Run(test.name, func(t *testing.T) {
   639  			tf := cmdtesting.NewTestFactory().WithNamespace(test.ns)
   640  			defer tf.Cleanup()
   641  
   642  			codec := runtime.NewCodec(scheme.DefaultJSONEncoder(), scheme.Codecs.UniversalDecoder(scheme.Scheme.PrioritizedVersionsAllGroups()...))
   643  			ns := scheme.Codecs.WithoutConversion()
   644  
   645  			tf.Client = &fake.RESTClient{
   646  				GroupVersion:         schema.GroupVersion{Version: "v1"},
   647  				NegotiatedSerializer: ns,
   648  				Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   649  					switch p, m := req.URL.Path, req.Method; {
   650  					case p == test.calls[m] && m == "GET":
   651  						return &http.Response{StatusCode: test.status, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, test.input)}, nil
   652  					case p == test.calls[m] && m == "POST":
   653  						return &http.Response{StatusCode: test.status, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, test.output)}, nil
   654  					default:
   655  						t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   656  						return nil, nil
   657  					}
   658  				}),
   659  			}
   660  
   661  			ioStreams, _, buf, _ := genericiooptions.NewTestIOStreams()
   662  			cmd := NewCmdExposeService(tf, ioStreams)
   663  			cmd.SetOut(buf)
   664  			cmd.SetErr(buf)
   665  			for flag, value := range test.flags {
   666  				cmd.Flags().Set(flag, value)
   667  			}
   668  			cmd.Run(cmd, test.args)
   669  
   670  			out := buf.String()
   671  
   672  			if test.expected == "" {
   673  				t.Errorf("%s: Invalid test case. Specify expected result.\n", test.name)
   674  			}
   675  
   676  			if !strings.Contains(out, test.expected) {
   677  				t.Errorf("%s: Unexpected output! Expected\n%s\ngot\n%s", test.name, test.expected, out)
   678  			}
   679  		})
   680  	}
   681  }
   682  
   683  func TestExposeOverride(t *testing.T) {
   684  	tests := []struct {
   685  		name         string
   686  		overrides    string
   687  		overrideType string
   688  		expected     string
   689  	}{
   690  		{
   691  			name:         "expose with merge override type should replace the entire spec",
   692  			overrides:    `{"spec": {"ports": [{"protocol": "TCP", "port": 1111, "targetPort": 2222}]}, "selector": {"app": "go"}}`,
   693  			overrideType: "merge",
   694  			expected: `apiVersion: v1
   695  kind: Service
   696  metadata:
   697    creationTimestamp: null
   698    labels:
   699      svc: test
   700    name: foo
   701    namespace: test
   702  spec:
   703    ports:
   704    - port: 1111
   705      protocol: TCP
   706      targetPort: 2222
   707    selector:
   708      app: go
   709  status:
   710    loadBalancer: {}
   711  `,
   712  		},
   713  		{
   714  			name:         "expose with strategic override type should add port before existing port",
   715  			overrides:    `{"spec": {"ports": [{"protocol": "TCP", "port": 1111, "targetPort": 2222}]}}`,
   716  			overrideType: "strategic",
   717  			expected: `apiVersion: v1
   718  kind: Service
   719  metadata:
   720    creationTimestamp: null
   721    labels:
   722      svc: test
   723    name: foo
   724    namespace: test
   725  spec:
   726    ports:
   727    - port: 1111
   728      protocol: TCP
   729      targetPort: 2222
   730    - port: 14
   731      protocol: UDP
   732      targetPort: 14
   733    selector:
   734      app: go
   735  status:
   736    loadBalancer: {}
   737  `,
   738  		},
   739  		{
   740  			name: "expose with json override type should add port before existing port",
   741  			overrides: `[
   742  				{"op": "add", "path": "/spec/ports/0", "value": {"port": 1111, "protocol": "TCP", "targetPort": 2222}}
   743  			]`,
   744  			overrideType: "json",
   745  			expected: `apiVersion: v1
   746  kind: Service
   747  metadata:
   748    creationTimestamp: null
   749    labels:
   750      svc: test
   751    name: foo
   752    namespace: test
   753  spec:
   754    ports:
   755    - port: 1111
   756      protocol: TCP
   757      targetPort: 2222
   758    - port: 14
   759      protocol: UDP
   760      targetPort: 14
   761    selector:
   762      app: go
   763  status:
   764    loadBalancer: {}
   765  `,
   766  		},
   767  		{
   768  			name: "expose with json override type should add port after existing port",
   769  			overrides: `[
   770  				{"op": "add", "path": "/spec/ports/1", "value": {"port": 1111, "protocol": "TCP", "targetPort": 2222}}
   771  			]`,
   772  			overrideType: "json",
   773  			expected: `apiVersion: v1
   774  kind: Service
   775  metadata:
   776    creationTimestamp: null
   777    labels:
   778      svc: test
   779    name: foo
   780    namespace: test
   781  spec:
   782    ports:
   783    - port: 14
   784      protocol: UDP
   785      targetPort: 14
   786    - port: 1111
   787      protocol: TCP
   788      targetPort: 2222
   789    selector:
   790      app: go
   791  status:
   792    loadBalancer: {}
   793  `,
   794  		},
   795  	}
   796  	for _, test := range tests {
   797  		t.Run(test.name, func(t *testing.T) {
   798  			tf := cmdtesting.NewTestFactory().WithNamespace("test")
   799  			defer tf.Cleanup()
   800  
   801  			codec := runtime.NewCodec(scheme.DefaultJSONEncoder(), scheme.Codecs.UniversalDecoder(scheme.Scheme.PrioritizedVersionsAllGroups()...))
   802  			ns := scheme.Codecs.WithoutConversion()
   803  
   804  			tf.Client = &fake.RESTClient{
   805  				GroupVersion:         schema.GroupVersion{Version: "v1"},
   806  				NegotiatedSerializer: ns,
   807  				Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   808  					switch p, m := req.URL.Path, req.Method; {
   809  					case p == "/namespaces/test/services/baz" && m == "GET":
   810  						return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &corev1.Service{
   811  							ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
   812  							Spec: corev1.ServiceSpec{
   813  								Selector: map[string]string{"app": "go"},
   814  							},
   815  						})}, nil
   816  					case p == "/namespaces/test/services" && m == "POST":
   817  						return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &corev1.Service{
   818  							ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
   819  							Spec: corev1.ServiceSpec{
   820  								Ports: []corev1.ServicePort{
   821  									{
   822  										Protocol:   corev1.ProtocolUDP,
   823  										Port:       14,
   824  										TargetPort: intstr.FromInt32(14),
   825  									},
   826  								},
   827  								Selector: map[string]string{"app": "go"},
   828  							},
   829  						})}, nil
   830  					default:
   831  						t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   832  						return nil, nil
   833  					}
   834  				}),
   835  			}
   836  
   837  			ioStreams, _, buf, _ := genericiooptions.NewTestIOStreams()
   838  			cmd := NewCmdExposeService(tf, ioStreams)
   839  			cmd.SetOut(buf)
   840  			cmd.Flags().Set("protocol", "UDP")
   841  			cmd.Flags().Set("port", "14")
   842  			cmd.Flags().Set("name", "foo")
   843  			cmd.Flags().Set("labels", "svc=test")
   844  			cmd.Flags().Set("dry-run", "client")
   845  			cmd.Flags().Set("overrides", test.overrides)
   846  			cmd.Flags().Set("override-type", test.overrideType)
   847  			cmd.Flags().Set("output", "yaml")
   848  			cmd.Run(cmd, []string{"service", "baz"})
   849  
   850  			out := buf.String()
   851  
   852  			if test.expected == "" {
   853  				t.Errorf("%s: Invalid test case. Specify expected result.\n", test.name)
   854  			}
   855  
   856  			if !strings.Contains(out, test.expected) {
   857  				t.Errorf("%s: Unexpected output! Expected\n%s\ngot\n%s", test.name, test.expected, out)
   858  			}
   859  		})
   860  	}
   861  }
   862  
   863  func TestGenerateService(t *testing.T) {
   864  	tests := map[string]struct {
   865  		selector        string
   866  		name            string
   867  		port            string
   868  		protocol        string
   869  		protocols       string
   870  		targetPort      string
   871  		clusterIP       string
   872  		labels          string
   873  		externalIP      string
   874  		serviceType     string
   875  		sessionAffinity string
   876  		setup           func(t *testing.T, exposeServiceOptions *ExposeServiceOptions) func()
   877  
   878  		expected  *corev1.Service
   879  		expectErr string
   880  	}{
   881  		"test1": {
   882  			selector:   "foo=bar,baz=blah",
   883  			name:       "test",
   884  			port:       "80",
   885  			protocol:   "TCP",
   886  			targetPort: "1234",
   887  			expected: &corev1.Service{
   888  				ObjectMeta: metav1.ObjectMeta{
   889  					Name: "test",
   890  				},
   891  				Spec: corev1.ServiceSpec{
   892  					Selector: map[string]string{
   893  						"foo": "bar",
   894  						"baz": "blah",
   895  					},
   896  					Ports: []corev1.ServicePort{
   897  						{
   898  							Port:       80,
   899  							Protocol:   "TCP",
   900  							TargetPort: intstr.FromInt32(1234),
   901  						},
   902  					},
   903  				},
   904  			},
   905  		},
   906  		"test2": {
   907  			selector:   "foo=bar,baz=blah",
   908  			name:       "test",
   909  			port:       "80",
   910  			protocol:   "UDP",
   911  			targetPort: "foobar",
   912  			expected: &corev1.Service{
   913  				ObjectMeta: metav1.ObjectMeta{
   914  					Name: "test",
   915  				},
   916  				Spec: corev1.ServiceSpec{
   917  					Selector: map[string]string{
   918  						"foo": "bar",
   919  						"baz": "blah",
   920  					},
   921  					Ports: []corev1.ServicePort{
   922  						{
   923  							Port:       80,
   924  							Protocol:   "UDP",
   925  							TargetPort: intstr.FromString("foobar"),
   926  						},
   927  					},
   928  				},
   929  			},
   930  		},
   931  		"test3": {
   932  			selector:   "foo=bar,baz=blah",
   933  			labels:     "key1=value1,key2=value2",
   934  			name:       "test",
   935  			port:       "80",
   936  			protocol:   "TCP",
   937  			targetPort: "1234",
   938  			expected: &corev1.Service{
   939  				ObjectMeta: metav1.ObjectMeta{
   940  					Name: "test",
   941  					Labels: map[string]string{
   942  						"key1": "value1",
   943  						"key2": "value2",
   944  					},
   945  				},
   946  				Spec: corev1.ServiceSpec{
   947  					Selector: map[string]string{
   948  						"foo": "bar",
   949  						"baz": "blah",
   950  					},
   951  					Ports: []corev1.ServicePort{
   952  						{
   953  							Port:       80,
   954  							Protocol:   "TCP",
   955  							TargetPort: intstr.FromInt32(1234),
   956  						},
   957  					},
   958  				},
   959  			},
   960  		},
   961  		"test4": {
   962  			selector:   "foo=bar,baz=blah",
   963  			name:       "test",
   964  			port:       "80",
   965  			protocol:   "UDP",
   966  			externalIP: "1.2.3.4",
   967  			targetPort: "foobar",
   968  			expected: &corev1.Service{
   969  				ObjectMeta: metav1.ObjectMeta{
   970  					Name: "test",
   971  				},
   972  				Spec: corev1.ServiceSpec{
   973  					Selector: map[string]string{
   974  						"foo": "bar",
   975  						"baz": "blah",
   976  					},
   977  					Ports: []corev1.ServicePort{
   978  						{
   979  							Port:       80,
   980  							Protocol:   "UDP",
   981  							TargetPort: intstr.FromString("foobar"),
   982  						},
   983  					},
   984  					ExternalIPs: []string{"1.2.3.4"},
   985  				},
   986  			},
   987  		},
   988  		"test5": {
   989  			selector:    "foo=bar,baz=blah",
   990  			name:        "test",
   991  			port:        "80",
   992  			protocol:    "UDP",
   993  			externalIP:  "1.2.3.4",
   994  			serviceType: "LoadBalancer",
   995  			targetPort:  "foobar",
   996  			expected: &corev1.Service{
   997  				ObjectMeta: metav1.ObjectMeta{
   998  					Name: "test",
   999  				},
  1000  				Spec: corev1.ServiceSpec{
  1001  					Selector: map[string]string{
  1002  						"foo": "bar",
  1003  						"baz": "blah",
  1004  					},
  1005  					Ports: []corev1.ServicePort{
  1006  						{
  1007  							Port:       80,
  1008  							Protocol:   "UDP",
  1009  							TargetPort: intstr.FromString("foobar"),
  1010  						},
  1011  					},
  1012  					Type:        corev1.ServiceTypeLoadBalancer,
  1013  					ExternalIPs: []string{"1.2.3.4"},
  1014  				},
  1015  			},
  1016  		},
  1017  		"test6": {
  1018  			selector:    "foo=bar,baz=blah",
  1019  			name:        "test",
  1020  			port:        "80",
  1021  			protocol:    "UDP",
  1022  			targetPort:  "foobar",
  1023  			serviceType: string(corev1.ServiceTypeNodePort),
  1024  			expected: &corev1.Service{
  1025  				ObjectMeta: metav1.ObjectMeta{
  1026  					Name: "test",
  1027  				},
  1028  				Spec: corev1.ServiceSpec{
  1029  					Selector: map[string]string{
  1030  						"foo": "bar",
  1031  						"baz": "blah",
  1032  					},
  1033  					Ports: []corev1.ServicePort{
  1034  						{
  1035  							Port:       80,
  1036  							Protocol:   "UDP",
  1037  							TargetPort: intstr.FromString("foobar"),
  1038  						},
  1039  					},
  1040  					Type: corev1.ServiceTypeNodePort,
  1041  				},
  1042  			},
  1043  		},
  1044  		"test7": {
  1045  			selector:    "foo=bar,baz=blah",
  1046  			name:        "test",
  1047  			port:        "80",
  1048  			protocol:    "UDP",
  1049  			targetPort:  "foobar",
  1050  			serviceType: string(corev1.ServiceTypeNodePort),
  1051  			expected: &corev1.Service{
  1052  				ObjectMeta: metav1.ObjectMeta{
  1053  					Name: "test",
  1054  				},
  1055  				Spec: corev1.ServiceSpec{
  1056  					Selector: map[string]string{
  1057  						"foo": "bar",
  1058  						"baz": "blah",
  1059  					},
  1060  					Ports: []corev1.ServicePort{
  1061  						{
  1062  							Port:       80,
  1063  							Protocol:   "UDP",
  1064  							TargetPort: intstr.FromString("foobar"),
  1065  						},
  1066  					},
  1067  					Type: corev1.ServiceTypeNodePort,
  1068  				},
  1069  			},
  1070  		},
  1071  		"test8": {
  1072  			selector:   "foo=bar,baz=blah",
  1073  			name:       "test",
  1074  			port:       "80",
  1075  			protocol:   "TCP",
  1076  			targetPort: "1234",
  1077  			expected: &corev1.Service{
  1078  				ObjectMeta: metav1.ObjectMeta{
  1079  					Name: "test",
  1080  				},
  1081  				Spec: corev1.ServiceSpec{
  1082  					Selector: map[string]string{
  1083  						"foo": "bar",
  1084  						"baz": "blah",
  1085  					},
  1086  					Ports: []corev1.ServicePort{
  1087  						{
  1088  							Port:       80,
  1089  							Protocol:   "TCP",
  1090  							TargetPort: intstr.FromInt32(1234),
  1091  						},
  1092  					},
  1093  				},
  1094  			},
  1095  		},
  1096  		"test9": {
  1097  			selector:        "foo=bar,baz=blah",
  1098  			name:            "test",
  1099  			port:            "80",
  1100  			protocol:        "TCP",
  1101  			sessionAffinity: "ClientIP",
  1102  			targetPort:      "1234",
  1103  			expected: &corev1.Service{
  1104  				ObjectMeta: metav1.ObjectMeta{
  1105  					Name: "test",
  1106  				},
  1107  				Spec: corev1.ServiceSpec{
  1108  					Selector: map[string]string{
  1109  						"foo": "bar",
  1110  						"baz": "blah",
  1111  					},
  1112  					Ports: []corev1.ServicePort{
  1113  						{
  1114  							Port:       80,
  1115  							Protocol:   "TCP",
  1116  							TargetPort: intstr.FromInt32(1234),
  1117  						},
  1118  					},
  1119  					SessionAffinity: corev1.ServiceAffinityClientIP,
  1120  				},
  1121  			},
  1122  		},
  1123  		"test10": {
  1124  			selector:   "foo=bar,baz=blah",
  1125  			name:       "test",
  1126  			port:       "80",
  1127  			protocol:   "TCP",
  1128  			clusterIP:  "10.10.10.10",
  1129  			targetPort: "1234",
  1130  			expected: &corev1.Service{
  1131  				ObjectMeta: metav1.ObjectMeta{
  1132  					Name: "test",
  1133  				},
  1134  				Spec: corev1.ServiceSpec{
  1135  					Selector: map[string]string{
  1136  						"foo": "bar",
  1137  						"baz": "blah",
  1138  					},
  1139  					Ports: []corev1.ServicePort{
  1140  						{
  1141  
  1142  							Port:       80,
  1143  							Protocol:   "TCP",
  1144  							TargetPort: intstr.FromInt32(1234),
  1145  						},
  1146  					},
  1147  					ClusterIP: "10.10.10.10",
  1148  				},
  1149  			},
  1150  		},
  1151  		"test11": {
  1152  			selector:   "foo=bar,baz=blah",
  1153  			name:       "test",
  1154  			port:       "80",
  1155  			protocol:   "TCP",
  1156  			clusterIP:  "None",
  1157  			targetPort: "1234",
  1158  			expected: &corev1.Service{
  1159  				ObjectMeta: metav1.ObjectMeta{
  1160  					Name: "test",
  1161  				},
  1162  				Spec: corev1.ServiceSpec{
  1163  					Selector: map[string]string{
  1164  						"foo": "bar",
  1165  						"baz": "blah",
  1166  					},
  1167  					Ports: []corev1.ServicePort{
  1168  						{
  1169  
  1170  							Port:       80,
  1171  							Protocol:   "TCP",
  1172  							TargetPort: intstr.FromInt32(1234),
  1173  						},
  1174  					},
  1175  					ClusterIP: corev1.ClusterIPNone,
  1176  				},
  1177  			},
  1178  		},
  1179  		"test12": {
  1180  			selector:   "foo=bar",
  1181  			name:       "test",
  1182  			port:       "80,443",
  1183  			protocol:   "TCP",
  1184  			targetPort: "foobar",
  1185  			expected: &corev1.Service{
  1186  				ObjectMeta: metav1.ObjectMeta{
  1187  					Name: "test",
  1188  				},
  1189  				Spec: corev1.ServiceSpec{
  1190  					Selector: map[string]string{
  1191  						"foo": "bar",
  1192  					},
  1193  					Ports: []corev1.ServicePort{
  1194  						{
  1195  							Name:       "port-1",
  1196  							Port:       80,
  1197  							Protocol:   corev1.ProtocolTCP,
  1198  							TargetPort: intstr.FromString("foobar"),
  1199  						},
  1200  						{
  1201  							Name:       "port-2",
  1202  							Port:       443,
  1203  							Protocol:   corev1.ProtocolTCP,
  1204  							TargetPort: intstr.FromString("foobar"),
  1205  						},
  1206  					},
  1207  				},
  1208  			},
  1209  		},
  1210  		"test13": {
  1211  			selector:   "foo=bar",
  1212  			name:       "test",
  1213  			port:       "80,443",
  1214  			protocol:   "UDP",
  1215  			targetPort: "1234",
  1216  			expected: &corev1.Service{
  1217  				ObjectMeta: metav1.ObjectMeta{
  1218  					Name: "test",
  1219  				},
  1220  				Spec: corev1.ServiceSpec{
  1221  					Selector: map[string]string{
  1222  						"foo": "bar",
  1223  					},
  1224  					Ports: []corev1.ServicePort{
  1225  						{
  1226  							Name:       "port-1",
  1227  							Port:       80,
  1228  							Protocol:   corev1.ProtocolUDP,
  1229  							TargetPort: intstr.FromInt32(1234),
  1230  						},
  1231  						{
  1232  							Name:       "port-2",
  1233  							Port:       443,
  1234  							Protocol:   corev1.ProtocolUDP,
  1235  							TargetPort: intstr.FromInt32(1234),
  1236  						},
  1237  					},
  1238  				},
  1239  			},
  1240  		},
  1241  		"test14": {
  1242  			selector: "foo=bar",
  1243  			name:     "test",
  1244  			port:     "80,443",
  1245  			protocol: "TCP",
  1246  			expected: &corev1.Service{
  1247  				ObjectMeta: metav1.ObjectMeta{
  1248  					Name: "test",
  1249  				},
  1250  				Spec: corev1.ServiceSpec{
  1251  					Selector: map[string]string{
  1252  						"foo": "bar",
  1253  					},
  1254  					Ports: []corev1.ServicePort{
  1255  						{
  1256  							Name:       "port-1",
  1257  							Port:       80,
  1258  							Protocol:   corev1.ProtocolTCP,
  1259  							TargetPort: intstr.FromInt32(80),
  1260  						},
  1261  						{
  1262  							Name:       "port-2",
  1263  							Port:       443,
  1264  							Protocol:   corev1.ProtocolTCP,
  1265  							TargetPort: intstr.FromInt32(443),
  1266  						},
  1267  					},
  1268  				},
  1269  			},
  1270  		},
  1271  		"test15": {
  1272  			selector:  "foo=bar",
  1273  			name:      "test",
  1274  			port:      "80,8080",
  1275  			protocols: "8080/UDP",
  1276  			expected: &corev1.Service{
  1277  				ObjectMeta: metav1.ObjectMeta{
  1278  					Name: "test",
  1279  				},
  1280  				Spec: corev1.ServiceSpec{
  1281  					Selector: map[string]string{
  1282  						"foo": "bar",
  1283  					},
  1284  					Ports: []corev1.ServicePort{
  1285  						{
  1286  							Name:       "port-1",
  1287  							Port:       80,
  1288  							Protocol:   corev1.ProtocolTCP,
  1289  							TargetPort: intstr.FromInt32(80),
  1290  						},
  1291  						{
  1292  							Name:       "port-2",
  1293  							Port:       8080,
  1294  							Protocol:   corev1.ProtocolUDP,
  1295  							TargetPort: intstr.FromInt32(8080),
  1296  						},
  1297  					},
  1298  				},
  1299  			},
  1300  		},
  1301  		"test16": {
  1302  			selector:  "foo=bar",
  1303  			name:      "test",
  1304  			port:      "80,8080,8081",
  1305  			protocols: "8080/UDP,8081/TCP",
  1306  			expected: &corev1.Service{
  1307  				ObjectMeta: metav1.ObjectMeta{
  1308  					Name: "test",
  1309  				},
  1310  				Spec: corev1.ServiceSpec{
  1311  					Selector: map[string]string{
  1312  						"foo": "bar",
  1313  					},
  1314  					Ports: []corev1.ServicePort{
  1315  						{
  1316  							Name:       "port-1",
  1317  							Port:       80,
  1318  							Protocol:   corev1.ProtocolTCP,
  1319  							TargetPort: intstr.FromInt32(80),
  1320  						},
  1321  						{
  1322  							Name:       "port-2",
  1323  							Port:       8080,
  1324  							Protocol:   corev1.ProtocolUDP,
  1325  							TargetPort: intstr.FromInt32(8080),
  1326  						},
  1327  						{
  1328  							Name:       "port-3",
  1329  							Port:       8081,
  1330  							Protocol:   corev1.ProtocolTCP,
  1331  							TargetPort: intstr.FromInt32(8081),
  1332  						},
  1333  					},
  1334  				},
  1335  			},
  1336  		},
  1337  		"test17": {
  1338  			selector:  "foo=bar,baz=blah",
  1339  			name:      "test",
  1340  			protocol:  "TCP",
  1341  			clusterIP: "None",
  1342  			expected: &corev1.Service{
  1343  				ObjectMeta: metav1.ObjectMeta{
  1344  					Name: "test",
  1345  				},
  1346  				Spec: corev1.ServiceSpec{
  1347  					Selector: map[string]string{
  1348  						"foo": "bar",
  1349  						"baz": "blah",
  1350  					},
  1351  					Ports:     []corev1.ServicePort{},
  1352  					ClusterIP: corev1.ClusterIPNone,
  1353  				},
  1354  			},
  1355  		},
  1356  		"test18": {
  1357  			selector:  "foo=bar",
  1358  			name:      "test",
  1359  			clusterIP: "None",
  1360  			expected: &corev1.Service{
  1361  				ObjectMeta: metav1.ObjectMeta{
  1362  					Name: "test",
  1363  				},
  1364  				Spec: corev1.ServiceSpec{
  1365  					Selector: map[string]string{
  1366  						"foo": "bar",
  1367  					},
  1368  					Ports:     []corev1.ServicePort{},
  1369  					ClusterIP: corev1.ClusterIPNone,
  1370  				},
  1371  			},
  1372  		},
  1373  		"test19": {
  1374  			selector:   "foo=bar,baz=blah",
  1375  			name:       "test",
  1376  			port:       "80",
  1377  			protocol:   "SCTP",
  1378  			targetPort: "1234",
  1379  			expected: &corev1.Service{
  1380  				ObjectMeta: metav1.ObjectMeta{
  1381  					Name: "test",
  1382  				},
  1383  				Spec: corev1.ServiceSpec{
  1384  					Selector: map[string]string{
  1385  						"foo": "bar",
  1386  						"baz": "blah",
  1387  					},
  1388  					Ports: []corev1.ServicePort{
  1389  						{
  1390  
  1391  							Port:       80,
  1392  							Protocol:   "SCTP",
  1393  							TargetPort: intstr.FromInt32(1234),
  1394  						},
  1395  					},
  1396  				},
  1397  			},
  1398  		},
  1399  		"test20": {
  1400  			selector:   "foo=bar,baz=blah",
  1401  			labels:     "key1=value1,key2=value2",
  1402  			name:       "test",
  1403  			port:       "80",
  1404  			protocol:   "SCTP",
  1405  			targetPort: "1234",
  1406  			expected: &corev1.Service{
  1407  				ObjectMeta: metav1.ObjectMeta{
  1408  					Name: "test",
  1409  					Labels: map[string]string{
  1410  						"key1": "value1",
  1411  						"key2": "value2",
  1412  					},
  1413  				},
  1414  				Spec: corev1.ServiceSpec{
  1415  					Selector: map[string]string{
  1416  						"foo": "bar",
  1417  						"baz": "blah",
  1418  					},
  1419  					Ports: []corev1.ServicePort{
  1420  						{
  1421  
  1422  							Port:       80,
  1423  							Protocol:   "SCTP",
  1424  							TargetPort: intstr.FromInt32(1234),
  1425  						},
  1426  					},
  1427  				},
  1428  			},
  1429  		},
  1430  		"test21": {
  1431  			selector:   "foo=bar,baz=blah",
  1432  			name:       "test",
  1433  			port:       "80",
  1434  			protocol:   "SCTP",
  1435  			targetPort: "1234",
  1436  			expected: &corev1.Service{
  1437  				ObjectMeta: metav1.ObjectMeta{
  1438  					Name: "test",
  1439  				},
  1440  				Spec: corev1.ServiceSpec{
  1441  					Selector: map[string]string{
  1442  						"foo": "bar",
  1443  						"baz": "blah",
  1444  					},
  1445  					Ports: []corev1.ServicePort{
  1446  						{
  1447  
  1448  							Port:       80,
  1449  							Protocol:   "SCTP",
  1450  							TargetPort: intstr.FromInt32(1234),
  1451  						},
  1452  					},
  1453  				},
  1454  			},
  1455  		},
  1456  		"test22": {
  1457  			selector:        "foo=bar,baz=blah",
  1458  			name:            "test",
  1459  			port:            "80",
  1460  			protocol:        "SCTP",
  1461  			sessionAffinity: "ClientIP",
  1462  			targetPort:      "1234",
  1463  			expected: &corev1.Service{
  1464  				ObjectMeta: metav1.ObjectMeta{
  1465  					Name: "test",
  1466  				},
  1467  				Spec: corev1.ServiceSpec{
  1468  					Selector: map[string]string{
  1469  						"foo": "bar",
  1470  						"baz": "blah",
  1471  					},
  1472  					Ports: []corev1.ServicePort{
  1473  						{
  1474  							Port:       80,
  1475  							Protocol:   "SCTP",
  1476  							TargetPort: intstr.FromInt32(1234),
  1477  						},
  1478  					},
  1479  					SessionAffinity: corev1.ServiceAffinityClientIP,
  1480  				},
  1481  			},
  1482  		},
  1483  		"test23": {
  1484  			selector:   "foo=bar,baz=blah",
  1485  			name:       "test",
  1486  			port:       "80",
  1487  			protocol:   "SCTP",
  1488  			clusterIP:  "10.10.10.10",
  1489  			targetPort: "1234",
  1490  			expected: &corev1.Service{
  1491  				ObjectMeta: metav1.ObjectMeta{
  1492  					Name: "test",
  1493  				},
  1494  				Spec: corev1.ServiceSpec{
  1495  					Selector: map[string]string{
  1496  						"foo": "bar",
  1497  						"baz": "blah",
  1498  					},
  1499  					Ports: []corev1.ServicePort{
  1500  						{
  1501  							Port:       80,
  1502  							Protocol:   "SCTP",
  1503  							TargetPort: intstr.FromInt32(1234),
  1504  						},
  1505  					},
  1506  					ClusterIP: "10.10.10.10",
  1507  				},
  1508  			},
  1509  		},
  1510  		"test24": {
  1511  			selector:   "foo=bar,baz=blah",
  1512  			name:       "test",
  1513  			port:       "80",
  1514  			protocol:   "SCTP",
  1515  			clusterIP:  "None",
  1516  			targetPort: "1234",
  1517  			expected: &corev1.Service{
  1518  				ObjectMeta: metav1.ObjectMeta{
  1519  					Name: "test",
  1520  				},
  1521  				Spec: corev1.ServiceSpec{
  1522  					Selector: map[string]string{
  1523  						"foo": "bar",
  1524  						"baz": "blah",
  1525  					},
  1526  					Ports: []corev1.ServicePort{
  1527  						{
  1528  
  1529  							Port:       80,
  1530  							Protocol:   "SCTP",
  1531  							TargetPort: intstr.FromInt32(1234),
  1532  						},
  1533  					},
  1534  					ClusterIP: corev1.ClusterIPNone,
  1535  				},
  1536  			},
  1537  		},
  1538  		"test25": {
  1539  			selector:   "foo=bar",
  1540  			name:       "test",
  1541  			port:       "80,443",
  1542  			protocol:   "SCTP",
  1543  			targetPort: "foobar",
  1544  			expected: &corev1.Service{
  1545  				ObjectMeta: metav1.ObjectMeta{
  1546  					Name: "test",
  1547  				},
  1548  				Spec: corev1.ServiceSpec{
  1549  					Selector: map[string]string{
  1550  						"foo": "bar",
  1551  					},
  1552  					Ports: []corev1.ServicePort{
  1553  						{
  1554  							Name:       "port-1",
  1555  							Port:       80,
  1556  							Protocol:   corev1.ProtocolSCTP,
  1557  							TargetPort: intstr.FromString("foobar"),
  1558  						},
  1559  						{
  1560  							Name:       "port-2",
  1561  							Port:       443,
  1562  							Protocol:   corev1.ProtocolSCTP,
  1563  							TargetPort: intstr.FromString("foobar"),
  1564  						},
  1565  					},
  1566  				},
  1567  			},
  1568  		},
  1569  		"test26": {
  1570  			selector: "foo=bar",
  1571  			name:     "test",
  1572  			port:     "80,443",
  1573  			protocol: "SCTP",
  1574  			expected: &corev1.Service{
  1575  				ObjectMeta: metav1.ObjectMeta{
  1576  					Name: "test",
  1577  				},
  1578  				Spec: corev1.ServiceSpec{
  1579  					Selector: map[string]string{
  1580  						"foo": "bar",
  1581  					},
  1582  					Ports: []corev1.ServicePort{
  1583  						{
  1584  							Name:       "port-1",
  1585  							Port:       80,
  1586  							Protocol:   corev1.ProtocolSCTP,
  1587  							TargetPort: intstr.FromInt32(80),
  1588  						},
  1589  						{
  1590  							Name:       "port-2",
  1591  							Port:       443,
  1592  							Protocol:   corev1.ProtocolSCTP,
  1593  							TargetPort: intstr.FromInt32(443),
  1594  						},
  1595  					},
  1596  				},
  1597  			},
  1598  		},
  1599  		"test27": {
  1600  			selector:  "foo=bar",
  1601  			name:      "test",
  1602  			port:      "80,8080",
  1603  			protocols: "8080/SCTP",
  1604  			expected: &corev1.Service{
  1605  				ObjectMeta: metav1.ObjectMeta{
  1606  					Name: "test",
  1607  				},
  1608  				Spec: corev1.ServiceSpec{
  1609  					Selector: map[string]string{
  1610  						"foo": "bar",
  1611  					},
  1612  					Ports: []corev1.ServicePort{
  1613  						{
  1614  							Name:       "port-1",
  1615  							Port:       80,
  1616  							Protocol:   corev1.ProtocolTCP,
  1617  							TargetPort: intstr.FromInt32(80),
  1618  						},
  1619  						{
  1620  							Name:       "port-2",
  1621  							Port:       8080,
  1622  							Protocol:   corev1.ProtocolSCTP,
  1623  							TargetPort: intstr.FromInt32(8080),
  1624  						},
  1625  					},
  1626  				},
  1627  			},
  1628  		},
  1629  		"test28": {
  1630  			selector:  "foo=bar",
  1631  			name:      "test",
  1632  			port:      "80,8080,8081,8082",
  1633  			protocols: "8080/UDP,8081/TCP,8082/SCTP",
  1634  			expected: &corev1.Service{
  1635  				ObjectMeta: metav1.ObjectMeta{
  1636  					Name: "test",
  1637  				},
  1638  				Spec: corev1.ServiceSpec{
  1639  					Selector: map[string]string{
  1640  						"foo": "bar",
  1641  					},
  1642  					Ports: []corev1.ServicePort{
  1643  						{
  1644  							Name:       "port-1",
  1645  							Port:       80,
  1646  							Protocol:   corev1.ProtocolTCP,
  1647  							TargetPort: intstr.FromInt32(80),
  1648  						},
  1649  						{
  1650  							Name:       "port-2",
  1651  							Port:       8080,
  1652  							Protocol:   corev1.ProtocolUDP,
  1653  							TargetPort: intstr.FromInt32(8080),
  1654  						},
  1655  						{
  1656  							Name:       "port-3",
  1657  							Port:       8081,
  1658  							Protocol:   corev1.ProtocolTCP,
  1659  							TargetPort: intstr.FromInt32(8081),
  1660  						},
  1661  						{
  1662  							Name:       "port-4",
  1663  							Port:       8082,
  1664  							Protocol:   corev1.ProtocolSCTP,
  1665  							TargetPort: intstr.FromInt32(8082),
  1666  						},
  1667  					},
  1668  				},
  1669  			},
  1670  		},
  1671  		"test 29": {
  1672  			selector:   "foo=bar,baz=blah",
  1673  			name:       "test",
  1674  			protocol:   "SCTP",
  1675  			targetPort: "1234",
  1676  			clusterIP:  "None",
  1677  			expected: &corev1.Service{
  1678  				ObjectMeta: metav1.ObjectMeta{
  1679  					Name: "test",
  1680  				},
  1681  				Spec: corev1.ServiceSpec{
  1682  					Selector: map[string]string{
  1683  						"foo": "bar",
  1684  						"baz": "blah",
  1685  					},
  1686  					Ports:     []corev1.ServicePort{},
  1687  					ClusterIP: corev1.ClusterIPNone,
  1688  				},
  1689  			},
  1690  		},
  1691  		// Fixed #114402 kubectl expose fails for apps with same-port, different-protocol
  1692  		"test #114402": {
  1693  			selector:  "foo=bar,baz=blah",
  1694  			name:      "test",
  1695  			clusterIP: "None",
  1696  			protocols: "53/TCP,53/UDP",
  1697  			port:      "53",
  1698  			expected: &corev1.Service{
  1699  				ObjectMeta: metav1.ObjectMeta{
  1700  					Name: "test",
  1701  				},
  1702  				Spec: corev1.ServiceSpec{
  1703  					Selector: map[string]string{
  1704  						"foo": "bar",
  1705  						"baz": "blah",
  1706  					},
  1707  					Ports: []corev1.ServicePort{
  1708  						{
  1709  							Name:       "port-1-tcp",
  1710  							Port:       53,
  1711  							Protocol:   corev1.ProtocolTCP,
  1712  							TargetPort: intstr.FromInt32(53),
  1713  						},
  1714  						{
  1715  							Name:       "port-1-udp",
  1716  							Port:       53,
  1717  							Protocol:   corev1.ProtocolUDP,
  1718  							TargetPort: intstr.FromInt32(53),
  1719  						},
  1720  					},
  1721  					ClusterIP: corev1.ClusterIPNone,
  1722  				},
  1723  			},
  1724  		},
  1725  		"check selector": {
  1726  			name:       "test",
  1727  			protocol:   "SCTP",
  1728  			targetPort: "1234",
  1729  			clusterIP:  "None",
  1730  			expectErr:  `selector must be specified`,
  1731  		},
  1732  		"check name": {
  1733  			selector:   "foo=bar,baz=blah",
  1734  			protocol:   "SCTP",
  1735  			targetPort: "1234",
  1736  			clusterIP:  "None",
  1737  			expectErr:  `name must be specified`,
  1738  		},
  1739  	}
  1740  	for name, test := range tests {
  1741  		t.Run(name, func(t *testing.T) {
  1742  			exposeServiceOptions := ExposeServiceOptions{
  1743  				Selector:        test.selector,
  1744  				Name:            test.name,
  1745  				Protocol:        test.protocol,
  1746  				Protocols:       test.protocols,
  1747  				Port:            test.port,
  1748  				ClusterIP:       test.clusterIP,
  1749  				TargetPort:      test.targetPort,
  1750  				Labels:          test.labels,
  1751  				ExternalIP:      test.externalIP,
  1752  				Type:            test.serviceType,
  1753  				SessionAffinity: test.sessionAffinity,
  1754  			}
  1755  
  1756  			service, err := exposeServiceOptions.createService()
  1757  			if test.expectErr == "" {
  1758  				require.NoError(t, err)
  1759  				if !apiequality.Semantic.DeepEqual(service, test.expected) {
  1760  					t.Errorf("\nexpected:\n%#v\ngot:\n%#v", test.expected, service)
  1761  				}
  1762  			} else {
  1763  				require.Error(t, err)
  1764  				require.EqualError(t, err, test.expectErr)
  1765  			}
  1766  
  1767  		})
  1768  
  1769  	}
  1770  }
  1771  

View as plain text