diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index 6a49148ba..6f409a65c 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -1510,10 +1510,13 @@ func (s *Service) InitFields(job string, taskGroup string, task string) { func (s *Service) Validate() error { var mErr multierror.Error - // Ensure the name does not have a period in it. - // RFC-2782: https://tools.ietf.org/html/rfc2782 - if strings.Contains(s.Name, ".") { - mErr.Errors = append(mErr.Errors, fmt.Errorf("service name can't contain periods: %q", s.Name)) + // Ensure the service name is valid per RFC-952 §1 + // (https://tools.ietf.org/html/rfc952), RFC-1123 §2.1 + // (https://tools.ietf.org/html/rfc1123), and RFC-2782 + // (https://tools.ietf.org/html/rfc2782). + re := regexp.MustCompile(`^(?i:[a-z0-9]|[a-z0-9][a-z0-9\-]{0,61}[a-z0-9])$`) + if !re.MatchString(s.Name) { + mErr.Errors = append(mErr.Errors, fmt.Errorf("service name must be valid per RFC 1123 and can contain only alphanumeric characters or dashes and must be less than 63 characters long: %q", s.Name)) } for _, c := range s.Checks { diff --git a/nomad/structs/structs_test.go b/nomad/structs/structs_test.go index dbfe88686..07a532575 100644 --- a/nomad/structs/structs_test.go +++ b/nomad/structs/structs_test.go @@ -519,7 +519,7 @@ func TestInvalidServiceCheck(t *testing.T) { }, } if err := s.Validate(); err == nil { - t.Fatalf("Service should be invalid") + t.Fatalf("Service should be invalid (invalid type)") } s = Service{ @@ -527,7 +527,23 @@ func TestInvalidServiceCheck(t *testing.T) { PortLabel: "bar", } if err := s.Validate(); err == nil { - t.Fatalf("Service should be invalid: %v", err) + t.Fatalf("Service should be invalid (contains a dot): %v", err) + } + + s = Service{ + Name: "-my-service", + PortLabel: "bar", + } + if err := s.Validate(); err == nil { + t.Fatalf("Service should be invalid (begins with a hyphen): %v", err) + } + + s = Service{ + Name: "abcdef0123456789-abcdef0123456789-abcdef0123456789-abcdef0123456", + PortLabel: "bar", + } + if err := s.Validate(); err == nil { + t.Fatalf("Service should be invalid (too long): %v", err) } } diff --git a/website/source/docs/jobspec/servicediscovery.html.md b/website/source/docs/jobspec/servicediscovery.html.md index c2432259a..7585d1c47 100644 --- a/website/source/docs/jobspec/servicediscovery.html.md +++ b/website/source/docs/jobspec/servicediscovery.html.md @@ -76,13 +76,17 @@ group "database" { ``` * `name`: Nomad automatically determines the name of a Task. By default the - name of a service is $(job-name)-$(task-group)-$(task-name). Users can + name of a service is `$(job-name)-$(task-group)-$(task-name)`. Users can explicitly name the service by specifying this option. If multiple services - are defined for a Task then only one task can have the default name, all the - services have to be explicitly named. Users can add the following to the - service names: ```${JOB}```, ```${TASKGROUP}```, ```${TASK}```, ```${BASE}```. - Nomad will replace them with the appropriate value of the Job, Task Group and - Task names while registering the Job. ```${BASE}``` expands to ${JOB}-${TASKGROUP}-${TASK} + are defined for a Task then only one task can have the default name, all + the services have to be explicitly named. Users can add the following to + the service names: `${JOB}`, `${TASKGROUP}`, `${TASK}`, `${BASE}`. Nomad + will replace them with the appropriate value of the Job, Task Group, and + Task names while registering the Job. `${BASE}` expands to + `${JOB}-${TASKGROUP}-${TASK}`. Names must be adhere to + [RFC-1123 §2.1](https://tools.ietf.org/html/rfc1123#section-2) and are + limited to alphanumeric and hyphen characters (i.e. `[a-z0-9\-]`), and be + less than 64 characters in length. * `tags`: A list of tags associated with this Service.