diff --git a/.generated_docs b/.generated_docs
index 6d76553002a..7a45b6a8476 100644
--- a/.generated_docs
+++ b/.generated_docs
@@ -42,6 +42,7 @@ 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-externalname.1
docs/man/man1/kubectl-create-service-loadbalancer.1
docs/man/man1/kubectl-create-service-nodeport.1
docs/man/man1/kubectl-create-service.1
@@ -118,6 +119,7 @@ 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_externalname.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
diff --git a/docs/man/man1/kubectl-create-service-externalname.1 b/docs/man/man1/kubectl-create-service-externalname.1
new file mode 100644
index 00000000000..b6fd7a0f989
--- /dev/null
+++ b/docs/man/man1/kubectl-create-service-externalname.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_externalname.md b/docs/user-guide/kubectl/kubectl_create_service_externalname.md
new file mode 100644
index 00000000000..5b1804865cb
--- /dev/null
+++ b/docs/user-guide/kubectl/kubectl_create_service_externalname.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/hack/make-rules/test-cmd.sh b/hack/make-rules/test-cmd.sh
index dab33477e7e..b35f36b8a8b 100755
--- a/hack/make-rules/test-cmd.sh
+++ b/hack/make-rules/test-cmd.sh
@@ -1934,6 +1934,22 @@ __EOF__
# Post-condition: Only the default kubernetes services exist
kube::test::get_object_assert services "{{range.items}}{{$id_field}}:{{end}}" 'kubernetes:'
+ ### Create an ExternalName service
+ # Pre-condition: Only the default kubernetes service exist
+ kube::test::get_object_assert services "{{range.items}}{{$id_field}}:{{end}}" 'kubernetes:'
+ # Command
+ kubectl create service externalname beep-boop --external-name bar.com
+ # Post-condition: beep-boop service is created
+ kube::test::get_object_assert services "{{range.items}}{{$id_field}}:{{end}}" 'beep-boop:kubernetes:'
+
+ ### Delete beep-boop service by id
+ # Pre-condition: beep-boop service exists
+ kube::test::get_object_assert services "{{range.items}}{{$id_field}}:{{end}}" 'beep-boop:kubernetes:'
+ # Command
+ kubectl delete service beep-boop "${kube_flags[@]}"
+ # Post-condition: Only the default kubernetes services exist
+ kube::test::get_object_assert services "{{range.items}}{{$id_field}}:{{end}}" 'kubernetes:'
+
###########################
# Replication controllers #
diff --git a/hack/verify-flags/known-flags.txt b/hack/verify-flags/known-flags.txt
index ca5a43e947d..25d33b491c1 100644
--- a/hack/verify-flags/known-flags.txt
+++ b/hack/verify-flags/known-flags.txt
@@ -195,6 +195,7 @@ external-etcd-endpoints
external-etcd-keyfile
external-hostname
external-ip
+external-name
extra-peer-dirs
failover-timeout
failure-domains
diff --git a/pkg/kubectl/cmd/create_service.go b/pkg/kubectl/cmd/create_service.go
index 06655687800..7b1d20ac6ed 100644
--- a/pkg/kubectl/cmd/create_service.go
+++ b/pkg/kubectl/cmd/create_service.go
@@ -42,6 +42,7 @@ func NewCmdCreateService(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
cmd.AddCommand(NewCmdCreateServiceClusterIP(f, cmdOut))
cmd.AddCommand(NewCmdCreateServiceNodePort(f, cmdOut))
cmd.AddCommand(NewCmdCreateServiceLoadBalancer(f, cmdOut))
+ cmd.AddCommand(NewCmdCreateServiceExternalName(f, cmdOut))
return cmd
}
@@ -220,3 +221,64 @@ func CreateServiceLoadBalancer(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.C
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
}
+
+var (
+ serviceExternalNameLong = templates.LongDesc(`
+ Create an ExternalName service with the specified name.
+
+ ExternalName service references to an external DNS address instead of
+ only pods, which will allow application authors to reference services
+ that exist off platform, on other clusters, or locally.`)
+
+ serviceExternalNameExample = templates.Examples(`
+ # Create a new ExternalName service named my-ns
+ kubectl create service externalname my-ns --external-name bar.com`)
+)
+
+// NewCmdCreateServiceExternalName is a macro command for creating a ExternalName service
+func NewCmdCreateServiceExternalName(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "externalname NAME --external-name external.name [--dry-run]",
+ Short: "Create an ExternalName service.",
+ Long: serviceExternalNameLong,
+ Example: serviceExternalNameExample,
+ Run: func(cmd *cobra.Command, args []string) {
+ err := CreateExternalNameService(f, cmdOut, cmd, args)
+ cmdutil.CheckErr(err)
+ },
+ }
+ cmdutil.AddApplyAnnotationFlags(cmd)
+ cmdutil.AddValidateFlags(cmd)
+ cmdutil.AddPrinterFlags(cmd)
+ cmdutil.AddGeneratorFlags(cmd, cmdutil.ServiceExternalNameGeneratorV1Name)
+ addPortFlags(cmd)
+ cmd.Flags().String("external-name", "", "external name of service")
+ cmd.MarkFlagRequired("external-name")
+ return cmd
+}
+
+// CreateExternalNameService is the implementation of the service externalname command
+func CreateExternalNameService(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.ServiceExternalNameGeneratorV1Name:
+ generator = &kubectl.ServiceCommonGeneratorV1{
+ Name: name,
+ Type: api.ServiceTypeExternalName,
+ ExternalName: cmdutil.GetFlagString(cmd, "external-name"),
+ 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"),
+ })
+}
diff --git a/pkg/kubectl/cmd/util/factory.go b/pkg/kubectl/cmd/util/factory.go
index bae3a24b81c..babbe1df1db 100644
--- a/pkg/kubectl/cmd/util/factory.go
+++ b/pkg/kubectl/cmd/util/factory.go
@@ -188,6 +188,7 @@ const (
ServiceNodePortGeneratorV1Name = "service-nodeport/v1"
ServiceClusterIPGeneratorV1Name = "service-clusterip/v1"
ServiceLoadBalancerGeneratorV1Name = "service-loadbalancer/v1"
+ ServiceExternalNameGeneratorV1Name = "service-externalname/v1"
ServiceAccountV1GeneratorName = "serviceaccount/v1"
HorizontalPodAutoscalerV1Beta1GeneratorName = "horizontalpodautoscaler/v1beta1"
HorizontalPodAutoscalerV1GeneratorName = "horizontalpodautoscaler/v1"
diff --git a/pkg/kubectl/service_basic.go b/pkg/kubectl/service_basic.go
index 9f1c3d2b6e4..872594a9d21 100644
--- a/pkg/kubectl/service_basic.go
+++ b/pkg/kubectl/service_basic.go
@@ -24,14 +24,16 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/intstr"
+ "k8s.io/kubernetes/pkg/util/validation"
)
type ServiceCommonGeneratorV1 struct {
- Name string
- TCP []string
- Type api.ServiceType
- ClusterIP string
- NodePort int
+ Name string
+ TCP []string
+ Type api.ServiceType
+ ClusterIP string
+ NodePort int
+ ExternalName string
}
type ServiceClusterIPGeneratorV1 struct {
@@ -46,6 +48,11 @@ type ServiceLoadBalancerGeneratorV1 struct {
ServiceCommonGeneratorV1
}
+// TODO: is this really necessary?
+type ServiceExternalNameGeneratorV1 struct {
+ ServiceCommonGeneratorV1
+}
+
func (ServiceClusterIPGeneratorV1) ParamNames() []GeneratorParam {
return []GeneratorParam{
{"name", true},
@@ -67,6 +74,13 @@ func (ServiceLoadBalancerGeneratorV1) ParamNames() []GeneratorParam {
}
}
+func (ServiceExternalNameGeneratorV1) ParamNames() []GeneratorParam {
+ return []GeneratorParam{
+ {"name", true},
+ {"externalname", true},
+ }
+}
+
func parsePorts(portString string) (int32, intstr.IntOrString, error) {
portStringSlice := strings.Split(portString, ":")
@@ -100,9 +114,14 @@ func (s ServiceCommonGeneratorV1) GenerateCommon(params map[string]interface{})
if !isString {
return fmt.Errorf("expected string, saw %v for 'clusterip'", clusterip)
}
+ externalname, isString := params["externalname"].(string)
+ if !isString {
+ return fmt.Errorf("expected string, saw %v for 'externalname'", externalname)
+ }
s.Name = name
s.TCP = tcpStrings
s.ClusterIP = clusterip
+ s.ExternalName = externalname
return nil
}
@@ -145,6 +164,19 @@ func (s ServiceClusterIPGeneratorV1) Generate(params map[string]interface{}) (ru
return delegate.StructuredGenerate()
}
+func (s ServiceExternalNameGeneratorV1) Generate(params map[string]interface{}) (runtime.Object, error) {
+ err := ValidateParams(s.ParamNames(), params)
+ if err != nil {
+ return nil, err
+ }
+ delegate := &ServiceCommonGeneratorV1{Type: api.ServiceTypeExternalName, 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 {
@@ -159,9 +191,14 @@ func (s ServiceCommonGeneratorV1) validate() error {
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 {
+ if s.ClusterIP != api.ClusterIPNone && len(s.TCP) == 0 && s.Type != api.ServiceTypeExternalName {
return fmt.Errorf("at least one tcp port specifier must be provided")
}
+ if s.Type == api.ServiceTypeExternalName {
+ if errs := validation.IsDNS1123Subdomain(s.ExternalName); len(errs) != 0 {
+ return fmt.Errorf("invalid service external name %s", s.ExternalName)
+ }
+ }
return nil
}
@@ -199,9 +236,10 @@ func (s ServiceCommonGeneratorV1) StructuredGenerate() (runtime.Object, error) {
Labels: labels,
},
Spec: api.ServiceSpec{
- Type: api.ServiceType(s.Type),
- Selector: selector,
- Ports: ports,
+ Type: api.ServiceType(s.Type),
+ Selector: selector,
+ Ports: ports,
+ ExternalName: s.ExternalName,
},
}
if len(s.ClusterIP) > 0 {