diff --git a/hack/verify-flags/known-flags.txt b/hack/verify-flags/known-flags.txt index 5e041bd1515..4deadf9ad69 100644 --- a/hack/verify-flags/known-flags.txt +++ b/hack/verify-flags/known-flags.txt @@ -58,6 +58,7 @@ cloud-provider cluster-cidr cluster-dns cluster-domain +cluster-ip cluster-name cluster-tag cluster-monitor-period diff --git a/pkg/kubectl/cmd/expose.go b/pkg/kubectl/cmd/expose.go index c4fca0bd7d9..11573e80ca7 100644 --- a/pkg/kubectl/cmd/expose.go +++ b/pkg/kubectl/cmd/expose.go @@ -120,6 +120,7 @@ func NewCmdExposeService(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().String("overrides", "", "An inline JSON override for the generated object. If this is non-empty, it is used to override the generated object. Requires that the object supply a valid apiVersion field.") cmd.Flags().String("name", "", "The name for the newly created object.") cmd.Flags().String("session-affinity", "", "If non-empty, set the session affinity for the service to this; legal values: 'None', 'ClientIP'") + cmd.Flags().String("cluster-ip", "", "ClusterIP to be assigned to the service. Leave empty to auto-allocate, or set to 'None' to create a headless service.") usage := "Filename, directory, or URL to a file identifying the resource to expose a service" kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage) diff --git a/pkg/kubectl/cmd/expose_test.go b/pkg/kubectl/cmd/expose_test.go index 2338e0c344a..70d6584aaf9 100644 --- a/pkg/kubectl/cmd/expose_test.go +++ b/pkg/kubectl/cmd/expose_test.go @@ -198,6 +198,70 @@ func TestRunExposeService(t *testing.T) { }, status: 200, }, + { + name: "expose-service-cluster-ip", + args: []string{"service", "baz"}, + ns: "test", + calls: map[string]string{ + "GET": "/namespaces/test/services/baz", + "POST": "/namespaces/test/services", + }, + input: &api.Service{ + ObjectMeta: api.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"app": "go"}, + }, + }, + flags: map[string]string{"selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test", "cluster-ip": "10.10.10.10", "dry-run": "true"}, + output: &api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}}, + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{ + { + Protocol: api.ProtocolUDP, + Port: 14, + TargetPort: intstr.FromInt(14), + }, + }, + Selector: map[string]string{"func": "stream"}, + ClusterIP: "10.10.10.10", + }, + }, + expected: "service \"foo\" exposed", + status: 200, + }, + { + name: "expose-headless-service", + args: []string{"service", "baz"}, + ns: "test", + calls: map[string]string{ + "GET": "/namespaces/test/services/baz", + "POST": "/namespaces/test/services", + }, + input: &api.Service{ + ObjectMeta: api.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"app": "go"}, + }, + }, + flags: map[string]string{"selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test", "cluster-ip": "None", "dry-run": "true"}, + output: &api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}}, + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{ + { + Protocol: api.ProtocolUDP, + Port: 14, + TargetPort: intstr.FromInt(14), + }, + }, + Selector: map[string]string{"func": "stream"}, + ClusterIP: api.ClusterIPNone, + }, + }, + expected: "service \"foo\" exposed", + status: 200, + }, { name: "expose-from-file", args: []string{}, diff --git a/pkg/kubectl/service.go b/pkg/kubectl/service.go index 361d75f8be7..604dbf327e0 100644 --- a/pkg/kubectl/service.go +++ b/pkg/kubectl/service.go @@ -72,6 +72,7 @@ func paramNames() []GeneratorParam { {"target-port", false}, {"port-name", false}, {"session-affinity", false}, + {"cluster-ip", false}, } } @@ -225,5 +226,12 @@ func generate(genericParams map[string]interface{}) (runtime.Object, error) { return nil, fmt.Errorf("unknown session affinity: %s", params["session-affinity"]) } } + if len(params["cluster-ip"]) != 0 { + if params["cluster-ip"] == "None" { + service.Spec.ClusterIP = api.ClusterIPNone + } else { + service.Spec.ClusterIP = params["cluster-ip"] + } + } return &service, nil } diff --git a/pkg/kubectl/service_test.go b/pkg/kubectl/service_test.go index bd7f26ce4ef..6d7ad342f8d 100644 --- a/pkg/kubectl/service_test.go +++ b/pkg/kubectl/service_test.go @@ -303,6 +303,66 @@ func TestGenerateService(t *testing.T) { }, }, }, + { + generator: ServiceGeneratorV2{}, + params: map[string]interface{}{ + "selector": "foo=bar,baz=blah", + "name": "test", + "port": "80", + "protocol": "TCP", + "container-port": "1234", + "cluster-ip": "10.10.10.10", + }, + expected: api.Service{ + ObjectMeta: api.ObjectMeta{ + Name: "test", + }, + Spec: api.ServiceSpec{ + Selector: map[string]string{ + "foo": "bar", + "baz": "blah", + }, + Ports: []api.ServicePort{ + { + Port: 80, + Protocol: "TCP", + TargetPort: intstr.FromInt(1234), + }, + }, + ClusterIP: "10.10.10.10", + }, + }, + }, + { + generator: ServiceGeneratorV2{}, + params: map[string]interface{}{ + "selector": "foo=bar,baz=blah", + "name": "test", + "port": "80", + "protocol": "TCP", + "container-port": "1234", + "cluster-ip": "None", + }, + expected: api.Service{ + ObjectMeta: api.ObjectMeta{ + Name: "test", + }, + Spec: api.ServiceSpec{ + Selector: map[string]string{ + "foo": "bar", + "baz": "blah", + }, + Ports: []api.ServicePort{ + { + Port: 80, + Protocol: "TCP", + TargetPort: intstr.FromInt(1234), + }, + }, + ClusterIP: api.ClusterIPNone, + }, + }, + }, { generator: ServiceGeneratorV1{}, params: map[string]interface{}{