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 @@
+
+
+
+
+
+
+
+
+
+
+
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.
+
+
+[]()
+
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 @@
+
+
+
+
+
+
+
+
+
+
+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.
+
+
+[]()
+
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 @@
+
+
+
+
+
+
+
+
+
+
+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.
+
+
+[]()
+
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 @@
+
+
+
+
+
+
+
+
+
+
+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.
+
+
+[]()
+
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))
+ }
+ }
+}