From 8dc5ffea9ecb2bd95b1cf8529e094260d329eb79 Mon Sep 17 00:00:00 2001 From: Vishnu U Date: Fri, 13 Mar 2026 01:38:48 +0530 Subject: [PATCH 1/9] feat: Add additional target groups fields, update strategy block, periodic config block etc. to nomad job resource and data source This commit introduces new computed fields to the `nomad_job` resource and data source to expose the deployment state of task groups, update_strategy, periodic_config and other attributes. This includes fields such as `placed_canaries`, `desired_total`, `healthy_allocs`, and others. To prevent perpetual diffs caused by these server-set values, the resource's `CustomizeDiff` logic has been updated to merge the existing deployment state from the Terraform state into the planned `task_groups`. This ensures that plans remain clean after the initial apply. New acceptance tests have been added to verify that these deployment state attributes are correctly populated for both the resource and the data source. The existing service deployment test has also been stabilized by setting `detach = true`. --- nomad/datasource_nomad_job.go | 71 ++++-- nomad/datasource_nomad_job_test.go | 12 + nomad/resource_job.go | 391 ++++++++++++++++++++++++++++- nomad/resource_job_test.go | 143 ++++++++++- 4 files changed, 579 insertions(+), 38 deletions(-) diff --git a/nomad/datasource_nomad_job.go b/nomad/datasource_nomad_job.go index 3b984c6c..f1a856e8 100644 --- a/nomad/datasource_nomad_job.go +++ b/nomad/datasource_nomad_job.go @@ -132,6 +132,43 @@ func dataSourceJob() *schema.Resource { }, }, }, + "update_strategy": { + Description: "Job Update Strategy", + Computed: true, + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "stagger": { + Type: schema.TypeString, + Computed: true, + }, + "max_parallel": { + Type: schema.TypeInt, + Computed: true, + }, + "health_check": { + Type: schema.TypeString, + Computed: true, + }, + "min_healthy_time": { + Type: schema.TypeString, + Computed: true, + }, + "healthy_deadline": { + Type: schema.TypeString, + Computed: true, + }, + "auto_revert": { + Type: schema.TypeBool, + Computed: true, + }, + "canary": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, "periodic_config": { Description: "Job Periodic Configuration", Computed: true, @@ -204,29 +241,23 @@ func dataSourceJobRead(d *schema.ResourceData, meta interface{}) error { d.Set("stop", job.Stop) d.Set("priority", job.Priority) d.Set("parent_id", job.ParentID) - d.Set("task_groups", jobTaskGroupsRaw(job.TaskGroups)) + + // Fetch the latest deployment for task group deployment status. + var deploymentTGs map[string]*api.DeploymentState + deploys, _, err := client.Jobs().Deployments(id, false, &api.QueryOptions{ + Namespace: ns, + }) + if err != nil { + log.Printf("[WARN] error listing deployments for Job %q: %s", id, err) + } else if len(deploys) > 0 { + deploymentTGs = deploys[0].TaskGroups + } + d.Set("task_groups", jobTaskGroupsRaw(job.TaskGroups, deploymentTGs)) d.Set("stable", job.Stable) d.Set("all_at_once", job.AllAtOnce) d.Set("constraints", job.Constraints) - if job.Periodic != nil { - periodic := map[string]interface{}{} - if job.Periodic.Enabled != nil { - periodic["enabled"] = *job.Periodic.Enabled - } - if job.Periodic.Spec != nil { - periodic["spec"] = *job.Periodic.Spec - } - if job.Periodic.SpecType != nil { - periodic["spec_type"] = *job.Periodic.SpecType - } - if job.Periodic.ProhibitOverlap != nil { - periodic["prohibit_overlap"] = *job.Periodic.ProhibitOverlap - } - if job.Periodic.TimeZone != nil { - periodic["timezone"] = *job.Periodic.TimeZone - } - d.Set("periodic_config", []map[string]interface{}{periodic}) - } + d.Set("update_strategy", flattenUpdateStrategy(job.Update)) + d.Set("periodic_config", flattenPeriodicConfig(job.Periodic)) return nil } diff --git a/nomad/datasource_nomad_job_test.go b/nomad/datasource_nomad_job_test.go index 4bb455e2..6e360065 100644 --- a/nomad/datasource_nomad_job_test.go +++ b/nomad/datasource_nomad_job_test.go @@ -32,6 +32,18 @@ func TestAccDataSourceNomadJob_Basic(t *testing.T) { "data.nomad_job.test-job", "priority", "50"), resource.TestCheckResourceAttr( "data.nomad_job.test-job", "namespace", "default"), + resource.TestCheckResourceAttr( + "data.nomad_job.test-job", "update_strategy.#", "1"), + resource.TestCheckResourceAttr( + "data.nomad_job.test-job", "update_strategy.0.max_parallel", "2"), + resource.TestCheckResourceAttr( + "data.nomad_job.test-job", "update_strategy.0.min_healthy_time", "11s"), + resource.TestCheckResourceAttr( + "data.nomad_job.test-job", "update_strategy.0.healthy_deadline", "6m0s"), + resource.TestCheckResourceAttr( + "data.nomad_job.test-job", "update_strategy.0.auto_revert", "true"), + resource.TestCheckResourceAttr( + "data.nomad_job.test-job", "update_strategy.0.canary", "1"), ), }, }, diff --git a/nomad/resource_job.go b/nomad/resource_job.go index 38f33775..3e28c32c 100644 --- a/nomad/resource_job.go +++ b/nomad/resource_job.go @@ -16,7 +16,7 @@ import ( "github.com/hashicorp/nomad/api" "github.com/hashicorp/nomad/jobspec2" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "golang.org/x/exp/maps" @@ -153,6 +153,165 @@ func resourceJob() *schema.Resource { Type: schema.TypeString, }, + "status_description": { + Description: "The status description of the job.", + Computed: true, + Type: schema.TypeString, + }, + + "version": { + Description: "The version of the job.", + Computed: true, + Type: schema.TypeInt, + }, + + "submit_time": { + Description: "The time the job was submitted.", + Computed: true, + Type: schema.TypeInt, + }, + + "create_index": { + Description: "The creation index of the job.", + Computed: true, + Type: schema.TypeInt, + }, + + "stop": { + Description: "Whether the job is stopped.", + Computed: true, + Type: schema.TypeBool, + }, + + "priority": { + Description: "The priority of the job for scheduling and resource access.", + Computed: true, + Type: schema.TypeInt, + }, + + "parent_id": { + Description: "The parent job ID, if applicable.", + Computed: true, + Type: schema.TypeString, + }, + + "stable": { + Description: "Whether the job is stable.", + Computed: true, + Type: schema.TypeBool, + }, + + "all_at_once": { + Description: "Whether the scheduler can make partial placements on oversubscribed nodes.", + Computed: true, + Type: schema.TypeBool, + }, + + "constraints": { + Description: "The job constraints.", + Computed: true, + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ltarget": { + Description: "The attribute being constrained.", + Type: schema.TypeString, + Computed: true, + }, + "rtarget": { + Description: "The constraint value.", + Type: schema.TypeString, + Computed: true, + }, + "operand": { + Description: "The operator used to compare the attribute to the constraint.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + "update_strategy": { + Description: "The job's update strategy for rolling updates and canary deployments.", + Computed: true, + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "stagger": { + Description: "Delay between migrating job allocations off cluster nodes marked for draining.", + Type: schema.TypeString, + Computed: true, + }, + "max_parallel": { + Description: "Number of task groups that can be updated at the same time.", + Type: schema.TypeInt, + Computed: true, + }, + "health_check": { + Description: "Type of mechanism in which allocations health is determined.", + Type: schema.TypeString, + Computed: true, + }, + "min_healthy_time": { + Description: "Minimum time the allocation must be in the healthy state.", + Type: schema.TypeString, + Computed: true, + }, + "healthy_deadline": { + Description: "Deadline in which the allocation must be marked as healthy.", + Type: schema.TypeString, + Computed: true, + }, + "auto_revert": { + Description: "Whether the job should auto-revert to the last stable job on deployment failure.", + Type: schema.TypeBool, + Computed: true, + }, + "canary": { + Description: "Number of canary jobs that need to reach healthy status before unblocking rolling updates.", + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + + "periodic_config": { + Description: "The job's periodic configuration for time-based scheduling.", + Computed: true, + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enabled": { + Description: "Whether periodic scheduling is enabled for this job.", + Type: schema.TypeBool, + Computed: true, + }, + "spec": { + Description: "The cron spec for the periodic job.", + Type: schema.TypeString, + Computed: true, + }, + "spec_type": { + Description: "The type of the periodic spec.", + Type: schema.TypeString, + Computed: true, + }, + "prohibit_overlap": { + Description: "Whether the job should wait until previous instances have completed.", + Type: schema.TypeBool, + Computed: true, + }, + "timezone": { + Description: "Time zone to evaluate the next launch interval against.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "region": { Description: "The target region for the job, as derived from the jobspec.", Computed: true, @@ -218,6 +377,39 @@ func taskGroupSchema() *schema.Schema { Computed: true, Type: schema.TypeInt, }, + "placed_canaries": { + Computed: true, + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "auto_revert": { + Computed: true, + Type: schema.TypeBool, + }, + "promoted": { + Computed: true, + Type: schema.TypeBool, + }, + "desired_canaries": { + Computed: true, + Type: schema.TypeInt, + }, + "desired_total": { + Computed: true, + Type: schema.TypeInt, + }, + "placed_allocs": { + Computed: true, + Type: schema.TypeInt, + }, + "healthy_allocs": { + Computed: true, + Type: schema.TypeInt, + }, + "unhealthy_allocs": { + Computed: true, + Type: schema.TypeInt, + }, // "scaling": { // Computed: true, // Type: schema.TypeList, @@ -422,7 +614,7 @@ func resourceJobRegister(d *schema.ResourceData, meta interface{}) error { // if they result in a deployment, monitors that deployment until completion. func monitorDeployment(client *api.Client, timeout time.Duration, namespace string, initialEvalID string) (*api.Deployment, error) { - stateConf := &resource.StateChangeConf{ + stateConf := &retry.StateChangeConf{ Pending: []string{MonitoringEvaluation}, Target: []string{EvaluationComplete}, Refresh: evaluationStateRefreshFunc(client, namespace, initialEvalID), @@ -431,7 +623,7 @@ func monitorDeployment(client *api.Client, timeout time.Duration, namespace stri MinTimeout: 3 * time.Second, } - state, err := stateConf.WaitForState() + state, err := stateConf.WaitForStateContext(context.Background()) if err != nil { return nil, fmt.Errorf("error waiting for evaluation: %s", err) } @@ -442,7 +634,7 @@ func monitorDeployment(client *api.Client, timeout time.Duration, namespace stri return nil, nil } - stateConf = &resource.StateChangeConf{ + stateConf = &retry.StateChangeConf{ Pending: []string{MonitoringDeployment}, Target: []string{DeploymentSuccessful}, Refresh: deploymentStateRefreshFunc(client, namespace, evaluation.DeploymentID), @@ -451,16 +643,16 @@ func monitorDeployment(client *api.Client, timeout time.Duration, namespace stri MinTimeout: 5 * time.Second, } - state, err = stateConf.WaitForState() + state, err = stateConf.WaitForStateContext(context.Background()) if err != nil { return nil, fmt.Errorf("error waiting for evaluation: %s", err) } return state.(*api.Deployment), nil } -// evaluationStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch +// evaluationStateRefreshFunc returns a retry.StateRefreshFunc that is used to watch // the evaluation(s) from a job create/update -func evaluationStateRefreshFunc(client *api.Client, namespace string, initialEvalID string) resource.StateRefreshFunc { +func evaluationStateRefreshFunc(client *api.Client, namespace string, initialEvalID string) retry.StateRefreshFunc { // evalID is the evaluation that we are currently monitoring. This will change // along with follow-up evaluations. @@ -498,9 +690,9 @@ func evaluationStateRefreshFunc(client *api.Client, namespace string, initialEva } } -// deploymentStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch +// deploymentStateRefreshFunc returns a retry.StateRefreshFunc that is used to watch // the deployment from a job create/update -func deploymentStateRefreshFunc(client *api.Client, namespace string, deploymentID string) resource.StateRefreshFunc { +func deploymentStateRefreshFunc(client *api.Client, namespace string, deploymentID string) retry.StateRefreshFunc { return func() (interface{}, string, error) { // monitor the deployment var state string @@ -588,7 +780,6 @@ func resourceJobRead(d *schema.ResourceData, meta interface{}) error { d.Set("type", job.Type) d.Set("region", job.Region) d.Set("datacenters", job.Datacenters) - d.Set("task_groups", jobTaskGroupsRaw(job.TaskGroups)) d.Set("namespace", job.Namespace) if job.JobModifyIndex != nil { d.Set("modify_index", strconv.FormatUint(*job.JobModifyIndex, 10)) @@ -596,6 +787,28 @@ func resourceJobRead(d *schema.ResourceData, meta interface{}) error { d.Set("modify_index", "0") } d.Set("status", job.Status) + d.Set("status_description", job.StatusDescription) + d.Set("version", job.Version) + d.Set("submit_time", job.SubmitTime) + d.Set("create_index", job.CreateIndex) + d.Set("stop", job.Stop) + d.Set("priority", job.Priority) + d.Set("parent_id", job.ParentID) + d.Set("stable", job.Stable) + d.Set("all_at_once", job.AllAtOnce) + d.Set("constraints", flattenJobConstraints(job.Constraints)) + d.Set("update_strategy", flattenUpdateStrategy(job.Update)) + d.Set("periodic_config", flattenPeriodicConfig(job.Periodic)) + + // Fetch the latest deployment for task group deployment status. + var deploymentTGs map[string]*api.DeploymentState + deploys, _, err := client.Jobs().Deployments(id, false, opts) + if err != nil { + log.Printf("[WARN] error listing deployments for Job %q: %s", id, err) + } else if len(deploys) > 0 { + deploymentTGs = deploys[0].TaskGroups + } + d.Set("task_groups", jobTaskGroupsRaw(job.TaskGroups, deploymentTGs)) if d.Get("read_allocation_ids").(bool) { allocStubs, _, err := client.Jobs().Allocations(id, false, opts) @@ -682,6 +895,18 @@ func resourceJobCustomizeDiff(_ context.Context, d *schema.ResourceDiff, meta in d.SetNewComputed("deployment_id") d.SetNewComputed("deployment_status") d.SetNewComputed("status") + d.SetNewComputed("status_description") + d.SetNewComputed("version") + d.SetNewComputed("submit_time") + d.SetNewComputed("create_index") + d.SetNewComputed("stop") + d.SetNewComputed("priority") + d.SetNewComputed("parent_id") + d.SetNewComputed("stable") + d.SetNewComputed("all_at_once") + d.SetNewComputed("constraints") + d.SetNewComputed("update_strategy") + d.SetNewComputed("periodic_config") return nil } @@ -734,6 +959,7 @@ func resourceJobCustomizeDiff(_ context.Context, d *schema.ResourceDiff, meta in d.SetNew("region", job.Region) d.SetNew("datacenters", job.Datacenters) d.SetNew("status", job.Status) + d.SetNew("periodic_config", flattenPeriodicConfig(job.Periodic)) // If the identity has changed and the config asks us to deregister on identity // change then the id field "forces new resource". @@ -777,11 +1003,91 @@ func resourceJobCustomizeDiff(_ context.Context, d *schema.ResourceDiff, meta in // similarly, we won't know the allocation ids until after the job registration eval d.SetNewComputed("allocation_ids") - d.SetNew("task_groups", jobTaskGroupsRaw(job.TaskGroups)) + plannedTaskGroups := jobTaskGroupsRaw(job.TaskGroups, nil) + plannedTaskGroups = mergeDeploymentStateFromState(plannedTaskGroups, d.Get("task_groups")) + d.SetNew("task_groups", plannedTaskGroups) return nil } +func mergeDeploymentStateFromState(planned []interface{}, state interface{}) []interface{} { + stateList, ok := state.([]interface{}) + if !ok || len(stateList) == 0 { + return planned + } + + stateByName := make(map[string]map[string]interface{}, len(stateList)) + for _, item := range stateList { + tgM, ok := item.(map[string]interface{}) + if !ok { + continue + } + name, ok := tgM["name"].(string) + if !ok || name == "" { + continue + } + stateByName[name] = tgM + } + + keys := []string{ + "placed_canaries", + "auto_revert", + "promoted", + "desired_canaries", + "desired_total", + "placed_allocs", + "healthy_allocs", + "unhealthy_allocs", + } + + for _, item := range planned { + plannedTG, ok := item.(map[string]interface{}) + if !ok { + continue + } + name, ok := plannedTG["name"].(string) + if !ok || name == "" { + continue + } + stateTG, ok := stateByName[name] + if !ok { + continue + } + for _, key := range keys { + if v, ok := stateTG[key]; ok { + plannedTG[key] = v + } + } + } + + return planned +} + +func flattenPeriodicConfig(periodic *api.PeriodicConfig) []map[string]interface{} { + if periodic == nil { + return nil + } + + flattened := map[string]interface{}{} + if periodic.Enabled != nil { + flattened["enabled"] = *periodic.Enabled + } + if periodic.Spec != nil { + flattened["spec"] = *periodic.Spec + } + if periodic.SpecType != nil { + flattened["spec_type"] = *periodic.SpecType + } + if periodic.ProhibitOverlap != nil { + flattened["prohibit_overlap"] = *periodic.ProhibitOverlap + } + if periodic.TimeZone != nil { + flattened["timezone"] = *periodic.TimeZone + } + + return []map[string]interface{}{flattened} +} + func parseJobParserConfig(d ResourceFieldGetter) (JobParserConfig, error) { config := JobParserConfig{} @@ -910,14 +1216,16 @@ func parseHCL2Jobspec(raw string, config HCL2JobParserConfig) (*api.Job, error) }) } -func jobTaskGroupsRaw(tgs []*api.TaskGroup) []interface{} { +func jobTaskGroupsRaw(tgs []*api.TaskGroup, deploymentTGs map[string]*api.DeploymentState) []interface{} { ret := make([]interface{}, 0, len(tgs)) for _, tg := range tgs { tgM := make(map[string]interface{}) + tgName := "" if tg.Name != nil { - tgM["name"] = *tg.Name + tgName = *tg.Name + tgM["name"] = tgName } else { tgM["name"] = "" } @@ -978,12 +1286,69 @@ func jobTaskGroupsRaw(tgs []*api.TaskGroup) []interface{} { tgM["volumes"] = volumesI + // Populate deployment state fields for this task group. + if ds, ok := deploymentTGs[tgName]; ok && ds != nil { + canaries := make([]interface{}, len(ds.PlacedCanaries)) + for i, c := range ds.PlacedCanaries { + canaries[i] = c + } + tgM["placed_canaries"] = canaries + tgM["auto_revert"] = ds.AutoRevert + tgM["promoted"] = ds.Promoted + tgM["desired_canaries"] = ds.DesiredCanaries + tgM["desired_total"] = ds.DesiredTotal + tgM["placed_allocs"] = ds.PlacedAllocs + tgM["healthy_allocs"] = ds.HealthyAllocs + tgM["unhealthy_allocs"] = ds.UnhealthyAllocs + } + ret = append(ret, tgM) } return ret } +func flattenJobConstraints(constraints []*api.Constraint) []map[string]interface{} { + result := make([]map[string]interface{}, 0, len(constraints)) + for _, c := range constraints { + result = append(result, map[string]interface{}{ + "ltarget": c.LTarget, + "rtarget": c.RTarget, + "operand": c.Operand, + }) + } + return result +} + +func flattenUpdateStrategy(update *api.UpdateStrategy) []map[string]interface{} { + if update == nil { + return nil + } + u := map[string]interface{}{} + if update.Stagger != nil { + u["stagger"] = update.Stagger.String() + } + if update.MaxParallel != nil { + u["max_parallel"] = *update.MaxParallel + } + if update.HealthCheck != nil { + u["health_check"] = *update.HealthCheck + } + if update.MinHealthyTime != nil { + u["min_healthy_time"] = update.MinHealthyTime.String() + } + if update.HealthyDeadline != nil { + u["healthy_deadline"] = update.HealthyDeadline.String() + } + if update.AutoRevert != nil { + u["auto_revert"] = *update.AutoRevert + } + if update.Canary != nil { + u["canary"] = *update.Canary + } + return []map[string]interface{}{u} +} + // jobspecDiffSuppress is the DiffSuppressFunc used by the schema to // check if two jobspecs are equal. func jobspecDiffSuppress(k, old, new string, d *schema.ResourceData) bool { diff --git a/nomad/resource_job_test.go b/nomad/resource_job_test.go index dc83ad8a..e77f0b1b 100644 --- a/nomad/resource_job_test.go +++ b/nomad/resource_job_test.go @@ -57,6 +57,27 @@ func TestResourceJob_service(t *testing.T) { }) } +func TestResourceJob_serviceDeploymentStateFast(t *testing.T) { + resourceName := "nomad_job.service_deployment_state" + r.Test(t, r.TestCase{ + Providers: testProviders, + PreCheck: func() { testAccPreCheck(t) }, + Steps: []r.TestStep{ + { + Config: testResourceJob_serviceDeploymentStateFast, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "task_groups.#", "1"), + resource.TestCheckResourceAttrSet(resourceName, "task_groups.0.desired_total"), + resource.TestCheckResourceAttrSet(resourceName, "task_groups.0.placed_allocs"), + resource.TestCheckResourceAttrSet(resourceName, "task_groups.0.healthy_allocs"), + resource.TestCheckResourceAttrSet(resourceName, "task_groups.0.unhealthy_allocs"), + ), + }, + }, + CheckDestroy: testResourceJob_checkDestroy("foo-service-deployment-state"), + }) +} + func TestResourceJob_namespace(t *testing.T) { r.Test(t, r.TestCase{ Providers: testProviders, @@ -217,6 +238,8 @@ func TestResourceJob_serviceWithoutDeployment(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "deployment_id", ""), resource.TestCheckResourceAttr(resourceName, "deployment_status", ""), + resource.TestCheckResourceAttr(resourceName, "update_strategy.#", "1"), + resource.TestCheckResourceAttr(resourceName, "update_strategy.0.max_parallel", "0"), ), }, }, @@ -224,6 +247,27 @@ func TestResourceJob_serviceWithoutDeployment(t *testing.T) { }) } +func TestResourceJob_periodicConfig(t *testing.T) { + resourceName := "nomad_job.periodic" + r.Test(t, r.TestCase{ + Providers: testProviders, + PreCheck: func() { testAccPreCheck(t) }, + Steps: []r.TestStep{ + { + Config: testResourceJob_periodicConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "periodic_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "periodic_config.0.enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "periodic_config.0.spec", "*/1 * * * * *"), + resource.TestCheckResourceAttr(resourceName, "periodic_config.0.prohibit_overlap", "true"), + resource.TestCheckResourceAttr(resourceName, "periodic_config.0.timezone", "UTC"), + ), + }, + }, + CheckDestroy: testResourceJob_checkDestroy("foo-periodic"), + }) +} + func TestResourceJob_multiregion(t *testing.T) { r.Test(t, r.TestCase{ Providers: testProviders, @@ -922,6 +966,27 @@ resource "nomad_job" "test" { } ` +var testResourceJob_serviceDeploymentStateFast = ` +resource "nomad_job" "service_deployment_state" { + detach = true + jobspec = < Date: Fri, 13 Mar 2026 17:33:09 +0530 Subject: [PATCH 2/9] nomad_job: add support for task group update_strategy fields --- nomad/datasource_nomad_job.go | 38 +-------- nomad/datasource_nomad_job_test.go | 12 ++- nomad/resource_job.go | 121 ++++++++++++++++++----------- 3 files changed, 86 insertions(+), 85 deletions(-) diff --git a/nomad/datasource_nomad_job.go b/nomad/datasource_nomad_job.go index f1a856e8..9bc2cbab 100644 --- a/nomad/datasource_nomad_job.go +++ b/nomad/datasource_nomad_job.go @@ -132,43 +132,7 @@ func dataSourceJob() *schema.Resource { }, }, }, - "update_strategy": { - Description: "Job Update Strategy", - Computed: true, - Type: schema.TypeList, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "stagger": { - Type: schema.TypeString, - Computed: true, - }, - "max_parallel": { - Type: schema.TypeInt, - Computed: true, - }, - "health_check": { - Type: schema.TypeString, - Computed: true, - }, - "min_healthy_time": { - Type: schema.TypeString, - Computed: true, - }, - "healthy_deadline": { - Type: schema.TypeString, - Computed: true, - }, - "auto_revert": { - Type: schema.TypeBool, - Computed: true, - }, - "canary": { - Type: schema.TypeInt, - Computed: true, - }, - }, - }, - }, + "update_strategy": updateStrategySchema(), "periodic_config": { Description: "Job Periodic Configuration", Computed: true, diff --git a/nomad/datasource_nomad_job_test.go b/nomad/datasource_nomad_job_test.go index 6e360065..8f8628e8 100644 --- a/nomad/datasource_nomad_job_test.go +++ b/nomad/datasource_nomad_job_test.go @@ -37,13 +37,17 @@ func TestAccDataSourceNomadJob_Basic(t *testing.T) { resource.TestCheckResourceAttr( "data.nomad_job.test-job", "update_strategy.0.max_parallel", "2"), resource.TestCheckResourceAttr( - "data.nomad_job.test-job", "update_strategy.0.min_healthy_time", "11s"), + "data.nomad_job.test-job", "task_groups.0.update_strategy.#", "1"), resource.TestCheckResourceAttr( - "data.nomad_job.test-job", "update_strategy.0.healthy_deadline", "6m0s"), + "data.nomad_job.test-job", "task_groups.0.update_strategy.0.max_parallel", "2"), resource.TestCheckResourceAttr( - "data.nomad_job.test-job", "update_strategy.0.auto_revert", "true"), + "data.nomad_job.test-job", "task_groups.0.update_strategy.0.min_healthy_time", "11s"), resource.TestCheckResourceAttr( - "data.nomad_job.test-job", "update_strategy.0.canary", "1"), + "data.nomad_job.test-job", "task_groups.0.update_strategy.0.healthy_deadline", "6m0s"), + resource.TestCheckResourceAttr( + "data.nomad_job.test-job", "task_groups.0.update_strategy.0.auto_revert", "true"), + resource.TestCheckResourceAttr( + "data.nomad_job.test-job", "task_groups.0.update_strategy.0.canary", "1"), ), }, }, diff --git a/nomad/resource_job.go b/nomad/resource_job.go index 3e28c32c..cb369cb1 100644 --- a/nomad/resource_job.go +++ b/nomad/resource_job.go @@ -232,50 +232,7 @@ func resourceJob() *schema.Resource { }, }, - "update_strategy": { - Description: "The job's update strategy for rolling updates and canary deployments.", - Computed: true, - Type: schema.TypeList, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "stagger": { - Description: "Delay between migrating job allocations off cluster nodes marked for draining.", - Type: schema.TypeString, - Computed: true, - }, - "max_parallel": { - Description: "Number of task groups that can be updated at the same time.", - Type: schema.TypeInt, - Computed: true, - }, - "health_check": { - Description: "Type of mechanism in which allocations health is determined.", - Type: schema.TypeString, - Computed: true, - }, - "min_healthy_time": { - Description: "Minimum time the allocation must be in the healthy state.", - Type: schema.TypeString, - Computed: true, - }, - "healthy_deadline": { - Description: "Deadline in which the allocation must be marked as healthy.", - Type: schema.TypeString, - Computed: true, - }, - "auto_revert": { - Description: "Whether the job should auto-revert to the last stable job on deployment failure.", - Type: schema.TypeBool, - Computed: true, - }, - "canary": { - Description: "Number of canary jobs that need to reach healthy status before unblocking rolling updates.", - Type: schema.TypeInt, - Computed: true, - }, - }, - }, - }, + "update_strategy": updateStrategySchema(), "periodic_config": { Description: "The job's periodic configuration for time-based scheduling.", @@ -377,6 +334,7 @@ func taskGroupSchema() *schema.Schema { Computed: true, Type: schema.TypeInt, }, + "update_strategy": updateStrategySchema(), "placed_canaries": { Computed: true, Type: schema.TypeList, @@ -495,6 +453,53 @@ func taskGroupSchema() *schema.Schema { } } +func updateStrategySchema() *schema.Schema { + return &schema.Schema{ + Description: "The update strategy for rolling updates and canary deployments.", + Computed: true, + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "stagger": { + Description: "Delay between migrating job allocations off cluster nodes marked for draining.", + Type: schema.TypeString, + Computed: true, + }, + "max_parallel": { + Description: "Number of task groups that can be updated at the same time.", + Type: schema.TypeInt, + Computed: true, + }, + "health_check": { + Description: "Type of mechanism in which allocations health is determined.", + Type: schema.TypeString, + Computed: true, + }, + "min_healthy_time": { + Description: "Minimum time the allocation must be in the healthy state.", + Type: schema.TypeString, + Computed: true, + }, + "healthy_deadline": { + Description: "Deadline in which the allocation must be marked as healthy.", + Type: schema.TypeString, + Computed: true, + }, + "auto_revert": { + Description: "Whether the job should auto-revert to the last stable job on deployment failure.", + Type: schema.TypeBool, + Computed: true, + }, + "canary": { + Description: "Number of canary jobs that need to reach healthy status before unblocking rolling updates.", + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + } +} + // JobParserConfig stores configuration options for how to parse the jobspec. type JobParserConfig struct { JSON JSONJobParserConfig @@ -1003,6 +1008,7 @@ func resourceJobCustomizeDiff(_ context.Context, d *schema.ResourceDiff, meta in // similarly, we won't know the allocation ids until after the job registration eval d.SetNewComputed("allocation_ids") + canonicalizeTaskGroupUpdateStrategies(job) plannedTaskGroups := jobTaskGroupsRaw(job.TaskGroups, nil) plannedTaskGroups = mergeDeploymentStateFromState(plannedTaskGroups, d.Get("task_groups")) d.SetNew("task_groups", plannedTaskGroups) @@ -1010,6 +1016,30 @@ func resourceJobCustomizeDiff(_ context.Context, d *schema.ResourceDiff, meta in return nil } +func canonicalizeTaskGroupUpdateStrategies(job *api.Job) { + if job == nil { + return + } + + for _, taskGroup := range job.TaskGroups { + if taskGroup == nil { + continue + } + + if jobUpdate, taskGroupUpdate := job.Update != nil, taskGroup.Update != nil; jobUpdate && taskGroupUpdate { + merged := job.Update.Copy() + merged.Merge(taskGroup.Update) + taskGroup.Update = merged + } else if jobUpdate && !job.Update.Empty() { + taskGroup.Update = job.Update.Copy() + } + + if taskGroup.Update != nil { + taskGroup.Update.Canonicalize() + } + } +} + func mergeDeploymentStateFromState(planned []interface{}, state interface{}) []interface{} { stateList, ok := state.([]interface{}) if !ok || len(stateList) == 0 { @@ -1239,6 +1269,9 @@ func jobTaskGroupsRaw(tgs []*api.TaskGroup, deploymentTGs map[string]*api.Deploy } else { tgM["meta"] = make(map[string]interface{}) } + if tg.Update != nil { + tgM["update_strategy"] = flattenUpdateStrategy(tg.Update) + } tasksI := make([]interface{}, 0, len(tg.Tasks)) for _, task := range tg.Tasks { From ac4ec3b1c57ba779b7373b4f2641a6c517f13751 Mon Sep 17 00:00:00 2001 From: Vishnu U Date: Fri, 13 Mar 2026 17:38:35 +0530 Subject: [PATCH 3/9] chore: update CHANGELOG.md for GH-585 adding additional fields related to job and task group --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d0dc540..1f7a0eeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ IMPROVEMENTS: * data source/nomad_node_pool: Added the `node_identity_ttl` argument ([#569](https://github.com/hashicorp/terraform-provider-nomad/pull/569)) * resource/nomad_node_pool: Added the `node_identity_ttl` argument to configure the node identity TTL for nodes in the pool ([#569](https://github.com/hashicorp/terraform-provider-nomad/pull/569)) * resource/nomad_quota_specification: add support for all `QuotaResources` fields including `secrets_mb`, `devices`, `numa`, and `storage` ([#584](https://github.com/hashicorp/terraform-provider-nomad/pull/584)) +* resource/nomad_job, data source/nomad_job: Added and aligned computed job and task group attributes, including periodic and update strategy fields, to match the `nomad_job` implementation and documentation ([#585](https://github.com/hashicorp/terraform-provider-nomad/pull/585)) ## 2.5.2 (November 13, 2025) From 82a51dc518907235adbcd916c3a77f49920c7007 Mon Sep 17 00:00:00 2001 From: Vishnu U Date: Fri, 13 Mar 2026 17:45:25 +0530 Subject: [PATCH 4/9] docs: update newly added fields in nomad_job resource and datasource --- website/docs/d/job.html.markdown | 60 ++++++++++++++++-------- website/docs/r/job.html.markdown | 78 +++++++++++++++++++++++++++++++- 2 files changed, 118 insertions(+), 20 deletions(-) diff --git a/website/docs/d/job.html.markdown b/website/docs/d/job.html.markdown index 57662fe0..9ee8299d 100644 --- a/website/docs/d/job.html.markdown +++ b/website/docs/d/job.html.markdown @@ -49,32 +49,56 @@ The following attributes are exported: * `stop`: `(boolean)` Job enabled status. * `priority`: `(integer)` Used for the prioritization of scheduling and resource access. * `parent_id`: `(string)` Job's parent ID. -* `task_groups`: `(list of maps)` A list of of the job's task groups. - * `placed_canaries`: `(string)` - * `auto_revert`: `(boolean)` - * `promoted`: `(boolean)` - * `desired_canaries`: `(integer)` - * `desired_total`: `(integer)` - * `placed_alloc`: `(integer)` - * `healthy_alloc`: `(integer)` - * `unhealthy_alloc`: `(integer)` +* `task_groups`: `(list of maps)` A list of the job's task groups. + * `name`: `(string)` Task group name. + * `count`: `(integer)` Task group count. + * `update_strategy`: `(list of maps)` Effective update strategy for the task group. + * `stagger`: `(string)` Delay between migrating job allocations off cluster nodes marked for draining. + * `max_parallel`: `(integer)` Number of task groups that can be updated at the same time. + * `health_check`: `(string)` Type of mechanism in which allocations health is determined. + * `min_healthy_time`: `(string)` Minimum time the allocation must be in the healthy state. + * `healthy_deadline`: `(string)` Deadline in which the allocation must be marked as healthy. + * `auto_revert`: `(boolean)` Whether the job should auto-revert to the last stable job on deployment failure. + * `canary`: `(integer)` Number of canary jobs that need to reach healthy status before unblocking rolling updates. + * `placed_canaries`: `(list of strings)` Allocations placed as canaries for the task group. + * `auto_revert`: `(boolean)` Whether the latest deployment for the task group is marked for auto-revert. + * `promoted`: `(boolean)` Whether the canary deployment has been promoted. + * `desired_canaries`: `(integer)` Desired number of canaries. + * `desired_total`: `(integer)` Desired total number of allocations. + * `placed_allocs`: `(integer)` Number of placed allocations. + * `healthy_allocs`: `(integer)` Number of healthy allocations. + * `unhealthy_allocs`: `(integer)` Number of unhealthy allocations. + * `task`: `(list of maps)` Tasks in the task group. + * `name`: `(string)` Task name. + * `driver`: `(string)` Task driver. + * `meta`: `(map of strings)` Task metadata. + * `volume_mounts`: `(list of maps)` Task volume mounts. + * `volume`: `(string)` Volume name. + * `destination`: `(string)` Destination path inside the task. + * `read_only`: `(boolean)` Whether the volume mount is read-only. + * `volumes`: `(list of maps)` Volume requests for the task group. + * `name`: `(string)` Volume name. + * `type`: `(string)` Volume type. + * `read_only`: `(boolean)` Whether the volume is read-only. + * `source`: `(string)` Volume source. + * `meta`: `(map of strings)` Task group metadata. * `stable`: `(boolean)` Job stability status. * `all_at_once`: `(boolean)` If the scheduler can make partial placements on oversubscribed nodes. -* `contraints`: `(list of maps)` Job constraints. +* `constraints`: `(list of maps)` Job constraints. * `ltarget`: `(string)` Attribute being constrained. * `rtarget`: `(string)` Constraint value. * `operand`: `(string)` Operator used to compare the attribute to the constraint. -* `update_strategy`: `(list of maps)` Job's update strategy which controls rolling updates and canary deployments. +* `update_strategy`: `(list of maps)` Job-level update strategy returned by Nomad. * `stagger`: `(string)` Delay between migrating job allocations off cluster nodes marked for draining. * `max_parallel`: `(integer)` Number of task groups that can be updated at the same time. * `health_check`: `(string)` Type of mechanism in which allocations health is determined. - * `min_healthy_time`: `(string)` Minimum time the job allocation must be in the healthy state. - * `healthy_deadline`: `(string)` Deadline in which the allocation must be marked as healthy after which the allocation is automatically transitioned to unhealthy. - * `auto_revert`: `(boolean)` Specifies if the job should auto-revert to the last stable job on deployment failure. + * `min_healthy_time`: `(string)` Minimum time the allocation must be in the healthy state. + * `healthy_deadline`: `(string)` Deadline in which the allocation must be marked as healthy. + * `auto_revert`: `(boolean)` Whether the job should auto-revert to the last stable job on deployment failure. * `canary`: `(integer)` Number of canary jobs that need to reach healthy status before unblocking rolling updates. -* `periodic_config`: `(list of maps)` Job's periodic configuration (time based scheduling). +* `periodic_config`: `(list of maps)` Job's periodic configuration. * `enabled`: `(boolean)` If periodic scheduling is enabled for the specified job. - * `spec`: `(string)` - * `spec_type`: `(string)` - * `prohibit_overlap`: `(boolean)` If the specified job should wait until previous instances of the job have completed. + * `spec`: `(string)` Cron specification for the periodic job. + * `spec_type`: `(string)` Type of periodic specification. + * `prohibit_overlap`: `(boolean)` If the specified job should wait until previous instances of the job have completed. * `timezone`: `(string)` Time zone to evaluate the next launch interval against. diff --git a/website/docs/r/job.html.markdown b/website/docs/r/job.html.markdown index c7fd30f4..57c4e309 100644 --- a/website/docs/r/job.html.markdown +++ b/website/docs/r/job.html.markdown @@ -179,7 +179,6 @@ example, Terraform will not be able to detect changes to files loaded using the [`file`](https://www.nomadproject.io/docs/job-specification/hcl2/functions/file/file) function inside a jobspec. - To avoid confusion, these functions are disabled by default. To enable them set `allow_fs` to `true`: @@ -265,10 +264,85 @@ The following arguments are supported: - `allow_fs` `(boolean: false)` - Set this to `true` to be able to use [HCL2 filesystem functions](#filesystem-functions) +## Attributes Reference + +The following attributes are exported: + +- `name` `(string)` - The job name, as derived from the jobspec. +- `namespace` `(string)` - The namespace of the job, as derived from the jobspec. +- `type` `(string)` - The type of the job, as derived from the jobspec. +- `region` `(string)` - The target region for the job. +- `datacenters` `(set of strings)` - The target datacenters for the job. +- `modify_index` `(string)` - Integer that increments for each change. Used to detect any changes between plan and apply. +- `status` `(string)` - The current status of the job. +- `status_description` `(string)` - Additional status information returned by Nomad. +- `version` `(integer)` - The current job version. +- `submit_time` `(integer)` - The Unix timestamp when the job was submitted. +- `create_index` `(integer)` - The job creation index. +- `stop` `(boolean)` - Whether the job is stopped. +- `priority` `(integer)` - The job priority for scheduling and resource access. +- `parent_id` `(string)` - The parent job ID, if applicable. +- `stable` `(boolean)` - Whether the job is stable. +- `all_at_once` `(boolean)` - Whether the scheduler can make partial placements on oversubscribed nodes. +- `deployment_id` `(string)` - If `detach = false`, the deployment associated with the last create or update, if one exists. +- `deployment_status` `(string)` - If `detach = false`, the status for the deployment associated with the last create or update, if one exists. +- `allocation_ids` `(list of strings)` - Allocation IDs associated with the job when `read_allocation_ids = true`. +- `constraints` `(list of maps)` - Job constraints. + - `ltarget` `(string)` - Attribute being constrained. + - `rtarget` `(string)` - Constraint value. + - `operand` `(string)` - Operator used to compare the attribute to the constraint. +- `update_strategy` `(list of maps)` - Job-level update strategy returned by Nomad. + - `stagger` `(string)` - Delay between migrating job allocations off cluster nodes marked for draining. + - `max_parallel` `(integer)` - Number of task groups that can be updated at the same time. + - `health_check` `(string)` - Type of mechanism in which allocations health is determined. + - `min_healthy_time` `(string)` - Minimum time the allocation must be in the healthy state. + - `healthy_deadline` `(string)` - Deadline in which the allocation must be marked as healthy. + - `auto_revert` `(boolean)` - Whether the job should auto-revert to the last stable job on deployment failure. + - `canary` `(integer)` - Number of canary jobs that need to reach healthy status before unblocking rolling updates. +- `periodic_config` `(list of maps)` - The job's periodic configuration. + - `enabled` `(boolean)` - Whether periodic scheduling is enabled for the job. + - `spec` `(string)` - Cron specification for the periodic job. + - `spec_type` `(string)` - Type of periodic specification. + - `prohibit_overlap` `(boolean)` - Whether the job should wait until previous instances have completed. + - `timezone` `(string)` - Time zone used to evaluate the next launch interval. +- `task_groups` `(list of maps)` - A list of the job's task groups. + - `name` `(string)` - Task group name. + - `count` `(integer)` - Task group count. + - `update_strategy` `(list of maps)` - Effective update strategy for the task group. + - `stagger` `(string)` - Delay between migrating job allocations off cluster nodes marked for draining. + - `max_parallel` `(integer)` - Number of task groups that can be updated at the same time. + - `health_check` `(string)` - Type of mechanism in which allocations health is determined. + - `min_healthy_time` `(string)` - Minimum time the allocation must be in the healthy state. + - `healthy_deadline` `(string)` - Deadline in which the allocation must be marked as healthy. + - `auto_revert` `(boolean)` - Whether the job should auto-revert to the last stable job on deployment failure. + - `canary` `(integer)` - Number of canary jobs that need to reach healthy status before unblocking rolling updates. + - `placed_canaries` `(list of strings)` - Allocations placed as canaries for the task group. + - `auto_revert` `(boolean)` - Whether the latest deployment for the task group is marked for auto-revert. + - `promoted` `(boolean)` - Whether the canary deployment has been promoted. + - `desired_canaries` `(integer)` - Desired number of canaries. + - `desired_total` `(integer)` - Desired total number of allocations. + - `placed_allocs` `(integer)` - Number of placed allocations. + - `healthy_allocs` `(integer)` - Number of healthy allocations. + - `unhealthy_allocs` `(integer)` - Number of unhealthy allocations. + - `task` `(list of maps)` - Tasks in the task group. + - `name` `(string)` - Task name. + - `driver` `(string)` - Task driver. + - `meta` `(map of strings)` - Task metadata. + - `volume_mounts` `(list of maps)` - Task volume mounts. + - `volume` `(string)` - Volume name. + - `destination` `(string)` - Destination path inside the task. + - `read_only` `(boolean)` - Whether the volume mount is read-only. + - `volumes` `(list of maps)` - Volume requests for the task group. + - `name` `(string)` - Volume name. + - `type` `(string)` - Volume type. + - `read_only` `(boolean)` - Whether the volume is read-only. + - `source` `(string)` - Volume source. + - `meta` `(map of strings)` - Task group metadata. + ### Timeouts `nomad_job` provides the following [`Timeouts`][tf_docs_timeouts] configuration -options when [`detach`](#detach) is set to `false`: +options when `detach` is set to `false`: - `create` `(string: "5m")` - Timeout when registering a new job. - `update` `(string: "5m")` - Timeout when updating an existing job. From cd230440a7f8689675552288aeee1b72b425db27 Mon Sep 17 00:00:00 2001 From: Vishnu U Date: Mon, 16 Mar 2026 11:51:06 +0530 Subject: [PATCH 5/9] update descriptions of update_strategy and periodic_config attributes --- nomad/resource_job.go | 24 ++++++++++---------- website/docs/d/job.html.markdown | 38 ++++++++++++++++---------------- website/docs/r/job.html.markdown | 36 +++++++++++++++--------------- 3 files changed, 49 insertions(+), 49 deletions(-) diff --git a/nomad/resource_job.go b/nomad/resource_job.go index cb369cb1..e3072449 100644 --- a/nomad/resource_job.go +++ b/nomad/resource_job.go @@ -241,27 +241,27 @@ func resourceJob() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "enabled": { - Description: "Whether periodic scheduling is enabled for this job.", + Description: "Whether the periodic job is enabled. When disabled, scheduled runs and force launches are prevented.", Type: schema.TypeBool, Computed: true, }, "spec": { - Description: "The cron spec for the periodic job.", + Description: "Cron expression configuring the interval at which the job is launched.", Type: schema.TypeString, Computed: true, }, "spec_type": { - Description: "The type of the periodic spec.", + Description: "Type of periodic specification, such as cron.", Type: schema.TypeString, Computed: true, }, "prohibit_overlap": { - Description: "Whether the job should wait until previous instances have completed.", + Description: "Whether this job should wait until previous instances of the same job have completed before launching again.", Type: schema.TypeBool, Computed: true, }, "timezone": { - Description: "Time zone to evaluate the next launch interval against.", + Description: "Time zone used to evaluate the next launch interval.", Type: schema.TypeString, Computed: true, }, @@ -461,37 +461,37 @@ func updateStrategySchema() *schema.Schema { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "stagger": { - Description: "Delay between migrating job allocations off cluster nodes marked for draining.", + Description: "Delay between each set of max_parallel updates when updating system jobs.", Type: schema.TypeString, Computed: true, }, "max_parallel": { - Description: "Number of task groups that can be updated at the same time.", + Description: "Number of allocations within a task group that can be destructively updated at the same time. Setting 0 forces updates instead of deployments.", Type: schema.TypeInt, Computed: true, }, "health_check": { - Description: "Type of mechanism in which allocations health is determined.", + Description: "Mechanism used to determine allocation health: checks, task_states, or manual.", Type: schema.TypeString, Computed: true, }, "min_healthy_time": { - Description: "Minimum time the allocation must be in the healthy state.", + Description: "Minimum time the allocation must be in the healthy state before further updates can proceed.", Type: schema.TypeString, Computed: true, }, "healthy_deadline": { - Description: "Deadline in which the allocation must be marked as healthy.", + Description: "Deadline by which the allocation must become healthy before it is marked unhealthy.", Type: schema.TypeString, Computed: true, }, "auto_revert": { - Description: "Whether the job should auto-revert to the last stable job on deployment failure.", + Description: "Whether the job should automatically revert to the last stable job on deployment failure.", Type: schema.TypeBool, Computed: true, }, "canary": { - Description: "Number of canary jobs that need to reach healthy status before unblocking rolling updates.", + Description: "Number of canary allocations created before destructive updates continue.", Type: schema.TypeInt, Computed: true, }, diff --git a/website/docs/d/job.html.markdown b/website/docs/d/job.html.markdown index 9ee8299d..b73f0732 100644 --- a/website/docs/d/job.html.markdown +++ b/website/docs/d/job.html.markdown @@ -53,13 +53,13 @@ The following attributes are exported: * `name`: `(string)` Task group name. * `count`: `(integer)` Task group count. * `update_strategy`: `(list of maps)` Effective update strategy for the task group. - * `stagger`: `(string)` Delay between migrating job allocations off cluster nodes marked for draining. - * `max_parallel`: `(integer)` Number of task groups that can be updated at the same time. - * `health_check`: `(string)` Type of mechanism in which allocations health is determined. - * `min_healthy_time`: `(string)` Minimum time the allocation must be in the healthy state. - * `healthy_deadline`: `(string)` Deadline in which the allocation must be marked as healthy. - * `auto_revert`: `(boolean)` Whether the job should auto-revert to the last stable job on deployment failure. - * `canary`: `(integer)` Number of canary jobs that need to reach healthy status before unblocking rolling updates. + * `stagger`: `(string)` Delay between each set of `max_parallel` updates when updating system jobs. + * `max_parallel`: `(integer)` Number of allocations within a task group that can be destructively updated at the same time. Setting `0` forces updates instead of deployments. + * `health_check`: `(string)` Mechanism used to determine allocation health: `checks`, `task_states`, or `manual`. + * `min_healthy_time`: `(string)` Minimum time the allocation must be in the healthy state before further updates can proceed. + * `healthy_deadline`: `(string)` Deadline by which the allocation must become healthy before it is marked unhealthy. + * `auto_revert`: `(boolean)` Whether the job should automatically revert to the last stable job on deployment failure. + * `canary`: `(integer)` Number of canary allocations created before destructive updates continue. * `placed_canaries`: `(list of strings)` Allocations placed as canaries for the task group. * `auto_revert`: `(boolean)` Whether the latest deployment for the task group is marked for auto-revert. * `promoted`: `(boolean)` Whether the canary deployment has been promoted. @@ -89,16 +89,16 @@ The following attributes are exported: * `rtarget`: `(string)` Constraint value. * `operand`: `(string)` Operator used to compare the attribute to the constraint. * `update_strategy`: `(list of maps)` Job-level update strategy returned by Nomad. - * `stagger`: `(string)` Delay between migrating job allocations off cluster nodes marked for draining. - * `max_parallel`: `(integer)` Number of task groups that can be updated at the same time. - * `health_check`: `(string)` Type of mechanism in which allocations health is determined. - * `min_healthy_time`: `(string)` Minimum time the allocation must be in the healthy state. - * `healthy_deadline`: `(string)` Deadline in which the allocation must be marked as healthy. - * `auto_revert`: `(boolean)` Whether the job should auto-revert to the last stable job on deployment failure. - * `canary`: `(integer)` Number of canary jobs that need to reach healthy status before unblocking rolling updates. + * `stagger`: `(string)` Delay between each set of `max_parallel` updates when updating system jobs. + * `max_parallel`: `(integer)` Number of allocations within a task group that can be destructively updated at the same time. Setting `0` forces updates instead of deployments. + * `health_check`: `(string)` Mechanism used to determine allocation health: `checks`, `task_states`, or `manual`. + * `min_healthy_time`: `(string)` Minimum time the allocation must be in the healthy state before further updates can proceed. + * `healthy_deadline`: `(string)` Deadline by which the allocation must become healthy before it is marked unhealthy. + * `auto_revert`: `(boolean)` Whether the job should automatically revert to the last stable job on deployment failure. + * `canary`: `(integer)` Number of canary allocations created before destructive updates continue. * `periodic_config`: `(list of maps)` Job's periodic configuration. - * `enabled`: `(boolean)` If periodic scheduling is enabled for the specified job. - * `spec`: `(string)` Cron specification for the periodic job. - * `spec_type`: `(string)` Type of periodic specification. - * `prohibit_overlap`: `(boolean)` If the specified job should wait until previous instances of the job have completed. - * `timezone`: `(string)` Time zone to evaluate the next launch interval against. + * `enabled`: `(boolean)` Whether the periodic job is enabled. When disabled, scheduled runs and force launches are prevented. + * `spec`: `(string)` Cron expression configuring the interval at which the job is launched. + * `spec_type`: `(string)` Type of periodic specification, such as `cron`. + * `prohibit_overlap`: `(boolean)` Whether this job should wait until previous instances of the same job have completed before launching again. + * `timezone`: `(string)` Time zone used to evaluate the next launch interval. diff --git a/website/docs/r/job.html.markdown b/website/docs/r/job.html.markdown index 57c4e309..44198683 100644 --- a/website/docs/r/job.html.markdown +++ b/website/docs/r/job.html.markdown @@ -292,30 +292,30 @@ The following attributes are exported: - `rtarget` `(string)` - Constraint value. - `operand` `(string)` - Operator used to compare the attribute to the constraint. - `update_strategy` `(list of maps)` - Job-level update strategy returned by Nomad. - - `stagger` `(string)` - Delay between migrating job allocations off cluster nodes marked for draining. - - `max_parallel` `(integer)` - Number of task groups that can be updated at the same time. - - `health_check` `(string)` - Type of mechanism in which allocations health is determined. - - `min_healthy_time` `(string)` - Minimum time the allocation must be in the healthy state. - - `healthy_deadline` `(string)` - Deadline in which the allocation must be marked as healthy. - - `auto_revert` `(boolean)` - Whether the job should auto-revert to the last stable job on deployment failure. - - `canary` `(integer)` - Number of canary jobs that need to reach healthy status before unblocking rolling updates. + - `stagger` `(string)` - Delay between each set of `max_parallel` updates when updating system jobs. + - `max_parallel` `(integer)` - Number of allocations within a task group that can be destructively updated at the same time. Setting `0` forces updates instead of deployments. + - `health_check` `(string)` - Mechanism used to determine allocation health: `checks`, `task_states`, or `manual`. + - `min_healthy_time` `(string)` - Minimum time the allocation must be in the healthy state before further updates can proceed. + - `healthy_deadline` `(string)` - Deadline by which the allocation must become healthy before it is marked unhealthy. + - `auto_revert` `(boolean)` - Whether the job should automatically revert to the last stable job on deployment failure. + - `canary` `(integer)` - Number of canary allocations created before destructive updates continue. - `periodic_config` `(list of maps)` - The job's periodic configuration. - - `enabled` `(boolean)` - Whether periodic scheduling is enabled for the job. - - `spec` `(string)` - Cron specification for the periodic job. - - `spec_type` `(string)` - Type of periodic specification. - - `prohibit_overlap` `(boolean)` - Whether the job should wait until previous instances have completed. + - `enabled` `(boolean)` - Whether the periodic job is enabled. When disabled, scheduled runs and force launches are prevented. + - `spec` `(string)` - Cron expression configuring the interval at which the job is launched. + - `spec_type` `(string)` - Type of periodic specification, such as `cron`. + - `prohibit_overlap` `(boolean)` - Whether this job should wait until previous instances of the same job have completed before launching again. - `timezone` `(string)` - Time zone used to evaluate the next launch interval. - `task_groups` `(list of maps)` - A list of the job's task groups. - `name` `(string)` - Task group name. - `count` `(integer)` - Task group count. - `update_strategy` `(list of maps)` - Effective update strategy for the task group. - - `stagger` `(string)` - Delay between migrating job allocations off cluster nodes marked for draining. - - `max_parallel` `(integer)` - Number of task groups that can be updated at the same time. - - `health_check` `(string)` - Type of mechanism in which allocations health is determined. - - `min_healthy_time` `(string)` - Minimum time the allocation must be in the healthy state. - - `healthy_deadline` `(string)` - Deadline in which the allocation must be marked as healthy. - - `auto_revert` `(boolean)` - Whether the job should auto-revert to the last stable job on deployment failure. - - `canary` `(integer)` - Number of canary jobs that need to reach healthy status before unblocking rolling updates. + - `stagger` `(string)` - Delay between each set of `max_parallel` updates when updating system jobs. + - `max_parallel` `(integer)` - Number of allocations within a task group that can be destructively updated at the same time. Setting `0` forces updates instead of deployments. + - `health_check` `(string)` - Mechanism used to determine allocation health: `checks`, `task_states`, or `manual`. + - `min_healthy_time` `(string)` - Minimum time the allocation must be in the healthy state before further updates can proceed. + - `healthy_deadline` `(string)` - Deadline by which the allocation must become healthy before it is marked unhealthy. + - `auto_revert` `(boolean)` - Whether the job should automatically revert to the last stable job on deployment failure. + - `canary` `(integer)` - Number of canary allocations created before destructive updates continue. - `placed_canaries` `(list of strings)` - Allocations placed as canaries for the task group. - `auto_revert` `(boolean)` - Whether the latest deployment for the task group is marked for auto-revert. - `promoted` `(boolean)` - Whether the canary deployment has been promoted. From 3bf2988e253a59a4a31cdf5c2f74ba03e34da7d7 Mon Sep 17 00:00:00 2001 From: Vishnu U Date: Tue, 17 Mar 2026 14:16:50 +0530 Subject: [PATCH 6/9] nomad_job: align job attributes and latest deployment handling --- nomad/datasource_nomad_job.go | 11 +---------- nomad/resource_job.go | 21 +++++++++++++-------- website/docs/d/job.html.markdown | 1 + website/docs/r/job.html.markdown | 1 + 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/nomad/datasource_nomad_job.go b/nomad/datasource_nomad_job.go index 9bc2cbab..35ccd94b 100644 --- a/nomad/datasource_nomad_job.go +++ b/nomad/datasource_nomad_job.go @@ -206,16 +206,7 @@ func dataSourceJobRead(d *schema.ResourceData, meta interface{}) error { d.Set("priority", job.Priority) d.Set("parent_id", job.ParentID) - // Fetch the latest deployment for task group deployment status. - var deploymentTGs map[string]*api.DeploymentState - deploys, _, err := client.Jobs().Deployments(id, false, &api.QueryOptions{ - Namespace: ns, - }) - if err != nil { - log.Printf("[WARN] error listing deployments for Job %q: %s", id, err) - } else if len(deploys) > 0 { - deploymentTGs = deploys[0].TaskGroups - } + deploymentTGs := latestDeploymentTaskGroups(client, id, &api.QueryOptions{Namespace: ns}) d.Set("task_groups", jobTaskGroupsRaw(job.TaskGroups, deploymentTGs)) d.Set("stable", job.Stable) d.Set("all_at_once", job.AllAtOnce) diff --git a/nomad/resource_job.go b/nomad/resource_job.go index e3072449..3edc60e0 100644 --- a/nomad/resource_job.go +++ b/nomad/resource_job.go @@ -805,14 +805,7 @@ func resourceJobRead(d *schema.ResourceData, meta interface{}) error { d.Set("update_strategy", flattenUpdateStrategy(job.Update)) d.Set("periodic_config", flattenPeriodicConfig(job.Periodic)) - // Fetch the latest deployment for task group deployment status. - var deploymentTGs map[string]*api.DeploymentState - deploys, _, err := client.Jobs().Deployments(id, false, opts) - if err != nil { - log.Printf("[WARN] error listing deployments for Job %q: %s", id, err) - } else if len(deploys) > 0 { - deploymentTGs = deploys[0].TaskGroups - } + deploymentTGs := latestDeploymentTaskGroups(client, id, opts) d.Set("task_groups", jobTaskGroupsRaw(job.TaskGroups, deploymentTGs)) if d.Get("read_allocation_ids").(bool) { @@ -1040,6 +1033,18 @@ func canonicalizeTaskGroupUpdateStrategies(job *api.Job) { } } +func latestDeploymentTaskGroups(client *api.Client, jobID string, opts *api.QueryOptions) map[string]*api.DeploymentState { + deployment, _, err := client.Jobs().LatestDeployment(jobID, opts) + if err != nil { + log.Printf("[WARN] error reading latest deployment for Job %q: %s", jobID, err) + return nil + } + if deployment == nil { + return nil + } + return deployment.TaskGroups +} + func mergeDeploymentStateFromState(planned []interface{}, state interface{}) []interface{} { stateList, ok := state.([]interface{}) if !ok || len(stateList) == 0 { diff --git a/website/docs/d/job.html.markdown b/website/docs/d/job.html.markdown index b73f0732..c5c4efb2 100644 --- a/website/docs/d/job.html.markdown +++ b/website/docs/d/job.html.markdown @@ -50,6 +50,7 @@ The following attributes are exported: * `priority`: `(integer)` Used for the prioritization of scheduling and resource access. * `parent_id`: `(string)` Job's parent ID. * `task_groups`: `(list of maps)` A list of the job's task groups. + Deployment-derived task group fields such as `placed_canaries`, `promoted`, `desired_canaries`, `desired_total`, `placed_allocs`, `healthy_allocs`, and `unhealthy_allocs` are only populated when Nomad reports a latest deployment for the job. These fields are typically empty for job types without deployments, such as `batch` and `sysbatch`. * `name`: `(string)` Task group name. * `count`: `(integer)` Task group count. * `update_strategy`: `(list of maps)` Effective update strategy for the task group. diff --git a/website/docs/r/job.html.markdown b/website/docs/r/job.html.markdown index 44198683..c55bbe0c 100644 --- a/website/docs/r/job.html.markdown +++ b/website/docs/r/job.html.markdown @@ -306,6 +306,7 @@ The following attributes are exported: - `prohibit_overlap` `(boolean)` - Whether this job should wait until previous instances of the same job have completed before launching again. - `timezone` `(string)` - Time zone used to evaluate the next launch interval. - `task_groups` `(list of maps)` - A list of the job's task groups. + Deployment-derived task group fields such as `placed_canaries`, `promoted`, `desired_canaries`, `desired_total`, `placed_allocs`, `healthy_allocs`, and `unhealthy_allocs` are only populated when Nomad reports a latest deployment for the job. These fields are typically empty for job types without deployments, such as `batch` and `sysbatch`. - `name` `(string)` - Task group name. - `count` `(integer)` - Task group count. - `update_strategy` `(list of maps)` - Effective update strategy for the task group. From cd8698f33b2b3ea3a293fd49467ae12e4f52a5e2 Mon Sep 17 00:00:00 2001 From: Vishnu U Date: Tue, 17 Mar 2026 15:46:06 +0530 Subject: [PATCH 7/9] nomad_job: include deployment metadata in deployment_state Add deployment-level id, status, and status_description to the deployment_state field so it represents the latest deployment more completely, instead of only exposing nested task group state. Also update resource and data source tests plus docs to reflect the expanded schema. --- nomad/datasource_nomad_job.go | 8 +- nomad/datasource_nomad_job_test.go | 16 ++ nomad/resource_job.go | 239 +++++++++++++++-------------- nomad/resource_job_test.go | 58 ++++--- website/docs/d/job.html.markdown | 23 +-- website/docs/r/job.html.markdown | 23 +-- 6 files changed, 208 insertions(+), 159 deletions(-) diff --git a/nomad/datasource_nomad_job.go b/nomad/datasource_nomad_job.go index 35ccd94b..cdfeaf34 100644 --- a/nomad/datasource_nomad_job.go +++ b/nomad/datasource_nomad_job.go @@ -100,7 +100,8 @@ func dataSourceJob() *schema.Resource { Computed: true, Type: schema.TypeString, }, - "task_groups": taskGroupSchema(), + "task_groups": taskGroupSchema(), + "deployment_state": deploymentStateSchema(), "stable": { Description: "Job Stable", Type: schema.TypeBool, @@ -206,8 +207,9 @@ func dataSourceJobRead(d *schema.ResourceData, meta interface{}) error { d.Set("priority", job.Priority) d.Set("parent_id", job.ParentID) - deploymentTGs := latestDeploymentTaskGroups(client, id, &api.QueryOptions{Namespace: ns}) - d.Set("task_groups", jobTaskGroupsRaw(job.TaskGroups, deploymentTGs)) + deployment := latestDeployment(client, id, &api.QueryOptions{Namespace: ns}) + d.Set("task_groups", jobTaskGroupsRaw(job.TaskGroups)) + d.Set("deployment_state", deploymentStateRaw(deployment)) d.Set("stable", job.Stable) d.Set("all_at_once", job.AllAtOnce) d.Set("constraints", job.Constraints) diff --git a/nomad/datasource_nomad_job_test.go b/nomad/datasource_nomad_job_test.go index 8f8628e8..6702bd2d 100644 --- a/nomad/datasource_nomad_job_test.go +++ b/nomad/datasource_nomad_job_test.go @@ -48,6 +48,22 @@ func TestAccDataSourceNomadJob_Basic(t *testing.T) { "data.nomad_job.test-job", "task_groups.0.update_strategy.0.auto_revert", "true"), resource.TestCheckResourceAttr( "data.nomad_job.test-job", "task_groups.0.update_strategy.0.canary", "1"), + resource.TestCheckResourceAttr( + "data.nomad_job.test-job", "deployment_state.#", "1"), + resource.TestCheckResourceAttrSet( + "data.nomad_job.test-job", "deployment_state.0.id"), + resource.TestCheckResourceAttrSet( + "data.nomad_job.test-job", "deployment_state.0.status"), + resource.TestCheckResourceAttr( + "data.nomad_job.test-job", "deployment_state.0.task_groups.#", "1"), + resource.TestCheckResourceAttrSet( + "data.nomad_job.test-job", "deployment_state.0.task_groups.0.desired_total"), + resource.TestCheckResourceAttrSet( + "data.nomad_job.test-job", "deployment_state.0.task_groups.0.placed_allocs"), + resource.TestCheckResourceAttrSet( + "data.nomad_job.test-job", "deployment_state.0.task_groups.0.healthy_allocs"), + resource.TestCheckResourceAttrSet( + "data.nomad_job.test-job", "deployment_state.0.task_groups.0.unhealthy_allocs"), ), }, }, diff --git a/nomad/resource_job.go b/nomad/resource_job.go index 3edc60e0..6598c084 100644 --- a/nomad/resource_job.go +++ b/nomad/resource_job.go @@ -302,7 +302,8 @@ func resourceJob() *schema.Resource { }, }, - "task_groups": taskGroupSchema(), + "task_groups": taskGroupSchema(), + "deployment_state": deploymentStateSchema(), "purge_on_destroy": { Description: "Whether to purge the job when the resource is destroyed.", @@ -335,39 +336,6 @@ func taskGroupSchema() *schema.Schema { Type: schema.TypeInt, }, "update_strategy": updateStrategySchema(), - "placed_canaries": { - Computed: true, - Type: schema.TypeList, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "auto_revert": { - Computed: true, - Type: schema.TypeBool, - }, - "promoted": { - Computed: true, - Type: schema.TypeBool, - }, - "desired_canaries": { - Computed: true, - Type: schema.TypeInt, - }, - "desired_total": { - Computed: true, - Type: schema.TypeInt, - }, - "placed_allocs": { - Computed: true, - Type: schema.TypeInt, - }, - "healthy_allocs": { - Computed: true, - Type: schema.TypeInt, - }, - "unhealthy_allocs": { - Computed: true, - Type: schema.TypeInt, - }, // "scaling": { // Computed: true, // Type: schema.TypeList, @@ -453,6 +421,79 @@ func taskGroupSchema() *schema.Schema { } } +func deploymentStateSchema() *schema.Schema { + return &schema.Schema{ + Description: "State from the latest deployment for the job, if one exists.", + Computed: true, + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Computed: true, + Type: schema.TypeString, + }, + "status": { + Computed: true, + Type: schema.TypeString, + }, + "status_description": { + Computed: true, + Type: schema.TypeString, + }, + "task_groups": deploymentTaskGroupStateSchema(), + }, + }, + } +} + +func deploymentTaskGroupStateSchema() *schema.Schema { + return &schema.Schema{ + Computed: true, + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Computed: true, + Type: schema.TypeString, + }, + "placed_canaries": { + Computed: true, + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "auto_revert": { + Computed: true, + Type: schema.TypeBool, + }, + "promoted": { + Computed: true, + Type: schema.TypeBool, + }, + "desired_canaries": { + Computed: true, + Type: schema.TypeInt, + }, + "desired_total": { + Computed: true, + Type: schema.TypeInt, + }, + "placed_allocs": { + Computed: true, + Type: schema.TypeInt, + }, + "healthy_allocs": { + Computed: true, + Type: schema.TypeInt, + }, + "unhealthy_allocs": { + Computed: true, + Type: schema.TypeInt, + }, + }, + }, + } +} + func updateStrategySchema() *schema.Schema { return &schema.Schema{ Description: "The update strategy for rolling updates and canary deployments.", @@ -805,8 +846,9 @@ func resourceJobRead(d *schema.ResourceData, meta interface{}) error { d.Set("update_strategy", flattenUpdateStrategy(job.Update)) d.Set("periodic_config", flattenPeriodicConfig(job.Periodic)) - deploymentTGs := latestDeploymentTaskGroups(client, id, opts) - d.Set("task_groups", jobTaskGroupsRaw(job.TaskGroups, deploymentTGs)) + deployment := latestDeployment(client, id, opts) + d.Set("task_groups", jobTaskGroupsRaw(job.TaskGroups)) + d.Set("deployment_state", deploymentStateRaw(deployment)) if d.Get("read_allocation_ids").(bool) { allocStubs, _, err := client.Jobs().Allocations(id, false, opts) @@ -890,6 +932,7 @@ func resourceJobCustomizeDiff(_ context.Context, d *schema.ResourceDiff, meta in d.SetNewComputed("datacenters") d.SetNewComputed("allocation_ids") d.SetNewComputed("task_groups") + d.SetNewComputed("deployment_state") d.SetNewComputed("deployment_id") d.SetNewComputed("deployment_status") d.SetNewComputed("status") @@ -1000,10 +1043,10 @@ func resourceJobCustomizeDiff(_ context.Context, d *schema.ResourceDiff, meta in d.SetNewComputed("modify_index") // similarly, we won't know the allocation ids until after the job registration eval d.SetNewComputed("allocation_ids") + d.SetNewComputed("deployment_state") canonicalizeTaskGroupUpdateStrategies(job) - plannedTaskGroups := jobTaskGroupsRaw(job.TaskGroups, nil) - plannedTaskGroups = mergeDeploymentStateFromState(plannedTaskGroups, d.Get("task_groups")) + plannedTaskGroups := jobTaskGroupsRaw(job.TaskGroups) d.SetNew("task_groups", plannedTaskGroups) return nil @@ -1033,69 +1076,13 @@ func canonicalizeTaskGroupUpdateStrategies(job *api.Job) { } } -func latestDeploymentTaskGroups(client *api.Client, jobID string, opts *api.QueryOptions) map[string]*api.DeploymentState { +func latestDeployment(client *api.Client, jobID string, opts *api.QueryOptions) *api.Deployment { deployment, _, err := client.Jobs().LatestDeployment(jobID, opts) if err != nil { log.Printf("[WARN] error reading latest deployment for Job %q: %s", jobID, err) return nil } - if deployment == nil { - return nil - } - return deployment.TaskGroups -} - -func mergeDeploymentStateFromState(planned []interface{}, state interface{}) []interface{} { - stateList, ok := state.([]interface{}) - if !ok || len(stateList) == 0 { - return planned - } - - stateByName := make(map[string]map[string]interface{}, len(stateList)) - for _, item := range stateList { - tgM, ok := item.(map[string]interface{}) - if !ok { - continue - } - name, ok := tgM["name"].(string) - if !ok || name == "" { - continue - } - stateByName[name] = tgM - } - - keys := []string{ - "placed_canaries", - "auto_revert", - "promoted", - "desired_canaries", - "desired_total", - "placed_allocs", - "healthy_allocs", - "unhealthy_allocs", - } - - for _, item := range planned { - plannedTG, ok := item.(map[string]interface{}) - if !ok { - continue - } - name, ok := plannedTG["name"].(string) - if !ok || name == "" { - continue - } - stateTG, ok := stateByName[name] - if !ok { - continue - } - for _, key := range keys { - if v, ok := stateTG[key]; ok { - plannedTG[key] = v - } - } - } - - return planned + return deployment } func flattenPeriodicConfig(periodic *api.PeriodicConfig) []map[string]interface{} { @@ -1251,7 +1238,7 @@ func parseHCL2Jobspec(raw string, config HCL2JobParserConfig) (*api.Job, error) }) } -func jobTaskGroupsRaw(tgs []*api.TaskGroup, deploymentTGs map[string]*api.DeploymentState) []interface{} { +func jobTaskGroupsRaw(tgs []*api.TaskGroup) []interface{} { ret := make([]interface{}, 0, len(tgs)) for _, tg := range tgs { @@ -1324,28 +1311,54 @@ func jobTaskGroupsRaw(tgs []*api.TaskGroup, deploymentTGs map[string]*api.Deploy tgM["volumes"] = volumesI - // Populate deployment state fields for this task group. - if ds, ok := deploymentTGs[tgName]; ok && ds != nil { - canaries := make([]interface{}, len(ds.PlacedCanaries)) - for i, c := range ds.PlacedCanaries { - canaries[i] = c - } - tgM["placed_canaries"] = canaries - tgM["auto_revert"] = ds.AutoRevert - tgM["promoted"] = ds.Promoted - tgM["desired_canaries"] = ds.DesiredCanaries - tgM["desired_total"] = ds.DesiredTotal - tgM["placed_allocs"] = ds.PlacedAllocs - tgM["healthy_allocs"] = ds.HealthyAllocs - tgM["unhealthy_allocs"] = ds.UnhealthyAllocs - } - ret = append(ret, tgM) } return ret } +func deploymentStateRaw(deployment *api.Deployment) []interface{} { + if deployment == nil { + return nil + } + + taskGroups := make([]interface{}, 0, len(deployment.TaskGroups)) + for name, state := range deployment.TaskGroups { + if state == nil { + continue + } + + placedCanaries := make([]interface{}, len(state.PlacedCanaries)) + for i, canary := range state.PlacedCanaries { + placedCanaries[i] = canary + } + + taskGroups = append(taskGroups, map[string]interface{}{ + "name": name, + "placed_canaries": placedCanaries, + "auto_revert": state.AutoRevert, + "promoted": state.Promoted, + "desired_canaries": state.DesiredCanaries, + "desired_total": state.DesiredTotal, + "placed_allocs": state.PlacedAllocs, + "healthy_allocs": state.HealthyAllocs, + "unhealthy_allocs": state.UnhealthyAllocs, + }) + } + + sort.Slice(taskGroups, func(i, j int) bool { + return taskGroups[i].(map[string]interface{})["name"].(string) < + taskGroups[j].(map[string]interface{})["name"].(string) + }) + + return []interface{}{map[string]interface{}{ + "id": deployment.ID, + "status": deployment.Status, + "status_description": deployment.StatusDescription, + "task_groups": taskGroups, + }} +} + func flattenJobConstraints(constraints []*api.Constraint) []map[string]interface{} { result := make([]map[string]interface{}, 0, len(constraints)) for _, c := range constraints { diff --git a/nomad/resource_job_test.go b/nomad/resource_job_test.go index e77f0b1b..63fd08ac 100644 --- a/nomad/resource_job_test.go +++ b/nomad/resource_job_test.go @@ -67,10 +67,14 @@ func TestResourceJob_serviceDeploymentStateFast(t *testing.T) { Config: testResourceJob_serviceDeploymentStateFast, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "task_groups.#", "1"), - resource.TestCheckResourceAttrSet(resourceName, "task_groups.0.desired_total"), - resource.TestCheckResourceAttrSet(resourceName, "task_groups.0.placed_allocs"), - resource.TestCheckResourceAttrSet(resourceName, "task_groups.0.healthy_allocs"), - resource.TestCheckResourceAttrSet(resourceName, "task_groups.0.unhealthy_allocs"), + resource.TestCheckResourceAttr(resourceName, "deployment_state.#", "1"), + resource.TestCheckResourceAttrSet(resourceName, "deployment_state.0.id"), + resource.TestCheckResourceAttrSet(resourceName, "deployment_state.0.status"), + resource.TestCheckResourceAttr(resourceName, "deployment_state.0.task_groups.#", "1"), + resource.TestCheckResourceAttrSet(resourceName, "deployment_state.0.task_groups.0.desired_total"), + resource.TestCheckResourceAttrSet(resourceName, "deployment_state.0.task_groups.0.placed_allocs"), + resource.TestCheckResourceAttrSet(resourceName, "deployment_state.0.task_groups.0.healthy_allocs"), + resource.TestCheckResourceAttrSet(resourceName, "deployment_state.0.task_groups.0.unhealthy_allocs"), ), }, }, @@ -2424,46 +2428,50 @@ func TestVolumeSorting(t *testing.T) { }, }, } - tg1 := jobTaskGroupsRaw(tgs, nil) + tg1 := jobTaskGroupsRaw(tgs) tgs[0].Volumes = map[string]*api.VolumeRequest{ vols[1].Name: vols[1], vols[0].Name: vols[0], } - tg2 := jobTaskGroupsRaw(tgs, nil) + tg2 := jobTaskGroupsRaw(tgs) require.ElementsMatch(tg1, tg2) } -func TestJobTaskGroupsRawDeploymentState(t *testing.T) { +func TestDeploymentStateRaw(t *testing.T) { require := require.New(t) groupName := "group" - groupCount := 2 - tgs := []*api.TaskGroup{ - { - Name: &groupName, - Count: &groupCount, + deployment := &api.Deployment{ + TaskGroups: map[string]*api.DeploymentState{ + groupName: { + PlacedCanaries: []string{"canary-alloc"}, + AutoRevert: true, + Promoted: true, + DesiredCanaries: 1, + DesiredTotal: 2, + PlacedAllocs: 2, + HealthyAllocs: 2, + UnhealthyAllocs: 0, + }, }, } - deploymentTGs := map[string]*api.DeploymentState{ - groupName: { - PlacedCanaries: []string{"canary-alloc"}, - AutoRevert: true, - Promoted: true, - DesiredCanaries: 1, - DesiredTotal: 2, - PlacedAllocs: 2, - HealthyAllocs: 2, - UnhealthyAllocs: 0, - }, - } + deploymentRaw := deploymentStateRaw(deployment) + require.Len(deploymentRaw, 1) - groupsRaw := jobTaskGroupsRaw(tgs, deploymentTGs) + stateRaw, ok := deploymentRaw[0].(map[string]interface{}) + require.True(ok) + require.Equal("", stateRaw["id"]) + require.Equal("", stateRaw["status"]) + require.Equal("", stateRaw["status_description"]) + groupsRaw, ok := stateRaw["task_groups"].([]interface{}) + require.True(ok) require.Len(groupsRaw, 1) groupRaw, ok := groupsRaw[0].(map[string]interface{}) require.True(ok) + require.Equal(groupName, groupRaw["name"]) require.Equal([]interface{}{"canary-alloc"}, groupRaw["placed_canaries"]) require.Equal(true, groupRaw["auto_revert"]) require.Equal(true, groupRaw["promoted"]) diff --git a/website/docs/d/job.html.markdown b/website/docs/d/job.html.markdown index c5c4efb2..ab2c63e9 100644 --- a/website/docs/d/job.html.markdown +++ b/website/docs/d/job.html.markdown @@ -50,7 +50,6 @@ The following attributes are exported: * `priority`: `(integer)` Used for the prioritization of scheduling and resource access. * `parent_id`: `(string)` Job's parent ID. * `task_groups`: `(list of maps)` A list of the job's task groups. - Deployment-derived task group fields such as `placed_canaries`, `promoted`, `desired_canaries`, `desired_total`, `placed_allocs`, `healthy_allocs`, and `unhealthy_allocs` are only populated when Nomad reports a latest deployment for the job. These fields are typically empty for job types without deployments, such as `batch` and `sysbatch`. * `name`: `(string)` Task group name. * `count`: `(integer)` Task group count. * `update_strategy`: `(list of maps)` Effective update strategy for the task group. @@ -61,14 +60,6 @@ The following attributes are exported: * `healthy_deadline`: `(string)` Deadline by which the allocation must become healthy before it is marked unhealthy. * `auto_revert`: `(boolean)` Whether the job should automatically revert to the last stable job on deployment failure. * `canary`: `(integer)` Number of canary allocations created before destructive updates continue. - * `placed_canaries`: `(list of strings)` Allocations placed as canaries for the task group. - * `auto_revert`: `(boolean)` Whether the latest deployment for the task group is marked for auto-revert. - * `promoted`: `(boolean)` Whether the canary deployment has been promoted. - * `desired_canaries`: `(integer)` Desired number of canaries. - * `desired_total`: `(integer)` Desired total number of allocations. - * `placed_allocs`: `(integer)` Number of placed allocations. - * `healthy_allocs`: `(integer)` Number of healthy allocations. - * `unhealthy_allocs`: `(integer)` Number of unhealthy allocations. * `task`: `(list of maps)` Tasks in the task group. * `name`: `(string)` Task name. * `driver`: `(string)` Task driver. @@ -103,3 +94,17 @@ The following attributes are exported: * `spec_type`: `(string)` Type of periodic specification, such as `cron`. * `prohibit_overlap`: `(boolean)` Whether this job should wait until previous instances of the same job have completed before launching again. * `timezone`: `(string)` Time zone used to evaluate the next launch interval. +* `deployment_state`: `(list of maps)` State from the latest deployment for the job, if one exists. This data is typically empty for job types without deployments, such as `batch` and `sysbatch`. + * `id`: `(string)` ID of the latest deployment. + * `status`: `(string)` Status of the latest deployment. + * `status_description`: `(string)` Status description of the latest deployment. + * `task_groups`: `(list of maps)` Deployment state keyed by task group. + * `name`: `(string)` Task group name. + * `placed_canaries`: `(list of strings)` Allocations placed as canaries for the task group. + * `auto_revert`: `(boolean)` Whether the latest deployment for the task group is marked for auto-revert. + * `promoted`: `(boolean)` Whether the canary deployment has been promoted. + * `desired_canaries`: `(integer)` Desired number of canaries. + * `desired_total`: `(integer)` Desired total number of allocations. + * `placed_allocs`: `(integer)` Number of placed allocations. + * `healthy_allocs`: `(integer)` Number of healthy allocations. + * `unhealthy_allocs`: `(integer)` Number of unhealthy allocations. diff --git a/website/docs/r/job.html.markdown b/website/docs/r/job.html.markdown index c55bbe0c..2427fef3 100644 --- a/website/docs/r/job.html.markdown +++ b/website/docs/r/job.html.markdown @@ -306,7 +306,6 @@ The following attributes are exported: - `prohibit_overlap` `(boolean)` - Whether this job should wait until previous instances of the same job have completed before launching again. - `timezone` `(string)` - Time zone used to evaluate the next launch interval. - `task_groups` `(list of maps)` - A list of the job's task groups. - Deployment-derived task group fields such as `placed_canaries`, `promoted`, `desired_canaries`, `desired_total`, `placed_allocs`, `healthy_allocs`, and `unhealthy_allocs` are only populated when Nomad reports a latest deployment for the job. These fields are typically empty for job types without deployments, such as `batch` and `sysbatch`. - `name` `(string)` - Task group name. - `count` `(integer)` - Task group count. - `update_strategy` `(list of maps)` - Effective update strategy for the task group. @@ -317,14 +316,6 @@ The following attributes are exported: - `healthy_deadline` `(string)` - Deadline by which the allocation must become healthy before it is marked unhealthy. - `auto_revert` `(boolean)` - Whether the job should automatically revert to the last stable job on deployment failure. - `canary` `(integer)` - Number of canary allocations created before destructive updates continue. - - `placed_canaries` `(list of strings)` - Allocations placed as canaries for the task group. - - `auto_revert` `(boolean)` - Whether the latest deployment for the task group is marked for auto-revert. - - `promoted` `(boolean)` - Whether the canary deployment has been promoted. - - `desired_canaries` `(integer)` - Desired number of canaries. - - `desired_total` `(integer)` - Desired total number of allocations. - - `placed_allocs` `(integer)` - Number of placed allocations. - - `healthy_allocs` `(integer)` - Number of healthy allocations. - - `unhealthy_allocs` `(integer)` - Number of unhealthy allocations. - `task` `(list of maps)` - Tasks in the task group. - `name` `(string)` - Task name. - `driver` `(string)` - Task driver. @@ -339,6 +330,20 @@ The following attributes are exported: - `read_only` `(boolean)` - Whether the volume is read-only. - `source` `(string)` - Volume source. - `meta` `(map of strings)` - Task group metadata. +- `deployment_state` `(list of maps)` - State from the latest deployment for the job, if one exists. This data is typically empty for job types without deployments, such as `batch` and `sysbatch`. + - `id` `(string)` - ID of the latest deployment. + - `status` `(string)` - Status of the latest deployment. + - `status_description` `(string)` - Status description of the latest deployment. + - `task_groups` `(list of maps)` - Deployment state keyed by task group. + - `name` `(string)` - Task group name. + - `placed_canaries` `(list of strings)` - Allocations placed as canaries for the task group. + - `auto_revert` `(boolean)` - Whether the latest deployment for the task group is marked for auto-revert. + - `promoted` `(boolean)` - Whether the canary deployment has been promoted. + - `desired_canaries` `(integer)` - Desired number of canaries. + - `desired_total` `(integer)` - Desired total number of allocations. + - `placed_allocs` `(integer)` - Number of placed allocations. + - `healthy_allocs` `(integer)` - Number of healthy allocations. + - `unhealthy_allocs` `(integer)` - Number of unhealthy allocations. ### Timeouts From 8c0a83067e59278cc6f005f2eeba6bcaed41fde6 Mon Sep 17 00:00:00 2001 From: Vishnu U Date: Tue, 17 Mar 2026 16:00:36 +0530 Subject: [PATCH 8/9] tests: remove racy deployment_state acceptance test --- nomad/resource_job_test.go | 46 -------------------------------------- 1 file changed, 46 deletions(-) diff --git a/nomad/resource_job_test.go b/nomad/resource_job_test.go index 63fd08ac..ca8e10c5 100644 --- a/nomad/resource_job_test.go +++ b/nomad/resource_job_test.go @@ -57,31 +57,6 @@ func TestResourceJob_service(t *testing.T) { }) } -func TestResourceJob_serviceDeploymentStateFast(t *testing.T) { - resourceName := "nomad_job.service_deployment_state" - r.Test(t, r.TestCase{ - Providers: testProviders, - PreCheck: func() { testAccPreCheck(t) }, - Steps: []r.TestStep{ - { - Config: testResourceJob_serviceDeploymentStateFast, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "task_groups.#", "1"), - resource.TestCheckResourceAttr(resourceName, "deployment_state.#", "1"), - resource.TestCheckResourceAttrSet(resourceName, "deployment_state.0.id"), - resource.TestCheckResourceAttrSet(resourceName, "deployment_state.0.status"), - resource.TestCheckResourceAttr(resourceName, "deployment_state.0.task_groups.#", "1"), - resource.TestCheckResourceAttrSet(resourceName, "deployment_state.0.task_groups.0.desired_total"), - resource.TestCheckResourceAttrSet(resourceName, "deployment_state.0.task_groups.0.placed_allocs"), - resource.TestCheckResourceAttrSet(resourceName, "deployment_state.0.task_groups.0.healthy_allocs"), - resource.TestCheckResourceAttrSet(resourceName, "deployment_state.0.task_groups.0.unhealthy_allocs"), - ), - }, - }, - CheckDestroy: testResourceJob_checkDestroy("foo-service-deployment-state"), - }) -} - func TestResourceJob_namespace(t *testing.T) { r.Test(t, r.TestCase{ Providers: testProviders, @@ -970,27 +945,6 @@ resource "nomad_job" "test" { } ` -var testResourceJob_serviceDeploymentStateFast = ` -resource "nomad_job" "service_deployment_state" { - detach = true - jobspec = < Date: Tue, 17 Mar 2026 20:30:54 +0530 Subject: [PATCH 9/9] nomad_job: remove racy deployment_state exposure Remove deployment_state from the nomad_job resource and data source. The field exposed deployment runtime state with ambiguous timing semantics and could be absent or partially populated depending on when Nomad was queried. Remove the related tests and docs as well. --- nomad/datasource_nomad_job.go | 5 +- nomad/datasource_nomad_job_test.go | 16 ---- nomad/resource_job.go | 132 +---------------------------- nomad/resource_job_test.go | 44 ---------- website/docs/d/job.html.markdown | 14 --- website/docs/r/job.html.markdown | 15 ---- 6 files changed, 2 insertions(+), 224 deletions(-) diff --git a/nomad/datasource_nomad_job.go b/nomad/datasource_nomad_job.go index cdfeaf34..0b8fcbbe 100644 --- a/nomad/datasource_nomad_job.go +++ b/nomad/datasource_nomad_job.go @@ -100,8 +100,7 @@ func dataSourceJob() *schema.Resource { Computed: true, Type: schema.TypeString, }, - "task_groups": taskGroupSchema(), - "deployment_state": deploymentStateSchema(), + "task_groups": taskGroupSchema(), "stable": { Description: "Job Stable", Type: schema.TypeBool, @@ -207,9 +206,7 @@ func dataSourceJobRead(d *schema.ResourceData, meta interface{}) error { d.Set("priority", job.Priority) d.Set("parent_id", job.ParentID) - deployment := latestDeployment(client, id, &api.QueryOptions{Namespace: ns}) d.Set("task_groups", jobTaskGroupsRaw(job.TaskGroups)) - d.Set("deployment_state", deploymentStateRaw(deployment)) d.Set("stable", job.Stable) d.Set("all_at_once", job.AllAtOnce) d.Set("constraints", job.Constraints) diff --git a/nomad/datasource_nomad_job_test.go b/nomad/datasource_nomad_job_test.go index 6702bd2d..8f8628e8 100644 --- a/nomad/datasource_nomad_job_test.go +++ b/nomad/datasource_nomad_job_test.go @@ -48,22 +48,6 @@ func TestAccDataSourceNomadJob_Basic(t *testing.T) { "data.nomad_job.test-job", "task_groups.0.update_strategy.0.auto_revert", "true"), resource.TestCheckResourceAttr( "data.nomad_job.test-job", "task_groups.0.update_strategy.0.canary", "1"), - resource.TestCheckResourceAttr( - "data.nomad_job.test-job", "deployment_state.#", "1"), - resource.TestCheckResourceAttrSet( - "data.nomad_job.test-job", "deployment_state.0.id"), - resource.TestCheckResourceAttrSet( - "data.nomad_job.test-job", "deployment_state.0.status"), - resource.TestCheckResourceAttr( - "data.nomad_job.test-job", "deployment_state.0.task_groups.#", "1"), - resource.TestCheckResourceAttrSet( - "data.nomad_job.test-job", "deployment_state.0.task_groups.0.desired_total"), - resource.TestCheckResourceAttrSet( - "data.nomad_job.test-job", "deployment_state.0.task_groups.0.placed_allocs"), - resource.TestCheckResourceAttrSet( - "data.nomad_job.test-job", "deployment_state.0.task_groups.0.healthy_allocs"), - resource.TestCheckResourceAttrSet( - "data.nomad_job.test-job", "deployment_state.0.task_groups.0.unhealthy_allocs"), ), }, }, diff --git a/nomad/resource_job.go b/nomad/resource_job.go index 6598c084..bac88ec1 100644 --- a/nomad/resource_job.go +++ b/nomad/resource_job.go @@ -302,8 +302,7 @@ func resourceJob() *schema.Resource { }, }, - "task_groups": taskGroupSchema(), - "deployment_state": deploymentStateSchema(), + "task_groups": taskGroupSchema(), "purge_on_destroy": { Description: "Whether to purge the job when the resource is destroyed.", @@ -421,79 +420,6 @@ func taskGroupSchema() *schema.Schema { } } -func deploymentStateSchema() *schema.Schema { - return &schema.Schema{ - Description: "State from the latest deployment for the job, if one exists.", - Computed: true, - Type: schema.TypeList, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "id": { - Computed: true, - Type: schema.TypeString, - }, - "status": { - Computed: true, - Type: schema.TypeString, - }, - "status_description": { - Computed: true, - Type: schema.TypeString, - }, - "task_groups": deploymentTaskGroupStateSchema(), - }, - }, - } -} - -func deploymentTaskGroupStateSchema() *schema.Schema { - return &schema.Schema{ - Computed: true, - Type: schema.TypeList, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Computed: true, - Type: schema.TypeString, - }, - "placed_canaries": { - Computed: true, - Type: schema.TypeList, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "auto_revert": { - Computed: true, - Type: schema.TypeBool, - }, - "promoted": { - Computed: true, - Type: schema.TypeBool, - }, - "desired_canaries": { - Computed: true, - Type: schema.TypeInt, - }, - "desired_total": { - Computed: true, - Type: schema.TypeInt, - }, - "placed_allocs": { - Computed: true, - Type: schema.TypeInt, - }, - "healthy_allocs": { - Computed: true, - Type: schema.TypeInt, - }, - "unhealthy_allocs": { - Computed: true, - Type: schema.TypeInt, - }, - }, - }, - } -} - func updateStrategySchema() *schema.Schema { return &schema.Schema{ Description: "The update strategy for rolling updates and canary deployments.", @@ -846,9 +772,7 @@ func resourceJobRead(d *schema.ResourceData, meta interface{}) error { d.Set("update_strategy", flattenUpdateStrategy(job.Update)) d.Set("periodic_config", flattenPeriodicConfig(job.Periodic)) - deployment := latestDeployment(client, id, opts) d.Set("task_groups", jobTaskGroupsRaw(job.TaskGroups)) - d.Set("deployment_state", deploymentStateRaw(deployment)) if d.Get("read_allocation_ids").(bool) { allocStubs, _, err := client.Jobs().Allocations(id, false, opts) @@ -932,7 +856,6 @@ func resourceJobCustomizeDiff(_ context.Context, d *schema.ResourceDiff, meta in d.SetNewComputed("datacenters") d.SetNewComputed("allocation_ids") d.SetNewComputed("task_groups") - d.SetNewComputed("deployment_state") d.SetNewComputed("deployment_id") d.SetNewComputed("deployment_status") d.SetNewComputed("status") @@ -1043,8 +966,6 @@ func resourceJobCustomizeDiff(_ context.Context, d *schema.ResourceDiff, meta in d.SetNewComputed("modify_index") // similarly, we won't know the allocation ids until after the job registration eval d.SetNewComputed("allocation_ids") - d.SetNewComputed("deployment_state") - canonicalizeTaskGroupUpdateStrategies(job) plannedTaskGroups := jobTaskGroupsRaw(job.TaskGroups) d.SetNew("task_groups", plannedTaskGroups) @@ -1076,15 +997,6 @@ func canonicalizeTaskGroupUpdateStrategies(job *api.Job) { } } -func latestDeployment(client *api.Client, jobID string, opts *api.QueryOptions) *api.Deployment { - deployment, _, err := client.Jobs().LatestDeployment(jobID, opts) - if err != nil { - log.Printf("[WARN] error reading latest deployment for Job %q: %s", jobID, err) - return nil - } - return deployment -} - func flattenPeriodicConfig(periodic *api.PeriodicConfig) []map[string]interface{} { if periodic == nil { return nil @@ -1317,48 +1229,6 @@ func jobTaskGroupsRaw(tgs []*api.TaskGroup) []interface{} { return ret } -func deploymentStateRaw(deployment *api.Deployment) []interface{} { - if deployment == nil { - return nil - } - - taskGroups := make([]interface{}, 0, len(deployment.TaskGroups)) - for name, state := range deployment.TaskGroups { - if state == nil { - continue - } - - placedCanaries := make([]interface{}, len(state.PlacedCanaries)) - for i, canary := range state.PlacedCanaries { - placedCanaries[i] = canary - } - - taskGroups = append(taskGroups, map[string]interface{}{ - "name": name, - "placed_canaries": placedCanaries, - "auto_revert": state.AutoRevert, - "promoted": state.Promoted, - "desired_canaries": state.DesiredCanaries, - "desired_total": state.DesiredTotal, - "placed_allocs": state.PlacedAllocs, - "healthy_allocs": state.HealthyAllocs, - "unhealthy_allocs": state.UnhealthyAllocs, - }) - } - - sort.Slice(taskGroups, func(i, j int) bool { - return taskGroups[i].(map[string]interface{})["name"].(string) < - taskGroups[j].(map[string]interface{})["name"].(string) - }) - - return []interface{}{map[string]interface{}{ - "id": deployment.ID, - "status": deployment.Status, - "status_description": deployment.StatusDescription, - "task_groups": taskGroups, - }} -} - func flattenJobConstraints(constraints []*api.Constraint) []map[string]interface{} { result := make([]map[string]interface{}, 0, len(constraints)) for _, c := range constraints { diff --git a/nomad/resource_job_test.go b/nomad/resource_job_test.go index ca8e10c5..0e89f68e 100644 --- a/nomad/resource_job_test.go +++ b/nomad/resource_job_test.go @@ -2392,50 +2392,6 @@ func TestVolumeSorting(t *testing.T) { require.ElementsMatch(tg1, tg2) } -func TestDeploymentStateRaw(t *testing.T) { - require := require.New(t) - - groupName := "group" - deployment := &api.Deployment{ - TaskGroups: map[string]*api.DeploymentState{ - groupName: { - PlacedCanaries: []string{"canary-alloc"}, - AutoRevert: true, - Promoted: true, - DesiredCanaries: 1, - DesiredTotal: 2, - PlacedAllocs: 2, - HealthyAllocs: 2, - UnhealthyAllocs: 0, - }, - }, - } - - deploymentRaw := deploymentStateRaw(deployment) - require.Len(deploymentRaw, 1) - - stateRaw, ok := deploymentRaw[0].(map[string]interface{}) - require.True(ok) - require.Equal("", stateRaw["id"]) - require.Equal("", stateRaw["status"]) - require.Equal("", stateRaw["status_description"]) - groupsRaw, ok := stateRaw["task_groups"].([]interface{}) - require.True(ok) - require.Len(groupsRaw, 1) - - groupRaw, ok := groupsRaw[0].(map[string]interface{}) - require.True(ok) - require.Equal(groupName, groupRaw["name"]) - require.Equal([]interface{}{"canary-alloc"}, groupRaw["placed_canaries"]) - require.Equal(true, groupRaw["auto_revert"]) - require.Equal(true, groupRaw["promoted"]) - require.Equal(1, groupRaw["desired_canaries"]) - require.Equal(2, groupRaw["desired_total"]) - require.Equal(2, groupRaw["placed_allocs"]) - require.Equal(2, groupRaw["healthy_allocs"]) - require.Equal(0, groupRaw["unhealthy_allocs"]) -} - var testResourceJob_invalidNomadServerConfig = ` provider "nomad" { alias = "tf_test" diff --git a/website/docs/d/job.html.markdown b/website/docs/d/job.html.markdown index ab2c63e9..14e4c0bd 100644 --- a/website/docs/d/job.html.markdown +++ b/website/docs/d/job.html.markdown @@ -94,17 +94,3 @@ The following attributes are exported: * `spec_type`: `(string)` Type of periodic specification, such as `cron`. * `prohibit_overlap`: `(boolean)` Whether this job should wait until previous instances of the same job have completed before launching again. * `timezone`: `(string)` Time zone used to evaluate the next launch interval. -* `deployment_state`: `(list of maps)` State from the latest deployment for the job, if one exists. This data is typically empty for job types without deployments, such as `batch` and `sysbatch`. - * `id`: `(string)` ID of the latest deployment. - * `status`: `(string)` Status of the latest deployment. - * `status_description`: `(string)` Status description of the latest deployment. - * `task_groups`: `(list of maps)` Deployment state keyed by task group. - * `name`: `(string)` Task group name. - * `placed_canaries`: `(list of strings)` Allocations placed as canaries for the task group. - * `auto_revert`: `(boolean)` Whether the latest deployment for the task group is marked for auto-revert. - * `promoted`: `(boolean)` Whether the canary deployment has been promoted. - * `desired_canaries`: `(integer)` Desired number of canaries. - * `desired_total`: `(integer)` Desired total number of allocations. - * `placed_allocs`: `(integer)` Number of placed allocations. - * `healthy_allocs`: `(integer)` Number of healthy allocations. - * `unhealthy_allocs`: `(integer)` Number of unhealthy allocations. diff --git a/website/docs/r/job.html.markdown b/website/docs/r/job.html.markdown index 2427fef3..0ecbc60b 100644 --- a/website/docs/r/job.html.markdown +++ b/website/docs/r/job.html.markdown @@ -330,21 +330,6 @@ The following attributes are exported: - `read_only` `(boolean)` - Whether the volume is read-only. - `source` `(string)` - Volume source. - `meta` `(map of strings)` - Task group metadata. -- `deployment_state` `(list of maps)` - State from the latest deployment for the job, if one exists. This data is typically empty for job types without deployments, such as `batch` and `sysbatch`. - - `id` `(string)` - ID of the latest deployment. - - `status` `(string)` - Status of the latest deployment. - - `status_description` `(string)` - Status description of the latest deployment. - - `task_groups` `(list of maps)` - Deployment state keyed by task group. - - `name` `(string)` - Task group name. - - `placed_canaries` `(list of strings)` - Allocations placed as canaries for the task group. - - `auto_revert` `(boolean)` - Whether the latest deployment for the task group is marked for auto-revert. - - `promoted` `(boolean)` - Whether the canary deployment has been promoted. - - `desired_canaries` `(integer)` - Desired number of canaries. - - `desired_total` `(integer)` - Desired total number of allocations. - - `placed_allocs` `(integer)` - Number of placed allocations. - - `healthy_allocs` `(integer)` - Number of healthy allocations. - - `unhealthy_allocs` `(integer)` - Number of unhealthy allocations. - ### Timeouts `nomad_job` provides the following [`Timeouts`][tf_docs_timeouts] configuration