diff --git a/.changes/unreleased/Deprecations-20250723-131135.yaml b/.changes/unreleased/Deprecations-20250723-131135.yaml
new file mode 100644
index 00000000..0b5dfd37
--- /dev/null
+++ b/.changes/unreleased/Deprecations-20250723-131135.yaml
@@ -0,0 +1,3 @@
+kind: Deprecations
+body: timeout_seconds is deprecated, new property in execution block
+time: 2025-07-23T13:11:35.329628+03:00
diff --git a/docs/data-sources/job.md b/docs/data-sources/job.md
index fe9036d1..2e3e19ab 100644
--- a/docs/data-sources/job.md
+++ b/docs/data-sources/job.md
@@ -43,7 +43,6 @@ Get detailed information for a specific dbt Cloud job.
- `schedule` (Attributes) (see [below for nested schema](#nestedatt--schedule))
- `self_deferring` (Boolean) Whether this job defers on a previous run of itself (overrides value in deferring_job_id)
- `settings` (Attributes) (see [below for nested schema](#nestedatt--settings))
-- `timeout_seconds` (Number, Deprecated) [Deprectated - Moved to execution.timeout_seconds] Number of seconds before the job times out
- `triggers` (Attributes) (see [below for nested schema](#nestedatt--triggers))
- `triggers_on_draft_pr` (Boolean) Whether the CI job should be automatically triggered on draft PRs
diff --git a/docs/data-sources/jobs.md b/docs/data-sources/jobs.md
index 23913550..d7feb9c6 100644
--- a/docs/data-sources/jobs.md
+++ b/docs/data-sources/jobs.md
@@ -69,7 +69,6 @@ Read-Only:
- `run_generate_sources` (Boolean) Whether the job test source freshness
- `schedule` (Attributes) (see [below for nested schema](#nestedatt--jobs--schedule))
- `settings` (Attributes) (see [below for nested schema](#nestedatt--jobs--settings))
-- `timeout_seconds` (Number, Deprecated) [Deprectated - Moved to execution.timeout_seconds] Number of seconds before the job times out
- `triggers` (Attributes) (see [below for nested schema](#nestedatt--jobs--triggers))
- `triggers_on_draft_pr` (Boolean) Whether the CI job should be automatically triggered on draft PRs
diff --git a/docs/resources/job.md b/docs/resources/job.md
index 3d23d477..7e2e2262 100644
--- a/docs/resources/job.md
+++ b/docs/resources/job.md
@@ -213,6 +213,7 @@ An example can be found [in this GitHub issue](https://github.com/dbt-labs/terra
- `deferring_job_id` (Number) Job identifier that this job defers to (legacy deferring approach)
- `description` (String) Description for the job
- `errors_on_lint_failure` (Boolean) Whether the CI job should fail when a lint error is found. Only used when `run_lint` is set to `true`. Defaults to `true`.
+- `execution` (Attributes) (see [below for nested schema](#nestedatt--execution))
- `generate_docs` (Boolean) Flag for whether the job should generate documentation
- `is_active` (Boolean) Should always be set to true as setting it to false is the same as creating a job in a deleted state. To create/keep a job in a 'deactivated' state, check the `triggers` config.
- `job_completion_trigger_condition` (Block List) Which other job should trigger this job when it finishes, and on which conditions (sometimes referred as 'job chaining'). (see [below for nested schema](#nestedblock--job_completion_trigger_condition))
@@ -228,7 +229,6 @@ An example can be found [in this GitHub issue](https://github.com/dbt-labs/terra
- `schedule_type` (String) Type of schedule to use, one of every_day/ days_of_week/ custom_cron/ interval_cron
- `self_deferring` (Boolean) Whether this job defers on a previous run of itself
- `target_name` (String) Target name for the dbt profile
-- `timeout_seconds` (Number, Deprecated) [Deprectated - Moved to execution.timeout_seconds] Number of seconds to allow the job to run before timing out
- `triggers_on_draft_pr` (Boolean) Whether the CI job should be automatically triggered on draft PRs
### Read-Only
@@ -247,6 +247,14 @@ Optional:
- `schedule` (Boolean) Whether the job runs on a schedule
+
+### Nested Schema for `execution`
+
+Optional:
+
+- `timeout_seconds` (Number) The number of seconds before the job times out
+
+
### Nested Schema for `job_completion_trigger_condition`
diff --git a/pkg/framework/objects/job/model.go b/pkg/framework/objects/job/model.go
index 60deeb4a..ca79a9f4 100644
--- a/pkg/framework/objects/job/model.go
+++ b/pkg/framework/objects/job/model.go
@@ -99,8 +99,7 @@ type SingleJobDataSourceModel struct {
}
type JobResourceModel struct {
- // Execution *JobExecution `tfsdk:"execution"` // has timeout-seconds
- TimeoutSeconds types.Int64 `tfsdk:"timeout_seconds"` // moved under execution , add deprecation message
+ Execution types.Object `tfsdk:"execution"` // has timeout-seconds
GenerateDocs types.Bool `tfsdk:"generate_docs"` // exists
RunGenerateSources types.Bool `tfsdk:"run_generate_sources"` // exists
ID types.Int64 `tfsdk:"id"` // will hold job id?
@@ -119,18 +118,18 @@ type JobResourceModel struct {
TriggersOnDraftPr types.Bool `tfsdk:"triggers_on_draft_pr"` // exists
// Environment *JobEnvironment `tfsdk:"environment"`
JobCompletionTriggerCondition []*JobCompletionTriggerCondition `tfsdk:"job_completion_trigger_condition"` // exists
- RunCompareChanges types.Bool `tfsdk:"run_compare_changes"` // exists
- IsActive types.Bool `tfsdk:"is_active"`
- TargetName types.String `tfsdk:"target_name"` // add deprecated
- NumThreads types.Int64 `tfsdk:"num_threads"` // add deprecated moved to settings
- RunLint types.Bool `tfsdk:"run_lint"`
- ErrorsOnLintFailure types.Bool `tfsdk:"errors_on_lint_failure"`
- ScheduleType types.String `tfsdk:"schedule_type"`
- ScheduleInterval types.Int64 `tfsdk:"schedule_interval"`
- ScheduleHours []types.Int64 `tfsdk:"schedule_hours"`
- ScheduleDays []types.Int64 `tfsdk:"schedule_days"`
- ScheduleCron types.String `tfsdk:"schedule_cron"` // add deprecated move to schedule
- DeferringJobId types.Int64 `tfsdk:"deferring_job_id"` // add deprecated move to deferring_job_definition_id
- SelfDeferring types.Bool `tfsdk:"self_deferring"`
- CompareChangesFlags types.String `tfsdk:"compare_changes_flags"`
+ RunCompareChanges types.Bool `tfsdk:"run_compare_changes"` // exists
+ IsActive types.Bool `tfsdk:"is_active"`
+ TargetName types.String `tfsdk:"target_name"` // add deprecated
+ NumThreads types.Int64 `tfsdk:"num_threads"` // add deprecated moved to settings
+ RunLint types.Bool `tfsdk:"run_lint"`
+ ErrorsOnLintFailure types.Bool `tfsdk:"errors_on_lint_failure"`
+ ScheduleType types.String `tfsdk:"schedule_type"`
+ ScheduleInterval types.Int64 `tfsdk:"schedule_interval"`
+ ScheduleHours []types.Int64 `tfsdk:"schedule_hours"`
+ ScheduleDays []types.Int64 `tfsdk:"schedule_days"`
+ ScheduleCron types.String `tfsdk:"schedule_cron"` // add deprecated move to schedule
+ DeferringJobId types.Int64 `tfsdk:"deferring_job_id"` // add deprecated move to deferring_job_definition_id
+ SelfDeferring types.Bool `tfsdk:"self_deferring"`
+ CompareChangesFlags types.String `tfsdk:"compare_changes_flags"`
}
diff --git a/pkg/framework/objects/job/resource.go b/pkg/framework/objects/job/resource.go
index 64422c94..6abed253 100644
--- a/pkg/framework/objects/job/resource.go
+++ b/pkg/framework/objects/job/resource.go
@@ -10,9 +10,12 @@ import (
"github.com/dbt-labs/terraform-provider-dbtcloud/pkg/dbt_cloud"
"github.com/dbt-labs/terraform-provider-dbtcloud/pkg/helper"
"github.com/dbt-labs/terraform-provider-dbtcloud/pkg/utils"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)
var (
@@ -87,7 +90,7 @@ func (j *jobResource) ImportState(ctx context.Context, req resource.ImportStateR
)
return
}
-
+
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), jobID)...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("job_id"), jobID)...)
}
@@ -162,7 +165,7 @@ func (j *jobResource) Create(ctx context.Context, req resource.CreateRequest, re
}
selfDeferring := plan.SelfDeferring.ValueBool()
- timeoutSeconds := int(plan.TimeoutSeconds.ValueInt64())
+ timeoutSeconds, _ := getTimeoutFromPlan(ctx, plan)
triggersOnDraftPR := plan.TriggersOnDraftPr.ValueBool()
runCompareChanges := plan.RunCompareChanges.ValueBool()
runLint := plan.RunLint.ValueBool()
@@ -190,7 +193,6 @@ func (j *jobResource) Create(ctx context.Context, req resource.CreateRequest, re
}
}
-
createDbtVersion := ""
if dbtVersion != nil {
createDbtVersion = *dbtVersion
@@ -241,19 +243,19 @@ func (j *jobResource) Create(ctx context.Context, req resource.CreateRequest, re
return
}
- plan.ID = types.Int64Value(int64(*createdJob.ID))
- plan.JobId = types.Int64Value(int64(*createdJob.ID))
+ plan.ID = types.Int64Value(int64(*createdJob.ID))
+ plan.JobId = types.Int64Value(int64(*createdJob.ID))
if createdJob.JobType != "" {
plan.JobType = types.StringValue(createdJob.JobType)
} else {
plan.JobType = types.StringNull()
}
-
+
jobIDStr := strconv.FormatInt(int64(*createdJob.ID), 10)
createdSelfDeferring := createdJob.DeferringJobId != nil && strconv.Itoa(*createdJob.DeferringJobId) == jobIDStr
plan.SelfDeferring = types.BoolValue(createdSelfDeferring)
-
+
diags := resp.State.Set(ctx, plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
@@ -362,8 +364,8 @@ func (j *jobResource) Read(ctx context.Context, req resource.ReadRequest, resp *
state.ScheduleDays = scheduleDaysNull
}
- if retrievedJob.Schedule.Date.Cron != nil &&
- retrievedJob.Schedule.Date.Type != "interval_cron" { // for interval_cron, the cron expression is auto generated in the code
+ if retrievedJob.Schedule.Date.Cron != nil &&
+ retrievedJob.Schedule.Date.Type != "interval_cron" { // for interval_cron, the cron expression is auto generated in the code
state.ScheduleCron = types.StringValue(*retrievedJob.Schedule.Date.Cron)
} else {
state.ScheduleCron = types.StringNull()
@@ -384,12 +386,12 @@ func (j *jobResource) Read(ctx context.Context, req resource.ReadRequest, resp *
}
state.SelfDeferring = types.BoolValue(selfDeferring)
- state.TimeoutSeconds = types.Int64Value(int64(retrievedJob.Execution.TimeoutSeconds))
+ state.Execution, _ = executionStateFromResponse(ctx, retrievedJob)
var triggers map[string]interface{}
triggersInput, _ := json.Marshal(retrievedJob.Triggers)
json.Unmarshal(triggersInput, &triggers)
-
+
// for now, we allow people to keep the triggers.custom_branch_only config even if the parameter was deprecated in the API
// we set the state to the current config value, so it doesn't do anything
var customBranchValue types.Bool
@@ -443,7 +445,7 @@ func (j *jobResource) Read(ctx context.Context, req resource.ReadRequest, resp *
state.CompareChangesFlags = types.StringValue(retrievedJob.CompareChangesFlags)
state.RunLint = types.BoolValue(retrievedJob.RunLint)
state.ErrorsOnLintFailure = types.BoolValue(retrievedJob.ErrorsOnLintFailure)
-
+
if retrievedJob.JobType != "" {
state.JobType = types.StringValue(retrievedJob.JobType)
} else {
@@ -578,9 +580,9 @@ func (j *jobResource) Update(ctx context.Context, req resource.UpdateRequest, re
}
}
- job.Execution.TimeoutSeconds = int(plan.TimeoutSeconds.ValueInt64())
+ job.Execution = getJobExecutionPtrFromPlan(ctx, plan)
job.TriggersOnDraftPR = plan.TriggersOnDraftPr.ValueBool()
-
+
if len(plan.JobCompletionTriggerCondition) == 0 {
job.JobCompletionTrigger = nil
} else {
@@ -598,7 +600,7 @@ func (j *jobResource) Update(ctx context.Context, req resource.UpdateRequest, re
}
job.JobCompletionTrigger = &jobCondTrigger
}
-
+
job.RunCompareChanges = plan.RunCompareChanges.ValueBool()
job.RunLint = plan.RunLint.ValueBool()
job.ErrorsOnLintFailure = plan.ErrorsOnLintFailure.ValueBool()
@@ -618,14 +620,92 @@ func (j *jobResource) Update(ctx context.Context, req resource.UpdateRequest, re
} else {
plan.JobType = types.StringNull()
}
-
+
updatedJobIDStr := strconv.FormatInt(jobID, 10)
updatedSelfDeferring := updatedJob.DeferringJobId != nil && strconv.Itoa(*updatedJob.DeferringJobId) == updatedJobIDStr
plan.SelfDeferring = types.BoolValue(updatedSelfDeferring)
-
+
diags := resp.State.Set(ctx, plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
+
+func getTimeoutFromPlan(ctx context.Context, plan JobResourceModel) (int, diag.Diagnostics) {
+ var diags diag.Diagnostics
+
+ // 1. Declare a variable for your target model.
+ var executionSettings JobExecution
+ var timeoutSeconds int // Use a pointer to handle the case where the inner field is null.
+
+ // 2. Check if the execution object is null or unknown. This is true if the user
+ // did not include the `execution` block in their configuration.
+ if plan.Execution.IsNull() || plan.Execution.IsUnknown() {
+ return 0, diags
+ }
+
+ // 3. Use the .As() method to convert the types.Object into your model.
+ diags.Append(plan.Execution.As(ctx, &executionSettings, basetypes.ObjectAsOptions{})...)
+ if diags.HasError() {
+ return 0, diags
+ }
+
+ // 4. Now that you have the populated model, you can access its fields.
+ // It's still important to check if the inner field is null.
+ if !executionSettings.TimeoutSeconds.IsNull() && !executionSettings.TimeoutSeconds.IsUnknown() {
+ timeoutSeconds = int(executionSettings.TimeoutSeconds.ValueInt64())
+ }
+
+ return timeoutSeconds, diags
+}
+
+func getJobExecutionPtrFromPlan(ctx context.Context, plan JobResourceModel) dbt_cloud.JobExecution {
+ var diags diag.Diagnostics
+
+ // 1. Declare a variable for your target model.
+ var executionSettings = dbt_cloud.JobExecution{
+ TimeoutSeconds: 0, // Default to null
+ }
+
+ // 2. Check if the execution object is null or unknown. This is true if the user
+ // did not include the `execution` block in their configuration.
+ if plan.Execution.IsNull() || plan.Execution.IsUnknown() {
+ return executionSettings
+ }
+
+ // 3. Use the .As() method to convert the types.Object into your model.
+ diags.Append(plan.Execution.As(ctx, &executionSettings, basetypes.ObjectAsOptions{})...)
+ if diags.HasError() {
+ return executionSettings
+ }
+
+ return executionSettings
+}
+
+// executionStateFromResponse converts the execution settings from a dbt Cloud API response
+// into a types.Object suitable for the Terraform state.
+func executionStateFromResponse(ctx context.Context, retrievedJob *dbt_cloud.Job) (types.Object, diag.Diagnostics) {
+ var diags diag.Diagnostics
+
+ // Define the schema for the execution object. This is needed for creating a null object.
+ attributeTypes := map[string]attr.Type{
+ "timeout_seconds": types.Int64Type,
+ }
+
+ // If the API response doesn't contain execution settings, return a null object.
+ if retrievedJob.Execution.TimeoutSeconds == 0 {
+ return types.ObjectNull(attributeTypes), diags
+ }
+
+ // If settings were returned, populate our model from the API response.
+ executionModel := JobExecution{
+ TimeoutSeconds: types.Int64Value(int64(retrievedJob.Execution.TimeoutSeconds)),
+ }
+
+ // Convert the populated model into a types.Object.
+ executionObject, d := types.ObjectValueFrom(ctx, attributeTypes, executionModel)
+ diags.Append(d...)
+
+ return executionObject, diags
+}
diff --git a/pkg/framework/objects/job/resource_acceptance_test.go b/pkg/framework/objects/job/resource_acceptance_test.go
index dc687e6f..67c838b5 100644
--- a/pkg/framework/objects/job/resource_acceptance_test.go
+++ b/pkg/framework/objects/job/resource_acceptance_test.go
@@ -686,6 +686,68 @@ resource "dbtcloud_job" "test_job" {
`, projectName, environmentName, acctest_config.DBT_CLOUD_VERSION, jobName, scheduleConfig)
}
+func TestAccDbtCloudJobResourceExecution(t *testing.T) {
+
+ jobName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
+ projectName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
+ environmentName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { acctest_helper.TestAccPreCheck(t) },
+ ProtoV6ProviderFactories: acctest_helper.TestAccProtoV6ProviderFactories,
+ CheckDestroy: testAccCheckDbtCloudJobDestroy,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccDbtCloudJobResourceExecutionConfig(
+ jobName,
+ projectName,
+ environmentName,
+ ),
+ Check: resource.ComposeTestCheckFunc(
+ testAccCheckDbtCloudJobExists("dbtcloud_job.test_job"),
+ resource.TestCheckResourceAttr("dbtcloud_job.test_job", "name", jobName),
+ ),
+ },
+ },
+ })
+}
+
+func testAccDbtCloudJobResourceExecutionConfig(
+ jobName, projectName, environmentName string,
+) string {
+ return fmt.Sprintf(`
+resource "dbtcloud_project" "test_job_project" {
+ name = "%s"
+}
+
+resource "dbtcloud_environment" "test_job_environment" {
+ project_id = dbtcloud_project.test_job_project.id
+ name = "%s"
+ dbt_version = "%s"
+ type = "deployment"
+}
+
+resource "dbtcloud_job" "test_job" {
+ name = "%s"
+ project_id = dbtcloud_project.test_job_project.id
+ environment_id = dbtcloud_environment.test_job_environment.environment_id
+
+ execution = {
+ "timeout_seconds"= 180
+ }
+
+ execute_steps = [
+ "dbt test"
+ ]
+ triggers = {
+ "github_webhook": false,
+ "git_provider_webhook": false,
+ "schedule": false,
+ }
+}
+`, projectName, environmentName, acctest_config.DBT_CLOUD_VERSION, jobName)
+}
+
func testAccDbtCloudJobResourceBasicConfigTriggers(
jobName, projectName, environmentName, trigger string,
) string {
diff --git a/pkg/framework/objects/job/schema.go b/pkg/framework/objects/job/schema.go
index b39a70e6..0bd9a688 100644
--- a/pkg/framework/objects/job/schema.go
+++ b/pkg/framework/objects/job/schema.go
@@ -33,11 +33,6 @@ func getJobAttributes() map[string]schema.Attribute {
},
},
},
- "timeout_seconds": schema.Int64Attribute{
- Computed: true,
- DeprecationMessage: "Moved to execution.timeout_seconds",
- Description: "[Deprectated - Moved to execution.timeout_seconds] Number of seconds before the job times out",
- },
"generate_docs": schema.BoolAttribute{
Computed: true,
Description: "Whether the job generate docs",
@@ -329,24 +324,16 @@ func (j *jobResource) Schema(
int64planmodifier.UseStateForUnknown(),
},
},
- // "execution": resource_schema.SingleNestedAttribute{
- // Optional: true,
- // Computed: true,
- // Attributes: map[string]resource_schema.Attribute{
- // "timeout_seconds": resource_schema.Int64Attribute{
- // Optional: true,
- // Computed: true,
- // Default: int64default.StaticInt64(0),
- // Description: "The number of seconds before the job times out",
- // },
- // },
- // },
- "timeout_seconds": resource_schema.Int64Attribute{
- Optional: true,
- Computed: true,
- Default: int64default.StaticInt64(0),
- DeprecationMessage: "Moved to execution.timeout_seconds",
- Description: "[Deprectated - Moved to execution.timeout_seconds] Number of seconds to allow the job to run before timing out",
+ "execution": resource_schema.SingleNestedAttribute{
+ Optional: true,
+ Attributes: map[string]resource_schema.Attribute{
+ "timeout_seconds": resource_schema.Int64Attribute{
+ Optional: true,
+ Computed: true,
+ Default: int64default.StaticInt64(0),
+ Description: "The number of seconds before the job times out",
+ },
+ },
},
"generate_docs": resource_schema.BoolAttribute{
Optional: true,