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 @@ + + + + +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_externalname.md?pixel)]() + 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 {