diff --git a/.changes/unreleased/Behind the scenes-20260406-101738.yaml b/.changes/unreleased/Behind the scenes-20260406-101738.yaml new file mode 100644 index 00000000..d9480af9 --- /dev/null +++ b/.changes/unreleased/Behind the scenes-20260406-101738.yaml @@ -0,0 +1,3 @@ +kind: Behind the scenes +body: Added migration scripts for the changed resources +time: 2026-04-06T10:17:38.840631+03:00 diff --git a/.changes/unreleased/Changes-20260406-090042.yaml b/.changes/unreleased/Changes-20260406-090042.yaml new file mode 100644 index 00000000..10c67308 --- /dev/null +++ b/.changes/unreleased/Changes-20260406-090042.yaml @@ -0,0 +1,3 @@ +kind: Changes +body: Remove deprecated resource dbtcloud_project_artefacts +time: 2026-04-06T09:00:42.01464+03:00 diff --git a/.changes/unreleased/Changes-20260406-090711.yaml b/.changes/unreleased/Changes-20260406-090711.yaml new file mode 100644 index 00000000..dacdc000 --- /dev/null +++ b/.changes/unreleased/Changes-20260406-090711.yaml @@ -0,0 +1,3 @@ +kind: Changes +body: Remove webhook_id and use id instead +time: 2026-04-06T09:07:11.439697+03:00 diff --git a/.changes/unreleased/Changes-20260406-091246.yaml b/.changes/unreleased/Changes-20260406-091246.yaml new file mode 100644 index 00000000..2dc79822 --- /dev/null +++ b/.changes/unreleased/Changes-20260406-091246.yaml @@ -0,0 +1,3 @@ +kind: Changes +body: Remove fetch_deploy_key +time: 2026-04-06T09:12:46.094171+03:00 diff --git a/.changes/unreleased/Changes-20260406-092408.yaml b/.changes/unreleased/Changes-20260406-092408.yaml new file mode 100644 index 00000000..dd104b61 --- /dev/null +++ b/.changes/unreleased/Changes-20260406-092408.yaml @@ -0,0 +1,3 @@ +kind: Changes +body: Remove target_name from databricks and spark creds +time: 2026-04-06T09:24:08.380134+03:00 diff --git a/.changes/unreleased/Changes-20260406-093202.yaml b/.changes/unreleased/Changes-20260406-093202.yaml new file mode 100644 index 00000000..c5b772ce --- /dev/null +++ b/.changes/unreleased/Changes-20260406-093202.yaml @@ -0,0 +1,3 @@ +kind: Changes +body: Remove adapter_type from databricks_credential +time: 2026-04-06T09:32:02.363664+03:00 diff --git a/.changes/unreleased/Changes-20260406-094611.yaml b/.changes/unreleased/Changes-20260406-094611.yaml new file mode 100644 index 00000000..03611bee --- /dev/null +++ b/.changes/unreleased/Changes-20260406-094611.yaml @@ -0,0 +1,3 @@ +kind: Changes +body: Remove timeout_seconds and job_deferral_id +time: 2026-04-06T09:46:11.01799+03:00 diff --git a/docs/data-sources/databricks_credential.md b/docs/data-sources/databricks_credential.md index a5cb473f..6b483e49 100644 --- a/docs/data-sources/databricks_credential.md +++ b/docs/data-sources/databricks_credential.md @@ -27,4 +27,3 @@ Databricks credential data source - `id` (String) The ID of this resource. Contains the project ID and the credential ID. - `num_threads` (Number) The number of threads to use - `schema` (String) The schema where to create models -- `target_name` (String) Target name diff --git a/docs/data-sources/job.md b/docs/data-sources/job.md index 06a487d4..e8b53b99 100644 --- a/docs/data-sources/job.md +++ b/docs/data-sources/job.md @@ -27,7 +27,6 @@ Get detailed information for a specific dbt Cloud job. - `dbt_version` (String) The version of dbt used for the job. If not set, the environment version will be used. - `deferring_environment_id` (Number) The ID of the environment this job defers to -- `deferring_job_id` (Number, Deprecated) [Deprectated - Deferral is now set at the environment level] The ID of the job definition this job defers to - `description` (String) The description of the job - `environment` (Attributes) Details of the environment the job is running in (see [below for nested schema](#nestedatt--environment)) - `environment_id` (Number) The ID of environment @@ -42,9 +41,8 @@ Get detailed information for a specific dbt Cloud job. - `run_compare_changes` (Boolean) Whether the job should compare data changes introduced by the code change in the PR - `run_generate_sources` (Boolean) Whether the job test source freshness - `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) +- `self_deferring` (Boolean) Whether this job defers on a previous run of itself - `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 241e4a7f..e1c05c91 100644 --- a/docs/data-sources/jobs.md +++ b/docs/data-sources/jobs.md @@ -53,7 +53,6 @@ Read-Only: - `dbt_version` (String) The version of dbt used for the job. If not set, the environment version will be used. - `deferring_environment_id` (Number) The ID of the environment this job defers to -- `deferring_job_definition_id` (Number, Deprecated) [Deprectated - Deferral is now set at the environment level] The ID of the job definition this job defers to - `description` (String) The description of the job - `environment` (Attributes) Details of the environment the job is running in (see [below for nested schema](#nestedatt--jobs--environment)) - `environment_id` (Number) The ID of environment @@ -70,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/data-sources/repository.md b/docs/data-sources/repository.md index 0416b8fa..3bbcf1b9 100644 --- a/docs/data-sources/repository.md +++ b/docs/data-sources/repository.md @@ -20,10 +20,6 @@ Retrieve data for a single repository - `project_id` (Number) Project ID to create the repository in - `repository_id` (Number) ID for the repository -### Optional - -- `fetch_deploy_key` (Boolean, Deprecated) Whether we should return the public deploy key - ### Read-Only - `azure_active_directory_project_id` (String) The Azure Dev Ops project ID diff --git a/docs/data-sources/spark_credential.md b/docs/data-sources/spark_credential.md index 6a5e3165..fabdfd71 100644 --- a/docs/data-sources/spark_credential.md +++ b/docs/data-sources/spark_credential.md @@ -32,4 +32,3 @@ data "dbtcloud_spark_credential" "my_spark_cred" { - `id` (String) The ID of this resource. Contains the project ID and the credential ID. - `num_threads` (Number) The number of threads to use - `schema` (String) The schema where to create models -- `target_name` (String) Target name diff --git a/docs/data-sources/webhook.md b/docs/data-sources/webhook.md index e143ad56..18a00199 100644 --- a/docs/data-sources/webhook.md +++ b/docs/data-sources/webhook.md @@ -17,7 +17,7 @@ Retrieve webhook details ### Required -- `webhook_id` (String, Deprecated) Webhook's ID +- `id` (String) Webhook's ID ### Read-Only @@ -27,6 +27,5 @@ Retrieve webhook details - `description` (String) Webhooks Description - `event_types` (List of String) Webhooks Event Types - `http_status_code` (String) Webhooks HTTP Status Code -- `id` (String) Webhook's ID - `job_ids` (List of Number) List of job IDs to trigger the webhook - `name` (String) Webhooks Name diff --git a/docs/resources/databricks_credential.md b/docs/resources/databricks_credential.md index 2d49fa71..877ee977 100644 --- a/docs/resources/databricks_credential.md +++ b/docs/resources/databricks_credential.md @@ -18,7 +18,6 @@ resource "dbtcloud_databricks_credential" "my_databricks_cred" { project_id = dbtcloud_project.dbt_project.id token = "abcdefgh" schema = "my_schema" - adapter_type = "databricks" } // Using write-only attributes (not stored in state, requires Terraform >= 1.11) @@ -48,11 +47,9 @@ resource "dbtcloud_databricks_credential" "my_databricks_cred_wo" { ### Optional -- `adapter_type` (String, Deprecated) The type of the adapter. 'spark' is deprecated, but still supported for backwards compatibility. For Spark, please use the spark_credential resource. Optional only when semantic_layer_credential is set to true; otherwise, this field is required. - `catalog` (String) The catalog where to create models (only for the databricks adapter) - `schema` (String) The schema where to create models. Optional only when semantic_layer_credential is set to true; otherwise, this field is required. - `semantic_layer_credential` (Boolean) This field indicates that the credential is used as part of the Semantic Layer configuration. It is used to create a Databricks credential for the Semantic Layer. -- `target_name` (String, Deprecated) Target name - `token` (String, Sensitive) Token for Databricks user. Consider using `token_wo` instead, which is not stored in state. - `token_wo` (String) Write-only alternative to `token`. The value is not stored in state. Requires `token_wo_version` to trigger updates. - `token_wo_version` (Number) Version number for `token_wo`. Increment this value to trigger an update of the token when using `token_wo`. diff --git a/docs/resources/databricks_semantic_layer_credential.md b/docs/resources/databricks_semantic_layer_credential.md index a69f6812..157aef76 100644 --- a/docs/resources/databricks_semantic_layer_credential.md +++ b/docs/resources/databricks_semantic_layer_credential.md @@ -59,11 +59,9 @@ Required: Optional: -- `adapter_type` (String, Deprecated) The type of the adapter. 'spark' is deprecated, but still supported for backwards compatibility. For Spark, please use the spark_credential resource. Optional only when semantic_layer_credential is set to true; otherwise, this field is required. - `catalog` (String) The catalog where to create models (only for the databricks adapter) - `schema` (String) The schema where to create models. Optional only when semantic_layer_credential is set to true; otherwise, this field is required. - `semantic_layer_credential` (Boolean) This field indicates that the credential is used as part of the Semantic Layer configuration. It is used to create a Databricks credential for the Semantic Layer. -- `target_name` (String, Deprecated) Target name - `token` (String, Sensitive) Token for Databricks user. Consider using `token_wo` instead, which is not stored in state. - `token_wo` (String) Write-only alternative to `token`. The value is not stored in state. Requires `token_wo_version` to trigger updates. - `token_wo_version` (Number) Version number for `token_wo`. Increment this value to trigger an update of the token when using `token_wo`. diff --git a/docs/resources/job.md b/docs/resources/job.md index 601df9be..bb009b04 100644 --- a/docs/resources/job.md +++ b/docs/resources/job.md @@ -46,7 +46,6 @@ resource "dbtcloud_job" "daily_job" { schedule_type = "days_of_week" schedule_hours = [0] - # set job timeout using the execution block (recommended) execution = { timeout_seconds = 1800 } @@ -79,7 +78,6 @@ resource "dbtcloud_job" "ci_job" { schedule_days = [0, 1, 2, 3, 4, 5, 6] schedule_type = "days_of_week" - # set job timeout - use execution block (recommended) instead of the deprecated timeout_seconds execution = { timeout_seconds = 3600 } @@ -240,7 +238,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) Number of seconds to allow the job to run before timing out. Use execution.timeout_seconds instead. - `triggers_on_draft_pr` (Boolean) Whether the CI job should be automatically triggered on draft PRs - `validate_execute_steps` (Boolean) When set to `true`, the provider will validate the `execute_steps` during plan time to ensure they contain valid dbt commands. If a command is not recognized (e.g., a new dbt command not yet supported by the provider), the validation will fail. Defaults to `false` to allow flexibility with newer dbt commands. diff --git a/docs/resources/project_artefacts.md b/docs/resources/project_artefacts.md deleted file mode 100644 index c6c4219c..00000000 --- a/docs/resources/project_artefacts.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -page_title: "dbtcloud_project_artefacts Resource - dbtcloud" -subcategory: "" -description: |- - [Deprecated] Resource for mentioning what jobs are the source of truth for the legacy dbt Docs and dbt Source Freshness pages. dbt Explorer doesn't require this config anymore. ---- - -# dbtcloud_project_artefacts (Resource) - - -[Deprecated] Resource for mentioning what jobs are the source of truth for the legacy dbt Docs and dbt Source Freshness pages. dbt Explorer doesn't require this config anymore. - -## Example Usage - -```terraform -resource "dbtcloud_project_artefacts" "my_project_artefacts" { - project_id = dbtcloud_project.dbt_project.id - docs_job_id = dbtcloud_job.prod_job.id - freshness_job_id = dbtcloud_job.prod_job.id -} -``` - - -## Schema - -### Required - -- `project_id` (Number) Project ID - -### Optional - -- `docs_job_id` (Number) Docs Job ID -- `freshness_job_id` (Number) Freshness Job ID - -### Read-Only - -- `id` (String) The ID of the project artefacts resource. - -## Import - -Import is supported using the following syntax: - -```shell -# using import blocks (requires Terraform >= 1.5) -import { - to = dbtcloud_project_artefacts.my_artefacts - id = "project_id" -} - -import { - to = dbtcloud_project_artefacts.my_artefacts - id = "12345" -} - -# using the older import command -terraform import dbtcloud_project_artefacts.my_artefacts "project_id" -terraform import dbtcloud_project_artefacts.my_artefacts 12345 -``` diff --git a/docs/resources/repository.md b/docs/resources/repository.md index d641b068..4bbb1409 100644 --- a/docs/resources/repository.md +++ b/docs/resources/repository.md @@ -112,7 +112,6 @@ resource "dbtcloud_repository" "ado_repo" { - `azure_active_directory_project_id` (String) The Azure Dev Ops project ID. It can be retrieved via the Azure API or using the data source `dbtcloud_azure_dev_ops_project` and the project name - (required for ADO native integration only) - `azure_active_directory_repository_id` (String) The Azure Dev Ops repository ID. It can be retrieved via the Azure API or using the data source `dbtcloud_azure_dev_ops_repository` along with the ADO Project ID and the repository name - (required for ADO native integration only) - `azure_bypass_webhook_registration_failure` (Boolean) If set to False (the default), the connection will fail if the service user doesn't have access to set webhooks (required for auto-triggering CI jobs). If set to True, the connection will be successful but no automated CI job will be triggered - (for ADO native integration only) -- `fetch_deploy_key` (Boolean, Deprecated) Whether we should return the public deploy key - (for the `deploy_key` strategy) - `git_clone_strategy` (String) Git clone strategy for the repository. Can be `deploy_key` (default) for cloning via SSH Deploy Key, `github_app` for GitHub native integration, `deploy_token` for the GitLab native integration and `azure_active_directory_app` for ADO native integration - `github_installation_id` (Number) Identifier for the GitHub App - (for GitHub native integration only) - `gitlab_project_id` (Number) Identifier for the Gitlab project - (for GitLab native integration only) diff --git a/docs/resources/spark_credential.md b/docs/resources/spark_credential.md index 239f4ed6..2217e942 100644 --- a/docs/resources/spark_credential.md +++ b/docs/resources/spark_credential.md @@ -47,7 +47,6 @@ resource "dbtcloud_spark_credential" "my_spark_cred_wo" { ### Optional -- `target_name` (String, Deprecated) Target name - `token` (String, Sensitive) Token for Apache Spark user. Consider using `token_wo` instead, which is not stored in state. - `token_wo` (String) Write-only alternative to `token`. The value is not stored in state. Requires `token_wo_version` to trigger updates. - `token_wo_version` (Number) Version number for `token_wo`. Increment this value to trigger an update of the token when using `token_wo`. diff --git a/docs/resources/webhook.md b/docs/resources/webhook.md index ac64b373..6a909882 100644 --- a/docs/resources/webhook.md +++ b/docs/resources/webhook.md @@ -49,7 +49,6 @@ resource "dbtcloud_webhook" "test_webhook" { - `hmac_secret` (String, Sensitive) Secret key for the webhook. Can be used to validate the authenticity of the webhook. - `http_status_code` (String) Latest HTTP status of the webhook - `id` (String) Webhook's ID -- `webhook_id` (String, Deprecated) Webhook's ID ## Import diff --git a/examples/resources/dbtcloud_databricks_credential/resource.tf b/examples/resources/dbtcloud_databricks_credential/resource.tf index eea1de40..414efae9 100644 --- a/examples/resources/dbtcloud_databricks_credential/resource.tf +++ b/examples/resources/dbtcloud_databricks_credential/resource.tf @@ -3,7 +3,6 @@ resource "dbtcloud_databricks_credential" "my_databricks_cred" { project_id = dbtcloud_project.dbt_project.id token = "abcdefgh" schema = "my_schema" - adapter_type = "databricks" } // Using write-only attributes (not stored in state, requires Terraform >= 1.11) diff --git a/examples/resources/dbtcloud_job/resource.tf b/examples/resources/dbtcloud_job/resource.tf index 20319cbe..5b8c1c96 100644 --- a/examples/resources/dbtcloud_job/resource.tf +++ b/examples/resources/dbtcloud_job/resource.tf @@ -23,7 +23,6 @@ resource "dbtcloud_job" "daily_job" { schedule_type = "days_of_week" schedule_hours = [0] - # set job timeout using the execution block (recommended) execution = { timeout_seconds = 1800 } @@ -56,7 +55,6 @@ resource "dbtcloud_job" "ci_job" { schedule_days = [0, 1, 2, 3, 4, 5, 6] schedule_type = "days_of_week" - # set job timeout - use execution block (recommended) instead of the deprecated timeout_seconds execution = { timeout_seconds = 3600 } diff --git a/examples/resources/dbtcloud_project_artefacts/import.sh b/examples/resources/dbtcloud_project_artefacts/import.sh deleted file mode 100644 index da287874..00000000 --- a/examples/resources/dbtcloud_project_artefacts/import.sh +++ /dev/null @@ -1,14 +0,0 @@ -# using import blocks (requires Terraform >= 1.5) -import { - to = dbtcloud_project_artefacts.my_artefacts - id = "project_id" -} - -import { - to = dbtcloud_project_artefacts.my_artefacts - id = "12345" -} - -# using the older import command -terraform import dbtcloud_project_artefacts.my_artefacts "project_id" -terraform import dbtcloud_project_artefacts.my_artefacts 12345 diff --git a/examples/resources/dbtcloud_project_artefacts/resource.tf b/examples/resources/dbtcloud_project_artefacts/resource.tf deleted file mode 100644 index 0fc341e9..00000000 --- a/examples/resources/dbtcloud_project_artefacts/resource.tf +++ /dev/null @@ -1,5 +0,0 @@ -resource "dbtcloud_project_artefacts" "my_project_artefacts" { - project_id = dbtcloud_project.dbt_project.id - docs_job_id = dbtcloud_job.prod_job.id - freshness_job_id = dbtcloud_job.prod_job.id -} \ No newline at end of file diff --git a/migration_scripts_to_v2/databricks_credential/README.md b/migration_scripts_to_v2/databricks_credential/README.md new file mode 100644 index 00000000..2199e135 --- /dev/null +++ b/migration_scripts_to_v2/databricks_credential/README.md @@ -0,0 +1,75 @@ +# Migration: dbtcloud_databricks_credential + +## What changed + +- **`resource "dbtcloud_databricks_credential"`**: + - `adapter_type` removed — the credential type is already implicit in the resource name. + - `target_name` removed — this field had no effect on the Databricks connection. +- **`data "dbtcloud_databricks_credential"`**: + - `target_name` removed. + +## What the script does + +- Removes `adapter_type = ...` lines from all `resource "dbtcloud_databricks_credential"` blocks. +- Removes `target_name = ...` lines from all `resource "dbtcloud_databricks_credential"` blocks. +- Removes `target_name = ...` lines from all `data "dbtcloud_databricks_credential"` blocks. + +## Usage + +```bash +# Preview changes without modifying files +python migrate_databricks_credential.py --dry-run ./path/to/terraform/ + +# Apply changes (creates .tf.bak backups) +python migrate_databricks_credential.py ./path/to/terraform/ + +# Multiple paths +python migrate_databricks_credential.py ./envs/prod/ ./envs/staging/ ./modules/ +``` + +## Example + +**Input** (`example_input.tf`): +```hcl +resource "dbtcloud_databricks_credential" "prod_credential" { + project_id = 1234 + adapter_type = "databricks" + target_name = "prod" + token = var.databricks_token + schema = "my_schema" + catalog = "my_catalog" +} + +resource "dbtcloud_databricks_credential" "dev_credential" { + project_id = 1234 + adapter_type = "spark" + target_name = "dev" + token = var.databricks_token_dev + schema = "dev_schema" +} + +data "dbtcloud_databricks_credential" "existing" { + project_id = 1234 + credential_id = 5678 + target_name = "prod" +} +``` + +**Output** (dry-run): +``` +databricks_credential/example_input.tf + [REMOVE] 2 `adapter_type` attribute(s) from resource "dbtcloud_databricks_credential" block(s) + [REMOVE] 2 `target_name` attribute(s) from resource "dbtcloud_databricks_credential" block(s) + [REMOVE] 1 `target_name` attribute(s) from data "dbtcloud_databricks_credential" block(s) + (dry-run: no changes written) + +Done. 1/1 file(s) had changes. +``` + +**Result:** `adapter_type` and `target_name` are removed from all resource blocks; `target_name` is removed from the data source block. + +## Next steps + +After running the script: + +1. Run `terraform plan` — it should show no changes related to credential attributes. diff --git a/migration_scripts_to_v2/databricks_credential/example_input.tf b/migration_scripts_to_v2/databricks_credential/example_input.tf new file mode 100644 index 00000000..e45756cc --- /dev/null +++ b/migration_scripts_to_v2/databricks_credential/example_input.tf @@ -0,0 +1,22 @@ +resource "dbtcloud_databricks_credential" "prod_credential" { + project_id = 1234 + adapter_type = "databricks" + target_name = "prod" + token = var.databricks_token + schema = "my_schema" + catalog = "my_catalog" +} + +resource "dbtcloud_databricks_credential" "dev_credential" { + project_id = 1234 + adapter_type = "spark" + target_name = "dev" + token = var.databricks_token_dev + schema = "dev_schema" +} + +data "dbtcloud_databricks_credential" "existing" { + project_id = 1234 + credential_id = 5678 + target_name = "prod" +} diff --git a/migration_scripts_to_v2/databricks_credential/migrate_databricks_credential.py b/migration_scripts_to_v2/databricks_credential/migrate_databricks_credential.py new file mode 100644 index 00000000..7d54c72e --- /dev/null +++ b/migration_scripts_to_v2/databricks_credential/migrate_databricks_credential.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +""" +Migration script: dbtcloud_databricks_credential changes (v1 -> v2) + +Changes in v2: + - resource "dbtcloud_databricks_credential": + - `adapter_type` attribute removed (was deprecated; use the credential type itself) + - `target_name` attribute removed (was deprecated; no replacement needed) + - data "dbtcloud_databricks_credential": + - `target_name` attribute removed + +This script removes the above attributes from all matching blocks. + +Usage: + python migrate_databricks_credential.py [--dry-run] [ ...] + + can be a .tf file or a directory (searched recursively). + +Examples: + python migrate_databricks_credential.py ./terraform/ + python migrate_databricks_credential.py --dry-run ./envs/prod/ ./envs/staging/ + python migrate_databricks_credential.py module1/main.tf module2/main.tf +""" + +import argparse +import re +import shutil +import sys +from pathlib import Path + + +def find_tf_files(paths: list[str]) -> list[Path]: + result = [] + for p in paths: + path = Path(p) + if path.is_dir(): + result.extend(sorted(path.rglob("*.tf"))) + elif path.is_file() and path.suffix == ".tf": + result.append(path) + else: + print(f"WARNING: skipping {p} (not a .tf file or directory)", file=sys.stderr) + return result + + +def remove_attribute_from_blocks(content: str, block_type: str, resource_type: str, attr: str) -> tuple[str, int]: + """Remove `attr = ...` lines that appear inside blocks of the given type and resource type.""" + block_header = re.compile( + r'^([ \t]*)(?:' + re.escape(block_type) + r')\s+"' + re.escape(resource_type) + r'"\s+"[^"]+"\s*\{', + re.MULTILINE, + ) + attr_line = re.compile(r'^[ \t]*' + re.escape(attr) + r'\s*=\s*[^\n]+\n?', re.MULTILINE) + + count = 0 + result = [] + pos = 0 + + for m in block_header.finditer(content): + result.append(content[pos : m.start()]) + depth = 1 + i = m.end() + while i < len(content) and depth > 0: + if content[i] == "{": + depth += 1 + elif content[i] == "}": + depth -= 1 + i += 1 + block_content = content[m.start() : i] + new_block, n = attr_line.subn("", block_content) + count += n + result.append(new_block) + pos = i + + result.append(content[pos:]) + return "".join(result), count + + +def process_file(path: Path, dry_run: bool) -> bool: + original = path.read_text(encoding="utf-8") + content = original + changes = [] + + # Resource: remove adapter_type and target_name + content, n = remove_attribute_from_blocks(content, "resource", "dbtcloud_databricks_credential", "adapter_type") + if n: + changes.append(f" [REMOVE] {n} `adapter_type` attribute(s) from resource \"dbtcloud_databricks_credential\" block(s)") + + content, n = remove_attribute_from_blocks(content, "resource", "dbtcloud_databricks_credential", "target_name") + if n: + changes.append(f" [REMOVE] {n} `target_name` attribute(s) from resource \"dbtcloud_databricks_credential\" block(s)") + + # Data source: remove target_name + content, n = remove_attribute_from_blocks(content, "data", "dbtcloud_databricks_credential", "target_name") + if n: + changes.append(f" [REMOVE] {n} `target_name` attribute(s) from data \"dbtcloud_databricks_credential\" block(s)") + + if not changes: + return False + + print(f"\n{path}") + for c in changes: + print(c) + + if dry_run: + print(" (dry-run: no changes written)") + return True + + shutil.copy2(path, path.with_suffix(".tf.bak")) + path.write_text(content, encoding="utf-8") + print(f" -> written (backup: {path.with_suffix('.tf.bak')})") + return True + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("paths", nargs="+", help=".tf files or directories to process") + parser.add_argument("--dry-run", action="store_true", help="show changes without writing files") + args = parser.parse_args() + + files = find_tf_files(args.paths) + if not files: + print("No .tf files found.", file=sys.stderr) + sys.exit(1) + + changed = 0 + for f in files: + if process_file(f, args.dry_run): + changed += 1 + + print(f"\nDone. {changed}/{len(files)} file(s) had changes.") + if changed and not args.dry_run: + print("\nNext steps:") + print(" 1. Review the changes above.") + print(" 2. Run: terraform plan") + + +if __name__ == "__main__": + main() diff --git a/migration_scripts_to_v2/job/README.md b/migration_scripts_to_v2/job/README.md new file mode 100644 index 00000000..3d4ac360 --- /dev/null +++ b/migration_scripts_to_v2/job/README.md @@ -0,0 +1,109 @@ +# Migration: dbtcloud_job + +## What changed + +- **`resource "dbtcloud_job"`**: the top-level `timeout_seconds` attribute has been removed. Set execution timeout via the `execution` block instead: + ```hcl + # Before + resource "dbtcloud_job" "my_job" { + timeout_seconds = 3600 + } + + # After + resource "dbtcloud_job" "my_job" { + execution = { + timeout_seconds = 3600 + } + } + ``` + +- **`data "dbtcloud_job"` (singular)**: the `deferring_job_id` attribute has been removed from the data source schema. It is still available on the resource. + +- **`data "dbtcloud_jobs"` (plural)**: the `deferring_job_definition_id` attribute has been removed. + +## What the script does + +**For `resource "dbtcloud_job"` blocks**, the script handles three cases: + +| Situation | Action | +|---|---| +| `timeout_seconds` at top level, no `execution` block | Creates an `execution` block with `timeout_seconds` | +| `timeout_seconds` at top level + `execution` block without `timeout_seconds` | Moves `timeout_seconds` into the existing `execution` block | +| `timeout_seconds` at top level + `execution` block already has `timeout_seconds` | Removes the top-level attribute (the `execution` block value takes precedence) | + +**For `data "dbtcloud_job"` blocks**: removes `deferring_job_id = ...` lines. + +**For `data "dbtcloud_jobs"` blocks**: removes `deferring_job_definition_id = ...` lines. + +**Expression references**: any reference to `data.dbtcloud_job..deferring_job_id` in expressions is flagged with a `[WARN]` — these require manual cleanup as there is no direct replacement. + +## Usage + +```bash +# Preview changes without modifying files +python migrate_job.py --dry-run ./path/to/terraform/ + +# Apply changes (creates .tf.bak backups) +python migrate_job.py ./path/to/terraform/ + +# Multiple paths +python migrate_job.py ./envs/prod/ ./envs/staging/ ./modules/ +``` + +## Example + +**Input** (`example_input.tf`): +```hcl +# Case 1: top-level timeout_seconds only +resource "dbtcloud_job" "deploy_job" { + ... + timeout_seconds = 3600 +} + +# Case 2: top-level timeout_seconds + execution block without timeout +resource "dbtcloud_job" "ci_job" { + ... + timeout_seconds = 1800 + execution = { + } +} + +# Case 3: both top-level and execution.timeout_seconds set +resource "dbtcloud_job" "another_job" { + ... + timeout_seconds = 7200 + execution = { + timeout_seconds = 900 + } +} + +data "dbtcloud_job" "my_job" { + job_id = 1234 + deferring_job_id = 5678 +} + +data "dbtcloud_jobs" "all_jobs" { + project_id = 5678 + deferring_job_definition_id = 1234 +} +``` + +**Output** (dry-run): +``` +job/example_input.tf + [REMOVE] top-level `timeout_seconds` from resource "dbtcloud_job" "another_job" (execution block already has timeout_seconds) + [MIGRATE] resource "dbtcloud_job" "ci_job": moved `timeout_seconds = 1800` into existing execution block + [MIGRATE] resource "dbtcloud_job" "deploy_job": converted `timeout_seconds = 3600` to execution block + [REMOVE] 1 `deferring_job_id` attribute(s) from data "dbtcloud_job" "my_job" + [REMOVE] 1 `deferring_job_definition_id` attribute(s) from data "dbtcloud_jobs" "all_jobs" + (dry-run: no changes written) + +Done. 1/1 file(s) had changes or warnings. +``` + +## Next steps + +After running the script: + +1. Manually fix any `[WARN]` references to `data.dbtcloud_job..deferring_job_id` — this attribute no longer exists on the data source. +2. Run `terraform plan` — it should show no changes related to job attributes. diff --git a/migration_scripts_to_v2/job/example_input.tf b/migration_scripts_to_v2/job/example_input.tf new file mode 100644 index 00000000..e29a8f24 --- /dev/null +++ b/migration_scripts_to_v2/job/example_input.tf @@ -0,0 +1,59 @@ +# Case 1: top-level timeout_seconds only — will be converted to an execution block +resource "dbtcloud_job" "deploy_job" { + environment_id = 1234 + execute_steps = ["dbt build"] + name = "Daily Deploy" + project_id = 5678 + triggers = { + "github_webhook" : false + "git_provider_webhook" : false + "schedule" : true + } + timeout_seconds = 3600 +} + +# Case 2: top-level timeout_seconds alongside an execution block that has no timeout +# — timeout_seconds will be moved into the execution block +resource "dbtcloud_job" "ci_job" { + environment_id = 1234 + execute_steps = ["dbt build -s state:modified+"] + name = "CI Job" + project_id = 5678 + triggers = { + "github_webhook" : true + "git_provider_webhook" : true + "schedule" : false + } + timeout_seconds = 1800 + execution = { + # timeout_seconds not yet set here + } +} + +# Case 3: both top-level timeout_seconds and execution.timeout_seconds present +# — top-level is removed; the execution block value is kept +resource "dbtcloud_job" "another_job" { + environment_id = 1234 + execute_steps = ["dbt run"] + name = "Another Job" + project_id = 5678 + triggers = { + "schedule" : false + } + timeout_seconds = 7200 + execution = { + timeout_seconds = 900 + } +} + +# Singular data source: deferring_job_id will be removed +data "dbtcloud_job" "my_job" { + job_id = 1234 + deferring_job_id = 5678 +} + +# Plural data source: deferring_job_definition_id will be removed +data "dbtcloud_jobs" "all_jobs" { + project_id = 5678 + deferring_job_definition_id = 1234 +} diff --git a/migration_scripts_to_v2/job/migrate_job.py b/migration_scripts_to_v2/job/migrate_job.py new file mode 100644 index 00000000..4ece1933 --- /dev/null +++ b/migration_scripts_to_v2/job/migrate_job.py @@ -0,0 +1,290 @@ +#!/usr/bin/env python3 +""" +Migration script: dbtcloud_job changes (v1 -> v2) + +Changes in v2: + - resource "dbtcloud_job": + - Top-level `timeout_seconds` removed. Use the `execution` block instead: + # Before + timeout_seconds = 3600 + + # After + execution = { + timeout_seconds = 3600 + } + If a resource already has an `execution` block with `timeout_seconds`, the + top-level attribute is simply removed (no duplicate is created). + + - data "dbtcloud_job" (singular): + - `deferring_job_id` attribute removed (no replacement in data source). + - References to `.deferring_job_id` in expressions are flagged with a warning. + + - data "dbtcloud_jobs" (plural): + - `deferring_job_definition_id` attribute removed. + +This script handles all of the above automatically where safe, and flags cases +that require manual review. + +Usage: + python migrate_job.py [--dry-run] [ ...] + + can be a .tf file or a directory (searched recursively). + +Examples: + python migrate_job.py ./terraform/ + python migrate_job.py --dry-run ./envs/prod/ ./envs/staging/ + python migrate_job.py module1/main.tf module2/main.tf +""" + +import argparse +import re +import shutil +import sys +from pathlib import Path + + +def find_tf_files(paths: list[str]) -> list[Path]: + result = [] + for p in paths: + path = Path(p) + if path.is_dir(): + result.extend(sorted(path.rglob("*.tf"))) + elif path.is_file() and path.suffix == ".tf": + result.append(path) + else: + print(f"WARNING: skipping {p} (not a .tf file or directory)", file=sys.stderr) + return result + + +def find_blocks(content: str, block_type: str, resource_type: str) -> list[tuple[int, int, str]]: + """ + Return list of (start, end, label) tuples for every matching block. + `end` points to the character after the closing brace. + """ + pattern = re.compile( + r'^[ \t]*' + re.escape(block_type) + r'\s+"' + re.escape(resource_type) + r'"\s+"([^"]+)"\s*\{', + re.MULTILINE, + ) + blocks = [] + for m in pattern.finditer(content): + label = m.group(1) + depth = 1 + i = m.end() + while i < len(content) and depth > 0: + if content[i] == "{": + depth += 1 + elif content[i] == "}": + depth -= 1 + i += 1 + blocks.append((m.start(), i, label)) + return blocks + + +def remove_attribute_in_range(content: str, start: int, end: int, attr: str) -> tuple[str, int]: + """Remove bare `attr = ...` lines within content[start:end].""" + attr_re = re.compile(r'^[ \t]*' + re.escape(attr) + r'\s*=\s*[^\n]+\n?', re.MULTILINE) + before = content[:start] + block = content[start:end] + after = content[end:] + new_block, count = attr_re.subn("", block) + return before + new_block + after, count + + +def has_execution_block(block_content: str) -> bool: + """Return True if the block already contains an `execution` sub-block.""" + return bool(re.search(r'\bexecution\s*=?\s*\{', block_content)) + + +def has_execution_timeout(block_content: str) -> bool: + """Return True if an execution block already contains timeout_seconds.""" + exec_match = re.search(r'\bexecution\s*=?\s*\{', block_content) + if not exec_match: + return False + # find the execution block body + depth = 1 + i = exec_match.end() + while i < len(block_content) and depth > 0: + if block_content[i] == "{": + depth += 1 + elif block_content[i] == "}": + depth -= 1 + i += 1 + exec_body = block_content[exec_match.start():i] + return bool(re.search(r'\btimeout_seconds\s*=', exec_body)) + + +def migrate_job_resource_timeout(content: str) -> tuple[str, list[str]]: + """ + For each resource "dbtcloud_job" block: + - If `timeout_seconds = N` exists at top level AND no `execution` block exists: + remove the top-level attribute and insert an execution block. + - If `timeout_seconds = N` exists at top level AND `execution` block already has + `timeout_seconds`: just remove the top-level attribute (execution wins). + - If `timeout_seconds = N` exists at top level AND `execution` block exists but + WITHOUT `timeout_seconds`: remove top-level and add timeout_seconds to execution. + """ + top_level_timeout_re = re.compile( + r'^([ \t]*)timeout_seconds\s*=\s*(\d+)\n?', re.MULTILINE + ) + + changes = [] + blocks = find_blocks(content, "resource", "dbtcloud_job") + + # Process in reverse order so offsets remain valid + for start, end, label in reversed(blocks): + block = content[start:end] + + m = top_level_timeout_re.search(block) + if not m: + continue + + indent = m.group(1) + timeout_value = m.group(2) + + if has_execution_timeout(block): + # execution block already has timeout_seconds — just remove top-level + new_block = top_level_timeout_re.sub("", block, count=1) + changes.append( + f' [REMOVE] top-level `timeout_seconds` from resource "dbtcloud_job" "{label}" ' + f'(execution block already has timeout_seconds)' + ) + elif has_execution_block(block): + # execution block exists but without timeout_seconds — add it there + exec_re = re.compile(r'(\bexecution\s*=?\s*\{)') + new_block = top_level_timeout_re.sub("", block, count=1) + new_block = exec_re.sub( + r'\1\n' + indent + ' timeout_seconds = ' + timeout_value, + new_block, + count=1, + ) + changes.append( + f' [MIGRATE] resource "dbtcloud_job" "{label}": ' + f'moved `timeout_seconds = {timeout_value}` into existing execution block' + ) + else: + # No execution block at all — remove top-level and add execution block + # Insert execution block right after removing timeout_seconds + new_block = top_level_timeout_re.sub( + indent + 'execution = {\n' + indent + ' timeout_seconds = ' + timeout_value + '\n' + indent + '}\n', + block, + count=1, + ) + changes.append( + f' [MIGRATE] resource "dbtcloud_job" "{label}": ' + f'converted `timeout_seconds = {timeout_value}` to execution block' + ) + + content = content[:start] + new_block + content[end:] + + return content, changes + + +def remove_attribute_from_blocks(content: str, block_type: str, resource_type: str, attr: str) -> tuple[str, list[str]]: + """Remove `attr = ...` lines from all matching blocks.""" + attr_re = re.compile(r'^[ \t]*' + re.escape(attr) + r'\s*=\s*[^\n]+\n?', re.MULTILINE) + changes = [] + blocks = find_blocks(content, block_type, resource_type) + + for start, end, label in reversed(blocks): + block = content[start:end] + new_block, n = attr_re.subn("", block) + if n: + changes.append( + f' [REMOVE] {n} `{attr}` attribute(s) from {block_type} "{resource_type}" "{label}"' + ) + content = content[:start] + new_block + content[end:] + + return content, changes + + +def warn_attribute_references(content: str, resource_type: str, attr: str) -> list[str]: + """Warn about expression references to a removed attribute.""" + pattern = re.compile( + r'(data\.' + re.escape(resource_type) + r'\.[a-zA-Z0-9_\-]+\.' + re.escape(attr) + r')' + ) + warnings = [] + for m in pattern.finditer(content): + line_num = content[:m.start()].count("\n") + 1 + warnings.append( + f' [WARN] line {line_num}: reference to removed attribute `{m.group(1)}` — ' + f'this attribute no longer exists in v2; remove or replace this reference manually.' + ) + return warnings + + +def process_file(path: Path, dry_run: bool) -> bool: + original = path.read_text(encoding="utf-8") + content = original + all_changes = [] + has_warnings = False + + # 1. Migrate top-level timeout_seconds in resource blocks + content, changes = migrate_job_resource_timeout(content) + all_changes.extend(changes) + + # 2. Remove deferring_job_id from data "dbtcloud_job" blocks + content, changes = remove_attribute_from_blocks(content, "data", "dbtcloud_job", "deferring_job_id") + all_changes.extend(changes) + + # 3. Remove deferring_job_definition_id from data "dbtcloud_jobs" blocks + content, changes = remove_attribute_from_blocks(content, "data", "dbtcloud_jobs", "deferring_job_definition_id") + all_changes.extend(changes) + + # 4. Warn about references to removed data source attributes + warnings = warn_attribute_references(content, "dbtcloud_job", "deferring_job_id") + if warnings: + has_warnings = True + all_changes.extend(warnings) + + if not all_changes: + return False + + print(f"\n{path}") + for c in all_changes: + print(c) + + if dry_run: + print(" (dry-run: no changes written)") + return True + + if has_warnings and not all_changes[0].startswith(" [WARN]"): + # changes to write (warnings are just informational) + pass + + # Only write if there are actual changes (not just warnings) + actual_changes = [c for c in all_changes if not c.startswith(" [WARN]")] + if not actual_changes: + return True # warnings only, no file changes + + shutil.copy2(path, path.with_suffix(".tf.bak")) + path.write_text(content, encoding="utf-8") + print(f" -> written (backup: {path.with_suffix('.tf.bak')})") + return True + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("paths", nargs="+", help=".tf files or directories to process") + parser.add_argument("--dry-run", action="store_true", help="show changes without writing files") + args = parser.parse_args() + + files = find_tf_files(args.paths) + if not files: + print("No .tf files found.", file=sys.stderr) + sys.exit(1) + + changed = 0 + for f in files: + if process_file(f, args.dry_run): + changed += 1 + + print(f"\nDone. {changed}/{len(files)} file(s) had changes or warnings.") + if changed and not args.dry_run: + print("\nNext steps:") + print(" 1. Review the changes above.") + print(" 2. Manually fix any [WARN] references to removed attributes.") + print(" 3. Run: terraform plan") + + +if __name__ == "__main__": + main() diff --git a/migration_scripts_to_v2/migrate_all.py b/migration_scripts_to_v2/migrate_all.py new file mode 100644 index 00000000..5641d2a3 --- /dev/null +++ b/migration_scripts_to_v2/migrate_all.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +""" +Master migration script: dbt Cloud Terraform Provider v1 -> v2 + +Runs all individual migration scripts in sequence against the supplied paths. + +Usage: + python migrate_all.py [--dry-run] [ ...] + + can be a .tf file or a directory (searched recursively). + +Examples: + python migrate_all.py ./terraform/ + python migrate_all.py --dry-run ./envs/prod/ ./envs/staging/ + python migrate_all.py module1/ module2/ module3/ +""" + +import argparse +import subprocess +import sys +from pathlib import Path + + +SCRIPTS = [ + ("project_artefacts/migrate_project_artefacts.py", "dbtcloud_project_artefacts (resource removed)"), + ("webhook/migrate_webhook.py", "dbtcloud_webhook (webhook_id removed)"), + ("repository/migrate_repository.py", "dbtcloud_repository (fetch_deploy_key removed)"), + ("databricks_credential/migrate_databricks_credential.py", "dbtcloud_databricks_credential (adapter_type, target_name removed)"), + ("spark_credential/migrate_spark_credential.py", "dbtcloud_spark_credential (target_name removed)"), + ("job/migrate_job.py", "dbtcloud_job (timeout_seconds migrated to execution block; data source attrs removed)"), +] + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("paths", nargs="+", help=".tf files or directories to process") + parser.add_argument("--dry-run", action="store_true", help="show changes without writing files") + args = parser.parse_args() + + scripts_dir = Path(__file__).parent + python = sys.executable + + print("=" * 70) + print("dbt Cloud Terraform Provider: v1 -> v2 migration") + print("=" * 70) + + for script_name, description in SCRIPTS: + script_path = scripts_dir / script_name + print(f"\n{'─' * 70}") + print(f"Running: {description}") + print(f"Script: {script_name}") + print(f"{'─' * 70}") + + cmd = [python, str(script_path)] + (["--dry-run"] if args.dry_run else []) + args.paths + result = subprocess.run(cmd) + if result.returncode not in (0, 1): + print(f"\nERROR: {script_name} exited with code {result.returncode}", file=sys.stderr) + + print(f"\n{'=' * 70}") + print("All migration scripts complete.") + if args.dry_run: + print("This was a dry run — no files were modified.") + print("Re-run without --dry-run to apply changes.") + else: + print("\nNext steps:") + print(" 1. Review all [WARN] messages above for items needing manual attention.") + print(" 2. For dbtcloud_project_artefacts removals, run:") + print(" terraform state rm dbtcloud_project_artefacts.") + print(" 3. Run: terraform init -upgrade") + print(" 4. Run: terraform plan") + print("=" * 70) + + +if __name__ == "__main__": + main() diff --git a/migration_scripts_to_v2/project_artefacts/README.md b/migration_scripts_to_v2/project_artefacts/README.md new file mode 100644 index 00000000..5d6f5439 --- /dev/null +++ b/migration_scripts_to_v2/project_artefacts/README.md @@ -0,0 +1,66 @@ +# Migration: dbtcloud_project_artefacts + +## What changed + +The `dbtcloud_project_artefacts` resource has been **removed** in v2. There is no replacement — the project artefacts configuration is no longer managed as a separate resource. + +## What the script does + +- Finds every `resource "dbtcloud_project_artefacts"` block in your `.tf` files and removes it entirely. +- Prints the name of each removed block along with the `terraform state rm` command you must run manually to clean up the state. + +> **Note:** The script only modifies your configuration files. You must remove the resources from your Terraform state separately (see Next Steps below). + +## Usage + +```bash +# Preview changes without modifying files +python migrate_project_artefacts.py --dry-run ./path/to/terraform/ + +# Apply changes (creates .tf.bak backups) +python migrate_project_artefacts.py ./path/to/terraform/ + +# Multiple paths +python migrate_project_artefacts.py ./envs/prod/ ./envs/staging/ ./modules/ +``` + +## Example + +**Input** (`example_input.tf`): +```hcl +resource "dbtcloud_project_artefacts" "prod_artefacts" { + project_id = 1234 + docs_job_id = 5678 + freshness_job_id = 9012 +} + +resource "dbtcloud_project_artefacts" "staging_artefacts" { + project_id = 2345 + docs_job_id = 6789 +} +``` + +**Output** (dry-run): +``` +project_artefacts/example_input.tf + [REMOVE] resource "dbtcloud_project_artefacts" "prod_artefacts" + -> also run: terraform state rm dbtcloud_project_artefacts.prod_artefacts + [REMOVE] resource "dbtcloud_project_artefacts" "staging_artefacts" + -> also run: terraform state rm dbtcloud_project_artefacts.staging_artefacts + (dry-run: no changes written) + +Done. 1/1 file(s) had changes. +``` + +**Result:** Both resource blocks are deleted from the file. + +## Next steps + +After running the script: + +1. For each removed resource, remove it from your Terraform state: + ```bash + terraform state rm dbtcloud_project_artefacts. + ``` +2. Run `terraform init -upgrade` to pick up the new provider version. +3. Run `terraform plan` — it should show no changes related to `dbtcloud_project_artefacts`. diff --git a/migration_scripts_to_v2/project_artefacts/example_input.tf b/migration_scripts_to_v2/project_artefacts/example_input.tf new file mode 100644 index 00000000..8049ee9f --- /dev/null +++ b/migration_scripts_to_v2/project_artefacts/example_input.tf @@ -0,0 +1,10 @@ +resource "dbtcloud_project_artefacts" "prod_artefacts" { + project_id = 1234 + docs_job_id = 5678 + freshness_job_id = 9012 +} + +resource "dbtcloud_project_artefacts" "staging_artefacts" { + project_id = 2345 + docs_job_id = 6789 +} diff --git a/migration_scripts_to_v2/project_artefacts/migrate_project_artefacts.py b/migration_scripts_to_v2/project_artefacts/migrate_project_artefacts.py new file mode 100644 index 00000000..f41bdb99 --- /dev/null +++ b/migration_scripts_to_v2/project_artefacts/migrate_project_artefacts.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +""" +Migration script: dbtcloud_project_artefacts removal (v1 -> v2) + +The dbtcloud_project_artefacts resource has been removed in v2. +This script finds all usages and removes them from your Terraform configs. + +IMPORTANT: After running this script, also remove the resource from state: + terraform state rm dbtcloud_project_artefacts. + +Usage: + python migrate_project_artefacts.py [--dry-run] [ ...] + + can be a .tf file or a directory (searched recursively). + +Examples: + python migrate_project_artefacts.py ./terraform/ + python migrate_project_artefacts.py --dry-run ./envs/prod/ ./envs/staging/ + python migrate_project_artefacts.py module1/main.tf module2/main.tf +""" + +import argparse +import re +import shutil +import sys +from pathlib import Path + + +def find_tf_files(paths: list[str]) -> list[Path]: + result = [] + for p in paths: + path = Path(p) + if path.is_dir(): + result.extend(sorted(path.rglob("*.tf"))) + elif path.is_file() and path.suffix == ".tf": + result.append(path) + else: + print(f"WARNING: skipping {p} (not a .tf file or directory)", file=sys.stderr) + return result + + +def remove_resource_block(content: str, resource_type: str) -> tuple[str, list[str]]: + """ + Remove all blocks matching: + resource "" "" { ... } + + Handles nested braces. Returns (new_content, list_of_removed_block_names). + """ + removed = [] + pattern = re.compile( + r'^([ \t]*)resource\s+"' + re.escape(resource_type) + r'"\s+"([^"]+)"\s*\{', + re.MULTILINE, + ) + + result = [] + pos = 0 + for m in pattern.finditer(content): + result.append(content[pos : m.start()]) + name = m.group(2) + # walk forward to find matching closing brace + depth = 1 + i = m.end() + while i < len(content) and depth > 0: + if content[i] == "{": + depth += 1 + elif content[i] == "}": + depth -= 1 + i += 1 + # consume trailing newline(s) + while i < len(content) and content[i] == "\n": + i += 1 + removed.append(name) + pos = i + + result.append(content[pos:]) + return "".join(result), removed + + +def process_file(path: Path, dry_run: bool) -> bool: + original = path.read_text(encoding="utf-8") + new_content, removed = remove_resource_block(original, "dbtcloud_project_artefacts") + + if not removed: + return False + + print(f"\n{path}") + for name in removed: + print(f" [REMOVE] resource \"dbtcloud_project_artefacts\" \"{name}\"") + print(f" -> also run: terraform state rm dbtcloud_project_artefacts.{name}") + + if dry_run: + print(" (dry-run: no changes written)") + return True + + shutil.copy2(path, path.with_suffix(".tf.bak")) + path.write_text(new_content, encoding="utf-8") + print(f" -> written (backup: {path.with_suffix('.tf.bak')})") + return True + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("paths", nargs="+", help=".tf files or directories to process") + parser.add_argument("--dry-run", action="store_true", help="show changes without writing files") + args = parser.parse_args() + + files = find_tf_files(args.paths) + if not files: + print("No .tf files found.", file=sys.stderr) + sys.exit(1) + + changed = 0 + for f in files: + if process_file(f, args.dry_run): + changed += 1 + + print(f"\nDone. {changed}/{len(files)} file(s) had changes.") + if changed and not args.dry_run: + print("\nNext steps:") + print(" 1. Review the changes above.") + print(" 2. Remove each resource from state:") + print(" terraform state rm dbtcloud_project_artefacts.") + print(" 3. Run: terraform plan") + + +if __name__ == "__main__": + main() diff --git a/migration_scripts_to_v2/repository/README.md b/migration_scripts_to_v2/repository/README.md new file mode 100644 index 00000000..fe63e21b --- /dev/null +++ b/migration_scripts_to_v2/repository/README.md @@ -0,0 +1,67 @@ +# Migration: dbtcloud_repository + +## What changed + +- **`resource "dbtcloud_repository"`**: the `fetch_deploy_key` attribute has been removed. This attribute had no effect on the repository configuration and was only used to fetch the deploy key value. +- **`data "dbtcloud_repository"`**: the `fetch_deploy_key` attribute has been removed. + +## What the script does + +- Removes `fetch_deploy_key = ...` lines from all `resource "dbtcloud_repository"` blocks. +- Removes `fetch_deploy_key = ...` lines from all `data "dbtcloud_repository"` blocks. + +## Usage + +```bash +# Preview changes without modifying files +python migrate_repository.py --dry-run ./path/to/terraform/ + +# Apply changes (creates .tf.bak backups) +python migrate_repository.py ./path/to/terraform/ + +# Multiple paths +python migrate_repository.py ./envs/prod/ ./envs/staging/ ./modules/ +``` + +## Example + +**Input** (`example_input.tf`): +```hcl +resource "dbtcloud_repository" "prod_repo" { + project_id = 1234 + remote_url = "https://github.com/my-org/my-repo.git" + git_clone_strategy = "github_app" + fetch_deploy_key = false +} + +resource "dbtcloud_repository" "staging_repo" { + project_id = 2345 + remote_url = "git@github.com:my-org/my-repo.git" + git_clone_strategy = "deploy_key" + fetch_deploy_key = true +} + +data "dbtcloud_repository" "existing_repo" { + project_id = 1234 + repository_id = 5678 + fetch_deploy_key = false +} +``` + +**Output** (dry-run): +``` +repository/example_input.tf + [REMOVE] 2 `fetch_deploy_key` attribute(s) from resource "dbtcloud_repository" block(s) + [REMOVE] 1 `fetch_deploy_key` attribute(s) from data "dbtcloud_repository" block(s) + (dry-run: no changes written) + +Done. 1/1 file(s) had changes. +``` + +**Result:** `fetch_deploy_key` is removed from all resource and data source blocks. + +## Next steps + +After running the script: + +1. Run `terraform plan` — it should show no changes related to repository attributes. diff --git a/migration_scripts_to_v2/repository/example_input.tf b/migration_scripts_to_v2/repository/example_input.tf new file mode 100644 index 00000000..c9f273d1 --- /dev/null +++ b/migration_scripts_to_v2/repository/example_input.tf @@ -0,0 +1,19 @@ +resource "dbtcloud_repository" "prod_repo" { + project_id = 1234 + remote_url = "https://github.com/my-org/my-repo.git" + git_clone_strategy = "github_app" + fetch_deploy_key = false +} + +resource "dbtcloud_repository" "staging_repo" { + project_id = 2345 + remote_url = "git@github.com:my-org/my-repo.git" + git_clone_strategy = "deploy_key" + fetch_deploy_key = true +} + +data "dbtcloud_repository" "existing_repo" { + project_id = 1234 + repository_id = 5678 + fetch_deploy_key = false +} diff --git a/migration_scripts_to_v2/repository/migrate_repository.py b/migration_scripts_to_v2/repository/migrate_repository.py new file mode 100644 index 00000000..59a0cf1b --- /dev/null +++ b/migration_scripts_to_v2/repository/migrate_repository.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +""" +Migration script: dbtcloud_repository changes (v1 -> v2) + +Changes in v2: + - resource "dbtcloud_repository": the `fetch_deploy_key` attribute has been removed. + - data "dbtcloud_repository": the `fetch_deploy_key` attribute has been removed. + +This script removes `fetch_deploy_key = ...` lines from all dbtcloud_repository +resource and data source blocks. + +Usage: + python migrate_repository.py [--dry-run] [ ...] + + can be a .tf file or a directory (searched recursively). + +Examples: + python migrate_repository.py ./terraform/ + python migrate_repository.py --dry-run ./envs/prod/ ./envs/staging/ + python migrate_repository.py module1/main.tf module2/main.tf +""" + +import argparse +import re +import shutil +import sys +from pathlib import Path + + +def find_tf_files(paths: list[str]) -> list[Path]: + result = [] + for p in paths: + path = Path(p) + if path.is_dir(): + result.extend(sorted(path.rglob("*.tf"))) + elif path.is_file() and path.suffix == ".tf": + result.append(path) + else: + print(f"WARNING: skipping {p} (not a .tf file or directory)", file=sys.stderr) + return result + + +def remove_attribute_from_blocks(content: str, block_type: str, resource_type: str, attr: str) -> tuple[str, int]: + """Remove `attr = ...` lines that appear inside blocks of the given type and resource type.""" + block_header = re.compile( + r'^([ \t]*)(?:' + re.escape(block_type) + r')\s+"' + re.escape(resource_type) + r'"\s+"[^"]+"\s*\{', + re.MULTILINE, + ) + attr_line = re.compile(r'^[ \t]*' + re.escape(attr) + r'\s*=\s*[^\n]+\n?', re.MULTILINE) + + count = 0 + result = [] + pos = 0 + + for m in block_header.finditer(content): + result.append(content[pos : m.start()]) + depth = 1 + i = m.end() + while i < len(content) and depth > 0: + if content[i] == "{": + depth += 1 + elif content[i] == "}": + depth -= 1 + i += 1 + block_content = content[m.start() : i] + new_block, n = attr_line.subn("", block_content) + count += n + result.append(new_block) + pos = i + + result.append(content[pos:]) + return "".join(result), count + + +def process_file(path: Path, dry_run: bool) -> bool: + original = path.read_text(encoding="utf-8") + content = original + changes = [] + + content, n = remove_attribute_from_blocks(content, "resource", "dbtcloud_repository", "fetch_deploy_key") + if n: + changes.append(f" [REMOVE] {n} `fetch_deploy_key` attribute(s) from resource \"dbtcloud_repository\" block(s)") + + content, n = remove_attribute_from_blocks(content, "data", "dbtcloud_repository", "fetch_deploy_key") + if n: + changes.append(f" [REMOVE] {n} `fetch_deploy_key` attribute(s) from data \"dbtcloud_repository\" block(s)") + + if not changes: + return False + + print(f"\n{path}") + for c in changes: + print(c) + + if dry_run: + print(" (dry-run: no changes written)") + return True + + shutil.copy2(path, path.with_suffix(".tf.bak")) + path.write_text(content, encoding="utf-8") + print(f" -> written (backup: {path.with_suffix('.tf.bak')})") + return True + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("paths", nargs="+", help=".tf files or directories to process") + parser.add_argument("--dry-run", action="store_true", help="show changes without writing files") + args = parser.parse_args() + + files = find_tf_files(args.paths) + if not files: + print("No .tf files found.", file=sys.stderr) + sys.exit(1) + + changed = 0 + for f in files: + if process_file(f, args.dry_run): + changed += 1 + + print(f"\nDone. {changed}/{len(files)} file(s) had changes.") + if changed and not args.dry_run: + print("\nNext steps:") + print(" 1. Review the changes above.") + print(" 2. Run: terraform plan") + + +if __name__ == "__main__": + main() diff --git a/migration_scripts_to_v2/spark_credential/README.md b/migration_scripts_to_v2/spark_credential/README.md new file mode 100644 index 00000000..22f877c2 --- /dev/null +++ b/migration_scripts_to_v2/spark_credential/README.md @@ -0,0 +1,67 @@ +# Migration: dbtcloud_spark_credential + +## What changed + +- **`resource "dbtcloud_spark_credential"`**: the `target_name` attribute has been removed. +- **`data "dbtcloud_spark_credential"`**: the `target_name` attribute has been removed. + +## What the script does + +- Removes `target_name = ...` lines from all `resource "dbtcloud_spark_credential"` blocks. +- Removes `target_name = ...` lines from all `data "dbtcloud_spark_credential"` blocks. + +## Usage + +```bash +# Preview changes without modifying files +python migrate_spark_credential.py --dry-run ./path/to/terraform/ + +# Apply changes (creates .tf.bak backups) +python migrate_spark_credential.py ./path/to/terraform/ + +# Multiple paths +python migrate_spark_credential.py ./envs/prod/ ./envs/staging/ ./modules/ +``` + +## Example + +**Input** (`example_input.tf`): +```hcl +resource "dbtcloud_spark_credential" "prod_credential" { + project_id = 1234 + target_name = "prod" + token = var.spark_token + schema = "my_schema" +} + +resource "dbtcloud_spark_credential" "dev_credential" { + project_id = 1234 + target_name = "dev" + token = var.spark_token_dev + schema = "dev_schema" +} + +data "dbtcloud_spark_credential" "existing" { + project_id = 1234 + credential_id = 5678 + target_name = "prod" +} +``` + +**Output** (dry-run): +``` +spark_credential/example_input.tf + [REMOVE] 2 `target_name` attribute(s) from resource "dbtcloud_spark_credential" block(s) + [REMOVE] 1 `target_name` attribute(s) from data "dbtcloud_spark_credential" block(s) + (dry-run: no changes written) + +Done. 1/1 file(s) had changes. +``` + +**Result:** `target_name` is removed from all resource and data source blocks. + +## Next steps + +After running the script: + +1. Run `terraform plan` — it should show no changes related to credential attributes. diff --git a/migration_scripts_to_v2/spark_credential/example_input.tf b/migration_scripts_to_v2/spark_credential/example_input.tf new file mode 100644 index 00000000..c341b6da --- /dev/null +++ b/migration_scripts_to_v2/spark_credential/example_input.tf @@ -0,0 +1,19 @@ +resource "dbtcloud_spark_credential" "prod_credential" { + project_id = 1234 + target_name = "prod" + token = var.spark_token + schema = "my_schema" +} + +resource "dbtcloud_spark_credential" "dev_credential" { + project_id = 1234 + target_name = "dev" + token = var.spark_token_dev + schema = "dev_schema" +} + +data "dbtcloud_spark_credential" "existing" { + project_id = 1234 + credential_id = 5678 + target_name = "prod" +} diff --git a/migration_scripts_to_v2/spark_credential/migrate_spark_credential.py b/migration_scripts_to_v2/spark_credential/migrate_spark_credential.py new file mode 100644 index 00000000..9d5f3d23 --- /dev/null +++ b/migration_scripts_to_v2/spark_credential/migrate_spark_credential.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +""" +Migration script: dbtcloud_spark_credential changes (v1 -> v2) + +Changes in v2: + - resource "dbtcloud_spark_credential": `target_name` attribute removed. + - data "dbtcloud_spark_credential": `target_name` attribute removed. + +This script removes `target_name = ...` lines from all matching blocks. + +Usage: + python migrate_spark_credential.py [--dry-run] [ ...] + + can be a .tf file or a directory (searched recursively). + +Examples: + python migrate_spark_credential.py ./terraform/ + python migrate_spark_credential.py --dry-run ./envs/prod/ ./envs/staging/ + python migrate_spark_credential.py module1/main.tf module2/main.tf +""" + +import argparse +import re +import shutil +import sys +from pathlib import Path + + +def find_tf_files(paths: list[str]) -> list[Path]: + result = [] + for p in paths: + path = Path(p) + if path.is_dir(): + result.extend(sorted(path.rglob("*.tf"))) + elif path.is_file() and path.suffix == ".tf": + result.append(path) + else: + print(f"WARNING: skipping {p} (not a .tf file or directory)", file=sys.stderr) + return result + + +def remove_attribute_from_blocks(content: str, block_type: str, resource_type: str, attr: str) -> tuple[str, int]: + """Remove `attr = ...` lines that appear inside blocks of the given type and resource type.""" + block_header = re.compile( + r'^([ \t]*)(?:' + re.escape(block_type) + r')\s+"' + re.escape(resource_type) + r'"\s+"[^"]+"\s*\{', + re.MULTILINE, + ) + attr_line = re.compile(r'^[ \t]*' + re.escape(attr) + r'\s*=\s*[^\n]+\n?', re.MULTILINE) + + count = 0 + result = [] + pos = 0 + + for m in block_header.finditer(content): + result.append(content[pos : m.start()]) + depth = 1 + i = m.end() + while i < len(content) and depth > 0: + if content[i] == "{": + depth += 1 + elif content[i] == "}": + depth -= 1 + i += 1 + block_content = content[m.start() : i] + new_block, n = attr_line.subn("", block_content) + count += n + result.append(new_block) + pos = i + + result.append(content[pos:]) + return "".join(result), count + + +def process_file(path: Path, dry_run: bool) -> bool: + original = path.read_text(encoding="utf-8") + content = original + changes = [] + + content, n = remove_attribute_from_blocks(content, "resource", "dbtcloud_spark_credential", "target_name") + if n: + changes.append(f" [REMOVE] {n} `target_name` attribute(s) from resource \"dbtcloud_spark_credential\" block(s)") + + content, n = remove_attribute_from_blocks(content, "data", "dbtcloud_spark_credential", "target_name") + if n: + changes.append(f" [REMOVE] {n} `target_name` attribute(s) from data \"dbtcloud_spark_credential\" block(s)") + + if not changes: + return False + + print(f"\n{path}") + for c in changes: + print(c) + + if dry_run: + print(" (dry-run: no changes written)") + return True + + shutil.copy2(path, path.with_suffix(".tf.bak")) + path.write_text(content, encoding="utf-8") + print(f" -> written (backup: {path.with_suffix('.tf.bak')})") + return True + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("paths", nargs="+", help=".tf files or directories to process") + parser.add_argument("--dry-run", action="store_true", help="show changes without writing files") + args = parser.parse_args() + + files = find_tf_files(args.paths) + if not files: + print("No .tf files found.", file=sys.stderr) + sys.exit(1) + + changed = 0 + for f in files: + if process_file(f, args.dry_run): + changed += 1 + + print(f"\nDone. {changed}/{len(files)} file(s) had changes.") + if changed and not args.dry_run: + print("\nNext steps:") + print(" 1. Review the changes above.") + print(" 2. Run: terraform plan") + + +if __name__ == "__main__": + main() diff --git a/migration_scripts_to_v2/webhook/README.md b/migration_scripts_to_v2/webhook/README.md new file mode 100644 index 00000000..3a23be93 --- /dev/null +++ b/migration_scripts_to_v2/webhook/README.md @@ -0,0 +1,73 @@ +# Migration: dbtcloud_webhook + +## What changed + +- **`resource "dbtcloud_webhook"`**: the `webhook_id` attribute has been removed. Use the `id` attribute instead — it holds the same value and has always been populated. +- **`data "dbtcloud_webhook"`**: the `webhook_id` attribute has been removed. The `id` attribute is now `Required` (you must supply it to look up a webhook). + +## What the script does + +- Removes `webhook_id = ...` lines from all `resource "dbtcloud_webhook"` blocks. +- Removes `webhook_id = ...` lines from all `data "dbtcloud_webhook"` blocks. +- Rewrites any expression references of the form `data.dbtcloud_webhook..webhook_id` to `data.dbtcloud_webhook..id`. + +## Usage + +```bash +# Preview changes without modifying files +python migrate_webhook.py --dry-run ./path/to/terraform/ + +# Apply changes (creates .tf.bak backups) +python migrate_webhook.py ./path/to/terraform/ + +# Multiple paths +python migrate_webhook.py ./envs/prod/ ./envs/staging/ ./modules/ +``` + +## Example + +**Input** (`example_input.tf`): +```hcl +resource "dbtcloud_webhook" "job_completed" { + client_url = "https://example.com/webhook" + event_types = ["job.run.completed"] + name = "Job Completed Hook" + webhook_id = "wsu_abc123" +} + +resource "dbtcloud_webhook" "job_started" { + client_url = "https://example.com/webhook/start" + event_types = ["job.run.started"] + name = "Job Started Hook" + webhook_id = "wsu_def456" +} + +data "dbtcloud_webhook" "existing_hook" { + id = "wsu_abc123" + webhook_id = "wsu_abc123" +} + +output "webhook_endpoint" { + value = data.dbtcloud_webhook.existing_hook.webhook_id +} +``` + +**Output** (dry-run): +``` +webhook/example_input.tf + [REMOVE] 2 `webhook_id` attribute(s) from resource "dbtcloud_webhook" block(s) + [REMOVE] 1 `webhook_id` attribute(s) from data "dbtcloud_webhook" block(s) + [REPLACE] 1 reference(s) `.webhook_id` -> `.id` in expressions + (dry-run: no changes written) + +Done. 1/1 file(s) had changes. +``` + +**Result:** `webhook_id` is removed from all resource and data source blocks, and the output reference is rewritten to use `.id`. + +## Next steps + +After running the script: + +1. Check any `data "dbtcloud_webhook"` blocks to confirm `id` is set — this is now a required field. +2. Run `terraform plan` — it should show no changes related to webhook attributes. diff --git a/migration_scripts_to_v2/webhook/example_input.tf b/migration_scripts_to_v2/webhook/example_input.tf new file mode 100644 index 00000000..b419c5eb --- /dev/null +++ b/migration_scripts_to_v2/webhook/example_input.tf @@ -0,0 +1,22 @@ +resource "dbtcloud_webhook" "job_completed" { + client_url = "https://example.com/webhook" + event_types = ["job.run.completed"] + name = "Job Completed Hook" + webhook_id = "wsu_abc123" +} + +resource "dbtcloud_webhook" "job_started" { + client_url = "https://example.com/webhook/start" + event_types = ["job.run.started"] + name = "Job Started Hook" + webhook_id = "wsu_def456" +} + +data "dbtcloud_webhook" "existing_hook" { + id = "wsu_abc123" + webhook_id = "wsu_abc123" +} + +output "webhook_endpoint" { + value = data.dbtcloud_webhook.existing_hook.webhook_id +} diff --git a/migration_scripts_to_v2/webhook/migrate_webhook.py b/migration_scripts_to_v2/webhook/migrate_webhook.py new file mode 100644 index 00000000..76af55d7 --- /dev/null +++ b/migration_scripts_to_v2/webhook/migrate_webhook.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +""" +Migration script: dbtcloud_webhook changes (v1 -> v2) + +Changes in v2: + - resource "dbtcloud_webhook": the `webhook_id` attribute has been removed. + Use the `id` attribute instead. + - data "dbtcloud_webhook": the `webhook_id` attribute has been removed. + The `id` attribute is now Required (you must supply it to look up a webhook). + +This script: + 1. Removes `webhook_id = ...` lines from dbtcloud_webhook resource blocks. + 2. Removes `webhook_id = ...` lines from dbtcloud_webhook data source blocks. + 3. Replaces references to `.webhook_id` attribute in expressions with `.id`. + +Usage: + python migrate_webhook.py [--dry-run] [ ...] + + can be a .tf file or a directory (searched recursively). + +Examples: + python migrate_webhook.py ./terraform/ + python migrate_webhook.py --dry-run ./envs/prod/ ./envs/staging/ + python migrate_webhook.py module1/main.tf module2/main.tf +""" + +import argparse +import re +import shutil +import sys +from pathlib import Path + + +def find_tf_files(paths: list[str]) -> list[Path]: + result = [] + for p in paths: + path = Path(p) + if path.is_dir(): + result.extend(sorted(path.rglob("*.tf"))) + elif path.is_file() and path.suffix == ".tf": + result.append(path) + else: + print(f"WARNING: skipping {p} (not a .tf file or directory)", file=sys.stderr) + return result + + +def remove_attribute_from_blocks(content: str, block_type: str, resource_type: str, attr: str) -> tuple[str, int]: + """Remove `attr = ...` lines that appear inside blocks of the given type and resource type.""" + # Match the block header + block_header = re.compile( + r'^([ \t]*)(?:' + re.escape(block_type) + r')\s+"' + re.escape(resource_type) + r'"\s+"[^"]+"\s*\{', + re.MULTILINE, + ) + # Match the attribute line (handles quoted strings, numbers, booleans, references) + attr_line = re.compile(r'^[ \t]*' + re.escape(attr) + r'\s*=\s*[^\n]+\n?', re.MULTILINE) + + count = 0 + result = [] + pos = 0 + + for m in block_header.finditer(content): + result.append(content[pos : m.start()]) + # find the end of the block (matching closing brace) + depth = 1 + i = m.end() + while i < len(content) and depth > 0: + if content[i] == "{": + depth += 1 + elif content[i] == "}": + depth -= 1 + i += 1 + block_content = content[m.start() : i] + new_block, n = attr_line.subn("", block_content) + count += n + result.append(new_block) + pos = i + + result.append(content[pos:]) + return "".join(result), count + + +def replace_attribute_references(content: str, resource_type: str, old_attr: str, new_attr: str) -> tuple[str, int]: + """Replace data... with data....""" + pattern = re.compile( + r'\b(data\.' + re.escape(resource_type) + r'\.[a-zA-Z0-9_\-]+)\.' + re.escape(old_attr) + r'\b' + ) + new_content, count = pattern.subn(r'\1.' + new_attr, content) + return new_content, count + + +def process_file(path: Path, dry_run: bool) -> bool: + original = path.read_text(encoding="utf-8") + content = original + changes = [] + + # Remove webhook_id from resource blocks + content, n = remove_attribute_from_blocks(content, "resource", "dbtcloud_webhook", "webhook_id") + if n: + changes.append(f" [REMOVE] {n} `webhook_id` attribute(s) from resource \"dbtcloud_webhook\" block(s)") + + # Remove webhook_id from data source blocks + content, n = remove_attribute_from_blocks(content, "data", "dbtcloud_webhook", "webhook_id") + if n: + changes.append(f" [REMOVE] {n} `webhook_id` attribute(s) from data \"dbtcloud_webhook\" block(s)") + + # Replace .webhook_id references with .id in expressions + content, n = replace_attribute_references(content, "dbtcloud_webhook", "webhook_id", "id") + if n: + changes.append(f" [REPLACE] {n} reference(s) `.webhook_id` -> `.id` in expressions") + + if not changes: + return False + + print(f"\n{path}") + for c in changes: + print(c) + + if dry_run: + print(" (dry-run: no changes written)") + return True + + shutil.copy2(path, path.with_suffix(".tf.bak")) + path.write_text(content, encoding="utf-8") + print(f" -> written (backup: {path.with_suffix('.tf.bak')})") + return True + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("paths", nargs="+", help=".tf files or directories to process") + parser.add_argument("--dry-run", action="store_true", help="show changes without writing files") + args = parser.parse_args() + + files = find_tf_files(args.paths) + if not files: + print("No .tf files found.", file=sys.stderr) + sys.exit(1) + + changed = 0 + for f in files: + if process_file(f, args.dry_run): + changed += 1 + + print(f"\nDone. {changed}/{len(files)} file(s) had changes.") + if changed and not args.dry_run: + print("\nNext steps:") + print(" 1. Review the changes above.") + print(" 2. For each data \"dbtcloud_webhook\" block that previously used `webhook_id`") + print(" to look up a webhook, make sure `id` is now explicitly set.") + print(" 3. Run: terraform plan") + + +if __name__ == "__main__": + main() diff --git a/pkg/dbt_cloud/client.go b/pkg/dbt_cloud/client.go index f3501dbb..4e0d53df 100644 --- a/pkg/dbt_cloud/client.go +++ b/pkg/dbt_cloud/client.go @@ -1,6 +1,7 @@ package dbt_cloud import ( + "bytes" "encoding/json" "fmt" "io" @@ -188,7 +189,25 @@ func (c *Client) doRequestWithRetry(req *http.Request) ([]byte, error) { setRequestHeaders(req, c.Token) + // Capture the request body bytes upfront so they can be replayed on retries. + // http.Request.Body is consumed after the first Do() call, so without this + // any retry would send an empty body. + var requestBodyBytes []byte + if req.Body != nil { + requestBodyBytes, err = io.ReadAll(req.Body) + if err != nil { + return nil, fmt.Errorf("error reading request body: %w", err) + } + req.Body = io.NopCloser(bytes.NewReader(requestBodyBytes)) + } + for i := 0; i < c.MaxRetries; i++ { + // Reset the body at the start of every attempt after the first, since + // the previous Do() call will have consumed it. + if i > 0 && requestBodyBytes != nil { + req.Body = io.NopCloser(bytes.NewReader(requestBodyBytes)) + } + res, err := c.HTTPClient.Do(req) if err != nil { return nil, err @@ -239,6 +258,19 @@ func (c *Client) doRequestWithRetry(req *http.Request) ([]byte, error) { } if res.StatusCode == 500 { + // Only retry when the response has no JSON Content-Type, which + // indicates the request never reached the application (e.g. a + // load-balancer or reverse-proxy swallowed it). When the API itself + // returns a 500 it always sets Content-Type: application/json, so + // retrying that case is not safe — a create may have succeeded + // server-side and a retry could produce a duplicate resource. + isInfraError := !strings.Contains(res.Header.Get("Content-Type"), "application/json") + if isInfraError && i < c.MaxRetries-1 { + waitDuration := time.Duration(c.RetryIntervalSeconds) * time.Second * time.Duration(1<= 1.11") projectName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) - targetName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) catalog := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) token := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) token2 := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) @@ -364,7 +302,7 @@ func TestAccDbtCloudDatabricksCredentialResourceWriteOnly(t *testing.T) { // Step 1: Create with write-only token { Config: testAccDbtCloudDatabricksCredentialWriteOnlyConfig( - projectName, catalog, targetName, token, 1, + projectName, catalog, token, 1, ), Check: resource.ComposeTestCheckFunc( testAccCheckDbtCloudDatabricksCredentialExists( @@ -390,7 +328,7 @@ func TestAccDbtCloudDatabricksCredentialResourceWriteOnly(t *testing.T) { // Step 2: Update by incrementing version with new token { Config: testAccDbtCloudDatabricksCredentialWriteOnlyConfig( - projectName, catalog, targetName, token2, 2, + projectName, catalog, token2, 2, ), Check: resource.ComposeTestCheckFunc( testAccCheckDbtCloudDatabricksCredentialExists( @@ -413,7 +351,7 @@ func TestAccDbtCloudDatabricksCredentialResourceWriteOnly(t *testing.T) { ImportState: true, ImportStateVerify: true, ImportStateVerifyIgnore: []string{ - "token", "adapter_type", "semantic_layer_credential", + "token", "semantic_layer_credential", "token_wo", "token_wo_version", }, }, @@ -422,7 +360,7 @@ func TestAccDbtCloudDatabricksCredentialResourceWriteOnly(t *testing.T) { } func testAccDbtCloudDatabricksCredentialWriteOnlyConfig( - projectName, catalogName, targetName, tokenWo string, tokenWoVersion int, + projectName, catalogName, tokenWo string, tokenWoVersion int, ) string { return fmt.Sprintf(` resource "dbtcloud_project" "test_project" { @@ -453,13 +391,11 @@ resource "dbtcloud_environment" "prod_environment" { resource "dbtcloud_databricks_credential" "test_credential_wo" { project_id = dbtcloud_project.test_project.id catalog = "%s" - target_name = "%s" token_wo = "%s" token_wo_version = %d schema = "my_schema" - adapter_type = "databricks" } -`, projectName, catalogName, targetName, tokenWo, tokenWoVersion) +`, projectName, catalogName, tokenWo, tokenWoVersion) } func databricksCredentialMockHandlers(accountID int64, projectID, credentialID int, tracker *testhelpers.APICallTracker) map[string]testhelpers.MockEndpointHandler { diff --git a/pkg/framework/objects/databricks_credential/schema.go b/pkg/framework/objects/databricks_credential/schema.go index bd3e19ae..b3243af7 100644 --- a/pkg/framework/objects/databricks_credential/schema.go +++ b/pkg/framework/objects/databricks_credential/schema.go @@ -29,10 +29,6 @@ var dataSourceSchema = datasource_schema.Schema{ Description: "Credential ID", Required: true, }, - "target_name": datasource_schema.StringAttribute{ - Description: "Target name", - Computed: true, - }, "num_threads": datasource_schema.Int64Attribute{ Description: "The number of threads to use", Computed: true, @@ -76,13 +72,6 @@ var DatabricksResourceSchema = resource_schema.Schema{ int64planmodifier.UseStateForUnknown(), }, }, - "target_name": resource_schema.StringAttribute{ - Description: "Target name", - Optional: true, - Computed: true, - Default: stringdefault.StaticString("default"), - DeprecationMessage: "This field is deprecated at the environment level (it was never possible to set it in the UI) and will be removed in a future release. Please remove it and set the target name at the job level or leverage environment variables.", - }, "token": resource_schema.StringAttribute{ Description: "Token for Databricks user. Consider using `token_wo` instead, which is not stored in state.", Optional: true, @@ -120,20 +109,6 @@ var DatabricksResourceSchema = resource_schema.Schema{ helper.SchemaNameValidator(), }, }, - "adapter_type": resource_schema.StringAttribute{ - Description: "The type of the adapter. 'spark' is deprecated, but still supported for backwards compatibility. For Spark, please use the spark_credential resource. Optional only when semantic_layer_credential is set to true; otherwise, this field is required.", - Optional: true, - Computed: true, - DeprecationMessage: "This field is deprecated and will be removed in a future release. Semantic Layer spark credentials are not supported yet, only databricks is supported.", - Default: stringdefault.StaticString("databricks"), - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - Validators: []validator.String{ - stringvalidator.OneOf("databricks", "spark"), - helper.SemanticLayerCredentialValidator{FieldName: "adapter_type"}, - }, - }, "semantic_layer_credential": resource_schema.BoolAttribute{ Optional: true, Description: "This field indicates that the credential is used as part of the Semantic Layer configuration. It is used to create a Databricks credential for the Semantic Layer.", diff --git a/pkg/framework/objects/job/data_source.go b/pkg/framework/objects/job/data_source.go index b97c4910..2d89123c 100644 --- a/pkg/framework/objects/job/data_source.go +++ b/pkg/framework/objects/job/data_source.go @@ -87,7 +87,6 @@ func (j *jobDataSource) Read(ctx context.Context, req datasource.ReadRequest, re state.Execution = &JobExecution{ TimeoutSeconds: types.Int64Value(int64(job.Execution.TimeoutSeconds)), } - state.TimeoutSeconds = types.Int64Value(int64(job.Execution.TimeoutSeconds)) state.GenerateDocs = types.BoolValue(job.GenerateDocs) state.RunGenerateSources = types.BoolValue(job.RunGenerateSources) state.ID = types.Int64PointerValue(helper.IntPointerToInt64Pointer(job.ID)) @@ -97,7 +96,6 @@ func (j *jobDataSource) Read(ctx context.Context, req datasource.ReadRequest, re state.Description = types.StringValue(job.Description) state.DbtVersion = types.StringPointerValue(job.DbtVersion) state.ExecuteSteps = helper.SliceStringToSliceTypesString(job.ExecuteSteps) - state.DeferringJobId = types.Int64PointerValue(helper.IntPointerToInt64Pointer(job.DeferringJobId)) state.SelfDeferring = types.BoolValue(job.DeferringJobId != nil && *job.DeferringJobId == *job.ID) state.DeferringEnvironmentID = types.Int64PointerValue(helper.IntPointerToInt64Pointer(job.DeferringEnvironmentId)) diff --git a/pkg/framework/objects/job/data_source_acceptance_test.go b/pkg/framework/objects/job/data_source_acceptance_test.go index 26174ada..16a22291 100644 --- a/pkg/framework/objects/job/data_source_acceptance_test.go +++ b/pkg/framework/objects/job/data_source_acceptance_test.go @@ -21,8 +21,6 @@ func TestDbtCloudJobDataSource(t *testing.T) { resource.TestCheckResourceAttrSet("data.dbtcloud_job.test", "project_id"), resource.TestCheckResourceAttrSet("data.dbtcloud_job.test", "environment_id"), resource.TestCheckResourceAttr("data.dbtcloud_job.test", "name", randomJobName), - resource.TestCheckResourceAttr("data.dbtcloud_job.test", "execution.timeout_seconds", "180"), - resource.TestCheckResourceAttr("data.dbtcloud_job.test", "timeout_seconds", "180"), resource.TestCheckResourceAttr("data.dbtcloud_job.test", "triggers_on_draft_pr", "false"), resource.TestCheckResourceAttr( "data.dbtcloud_job.test", @@ -67,7 +65,6 @@ func jobResourceConfig(jobName string) string { "schedule" : false, "git_provider_webhook": false } - timeout_seconds = 180 } data "dbtcloud_job" "test" { diff --git a/pkg/framework/objects/job/data_source_all.go b/pkg/framework/objects/job/data_source_all.go index 579f3134..f4d630f4 100644 --- a/pkg/framework/objects/job/data_source_all.go +++ b/pkg/framework/objects/job/data_source_all.go @@ -110,9 +110,6 @@ func (d *jobsDataSource) Read( job.DbtVersion, ), ExecuteSteps: helper.SliceStringToSliceTypesString(job.ExecuteSteps), - DeferringJobDefinitionID: types.Int64PointerValue(helper.IntPointerToInt64Pointer( - job.DeferringJobId), - ), DeferringEnvironmentID: types.Int64PointerValue(helper.IntPointerToInt64Pointer( job.DeferringEnvironmentId), ), diff --git a/pkg/framework/objects/job/data_source_all_acceptance_test.go b/pkg/framework/objects/job/data_source_all_acceptance_test.go index 95f3313b..cbff6213 100644 --- a/pkg/framework/objects/job/data_source_all_acceptance_test.go +++ b/pkg/framework/objects/job/data_source_all_acceptance_test.go @@ -101,7 +101,9 @@ func jobsResourceConfig(jobName string, jobName2 string) string { "schedule" : false, "git_provider_webhook": false } - timeout_seconds = 180 + execution = { + timeout_seconds = 180 + } job_completion_trigger_condition { job_id = dbtcloud_job.test_job2.id project_id = dbtcloud_project.test_project.id @@ -121,8 +123,7 @@ func jobsResourceConfig(jobName string, jobName2 string) string { "schedule" : false, "git_provider_webhook": false } - timeout_seconds = 1800 - } + } data "dbtcloud_jobs" "test" { project_id = dbtcloud_project.test_project.id diff --git a/pkg/framework/objects/job/model.go b/pkg/framework/objects/job/model.go index 89e3c2a7..3a74b4d2 100644 --- a/pkg/framework/objects/job/model.go +++ b/pkg/framework/objects/job/model.go @@ -48,7 +48,6 @@ type JobSchedule struct { type JobDataSourceModel struct { Execution *JobExecution `tfsdk:"execution"` - TimeoutSeconds types.Int64 `tfsdk:"timeout_seconds"` GenerateDocs types.Bool `tfsdk:"generate_docs"` RunGenerateSources types.Bool `tfsdk:"run_generate_sources"` ID types.Int64 `tfsdk:"id"` @@ -59,7 +58,6 @@ type JobDataSourceModel struct { Description types.String `tfsdk:"description"` DbtVersion types.String `tfsdk:"dbt_version"` ExecuteSteps []types.String `tfsdk:"execute_steps"` - DeferringJobDefinitionID types.Int64 `tfsdk:"deferring_job_definition_id"` DeferringEnvironmentID types.Int64 `tfsdk:"deferring_environment_id"` ForceNodeSelection types.Bool `tfsdk:"force_node_selection"` Triggers *JobTriggers `tfsdk:"triggers"` @@ -75,7 +73,6 @@ type JobDataSourceModel struct { // TODO remove this in the next major release type SingleJobDataSourceModel struct { Execution *JobExecution `tfsdk:"execution"` - TimeoutSeconds types.Int64 `tfsdk:"timeout_seconds"` GenerateDocs types.Bool `tfsdk:"generate_docs"` RunGenerateSources types.Bool `tfsdk:"run_generate_sources"` ID types.Int64 `tfsdk:"id"` @@ -86,7 +83,6 @@ type SingleJobDataSourceModel struct { Description types.String `tfsdk:"description"` DbtVersion types.String `tfsdk:"dbt_version"` ExecuteSteps []types.String `tfsdk:"execute_steps"` - DeferringJobId types.Int64 `tfsdk:"deferring_job_id"` DeferringEnvironmentID types.Int64 `tfsdk:"deferring_environment_id"` ForceNodeSelection types.Bool `tfsdk:"force_node_selection"` SelfDeferring types.Bool `tfsdk:"self_deferring"` @@ -102,7 +98,6 @@ type SingleJobDataSourceModel struct { type JobResourceModel struct { Execution *JobExecution `tfsdk:"execution"` - TimeoutSeconds types.Int64 `tfsdk:"timeout_seconds"` // deprecated, use Execution.TimeoutSeconds GenerateDocs types.Bool `tfsdk:"generate_docs"` // exists RunGenerateSources types.Bool `tfsdk:"run_generate_sources"` // exists ID types.Int64 `tfsdk:"id"` // will hold job id? diff --git a/pkg/framework/objects/job/resource.go b/pkg/framework/objects/job/resource.go index 2775fd79..f7d46c00 100644 --- a/pkg/framework/objects/job/resource.go +++ b/pkg/framework/objects/job/resource.go @@ -250,14 +250,9 @@ func (j *jobResource) Create(ctx context.Context, req resource.CreateRequest, re } selfDeferring := plan.SelfDeferring.ValueBool() - // Handle timeout_seconds from either execution block (preferred) or top-level (deprecated) var timeoutSeconds int if plan.Execution != nil && !plan.Execution.TimeoutSeconds.IsNull() { timeoutSeconds = int(plan.Execution.TimeoutSeconds.ValueInt64()) - } else { - if !plan.TimeoutSeconds.IsNull() { - timeoutSeconds = int(plan.TimeoutSeconds.ValueInt64()) - } } triggersOnDraftPR := plan.TriggersOnDraftPr.ValueBool() runCompareChanges := plan.RunCompareChanges.ValueBool() @@ -368,11 +363,6 @@ func (j *jobResource) Create(ctx context.Context, req resource.CreateRequest, re plan.Execution = &JobExecution{ TimeoutSeconds: types.Int64Value(int64(createdJob.Execution.TimeoutSeconds)), } - // Don't update deprecated timeout_seconds when user is using execution block - // to avoid "inconsistent result after apply" error - } else { - // Only update deprecated timeout_seconds when user is NOT using execution block - plan.TimeoutSeconds = types.Int64Value(int64(createdJob.Execution.TimeoutSeconds)) } if createdJob.JobType != "" { @@ -539,11 +529,6 @@ func (j *jobResource) Read(ctx context.Context, req resource.ReadRequest, resp * state.Execution = &JobExecution{ TimeoutSeconds: types.Int64Value(int64(retrievedJob.Execution.TimeoutSeconds)), } - // Don't update deprecated timeout_seconds when user is using execution block - // to avoid "inconsistent result after apply" error - } else { - // Only update deprecated timeout_seconds when user is NOT using execution block - state.TimeoutSeconds = types.Int64Value(int64(retrievedJob.Execution.TimeoutSeconds)) } var triggers map[string]interface{} @@ -753,11 +738,8 @@ func (j *jobResource) Update(ctx context.Context, req resource.UpdateRequest, re } } - // Handle timeout_seconds from either execution block (preferred) or top-level (deprecated) if plan.Execution != nil && !plan.Execution.TimeoutSeconds.IsNull() { job.Execution.TimeoutSeconds = int(plan.Execution.TimeoutSeconds.ValueInt64()) - } else { - job.Execution.TimeoutSeconds = int(plan.TimeoutSeconds.ValueInt64()) } job.TriggersOnDraftPR = plan.TriggersOnDraftPr.ValueBool() @@ -859,11 +841,6 @@ func (j *jobResource) Update(ctx context.Context, req resource.UpdateRequest, re plan.Execution = &JobExecution{ TimeoutSeconds: types.Int64Value(int64(updatedJob.Execution.TimeoutSeconds)), } - // Don't update deprecated timeout_seconds when user is using execution block - // to avoid "inconsistent result after apply" error - } else { - // Only update deprecated timeout_seconds when user is NOT using execution block - plan.TimeoutSeconds = types.Int64Value(int64(updatedJob.Execution.TimeoutSeconds)) } // Populate force_node_selection from API response diff --git a/pkg/framework/objects/job/resource_acceptance_test.go b/pkg/framework/objects/job/resource_acceptance_test.go index bb3ec302..4de9d787 100644 --- a/pkg/framework/objects/job/resource_acceptance_test.go +++ b/pkg/framework/objects/job/resource_acceptance_test.go @@ -86,11 +86,6 @@ func TestAccDbtCloudJobResource(t *testing.T) { acctest_config.DBT_CLOUD_VERSION, ), resource.TestCheckResourceAttr("dbtcloud_job.test_job", "target_name", "test"), - resource.TestCheckResourceAttr( - "dbtcloud_job.test_job", - "timeout_seconds", - "180", - ), resource.TestCheckResourceAttrSet("dbtcloud_job.test_job", "project_id"), resource.TestCheckResourceAttrSet("dbtcloud_job.test_job", "environment_id"), resource.TestCheckResourceAttrSet("dbtcloud_job.test_job", "is_active"), @@ -159,11 +154,6 @@ func TestAccDbtCloudJobResource(t *testing.T) { acctest_config.DBT_CLOUD_VERSION, ), resource.TestCheckResourceAttr("dbtcloud_job.test_job", "target_name", "test"), - resource.TestCheckResourceAttr( - "dbtcloud_job.test_job", - "timeout_seconds", - "180", - ), resource.TestCheckResourceAttrSet("dbtcloud_job.test_job", "project_id"), resource.TestCheckResourceAttrSet("dbtcloud_job.test_job", "environment_id"), resource.TestCheckResourceAttrSet("dbtcloud_job.test_job", "is_active"), @@ -404,7 +394,6 @@ resource "dbtcloud_job" "test_job" { generate_docs = true schedule_type = "every_day" schedule_hours = [9, 17] - timeout_seconds = 180 } `, projectName, environmentName, acctest_config.DBT_CLOUD_VERSION, environmentName, acctest_config.DBT_CLOUD_VERSION, jobName, acctest_config.DBT_CLOUD_VERSION) } @@ -452,7 +441,6 @@ resource "dbtcloud_job" "test_job" { generate_docs = true schedule_type = "every_day" schedule_hours = [9, 17] - timeout_seconds = 180 } resource "dbtcloud_job" "test_job_4" { diff --git a/pkg/framework/objects/job/resource_acceptance_timeout_test.go b/pkg/framework/objects/job/resource_acceptance_timeout_test.go index 8c24bf93..b36156e5 100644 --- a/pkg/framework/objects/job/resource_acceptance_timeout_test.go +++ b/pkg/framework/objects/job/resource_acceptance_timeout_test.go @@ -11,63 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) -// TestAccDbtCloudJobResourceTimeoutSecondsBackwardCompatibility tests that the -// deprecated top-level timeout_seconds attribute still works for backward compatibility -func TestAccDbtCloudJobResourceTimeoutSecondsBackwardCompatibility(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.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest_helper.TestAccPreCheck(t) }, - ProtoV6ProviderFactories: acctest_helper.TestAccProtoV6ProviderFactories, - CheckDestroy: testAccCheckDbtCloudJobDestroy, - Steps: []resource.TestStep{ - { - // Create job with deprecated timeout_seconds - Config: testAccDbtCloudJobResourceTimeoutConfig( - jobName, - projectName, - environmentName, - "deprecated", // uses top-level timeout_seconds - 180, - ), - Check: resource.ComposeTestCheckFunc( - testAccCheckDbtCloudJobExists("dbtcloud_job.test_job"), - resource.TestCheckResourceAttr("dbtcloud_job.test_job", "name", jobName), - resource.TestCheckResourceAttr("dbtcloud_job.test_job", "timeout_seconds", "180"), - ), - }, - // Update the timeout value using deprecated attribute - { - Config: testAccDbtCloudJobResourceTimeoutConfig( - jobName, - projectName, - environmentName, - "deprecated", - 360, - ), - Check: resource.ComposeTestCheckFunc( - testAccCheckDbtCloudJobExists("dbtcloud_job.test_job"), - resource.TestCheckResourceAttr("dbtcloud_job.test_job", "timeout_seconds", "360"), - ), - }, - // Import and verify - { - ResourceName: "dbtcloud_job.test_job", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "triggers.%", - "triggers.custom_branch_only", - "validate_execute_steps", - }, - }, - }, - }) -} - -// TestAccDbtCloudJobResourceExecutionTimeoutSeconds tests that the new +// TestAccDbtCloudJobResourceExecutionTimeoutSeconds tests that the // execution.timeout_seconds attribute works correctly func TestAccDbtCloudJobResourceExecutionTimeoutSeconds(t *testing.T) { jobName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) @@ -80,36 +24,28 @@ func TestAccDbtCloudJobResourceExecutionTimeoutSeconds(t *testing.T) { CheckDestroy: testAccCheckDbtCloudJobDestroy, Steps: []resource.TestStep{ { - // Create job with new execution.timeout_seconds Config: testAccDbtCloudJobResourceTimeoutConfig( jobName, projectName, environmentName, - "execution", // uses execution block 240, ), Check: resource.ComposeTestCheckFunc( testAccCheckDbtCloudJobExists("dbtcloud_job.test_job"), resource.TestCheckResourceAttr("dbtcloud_job.test_job", "name", jobName), resource.TestCheckResourceAttr("dbtcloud_job.test_job", "execution.timeout_seconds", "240"), - // When using execution block, deprecated timeout_seconds keeps its default (0) - resource.TestCheckResourceAttr("dbtcloud_job.test_job", "timeout_seconds", "0"), ), }, - // Update the timeout value using execution block { Config: testAccDbtCloudJobResourceTimeoutConfig( jobName, projectName, environmentName, - "execution", 480, ), Check: resource.ComposeTestCheckFunc( testAccCheckDbtCloudJobExists("dbtcloud_job.test_job"), resource.TestCheckResourceAttr("dbtcloud_job.test_job", "execution.timeout_seconds", "480"), - // When using execution block, deprecated timeout_seconds keeps its default (0) - resource.TestCheckResourceAttr("dbtcloud_job.test_job", "timeout_seconds", "0"), ), }, // Import and verify @@ -121,97 +57,15 @@ func TestAccDbtCloudJobResourceExecutionTimeoutSeconds(t *testing.T) { "triggers.%", "triggers.custom_branch_only", "validate_execute_steps", - "execution", // execution block is only set if user configured it - "timeout_seconds", // after import, this gets API value since we don't know user's config preference + "execution", // execution block is only set if user configured it }, }, }, }) } -// TestAccDbtCloudJobResourceTimeoutSecondsPrecedence tests that when both -// deprecated timeout_seconds and execution.timeout_seconds are set, -// execution.timeout_seconds takes precedence -func TestAccDbtCloudJobResourceTimeoutSecondsPrecedence(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.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest_helper.TestAccPreCheck(t) }, - ProtoV6ProviderFactories: acctest_helper.TestAccProtoV6ProviderFactories, - CheckDestroy: testAccCheckDbtCloudJobDestroy, - Steps: []resource.TestStep{ - { - // Create job with both timeout settings - execution should take precedence - Config: testAccDbtCloudJobResourceTimeoutBothConfig( - jobName, - projectName, - environmentName, - 100, // deprecated timeout_seconds (ignored when execution is set) - 300, // execution.timeout_seconds (should win) - ), - Check: resource.ComposeTestCheckFunc( - testAccCheckDbtCloudJobExists("dbtcloud_job.test_job"), - resource.TestCheckResourceAttr("dbtcloud_job.test_job", "name", jobName), - // execution.timeout_seconds takes precedence and is used for the API call - resource.TestCheckResourceAttr("dbtcloud_job.test_job", "execution.timeout_seconds", "300"), - // When execution block is used, timeout_seconds keeps its configured value - resource.TestCheckResourceAttr("dbtcloud_job.test_job", "timeout_seconds", "100"), - ), - }, - }, - }) -} - -// TestAccDbtCloudJobResourceTimeoutSecondsMigration tests migrating from -// deprecated timeout_seconds to the new execution.timeout_seconds -func TestAccDbtCloudJobResourceTimeoutSecondsMigration(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.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest_helper.TestAccPreCheck(t) }, - ProtoV6ProviderFactories: acctest_helper.TestAccProtoV6ProviderFactories, - CheckDestroy: testAccCheckDbtCloudJobDestroy, - Steps: []resource.TestStep{ - { - // Start with deprecated timeout_seconds - Config: testAccDbtCloudJobResourceTimeoutConfig( - jobName, - projectName, - environmentName, - "deprecated", - 180, - ), - Check: resource.ComposeTestCheckFunc( - testAccCheckDbtCloudJobExists("dbtcloud_job.test_job"), - resource.TestCheckResourceAttr("dbtcloud_job.test_job", "timeout_seconds", "180"), - ), - }, - // Migrate to execution.timeout_seconds - { - Config: testAccDbtCloudJobResourceTimeoutConfig( - jobName, - projectName, - environmentName, - "execution", - 180, - ), - Check: resource.ComposeTestCheckFunc( - testAccCheckDbtCloudJobExists("dbtcloud_job.test_job"), - resource.TestCheckResourceAttr("dbtcloud_job.test_job", "execution.timeout_seconds", "180"), - // After migration, deprecated timeout_seconds goes back to default (0) - resource.TestCheckResourceAttr("dbtcloud_job.test_job", "timeout_seconds", "0"), - ), - }, - }, - }) -} - -// TestAccDbtCloudJobResourceTimeoutSecondsDefault tests that when neither -// timeout is specified, it defaults to 0 +// TestAccDbtCloudJobResourceTimeoutSecondsDefault tests that when no timeout is +// specified, it defaults to 0 func TestAccDbtCloudJobResourceTimeoutSecondsDefault(t *testing.T) { jobName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) projectName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) @@ -224,18 +78,14 @@ func TestAccDbtCloudJobResourceTimeoutSecondsDefault(t *testing.T) { Steps: []resource.TestStep{ { // Create job without any timeout specified - Config: testAccDbtCloudJobResourceTimeoutConfig( + Config: testAccDbtCloudJobResourceNoTimeoutConfig( jobName, projectName, environmentName, - "none", - 0, // ignored when mode is "none" ), Check: resource.ComposeTestCheckFunc( testAccCheckDbtCloudJobExists("dbtcloud_job.test_job"), resource.TestCheckResourceAttr("dbtcloud_job.test_job", "name", jobName), - // Default should be 0 (no timeout) - resource.TestCheckResourceAttr("dbtcloud_job.test_job", "timeout_seconds", "0"), ), }, }, @@ -243,28 +93,9 @@ func TestAccDbtCloudJobResourceTimeoutSecondsDefault(t *testing.T) { } func testAccDbtCloudJobResourceTimeoutConfig( - jobName, projectName, environmentName, mode string, + jobName, projectName, environmentName string, timeoutSeconds int, ) string { - var timeoutConfig string - - switch mode { - case "deprecated": - // Use deprecated top-level timeout_seconds - timeoutConfig = fmt.Sprintf("timeout_seconds = %d", timeoutSeconds) - case "execution": - // Use new execution attribute (SingleNestedAttribute uses = syntax, not block syntax) - timeoutConfig = fmt.Sprintf(` - execution = { - timeout_seconds = %d - }`, timeoutSeconds) - case "none": - // No timeout specified - use defaults - timeoutConfig = "" - default: - panic("Invalid mode: " + mode) - } - return fmt.Sprintf(` resource "dbtcloud_project" "test_job_project" { name = "%s" @@ -289,14 +120,15 @@ resource "dbtcloud_job" "test_job" { "git_provider_webhook": false, "schedule": false, } - %s + execution = { + timeout_seconds = %d + } } -`, projectName, environmentName, acctest_config.DBT_CLOUD_VERSION, jobName, timeoutConfig) +`, projectName, environmentName, acctest_config.DBT_CLOUD_VERSION, jobName, timeoutSeconds) } -func testAccDbtCloudJobResourceTimeoutBothConfig( +func testAccDbtCloudJobResourceNoTimeoutConfig( jobName, projectName, environmentName string, - deprecatedTimeout, executionTimeout int, ) string { return fmt.Sprintf(` resource "dbtcloud_project" "test_job_project" { @@ -322,10 +154,6 @@ resource "dbtcloud_job" "test_job" { "git_provider_webhook": false, "schedule": false, } - timeout_seconds = %d - execution = { - timeout_seconds = %d - } } -`, projectName, environmentName, acctest_config.DBT_CLOUD_VERSION, jobName, deprecatedTimeout, executionTimeout) +`, projectName, environmentName, acctest_config.DBT_CLOUD_VERSION, jobName) } diff --git a/pkg/framework/objects/job/schema.go b/pkg/framework/objects/job/schema.go index c4f7f2d1..9247d38f 100644 --- a/pkg/framework/objects/job/schema.go +++ b/pkg/framework/objects/job/schema.go @@ -34,11 +34,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", @@ -209,15 +204,9 @@ func (j *jobDataSource) Schema( Description: "The ID of the job", } - jobAttributes["deferring_job_id"] = schema.Int64Attribute{ - Computed: true, - DeprecationMessage: "Deferral is now set at the environment level", - Description: "[Deprectated - Deferral is now set at the environment level] The ID of the job definition this job defers to", - } - jobAttributes["self_deferring"] = schema.BoolAttribute{ Computed: true, - Description: "Whether this job defers on a previous run of itself (overrides value in deferring_job_id)", + Description: "Whether this job defers on a previous run of itself", } jobAttributes["job_completion_trigger_condition"] = schema.ListNestedAttribute{ @@ -260,11 +249,6 @@ func (d *jobsDataSource) Schema( Computed: true, Description: "The ID of the job", } - jobAttributes["deferring_job_definition_id"] = schema.Int64Attribute{ - Computed: true, - DeprecationMessage: "Deferral is now set at the environment level", - Description: "[Deprectated - Deferral is now set at the environment level] The ID of the job definition this job defers to", - } jobAttributes["job_completion_trigger_condition"] = schema.SingleNestedAttribute{ Computed: true, Optional: true, @@ -346,13 +330,6 @@ func (j *jobResource) Schema( }, }, }, - "timeout_seconds": resource_schema.Int64Attribute{ - Optional: true, - Computed: true, - Default: int64default.StaticInt64(0), - DeprecationMessage: "Use execution.timeout_seconds instead", - Description: "Number of seconds to allow the job to run before timing out. Use execution.timeout_seconds instead.", - }, "generate_docs": resource_schema.BoolAttribute{ Optional: true, Computed: true, diff --git a/pkg/framework/objects/project_artefacts/model.go b/pkg/framework/objects/project_artefacts/model.go deleted file mode 100644 index c950ee39..00000000 --- a/pkg/framework/objects/project_artefacts/model.go +++ /dev/null @@ -1,12 +0,0 @@ -package project_artefacts - -import ( - "github.com/hashicorp/terraform-plugin-framework/types" -) - -type ProjectArtefactsResourceModel struct { - ID types.String `tfsdk:"id"` - ProjectID types.Int64 `tfsdk:"project_id"` - DocsJobID types.Int64 `tfsdk:"docs_job_id"` - FreshnessJobID types.Int64 `tfsdk:"freshness_job_id"` -} diff --git a/pkg/framework/objects/project_artefacts/resource.go b/pkg/framework/objects/project_artefacts/resource.go deleted file mode 100644 index 164ebd5d..00000000 --- a/pkg/framework/objects/project_artefacts/resource.go +++ /dev/null @@ -1,232 +0,0 @@ -package project_artefacts - -import ( - "context" - "strconv" - - "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/dbt_cloud" - "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/helper" - "github.com/hashicorp/terraform-plugin-framework/path" - "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/hashicorp/terraform-plugin-framework/types" -) - -var ( - _ resource.Resource = &projectArtefactsResource{} - _ resource.ResourceWithConfigure = &projectArtefactsResource{} - _ resource.ResourceWithImportState = &projectArtefactsResource{} -) - -type projectArtefactsResource struct { - client *dbt_cloud.Client -} - -// ImportState implements resource.ResourceWithImportState. -func (p *projectArtefactsResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - id_as_int, err := strconv.Atoi(req.ID) - if err != nil { - resp.Diagnostics.AddError("Invalid ID", "The ID must be an integer") - return - } - resp.State.SetAttribute(ctx, path.Root("id"), req.ID) - resp.State.SetAttribute(ctx, path.Root("project_id"), id_as_int) - -} - -// Configure implements resource.ResourceWithConfigure. -func (p *projectArtefactsResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { - switch c := req.ProviderData.(type) { - case nil: // do nothing - case *dbt_cloud.Client: - p.client = c - default: - resp.Diagnostics.AddError("Missing client", "A client is required to configure the project artefacts resource") - } -} - -// Create implements resource.Resource. -func (p *projectArtefactsResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var plan ProjectArtefactsResourceModel - - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if resp.Diagnostics.HasError() { - return - } - - projectIDString := strconv.FormatInt(plan.ProjectID.ValueInt64(), 10) - - project, err := p.client.GetProject(projectIDString) - if err != nil { - resp.Diagnostics.AddError( - "Unable to get project", - "Error: "+err.Error(), - ) - - return - } - - if plan.DocsJobID.ValueInt64() != 0 { - conv := int(plan.DocsJobID.ValueInt64()) - project.DocsJobId = &conv - } else { - project.DocsJobId = nil - } - - if plan.FreshnessJobID.ValueInt64() != 0 { - conv := int(plan.FreshnessJobID.ValueInt64()) - project.FreshnessJobId = &conv - } else { - project.FreshnessJobId = nil - } - - if _, err := p.client.UpdateProject(projectIDString, *project); err != nil { - resp.Diagnostics.AddError( - "Unable to update project", - "Error: "+err.Error(), - ) - - return - } - - plan.ID = types.StringValue(strconv.Itoa(*project.ID)) - - resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) -} - -// Delete implements resource.Resource. -func (p *projectArtefactsResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - var state ProjectArtefactsResourceModel - - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - - projectIDString := strconv.FormatInt(state.ProjectID.ValueInt64(), 10) - - project, err := p.client.GetProject(projectIDString) - if err != nil { - resp.Diagnostics.AddError( - "Unable to get project", - "Error: "+err.Error(), - ) - - return - } - - project.FreshnessJobId = nil - project.DocsJobId = nil - - _, err = p.client.UpdateProject(projectIDString, *project) - if err != nil { - resp.Diagnostics.AddError( - "Unable to update project", - "Error: "+err.Error(), - ) - - return - } -} - -// Metadata implements resource.Resource. -func (p *projectArtefactsResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_project_artefacts" -} - -// Read implements resource.Resource. -func (p *projectArtefactsResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - var state ProjectArtefactsResourceModel - - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - - projectIDString := strconv.FormatInt(state.ProjectID.ValueInt64(), 10) - - project, err := p.client.GetProject(projectIDString) - if err != nil { - if helper.HandleResourceNotFound(ctx, err, &resp.Diagnostics, &resp.State, "project artefacts") { - return - } - resp.Diagnostics.AddError( - "Unable to get project", - "Error: "+err.Error(), - ) - - return - } - - state.ID = types.StringValue(strconv.Itoa(*project.ID)) - if project.DocsJobId != nil { - state.DocsJobID = types.Int64PointerValue(helper.IntPointerToInt64Pointer(project.DocsJobId)) - } - - if project.FreshnessJobId != nil { - state.FreshnessJobID = types.Int64PointerValue(helper.IntPointerToInt64Pointer(project.FreshnessJobId)) - } - - resp.Diagnostics.Append(resp.State.Set(ctx, state)...) - -} - -// Update implements resource.Resource. -func (p *projectArtefactsResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - var plan, state ProjectArtefactsResourceModel - - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - - projectIDString := strconv.FormatInt(plan.ProjectID.ValueInt64(), 10) - - project, err := p.client.GetProject(projectIDString) - if err != nil { - resp.Diagnostics.AddError( - "Unable to get project", - "Error: "+err.Error(), - ) - - return - } - - if !state.DocsJobID.Equal(plan.DocsJobID) { - if plan.DocsJobID.ValueInt64() != 0 { - conv := int(plan.DocsJobID.ValueInt64()) - project.DocsJobId = &conv - } else { - project.DocsJobId = nil - } - } - - if !state.FreshnessJobID.Equal(plan.FreshnessJobID) { - if plan.FreshnessJobID.ValueInt64() != 0 { - conv := int(plan.FreshnessJobID.ValueInt64()) - project.FreshnessJobId = &conv - } else { - project.FreshnessJobId = nil - } - } - - project, err = p.client.UpdateProject(projectIDString, *project) - - if err != nil { - resp.Diagnostics.AddError( - "Unable to update project", - "Error: "+err.Error(), - ) - - return - } - - plan.DocsJobID = types.Int64PointerValue(helper.IntPointerToInt64Pointer(project.DocsJobId)) - plan.FreshnessJobID = types.Int64PointerValue(helper.IntPointerToInt64Pointer(project.FreshnessJobId)) - - resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) -} - -func ProjectArtefactsResource() resource.Resource { - return &projectArtefactsResource{} -} diff --git a/pkg/framework/objects/project_artefacts/resource_acceptance_test.go b/pkg/framework/objects/project_artefacts/resource_acceptance_test.go deleted file mode 100644 index 81ff2b35..00000000 --- a/pkg/framework/objects/project_artefacts/resource_acceptance_test.go +++ /dev/null @@ -1,188 +0,0 @@ -package project_artefacts_test - -import ( - "fmt" - "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/acctest_config" - "regexp" - "strings" - "testing" - - "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/acctest_helper" - "github.com/hashicorp/terraform-plugin-testing/helper/acctest" - "github.com/hashicorp/terraform-plugin-testing/helper/resource" - "github.com/hashicorp/terraform-plugin-testing/terraform" -) - -func TestAccDbtCloudProjectArtefactsResource(t *testing.T) { - - projectName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) - jobName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) - environmentName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest_helper.TestAccPreCheck(t) }, - ProtoV6ProviderFactories: acctest_helper.TestAccProtoV6ProviderFactories, - CheckDestroy: testAccCheckDbtCloudProjectArtefactsDestroy, - Steps: []resource.TestStep{ - { - Config: testAccDbtCloudProjectArtefactsResourceBasicConfig( - projectName, - environmentName, - jobName, - ), - Check: resource.ComposeTestCheckFunc( - testAccCheckDbtCloudProjectArtefactsExists( - "dbtcloud_project_artefacts.test_project_artefacts", - ), - ), - }, - // IMPORT - { - ResourceName: "dbtcloud_project_artefacts.test_project_artefacts", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{}, - }, - // EMPTY - { - Config: testAccDbtCloudProjectArtefactsResourceEmptyConfig(projectName), - Check: resource.ComposeTestCheckFunc( - testAccCheckDbtCloudProjectArtefactsEmpty("dbtcloud_project.test_project"), - ), - }, - }, - }) -} - -func testAccDbtCloudProjectArtefactsResourceBasicConfig( - projectName, environmentName, jobName string, -) string { - return fmt.Sprintf(` -resource "dbtcloud_project" "test_artefacts_project" { - name = "%s" -} - -resource "dbtcloud_environment" "test_job_environment" { - project_id = dbtcloud_project.test_artefacts_project.id - name = "%s" - dbt_version = "%s" - type = "development" -} - -resource "dbtcloud_job" "test_job" { - name = "%s" - project_id = dbtcloud_project.test_artefacts_project.id - environment_id = dbtcloud_environment.test_job_environment.environment_id - execute_steps = [ - "dbt test" - ] - triggers = { - "github_webhook": false, - "git_provider_webhook": false, - "schedule": false, - } - run_generate_sources = true - generate_docs = true -} - -resource "dbtcloud_project_artefacts" "test_project_artefacts" { - project_id = dbtcloud_project.test_artefacts_project.id - docs_job_id = dbtcloud_job.test_job.id - freshness_job_id = dbtcloud_job.test_job.id -} -`, projectName, environmentName, acctest_config.AcceptanceTestConfig.DbtCloudVersion, jobName) -} - -func testAccDbtCloudProjectArtefactsResourceEmptyConfig(projectName string) string { - return fmt.Sprintf(` -resource "dbtcloud_project" "test_project" { - name = "%s" -} - -resource "dbtcloud_project_artefacts" "test_project_artefacts" { - project_id = dbtcloud_project.test_project.id - docs_job_id = 0 - freshness_job_id = 0 - } -`, projectName) -} - -func testAccCheckDbtCloudProjectArtefactsExists(resource string) resource.TestCheckFunc { - return func(state *terraform.State) error { - rs, ok := state.RootModule().Resources[resource] - if !ok { - return fmt.Errorf("Not found: %s", resource) - } - if rs.Primary.ID == "" { - return fmt.Errorf("No Record ID is set") - } - apiClient, err := acctest_helper.SharedClient() - if err != nil { - return fmt.Errorf("Issue getting the client") - } - projectId := rs.Primary.ID - project, err := apiClient.GetProject(projectId) - if err != nil { - return fmt.Errorf("Can't get project") - } - if project.DocsJobId == nil { - return fmt.Errorf("error fetching item with resource %s. %s", resource, err) - } - if project.FreshnessJobId == nil { - return fmt.Errorf("error fetching item with resource %s. %s", resource, err) - } - return nil - } -} - -func testAccCheckDbtCloudProjectArtefactsEmpty(resource string) resource.TestCheckFunc { - return func(state *terraform.State) error { - rs, ok := state.RootModule().Resources[resource] - if !ok { - return fmt.Errorf("Not found: %s", resource) - } - if rs.Primary.ID == "" { - return fmt.Errorf("No Record ID is set") - } - apiClient, err := acctest_helper.SharedClient() - if err != nil { - return fmt.Errorf("Issue getting the client") - } - project, err := apiClient.GetProject(rs.Primary.ID) - if err != nil { - return fmt.Errorf("Can't get project") - } - if project.DocsJobId != nil { - return fmt.Errorf("error fetching item with resource %s. %s", resource, err) - } - if project.FreshnessJobId != nil { - return fmt.Errorf("error fetching item with resource %s. %s", resource, err) - } - return nil - } -} - -func testAccCheckDbtCloudProjectArtefactsDestroy(s *terraform.State) error { - apiClient, err := acctest_helper.SharedClient() - if err != nil { - return fmt.Errorf("Issue getting the client") - } - - for _, rs := range s.RootModule().Resources { - if rs.Type != "dbtcloud_project_artefacts" { - continue - } - projectId := rs.Primary.ID - project, err := apiClient.GetProject(projectId) - if project != nil { - return fmt.Errorf("Project still exists") - } - notFoundErr := "resource-not-found" - expectedErr := regexp.MustCompile(notFoundErr) - if !expectedErr.Match([]byte(err.Error())) { - return fmt.Errorf("expected %s, got %s", notFoundErr, err) - } - } - - return nil -} diff --git a/pkg/framework/objects/project_artefacts/schema.go b/pkg/framework/objects/project_artefacts/schema.go deleted file mode 100644 index 8f6cf631..00000000 --- a/pkg/framework/objects/project_artefacts/schema.go +++ /dev/null @@ -1,47 +0,0 @@ -package project_artefacts - -import ( - "context" - - "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" -) - -// Schema implements resource.Resource. -func (p *projectArtefactsResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Description: "[Deprecated] Resource for mentioning what jobs are the source of truth for the legacy dbt Docs and dbt Source Freshness pages. dbt Explorer doesn't require this config anymore.", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - Description: "The ID of the project artefacts resource.", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "project_id": schema.Int64Attribute{ - Description: "Project ID", - Required: true, - PlanModifiers: []planmodifier.Int64{ - int64planmodifier.RequiresReplace(), - }, - }, - "docs_job_id": schema.Int64Attribute{ - Description: "Docs Job ID", - Optional: true, - Computed: true, - Default: int64default.StaticInt64(0), - }, - "freshness_job_id": schema.Int64Attribute{ - Description: "Freshness Job ID", - Optional: true, - Computed: true, - Default: int64default.StaticInt64(0), - }, - }, - } -} diff --git a/pkg/framework/objects/repository/model.go b/pkg/framework/objects/repository/model.go index c636c0e6..09cbd29d 100644 --- a/pkg/framework/objects/repository/model.go +++ b/pkg/framework/objects/repository/model.go @@ -20,7 +20,6 @@ type RepositoryDataSourceModel struct { AzureActiveDirectoryProjectID types.String `tfsdk:"azure_active_directory_project_id"` AzureActiveDirectoryRepositoryID types.String `tfsdk:"azure_active_directory_repository_id"` AzureBypassWebhookRegistrationFailure types.Bool `tfsdk:"azure_bypass_webhook_registration_failure"` - FetchDeployKey types.Bool `tfsdk:"fetch_deploy_key"` } type RepositoryResourceModel struct { @@ -39,5 +38,4 @@ type RepositoryResourceModel struct { AzureActiveDirectoryProjectID types.String `tfsdk:"azure_active_directory_project_id"` AzureActiveDirectoryRepositoryID types.String `tfsdk:"azure_active_directory_repository_id"` AzureBypassWebhookRegistrationFailure types.Bool `tfsdk:"azure_bypass_webhook_registration_failure"` - FetchDeployKey types.Bool `tfsdk:"fetch_deploy_key"` } diff --git a/pkg/framework/objects/repository/resource.go b/pkg/framework/objects/repository/resource.go index f8a05cda..a1c94a21 100644 --- a/pkg/framework/objects/repository/resource.go +++ b/pkg/framework/objects/repository/resource.go @@ -204,12 +204,6 @@ func (r *repositoryResource) Create( plan.AzureBypassWebhookRegistrationFailure = types.BoolValue(azureBypassWebhookRegistrationFailure) } - // Handle the deprecated FetchDeployKey field - just maintain the planned value - // This field doesn't affect API behavior but needs to be consistent for Terraform - if plan.FetchDeployKey.IsNull() { - plan.FetchDeployKey = types.BoolValue(false) - } - resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) } @@ -307,12 +301,6 @@ func (r *repositoryResource) Read( state.AzureBypassWebhookRegistrationFailure = types.BoolValue(false) } - // Handle the deprecated FetchDeployKey field - maintain existing value or default to false - // This field doesn't affect API behavior but needs to be consistent for Terraform - if state.FetchDeployKey.IsNull() { - state.FetchDeployKey = types.BoolValue(false) - } - resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) } @@ -431,10 +419,6 @@ func (r *repositoryResource) Update( state.AzureBypassWebhookRegistrationFailure = types.BoolValue(false) } - // Handle the deprecated FetchDeployKey field - maintain the planned value - // This field doesn't affect API behavior but needs to be consistent for Terraform - state.FetchDeployKey = plan.FetchDeployKey - resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) } diff --git a/pkg/framework/objects/repository/resource_acceptance_test.go b/pkg/framework/objects/repository/resource_acceptance_test.go index db0e8a7a..b841a339 100644 --- a/pkg/framework/objects/repository/resource_acceptance_test.go +++ b/pkg/framework/objects/repository/resource_acceptance_test.go @@ -72,9 +72,8 @@ func TestAccDbtCloudRepositoryResourceDeploy(t *testing.T) { var importDeployTestStep = resource.TestStep{ // IMPORT ResourceName: "dbtcloud_repository.test_repository_github_app", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"fetch_deploy_key"}, + ImportState: true, + ImportStateVerify: true, } resource.ParallelTest(t, resource.TestCase{ @@ -93,9 +92,8 @@ func TestAccDbtCloudRepositoryResourceClone(t *testing.T) { var importCloneTestStep = resource.TestStep{ // IMPORT ResourceName: "dbtcloud_repository.test_repository_github", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"fetch_deploy_key"}, + ImportState: true, + ImportStateVerify: true, } resource.ParallelTest(t, resource.TestCase{ diff --git a/pkg/framework/objects/repository/schema.go b/pkg/framework/objects/repository/schema.go index f07179a4..5cef87d9 100644 --- a/pkg/framework/objects/repository/schema.go +++ b/pkg/framework/objects/repository/schema.go @@ -107,13 +107,6 @@ func ResourceSchema() resource_schema.Schema { boolplanmodifier.RequiresReplace(), }, }, - "fetch_deploy_key": resource_schema.BoolAttribute{ - Optional: true, - Computed: true, - Default: booldefault.StaticBool(false), - Description: "Whether we should return the public deploy key - (for the `deploy_key` strategy)", - DeprecationMessage: "This field is deprecated and will be removed in a future version of the provider, please remove it from your configuration. The key is always fetched when the clone strategy is `deploy_key`", - }, "deploy_key": resource_schema.StringAttribute{ Computed: true, Description: "Public key generated by dbt when using `deploy_key` clone strategy", @@ -143,12 +136,6 @@ func DataSourceSchema() datasource_schema.Schema { Required: true, Description: "Project ID to create the repository in", }, - "fetch_deploy_key": datasource_schema.BoolAttribute{ - Optional: true, - Computed: true, - Description: "Whether we should return the public deploy key", - DeprecationMessage: "This field is deprecated and will be removed in a future version of the provider. The key is always fetched when the clone strategy is `deploy_key`", - }, "is_active": datasource_schema.BoolAttribute{ Computed: true, Description: "Whether the repository is active", diff --git a/pkg/framework/objects/spark_credential/data_source.go b/pkg/framework/objects/spark_credential/data_source.go index 66c95fba..55d76f65 100644 --- a/pkg/framework/objects/spark_credential/data_source.go +++ b/pkg/framework/objects/spark_credential/data_source.go @@ -66,7 +66,6 @@ func (d *sparkCredentialDataSource) Read(ctx context.Context, req datasource.Rea state.ID = types.StringValue(fmt.Sprintf("%d%s%d", credential.Project_Id, dbt_cloud.ID_DELIMITER, *credential.ID)) state.NumThreads = types.Int64Value(int64(credential.Threads)) state.ProjectID = types.Int64Value(int64(credential.Project_Id)) - state.TargetName = types.StringValue(credential.Target_Name) state.Schema = types.StringValue(credential.UnencryptedCredentialDetails.Schema) diags = resp.State.Set(ctx, &state) diff --git a/pkg/framework/objects/spark_credential/model.go b/pkg/framework/objects/spark_credential/model.go index 377249e7..57d14a0b 100644 --- a/pkg/framework/objects/spark_credential/model.go +++ b/pkg/framework/objects/spark_credential/model.go @@ -8,7 +8,6 @@ type SparkCredentialDataSourceModel struct { ID types.String `tfsdk:"id"` CredentialID types.Int64 `tfsdk:"credential_id"` ProjectID types.Int64 `tfsdk:"project_id"` - TargetName types.String `tfsdk:"target_name"` NumThreads types.Int64 `tfsdk:"num_threads"` Schema types.String `tfsdk:"schema"` } @@ -17,7 +16,6 @@ type SparkCredentialResourceModel struct { ID types.String `tfsdk:"id"` CredentialID types.Int64 `tfsdk:"credential_id"` ProjectID types.Int64 `tfsdk:"project_id"` - TargetName types.String `tfsdk:"target_name"` Token types.String `tfsdk:"token"` TokenWo types.String `tfsdk:"token_wo"` TokenWoVersion types.Int64 `tfsdk:"token_wo_version"` diff --git a/pkg/framework/objects/spark_credential/resource.go b/pkg/framework/objects/spark_credential/resource.go index ae09d486..95eebba3 100644 --- a/pkg/framework/objects/spark_credential/resource.go +++ b/pkg/framework/objects/spark_credential/resource.go @@ -82,13 +82,6 @@ func (d *sparkCredentialResource) ImportState(ctx context.Context, req resource. credentialID, )...) - // Set target_name - resp.Diagnostics.Append(resp.State.SetAttribute( - ctx, - path.Root("target_name"), - credentialResponse.Target_Name, - )...) - // Set schema from API response resp.Diagnostics.Append(resp.State.SetAttribute( ctx, @@ -144,13 +137,11 @@ func (d *sparkCredentialResource) createGlobal(ctx context.Context, plan *SparkC projectID := int(plan.ProjectID.ValueInt64()) token := helper.ResolveWriteOnlyString(config.TokenWo, plan.Token) schema := plan.Schema.ValueString() - targetName := plan.TargetName.ValueString() sparkCredential, err := d.client.CreateSparkCredential( projectID, token, schema, - targetName, ) if err != nil { resp.Diagnostics.AddError("Error creating Apache Spark credential", err.Error()) @@ -218,7 +209,6 @@ func (d *sparkCredentialResource) Read(ctx context.Context, req resource.ReadReq } state.Schema = types.StringValue(credential.UnencryptedCredentialDetails.Schema) - state.TargetName = types.StringValue(credential.UnencryptedCredentialDetails.TargetName) diags = resp.State.Set(ctx, &state) resp.Diagnostics.Append(diags...) @@ -271,13 +261,11 @@ func (d *sparkCredentialResource) updateGlobal(ctx context.Context, plan, state // Check if any relevant fields have changed if !plan.Token.Equal(state.Token) || !plan.TokenWoVersion.Equal(state.TokenWoVersion) || - !plan.TargetName.Equal(state.TargetName) || !plan.Schema.Equal(state.Schema) { patchCredentialsDetails, err := dbt_cloud.GenerateSparkCredentialDetails( token, plan.Schema.ValueString(), - plan.TargetName.ValueString(), ) if err != nil { resp.Diagnostics.AddError("Error generating credential details", err.Error()) @@ -295,10 +283,6 @@ func (d *sparkCredentialResource) updateGlobal(ctx context.Context, plan, state if plan.Schema.Equal(state.Schema) { delete(patchCredentialsDetails.Fields, key) } - case "target_name": - if plan.TargetName.Equal(state.TargetName) { - delete(patchCredentialsDetails.Fields, key) - } } } diff --git a/pkg/framework/objects/spark_credential/resource_acceptance_test.go b/pkg/framework/objects/spark_credential/resource_acceptance_test.go index 3beff84f..d0d0f23a 100644 --- a/pkg/framework/objects/spark_credential/resource_acceptance_test.go +++ b/pkg/framework/objects/spark_credential/resource_acceptance_test.go @@ -15,7 +15,6 @@ import ( func TestAccDbtCloudSparkCredentialResourceGlobConn(t *testing.T) { projectName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) - targetName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) token := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) token2 := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) @@ -27,25 +26,18 @@ func TestAccDbtCloudSparkCredentialResourceGlobConn(t *testing.T) { { Config: testAccDbtCloudSparkCredentialResourceBasicConfigGlobConn( projectName, - targetName, token, ), Check: resource.ComposeTestCheckFunc( testAccCheckDbtCloudSparkCredentialExists( "dbtcloud_spark_credential.test_spark_credential", ), - resource.TestCheckResourceAttr( - "dbtcloud_spark_credential.test_spark_credential", - "target_name", - targetName, - ), ), }, // MODIFY { Config: testAccDbtCloudSparkCredentialResourceBasicConfigGlobConn( projectName, - targetName, token2, ), Check: resource.ComposeTestCheckFunc( @@ -71,7 +63,7 @@ func TestAccDbtCloudSparkCredentialResourceGlobConn(t *testing.T) { } func testAccDbtCloudSparkCredentialResourceBasicConfigGlobConn( - projectName, targetName, token string, + projectName, token string, ) string { return fmt.Sprintf(` resource "dbtcloud_project" "test_project" { @@ -79,10 +71,9 @@ resource "dbtcloud_project" "test_project" { } resource "dbtcloud_spark_credential" "test_spark_credential" { - project_id = dbtcloud_project.test_project.id - target_name = "%s" - token = "%s" - schema = "my_schema" + project_id = dbtcloud_project.test_project.id + token = "%s" + schema = "my_schema" } resource "dbtcloud_global_connection" "apache_spark" { @@ -107,14 +98,13 @@ resource "dbtcloud_environment" "spark_environment" { } -`, projectName, targetName, token) +`, projectName, token) } func TestAccDbtCloudSparkCredentialResourceWriteOnly(t *testing.T) { t.Skip("Requires Terraform >= 1.11") projectName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) - targetName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) token := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) token2 := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) @@ -127,7 +117,6 @@ func TestAccDbtCloudSparkCredentialResourceWriteOnly(t *testing.T) { { Config: testAccDbtCloudSparkCredentialWriteOnlyConfig( projectName, - targetName, token, 1, ), @@ -135,11 +124,6 @@ func TestAccDbtCloudSparkCredentialResourceWriteOnly(t *testing.T) { testAccCheckDbtCloudSparkCredentialExists( "dbtcloud_spark_credential.test_spark_credential_wo", ), - resource.TestCheckResourceAttr( - "dbtcloud_spark_credential.test_spark_credential_wo", - "target_name", - targetName, - ), // token_wo should not be in state resource.TestCheckNoResourceAttr( "dbtcloud_spark_credential.test_spark_credential_wo", @@ -156,7 +140,6 @@ func TestAccDbtCloudSparkCredentialResourceWriteOnly(t *testing.T) { { Config: testAccDbtCloudSparkCredentialWriteOnlyConfig( projectName, - targetName, token2, 2, ), @@ -189,7 +172,7 @@ func TestAccDbtCloudSparkCredentialResourceWriteOnly(t *testing.T) { } func testAccDbtCloudSparkCredentialWriteOnlyConfig( - projectName, targetName, tokenWo string, tokenWoVersion int, + projectName, tokenWo string, tokenWoVersion int, ) string { return fmt.Sprintf(` resource "dbtcloud_project" "test_project" { @@ -198,7 +181,6 @@ resource "dbtcloud_project" "test_project" { resource "dbtcloud_spark_credential" "test_spark_credential_wo" { project_id = dbtcloud_project.test_project.id - target_name = "%s" token_wo = "%s" token_wo_version = %d schema = "my_schema" @@ -226,7 +208,7 @@ resource "dbtcloud_environment" "spark_environment" { } -`, projectName, targetName, tokenWo, tokenWoVersion) +`, projectName, tokenWo, tokenWoVersion) } func testAccCheckDbtCloudSparkCredentialExists(resource string) resource.TestCheckFunc { diff --git a/pkg/framework/objects/spark_credential/schema.go b/pkg/framework/objects/spark_credential/schema.go index c7cba985..da58e253 100644 --- a/pkg/framework/objects/spark_credential/schema.go +++ b/pkg/framework/objects/spark_credential/schema.go @@ -7,7 +7,6 @@ import ( resource_schema "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" ) @@ -27,10 +26,6 @@ var dataSourceSchema = datasource_schema.Schema{ Description: "Credential ID", Required: true, }, - "target_name": datasource_schema.StringAttribute{ - Description: "Target name", - Computed: true, - }, "num_threads": datasource_schema.Int64Attribute{ Description: "The number of threads to use", Computed: true, @@ -66,13 +61,6 @@ var SparkResourceSchema = resource_schema.Schema{ int64planmodifier.UseStateForUnknown(), }, }, - "target_name": resource_schema.StringAttribute{ - Description: "Target name", - Optional: true, - Computed: true, - Default: stringdefault.StaticString("default"), - DeprecationMessage: "This field is deprecated at the environment level (it was never possible to set it in the UI) and will be removed in a future release. Please remove it and set the target name at the job level or leverage environment variables.", - }, "token": resource_schema.StringAttribute{ Description: "Token for Apache Spark user. Consider using `token_wo` instead, which is not stored in state.", Optional: true, diff --git a/pkg/framework/objects/webhook/data_source.go b/pkg/framework/objects/webhook/data_source.go index 915b88bf..f9806e82 100644 --- a/pkg/framework/objects/webhook/data_source.go +++ b/pkg/framework/objects/webhook/data_source.go @@ -41,16 +41,15 @@ func (d *webhookDataSource) Read( resp.Diagnostics.Append(req.Config.Get(ctx, &state)...) - webhook, err := d.client.GetWebhook(state.WebhookID.ValueString()) + webhook, err := d.client.GetWebhook(state.ID.ValueString()) if err != nil { resp.Diagnostics.AddError( - fmt.Sprintf("Did not find webhook with id: %s", state.WebhookID.ValueString()), + fmt.Sprintf("Did not find webhook with id: %s", state.ID.ValueString()), err.Error(), ) return } - state.WebhookID = types.StringValue(webhook.WebhookId) state.Name = types.StringValue(webhook.Name) state.Description = types.StringValue(webhook.Description) state.ClientURL = types.StringValue(webhook.ClientUrl) diff --git a/pkg/framework/objects/webhook/data_source_acceptance_test.go b/pkg/framework/objects/webhook/data_source_acceptance_test.go index 2f4fff44..157e4a85 100644 --- a/pkg/framework/objects/webhook/data_source_acceptance_test.go +++ b/pkg/framework/objects/webhook/data_source_acceptance_test.go @@ -16,7 +16,7 @@ func TestDbtCloudWebhookDataSource(t *testing.T) { config := webhooks(randomWebhookName, randomWebhookDescription) check := resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet("data.dbtcloud_webhook.test", "webhook_id"), + resource.TestCheckResourceAttrSet("data.dbtcloud_webhook.test", "id"), resource.TestCheckResourceAttr("data.dbtcloud_webhook.test", "name", randomWebhookName), resource.TestCheckResourceAttr( "data.dbtcloud_webhook.test", @@ -55,7 +55,7 @@ func webhooks(webhookName string, webhookDesc string) string { } data "dbtcloud_webhook" "test" { - webhook_id = dbtcloud_webhook.test_webhook.id + id = dbtcloud_webhook.test_webhook.id } `, webhookName, webhookDesc) } diff --git a/pkg/framework/objects/webhook/model.go b/pkg/framework/objects/webhook/model.go index 4430f1c9..998eaa2c 100644 --- a/pkg/framework/objects/webhook/model.go +++ b/pkg/framework/objects/webhook/model.go @@ -6,7 +6,6 @@ import ( type WebhookDataSourceModel struct { ID types.String `tfsdk:"id"` - WebhookID types.String `tfsdk:"webhook_id"` Name types.String `tfsdk:"name"` Description types.String `tfsdk:"description"` ClientURL types.String `tfsdk:"client_url"` @@ -19,7 +18,6 @@ type WebhookDataSourceModel struct { type WebhookResourceModel struct { ID types.String `tfsdk:"id"` - WebhookID types.String `tfsdk:"webhook_id"` Name types.String `tfsdk:"name"` Description types.String `tfsdk:"description"` ClientURL types.String `tfsdk:"client_url"` diff --git a/pkg/framework/objects/webhook/resource.go b/pkg/framework/objects/webhook/resource.go index d1c06034..99539cff 100644 --- a/pkg/framework/objects/webhook/resource.go +++ b/pkg/framework/objects/webhook/resource.go @@ -43,9 +43,6 @@ func (r *webhookResource) Schema( } func readWebhookToWebhookResourceModel(ctx context.Context, retrievedWebhook *dbt_cloud.WebhookRead, resourceModel *WebhookResourceModel) diag.Diagnostics { - // these two are identical. WebhookID should not have been used in the - // first place, but we're keeping it here for backwards compatibility reasons - resourceModel.WebhookID = types.StringValue(retrievedWebhook.WebhookId) resourceModel.ID = types.StringValue(retrievedWebhook.WebhookId) resourceModel.Name = types.StringValue(retrievedWebhook.Name) @@ -161,7 +158,6 @@ func (r *webhookResource) Create( } plan.ID = types.StringValue(createdWebhook.WebhookId) - plan.WebhookID = types.StringValue(createdWebhook.WebhookId) plan.JobIDs, diags = helper.SliceStringToTypesListInt64Value([]string(createdWebhook.JobIds)) if diags.HasError() { @@ -217,7 +213,7 @@ func (r *webhookResource) Update( var activeChanged = !reflect.DeepEqual(plan.Active, state.Active) if nameChanged || descriptionChanged || clientUrlChanged || eventTypesChanged || jobIdsChanged || activeChanged { - var webhookId = state.WebhookID.ValueString() + var webhookId = state.ID.ValueString() retrievedWebhook, err := r.client.GetWebhook(webhookId) if err != nil { resp.Diagnostics.AddError( @@ -282,7 +278,7 @@ func (r *webhookResource) Delete( return } - _, err := r.client.DeleteWebhook(state.WebhookID.ValueString()) + _, err := r.client.DeleteWebhook(state.ID.ValueString()) if err != nil { resp.Diagnostics.AddError( "Error deleting webhook", diff --git a/pkg/framework/objects/webhook/schema.go b/pkg/framework/objects/webhook/schema.go index 1fcbd724..66583451 100644 --- a/pkg/framework/objects/webhook/schema.go +++ b/pkg/framework/objects/webhook/schema.go @@ -13,15 +13,10 @@ import ( var dataSourceSchema = datasource_schema.Schema{ Description: "Retrieve webhook details", Attributes: map[string]datasource_schema.Attribute{ - "id": resource_schema.StringAttribute{ - Computed: true, + "id": datasource_schema.StringAttribute{ + Required: true, Description: "Webhook's ID", }, - "webhook_id": datasource_schema.StringAttribute{ - Required: true, - Description: "Webhook's ID", - DeprecationMessage: "Use `id` instead", - }, "name": datasource_schema.StringAttribute{ Computed: true, Description: "Webhooks Name", @@ -68,14 +63,6 @@ var resourceSchema = resource_schema.Schema{ stringplanmodifier.UseStateForUnknown(), }, }, - "webhook_id": resource_schema.StringAttribute{ - Computed: true, - Description: "Webhook's ID", - DeprecationMessage: "Use `id` instead", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, "name": resource_schema.StringAttribute{ Description: "Webhooks Name", Required: true, diff --git a/pkg/provider/framework_provider.go b/pkg/provider/framework_provider.go index c231b831..f87cd099 100644 --- a/pkg/provider/framework_provider.go +++ b/pkg/provider/framework_provider.go @@ -50,7 +50,6 @@ import ( "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/objects/postgres_credential" "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/objects/profile" "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/objects/project" - "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/objects/project_artefacts" "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/objects/project_repository" "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/objects/redshift_credential" "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/objects/repository" @@ -369,7 +368,6 @@ func (p *dbtCloudProvider) Resources(_ context.Context) []func() resource.Resour platform_metadata_credentials.SnowflakePlatformMetadataCredentialResource, platform_metadata_credentials.DatabricksPlatformMetadataCredentialResource, profile.ProfileResource, - project_artefacts.ProjectArtefactsResource, repository.RepositoryResource, scim_config.SCIMConfigResource, scim_config_token.SCIMConfigTokenResource,