diff --git a/nomad/job_endpoint_hook_implicit_identities.go b/nomad/job_endpoint_hook_implicit_identities.go index 89841aa6b31d..a55f50f16bfa 100644 --- a/nomad/job_endpoint_hook_implicit_identities.go +++ b/nomad/job_endpoint_hook_implicit_identities.go @@ -19,24 +19,46 @@ func (jobImplicitIdentitiesHook) Name() string { func (h jobImplicitIdentitiesHook) Mutate(job *structs.Job) (*structs.Job, []error, error) { for _, tg := range job.TaskGroups { + var hasIdentity bool + for _, s := range tg.Services { h.handleConsulService(s, tg) + hasIdentity = hasIdentity || s.Identity != nil } for _, t := range tg.Tasks { for _, s := range t.Services { h.handleConsulService(s, tg) + hasIdentity = hasIdentity || s.Identity != nil } if len(t.Templates) > 0 { h.handleConsulTasks(t, tg) } h.handleVault(t) + hasIdentity = hasIdentity || (len(t.Identities) > 0) + } + + if hasIdentity { + tg.Constraints = append(tg.Constraints, implicitIdentityClientVersionConstraint()) } } return job, nil, nil } +// implicitIdentityClientVersionConstraint is used when the client needs to +// support a workload identity workflow for Consul or Vault, or multiple +// identities in general. +func implicitIdentityClientVersionConstraint() *structs.Constraint { + // "-a" is used here so that it is "less than" all pre-release versions of + // Nomad 1.7.0 as well + return &structs.Constraint{ + LTarget: "${attr.nomad.version}", + RTarget: ">= 1.7.0-a", + Operand: structs.ConstraintSemver, + } +} + // handleConsulService injects a workload identity to the service if: // 1. The service uses the Consul provider, and // 2. The server is configured with `consul.service_identity` diff --git a/nomad/job_endpoint_hook_implicit_identities_test.go b/nomad/job_endpoint_hook_implicit_identities_test.go index 69b1cab79797..7d3510302783 100644 --- a/nomad/job_endpoint_hook_implicit_identities_test.go +++ b/nomad/job_endpoint_hook_implicit_identities_test.go @@ -119,6 +119,8 @@ func Test_jobImplicitIdentitiesHook_Mutate_consul_service(t *testing.T) { }, expectedOutputJob: &structs.Job{ TaskGroups: []*structs.TaskGroup{{ + Constraints: []*structs.Constraint{ + implicitIdentityClientVersionConstraint()}, Services: []*structs.Service{ { Provider: "consul", @@ -195,6 +197,8 @@ func Test_jobImplicitIdentitiesHook_Mutate_consul_service(t *testing.T) { }, expectedOutputJob: &structs.Job{ TaskGroups: []*structs.TaskGroup{{ + Constraints: []*structs.Constraint{ + implicitIdentityClientVersionConstraint()}, Services: []*structs.Service{{ Provider: "consul", PortLabel: "80", @@ -245,6 +249,8 @@ func Test_jobImplicitIdentitiesHook_Mutate_consul_service(t *testing.T) { expectedOutputJob: &structs.Job{ TaskGroups: []*structs.TaskGroup{{ Name: "group", + Constraints: []*structs.Constraint{ + implicitIdentityClientVersionConstraint()}, Tasks: []*structs.Task{{ Name: "web-task", Templates: []*structs.Template{{}}, @@ -371,6 +377,8 @@ func Test_jobImplicitIndentitiesHook_Mutate_vault(t *testing.T) { }, expectedOutputJob: &structs.Job{ TaskGroups: []*structs.TaskGroup{{ + Constraints: []*structs.Constraint{ + implicitIdentityClientVersionConstraint()}, Tasks: []*structs.Task{{ Identities: []*structs.WorkloadIdentity{{ Name: "vault_default", @@ -405,6 +413,8 @@ func Test_jobImplicitIndentitiesHook_Mutate_vault(t *testing.T) { }, expectedOutputJob: &structs.Job{ TaskGroups: []*structs.TaskGroup{{ + Constraints: []*structs.Constraint{ + implicitIdentityClientVersionConstraint()}, Tasks: []*structs.Task{{ Identities: []*structs.WorkloadIdentity{{ Name: "vault_default", @@ -444,6 +454,8 @@ func Test_jobImplicitIndentitiesHook_Mutate_vault(t *testing.T) { }, expectedOutputJob: &structs.Job{ TaskGroups: []*structs.TaskGroup{{ + Constraints: []*structs.Constraint{ + implicitIdentityClientVersionConstraint()}, Tasks: []*structs.Task{{ Identities: []*structs.WorkloadIdentity{{ Name: "vault_other", diff --git a/scheduler/feasible_test.go b/scheduler/feasible_test.go index e4d07910bf96..9c4990ae4382 100644 --- a/scheduler/feasible_test.go +++ b/scheduler/feasible_test.go @@ -1315,6 +1315,26 @@ func TestCheckSemverConstraint(t *testing.T) { lVal: "1.7.0-alpha1", rVal: ">= 1.6.0-beta1", result: true, }, + { + name: "Prereleases of same version handled according to semver", + lVal: "1.7.0-beta", rVal: ">= 1.7.0", + result: false, + }, + { + name: "Prereleases constraints allow GA version according to semver", + lVal: "1.7.0", rVal: ">= 1.7.0-dev", + result: true, + }, + { + name: "Prereleases constraints allow beta according to semver", + lVal: "1.7.0-beta.1", rVal: ">= 1.7.0-a", + result: true, + }, + { + name: "Prereleases constraints allow RC version according to semver", + lVal: "1.7.0-rc.1", rVal: ">= 1.7.0-dev", + result: true, + }, { name: "Meta is ignored according to semver", lVal: "1.3.0-beta1+ent", rVal: "= 1.3.0-beta1",