From 38543ed82cddee8f933995297d69a7aa3cc4bd99 Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Wed, 8 Jan 2025 19:30:35 -0800 Subject: [PATCH] feat(fabricitem)!: enable multi-format support --- .../unreleased/added-20241223-094847.yaml | 7 + .../unreleased/breaking-20241223-094847.yaml | 7 + docs/data-sources/eventhouse.md | 4 +- docs/data-sources/notebook.md | 5 +- docs/data-sources/report.md | 5 +- docs/data-sources/semantic_model.md | 5 +- docs/data-sources/spark_job_definition.md | 5 +- docs/resources/data_pipeline.md | 4 +- docs/resources/eventhouse.md | 4 +- docs/resources/notebook.md | 6 +- docs/resources/report.md | 6 +- docs/resources/semantic_model.md | 6 +- docs/resources/spark_job_definition.md | 6 +- .../fabric_notebook/data-source.tf | 1 + .../data-sources/fabric_report/data-source.tf | 1 + .../fabric_semantic_model/data-source.tf | 1 + .../data-source.tf | 1 + .../resources/fabric_notebook/resource.tf | 2 + examples/resources/fabric_report/resource.tf | 2 + .../fabric_semantic_model/resource.tf | 2 + .../fabric_spark_job_definition/resource.tf | 2 + .../regexp_if_attribute_is_one_of.go | 144 ++++++++++++++++++ internal/pkg/fabricitem/base.go | 4 +- .../pkg/fabricitem/data_item_definition.go | 19 ++- internal/pkg/fabricitem/data_schema.go | 42 ++--- internal/pkg/fabricitem/definition.go | 106 +++++++++++++ .../pkg/fabricitem/models_resource_item.go | 8 +- .../models_resource_item_definition.go | 22 ++- ...ource_item_config_definition_properties.go | 6 +- .../fabricitem/resource_item_definition.go | 9 +- .../resource_item_definition_properties.go | 6 +- internal/pkg/fabricitem/resource_schema.go | 35 +++-- internal/provider/utils/values.go | 12 +- internal/services/datapipeline/base.go | 9 +- .../datapipeline/resource_data_pipeline.go | 7 +- internal/services/eventhouse/base.go | 14 +- .../services/eventhouse/data_eventhouse.go | 3 +- .../eventhouse/resource_eventhouse.go | 6 +- internal/services/kqldatabase/base.go | 14 +- internal/services/notebook/base.go | 37 +++-- internal/services/notebook/data_notebook.go | 5 +- .../services/notebook/data_notebook_test.go | 52 +++++++ .../services/notebook/resource_notebook.go | 8 +- .../notebook/resource_notebook_test.go | 140 +++++++++++++++-- internal/services/report/base.go | 17 ++- internal/services/report/data_report.go | 3 +- internal/services/report/data_report_test.go | 1 + internal/services/report/resource_report.go | 24 ++- .../services/report/resource_report_test.go | 10 ++ internal/services/semanticmodel/base.go | 17 ++- .../semanticmodel/data_semantic_model.go | 3 +- .../semanticmodel/data_semantic_model_test.go | 1 + .../semanticmodel/resource_semantic_model.go | 24 ++- .../resource_semantic_model_test.go | 10 ++ internal/services/sparkjobdefinition/base.go | 12 +- .../data_spark_job_definition.go | 3 +- .../data_spark_job_definition_test.go | 1 + .../resource_spark_job_definition.go | 6 +- .../resource_spark_job_definition_test.go | 10 ++ .../workspace/resource_workspace_git.go | 2 +- .../fixtures/notebook/notebook.py.tmpl | 22 +++ 61 files changed, 776 insertions(+), 180 deletions(-) create mode 100644 .changes/unreleased/added-20241223-094847.yaml create mode 100644 .changes/unreleased/breaking-20241223-094847.yaml create mode 100644 internal/framework/validators/regexp_if_attribute_is_one_of.go create mode 100644 internal/pkg/fabricitem/definition.go create mode 100644 internal/testhelp/fixtures/notebook/notebook.py.tmpl diff --git a/.changes/unreleased/added-20241223-094847.yaml b/.changes/unreleased/added-20241223-094847.yaml new file mode 100644 index 00000000..3e7c6a5c --- /dev/null +++ b/.changes/unreleased/added-20241223-094847.yaml @@ -0,0 +1,7 @@ +kind: added +body: | + Added support for multi-format Notebook Resource/Data-Source. + By using `format` attribute, you can now define the format of the Notebook Resource/Data-Source. Accepted values are `jpynb`, and `py`. +time: 2024-12-23T09:48:47.1324573-08:00 +custom: + Issue: "168" diff --git a/.changes/unreleased/breaking-20241223-094847.yaml b/.changes/unreleased/breaking-20241223-094847.yaml new file mode 100644 index 00000000..050da5f0 --- /dev/null +++ b/.changes/unreleased/breaking-20241223-094847.yaml @@ -0,0 +1,7 @@ +kind: breaking +body: | + The `format` attribute is now REQUIRED for Resources/Data-Sources with definition support. + Currently applicable to the following Resources/Data-Sources: Report, Notebook, Semantic Model, and Spark Job Definition. +time: 2024-12-23T09:48:47.1324573-08:00 +custom: + Issue: "111" diff --git a/docs/data-sources/eventhouse.md b/docs/data-sources/eventhouse.md index 2e060250..1444594d 100644 --- a/docs/data-sources/eventhouse.md +++ b/docs/data-sources/eventhouse.md @@ -66,6 +66,7 @@ output "example_definition_content_object" { ### Optional - `display_name` (String) The Eventhouse display name. +- `format` (String) The Eventhouse format. Possible values: `Default` - `id` (String) The Eventhouse ID. - `output_definition` (Boolean) Output definition parts as gzip base64 content? Default: `false` @@ -75,9 +76,8 @@ output "example_definition_content_object" { ### Read-Only -- `definition` (Attributes Map) Definition parts. Possible path keys: `EventhouseProperties.json`. (see [below for nested schema](#nestedatt--definition)) +- `definition` (Attributes Map) Definition parts. Possible path keys: **Default** format: `EventhouseProperties.json` (see [below for nested schema](#nestedatt--definition)) - `description` (String) The Eventhouse description. -- `format` (String) The Eventhouse format. Possible values: `NotApplicable` - `properties` (Attributes) The Eventhouse properties. (see [below for nested schema](#nestedatt--properties)) diff --git a/docs/data-sources/notebook.md b/docs/data-sources/notebook.md index bcbc6358..a3b3552a 100644 --- a/docs/data-sources/notebook.md +++ b/docs/data-sources/notebook.md @@ -36,6 +36,7 @@ data "fabric_notebook" "example_by_id" { data "fabric_notebook" "example_definition" { id = "11111111-1111-1111-1111-111111111111" workspace_id = "00000000-0000-0000-0000-000000000000" + format = "ipynb" output_definition = true } @@ -68,6 +69,7 @@ output "example_definition_content_object" { ### Optional - `display_name` (String) The Notebook display name. +- `format` (String) The Notebook format. Possible values: `ipynb`, `py` - `id` (String) The Notebook ID. - `output_definition` (Boolean) Output definition parts as gzip base64 content? Default: `false` @@ -77,9 +79,8 @@ output "example_definition_content_object" { ### Read-Only -- `definition` (Attributes Map) Definition parts. Possible path keys: `notebook-content.ipynb`. (see [below for nested schema](#nestedatt--definition)) +- `definition` (Attributes Map) Definition parts. Possible path keys: **ipynb** format: `notebook-content.ipynb` **py** format: `notebook-content.py` (see [below for nested schema](#nestedatt--definition)) - `description` (String) The Notebook description. -- `format` (String) The Notebook format. Possible values: `ipynb`. diff --git a/docs/data-sources/report.md b/docs/data-sources/report.md index d1a463b1..20c36438 100644 --- a/docs/data-sources/report.md +++ b/docs/data-sources/report.md @@ -29,6 +29,7 @@ data "fabric_report" "example" { data "fabric_report" "example_definition" { id = "11111111-1111-1111-1111-111111111111" workspace_id = "00000000-0000-0000-0000-000000000000" + format = "PBIR-Legacy" output_definition = true } @@ -56,6 +57,7 @@ output "example_definition_report_object" { ### Optional +- `format` (String) The Report format. Possible values: `PBIR-Legacy`, `PBIR` - `output_definition` (Boolean) Output definition parts as gzip base64 content? Default: `false` !> Your terraform state file may grow a lot if you output definition content. Only use it when you must use data from the definition. @@ -64,10 +66,9 @@ output "example_definition_report_object" { ### Read-Only -- `definition` (Attributes Map) Definition parts. Possible path keys: `report.json`, `definition.pbir`, `StaticResources/RegisteredResources/*`, `StaticResources/SharedResources/*`. (see [below for nested schema](#nestedatt--definition)) +- `definition` (Attributes Map) Definition parts. Possible path keys: **PBIR-Legacy** format: `StaticResources/RegisteredResources/*`, `StaticResources/SharedResources/*`, `definition.pbir`, `report.json` **PBIR** format: `StaticResources/RegisteredResources/*`, `StaticResources/SharedResources/*`, `definition.pbir`, `definition/pages/*.json`, `definition/report.json`, `definition/version.json` (see [below for nested schema](#nestedatt--definition)) - `description` (String) The Report description. - `display_name` (String) The Report display name. -- `format` (String) The Report format. Possible values: `PBIR-Legacy`. diff --git a/docs/data-sources/semantic_model.md b/docs/data-sources/semantic_model.md index ddd44af7..7e725747 100644 --- a/docs/data-sources/semantic_model.md +++ b/docs/data-sources/semantic_model.md @@ -29,6 +29,7 @@ data "fabric_semantic_model" "example" { data "fabric_semantic_model" "example_definition" { id = "11111111-1111-1111-1111-111111111111" workspace_id = "00000000-0000-0000-0000-000000000000" + format = "TMSL" output_definition = true } @@ -53,6 +54,7 @@ output "example_definition_bim_object" { ### Optional +- `format` (String) The Semantic Model format. Possible values: `TMSL`, `TMDL` - `output_definition` (Boolean) Output definition parts as gzip base64 content? Default: `false` !> Your terraform state file may grow a lot if you output definition content. Only use it when you must use data from the definition. @@ -61,10 +63,9 @@ output "example_definition_bim_object" { ### Read-Only -- `definition` (Attributes Map) Definition parts. Possible path keys: `model.bim`, `definition.pbism`, `diagramLayout.json`. (see [below for nested schema](#nestedatt--definition)) +- `definition` (Attributes Map) Definition parts. Possible path keys: **TMSL** format: `definition.pbism`, `diagramLayp.json`, `model.bim` **TMDL** format: `definition.pbism`, `definition/database.tmdl`, `definition/model.tmdl`, `definition/tables/*.tmdl`, `diagramLayp.json` (see [below for nested schema](#nestedatt--definition)) - `description` (String) The Semantic Model description. - `display_name` (String) The Semantic Model display name. -- `format` (String) The Semantic Model format. Possible values: `TMSL`. diff --git a/docs/data-sources/spark_job_definition.md b/docs/data-sources/spark_job_definition.md index 935ea756..3d6cc75e 100644 --- a/docs/data-sources/spark_job_definition.md +++ b/docs/data-sources/spark_job_definition.md @@ -36,6 +36,7 @@ data "fabric_spark_job_definition" "example_by_name" { data "fabric_spark_job_definition" "example_definition" { id = "11111111-1111-1111-1111-111111111111" workspace_id = "00000000-0000-0000-0000-000000000000" + format = "SparkJobDefinitionV1" output_definition = true } @@ -68,6 +69,7 @@ output "example_definition_content_object" { ### Optional - `display_name` (String) The Spark Job Definition display name. +- `format` (String) The Spark Job Definition format. Possible values: `SparkJobDefinitionV1` - `id` (String) The Spark Job Definition ID. - `output_definition` (Boolean) Output definition parts as gzip base64 content? Default: `false` @@ -77,9 +79,8 @@ output "example_definition_content_object" { ### Read-Only -- `definition` (Attributes Map) Definition parts. Possible path keys: `SparkJobDefinitionV1.json`. (see [below for nested schema](#nestedatt--definition)) +- `definition` (Attributes Map) Definition parts. Possible path keys: **SparkJobDefinitionV1** format: `SparkJobDefinitionV1.json` (see [below for nested schema](#nestedatt--definition)) - `description` (String) The Spark Job Definition description. -- `format` (String) The Spark Job Definition format. Possible values: `SparkJobDefinitionV1`. - `properties` (Attributes) The Spark Job Definition properties. (see [below for nested schema](#nestedatt--properties)) diff --git a/docs/resources/data_pipeline.md b/docs/resources/data_pipeline.md index 4e13f1eb..fd73d0ce 100644 --- a/docs/resources/data_pipeline.md +++ b/docs/resources/data_pipeline.md @@ -67,14 +67,14 @@ resource "fabric_data_pipeline" "example_definition_update" { ### Optional -- `definition` (Attributes Map) Definition parts. Accepted path keys: `pipeline-content.json`. Read more about [Data Pipeline definition part paths](https://learn.microsoft.com/fabric/data-factory/pipeline-rest-api). (see [below for nested schema](#nestedatt--definition)) +- `definition` (Attributes Map) Definition parts. Read more about [Data Pipeline definition part paths](https://learn.microsoft.com/fabric/data-factory/pipeline-rest-api). Accepted path keys: **Default** format: `pipeline-content.json` (see [below for nested schema](#nestedatt--definition)) - `definition_update_enabled` (Boolean) Update definition on change of source content. Default: `true`. - `description` (String) The Data Pipeline description. +- `format` (String) The Data Pipeline format. Possible values: `Default` - `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts)) ### Read-Only -- `format` (String) The Data Pipeline format. Possible values: `NotApplicable` - `id` (String) The Data Pipeline ID. diff --git a/docs/resources/eventhouse.md b/docs/resources/eventhouse.md index 4b2dae98..74938e9e 100644 --- a/docs/resources/eventhouse.md +++ b/docs/resources/eventhouse.md @@ -64,14 +64,14 @@ resource "fabric_eventhouse" "example_definition_update" { ### Optional -- `definition` (Attributes Map) Definition parts. Accepted path keys: `EventhouseProperties.json`. Read more about [Eventhouse definition part paths](https://learn.microsoft.com/rest/api/fabric/articles/item-management/definitions/eventhouse-definition). (see [below for nested schema](#nestedatt--definition)) +- `definition` (Attributes Map) Definition parts. Read more about [Eventhouse definition part paths](https://learn.microsoft.com/rest/api/fabric/articles/item-management/definitions/eventhouse-definition). Accepted path keys: **Default** format: `EventhouseProperties.json` (see [below for nested schema](#nestedatt--definition)) - `definition_update_enabled` (Boolean) Update definition on change of source content. Default: `true`. - `description` (String) The Eventhouse description. +- `format` (String) The Eventhouse format. Possible values: `Default` - `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts)) ### Read-Only -- `format` (String) The Eventhouse format. Possible values: `NotApplicable` - `id` (String) The Eventhouse ID. - `properties` (Attributes) The Eventhouse properties. (see [below for nested schema](#nestedatt--properties)) diff --git a/docs/resources/notebook.md b/docs/resources/notebook.md index ae607466..1cb89c82 100644 --- a/docs/resources/notebook.md +++ b/docs/resources/notebook.md @@ -31,6 +31,7 @@ resource "fabric_notebook" "example_definition_bootstrap" { description = "example with definition bootstrapping" workspace_id = "00000000-0000-0000-0000-000000000000" definition_update_enabled = false + format = "ipynb" definition = { "notebook-content.ipynb" = { source = "${local.path}/notebook.ipynb.tmpl" @@ -43,6 +44,7 @@ resource "fabric_notebook" "example_definition_update" { display_name = "example" description = "example with definition update when source or tokens changed" workspace_id = "00000000-0000-0000-0000-000000000000" + format = "ipynb" definition = { "notebook-content.ipynb" = { source = "${local.path}/notebook.ipynb.tmpl" @@ -65,14 +67,14 @@ resource "fabric_notebook" "example_definition_update" { ### Optional -- `definition` (Attributes Map) Definition parts. Accepted path keys: `notebook-content.ipynb`. Read more about [Notebook definition part paths](https://learn.microsoft.com/rest/api/fabric/articles/item-management/definitions/notebook-definition). (see [below for nested schema](#nestedatt--definition)) +- `definition` (Attributes Map) Definition parts. Read more about [Notebook definition part paths](https://learn.microsoft.com/rest/api/fabric/articles/item-management/definitions/notebook-definition). Accepted path keys: **ipynb** format: `notebook-content.ipynb` **py** format: `notebook-content.py` (see [below for nested schema](#nestedatt--definition)) - `definition_update_enabled` (Boolean) Update definition on change of source content. Default: `true`. - `description` (String) The Notebook description. +- `format` (String) The Notebook format. Possible values: `ipynb`, `py` - `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts)) ### Read-Only -- `format` (String) The Notebook format. Possible values: `ipynb`. - `id` (String) The Notebook ID. diff --git a/docs/resources/report.md b/docs/resources/report.md index 84123323..97238537 100644 --- a/docs/resources/report.md +++ b/docs/resources/report.md @@ -24,6 +24,7 @@ resource "fabric_report" "example_bootstrap" { display_name = "example" workspace_id = "00000000-0000-0000-0000-000000000000" definition_update_enabled = false + format = "PBIR-Legacy" definition = { "report.json" = { source = "${local.path}/report.json" @@ -44,6 +45,7 @@ resource "fabric_report" "example_bootstrap" { resource "fabric_report" "example_update" { display_name = "example with update" workspace_id = "00000000-0000-0000-0000-000000000000" + format = "PBIR-Legacy" definition = { "report.json" = { source = "${local.path}/report.json" @@ -66,8 +68,9 @@ resource "fabric_report" "example_update" { ### Required -- `definition` (Attributes Map) Definition parts. Accepted path keys: `report.json`, `definition.pbir`, `StaticResources/RegisteredResources/*`, `StaticResources/SharedResources/*`. Read more about [Report definition part paths](https://learn.microsoft.com/rest/api/fabric/articles/item-management/definitions/report-definition). (see [below for nested schema](#nestedatt--definition)) +- `definition` (Attributes Map) Definition parts. Read more about [Report definition part paths](https://learn.microsoft.com/rest/api/fabric/articles/item-management/definitions/report-definition). Accepted path keys: **PBIR** format: `StaticResources/RegisteredResources/*`, `StaticResources/SharedResources/*`, `definition.pbir`, `definition/pages/*.json`, `definition/report.json`, `definition/version.json` **PBIR-Legacy** format: `StaticResources/RegisteredResources/*`, `StaticResources/SharedResources/*`, `definition.pbir`, `report.json` (see [below for nested schema](#nestedatt--definition)) - `display_name` (String) The Report display name. +- `format` (String) The Report format. Possible values: `PBIR-Legacy`, `PBIR` - `workspace_id` (String) The Workspace ID. ### Optional @@ -78,7 +81,6 @@ resource "fabric_report" "example_update" { ### Read-Only -- `format` (String) The Report format. Possible values: `PBIR-Legacy`. - `id` (String) The Report ID. diff --git a/docs/resources/semantic_model.md b/docs/resources/semantic_model.md index f3aa4e58..87d9b6d0 100644 --- a/docs/resources/semantic_model.md +++ b/docs/resources/semantic_model.md @@ -24,6 +24,7 @@ resource "fabric_semantic_model" "example_bootstrap" { display_name = "example" workspace_id = "00000000-0000-0000-0000-000000000000" definition_update_enabled = false + format = "TMSL" definition = { "model.bim" = { source = "${local.path}/model.bim.tmpl" @@ -38,6 +39,7 @@ resource "fabric_semantic_model" "example_bootstrap" { resource "fabric_semantic_model" "example_update" { display_name = "example with update" workspace_id = "00000000-0000-0000-0000-000000000000" + format = "TMSL" definition = { "model.bim" = { source = "${local.path}/model.bim.tmpl" @@ -57,8 +59,9 @@ resource "fabric_semantic_model" "example_update" { ### Required -- `definition` (Attributes Map) Definition parts. Accepted path keys: `model.bim`, `definition.pbism`, `diagramLayout.json`. Read more about [Semantic Model definition part paths](https://learn.microsoft.com/rest/api/fabric/articles/item-management/definitions/semantic-model-definition). (see [below for nested schema](#nestedatt--definition)) +- `definition` (Attributes Map) Definition parts. Read more about [Semantic Model definition part paths](https://learn.microsoft.com/rest/api/fabric/articles/item-management/definitions/semantic-model-definition). Accepted path keys: **TMSL** format: `definition.pbism`, `diagramLayp.json`, `model.bim` **TMDL** format: `definition.pbism`, `definition/database.tmdl`, `definition/model.tmdl`, `definition/tables/*.tmdl`, `diagramLayp.json` (see [below for nested schema](#nestedatt--definition)) - `display_name` (String) The Semantic Model display name. +- `format` (String) The Semantic Model format. Possible values: `TMSL`, `TMDL` - `workspace_id` (String) The Workspace ID. ### Optional @@ -69,7 +72,6 @@ resource "fabric_semantic_model" "example_update" { ### Read-Only -- `format` (String) The Semantic Model format. Possible values: `TMSL`. - `id` (String) The Semantic Model ID. diff --git a/docs/resources/spark_job_definition.md b/docs/resources/spark_job_definition.md index 047c6665..50a53016 100644 --- a/docs/resources/spark_job_definition.md +++ b/docs/resources/spark_job_definition.md @@ -31,6 +31,7 @@ resource "fabric_spark_job_definition" "example_definition_bootstrap" { description = "example with definition bootstrapping" workspace_id = "00000000-0000-0000-0000-000000000000" definition_update_enabled = false + format = "SparkJobDefinitionV1" definition = { "SparkJobDefinitionV1.json" = { source = "${local.path}/SparkJobDefinitionV1.json.tmpl" @@ -43,6 +44,7 @@ resource "fabric_spark_job_definition" "example_definition_update" { display_name = "example3" description = "example with definition update when source or tokens changed" workspace_id = "00000000-0000-0000-0000-000000000000" + format = "SparkJobDefinitionV1" definition = { "SparkJobDefinitionV1.json" = { source = "${local.path}/SparkJobDefinitionV1.json.tmpl" @@ -65,14 +67,14 @@ resource "fabric_spark_job_definition" "example_definition_update" { ### Optional -- `definition` (Attributes Map) Definition parts. Accepted path keys: `SparkJobDefinitionV1.json`. Read more about [Spark Job Definition definition part paths](https://learn.microsoft.com/rest/api/fabric/articles/item-management/definitions/spark-job-definition). (see [below for nested schema](#nestedatt--definition)) +- `definition` (Attributes Map) Definition parts. Read more about [Spark Job Definition definition part paths](https://learn.microsoft.com/rest/api/fabric/articles/item-management/definitions/spark-job-definition). Accepted path keys: **SparkJobDefinitionV1** format: `SparkJobDefinitionV1.json` (see [below for nested schema](#nestedatt--definition)) - `definition_update_enabled` (Boolean) Update definition on change of source content. Default: `true`. - `description` (String) The Spark Job Definition description. +- `format` (String) The Spark Job Definition format. Possible values: `SparkJobDefinitionV1` - `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts)) ### Read-Only -- `format` (String) The Spark Job Definition format. Possible values: `SparkJobDefinitionV1`. - `id` (String) The Spark Job Definition ID. - `properties` (Attributes) The Spark Job Definition properties. (see [below for nested schema](#nestedatt--properties)) diff --git a/examples/data-sources/fabric_notebook/data-source.tf b/examples/data-sources/fabric_notebook/data-source.tf index f3faf46b..e7210117 100644 --- a/examples/data-sources/fabric_notebook/data-source.tf +++ b/examples/data-sources/fabric_notebook/data-source.tf @@ -15,6 +15,7 @@ data "fabric_notebook" "example_by_id" { data "fabric_notebook" "example_definition" { id = "11111111-1111-1111-1111-111111111111" workspace_id = "00000000-0000-0000-0000-000000000000" + format = "ipynb" output_definition = true } diff --git a/examples/data-sources/fabric_report/data-source.tf b/examples/data-sources/fabric_report/data-source.tf index 677ba505..6fa4b5e6 100644 --- a/examples/data-sources/fabric_report/data-source.tf +++ b/examples/data-sources/fabric_report/data-source.tf @@ -8,6 +8,7 @@ data "fabric_report" "example" { data "fabric_report" "example_definition" { id = "11111111-1111-1111-1111-111111111111" workspace_id = "00000000-0000-0000-0000-000000000000" + format = "PBIR-Legacy" output_definition = true } diff --git a/examples/data-sources/fabric_semantic_model/data-source.tf b/examples/data-sources/fabric_semantic_model/data-source.tf index bc1d2b3a..a965feed 100644 --- a/examples/data-sources/fabric_semantic_model/data-source.tf +++ b/examples/data-sources/fabric_semantic_model/data-source.tf @@ -8,6 +8,7 @@ data "fabric_semantic_model" "example" { data "fabric_semantic_model" "example_definition" { id = "11111111-1111-1111-1111-111111111111" workspace_id = "00000000-0000-0000-0000-000000000000" + format = "TMSL" output_definition = true } diff --git a/examples/data-sources/fabric_spark_job_definition/data-source.tf b/examples/data-sources/fabric_spark_job_definition/data-source.tf index 0540e7a1..a3aaa5f6 100644 --- a/examples/data-sources/fabric_spark_job_definition/data-source.tf +++ b/examples/data-sources/fabric_spark_job_definition/data-source.tf @@ -15,6 +15,7 @@ data "fabric_spark_job_definition" "example_by_name" { data "fabric_spark_job_definition" "example_definition" { id = "11111111-1111-1111-1111-111111111111" workspace_id = "00000000-0000-0000-0000-000000000000" + format = "SparkJobDefinitionV1" output_definition = true } diff --git a/examples/resources/fabric_notebook/resource.tf b/examples/resources/fabric_notebook/resource.tf index 2f6e58dd..80f6fc2f 100644 --- a/examples/resources/fabric_notebook/resource.tf +++ b/examples/resources/fabric_notebook/resource.tf @@ -10,6 +10,7 @@ resource "fabric_notebook" "example_definition_bootstrap" { description = "example with definition bootstrapping" workspace_id = "00000000-0000-0000-0000-000000000000" definition_update_enabled = false + format = "ipynb" definition = { "notebook-content.ipynb" = { source = "${local.path}/notebook.ipynb.tmpl" @@ -22,6 +23,7 @@ resource "fabric_notebook" "example_definition_update" { display_name = "example" description = "example with definition update when source or tokens changed" workspace_id = "00000000-0000-0000-0000-000000000000" + format = "ipynb" definition = { "notebook-content.ipynb" = { source = "${local.path}/notebook.ipynb.tmpl" diff --git a/examples/resources/fabric_report/resource.tf b/examples/resources/fabric_report/resource.tf index 846b03f5..4faf413e 100644 --- a/examples/resources/fabric_report/resource.tf +++ b/examples/resources/fabric_report/resource.tf @@ -3,6 +3,7 @@ resource "fabric_report" "example_bootstrap" { display_name = "example" workspace_id = "00000000-0000-0000-0000-000000000000" definition_update_enabled = false + format = "PBIR-Legacy" definition = { "report.json" = { source = "${local.path}/report.json" @@ -23,6 +24,7 @@ resource "fabric_report" "example_bootstrap" { resource "fabric_report" "example_update" { display_name = "example with update" workspace_id = "00000000-0000-0000-0000-000000000000" + format = "PBIR-Legacy" definition = { "report.json" = { source = "${local.path}/report.json" diff --git a/examples/resources/fabric_semantic_model/resource.tf b/examples/resources/fabric_semantic_model/resource.tf index 8d536ef3..abe5e017 100644 --- a/examples/resources/fabric_semantic_model/resource.tf +++ b/examples/resources/fabric_semantic_model/resource.tf @@ -3,6 +3,7 @@ resource "fabric_semantic_model" "example_bootstrap" { display_name = "example" workspace_id = "00000000-0000-0000-0000-000000000000" definition_update_enabled = false + format = "TMSL" definition = { "model.bim" = { source = "${local.path}/model.bim.tmpl" @@ -17,6 +18,7 @@ resource "fabric_semantic_model" "example_bootstrap" { resource "fabric_semantic_model" "example_update" { display_name = "example with update" workspace_id = "00000000-0000-0000-0000-000000000000" + format = "TMSL" definition = { "model.bim" = { source = "${local.path}/model.bim.tmpl" diff --git a/examples/resources/fabric_spark_job_definition/resource.tf b/examples/resources/fabric_spark_job_definition/resource.tf index e71733e3..511033ca 100644 --- a/examples/resources/fabric_spark_job_definition/resource.tf +++ b/examples/resources/fabric_spark_job_definition/resource.tf @@ -10,6 +10,7 @@ resource "fabric_spark_job_definition" "example_definition_bootstrap" { description = "example with definition bootstrapping" workspace_id = "00000000-0000-0000-0000-000000000000" definition_update_enabled = false + format = "SparkJobDefinitionV1" definition = { "SparkJobDefinitionV1.json" = { source = "${local.path}/SparkJobDefinitionV1.json.tmpl" @@ -22,6 +23,7 @@ resource "fabric_spark_job_definition" "example_definition_update" { display_name = "example3" description = "example with definition update when source or tokens changed" workspace_id = "00000000-0000-0000-0000-000000000000" + format = "SparkJobDefinitionV1" definition = { "SparkJobDefinitionV1.json" = { source = "${local.path}/SparkJobDefinitionV1.json.tmpl" diff --git a/internal/framework/validators/regexp_if_attribute_is_one_of.go b/internal/framework/validators/regexp_if_attribute_is_one_of.go new file mode 100644 index 00000000..8815e2ad --- /dev/null +++ b/internal/framework/validators/regexp_if_attribute_is_one_of.go @@ -0,0 +1,144 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package validators + +import ( + "context" + "fmt" + "regexp" + "strings" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +var _ validator.String = RegexpIfAttributeIsOneOfValidator{} + +type RegexpIfAttributeIsOneOfValidator struct { + pathExpression path.Expression + exceptedValues []attr.Value + patterns []string + message string +} + +func RegexpIfAttributeIsOneOf(p path.Expression, exceptedValue []attr.Value, patterns []string, message string) RegexpIfAttributeIsOneOfValidator { + return RegexpIfAttributeIsOneOfValidator{ + pathExpression: p, + exceptedValues: exceptedValue, + patterns: patterns, + message: message, + } +} + +func (v RegexpIfAttributeIsOneOfValidator) Description(_ context.Context) string { + if v.message != "" { + return v.message + } + + return fmt.Sprintf("value must match pattern expression '%s'", strings.Join(v.patterns, ", ")) +} + +func (v RegexpIfAttributeIsOneOfValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +func (v RegexpIfAttributeIsOneOfValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { + paths, diags := req.Config.PathMatches(ctx, req.PathExpression.Merge(v.pathExpression)) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + + return + } + + if len(paths) == 0 { + resp.Diagnostics.AddError( + fmt.Sprintf("Invalid configuration for attribute %s", req.Path), + "Path must be set", + ) + + return + } + + p := paths[0] + + // mpVal is the value of the attribute in the path + var mpVal attr.Value + resp.Diagnostics.Append(req.Config.GetAttribute(ctx, p, &mpVal)...) + + if resp.Diagnostics.HasError() { + resp.Diagnostics.AddError( + fmt.Sprintf("Invalid configuration for attribute %s", req.Path), + fmt.Sprintf("Unable to retrieve attribute path: %q", p), + ) + + return + } + + // If the target attribute configuration is unknown or null, there is nothing else to validate + if mpVal.IsNull() || mpVal.IsUnknown() { + return + } + + for _, expectedValue := range v.exceptedValues { + // If the value of the target attribute is equal to one of the expected values, we need to validate the value of the current attribute + if mpVal.Equal(expectedValue) || mpVal.String() == expectedValue.String() { + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + resp.Diagnostics.AddAttributeError( + p, + fmt.Sprintf("Invalid configuration for attribute %s", req.Path), + "Value is empty. "+v.Description(ctx), + ) + + return + } + + re, err := v.convertPatternsToRegexp(v.patterns) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Invalid configuration for attribute %s", req.Path), + fmt.Sprintf("Unable to compile regular expression: %q", err), + ) + + return + } + + value := req.ConfigValue.ValueString() + + if !re.MatchString(value) { + resp.Diagnostics.Append(validatordiag.InvalidAttributeValueMatchDiagnostic( + req.Path, + v.Description(ctx), + value, + )) + } + } + } +} + +func (v RegexpIfAttributeIsOneOfValidator) convertPatternsToRegexp(patterns []string) (*regexp.Regexp, error) { + p := make([]string, 0) + + p = append(p, "^(") + + for _, pattern := range patterns { + p = append(p, regexp.QuoteMeta(pattern)) + if pattern != patterns[len(patterns)-1] { + p = append(p, "|") + } + } + + p = append(p, ")$") + + out := strings.Join(p, "") + out = strings.ReplaceAll(out, `\*`, ".+") + + re, err := regexp.Compile(out) + if err != nil { + return nil, err + } + + return re, nil +} diff --git a/internal/pkg/fabricitem/base.go b/internal/pkg/fabricitem/base.go index 45c8c196..20f4db0c 100644 --- a/internal/pkg/fabricitem/base.go +++ b/internal/pkg/fabricitem/base.go @@ -3,4 +3,6 @@ package fabricitem -const DefinitionFormatNotApplicable = "NotApplicable" +const ( + DefinitionFormatDefault = "Default" +) diff --git a/internal/pkg/fabricitem/data_item_definition.go b/internal/pkg/fabricitem/data_item_definition.go index 41dd22d6..5ac8b7a0 100644 --- a/internal/pkg/fabricitem/data_item_definition.go +++ b/internal/pkg/fabricitem/data_item_definition.go @@ -36,8 +36,7 @@ type DataSourceFabricItemDefinition struct { MarkdownDescription string IsDisplayNameUnique bool FormatTypeDefault string - FormatTypes []string - DefinitionPathKeys []string + DefinitionFormats []DefinitionFormat } func NewDataSourceFabricItemDefinition(config DataSourceFabricItemDefinition) datasource.DataSource { @@ -120,10 +119,12 @@ func (d *DataSourceFabricItemDefinition) Read(ctx context.Context, req datasourc return } - data.Format = types.StringNull() - - if d.FormatTypeDefault != "" { - data.Format = types.StringValue(d.FormatTypeDefault) + if data.Format.IsNull() || data.Format.IsUnknown() { + if d.FormatTypeDefault != "" { + data.Format = types.StringValue(d.FormatTypeDefault) + } else { + data.Format = types.StringNull() + } } resp.Diagnostics.Append(resp.State.Set(ctx, data)...) @@ -203,7 +204,11 @@ func (d *DataSourceFabricItemDefinition) getDefinition(ctx context.Context, mode respGetOpts := &fabcore.ItemsClientBeginGetItemDefinitionOptions{} if !model.Format.IsNull() { - respGetOpts.Format = model.Format.ValueStringPointer() + apiFormat := getDefinitionFormatAPI(d.DefinitionFormats, model.Format.ValueString()) + + if apiFormat != "" { + respGetOpts.Format = azto.Ptr(apiFormat) + } } respGet, err := d.client.GetItemDefinition(ctx, model.WorkspaceID.ValueString(), model.ID.ValueString(), respGetOpts) diff --git a/internal/pkg/fabricitem/data_schema.go b/internal/pkg/fabricitem/data_schema.go index 69d3761c..c6bf4471 100644 --- a/internal/pkg/fabricitem/data_schema.go +++ b/internal/pkg/fabricitem/data_schema.go @@ -8,8 +8,14 @@ import ( "fmt" "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" + superstringvalidator "github.com/orange-cloudavenue/terraform-plugin-framework-validators/stringvalidator" "github.com/microsoft/terraform-provider-fabric/internal/framework/customtypes" "github.com/microsoft/terraform-provider-fabric/internal/pkg/utils" @@ -27,7 +33,7 @@ func getDataSourceFabricItemSchema(ctx context.Context, d DataSourceFabricItem) func getDataSourceFabricItemDefinitionSchema(ctx context.Context, d DataSourceFabricItemDefinition) schema.Schema { attributes := getDataSourceFabricItemBaseAttributes(ctx, d.Name, d.IsDisplayNameUnique) - for key, value := range getDataSourceFabricItemDefinitionAttributes(ctx, d.Name, d.FormatTypes, d.DefinitionPathKeys) { + for key, value := range getDataSourceFabricItemDefinitionAttributes(ctx, d.Name, d.DefinitionFormats) { attributes[key] = value } @@ -51,7 +57,7 @@ func getDataSourceFabricItemDefinitionPropertiesSchema[Ttfprop, Titemprop any](c attributes := getDataSourceFabricItemBaseAttributes(ctx, d.Name, d.IsDisplayNameUnique) attributes["properties"] = getDataSourceFabricItemPropertiesNestedAttr[Ttfprop](ctx, d.Name, d.PropertiesAttributes) - for key, value := range getDataSourceFabricItemDefinitionAttributes(ctx, d.Name, d.FormatTypes, d.DefinitionPathKeys) { + for key, value := range getDataSourceFabricItemDefinitionAttributes(ctx, d.Name, d.DefinitionFormats) { attributes[key] = value } @@ -105,21 +111,25 @@ func getDataSourceFabricItemBaseAttributes(ctx context.Context, itemName string, } // Helper function to get Fabric Item data-source definition attributes. -func getDataSourceFabricItemDefinitionAttributes(ctx context.Context, name string, formatTypes, definitionPathKeys []string) map[string]schema.Attribute { +func getDataSourceFabricItemDefinitionAttributes(ctx context.Context, name string, definitionFormats []DefinitionFormat) map[string]schema.Attribute { attributes := make(map[string]schema.Attribute) - if len(formatTypes) > 0 { - attributes["format"] = schema.StringAttribute{ - MarkdownDescription: fmt.Sprintf("The %s format. Possible values: %s.", name, utils.ConvertStringSlicesToString(formatTypes, true, false)), - Computed: true, - } - } else { - attributes["format"] = schema.StringAttribute{ - MarkdownDescription: fmt.Sprintf("The %s format. Possible values: `%s`", name, DefinitionFormatNotApplicable), - Computed: true, - } + formatTypes := getDefinitionFormats(definitionFormats) + definitionFormatsDocs := getDefinitionFormatsPathsDocs(definitionFormats) + + // format attribute + attrFormat := schema.StringAttribute{} + + attrFormat.MarkdownDescription = fmt.Sprintf("The %s format. Possible values: %s", name, utils.ConvertStringSlicesToString(formatTypes, true, false)) + attrFormat.Optional = true + attrFormat.Validators = []validator.String{ + stringvalidator.OneOf(formatTypes...), + superstringvalidator.RequireIfAttributeIsOneOf(path.MatchRoot("output_definition"), []attr.Value{types.BoolValue(true)}), } + attributes["format"] = attrFormat + + // output_definition attribute attributes["output_definition"] = schema.BoolAttribute{ MarkdownDescription: "Output definition parts as gzip base64 content? Default: `false`\n\n" + "!> Your terraform state file may grow a lot if you output definition content. Only use it when you must use data from the definition.", @@ -141,11 +151,7 @@ func getDataSourceFabricItemDefinitionAttributes(ctx context.Context, name strin }, } - if len(definitionPathKeys) > 0 { - attrDefinition.MarkdownDescription = "Definition parts. Possible path keys: " + utils.ConvertStringSlicesToString(definitionPathKeys, true, false) + "." - } else { - attrDefinition.MarkdownDescription = "Definition parts." - } + attrDefinition.MarkdownDescription = "Definition parts. Possible path keys: " + definitionFormatsDocs attributes["definition"] = attrDefinition diff --git a/internal/pkg/fabricitem/definition.go b/internal/pkg/fabricitem/definition.go new file mode 100644 index 00000000..672d96d3 --- /dev/null +++ b/internal/pkg/fabricitem/definition.go @@ -0,0 +1,106 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MPL-2.0 + +package fabricitem + +import ( + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + superstringvalidator "github.com/orange-cloudavenue/terraform-plugin-framework-validators/stringvalidator" + + "github.com/microsoft/terraform-provider-fabric/internal/pkg/utils" +) + +type DefinitionFormat struct { + Type string + API string + Paths []string +} + +func getDefinitionFormats(values []DefinitionFormat) []string { + results := make([]string, len(values)) + + for i, value := range values { + results[i] = value.Type + } + + return results +} + +func getDefinitionFormatsPaths(values []DefinitionFormat) map[string][]string { + results := make(map[string][]string) + + for _, v := range values { + results[v.Type] = v.Paths + } + + return results +} + +func getDefinitionFormatsPathsDocs(values []DefinitionFormat) string { + elements := getDefinitionFormatsPaths(values) + + var results string + + i := 0 + + for k, v := range elements { + results += "**" + k + "** format: " + results += utils.ConvertStringSlicesToString(v, true, true) + + if i != len(elements)-1 { + results += " " + } + + i++ + } + + return results +} + +func GetDefinitionFormatPaths(values []DefinitionFormat, format string) []string { + for _, value := range values { + if value.Type == format { + return value.Paths + } + } + + return nil +} + +func getDefinitionFormatAPI(values []DefinitionFormat, format string) string { + for _, value := range values { + if value.Type == format { + return value.API + } + } + + return "" +} + +func DefinitionPathKeysValidator(values []DefinitionFormat) []validator.String { + results := make([]validator.String, 0, len(values)) + + for _, value := range values { + paths := []superstringvalidator.OneOfWithDescriptionIfAttributeIsOneOfValues{} + + for _, p := range value.Paths { + paths = append(paths, superstringvalidator.OneOfWithDescriptionIfAttributeIsOneOfValues{ + Value: p, + Description: p, + }) + } + + stringValidator := superstringvalidator.OneOfWithDescriptionIfAttributeIsOneOf( + path.MatchRoot("format"), + []attr.Value{types.StringValue(value.Type)}, + paths..., + ) + + results = append(results, stringValidator) + } + + return results +} diff --git a/internal/pkg/fabricitem/models_resource_item.go b/internal/pkg/fabricitem/models_resource_item.go index fc98cfca..fa8cca46 100644 --- a/internal/pkg/fabricitem/models_resource_item.go +++ b/internal/pkg/fabricitem/models_resource_item.go @@ -40,11 +40,11 @@ func (to *requestCreateFabricItem) setType(v fabcore.ItemType) { to.Type = &v } -func (to *requestCreateFabricItem) setDefinition(ctx context.Context, definition supertypes.MapNestedObjectValueOf[resourceFabricItemDefinitionPartModel], format types.String, definitionUpdateEnabled types.Bool) diag.Diagnostics { +func (to *requestCreateFabricItem) setDefinition(ctx context.Context, definition supertypes.MapNestedObjectValueOf[resourceFabricItemDefinitionPartModel], format types.String, definitionUpdateEnabled types.Bool, definitionFormats []DefinitionFormat) diag.Diagnostics { if !definition.IsNull() && !definition.IsUnknown() { var def fabricItemDefinition - def.setFormat(format) + def.setFormat(format, definitionFormats) if diags := def.setParts(ctx, definition, "", []string{}, definitionUpdateEnabled, false); diags.HasError() { return diags @@ -104,9 +104,9 @@ func fabricItemCheckUpdate(planDisplayName, planDescription, stateDisplayName, s return !reflect.DeepEqual(reqUpdatePlan.UpdateItemRequest, reqUpdateState.UpdateItemRequest) } -func fabricItemCheckUpdateDefinition(ctx context.Context, planDefinition, stateDefinition supertypes.MapNestedObjectValueOf[resourceFabricItemDefinitionPartModel], planFormat types.String, planDefinitionUpdateEnabled types.Bool, definitionEmpty string, definitionPaths []string, reqUpdate *requestUpdateFabricItemDefinition) (bool, diag.Diagnostics) { +func fabricItemCheckUpdateDefinition(ctx context.Context, planDefinition, stateDefinition supertypes.MapNestedObjectValueOf[resourceFabricItemDefinitionPartModel], planFormat types.String, planDefinitionUpdateEnabled types.Bool, definitionEmpty string, definitionFormats []DefinitionFormat, reqUpdate *requestUpdateFabricItemDefinition) (bool, diag.Diagnostics) { if !planDefinition.Equal(stateDefinition) && planDefinitionUpdateEnabled.ValueBool() { - if diags := reqUpdate.setDefinition(ctx, planDefinition, planFormat, planDefinitionUpdateEnabled, definitionEmpty, definitionPaths); diags.HasError() { + if diags := reqUpdate.setDefinition(ctx, planDefinition, planFormat, planDefinitionUpdateEnabled, definitionEmpty, definitionFormats); diags.HasError() { return false, diags } diff --git a/internal/pkg/fabricitem/models_resource_item_definition.go b/internal/pkg/fabricitem/models_resource_item_definition.go index ccb25ba2..1c622fb3 100644 --- a/internal/pkg/fabricitem/models_resource_item_definition.go +++ b/internal/pkg/fabricitem/models_resource_item_definition.go @@ -35,9 +35,13 @@ type fabricItemDefinition struct { fabcore.ItemDefinition } -func (to *fabricItemDefinition) setFormat(v types.String) { - if v.ValueString() != DefinitionFormatNotApplicable && v.ValueString() != "" { - to.Format = v.ValueStringPointer() +func (to *fabricItemDefinition) setFormat(v types.String, definitionFormats []DefinitionFormat) { + if v.ValueString() != DefinitionFormatDefault && v.ValueString() != "" { + apiFormat := getDefinitionFormatAPI(definitionFormats, v.ValueString()) + + if apiFormat != "" { + to.Format = &apiFormat + } } } @@ -62,7 +66,7 @@ func (to *fabricItemDefinition) setParts(ctx context.Context, definition superty } to.Parts = append(to.Parts, fabcore.ItemDefinitionPart{ - Path: azto.Ptr(definitionPaths[0]), + Path: &definitionPaths[0], Payload: &content, PayloadType: azto.Ptr(fabcore.PayloadTypeInlineBase64), }) @@ -78,7 +82,7 @@ func (to *fabricItemDefinition) setParts(ctx context.Context, definition superty } to.Parts = append(to.Parts, fabcore.ItemDefinitionPart{ - Path: azto.Ptr(defPartKey), + Path: &defPartKey, Payload: payloadB64, PayloadType: azto.Ptr(fabcore.PayloadTypeInlineBase64), }) @@ -92,12 +96,14 @@ type requestUpdateFabricItemDefinition struct { fabcore.UpdateItemDefinitionRequest } -func (to *requestUpdateFabricItemDefinition) setDefinition(ctx context.Context, definition supertypes.MapNestedObjectValueOf[resourceFabricItemDefinitionPartModel], format types.String, definitionUpdateEnabled types.Bool, definitionEmpty string, definitionPaths []string) diag.Diagnostics { +func (to *requestUpdateFabricItemDefinition) setDefinition(ctx context.Context, definition supertypes.MapNestedObjectValueOf[resourceFabricItemDefinitionPartModel], format types.String, definitionUpdateEnabled types.Bool, definitionEmpty string, definitionFormats []DefinitionFormat) diag.Diagnostics { var def fabricItemDefinition - def.setFormat(format) + def.setFormat(format, definitionFormats) + + definitionPathKeys := GetDefinitionFormatPaths(definitionFormats, format.ValueString()) - if diags := def.setParts(ctx, definition, definitionEmpty, definitionPaths, definitionUpdateEnabled, true); diags.HasError() { + if diags := def.setParts(ctx, definition, definitionEmpty, definitionPathKeys, definitionUpdateEnabled, true); diags.HasError() { return diags } diff --git a/internal/pkg/fabricitem/resource_item_config_definition_properties.go b/internal/pkg/fabricitem/resource_item_config_definition_properties.go index 2c80ac5b..59ad5541 100644 --- a/internal/pkg/fabricitem/resource_item_config_definition_properties.go +++ b/internal/pkg/fabricitem/resource_item_config_definition_properties.go @@ -74,7 +74,7 @@ func (r *ResourceFabricItemConfigDefinitionProperties[Ttfprop, Titemprop, Ttfcon var reqUpdateDefinition requestUpdateFabricItemDefinition - doUpdateDefinition, diags := fabricItemCheckUpdateDefinition(ctx, plan.Definition, state.Definition, plan.Format, plan.DefinitionUpdateEnabled, r.DefinitionEmpty, r.DefinitionPathKeys, &reqUpdateDefinition) + doUpdateDefinition, diags := fabricItemCheckUpdateDefinition(ctx, plan.Definition, state.Definition, plan.Format, plan.DefinitionUpdateEnabled, r.DefinitionEmpty, r.DefinitionFormats, &reqUpdateDefinition) if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { return } @@ -157,7 +157,7 @@ func (r *ResourceFabricItemConfigDefinitionProperties[Ttfprop, Titemprop, Ttfcon reqCreate.setDescription(plan.Description) reqCreate.setType(r.Type) - if resp.Diagnostics.Append(reqCreate.setDefinition(ctx, plan.Definition, plan.Format, plan.DefinitionUpdateEnabled)...); resp.Diagnostics.HasError() { + if resp.Diagnostics.Append(reqCreate.setDefinition(ctx, plan.Definition, plan.Format, plan.DefinitionUpdateEnabled, r.DefinitionFormats)...); resp.Diagnostics.HasError() { return } @@ -283,7 +283,7 @@ func (r *ResourceFabricItemConfigDefinitionProperties[Ttfprop, Titemprop, Ttfcon var reqUpdateDefinition requestUpdateFabricItemDefinition - doUpdateDefinition, diags := fabricItemCheckUpdateDefinition(ctx, plan.Definition, state.Definition, plan.Format, plan.DefinitionUpdateEnabled, r.DefinitionEmpty, r.DefinitionPathKeys, &reqUpdateDefinition) + doUpdateDefinition, diags := fabricItemCheckUpdateDefinition(ctx, plan.Definition, state.Definition, plan.Format, plan.DefinitionUpdateEnabled, r.DefinitionEmpty, r.DefinitionFormats, &reqUpdateDefinition) if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { return } diff --git a/internal/pkg/fabricitem/resource_item_definition.go b/internal/pkg/fabricitem/resource_item_definition.go index 2b864ef6..e949d339 100644 --- a/internal/pkg/fabricitem/resource_item_definition.go +++ b/internal/pkg/fabricitem/resource_item_definition.go @@ -42,12 +42,11 @@ type ResourceFabricItemDefinition struct { DisplayNameMaxLength int DescriptionMaxLength int FormatTypeDefault string - FormatTypes []string DefinitionPathDocsURL string - DefinitionPathKeys []string DefinitionPathKeysValidator []validator.Map DefinitionRequired bool DefinitionEmpty string + DefinitionFormats []DefinitionFormat } func NewResourceFabricItemDefinition(config ResourceFabricItemDefinition) resource.Resource { @@ -80,7 +79,7 @@ func (r *ResourceFabricItemDefinition) ModifyPlan(ctx context.Context, req resou var reqUpdateDefinition requestUpdateFabricItemDefinition - doUpdateDefinition, diags := fabricItemCheckUpdateDefinition(ctx, plan.Definition, state.Definition, plan.Format, plan.DefinitionUpdateEnabled, r.DefinitionEmpty, r.DefinitionPathKeys, &reqUpdateDefinition) + doUpdateDefinition, diags := fabricItemCheckUpdateDefinition(ctx, plan.Definition, state.Definition, plan.Format, plan.DefinitionUpdateEnabled, r.DefinitionEmpty, r.DefinitionFormats, &reqUpdateDefinition) if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { return } @@ -150,7 +149,7 @@ func (r *ResourceFabricItemDefinition) Create(ctx context.Context, req resource. reqCreate.setDescription(plan.Description) reqCreate.setType(r.Type) - if resp.Diagnostics.Append(reqCreate.setDefinition(ctx, plan.Definition, plan.Format, plan.DefinitionUpdateEnabled)...); resp.Diagnostics.HasError() { + if resp.Diagnostics.Append(reqCreate.setDefinition(ctx, plan.Definition, plan.Format, plan.DefinitionUpdateEnabled, r.DefinitionFormats)...); resp.Diagnostics.HasError() { return } @@ -262,7 +261,7 @@ func (r *ResourceFabricItemDefinition) Update(ctx context.Context, req resource. var reqUpdateDefinition requestUpdateFabricItemDefinition - doUpdateDefinition, diags := fabricItemCheckUpdateDefinition(ctx, plan.Definition, state.Definition, plan.Format, plan.DefinitionUpdateEnabled, r.DefinitionEmpty, r.DefinitionPathKeys, &reqUpdateDefinition) + doUpdateDefinition, diags := fabricItemCheckUpdateDefinition(ctx, plan.Definition, state.Definition, plan.Format, plan.DefinitionUpdateEnabled, r.DefinitionEmpty, r.DefinitionFormats, &reqUpdateDefinition) if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { return } diff --git a/internal/pkg/fabricitem/resource_item_definition_properties.go b/internal/pkg/fabricitem/resource_item_definition_properties.go index 37e2d440..b0389696 100644 --- a/internal/pkg/fabricitem/resource_item_definition_properties.go +++ b/internal/pkg/fabricitem/resource_item_definition_properties.go @@ -69,7 +69,7 @@ func (r *ResourceFabricItemDefinitionProperties[Ttfprop, Titemprop]) ModifyPlan( var reqUpdateDefinition requestUpdateFabricItemDefinition - doUpdateDefinition, diags := fabricItemCheckUpdateDefinition(ctx, plan.Definition, state.Definition, plan.Format, plan.DefinitionUpdateEnabled, r.DefinitionEmpty, r.DefinitionPathKeys, &reqUpdateDefinition) + doUpdateDefinition, diags := fabricItemCheckUpdateDefinition(ctx, plan.Definition, state.Definition, plan.Format, plan.DefinitionUpdateEnabled, r.DefinitionEmpty, r.DefinitionFormats, &reqUpdateDefinition) if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { return } @@ -139,7 +139,7 @@ func (r *ResourceFabricItemDefinitionProperties[Ttfprop, Titemprop]) Create(ctx reqCreate.setDescription(plan.Description) reqCreate.setType(r.Type) - if resp.Diagnostics.Append(reqCreate.setDefinition(ctx, plan.Definition, plan.Format, plan.DefinitionUpdateEnabled)...); resp.Diagnostics.HasError() { + if resp.Diagnostics.Append(reqCreate.setDefinition(ctx, plan.Definition, plan.Format, plan.DefinitionUpdateEnabled, r.DefinitionFormats)...); resp.Diagnostics.HasError() { return } @@ -258,7 +258,7 @@ func (r *ResourceFabricItemDefinitionProperties[Ttfprop, Titemprop]) Update(ctx var reqUpdateDefinition requestUpdateFabricItemDefinition - doUpdateDefinition, diags := fabricItemCheckUpdateDefinition(ctx, plan.Definition, state.Definition, plan.Format, plan.DefinitionUpdateEnabled, r.DefinitionEmpty, r.DefinitionPathKeys, &reqUpdateDefinition) + doUpdateDefinition, diags := fabricItemCheckUpdateDefinition(ctx, plan.Definition, state.Definition, plan.Format, plan.DefinitionUpdateEnabled, r.DefinitionEmpty, r.DefinitionFormats, &reqUpdateDefinition) if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { return } diff --git a/internal/pkg/fabricitem/resource_schema.go b/internal/pkg/fabricitem/resource_schema.go index dcc03955..94efa66b 100644 --- a/internal/pkg/fabricitem/resource_schema.go +++ b/internal/pkg/fabricitem/resource_schema.go @@ -22,6 +22,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" supertypes "github.com/orange-cloudavenue/terraform-plugin-framework-supertypes" + superstringvalidator "github.com/orange-cloudavenue/terraform-plugin-framework-validators/stringvalidator" "github.com/microsoft/terraform-provider-fabric/internal/framework/customtypes" "github.com/microsoft/terraform-provider-fabric/internal/framework/planmodifiers" @@ -40,7 +41,7 @@ func getResourceFabricItemSchema(ctx context.Context, r ResourceFabricItem) sche func getResourceFabricItemDefinitionSchema(ctx context.Context, r ResourceFabricItemDefinition) schema.Schema { attributes := getResourceFabricItemBaseAttributes(ctx, r.Name, r.DisplayNameMaxLength, r.DescriptionMaxLength, r.NameRenameAllowed) - for key, value := range getResourceFabricItemDefinitionAttributes(ctx, r.Name, r.FormatTypeDefault, r.FormatTypes, r.DefinitionPathDocsURL, r.DefinitionPathKeys, r.DefinitionPathKeysValidator, r.DefinitionRequired, false) { + for key, value := range getResourceFabricItemDefinitionAttributes(ctx, r.Name, r.FormatTypeDefault, r.DefinitionPathDocsURL, r.DefinitionFormats, r.DefinitionPathKeysValidator, r.DefinitionRequired, false) { attributes[key] = value } @@ -64,7 +65,7 @@ func getResourceFabricItemDefinitionPropertiesSchema[Ttfprop, Titemprop any](ctx attributes := getResourceFabricItemBaseAttributes(ctx, r.Name, r.DisplayNameMaxLength, r.DescriptionMaxLength, r.NameRenameAllowed) attributes["properties"] = getResourceFabricItemPropertiesNestedAttr[Ttfprop](ctx, r.Name, r.PropertiesAttributes) - for key, value := range getResourceFabricItemDefinitionAttributes(ctx, r.Name, r.FormatTypeDefault, r.FormatTypes, r.DefinitionPathDocsURL, r.DefinitionPathKeys, r.DefinitionPathKeysValidator, r.DefinitionRequired, false) { + for key, value := range getResourceFabricItemDefinitionAttributes(ctx, r.Name, r.FormatTypeDefault, r.DefinitionPathDocsURL, r.DefinitionFormats, r.DefinitionPathKeysValidator, r.DefinitionRequired, false) { attributes[key] = value } @@ -98,7 +99,7 @@ func getResourceFabricItemConfigDefinitionPropertiesSchema[Ttfprop, Titemprop, T attributes["configuration"] = attrConfiguration attributes["properties"] = getResourceFabricItemPropertiesNestedAttr[Ttfprop](ctx, r.Name, r.PropertiesAttributes) - for key, value := range getResourceFabricItemDefinitionAttributes(ctx, r.Name, r.FormatTypeDefault, r.FormatTypes, r.DefinitionPathDocsURL, r.DefinitionPathKeys, r.DefinitionPathKeysValidator, r.DefinitionRequired, true) { + for key, value := range getResourceFabricItemDefinitionAttributes(ctx, r.Name, r.FormatTypeDefault, r.DefinitionPathDocsURL, r.DefinitionFormats, r.DefinitionPathKeysValidator, r.DefinitionRequired, true) { attributes[key] = value } @@ -188,7 +189,7 @@ func getResourceFabricItemBaseAttributes(ctx context.Context, name string, displ } // Helper function to get Fabric Item definition attributes. -func getResourceFabricItemDefinitionAttributes(ctx context.Context, name, formatTypeDefault string, formatTypes []string, definitionPathDocsURL string, definitionPathKeys []string, definitionPathKeysValidator []validator.Map, definitionRequired, alongConfiguration bool) map[string]schema.Attribute { //revive:disable-line:flag-parameter,argument-limit +func getResourceFabricItemDefinitionAttributes(ctx context.Context, name, formatTypeDefault, definitionPathDocsURL string, definitionFormats []DefinitionFormat, definitionPathKeysValidator []validator.Map, definitionRequired, alongConfiguration bool) map[string]schema.Attribute { //revive:disable-line:flag-parameter,argument-limit attributes := make(map[string]schema.Attribute) attrDefinitionUpdateEnabled := schema.BoolAttribute{} @@ -206,27 +207,33 @@ func getResourceFabricItemDefinitionAttributes(ctx context.Context, name, format attributes["definition_update_enabled"] = attrDefinitionUpdateEnabled + formatTypes := getDefinitionFormats(definitionFormats) + definitionFormatsDocs := getDefinitionFormatsPathsDocs(definitionFormats) + attrFormat := schema.StringAttribute{} - attrFormat.Computed = true - if len(formatTypes) > 0 { - attrFormat.MarkdownDescription = fmt.Sprintf("The %s format. Possible values: %s.", name, utils.ConvertStringSlicesToString(formatTypes, true, false)) - attrFormat.Default = stringdefault.StaticString(formatTypeDefault) + attrFormat.MarkdownDescription = fmt.Sprintf("The %s format. Possible values: %s", name, utils.ConvertStringSlicesToString(formatTypes, true, false)) + attrFormat.Validators = []validator.String{ + stringvalidator.OneOf(utils.ConvertEnumsToStringSlices(formatTypes, true)...), + superstringvalidator.RequireIfAttributeIsSet(path.MatchRoot("definition")), + } + + if definitionRequired { + attrFormat.Required = true } else { - attrFormat.MarkdownDescription = fmt.Sprintf("The %s format. Possible values: `%s`", name, DefinitionFormatNotApplicable) - attrFormat.Default = stringdefault.StaticString(DefinitionFormatNotApplicable) + attrFormat.Optional = true + attrFormat.Computed = true + attrFormat.Default = stringdefault.StaticString(formatTypeDefault) } if alongConfiguration { - attrFormat.Validators = []validator.String{ - stringvalidator.ConflictsWith(path.MatchRoot("configuration")), - } + attrFormat.Validators = append(attrFormat.Validators, stringvalidator.ConflictsWith(path.MatchRoot("configuration"))) } attributes["format"] = attrFormat attrDefinition := schema.MapNestedAttribute{} - attrDefinition.MarkdownDescription = fmt.Sprintf("Definition parts. Accepted path keys: %s. Read more about [%s definition part paths](%s).", utils.ConvertStringSlicesToString(definitionPathKeys, true, false), name, definitionPathDocsURL) + attrDefinition.MarkdownDescription = fmt.Sprintf("Definition parts. Read more about [%s definition part paths](%s). Accepted path keys: %s", name, definitionPathDocsURL, definitionFormatsDocs) attrDefinition.CustomType = supertypes.NewMapNestedObjectTypeOf[resourceFabricItemDefinitionPartModel](ctx) attrDefinition.Validators = definitionPathKeysValidator attrDefinition.NestedObject = getResourceFabricItemDefinitionPartSchema(ctx) diff --git a/internal/provider/utils/values.go b/internal/provider/utils/values.go index cf6877df..5fe05cad 100644 --- a/internal/provider/utils/values.go +++ b/internal/provider/utils/values.go @@ -16,10 +16,10 @@ import ( func GetValueOrFileValue(attValue, attFile string, value, file types.String) (string, error) { valueResult := value.ValueString() - if path := file.ValueString(); path != "" { - fileRaw, err := os.ReadFile(path) + if p := file.ValueString(); p != "" { + fileRaw, err := os.ReadFile(p) if err != nil { - return "", fmt.Errorf("reading '%s' from file %q: %w", attFile, path, err) + return "", fmt.Errorf("reading '%s' from file %q: %w", attFile, p, err) } fileResult := strings.TrimSpace(string(fileRaw)) @@ -36,10 +36,10 @@ func GetValueOrFileValue(attValue, attFile string, value, file types.String) (st func GetCertOrFileCert(attValue, attFile string, value, file types.String) (string, error) { valueResult := strings.TrimSpace(value.ValueString()) - if path := file.ValueString(); path != "" { - b64, err := auth.ConvertFileToBase64(path) + if p := file.ValueString(); p != "" { + b64, err := auth.ConvertFileToBase64(p) if err != nil { - return "", fmt.Errorf("reading '%s' from file %q: %w", attFile, path, err) + return "", fmt.Errorf("reading '%s' from file %q: %w", attFile, p, err) } fileResult := strings.TrimSpace(b64) diff --git a/internal/services/datapipeline/base.go b/internal/services/datapipeline/base.go index 89ec5804..61a906c5 100644 --- a/internal/services/datapipeline/base.go +++ b/internal/services/datapipeline/base.go @@ -7,6 +7,7 @@ import ( fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" "github.com/microsoft/terraform-provider-fabric/internal/common" + "github.com/microsoft/terraform-provider-fabric/internal/pkg/fabricitem" ) const ( @@ -21,4 +22,10 @@ const ( ItemDefinitionPathDocsURL = "https://learn.microsoft.com/fabric/data-factory/pipeline-rest-api" ) -var ItemDefinitionPaths = []string{"pipeline-content.json"} //nolint:gochecknoglobals +var itemDefinitionFormats = []fabricitem.DefinitionFormat{ //nolint:gochecknoglobals + { + Type: fabricitem.DefinitionFormatDefault, + API: "", + Paths: []string{"pipeline-content.json"}, + }, +} diff --git a/internal/services/datapipeline/resource_data_pipeline.go b/internal/services/datapipeline/resource_data_pipeline.go index 2730140b..78b4efff 100644 --- a/internal/services/datapipeline/resource_data_pipeline.go +++ b/internal/services/datapipeline/resource_data_pipeline.go @@ -23,16 +23,15 @@ func NewResourceDataPipeline() resource.Resource { ItemDocsSPNSupport, DisplayNameMaxLength: 123, DescriptionMaxLength: 256, - FormatTypeDefault: "", - FormatTypes: []string{}, + FormatTypeDefault: fabricitem.DefinitionFormatDefault, DefinitionPathDocsURL: ItemDefinitionPathDocsURL, - DefinitionPathKeys: ItemDefinitionPaths, DefinitionPathKeysValidator: []validator.Map{ mapvalidator.SizeAtMost(1), - mapvalidator.KeysAre(stringvalidator.OneOf(ItemDefinitionPaths...)), + mapvalidator.KeysAre(stringvalidator.OneOf(fabricitem.GetDefinitionFormatPaths(itemDefinitionFormats, fabricitem.DefinitionFormatDefault)...)), }, DefinitionRequired: false, DefinitionEmpty: ItemDefinitionEmpty, + DefinitionFormats: itemDefinitionFormats, } return fabricitem.NewResourceFabricItemDefinition(config) diff --git a/internal/services/eventhouse/base.go b/internal/services/eventhouse/base.go index 8d30857e..6934e326 100644 --- a/internal/services/eventhouse/base.go +++ b/internal/services/eventhouse/base.go @@ -7,6 +7,7 @@ import ( fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" "github.com/microsoft/terraform-provider-fabric/internal/common" + "github.com/microsoft/terraform-provider-fabric/internal/pkg/fabricitem" ) const ( @@ -17,12 +18,15 @@ const ( ItemType = fabcore.ItemTypeEventhouse ItemDocsSPNSupport = common.DocsSPNSupported ItemDocsURL = "https://learn.microsoft.com/fabric/real-time-intelligence/eventhouse" - ItemFormatTypeDefault = "" + ItemFormatTypeDefault = fabricitem.DefinitionFormatDefault ItemDefinitionEmpty = `{}` ItemDefinitionPathDocsURL = "https://learn.microsoft.com/rest/api/fabric/articles/item-management/definitions/eventhouse-definition" ) -var ( - ItemFormatTypes = []string{} //nolint:gochecknoglobals - ItemDefinitionPaths = []string{"EventhouseProperties.json"} //nolint:gochecknoglobals -) +var itemDefinitionFormats = []fabricitem.DefinitionFormat{ //nolint:gochecknoglobals + { + Type: fabricitem.DefinitionFormatDefault, + API: "", + Paths: []string{"EventhouseProperties.json"}, + }, +} diff --git a/internal/services/eventhouse/data_eventhouse.go b/internal/services/eventhouse/data_eventhouse.go index 78e43261..75a258c0 100644 --- a/internal/services/eventhouse/data_eventhouse.go +++ b/internal/services/eventhouse/data_eventhouse.go @@ -79,8 +79,7 @@ func NewDataSourceEventhouse(ctx context.Context) datasource.DataSource { ItemDocsSPNSupport, IsDisplayNameUnique: true, FormatTypeDefault: ItemFormatTypeDefault, - FormatTypes: ItemFormatTypes, - DefinitionPathKeys: ItemDefinitionPaths, + DefinitionFormats: itemDefinitionFormats, }, PropertiesAttributes: getDataSourceEventhousePropertiesAttributes(ctx), PropertiesSetter: propertiesSetter, diff --git a/internal/services/eventhouse/resource_eventhouse.go b/internal/services/eventhouse/resource_eventhouse.go index 91ad8da0..4ccec754 100644 --- a/internal/services/eventhouse/resource_eventhouse.go +++ b/internal/services/eventhouse/resource_eventhouse.go @@ -7,7 +7,6 @@ import ( "context" "github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/schema/validator" @@ -62,15 +61,14 @@ func NewResourceEventhouse(ctx context.Context) resource.Resource { DisplayNameMaxLength: 123, DescriptionMaxLength: 256, FormatTypeDefault: ItemFormatTypeDefault, - FormatTypes: ItemFormatTypes, DefinitionPathDocsURL: ItemDefinitionPathDocsURL, - DefinitionPathKeys: ItemDefinitionPaths, DefinitionPathKeysValidator: []validator.Map{ mapvalidator.SizeAtMost(1), - mapvalidator.KeysAre(stringvalidator.OneOf(ItemDefinitionPaths...)), + mapvalidator.KeysAre(fabricitem.DefinitionPathKeysValidator(itemDefinitionFormats)...), }, DefinitionRequired: false, DefinitionEmpty: ItemDefinitionEmpty, + DefinitionFormats: itemDefinitionFormats, }, PropertiesAttributes: getResourceEventhousePropertiesAttributes(ctx), PropertiesSetter: propertiesSetter, diff --git a/internal/services/kqldatabase/base.go b/internal/services/kqldatabase/base.go index e54e5de9..c82c7a7b 100644 --- a/internal/services/kqldatabase/base.go +++ b/internal/services/kqldatabase/base.go @@ -7,6 +7,7 @@ import ( fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" "github.com/microsoft/terraform-provider-fabric/internal/common" + "github.com/microsoft/terraform-provider-fabric/internal/pkg/fabricitem" ) const ( @@ -17,11 +18,14 @@ const ( ItemType = fabcore.ItemTypeKQLDatabase ItemDocsSPNSupport = common.DocsSPNSupported ItemDocsURL = "https://learn.microsoft.com/fabric/real-time-intelligence/create-database" - ItemFormatTypeDefault = "" + ItemFormatTypeDefault = fabricitem.DefinitionFormatDefault ItemDefinitionPathDocsURL = "https://learn.microsoft.com/rest/api/fabric/articles/item-management/definitions/kql-database-definition" ) -var ( - ItemFormatTypes = []string{""} //nolint:gochecknoglobals - ItemDefinitionPaths = []string{"DatabaseProperties.json", "DatabaseSchema.kql"} //nolint:gochecknoglobals -) +// var itemDefinitionFormats = []fabricitem.DefinitionFormat{ //nolint:gochecknoglobals +// { +// Type: fabricitem.DefinitionFormatDefault, +// API: "", +// Paths: []string{"DatabaseProperties.json", "DatabaseSchema.kql"}, +// }, +// } diff --git a/internal/services/notebook/base.go b/internal/services/notebook/base.go index 881e382d..a771ffaf 100644 --- a/internal/services/notebook/base.go +++ b/internal/services/notebook/base.go @@ -7,22 +7,31 @@ import ( fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" "github.com/microsoft/terraform-provider-fabric/internal/common" + "github.com/microsoft/terraform-provider-fabric/internal/pkg/fabricitem" ) const ( - ItemName = "Notebook" - ItemTFName = "notebook" - ItemsName = "Notebooks" - ItemsTFName = "notebooks" - ItemType = fabcore.ItemTypeNotebook - ItemDocsSPNSupport = common.DocsSPNSupported - ItemDocsURL = "https://learn.microsoft.com/fabric/data-engineering/how-to-use-notebook" - ItemFormatTypeDefault = "ipynb" - ItemDefinitionEmptyIPYNB = `{"cells":[{"cell_type":"code","metadata":{},"source":["# Welcome to your notebook"]}],"metadata":{"language_info":{"name":"python"}},"nbformat":4,"nbformat_minor":5}` - ItemDefinitionPathDocsURL = "https://learn.microsoft.com/rest/api/fabric/articles/item-management/definitions/notebook-definition" + ItemName = "Notebook" + ItemTFName = "notebook" + ItemsName = "Notebooks" + ItemsTFName = "notebooks" + ItemType = fabcore.ItemTypeNotebook + ItemDocsSPNSupport = common.DocsSPNSupported + ItemDocsURL = "https://learn.microsoft.com/fabric/data-engineering/how-to-use-notebook" + ItemDefinitionFormatTypeDefault = "ipynb" + ItemDefinitionEmptyIPYNB = `{"cells":[{"cell_type":"code","metadata":{},"source":["# Welcome to your notebook"]}],"metadata":{"language_info":{"name":"python"}},"nbformat":4,"nbformat_minor":5}` + ItemDefinitionPathDocsURL = "https://learn.microsoft.com/rest/api/fabric/articles/item-management/definitions/notebook-definition" ) -var ( - ItemFormatTypes = []string{"ipynb"} //nolint:gochecknoglobals - ItemDefinitionPathsIPYNB = []string{"notebook-content.ipynb"} //nolint:gochecknoglobals -) +var itemDefinitionFormats = []fabricitem.DefinitionFormat{ //nolint:gochecknoglobals + { + Type: "ipynb", + API: "ipynb", + Paths: []string{"notebook-content.ipynb"}, + }, + { + Type: "py", + API: "", + Paths: []string{"notebook-content.py"}, + }, +} diff --git a/internal/services/notebook/data_notebook.go b/internal/services/notebook/data_notebook.go index d3c9e299..50907fb7 100644 --- a/internal/services/notebook/data_notebook.go +++ b/internal/services/notebook/data_notebook.go @@ -18,9 +18,8 @@ func NewDataSourceNotebook() datasource.DataSource { "Use this data source to fetch a [" + ItemName + "](" + ItemDocsURL + ").\n\n" + ItemDocsSPNSupport, IsDisplayNameUnique: true, - FormatTypeDefault: ItemFormatTypeDefault, - FormatTypes: ItemFormatTypes, - DefinitionPathKeys: ItemDefinitionPathsIPYNB, + FormatTypeDefault: ItemDefinitionFormatTypeDefault, + DefinitionFormats: itemDefinitionFormats, } return fabricitem.NewDataSourceFabricItemDefinition(config) diff --git a/internal/services/notebook/data_notebook_test.go b/internal/services/notebook/data_notebook_test.go index cd7a7816..3bd2700d 100644 --- a/internal/services/notebook/data_notebook_test.go +++ b/internal/services/notebook/data_notebook_test.go @@ -173,6 +173,7 @@ func TestAcc_NotebookDataSource(t *testing.T) { resource.TestCheckResourceAttr(testDataSourceItemFQN, "id", entityID), resource.TestCheckResourceAttr(testDataSourceItemFQN, "display_name", entityDisplayName), resource.TestCheckResourceAttr(testDataSourceItemFQN, "description", entityDescription), + resource.TestCheckNoResourceAttr(testDataSourceItemFQN, "definition"), ), }, // read by id - not found @@ -200,6 +201,7 @@ func TestAcc_NotebookDataSource(t *testing.T) { resource.TestCheckResourceAttr(testDataSourceItemFQN, "id", entityID), resource.TestCheckResourceAttr(testDataSourceItemFQN, "display_name", entityDisplayName), resource.TestCheckResourceAttr(testDataSourceItemFQN, "description", entityDescription), + resource.TestCheckNoResourceAttr(testDataSourceItemFQN, "definition"), ), }, // read by name - not found @@ -213,5 +215,55 @@ func TestAcc_NotebookDataSource(t *testing.T) { ), ExpectError: regexp.MustCompile(common.ErrorReadHeader), }, + // read by id with definition - default + { + Config: at.CompileConfig( + testDataSourceItemHeader, + map[string]any{ + "workspace_id": workspaceID, + "id": entityID, + "output_definition": true, + }, + ), + ExpectError: regexp.MustCompile("Invalid configuration for attribute format"), + }, + // read by id with definition - py + { + Config: at.CompileConfig( + testDataSourceItemHeader, + map[string]any{ + "workspace_id": workspaceID, + "id": entityID, + "output_definition": true, + "format": "py", + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testDataSourceItemFQN, "workspace_id", workspaceID), + resource.TestCheckResourceAttr(testDataSourceItemFQN, "id", entityID), + resource.TestCheckResourceAttr(testDataSourceItemFQN, "display_name", entityDisplayName), + resource.TestCheckResourceAttr(testDataSourceItemFQN, "description", entityDescription), + resource.TestCheckResourceAttrSet(testDataSourceItemFQN, "definition.notebook-content.py.content"), + ), + }, + // read by id with definition - ipynb + { + Config: at.CompileConfig( + testDataSourceItemHeader, + map[string]any{ + "workspace_id": workspaceID, + "id": entityID, + "output_definition": true, + "format": "ipynb", + }, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testDataSourceItemFQN, "workspace_id", workspaceID), + resource.TestCheckResourceAttr(testDataSourceItemFQN, "id", entityID), + resource.TestCheckResourceAttr(testDataSourceItemFQN, "display_name", entityDisplayName), + resource.TestCheckResourceAttr(testDataSourceItemFQN, "description", entityDescription), + resource.TestCheckResourceAttrSet(testDataSourceItemFQN, "definition.notebook-content.ipynb.content"), + ), + }, })) } diff --git a/internal/services/notebook/resource_notebook.go b/internal/services/notebook/resource_notebook.go index 48e72528..98fbc5b8 100644 --- a/internal/services/notebook/resource_notebook.go +++ b/internal/services/notebook/resource_notebook.go @@ -5,7 +5,6 @@ package notebook import ( "github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/schema/validator" @@ -23,16 +22,15 @@ func NewResourceNotebook() resource.Resource { ItemDocsSPNSupport, DisplayNameMaxLength: 123, DescriptionMaxLength: 256, - FormatTypeDefault: ItemFormatTypeDefault, - FormatTypes: ItemFormatTypes, + FormatTypeDefault: ItemDefinitionFormatTypeDefault, DefinitionPathDocsURL: ItemDefinitionPathDocsURL, - DefinitionPathKeys: ItemDefinitionPathsIPYNB, DefinitionPathKeysValidator: []validator.Map{ mapvalidator.SizeAtMost(1), - mapvalidator.KeysAre(stringvalidator.OneOf(ItemDefinitionPathsIPYNB...)), + mapvalidator.KeysAre(fabricitem.DefinitionPathKeysValidator(itemDefinitionFormats)...), }, DefinitionRequired: false, DefinitionEmpty: ItemDefinitionEmptyIPYNB, + DefinitionFormats: itemDefinitionFormats, } return fabricitem.NewResourceFabricItemDefinition(config) diff --git a/internal/services/notebook/resource_notebook_test.go b/internal/services/notebook/resource_notebook_test.go index 0839a038..32b1bd2f 100644 --- a/internal/services/notebook/resource_notebook_test.go +++ b/internal/services/notebook/resource_notebook_test.go @@ -28,12 +28,18 @@ var testHelperLocals = at.CompileLocalsConfig(map[string]any{ "path": testhelp.GetFixturesDirPath("notebook"), }) -var testHelperDefinition = map[string]any{ +var testHelperDefinitionIPYNB = map[string]any{ `"notebook-content.ipynb"`: map[string]any{ "source": "${local.path}/notebook.ipynb.tmpl", }, } +var testHelperDefinitionPY = map[string]any{ + `"notebook-content.py"`: map[string]any{ + "source": "${local.path}/notebook.py.tmpl", + }, +} + func TestUnit_NotebookResource_Attributes(t *testing.T) { resource.ParallelTest(t, testhelp.NewTestUnitCase(t, &testResourceItemFQN, fakes.FakeServer.ServerFactory, nil, []resource.TestStep{ // error - no attributes @@ -58,7 +64,8 @@ func TestUnit_NotebookResource_Attributes(t *testing.T) { map[string]any{ "workspace_id": "invalid uuid", "display_name": "test", - "definition": testHelperDefinition, + "format": "ipynb", + "definition": testHelperDefinitionIPYNB, }, )), ExpectError: regexp.MustCompile(customtypes.UUIDTypeErrorInvalidStringHeader), @@ -74,7 +81,8 @@ func TestUnit_NotebookResource_Attributes(t *testing.T) { "workspace_id": "00000000-0000-0000-0000-000000000000", "display_name": "test", "unexpected_attr": "test", - "definition": testHelperDefinition, + "format": "ipynb", + "definition": testHelperDefinitionIPYNB, }, )), ExpectError: regexp.MustCompile(`An argument named "unexpected_attr" is not expected here`), @@ -88,7 +96,8 @@ func TestUnit_NotebookResource_Attributes(t *testing.T) { testResourceItemHeader, map[string]any{ "display_name": "test", - "definition": testHelperDefinition, + "format": "ipynb", + "definition": testHelperDefinitionIPYNB, }, )), ExpectError: regexp.MustCompile(`The argument "workspace_id" is required, but no definition was found.`), @@ -102,7 +111,8 @@ func TestUnit_NotebookResource_Attributes(t *testing.T) { testResourceItemHeader, map[string]any{ "workspace_id": "00000000-0000-0000-0000-000000000000", - "definition": testHelperDefinition, + "format": "ipynb", + "definition": testHelperDefinitionIPYNB, }, )), ExpectError: regexp.MustCompile(`The argument "display_name" is required, but no definition was found.`), @@ -125,7 +135,8 @@ func TestUnit_NotebookResource_ImportState(t *testing.T) { map[string]any{ "workspace_id": *entity.WorkspaceID, "display_name": *entity.DisplayName, - "definition": testHelperDefinition, + "format": "ipynb", + "definition": testHelperDefinitionIPYNB, }, )) @@ -202,7 +213,8 @@ func TestUnit_NotebookResource_CRUD(t *testing.T) { map[string]any{ "workspace_id": *entityExist.WorkspaceID, "display_name": *entityExist.DisplayName, - "definition": testHelperDefinition, + "format": "ipynb", + "definition": testHelperDefinitionIPYNB, }, )), ExpectError: regexp.MustCompile(common.ErrorCreateHeader), @@ -217,7 +229,8 @@ func TestUnit_NotebookResource_CRUD(t *testing.T) { map[string]any{ "workspace_id": *entityBefore.WorkspaceID, "display_name": *entityBefore.DisplayName, - "definition": testHelperDefinition, + "format": "ipynb", + "definition": testHelperDefinitionIPYNB, }, )), Check: resource.ComposeAggregateTestCheckFunc( @@ -236,7 +249,8 @@ func TestUnit_NotebookResource_CRUD(t *testing.T) { "workspace_id": *entityBefore.WorkspaceID, "display_name": *entityAfter.DisplayName, "description": *entityAfter.Description, - "definition": testHelperDefinition, + "format": "ipynb", + "definition": testHelperDefinitionIPYNB, }, )), Check: resource.ComposeAggregateTestCheckFunc( @@ -268,7 +282,110 @@ func TestAcc_NotebookResource_CRUD(t *testing.T) { map[string]any{ "workspace_id": workspaceID, "display_name": entityCreateDisplayName, - "definition": testHelperDefinition, + }, + )), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testResourceItemFQN, "display_name", entityCreateDisplayName), + resource.TestCheckResourceAttr(testResourceItemFQN, "description", ""), + ), + }, + // Update and Read + { + ResourceName: testResourceItemFQN, + Config: at.JoinConfigs( + testHelperLocals, + at.CompileConfig( + testResourceItemHeader, + map[string]any{ + "workspace_id": workspaceID, + "display_name": entityUpdateDisplayName, + "description": entityUpdateDescription, + }, + )), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testResourceItemFQN, "display_name", entityUpdateDisplayName), + resource.TestCheckResourceAttr(testResourceItemFQN, "description", entityUpdateDescription), + ), + }, + }, + )) +} + +func TestAcc_NotebookDefinitionIPYNBResource_CRUD(t *testing.T) { + workspace := testhelp.WellKnown()["Workspace"].(map[string]any) + workspaceID := workspace["id"].(string) + + entityCreateDisplayName := testhelp.RandomName() + entityUpdateDisplayName := testhelp.RandomName() + entityUpdateDescription := testhelp.RandomName() + + resource.Test(t, testhelp.NewTestAccCase(t, &testResourceItemFQN, nil, []resource.TestStep{ + // Create and Read + { + ResourceName: testResourceItemFQN, + Config: at.JoinConfigs( + testHelperLocals, + at.CompileConfig( + testResourceItemHeader, + map[string]any{ + "workspace_id": workspaceID, + "display_name": entityCreateDisplayName, + "format": "ipynb", + "definition": testHelperDefinitionIPYNB, + }, + )), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testResourceItemFQN, "display_name", entityCreateDisplayName), + resource.TestCheckResourceAttr(testResourceItemFQN, "description", ""), + resource.TestCheckResourceAttr(testResourceItemFQN, "definition_update_enabled", "true"), + ), + }, + // Update and Read + { + ResourceName: testResourceItemFQN, + Config: at.JoinConfigs( + testHelperLocals, + at.CompileConfig( + testResourceItemHeader, + map[string]any{ + "workspace_id": workspaceID, + "display_name": entityUpdateDisplayName, + "description": entityUpdateDescription, + "format": "ipynb", + "definition": testHelperDefinitionIPYNB, + }, + )), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(testResourceItemFQN, "display_name", entityUpdateDisplayName), + resource.TestCheckResourceAttr(testResourceItemFQN, "description", entityUpdateDescription), + resource.TestCheckResourceAttr(testResourceItemFQN, "definition_update_enabled", "true"), + ), + }, + }, + )) +} + +func TestAcc_NotebookDefinitionPYResource_CRUD(t *testing.T) { + workspace := testhelp.WellKnown()["Workspace"].(map[string]any) + workspaceID := workspace["id"].(string) + + entityCreateDisplayName := testhelp.RandomName() + entityUpdateDisplayName := testhelp.RandomName() + entityUpdateDescription := testhelp.RandomName() + + resource.Test(t, testhelp.NewTestAccCase(t, &testResourceItemFQN, nil, []resource.TestStep{ + // Create and Read + { + ResourceName: testResourceItemFQN, + Config: at.JoinConfigs( + testHelperLocals, + at.CompileConfig( + testResourceItemHeader, + map[string]any{ + "workspace_id": workspaceID, + "display_name": entityCreateDisplayName, + "format": "py", + "definition": testHelperDefinitionPY, }, )), Check: resource.ComposeAggregateTestCheckFunc( @@ -288,7 +405,8 @@ func TestAcc_NotebookResource_CRUD(t *testing.T) { "workspace_id": workspaceID, "display_name": entityUpdateDisplayName, "description": entityUpdateDescription, - "definition": testHelperDefinition, + "format": "py", + "definition": testHelperDefinitionPY, }, )), Check: resource.ComposeAggregateTestCheckFunc( diff --git a/internal/services/report/base.go b/internal/services/report/base.go index 84e3c701..bebbe73b 100644 --- a/internal/services/report/base.go +++ b/internal/services/report/base.go @@ -7,6 +7,7 @@ import ( fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" "github.com/microsoft/terraform-provider-fabric/internal/common" + "github.com/microsoft/terraform-provider-fabric/internal/pkg/fabricitem" ) const ( @@ -21,7 +22,15 @@ const ( ItemDefinitionPathDocsURL = "https://learn.microsoft.com/rest/api/fabric/articles/item-management/definitions/report-definition" ) -var ( - ItemFormatTypes = []string{"PBIR-Legacy"} //nolint:gochecknoglobals - ItemDefinitionPathsPBIRLegacy = []string{"report.json", "definition.pbir", "StaticResources/RegisteredResources/*", "StaticResources/SharedResources/*"} //nolint:gochecknoglobals -) +var itemDefinitionFormats = []fabricitem.DefinitionFormat{ //nolint:gochecknoglobals + { + Type: "PBIR-Legacy", + API: "PBIR-Legacy", + Paths: []string{"report.json", "definition.pbir", "StaticResources/RegisteredResources/*", "StaticResources/SharedResources/*"}, + }, + { + Type: "PBIR", + API: "PBIR", + Paths: []string{"definition/report.json", "definition/version.json", "definition.pbir", "definition/pages/*.json", "StaticResources/RegisteredResources/*", "StaticResources/SharedResources/*"}, + }, +} diff --git a/internal/services/report/data_report.go b/internal/services/report/data_report.go index fd6e4002..cf359c73 100644 --- a/internal/services/report/data_report.go +++ b/internal/services/report/data_report.go @@ -19,8 +19,7 @@ func NewDataSourceReport() datasource.DataSource { ItemDocsSPNSupport, IsDisplayNameUnique: false, FormatTypeDefault: ItemFormatTypeDefault, - FormatTypes: ItemFormatTypes, - DefinitionPathKeys: ItemDefinitionPathsPBIRLegacy, + DefinitionFormats: itemDefinitionFormats, } return fabricitem.NewDataSourceFabricItemDefinition(config) diff --git a/internal/services/report/data_report_test.go b/internal/services/report/data_report_test.go index 3a2b32c2..791d89b5 100644 --- a/internal/services/report/data_report_test.go +++ b/internal/services/report/data_report_test.go @@ -156,6 +156,7 @@ func TestAcc_ReportDataSource(t *testing.T) { map[string]any{ "workspace_id": workspaceID, "id": entityID, + "format": "PBIR-Legacy", "output_definition": true, }, ), diff --git a/internal/services/report/resource_report.go b/internal/services/report/resource_report.go index 37935f31..e1548674 100644 --- a/internal/services/report/resource_report.go +++ b/internal/services/report/resource_report.go @@ -4,13 +4,14 @@ package report import ( - "regexp" - "github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + fwvalidators "github.com/microsoft/terraform-provider-fabric/internal/framework/validators" "github.com/microsoft/terraform-provider-fabric/internal/pkg/fabricitem" "github.com/microsoft/terraform-provider-fabric/internal/pkg/utils" ) @@ -27,20 +28,27 @@ func NewResourceReport() resource.Resource { DisplayNameMaxLength: 123, DescriptionMaxLength: 256, FormatTypeDefault: ItemFormatTypeDefault, - FormatTypes: ItemFormatTypes, DefinitionPathDocsURL: ItemDefinitionPathDocsURL, - DefinitionPathKeys: ItemDefinitionPathsPBIRLegacy, DefinitionPathKeysValidator: []validator.Map{ mapvalidator.SizeAtLeast(3), mapvalidator.KeysAre( - stringvalidator.RegexMatches( - regexp.MustCompile(`^(report\.json|definition\.pbir|StaticResources/RegisteredResources/.*|StaticResources/SharedResources/.*)$`), - "Definition path must match one of the following: "+utils.ConvertStringSlicesToString(ItemDefinitionPathsPBIRLegacy, true, false), + fwvalidators.RegexpIfAttributeIsOneOf( + path.MatchRoot("format"), + []attr.Value{types.StringValue("PBIR-Legacy")}, + fabricitem.GetDefinitionFormatPaths(itemDefinitionFormats, "PBIR-Legacy"), + "Definition path must match one of the following: "+utils.ConvertStringSlicesToString(fabricitem.GetDefinitionFormatPaths(itemDefinitionFormats, "PBIR-Legacy"), true, false), + ), + fwvalidators.RegexpIfAttributeIsOneOf( + path.MatchRoot("format"), + []attr.Value{types.StringValue("PBIR")}, + fabricitem.GetDefinitionFormatPaths(itemDefinitionFormats, "PBIR"), + "Definition path must match one of the following: "+utils.ConvertStringSlicesToString(fabricitem.GetDefinitionFormatPaths(itemDefinitionFormats, "PBIR"), true, false), ), ), }, DefinitionRequired: true, DefinitionEmpty: "", + DefinitionFormats: itemDefinitionFormats, } return fabricitem.NewResourceFabricItemDefinition(config) diff --git a/internal/services/report/resource_report_test.go b/internal/services/report/resource_report_test.go index ff56b02b..328193b5 100644 --- a/internal/services/report/resource_report_test.go +++ b/internal/services/report/resource_report_test.go @@ -68,6 +68,7 @@ func TestUnit_ReportResource_Attributes(t *testing.T) { map[string]any{ "workspace_id": "invalid uuid", "display_name": "test", + "format": "PBIR-Legacy", "definition": testHelperDefinition, }, )), @@ -84,6 +85,7 @@ func TestUnit_ReportResource_Attributes(t *testing.T) { "workspace_id": "00000000-0000-0000-0000-000000000000", "display_name": "test", "unexpected_attr": "test", + "format": "PBIR-Legacy", "definition": testHelperDefinition, }, )), @@ -98,6 +100,7 @@ func TestUnit_ReportResource_Attributes(t *testing.T) { testResourceItemHeader, map[string]any{ "display_name": "test", + "format": "PBIR-Legacy", "definition": testHelperDefinition, }, )), @@ -112,6 +115,7 @@ func TestUnit_ReportResource_Attributes(t *testing.T) { testResourceItemHeader, map[string]any{ "workspace_id": "00000000-0000-0000-0000-000000000000", + "format": "PBIR-Legacy", "definition": testHelperDefinition, }, )), @@ -149,6 +153,7 @@ func TestUnit_ReportResource_ImportState(t *testing.T) { map[string]any{ "workspace_id": *entity.WorkspaceID, "display_name": *entity.DisplayName, + "format": "PBIR-Legacy", "definition": testHelperDefinition, }, )) @@ -231,6 +236,7 @@ func TestUnit_ReportResource_CRUD(t *testing.T) { map[string]any{ "workspace_id": *entityExist.WorkspaceID, "display_name": *entityExist.DisplayName, + "format": "PBIR-Legacy", "definition": testHelperDefinition, }, )), @@ -246,6 +252,7 @@ func TestUnit_ReportResource_CRUD(t *testing.T) { map[string]any{ "workspace_id": *entityBefore.WorkspaceID, "display_name": *entityBefore.DisplayName, + "format": "PBIR-Legacy", "definition": testHelperDefinition, }, )), @@ -265,6 +272,7 @@ func TestUnit_ReportResource_CRUD(t *testing.T) { map[string]any{ "workspace_id": *entityBefore.WorkspaceID, "display_name": *entityAfter.DisplayName, + "format": "PBIR-Legacy", "definition": testHelperDefinition, }, )), @@ -300,6 +308,7 @@ func TestAcc_ReportResource_CRUD(t *testing.T) { map[string]any{ "workspace_id": workspaceID, "display_name": entityCreateDisplayName, + "format": "PBIR-Legacy", "definition": testHelperDefinition, }, )), @@ -319,6 +328,7 @@ func TestAcc_ReportResource_CRUD(t *testing.T) { map[string]any{ "workspace_id": workspaceID, "display_name": entityUpdateDisplayName, + "format": "PBIR-Legacy", "definition": testHelperDefinition, }, )), diff --git a/internal/services/semanticmodel/base.go b/internal/services/semanticmodel/base.go index b28d72dd..cecd3143 100644 --- a/internal/services/semanticmodel/base.go +++ b/internal/services/semanticmodel/base.go @@ -7,6 +7,7 @@ import ( fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" "github.com/microsoft/terraform-provider-fabric/internal/common" + "github.com/microsoft/terraform-provider-fabric/internal/pkg/fabricitem" ) const ( @@ -21,7 +22,15 @@ const ( ItemDefinitionPathDocsURL = "https://learn.microsoft.com/rest/api/fabric/articles/item-management/definitions/semantic-model-definition" ) -var ( - ItemFormatTypes = []string{"TMSL"} //nolint:gochecknoglobals - ItemDefinitionPathsTMSL = []string{"model.bim", "definition.pbism", "diagramLayout.json"} //nolint:gochecknoglobals -) +var itemDefinitionFormats = []fabricitem.DefinitionFormat{ //nolint:gochecknoglobals + { + Type: "TMSL", + API: "TMSL", + Paths: []string{"model.bim", "definition.pbism", "diagramLayp.json"}, + }, + { + Type: "TMDL", + API: "TMDL", + Paths: []string{"definition/database.tmdl", "definition/model.tmdl", "definition.pbism", "diagramLayp.json", "definition/tables/*.tmdl"}, + }, +} diff --git a/internal/services/semanticmodel/data_semantic_model.go b/internal/services/semanticmodel/data_semantic_model.go index b0ed23bb..fcd7bbdd 100644 --- a/internal/services/semanticmodel/data_semantic_model.go +++ b/internal/services/semanticmodel/data_semantic_model.go @@ -19,8 +19,7 @@ func NewDataSourceSemanticModel() datasource.DataSource { ItemDocsSPNSupport, IsDisplayNameUnique: false, FormatTypeDefault: ItemFormatTypeDefault, - FormatTypes: ItemFormatTypes, - DefinitionPathKeys: ItemDefinitionPathsTMSL, + DefinitionFormats: itemDefinitionFormats, } return fabricitem.NewDataSourceFabricItemDefinition(config) diff --git a/internal/services/semanticmodel/data_semantic_model_test.go b/internal/services/semanticmodel/data_semantic_model_test.go index 302f8865..0ea4792e 100644 --- a/internal/services/semanticmodel/data_semantic_model_test.go +++ b/internal/services/semanticmodel/data_semantic_model_test.go @@ -156,6 +156,7 @@ func TestAcc_SemanticModelDataSource(t *testing.T) { map[string]any{ "workspace_id": workspaceID, "id": entityID, + "format": "TMSL", "output_definition": true, }, ), diff --git a/internal/services/semanticmodel/resource_semantic_model.go b/internal/services/semanticmodel/resource_semantic_model.go index 8cfcfafa..022608c1 100644 --- a/internal/services/semanticmodel/resource_semantic_model.go +++ b/internal/services/semanticmodel/resource_semantic_model.go @@ -5,11 +5,15 @@ package semanticmodel import ( "github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + fwvalidators "github.com/microsoft/terraform-provider-fabric/internal/framework/validators" "github.com/microsoft/terraform-provider-fabric/internal/pkg/fabricitem" + "github.com/microsoft/terraform-provider-fabric/internal/pkg/utils" ) func NewResourceSemanticModel() resource.Resource { @@ -24,15 +28,27 @@ func NewResourceSemanticModel() resource.Resource { DisplayNameMaxLength: 123, DescriptionMaxLength: 256, FormatTypeDefault: ItemFormatTypeDefault, - FormatTypes: ItemFormatTypes, DefinitionPathDocsURL: ItemDefinitionPathDocsURL, - DefinitionPathKeys: ItemDefinitionPathsTMSL, DefinitionPathKeysValidator: []validator.Map{ mapvalidator.SizeAtLeast(2), - mapvalidator.KeysAre(stringvalidator.OneOf(ItemDefinitionPathsTMSL...)), + mapvalidator.KeysAre( + fwvalidators.RegexpIfAttributeIsOneOf( + path.MatchRoot("format"), + []attr.Value{types.StringValue("TMSL")}, + fabricitem.GetDefinitionFormatPaths(itemDefinitionFormats, "TMSL"), + "Definition path must match one of the following: "+utils.ConvertStringSlicesToString(fabricitem.GetDefinitionFormatPaths(itemDefinitionFormats, "TMSL"), true, false), + ), + fwvalidators.RegexpIfAttributeIsOneOf( + path.MatchRoot("format"), + []attr.Value{types.StringValue("TMDL")}, + fabricitem.GetDefinitionFormatPaths(itemDefinitionFormats, "TMDL"), + "Definition path must match one of the following: "+utils.ConvertStringSlicesToString(fabricitem.GetDefinitionFormatPaths(itemDefinitionFormats, "TMDL"), true, false), + ), + ), }, DefinitionRequired: true, DefinitionEmpty: "", + DefinitionFormats: itemDefinitionFormats, } return fabricitem.NewResourceFabricItemDefinition(config) diff --git a/internal/services/semanticmodel/resource_semantic_model_test.go b/internal/services/semanticmodel/resource_semantic_model_test.go index 40af2ada..d35a51cb 100644 --- a/internal/services/semanticmodel/resource_semantic_model_test.go +++ b/internal/services/semanticmodel/resource_semantic_model_test.go @@ -64,6 +64,7 @@ func TestUnit_SemanticModelResource_Attributes(t *testing.T) { map[string]any{ "workspace_id": "invalid uuid", "display_name": "test", + "format": "TMSL", "definition": testHelperDefinition, }, )), @@ -80,6 +81,7 @@ func TestUnit_SemanticModelResource_Attributes(t *testing.T) { "workspace_id": "00000000-0000-0000-0000-000000000000", "display_name": "test", "unexpected_attr": "test", + "format": "TMSL", "definition": testHelperDefinition, }, )), @@ -94,6 +96,7 @@ func TestUnit_SemanticModelResource_Attributes(t *testing.T) { testResourceItemHeader, map[string]any{ "display_name": "test", + "format": "TMSL", "definition": testHelperDefinition, }, )), @@ -108,6 +111,7 @@ func TestUnit_SemanticModelResource_Attributes(t *testing.T) { testResourceItemHeader, map[string]any{ "workspace_id": "00000000-0000-0000-0000-000000000000", + "format": "TMSL", "definition": testHelperDefinition, }, )), @@ -145,6 +149,7 @@ func TestUnit_SemanticModelResource_ImportState(t *testing.T) { map[string]any{ "workspace_id": *entity.WorkspaceID, "display_name": *entity.DisplayName, + "format": "TMSL", "definition": testHelperDefinition, }, )) @@ -222,6 +227,7 @@ func TestUnit_SemanticModelResource_CRUD(t *testing.T) { map[string]any{ "workspace_id": *entityExist.WorkspaceID, "display_name": *entityExist.DisplayName, + "format": "TMSL", "definition": testHelperDefinition, }, )), @@ -237,6 +243,7 @@ func TestUnit_SemanticModelResource_CRUD(t *testing.T) { map[string]any{ "workspace_id": *entityBefore.WorkspaceID, "display_name": *entityBefore.DisplayName, + "format": "TMSL", "definition": testHelperDefinition, }, )), @@ -256,6 +263,7 @@ func TestUnit_SemanticModelResource_CRUD(t *testing.T) { map[string]any{ "workspace_id": *entityBefore.WorkspaceID, "display_name": *entityAfter.DisplayName, + "format": "TMSL", "definition": testHelperDefinition, }, )), @@ -286,6 +294,7 @@ func TestAcc_SemanticModelResource_CRUD(t *testing.T) { map[string]any{ "workspace_id": workspaceID, "display_name": entityCreateDisplayName, + "format": "TMSL", "definition": testHelperDefinition, }, )), @@ -305,6 +314,7 @@ func TestAcc_SemanticModelResource_CRUD(t *testing.T) { map[string]any{ "workspace_id": workspaceID, "display_name": entityUpdateDisplayName, + "format": "TMSL", "definition": testHelperDefinition, }, )), diff --git a/internal/services/sparkjobdefinition/base.go b/internal/services/sparkjobdefinition/base.go index 5a9ed42a..3ac1e2db 100644 --- a/internal/services/sparkjobdefinition/base.go +++ b/internal/services/sparkjobdefinition/base.go @@ -7,6 +7,7 @@ import ( fabcore "github.com/microsoft/fabric-sdk-go/fabric/core" "github.com/microsoft/terraform-provider-fabric/internal/common" + "github.com/microsoft/terraform-provider-fabric/internal/pkg/fabricitem" ) const ( @@ -22,7 +23,10 @@ const ( ItemDefinitionPathDocsURL = "https://learn.microsoft.com/rest/api/fabric/articles/item-management/definitions/spark-job-definition" ) -var ( - ItemFormatTypes = []string{"SparkJobDefinitionV1"} //nolint:gochecknoglobals - ItemDefinitionPaths = []string{"SparkJobDefinitionV1.json"} //nolint:gochecknoglobals -) +var itemDefinitionFormats = []fabricitem.DefinitionFormat{ //nolint:gochecknoglobals + { + Type: "SparkJobDefinitionV1", + API: "SparkJobDefinitionV1", + Paths: []string{"SparkJobDefinitionV1.json"}, + }, +} diff --git a/internal/services/sparkjobdefinition/data_spark_job_definition.go b/internal/services/sparkjobdefinition/data_spark_job_definition.go index 386396e5..e8125332 100644 --- a/internal/services/sparkjobdefinition/data_spark_job_definition.go +++ b/internal/services/sparkjobdefinition/data_spark_job_definition.go @@ -79,8 +79,7 @@ func NewDataSourceSparkJobDefinition() datasource.DataSource { ItemDocsSPNSupport, IsDisplayNameUnique: true, FormatTypeDefault: ItemFormatTypeDefault, - FormatTypes: ItemFormatTypes, - DefinitionPathKeys: ItemDefinitionPaths, + DefinitionFormats: itemDefinitionFormats, }, PropertiesAttributes: getDataSourceSparkJobDefinitionPropertiesAttributes(), PropertiesSetter: propertiesSetter, diff --git a/internal/services/sparkjobdefinition/data_spark_job_definition_test.go b/internal/services/sparkjobdefinition/data_spark_job_definition_test.go index cb6d1700..d2e952c0 100644 --- a/internal/services/sparkjobdefinition/data_spark_job_definition_test.go +++ b/internal/services/sparkjobdefinition/data_spark_job_definition_test.go @@ -227,6 +227,7 @@ func TestAcc_SparkJobDefinitionDataSource(t *testing.T) { map[string]any{ "workspace_id": workspaceID, "id": entityID, + "format": "SparkJobDefinitionV1", "output_definition": true, }, ), diff --git a/internal/services/sparkjobdefinition/resource_spark_job_definition.go b/internal/services/sparkjobdefinition/resource_spark_job_definition.go index 3c22c21c..2836dfe4 100644 --- a/internal/services/sparkjobdefinition/resource_spark_job_definition.go +++ b/internal/services/sparkjobdefinition/resource_spark_job_definition.go @@ -7,7 +7,6 @@ import ( "context" "github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/schema/validator" @@ -62,15 +61,14 @@ func NewResourceSparkJobDefinition() resource.Resource { DisplayNameMaxLength: 123, DescriptionMaxLength: 256, FormatTypeDefault: ItemFormatTypeDefault, - FormatTypes: ItemFormatTypes, DefinitionPathDocsURL: ItemDefinitionPathDocsURL, - DefinitionPathKeys: ItemDefinitionPaths, DefinitionPathKeysValidator: []validator.Map{ mapvalidator.SizeAtMost(1), - mapvalidator.KeysAre(stringvalidator.OneOf(ItemDefinitionPaths...)), + mapvalidator.KeysAre(fabricitem.DefinitionPathKeysValidator(itemDefinitionFormats)...), }, DefinitionRequired: false, DefinitionEmpty: ItemDefinitionEmpty, + DefinitionFormats: itemDefinitionFormats, }, PropertiesAttributes: getResourceSparkJobDefinitionPropertiesAttributes(), PropertiesSetter: propertiesSetter, diff --git a/internal/services/sparkjobdefinition/resource_spark_job_definition_test.go b/internal/services/sparkjobdefinition/resource_spark_job_definition_test.go index 057529af..75c7ff0e 100644 --- a/internal/services/sparkjobdefinition/resource_spark_job_definition_test.go +++ b/internal/services/sparkjobdefinition/resource_spark_job_definition_test.go @@ -58,6 +58,7 @@ func TestUnit_SparkJobDefinitionResource_Attributes(t *testing.T) { map[string]any{ "workspace_id": "invalid uuid", "display_name": "test", + "format": "SparkJobDefinitionV1", "definition": testHelperDefinition, }, )), @@ -74,6 +75,7 @@ func TestUnit_SparkJobDefinitionResource_Attributes(t *testing.T) { "workspace_id": "00000000-0000-0000-0000-000000000000", "display_name": "test", "unexpected_attr": "test", + "format": "SparkJobDefinitionV1", "definition": testHelperDefinition, }, )), @@ -88,6 +90,7 @@ func TestUnit_SparkJobDefinitionResource_Attributes(t *testing.T) { testResourceItemHeader, map[string]any{ "display_name": "test", + "format": "SparkJobDefinitionV1", "definition": testHelperDefinition, }, )), @@ -102,6 +105,7 @@ func TestUnit_SparkJobDefinitionResource_Attributes(t *testing.T) { testResourceItemHeader, map[string]any{ "workspace_id": "00000000-0000-0000-0000-000000000000", + "format": "SparkJobDefinitionV1", "definition": testHelperDefinition, }, )), @@ -125,6 +129,7 @@ func TestUnit_SparkJobDefinitionResource_ImportState(t *testing.T) { map[string]any{ "workspace_id": *entity.WorkspaceID, "display_name": *entity.DisplayName, + "format": "SparkJobDefinitionV1", "definition": testHelperDefinition, }, )) @@ -202,6 +207,7 @@ func TestUnit_SparkJobDefinitionResource_CRUD(t *testing.T) { map[string]any{ "workspace_id": *entityExist.WorkspaceID, "display_name": *entityExist.DisplayName, + "format": "SparkJobDefinitionV1", "definition": testHelperDefinition, }, )), @@ -217,6 +223,7 @@ func TestUnit_SparkJobDefinitionResource_CRUD(t *testing.T) { map[string]any{ "workspace_id": *entityBefore.WorkspaceID, "display_name": *entityBefore.DisplayName, + "format": "SparkJobDefinitionV1", "definition": testHelperDefinition, }, )), @@ -236,6 +243,7 @@ func TestUnit_SparkJobDefinitionResource_CRUD(t *testing.T) { map[string]any{ "workspace_id": *entityBefore.WorkspaceID, "display_name": *entityAfter.DisplayName, + "format": "SparkJobDefinitionV1", "definition": testHelperDefinition, }, )), @@ -267,6 +275,7 @@ func TestAcc_SparkJobDefinitionResource_CRUD(t *testing.T) { map[string]any{ "workspace_id": workspaceID, "display_name": entityCreateDisplayName, + "format": "SparkJobDefinitionV1", "definition": testHelperDefinition, }, )), @@ -287,6 +296,7 @@ func TestAcc_SparkJobDefinitionResource_CRUD(t *testing.T) { map[string]any{ "workspace_id": workspaceID, "display_name": entityUpdateDisplayName, + "format": "SparkJobDefinitionV1", "definition": testHelperDefinition, }, )), diff --git a/internal/services/workspace/resource_workspace_git.go b/internal/services/workspace/resource_workspace_git.go index 871aeb08..ade465b8 100644 --- a/internal/services/workspace/resource_workspace_git.go +++ b/internal/services/workspace/resource_workspace_git.go @@ -315,7 +315,7 @@ func (r *resourceWorkspaceGit) Read(ctx context.Context, req resource.ReadReques return } - resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + resp.Diagnostics.Append(resp.State.Set(ctx, state)...) tflog.Debug(ctx, "READ", map[string]any{ "action": "end", diff --git a/internal/testhelp/fixtures/notebook/notebook.py.tmpl b/internal/testhelp/fixtures/notebook/notebook.py.tmpl new file mode 100644 index 00000000..6e270f50 --- /dev/null +++ b/internal/testhelp/fixtures/notebook/notebook.py.tmpl @@ -0,0 +1,22 @@ +# Fabric notebook source + +# METADATA ******************** + +# META { +# META "kernel_info": { +# META "name": "synapse_pyspark" +# META }, +# META "dependencies": {} +# META } + +# CELL ******************** + +# Welcome to your new notebook +# Type here in the cell editor to add code! + +# METADATA ******************** + +# META { +# META "language": "python", +# META "language_group": "synapse_pyspark" +# META }