Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .nextchanges/bundles/job-runs-on-bundle-deploy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
direct: `resources.job_runs` can set `lifecycle.triggers.on_bundle_deploy: true` to re-fire the run on every bundle deploy. Removing the trigger does not recreate the existing run.
7 changes: 7 additions & 0 deletions acceptance/bundle/refschema/out.fields.txt
Original file line number Diff line number Diff line change
Expand Up @@ -845,8 +845,15 @@ resources.job_runs.*.jar_params[*] string ALL
resources.job_runs.*.job_id int64 ALL
resources.job_runs.*.job_parameters map[string]string ALL
resources.job_runs.*.job_parameters.* string ALL
resources.job_runs.*.lifecycle *dresources.JobRunLifecycleState STATE
resources.job_runs.*.lifecycle *resources.JobRunLifecycle INPUT
resources.job_runs.*.lifecycle resources.Lifecycle INPUT
resources.job_runs.*.lifecycle.prevent_destroy bool INPUT
resources.job_runs.*.lifecycle.triggers *dresources.JobRunTriggersState STATE
resources.job_runs.*.lifecycle.triggers []resources.JobRunTrigger INPUT
resources.job_runs.*.lifecycle.triggers.on_bundle_deploy string STATE
resources.job_runs.*.lifecycle.triggers[*] resources.JobRunTrigger INPUT
resources.job_runs.*.lifecycle.triggers[*].on_bundle_deploy *bool INPUT
resources.job_runs.*.modified_status string INPUT
resources.job_runs.*.notebook_params map[string]string ALL
resources.job_runs.*.notebook_params.* string ALL
Expand Down
2 changes: 1 addition & 1 deletion acceptance/bundle/resources/job_runs/basic/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ job run [MY_RUN_ID]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID]
job run [MY_RUN_ID]: SUCCESS
Created job_runs.my_run
Created jobs.my_job
Files: 5 uploaded, 0 deleted
Files: 4 uploaded, 0 deleted
Resources: 2 created, 0 changed, 0 deleted, 0 unchanged

>>> [CLI] bundle deploy
Expand Down
4 changes: 0 additions & 4 deletions acceptance/bundle/resources/job_runs/failed_run/test.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
# job_runs is a direct-engine-only resource; the Terraform provider has no
# equivalent, so restrict the matrix to direct.
EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"]

# Runs the failing job for real, so the message the deploy names the task with is
# one a workspace reported. Serverless needs Unity Catalog.
Cloud = true
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
# job_runs is a direct-engine-only resource; the Terraform provider has no
# equivalent, so restrict the matrix to direct.
EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"]

# The interruption is staged by injecting a fault into the testserver, so this
# stays off cloud.
RecordRequests = true
Ignore = ["tmp.plan.json"]
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ job run [MY_RUN_ID]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID]
job run [MY_RUN_ID]: SUCCESS
Created job_runs.my_run
Created jobs.my_job
Files: 5 uploaded, 0 deleted
Files: 4 uploaded, 0 deleted
Resources: 2 created, 0 changed, 0 deleted, 0 unchanged

>>> print_requests.py //jobs/run-now
Expand Down
4 changes: 0 additions & 4 deletions acceptance/bundle/resources/job_runs/job_parameters/test.toml

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
bundle:
name: job-runs-on-bundle-deploy

resources:
jobs:
my_job:
name: my-job
tasks:
- task_key: main
notebook_task:
notebook_path: /Workspace/test

job_runs:
my_run:
job_id: ${resources.jobs.my_job.id}
lifecycle:
triggers:
- on_bundle_deploy: true

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

145 changes: 145 additions & 0 deletions acceptance/bundle/resources/job_runs/on_bundle_deploy/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@

=== first deploy triggers a run
>>> [CLI] bundle deploy
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-bundle-deploy/default/files...
job run [MY_RUN_ID]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID]?o=[NUMID]
job run [MY_RUN_ID]: SUCCESS
Created job_runs.my_run
Created jobs.my_job
Files: 5 uploaded, 0 deleted
Resources: 2 created, 0 changed, 0 deleted, 0 unchanged

>>> read_id.py my_job
[MY_JOB_ID]

>>> print_requests.py //jobs/run-now
{
"method": "POST",
"path": "/api/2.2/jobs/run-now",
"body": {
"idempotency_token": "[UUID]",
"job_id": [MY_JOB_ID]
}
}

=== redeploy re-fires with unchanged config
>>> [CLI] bundle plan
recreate job_runs.my_run

Plan: 1 to add, 0 to change, 1 to delete, 1 unchanged

>>> jq .plan["resources.job_runs.my_run"].changes tmp.plan.json
{
"lifecycle": {
"action": "recreate",
"reason": "immutable",
"old": {
"triggers": {
"on_bundle_deploy": "[UUID]"
}
},
"new": {
"triggers": {
"on_bundle_deploy": "[UUID]"
}
}
},
"lifecycle.triggers.on_bundle_deploy": {
"action": "recreate",
"reason": "immutable",
"old": "[UUID]",
"new": "[UUID]"
},
"result_state": {
"action": "skip",
"reason": "remote_already_set",
"new": "SUCCESS",
"remote": "SUCCESS"
}
}

=== bundle deploy
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-bundle-deploy/default/files...
job run [MY_RUN_ID_2]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID_2]?o=[NUMID]
job run [MY_RUN_ID_2]: SUCCESS
Recreated job_runs.my_run
Files: 3 uploaded, 0 deleted
Resources: 1 created, 0 changed, 1 deleted, 1 unchanged

>>> [CLI] bundle summary
Name: job-runs-on-bundle-deploy
Target: default
Workspace:
User: [USERNAME]
Path: /Workspace/Users/[USERNAME]/.bundle/job-runs-on-bundle-deploy/default
Resources:
Job Runs:
my_run:
Name:
URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID_2]?w=[NUMID]
Jobs:
my_job:
Name: my-job
URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]?w=[NUMID]

=== second run-now after recreate
>>> print_requests.py --keep //jobs/runs/delete
{
"method": "POST",
"path": "/api/2.2/jobs/runs/delete",
"body": {
"run_id": [MY_RUN_ID]
}
}

>>> print_requests.py //jobs/run-now
{
"method": "POST",
"path": "/api/2.2/jobs/run-now",
"body": {
"idempotency_token": "[UUID]",
"job_id": [MY_JOB_ID]
}
}

=== removing on_bundle_deploy rewrites state without a run
>>> [CLI] bundle plan
update job_runs.my_run

Plan: 0 to add, 1 to change, 0 to delete, 1 unchanged

>>> jq .plan["resources.job_runs.my_run"].changes tmp.plan.json
{
"lifecycle": {
"action": "update",
Comment thread
radakam marked this conversation as resolved.
"reason": "trigger removed",
"old": {
"triggers": {
"on_bundle_deploy": "[UUID]"
}
}
},
"result_state": {
"action": "skip",
"reason": "remote_already_set",
"new": "SUCCESS",
"remote": "SUCCESS"
}
}

=== bundle deploy
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-bundle-deploy/default/files...
Updated job_runs.my_run
Files: 4 uploaded, 0 deleted
Resources: 0 created, 1 changed, 0 deleted, 1 unchanged

>>> print_requests.py //jobs/run-now

>>> [CLI] bundle destroy --auto-approve
The following resources will be deleted:
delete resources.job_runs.my_run
delete resources.jobs.my_job

All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/job-runs-on-bundle-deploy/default

Destroy: 2 deleted
41 changes: 41 additions & 0 deletions acceptance/bundle/resources/job_runs/on_bundle_deploy/script
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
cleanup() {
trace $CLI bundle destroy --auto-approve
rm -f out.requests.txt
}
trap cleanup EXIT

title "first deploy triggers a run"
trace $CLI bundle deploy
trace read_id.py my_job
# Name the first run so the second becomes [MY_RUN_ID_2].
read_id.py my_run > /dev/null
trace print_requests.py //jobs/run-now

title "redeploy re-fires with unchanged config"
trace $CLI bundle plan
Comment thread
radakam marked this conversation as resolved.
# Save the plan so the READPLAN=1 variant deploys the fingerprint minted here.
# The deploys are not traced: readplanarg makes the command line differ per variant.
$CLI bundle plan -o json > tmp.plan.json
Comment thread
radakam marked this conversation as resolved.
trace jq '.plan["resources.job_runs.my_run"].changes' tmp.plan.json
title "bundle deploy\n"
$CLI bundle deploy $(readplanarg tmp.plan.json)
read_id.py my_run > /dev/null
trace $CLI bundle summary

title "second run-now after recreate"
trace print_requests.py --keep //jobs/runs/delete
trace print_requests.py //jobs/run-now

title "removing on_bundle_deploy rewrites state without a run"
# Drop the lifecycle block so the next plan compares a nil fingerprint against
# the UUID left in state: a state-only update that clears it, never a recreate.
update_file.py databricks.yml " lifecycle:
triggers:
- on_bundle_deploy: true
" ""
trace $CLI bundle plan
$CLI bundle plan -o json > tmp.plan.json
trace jq '.plan["resources.job_runs.my_run"].changes' tmp.plan.json
title "bundle deploy\n"
$CLI bundle deploy $(readplanarg tmp.plan.json)
trace print_requests.py //jobs/run-now
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Deploy both by re-planning and from a plan saved on disk, so the fingerprint
# minted during planning is exercised across plan serialization.
EnvMatrix.READPLAN = ["", "1"]

# The saved plan embeds a fresh fingerprint on every run.
Ignore = ["tmp.plan.json"]
2 changes: 1 addition & 1 deletion acceptance/bundle/resources/job_runs/redeploy/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ job run [MY_RUN_ID]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID]
job run [MY_RUN_ID]: SUCCESS
Created job_runs.my_run
Created jobs.my_job
Files: 5 uploaded, 0 deleted
Files: 4 uploaded, 0 deleted
Resources: 2 created, 0 changed, 0 deleted, 0 unchanged

>>> [CLI] bundle summary
Expand Down
4 changes: 0 additions & 4 deletions acceptance/bundle/resources/job_runs/redeploy/test.toml

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
# job_runs is a direct-engine-only resource; the Terraform provider has no
# equivalent, so restrict the matrix to direct.
EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"]

# Local only: the lost response is staged by injecting a fault into the testserver.
RecordRequests = true

Ignore = [".databricks"]

# Number each distinct token so a reused token shows as [0] on both requests.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# job_runs is a direct-engine-only resource; the Terraform provider has no
# equivalent, so restrict the matrix to direct.
EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"]
RecordRequests = true
4 changes: 0 additions & 4 deletions acceptance/bundle/resources/job_runs/wait/test.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
# job_runs is a direct-engine-only resource; the Terraform provider has no
# equivalent, so restrict the matrix to direct.
EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"]

# Runs the job for real on cloud. Serverless needs Unity Catalog.
Cloud = true
RequiresUnityCatalog = true
Expand Down
56 changes: 56 additions & 0 deletions bundle/config/mutator/validate_job_run_triggers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package mutator

import (
"context"
"fmt"

"github.com/databricks/cli/bundle"
"github.com/databricks/cli/libs/diag"
)

type validateJobRunTriggers struct{}

// ValidateJobRunTriggers rejects invalid lifecycle.triggers on job_runs.
func ValidateJobRunTriggers() bundle.Mutator {
return &validateJobRunTriggers{}
}

func (*validateJobRunTriggers) Name() string {
return "ValidateJobRunTriggers"
}

func (*validateJobRunTriggers) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnostics {
var diags diag.Diagnostics
for name, jr := range b.Config.Resources.JobRuns {
if jr == nil || jr.Lifecycle == nil {
continue
}
// Recreate-every-deploy cannot coexist with prevent_destroy.
if jr.HasOnBundleDeploy() && jr.Lifecycle.PreventDestroy {
diags = diags.Append(diag.Diagnostic{
Severity: diag.Error,
Summary: "lifecycle.triggers.on_bundle_deploy is incompatible with lifecycle.prevent_destroy",
Locations: b.Config.GetLocations(fmt.Sprintf("resources.job_runs.%s.lifecycle", name)),
})
}
for i, t := range jr.Lifecycle.Triggers {
path := fmt.Sprintf("resources.job_runs.%s.lifecycle.triggers[%d]", name, i)
if t.OnBundleDeploy == nil {
diags = diags.Append(diag.Diagnostic{
Severity: diag.Error,
Summary: "lifecycle.triggers entry must set on_bundle_deploy: true",
Locations: b.Config.GetLocations(path),
})
continue
}
if !*t.OnBundleDeploy {
diags = diags.Append(diag.Diagnostic{
Severity: diag.Error,
Summary: "lifecycle.triggers.on_bundle_deploy must be true when set",
Locations: b.Config.GetLocations(path + ".on_bundle_deploy"),
})
}
}
}
return diags
}
Loading
Loading