diff --git a/nomad/job_endpoint_hook_expose_check.go b/nomad/job_endpoint_hook_expose_check.go index eea2da41e..ebb51a524 100644 --- a/nomad/job_endpoint_hook_expose_check.go +++ b/nomad/job_endpoint_hook_expose_check.go @@ -1,9 +1,11 @@ package nomad import ( + "fmt" "strconv" "strings" + "github.com/hashicorp/nomad/helper/uuid" "github.com/hashicorp/nomad/nomad/structs" "github.com/pkg/errors" ) @@ -197,6 +199,21 @@ func exposePathForCheck(tg *structs.TaskGroup, s *structs.Service, check *struct return nil, nil } + // If the check is exposable but doesn't have a port label set build + // a port with a generated label, add it to the group's Dynamic ports + // and set the check port label to the generated label. + // + // This lets PortLabel be optional for any exposed check. + if check.PortLabel == "" { + port := structs.Port{ + Label: fmt.Sprintf("svc_%s_ck_%s", s.Name, uuid.Generate()[:6]), + To: -1, + } + + tg.Networks[0].DynamicPorts = append(tg.Networks[0].DynamicPorts, port) + check.PortLabel = port.Label + } + // Determine the local service port (i.e. what port the service is actually // listening to inside the network namespace). // @@ -216,9 +233,7 @@ func exposePathForCheck(tg *structs.TaskGroup, s *structs.Service, check *struct } // The Path, Protocol, and PortLabel are just copied over from the service - // check definition. It is required that the user configure their own port - // mapping for each check, including setting the 'to = -1' sentinel value - // enabling the network namespace pass-through. + // check definition. return &structs.ConsulExposePath{ Path: check.Path, Protocol: check.Protocol, diff --git a/nomad/job_endpoint_hook_expose_check_test.go b/nomad/job_endpoint_hook_expose_check_test.go index 8b00b5884..80c1b271c 100644 --- a/nomad/job_endpoint_hook_expose_check_test.go +++ b/nomad/job_endpoint_hook_expose_check_test.go @@ -346,6 +346,36 @@ func TestJobExposeCheckHook_exposePathForCheck(t *testing.T) { }, s, c) require.EqualError(t, err, `unable to determine local service port for service check group1->service1->check1`) }) + + t.Run("empty check port", func(t *testing.T) { + c := &structs.ServiceCheck{ + Name: "check1", + Type: "http", + Path: "/health", + } + s := &structs.Service{ + Name: "service1", + PortLabel: "9999", + Checks: []*structs.ServiceCheck{c}, + } + tg := &structs.TaskGroup{ + Name: "group1", + Services: []*structs.Service{s}, + Networks: structs.Networks{{ + Mode: "bridge", + DynamicPorts: []structs.Port{}, + }}, + } + ePath, err := exposePathForCheck(tg, s, c) + require.NoError(t, err) + require.Len(t, tg.Networks[0].DynamicPorts, 1) + require.Equal(t, &structs.ConsulExposePath{ + Path: "/health", + Protocol: "", + LocalPathPort: 9999, + ListenerPort: tg.Networks[0].DynamicPorts[0].Label, + }, ePath) + }) } func TestJobExposeCheckHook_containsExposePath(t *testing.T) {