From 42ca885edb468c016b5da7f5403e02d2e08261a1 Mon Sep 17 00:00:00 2001 From: Angus Salkeld Date: Wed, 29 Jun 2016 12:32:35 +0200 Subject: [PATCH] Add "create service" sub-commands This adds: create service clusterip create service nodeport create service loadbalancer --- .generated_docs | 8 + .../man1/kubectl-create-service-clusterip.1 | 3 + .../kubectl-create-service-loadbalancer.1 | 3 + .../man1/kubectl-create-service-nodeport.1 | 3 + docs/man/man1/kubectl-create-service.1 | 3 + .../kubectl/kubectl_create_service.md | 36 +++ .../kubectl_create_service_clusterip.md | 36 +++ .../kubectl_create_service_loadbalancer.md | 36 +++ .../kubectl_create_service_nodeport.md | 36 +++ pkg/kubectl/cmd/create.go | 1 + pkg/kubectl/cmd/create_service.go | 219 ++++++++++++++++++ pkg/kubectl/cmd/create_service_test.go | 55 +++++ pkg/kubectl/cmd/util/factory.go | 12 + pkg/kubectl/service_basic.go | 207 +++++++++++++++++ pkg/kubectl/service_basic_test.go | 129 +++++++++++ 15 files changed, 787 insertions(+) create mode 100644 docs/man/man1/kubectl-create-service-clusterip.1 create mode 100644 docs/man/man1/kubectl-create-service-loadbalancer.1 create mode 100644 docs/man/man1/kubectl-create-service-nodeport.1 create mode 100644 docs/man/man1/kubectl-create-service.1 create mode 100644 docs/user-guide/kubectl/kubectl_create_service.md create mode 100644 docs/user-guide/kubectl/kubectl_create_service_clusterip.md create mode 100644 docs/user-guide/kubectl/kubectl_create_service_loadbalancer.md create mode 100644 docs/user-guide/kubectl/kubectl_create_service_nodeport.md create mode 100644 pkg/kubectl/cmd/create_service.go create mode 100644 pkg/kubectl/cmd/create_service_test.go create mode 100644 pkg/kubectl/service_basic.go create mode 100644 pkg/kubectl/service_basic_test.go diff --git a/.generated_docs b/.generated_docs index 1bcefdf9d55..1cdfc537781 100644 --- a/.generated_docs +++ b/.generated_docs @@ -33,6 +33,10 @@ docs/man/man1/kubectl-create-secret-docker-registry.1 docs/man/man1/kubectl-create-secret-generic.1 docs/man/man1/kubectl-create-secret-tls.1 docs/man/man1/kubectl-create-secret.1 +docs/man/man1/kubectl-create-service-clusterip.1 +docs/man/man1/kubectl-create-service-loadbalancer.1 +docs/man/man1/kubectl-create-service-nodeport.1 +docs/man/man1/kubectl-create-service.1 docs/man/man1/kubectl-create-serviceaccount.1 docs/man/man1/kubectl-create.1 docs/man/man1/kubectl-delete.1 @@ -95,6 +99,10 @@ docs/user-guide/kubectl/kubectl_create_secret.md docs/user-guide/kubectl/kubectl_create_secret_docker-registry.md docs/user-guide/kubectl/kubectl_create_secret_generic.md docs/user-guide/kubectl/kubectl_create_secret_tls.md +docs/user-guide/kubectl/kubectl_create_service.md +docs/user-guide/kubectl/kubectl_create_service_clusterip.md +docs/user-guide/kubectl/kubectl_create_service_loadbalancer.md +docs/user-guide/kubectl/kubectl_create_service_nodeport.md docs/user-guide/kubectl/kubectl_create_serviceaccount.md docs/user-guide/kubectl/kubectl_delete.md docs/user-guide/kubectl/kubectl_describe.md diff --git a/docs/man/man1/kubectl-create-service-clusterip.1 b/docs/man/man1/kubectl-create-service-clusterip.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubectl-create-service-clusterip.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubectl-create-service-loadbalancer.1 b/docs/man/man1/kubectl-create-service-loadbalancer.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubectl-create-service-loadbalancer.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubectl-create-service-nodeport.1 b/docs/man/man1/kubectl-create-service-nodeport.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubectl-create-service-nodeport.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubectl-create-service.1 b/docs/man/man1/kubectl-create-service.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubectl-create-service.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/user-guide/kubectl/kubectl_create_service.md b/docs/user-guide/kubectl/kubectl_create_service.md new file mode 100644 index 00000000000..bf0f1869426 --- /dev/null +++ b/docs/user-guide/kubectl/kubectl_create_service.md @@ -0,0 +1,36 @@ + + + + +WARNING +WARNING +WARNING +WARNING +WARNING + +

PLEASE NOTE: This document applies to the HEAD of the source tree

+ +If you are using a released version of Kubernetes, you should +refer to the docs that go with that version. + +Documentation for other releases can be found at +[releases.k8s.io](http://releases.k8s.io). + +-- + + + + + +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. + + +[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_create_service.md?pixel)]() + diff --git a/docs/user-guide/kubectl/kubectl_create_service_clusterip.md b/docs/user-guide/kubectl/kubectl_create_service_clusterip.md new file mode 100644 index 00000000000..6a05536b134 --- /dev/null +++ b/docs/user-guide/kubectl/kubectl_create_service_clusterip.md @@ -0,0 +1,36 @@ + + + + +WARNING +WARNING +WARNING +WARNING +WARNING + +

PLEASE NOTE: This document applies to the HEAD of the source tree

+ +If you are using a released version of Kubernetes, you should +refer to the docs that go with that version. + +Documentation for other releases can be found at +[releases.k8s.io](http://releases.k8s.io). + +-- + + + + + +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. + + +[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_create_service_clusterip.md?pixel)]() + diff --git a/docs/user-guide/kubectl/kubectl_create_service_loadbalancer.md b/docs/user-guide/kubectl/kubectl_create_service_loadbalancer.md new file mode 100644 index 00000000000..04fef770db4 --- /dev/null +++ b/docs/user-guide/kubectl/kubectl_create_service_loadbalancer.md @@ -0,0 +1,36 @@ + + + + +WARNING +WARNING +WARNING +WARNING +WARNING + +

PLEASE NOTE: This document applies to the HEAD of the source tree

+ +If you are using a released version of Kubernetes, you should +refer to the docs that go with that version. + +Documentation for other releases can be found at +[releases.k8s.io](http://releases.k8s.io). + +-- + + + + + +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. + + +[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_create_service_loadbalancer.md?pixel)]() + diff --git a/docs/user-guide/kubectl/kubectl_create_service_nodeport.md b/docs/user-guide/kubectl/kubectl_create_service_nodeport.md new file mode 100644 index 00000000000..6630c908dbb --- /dev/null +++ b/docs/user-guide/kubectl/kubectl_create_service_nodeport.md @@ -0,0 +1,36 @@ + + + + +WARNING +WARNING +WARNING +WARNING +WARNING + +

PLEASE NOTE: This document applies to the HEAD of the source tree

+ +If you are using a released version of Kubernetes, you should +refer to the docs that go with that version. + +Documentation for other releases can be found at +[releases.k8s.io](http://releases.k8s.io). + +-- + + + + + +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. + + +[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_create_service_nodeport.md?pixel)]() + diff --git a/pkg/kubectl/cmd/create.go b/pkg/kubectl/cmd/create.go index 53cb68bd5a4..f23614a852d 100644 --- a/pkg/kubectl/cmd/create.go +++ b/pkg/kubectl/cmd/create.go @@ -84,6 +84,7 @@ func NewCmdCreate(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd.AddCommand(NewCmdCreateSecret(f, out)) cmd.AddCommand(NewCmdCreateConfigMap(f, out)) cmd.AddCommand(NewCmdCreateServiceAccount(f, out)) + cmd.AddCommand(NewCmdCreateService(f, out)) return cmd } diff --git a/pkg/kubectl/cmd/create_service.go b/pkg/kubectl/cmd/create_service.go new file mode 100644 index 00000000000..8dd3aa6bb63 --- /dev/null +++ b/pkg/kubectl/cmd/create_service.go @@ -0,0 +1,219 @@ +/* +Copyright 2016 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 cmd + +import ( + "fmt" + "io" + + "github.com/renstrom/dedent" + "github.com/spf13/cobra" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/kubectl" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" +) + +// NewCmdCreateService is a macro command to create a new namespace +func NewCmdCreateService(f *cmdutil.Factory, cmdOut io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "service", + Short: "Create a service using specified subcommand.", + Long: "Create a service using specified subcommand.", + Run: func(cmd *cobra.Command, args []string) { + cmd.Help() + }, + } + cmd.AddCommand(NewCmdCreateServiceClusterIP(f, cmdOut)) + cmd.AddCommand(NewCmdCreateServiceNodePort(f, cmdOut)) + cmd.AddCommand(NewCmdCreateServiceLoadBalancer(f, cmdOut)) + + return cmd +} + +var ( + serviceClusterIPLong = dedent.Dedent(` + Create a clusterIP service with the specified name.`) + + serviceClusterIPExample = dedent.Dedent(` + # Create a new clusterIP service named my-cs + kubectl create service clusterip my-cs --tcp=5678:8080 + + # Create a new clusterIP service named my-cs (in headless mode) + kubectl create service clusterip my-cs --clusterip="None"`) +) + +func addPortFlags(cmd *cobra.Command) { + cmd.Flags().StringSlice("tcp", []string{}, "Port pairs can be specified as ':'.") +} + +// NewCmdCreateServiceClusterIP is a command to create generic secrets from files, directories, or literal values +func NewCmdCreateServiceClusterIP(f *cmdutil.Factory, cmdOut io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "clusterip NAME [--tcp=:] [--dry-run]", + Short: "Create a clusterIP service.", + Long: serviceClusterIPLong, + Example: serviceClusterIPExample, + Run: func(cmd *cobra.Command, args []string) { + err := CreateServiceClusterIP(f, cmdOut, cmd, args) + cmdutil.CheckErr(err) + }, + } + cmdutil.AddApplyAnnotationFlags(cmd) + cmdutil.AddValidateFlags(cmd) + cmdutil.AddPrinterFlags(cmd) + cmdutil.AddGeneratorFlags(cmd, cmdutil.ServiceClusterIPGeneratorV1Name) + addPortFlags(cmd) + cmd.Flags().String("clusterip", "", "Assign your own ClusterIP or set to 'None' for a 'headless' service (no loadbalancing).") + return cmd +} + +// CreateServiceClusterIP implements the behavior to run the create namespace command +func CreateServiceClusterIP(f *cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error { + name, err := NameFromCommandArgs(cmd, args) + if err != nil { + return err + } + var generator kubectl.StructuredGenerator + switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName { + case cmdutil.ServiceClusterIPGeneratorV1Name: + generator = &kubectl.ServiceCommonGeneratorV1{ + Name: name, + TCP: cmdutil.GetFlagStringSlice(cmd, "tcp"), + Type: api.ServiceTypeClusterIP, + ClusterIP: cmdutil.GetFlagString(cmd, "clusterip"), + } + default: + return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName)) + } + return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{ + Name: name, + StructuredGenerator: generator, + DryRun: cmdutil.GetDryRunFlag(cmd), + OutputFormat: cmdutil.GetFlagString(cmd, "output"), + }) +} + +var ( + serviceNodePortLong = dedent.Dedent(` + Create a nodeport service with the specified name.`) + + serviceNodePortExample = dedent.Dedent(` + # Create a new nodeport service named my-ns + kubectl create service nodeport my-ns --tcp=5678:8080`) +) + +// NewCmdCreateServiceNodePort is a macro command for creating secrets to work with Docker registries +func NewCmdCreateServiceNodePort(f *cmdutil.Factory, cmdOut io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "nodeport NAME [--tcp=port:targetPort] [--dry-run]", + Short: "Create a NodePort service.", + Long: serviceNodePortLong, + Example: serviceNodePortExample, + Run: func(cmd *cobra.Command, args []string) { + err := CreateServiceNodePort(f, cmdOut, cmd, args) + cmdutil.CheckErr(err) + }, + } + cmdutil.AddApplyAnnotationFlags(cmd) + cmdutil.AddValidateFlags(cmd) + cmdutil.AddPrinterFlags(cmd) + cmdutil.AddGeneratorFlags(cmd, cmdutil.ServiceNodePortGeneratorV1Name) + addPortFlags(cmd) + return cmd +} + +// CreateServiceNodePort is the implementation of the create secret docker-registry command +func CreateServiceNodePort(f *cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error { + name, err := NameFromCommandArgs(cmd, args) + if err != nil { + return err + } + var generator kubectl.StructuredGenerator + switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName { + case cmdutil.ServiceNodePortGeneratorV1Name: + generator = &kubectl.ServiceCommonGeneratorV1{ + Name: name, + TCP: cmdutil.GetFlagStringSlice(cmd, "tcp"), + Type: api.ServiceTypeNodePort, + ClusterIP: "", + } + default: + return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName)) + } + return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{ + Name: name, + StructuredGenerator: generator, + DryRun: cmdutil.GetDryRunFlag(cmd), + OutputFormat: cmdutil.GetFlagString(cmd, "output"), + }) +} + +var ( + serviceLoadBalancerLong = dedent.Dedent(` + Create a LoadBalancer service with the specified name.`) + + serviceLoadBalancerExample = dedent.Dedent(` + # Create a new nodeport service named my-lbs + kubectl create service loadbalancer my-lbs --tcp=5678:8080`) +) + +// NewCmdCreateServiceLoadBalancer is a macro command for creating secrets to work with Docker registries +func NewCmdCreateServiceLoadBalancer(f *cmdutil.Factory, cmdOut io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "loadbalancer NAME [--tcp=port:targetPort] [--dry-run]", + Short: "Create a LoadBalancer service.", + Long: serviceLoadBalancerLong, + Example: serviceLoadBalancerExample, + Run: func(cmd *cobra.Command, args []string) { + err := CreateServiceLoadBalancer(f, cmdOut, cmd, args) + cmdutil.CheckErr(err) + }, + } + cmdutil.AddApplyAnnotationFlags(cmd) + cmdutil.AddValidateFlags(cmd) + cmdutil.AddPrinterFlags(cmd) + cmdutil.AddGeneratorFlags(cmd, cmdutil.ServiceLoadBalancerGeneratorV1Name) + addPortFlags(cmd) + return cmd +} + +// CreateServiceLoadBalancer is the implementation of the create secret tls command +func CreateServiceLoadBalancer(f *cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error { + name, err := NameFromCommandArgs(cmd, args) + if err != nil { + return err + } + var generator kubectl.StructuredGenerator + switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName { + case cmdutil.ServiceLoadBalancerGeneratorV1Name: + generator = &kubectl.ServiceCommonGeneratorV1{ + Name: name, + TCP: cmdutil.GetFlagStringSlice(cmd, "tcp"), + Type: api.ServiceTypeLoadBalancer, + ClusterIP: "", + } + default: + return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName)) + } + return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{ + Name: name, + StructuredGenerator: generator, + DryRun: cmdutil.GetFlagBool(cmd, "dry-run"), + OutputFormat: cmdutil.GetFlagString(cmd, "output"), + }) +} diff --git a/pkg/kubectl/cmd/create_service_test.go b/pkg/kubectl/cmd/create_service_test.go new file mode 100644 index 00000000000..6cf4312adaa --- /dev/null +++ b/pkg/kubectl/cmd/create_service_test.go @@ -0,0 +1,55 @@ +/* +Copyright 2016 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 cmd + +import ( + "bytes" + "net/http" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/unversioned/fake" +) + +func TestCreateService(t *testing.T) { + service := &api.Service{} + service.Name = "my-service" + f, tf, codec, negSer := NewAPIFactory() + tf.Printer = &testPrinter{} + tf.Client = &fake.RESTClient{ + NegotiatedSerializer: negSer, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + switch p, m := req.URL.Path, req.Method; { + case p == "/namespaces/test/services" && m == "POST": + return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, service)}, nil + default: + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil + } + }), + } + tf.Namespace = "test" + buf := bytes.NewBuffer([]byte{}) + cmd := NewCmdCreateServiceClusterIP(f, buf) + cmd.Flags().Set("output", "name") + cmd.Flags().Set("tcp", "8080:8000") + cmd.Run(cmd, []string{service.Name}) + expectedOutput := "service/" + service.Name + "\n" + if buf.String() != expectedOutput { + t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String()) + } +} diff --git a/pkg/kubectl/cmd/util/factory.go b/pkg/kubectl/cmd/util/factory.go index a961805cd84..eadfe07533b 100644 --- a/pkg/kubectl/cmd/util/factory.go +++ b/pkg/kubectl/cmd/util/factory.go @@ -158,6 +158,9 @@ const ( RunPodV1GeneratorName = "run-pod/v1" ServiceV1GeneratorName = "service/v1" ServiceV2GeneratorName = "service/v2" + ServiceNodePortGeneratorV1Name = "service-nodeport/v1" + ServiceClusterIPGeneratorV1Name = "service-clusterip/v1" + ServiceLoadBalancerGeneratorV1Name = "service-loadbalancer/v1" ServiceAccountV1GeneratorName = "serviceaccount/v1" HorizontalPodAutoscalerV1Beta1GeneratorName = "horizontalpodautoscaler/v1beta1" HorizontalPodAutoscalerV1GeneratorName = "horizontalpodautoscaler/v1" @@ -180,6 +183,15 @@ func DefaultGenerators(cmdName string) map[string]kubectl.Generator { ServiceV1GeneratorName: kubectl.ServiceGeneratorV1{}, ServiceV2GeneratorName: kubectl.ServiceGeneratorV2{}, } + generators["service-clusterip"] = map[string]kubectl.Generator{ + ServiceClusterIPGeneratorV1Name: kubectl.ServiceClusterIPGeneratorV1{}, + } + generators["service-nodeport"] = map[string]kubectl.Generator{ + ServiceNodePortGeneratorV1Name: kubectl.ServiceNodePortGeneratorV1{}, + } + generators["service-loadbalancer"] = map[string]kubectl.Generator{ + ServiceLoadBalancerGeneratorV1Name: kubectl.ServiceLoadBalancerGeneratorV1{}, + } generators["run"] = map[string]kubectl.Generator{ RunV1GeneratorName: kubectl.BasicReplicationController{}, RunPodV1GeneratorName: kubectl.BasicPod{}, diff --git a/pkg/kubectl/service_basic.go b/pkg/kubectl/service_basic.go new file mode 100644 index 00000000000..ee080aae3a9 --- /dev/null +++ b/pkg/kubectl/service_basic.go @@ -0,0 +1,207 @@ +/* +Copyright 2016 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 kubectl + +import ( + "fmt" + "strconv" + "strings" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/intstr" +) + +type ServiceCommonGeneratorV1 struct { + Name string + TCP []string + Type api.ServiceType + ClusterIP string +} + +type ServiceClusterIPGeneratorV1 struct { + ServiceCommonGeneratorV1 +} + +type ServiceNodePortGeneratorV1 struct { + ServiceCommonGeneratorV1 +} + +type ServiceLoadBalancerGeneratorV1 struct { + ServiceCommonGeneratorV1 +} + +func (ServiceClusterIPGeneratorV1) ParamNames() []GeneratorParam { + return []GeneratorParam{ + {"name", true}, + {"tcp", true}, + {"clusterip", false}, + } +} +func (ServiceNodePortGeneratorV1) ParamNames() []GeneratorParam { + return []GeneratorParam{ + {"name", true}, + {"tcp", true}, + } +} +func (ServiceLoadBalancerGeneratorV1) ParamNames() []GeneratorParam { + return []GeneratorParam{ + {"name", true}, + {"tcp", true}, + } +} + +func parsePorts(portString string) (int32, intstr.IntOrString, error) { + portStringSlice := strings.Split(portString, ":") + + port, err := strconv.Atoi(portStringSlice[0]) + if err != nil { + return 0, intstr.FromInt(0), err + } + if len(portStringSlice) == 1 { + return int32(port), intstr.FromInt(int(port)), nil + } + + var targetPort intstr.IntOrString + if portNum, err := strconv.Atoi(portStringSlice[1]); err != nil { + targetPort = intstr.FromString(portStringSlice[1]) + } else { + targetPort = intstr.FromInt(portNum) + } + return int32(port), targetPort, nil +} + +func (s ServiceCommonGeneratorV1) GenerateCommon(params map[string]interface{}) error { + name, isString := params["name"].(string) + if !isString { + return fmt.Errorf("expected string, saw %v for 'name'", name) + } + tcpStrings, isArray := params["tcp"].([]string) + if !isArray { + return fmt.Errorf("expected []string, found :%v", tcpStrings) + } + clusterip, isString := params["clusterip"].(string) + if !isString { + return fmt.Errorf("expected string, saw %v for 'clusterip'", clusterip) + } + s.Name = name + s.TCP = tcpStrings + s.ClusterIP = clusterip + return nil +} + +func (s ServiceLoadBalancerGeneratorV1) Generate(params map[string]interface{}) (runtime.Object, error) { + err := ValidateParams(s.ParamNames(), params) + if err != nil { + return nil, err + } + delegate := &ServiceCommonGeneratorV1{Type: api.ServiceTypeLoadBalancer, ClusterIP: ""} + err = delegate.GenerateCommon(params) + if err != nil { + return nil, err + } + return delegate.StructuredGenerate() +} + +func (s ServiceNodePortGeneratorV1) Generate(params map[string]interface{}) (runtime.Object, error) { + err := ValidateParams(s.ParamNames(), params) + if err != nil { + return nil, err + } + delegate := &ServiceCommonGeneratorV1{Type: api.ServiceTypeNodePort, ClusterIP: ""} + err = delegate.GenerateCommon(params) + if err != nil { + return nil, err + } + return delegate.StructuredGenerate() +} + +func (s ServiceClusterIPGeneratorV1) Generate(params map[string]interface{}) (runtime.Object, error) { + err := ValidateParams(s.ParamNames(), params) + if err != nil { + return nil, err + } + delegate := &ServiceCommonGeneratorV1{Type: api.ServiceTypeClusterIP, ClusterIP: ""} + err = delegate.GenerateCommon(params) + if err != nil { + return nil, err + } + return delegate.StructuredGenerate() +} + +// validate validates required fields are set to support structured generation +func (s ServiceCommonGeneratorV1) validate() error { + if len(s.Name) == 0 { + return fmt.Errorf("name must be specified") + } + if len(s.Type) == 0 { + return fmt.Errorf("type must be specified") + } + if s.ClusterIP == api.ClusterIPNone && s.Type != api.ServiceTypeClusterIP { + return fmt.Errorf("ClusterIP=None can only be used with ClusterIP service type") + } + if s.ClusterIP == api.ClusterIPNone && len(s.TCP) > 0 { + return fmt.Errorf("can not map ports with clusterip=None") + } + if s.ClusterIP != api.ClusterIPNone && len(s.TCP) == 0 { + return fmt.Errorf("at least one tcp port specifier must be provided") + } + return nil +} + +func (s ServiceCommonGeneratorV1) StructuredGenerate() (runtime.Object, error) { + err := s.validate() + if err != nil { + return nil, err + } + ports := []api.ServicePort{} + for _, tcpString := range s.TCP { + port, targetPort, err := parsePorts(tcpString) + if err != nil { + return nil, err + } + portName := strings.Replace(tcpString, ":", "-", -1) + ports = append(ports, api.ServicePort{ + Name: portName, + Port: port, + TargetPort: targetPort, + Protocol: api.Protocol("TCP"), + }) + } + + // setup default label and selector + labels := map[string]string{} + labels["app"] = s.Name + selector := map[string]string{} + selector["app"] = s.Name + + service := api.Service{ + ObjectMeta: api.ObjectMeta{ + Name: s.Name, + Labels: labels, + }, + Spec: api.ServiceSpec{ + Type: api.ServiceType(s.Type), + Selector: selector, + Ports: ports, + }, + } + if len(s.ClusterIP) > 0 { + service.Spec.ClusterIP = s.ClusterIP + } + return &service, nil +} diff --git a/pkg/kubectl/service_basic_test.go b/pkg/kubectl/service_basic_test.go new file mode 100644 index 00000000000..8aab29c81f7 --- /dev/null +++ b/pkg/kubectl/service_basic_test.go @@ -0,0 +1,129 @@ +/* +Copyright 2016 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 kubectl + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/util/intstr" +) + +func TestServiceBasicGenerate(t *testing.T) { + tests := []struct { + name string + serviceType api.ServiceType + tcp []string + clusterip string + expected *api.Service + expectErr bool + }{ + { + name: "clusterip-ok", + tcp: []string{"456", "321:908"}, + clusterip: "", + serviceType: api.ServiceTypeClusterIP, + expected: &api.Service{ + ObjectMeta: api.ObjectMeta{ + Name: "clusterip-ok", + Labels: map[string]string{"app": "clusterip-ok"}, + }, + Spec: api.ServiceSpec{Type: "ClusterIP", + Ports: []api.ServicePort{{Name: "456", Protocol: "TCP", Port: 456, TargetPort: intstr.IntOrString{Type: 0, IntVal: 456, StrVal: ""}, NodePort: 0}, + {Name: "321-908", Protocol: "TCP", Port: 321, TargetPort: intstr.IntOrString{Type: 0, IntVal: 908, StrVal: ""}, NodePort: 0}}, + Selector: map[string]string{"app": "clusterip-ok"}, + ClusterIP: "", ExternalIPs: []string(nil), LoadBalancerIP: ""}, + }, + expectErr: false, + }, + { + name: "clusterip-missing", + serviceType: api.ServiceTypeClusterIP, + expectErr: true, + }, + { + name: "clusterip-none and port mapping", + tcp: []string{"456:9898"}, + clusterip: "None", + serviceType: api.ServiceTypeClusterIP, + expectErr: true, + }, + { + name: "clusterip-none-wrong-type", + tcp: []string{}, + clusterip: "None", + serviceType: api.ServiceTypeNodePort, + expectErr: true, + }, + { + name: "clusterip-none-ok", + tcp: []string{}, + clusterip: "None", + serviceType: api.ServiceTypeClusterIP, + expected: &api.Service{ + ObjectMeta: api.ObjectMeta{ + Name: "clusterip-none-ok", + Labels: map[string]string{"app": "clusterip-none-ok"}, + }, + Spec: api.ServiceSpec{Type: "ClusterIP", + Ports: []api.ServicePort{}, + Selector: map[string]string{"app": "clusterip-none-ok"}, + ClusterIP: "None", ExternalIPs: []string(nil), LoadBalancerIP: ""}, + }, + expectErr: false, + }, + { + name: "loadbalancer-ok", + tcp: []string{"456:9898"}, + clusterip: "", + serviceType: api.ServiceTypeLoadBalancer, + expected: &api.Service{ + ObjectMeta: api.ObjectMeta{ + Name: "loadbalancer-ok", + Labels: map[string]string{"app": "loadbalancer-ok"}, + }, + Spec: api.ServiceSpec{Type: "LoadBalancer", + Ports: []api.ServicePort{{Name: "456-9898", Protocol: "TCP", Port: 456, TargetPort: intstr.IntOrString{Type: 0, IntVal: 9898, StrVal: ""}, NodePort: 0}}, + Selector: map[string]string{"app": "loadbalancer-ok"}, + ClusterIP: "", ExternalIPs: []string(nil), LoadBalancerIP: ""}, + }, + expectErr: false, + }, + { + expectErr: true, + }, + } + for _, test := range tests { + generator := ServiceCommonGeneratorV1{ + Name: test.name, + TCP: test.tcp, + Type: test.serviceType, + ClusterIP: test.clusterip, + } + obj, err := generator.StructuredGenerate() + if !test.expectErr && err != nil { + t.Errorf("unexpected error: %v", err) + } + if test.expectErr && err != nil { + continue + } + if !reflect.DeepEqual(obj.(*api.Service), test.expected) { + t.Errorf("test: %v\nexpected:\n%#v\nsaw:\n%#v", test.name, test.expected, obj.(*api.Service)) + } + } +}