From b25b6d9d9c72005abae33c24719ab205f121339f Mon Sep 17 00:00:00 2001 From: Pranshu Pravinkumar Pandya Date: Tue, 23 Jun 2026 14:58:49 +0200 Subject: [PATCH 1/3] Add warning when a job task has no retry policy set Adds a fast validation that emits a warning when a DABs job task does not configure max_retries. Such tasks are never retried on failure, which is a common source of avoidable job failures. An explicit max_retries (including 0) is treated as a deliberate choice and does not warn. --- bundle/config/validate/fast_validate.go | 1 + bundle/config/validate/job_task_retry_set.go | 63 +++++++ .../validate/job_task_retry_set_test.go | 155 ++++++++++++++++++ 3 files changed, 219 insertions(+) create mode 100644 bundle/config/validate/job_task_retry_set.go create mode 100644 bundle/config/validate/job_task_retry_set_test.go diff --git a/bundle/config/validate/fast_validate.go b/bundle/config/validate/fast_validate.go index d01eb8c1491..a809e6acdf6 100644 --- a/bundle/config/validate/fast_validate.go +++ b/bundle/config/validate/fast_validate.go @@ -29,6 +29,7 @@ func (f *fastValidate) Apply(ctx context.Context, rb *bundle.Bundle) diag.Diagno // Fast mutators with only in-memory checks JobClusterKeyDefined(), JobTaskClusterSpec(), + JobTaskRetrySet(), // Blocking mutators. Deployments will fail if these checks fail. ValidateArtifactPath(), diff --git a/bundle/config/validate/job_task_retry_set.go b/bundle/config/validate/job_task_retry_set.go new file mode 100644 index 00000000000..50e1c086711 --- /dev/null +++ b/bundle/config/validate/job_task_retry_set.go @@ -0,0 +1,63 @@ +package validate + +import ( + "context" + + "github.com/databricks/cli/bundle" + "github.com/databricks/cli/libs/diag" + "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/log" +) + +// JobTaskRetrySet warns when a job task does not configure a retry policy. +// Without max_retries, a task that fails with a transient error is not retried, +// which is a common source of avoidable job failures. +func JobTaskRetrySet() bundle.ReadOnlyMutator { + return &jobTaskRetrySet{} +} + +type jobTaskRetrySet struct{ bundle.RO } + +func (v *jobTaskRetrySet) Name() string { + return "validate:job_task_retry_set" +} + +const jobTaskRetryWarningSummary = "Task retry policy is not set" + +const jobTaskRetryWarningDetail = `No max_retries is configured for this task, so it will not be retried if it fails. +Set max_retries on the task to retry transient failures, or set it to 0 to explicitly disable retries.` + +func (v *jobTaskRetrySet) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { + diags := diag.Diagnostics{} + + patterns := []dyn.Pattern{ + // Job tasks + dyn.NewPattern(dyn.Key("resources"), dyn.Key("jobs"), dyn.AnyKey(), dyn.Key("tasks"), dyn.AnyIndex()), + // Job for_each_task subtasks + dyn.NewPattern(dyn.Key("resources"), dyn.Key("jobs"), dyn.AnyKey(), dyn.Key("tasks"), dyn.AnyIndex(), dyn.Key("for_each_task"), dyn.Key("task")), + } + + for _, p := range patterns { + _, err := dyn.MapByPattern(b.Config.Value(), p, func(p dyn.Path, task dyn.Value) (dyn.Value, error) { + // max_retries set (including an explicit 0) means the user has made a + // deliberate choice about retries, so we don't warn. + if task.Get("max_retries").IsValid() { + return task, nil + } + + diags = append(diags, diag.Diagnostic{ + Severity: diag.Warning, + Summary: jobTaskRetryWarningSummary, + Detail: jobTaskRetryWarningDetail, + Locations: task.Locations(), + Paths: []dyn.Path{p}, + }) + return task, nil + }) + if err != nil { + log.Debugf(ctx, "Error while applying job task retry validation: %s", err) + } + } + + return diags +} diff --git a/bundle/config/validate/job_task_retry_set_test.go b/bundle/config/validate/job_task_retry_set_test.go new file mode 100644 index 00000000000..582bcee2ad6 --- /dev/null +++ b/bundle/config/validate/job_task_retry_set_test.go @@ -0,0 +1,155 @@ +package validate + +import ( + "testing" + + "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/cli/bundle/internal/bundletest" + "github.com/databricks/cli/libs/diag" + "github.com/databricks/cli/libs/dyn" + "github.com/databricks/databricks-sdk-go/service/jobs" + "github.com/stretchr/testify/assert" +) + +func TestJobTaskRetrySetWarnsWhenUnset(t *testing.T) { + ctx := t.Context() + + b := &bundle.Bundle{ + Config: config.Root{ + Resources: config.Resources{ + Jobs: map[string]*resources.Job{ + "foo": { + JobSettings: jobs.JobSettings{ + Tasks: []jobs.Task{ + { + TaskKey: "my_task", + NotebookTask: &jobs.NotebookTask{}, + }, + }, + }, + }, + }, + }, + }, + } + + bundletest.SetLocation(b, "resources.jobs.foo.tasks[0]", []dyn.Location{{File: "a.yml", Line: 1, Column: 1}}) + + diags := JobTaskRetrySet().Apply(ctx, b) + assert.Equal(t, diag.Diagnostics{ + { + Severity: diag.Warning, + Summary: jobTaskRetryWarningSummary, + Detail: jobTaskRetryWarningDetail, + Locations: []dyn.Location{{File: "a.yml", Line: 1, Column: 1}}, + Paths: []dyn.Path{dyn.MustPathFromString("resources.jobs.foo.tasks[0]")}, + }, + }, diags) +} + +func TestJobTaskRetrySetNoWarningWhenSet(t *testing.T) { + ctx := t.Context() + + b := &bundle.Bundle{ + Config: config.Root{ + Resources: config.Resources{ + Jobs: map[string]*resources.Job{ + "foo": { + JobSettings: jobs.JobSettings{ + Tasks: []jobs.Task{ + { + TaskKey: "my_task", + NotebookTask: &jobs.NotebookTask{}, + MaxRetries: 3, + }, + }, + }, + }, + }, + }, + }, + } + + bundletest.SetLocation(b, "resources.jobs.foo.tasks[0]", []dyn.Location{{File: "a.yml", Line: 1, Column: 1}}) + + diags := JobTaskRetrySet().Apply(ctx, b) + assert.Empty(t, diags) +} + +func TestJobTaskRetrySetNoWarningWhenExplicitZero(t *testing.T) { + ctx := t.Context() + + b := &bundle.Bundle{ + Config: config.Root{ + Resources: config.Resources{ + Jobs: map[string]*resources.Job{ + "foo": { + JobSettings: jobs.JobSettings{ + Tasks: []jobs.Task{ + { + TaskKey: "my_task", + NotebookTask: &jobs.NotebookTask{}, + }, + }, + }, + }, + }, + }, + }, + } + + bundletest.SetLocation(b, "resources.jobs.foo.tasks[0]", []dyn.Location{{File: "a.yml", Line: 1, Column: 1}}) + + // max_retries: 0 is omitted by typed conversion, so set it explicitly on the + // dyn value to exercise the deliberate "never retry" case. + bundletest.Mutate(t, b, func(v dyn.Value) (dyn.Value, error) { + return dyn.Set(v, "resources.jobs.foo.tasks[0].max_retries", dyn.V(0)) + }) + + diags := JobTaskRetrySet().Apply(ctx, b) + assert.Empty(t, diags) +} + +func TestJobTaskRetrySetWarnsForEachTask(t *testing.T) { + ctx := t.Context() + + b := &bundle.Bundle{ + Config: config.Root{ + Resources: config.Resources{ + Jobs: map[string]*resources.Job{ + "foo": { + JobSettings: jobs.JobSettings{ + Tasks: []jobs.Task{ + { + TaskKey: "my_task", + MaxRetries: 3, + ForEachTask: &jobs.ForEachTask{ + Task: jobs.Task{ + TaskKey: "inner", + NotebookTask: &jobs.NotebookTask{}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + bundletest.SetLocation(b, "resources.jobs.foo.tasks[0].for_each_task.task", []dyn.Location{{File: "a.yml", Line: 1, Column: 1}}) + + diags := JobTaskRetrySet().Apply(ctx, b) + assert.Equal(t, diag.Diagnostics{ + { + Severity: diag.Warning, + Summary: jobTaskRetryWarningSummary, + Detail: jobTaskRetryWarningDetail, + Locations: []dyn.Location{{File: "a.yml", Line: 1, Column: 1}}, + Paths: []dyn.Path{dyn.MustPathFromString("resources.jobs.foo.tasks[0].for_each_task.task")}, + }, + }, diags) +} From 79dcdb9d8f313cb8e5327c35db3667e38d481972 Mon Sep 17 00:00:00 2001 From: Pranshu Pravinkumar Pandya Date: Tue, 23 Jun 2026 15:29:26 +0200 Subject: [PATCH 2/3] Warn on continuous jobs missing task_retry_mode instead of per-task task_retry_mode is a property of a job's continuous block (enum NEVER | ON_FAILURE, default NEVER) that controls whether a continuous job retries its tasks. The warning now fires only when a job defines a continuous block without task_retry_mode, rather than on every task missing max_retries. --- .../validate/continuous_task_retry_mode.go | 56 +++++++ .../continuous_task_retry_mode_test.go | 98 +++++++++++ bundle/config/validate/fast_validate.go | 2 +- bundle/config/validate/job_task_retry_set.go | 63 ------- .../validate/job_task_retry_set_test.go | 155 ------------------ 5 files changed, 155 insertions(+), 219 deletions(-) create mode 100644 bundle/config/validate/continuous_task_retry_mode.go create mode 100644 bundle/config/validate/continuous_task_retry_mode_test.go delete mode 100644 bundle/config/validate/job_task_retry_set.go delete mode 100644 bundle/config/validate/job_task_retry_set_test.go diff --git a/bundle/config/validate/continuous_task_retry_mode.go b/bundle/config/validate/continuous_task_retry_mode.go new file mode 100644 index 00000000000..e0297608ecc --- /dev/null +++ b/bundle/config/validate/continuous_task_retry_mode.go @@ -0,0 +1,56 @@ +package validate + +import ( + "context" + + "github.com/databricks/cli/bundle" + "github.com/databricks/cli/libs/diag" + "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/log" +) + +// ContinuousTaskRetryMode warns when a continuous job does not set task_retry_mode. +// task_retry_mode defaults to NEVER, so the tasks of a continuous job are not +// retried on failure unless it is explicitly set to ON_FAILURE. +func ContinuousTaskRetryMode() bundle.ReadOnlyMutator { + return &continuousTaskRetryMode{} +} + +type continuousTaskRetryMode struct{ bundle.RO } + +func (v *continuousTaskRetryMode) Name() string { + return "validate:continuous_task_retry_mode" +} + +const continuousTaskRetryWarningSummary = "Continuous job does not set task_retry_mode" + +const continuousTaskRetryWarningDetail = `task_retry_mode is not set on this continuous job, so it defaults to NEVER and the job's tasks are not retried on failure. +Set continuous.task_retry_mode to ON_FAILURE to enable task-level retries, or to NEVER to silence this warning.` + +func (v *continuousTaskRetryMode) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { + diags := diag.Diagnostics{} + + pattern := dyn.NewPattern(dyn.Key("resources"), dyn.Key("jobs"), dyn.AnyKey(), dyn.Key("continuous")) + + _, err := dyn.MapByPattern(b.Config.Value(), pattern, func(p dyn.Path, continuous dyn.Value) (dyn.Value, error) { + // An explicit task_retry_mode (NEVER or ON_FAILURE) is a deliberate + // choice, so we don't warn. + if continuous.Get("task_retry_mode").IsValid() { + return continuous, nil + } + + diags = append(diags, diag.Diagnostic{ + Severity: diag.Warning, + Summary: continuousTaskRetryWarningSummary, + Detail: continuousTaskRetryWarningDetail, + Locations: continuous.Locations(), + Paths: []dyn.Path{p}, + }) + return continuous, nil + }) + if err != nil { + log.Debugf(ctx, "Error while applying continuous task retry mode validation: %s", err) + } + + return diags +} diff --git a/bundle/config/validate/continuous_task_retry_mode_test.go b/bundle/config/validate/continuous_task_retry_mode_test.go new file mode 100644 index 00000000000..9eac010733b --- /dev/null +++ b/bundle/config/validate/continuous_task_retry_mode_test.go @@ -0,0 +1,98 @@ +package validate + +import ( + "testing" + + "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/cli/bundle/internal/bundletest" + "github.com/databricks/cli/libs/diag" + "github.com/databricks/cli/libs/dyn" + "github.com/databricks/databricks-sdk-go/service/jobs" + "github.com/stretchr/testify/assert" +) + +func TestContinuousTaskRetryModeWarnsWhenUnset(t *testing.T) { + ctx := t.Context() + + b := &bundle.Bundle{ + Config: config.Root{ + Resources: config.Resources{ + Jobs: map[string]*resources.Job{ + "foo": { + JobSettings: jobs.JobSettings{ + Continuous: &jobs.Continuous{}, + }, + }, + }, + }, + }, + } + + bundletest.SetLocation(b, "resources.jobs.foo.continuous", []dyn.Location{{File: "a.yml", Line: 1, Column: 1}}) + + diags := ContinuousTaskRetryMode().Apply(ctx, b) + assert.Equal(t, diag.Diagnostics{ + { + Severity: diag.Warning, + Summary: continuousTaskRetryWarningSummary, + Detail: continuousTaskRetryWarningDetail, + Locations: []dyn.Location{{File: "a.yml", Line: 1, Column: 1}}, + Paths: []dyn.Path{dyn.MustPathFromString("resources.jobs.foo.continuous")}, + }, + }, diags) +} + +func TestContinuousTaskRetryModeNoWarningWhenSet(t *testing.T) { + ctx := t.Context() + + b := &bundle.Bundle{ + Config: config.Root{ + Resources: config.Resources{ + Jobs: map[string]*resources.Job{ + "foo": { + JobSettings: jobs.JobSettings{ + Continuous: &jobs.Continuous{ + TaskRetryMode: jobs.TaskRetryModeOnFailure, + }, + }, + }, + }, + }, + }, + } + + bundletest.SetLocation(b, "resources.jobs.foo.continuous", []dyn.Location{{File: "a.yml", Line: 1, Column: 1}}) + + diags := ContinuousTaskRetryMode().Apply(ctx, b) + assert.Empty(t, diags) +} + +func TestContinuousTaskRetryModeNoWarningWithoutContinuous(t *testing.T) { + ctx := t.Context() + + b := &bundle.Bundle{ + Config: config.Root{ + Resources: config.Resources{ + Jobs: map[string]*resources.Job{ + "foo": { + JobSettings: jobs.JobSettings{ + Tasks: []jobs.Task{ + { + TaskKey: "my_task", + NotebookTask: &jobs.NotebookTask{}, + }, + }, + }, + }, + }, + }, + }, + } + + bundletest.SetLocation(b, "resources.jobs.foo", []dyn.Location{{File: "a.yml", Line: 1, Column: 1}}) + + diags := ContinuousTaskRetryMode().Apply(ctx, b) + assert.Empty(t, diags) +} diff --git a/bundle/config/validate/fast_validate.go b/bundle/config/validate/fast_validate.go index a809e6acdf6..2ca7dbd6426 100644 --- a/bundle/config/validate/fast_validate.go +++ b/bundle/config/validate/fast_validate.go @@ -29,7 +29,7 @@ func (f *fastValidate) Apply(ctx context.Context, rb *bundle.Bundle) diag.Diagno // Fast mutators with only in-memory checks JobClusterKeyDefined(), JobTaskClusterSpec(), - JobTaskRetrySet(), + ContinuousTaskRetryMode(), // Blocking mutators. Deployments will fail if these checks fail. ValidateArtifactPath(), diff --git a/bundle/config/validate/job_task_retry_set.go b/bundle/config/validate/job_task_retry_set.go deleted file mode 100644 index 50e1c086711..00000000000 --- a/bundle/config/validate/job_task_retry_set.go +++ /dev/null @@ -1,63 +0,0 @@ -package validate - -import ( - "context" - - "github.com/databricks/cli/bundle" - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/log" -) - -// JobTaskRetrySet warns when a job task does not configure a retry policy. -// Without max_retries, a task that fails with a transient error is not retried, -// which is a common source of avoidable job failures. -func JobTaskRetrySet() bundle.ReadOnlyMutator { - return &jobTaskRetrySet{} -} - -type jobTaskRetrySet struct{ bundle.RO } - -func (v *jobTaskRetrySet) Name() string { - return "validate:job_task_retry_set" -} - -const jobTaskRetryWarningSummary = "Task retry policy is not set" - -const jobTaskRetryWarningDetail = `No max_retries is configured for this task, so it will not be retried if it fails. -Set max_retries on the task to retry transient failures, or set it to 0 to explicitly disable retries.` - -func (v *jobTaskRetrySet) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - diags := diag.Diagnostics{} - - patterns := []dyn.Pattern{ - // Job tasks - dyn.NewPattern(dyn.Key("resources"), dyn.Key("jobs"), dyn.AnyKey(), dyn.Key("tasks"), dyn.AnyIndex()), - // Job for_each_task subtasks - dyn.NewPattern(dyn.Key("resources"), dyn.Key("jobs"), dyn.AnyKey(), dyn.Key("tasks"), dyn.AnyIndex(), dyn.Key("for_each_task"), dyn.Key("task")), - } - - for _, p := range patterns { - _, err := dyn.MapByPattern(b.Config.Value(), p, func(p dyn.Path, task dyn.Value) (dyn.Value, error) { - // max_retries set (including an explicit 0) means the user has made a - // deliberate choice about retries, so we don't warn. - if task.Get("max_retries").IsValid() { - return task, nil - } - - diags = append(diags, diag.Diagnostic{ - Severity: diag.Warning, - Summary: jobTaskRetryWarningSummary, - Detail: jobTaskRetryWarningDetail, - Locations: task.Locations(), - Paths: []dyn.Path{p}, - }) - return task, nil - }) - if err != nil { - log.Debugf(ctx, "Error while applying job task retry validation: %s", err) - } - } - - return diags -} diff --git a/bundle/config/validate/job_task_retry_set_test.go b/bundle/config/validate/job_task_retry_set_test.go deleted file mode 100644 index 582bcee2ad6..00000000000 --- a/bundle/config/validate/job_task_retry_set_test.go +++ /dev/null @@ -1,155 +0,0 @@ -package validate - -import ( - "testing" - - "github.com/databricks/cli/bundle" - "github.com/databricks/cli/bundle/config" - "github.com/databricks/cli/bundle/config/resources" - "github.com/databricks/cli/bundle/internal/bundletest" - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/databricks-sdk-go/service/jobs" - "github.com/stretchr/testify/assert" -) - -func TestJobTaskRetrySetWarnsWhenUnset(t *testing.T) { - ctx := t.Context() - - b := &bundle.Bundle{ - Config: config.Root{ - Resources: config.Resources{ - Jobs: map[string]*resources.Job{ - "foo": { - JobSettings: jobs.JobSettings{ - Tasks: []jobs.Task{ - { - TaskKey: "my_task", - NotebookTask: &jobs.NotebookTask{}, - }, - }, - }, - }, - }, - }, - }, - } - - bundletest.SetLocation(b, "resources.jobs.foo.tasks[0]", []dyn.Location{{File: "a.yml", Line: 1, Column: 1}}) - - diags := JobTaskRetrySet().Apply(ctx, b) - assert.Equal(t, diag.Diagnostics{ - { - Severity: diag.Warning, - Summary: jobTaskRetryWarningSummary, - Detail: jobTaskRetryWarningDetail, - Locations: []dyn.Location{{File: "a.yml", Line: 1, Column: 1}}, - Paths: []dyn.Path{dyn.MustPathFromString("resources.jobs.foo.tasks[0]")}, - }, - }, diags) -} - -func TestJobTaskRetrySetNoWarningWhenSet(t *testing.T) { - ctx := t.Context() - - b := &bundle.Bundle{ - Config: config.Root{ - Resources: config.Resources{ - Jobs: map[string]*resources.Job{ - "foo": { - JobSettings: jobs.JobSettings{ - Tasks: []jobs.Task{ - { - TaskKey: "my_task", - NotebookTask: &jobs.NotebookTask{}, - MaxRetries: 3, - }, - }, - }, - }, - }, - }, - }, - } - - bundletest.SetLocation(b, "resources.jobs.foo.tasks[0]", []dyn.Location{{File: "a.yml", Line: 1, Column: 1}}) - - diags := JobTaskRetrySet().Apply(ctx, b) - assert.Empty(t, diags) -} - -func TestJobTaskRetrySetNoWarningWhenExplicitZero(t *testing.T) { - ctx := t.Context() - - b := &bundle.Bundle{ - Config: config.Root{ - Resources: config.Resources{ - Jobs: map[string]*resources.Job{ - "foo": { - JobSettings: jobs.JobSettings{ - Tasks: []jobs.Task{ - { - TaskKey: "my_task", - NotebookTask: &jobs.NotebookTask{}, - }, - }, - }, - }, - }, - }, - }, - } - - bundletest.SetLocation(b, "resources.jobs.foo.tasks[0]", []dyn.Location{{File: "a.yml", Line: 1, Column: 1}}) - - // max_retries: 0 is omitted by typed conversion, so set it explicitly on the - // dyn value to exercise the deliberate "never retry" case. - bundletest.Mutate(t, b, func(v dyn.Value) (dyn.Value, error) { - return dyn.Set(v, "resources.jobs.foo.tasks[0].max_retries", dyn.V(0)) - }) - - diags := JobTaskRetrySet().Apply(ctx, b) - assert.Empty(t, diags) -} - -func TestJobTaskRetrySetWarnsForEachTask(t *testing.T) { - ctx := t.Context() - - b := &bundle.Bundle{ - Config: config.Root{ - Resources: config.Resources{ - Jobs: map[string]*resources.Job{ - "foo": { - JobSettings: jobs.JobSettings{ - Tasks: []jobs.Task{ - { - TaskKey: "my_task", - MaxRetries: 3, - ForEachTask: &jobs.ForEachTask{ - Task: jobs.Task{ - TaskKey: "inner", - NotebookTask: &jobs.NotebookTask{}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - } - - bundletest.SetLocation(b, "resources.jobs.foo.tasks[0].for_each_task.task", []dyn.Location{{File: "a.yml", Line: 1, Column: 1}}) - - diags := JobTaskRetrySet().Apply(ctx, b) - assert.Equal(t, diag.Diagnostics{ - { - Severity: diag.Warning, - Summary: jobTaskRetryWarningSummary, - Detail: jobTaskRetryWarningDetail, - Locations: []dyn.Location{{File: "a.yml", Line: 1, Column: 1}}, - Paths: []dyn.Path{dyn.MustPathFromString("resources.jobs.foo.tasks[0].for_each_task.task")}, - }, - }, diags) -} From 759cd19d5ac3d31269d8e2d8b3875d62dfe63e22 Mon Sep 17 00:00:00 2001 From: Pranshu Pravinkumar Pandya Date: Tue, 23 Jun 2026 15:35:34 +0200 Subject: [PATCH 3/3] Add changelog entry and acceptance test inputs for task_retry_mode warning NEXT_CHANGELOG.md documents the new bundle validate warning. The acceptance test under acceptance/bundle/validate/continuous_task_retry_mode covers a continuous job missing task_retry_mode (warns) alongside one that sets it (no warning). Its output.txt and out.test.toml must be generated with `./task test-update`, which requires the Go 1.26.4 toolchain unavailable here. --- NEXT_CHANGELOG.md | 1 + .../continuous_task_retry_mode/databricks.yml | 25 +++++++++++++++++++ .../continuous_task_retry_mode/script | 1 + 3 files changed, 27 insertions(+) create mode 100644 acceptance/bundle/validate/continuous_task_retry_mode/databricks.yml create mode 100644 acceptance/bundle/validate/continuous_task_retry_mode/script diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index eb422ac238a..cc0cd0c73bf 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -7,6 +7,7 @@ ### CLI ### Bundles +* `bundle validate` now warns when a job defines a `continuous` block without `continuous.task_retry_mode`, since it defaults to `NEVER` and the continuous job's tasks are not retried on failure ([#5691](https://github.com/databricks/cli/pull/5691)). * Add documentation for the common bundle resource fields `permissions`, `lifecycle`, and `grants` in the JSON schema, so they surface in editor completions and the docs. * `bundle run` now prints the modern job run URL (`/jobs//runs/`) so that non-admin users permitted to view the run are taken to the run instead of the workspace homepage. * References to a registered model's `registered_model_id` now resolve under the direct engine, matching Terraform behavior ([#5621](https://github.com/databricks/cli/pull/5621)). diff --git a/acceptance/bundle/validate/continuous_task_retry_mode/databricks.yml b/acceptance/bundle/validate/continuous_task_retry_mode/databricks.yml new file mode 100644 index 00000000000..edb0de3f531 --- /dev/null +++ b/acceptance/bundle/validate/continuous_task_retry_mode/databricks.yml @@ -0,0 +1,25 @@ +bundle: + name: test-bundle + +resources: + jobs: + # Continuous job without task_retry_mode: should warn (defaults to NEVER). + continuous_no_retry: + name: continuous_no_retry + continuous: + pause_status: UNPAUSED + tasks: + - task_key: run_task + run_job_task: + job_id: 1234 + + # Continuous job with task_retry_mode set: should not warn. + continuous_with_retry: + name: continuous_with_retry + continuous: + pause_status: UNPAUSED + task_retry_mode: ON_FAILURE + tasks: + - task_key: run_task + run_job_task: + job_id: 1234 diff --git a/acceptance/bundle/validate/continuous_task_retry_mode/script b/acceptance/bundle/validate/continuous_task_retry_mode/script new file mode 100644 index 00000000000..5350876150f --- /dev/null +++ b/acceptance/bundle/validate/continuous_task_retry_mode/script @@ -0,0 +1 @@ +trace $CLI bundle validate