Repository: databricks/terraform-provider-databricks
Pull request #4647: [FEATURE] Support Power BI task in Terraform
Author: login: alexguo-db
Created: 2025-04-15T02:50:09Z | Merged: 2025-04-16T18:09:27Z

## Changes
<!-- Summary of your changes that are easy to understand -->
- Support Power BI task in Terraform

## Tests
<!-- 
How is this tested? Please see the checklist below and also describe any other relevant tests 
-->

- [x] `make test` run locally
- [x] relevant change in `docs/` folder
- [ ] covered with integration tests in `internal/acceptance`
- [x] using Go SDK
- [ ] using TF Plugin Framework



Diff:
diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md
index 880cf8c743..5122c81902 100644
--- a/NEXT_CHANGELOG.md
+++ b/NEXT_CHANGELOG.md
@@ -4,6 +4,8 @@
 
 ### New Features and Improvements
 
+ * Add support for `power_bi_task` in jobs ([#4647](https://github.com/databricks/terraform-provider-databricks/pull/4647))
+
 ### Bug Fixes
 
 ### Documentation
diff --git a/docs/resources/job.md b/docs/resources/job.md
index 00b72b336c..989b21f209 100644
--- a/docs/resources/job.md
+++ b/docs/resources/job.md
@@ -122,6 +122,7 @@ This block describes individual tasks:
   * `for_each_task`
   * `notebook_task`
   * `pipeline_task`
+  * `power_bi_task`
   * `python_wheel_task`
   * `run_job_task`
   * `spark_jar_task`
@@ -200,6 +201,25 @@ You also need to include a `git_source` block to configure the repository that c
 
 -> The following configuration blocks are only supported inside a `task` block
 
+#### power_bi_task Configuration Block
+
+The `power_bi_task` triggers a Power BI semantic model update. 
+
+* `tables` (Required) (Array) The tables to be exported to Power BI. Block consists of following fields:
+  * `storage_mode` (Required) The Power BI storage mode of the table
+  * `catalog` (Required) The catalog name in Databricks
+  * `schema` (Required) The schema name in Databricks
+  * `name` (Optional) The table name in Databricks. If empty, all tables under the schema are selected.
+* `warehouse_id` (Required) The SQL warehouse ID to use as the Power BI data source
+* `power_bi_model` (Required) The semantic model to update. Block consists of following fields:
+  * `workspace_name` (Required) The name of the Power BI workspace of the model
+  * `model_name` (Required) The name of the Power BI model
+  * `storage_mode` (Required) The default storage mode of the Power BI model
+  * `authentication_method` (Required) How the published Power BI model authenticates to Databricks
+  * `overwrite_existing` (Optional) Whether to overwrite existing Power BI models. Default is false
+* `connection_resource_name` (Required) The resource name of the UC connection to authenticate from Databricks to Power BI
+* `refresh_after_update` (Optional) Whether the model should be refreshed after the update. Default is false
+
 #### python_wheel_task Configuration Block
 
 * `entry_point` - (Optional) Python function as entry point for the task
diff --git a/jobs/resource_job.go b/jobs/resource_job.go
index 63bbab012f..51a4f2a2ae 100644
--- a/jobs/resource_job.go
+++ b/jobs/resource_job.go
@@ -145,6 +145,7 @@ type ForEachNestedTask struct {
 	SparkPythonTask *SparkPythonTask    `json:"spark_python_task,omitempty" tf:"group:task_type"`
 	SparkSubmitTask *SparkSubmitTask    `json:"spark_submit_task,omitempty" tf:"group:task_type"`
 	PipelineTask    *PipelineTask       `json:"pipeline_task,omitempty" tf:"group:task_type"`
+	PowerBiTask     *jobs.PowerBiTask   `json:"power_bi_task,omitempty" tf:"group:task_type"`
 	PythonWheelTask *PythonWheelTask    `json:"python_wheel_task,omitempty" tf:"group:task_type"`
 	SqlTask         *SqlTask            `json:"sql_task,omitempty" tf:"group:task_type"`
 	DbtTask         *DbtTask            `json:"dbt_task,omitempty" tf:"group:task_type"`
@@ -226,6 +227,7 @@ type JobTaskSettings struct {
 	SparkPythonTask *SparkPythonTask    `json:"spark_python_task,omitempty" tf:"group:task_type"`
 	SparkSubmitTask *SparkSubmitTask    `json:"spark_submit_task,omitempty" tf:"group:task_type"`
 	PipelineTask    *PipelineTask       `json:"pipeline_task,omitempty" tf:"group:task_type"`
+	PowerBiTask     *jobs.PowerBiTask   `json:"power_bi_task,omitempty" tf:"group:task_type"`
 	PythonWheelTask *PythonWheelTask    `json:"python_wheel_task,omitempty" tf:"group:task_type"`
 	SqlTask         *SqlTask            `json:"sql_task,omitempty" tf:"group:task_type"`
 	DbtTask         *DbtTask            `json:"dbt_task,omitempty" tf:"group:task_type"`
diff --git a/jobs/resource_job_test.go b/jobs/resource_job_test.go
index dcf0c2c55a..0a0785ede6 100644
--- a/jobs/resource_job_test.go
+++ b/jobs/resource_job_test.go
@@ -8,6 +8,7 @@ import (
 	"time"
 
 	"github.com/databricks/databricks-sdk-go/apierr"
+	"github.com/databricks/databricks-sdk-go/experimental/mocks"
 	"github.com/databricks/databricks-sdk-go/service/compute"
 	"github.com/databricks/databricks-sdk-go/service/jobs"
 	"github.com/databricks/terraform-provider-databricks/clusters"
@@ -15,6 +16,7 @@ import (
 	"github.com/databricks/terraform-provider-databricks/qa"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
 	"github.com/stretchr/testify/require"
 )
 
@@ -621,6 +623,123 @@ func TestResourceJobCreate_ForEachTask(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, "789", d.Id())
 }
+func TestResourceJobCreate_PowerBiTask(t *testing.T) {
+	d, err := qa.ResourceFixture{
+		MockWorkspaceClientFunc: func(w *mocks.MockWorkspaceClient) {
+			e := w.GetMockJobsAPI().EXPECT()
+			e.Create(mock.Anything, jobs.CreateJob{
+				Name:              "power_bi_task_name",
+				MaxConcurrentRuns: 1,
+				Queue: &jobs.QueueSettings{
+					Enabled: false,
+				},
+				Tasks: []jobs.Task{
+					{
+						TaskKey: "power_bi_task_key",
+						PowerBiTask: &jobs.PowerBiTask{
+							ConnectionResourceName: "test-connection",
+							PowerBiModel: &jobs.PowerBiModel{
+								AuthenticationMethod: jobs.AuthenticationMethodOauth,
+								ModelName:            "TestModel",
+								OverwriteExisting:    true,
+								StorageMode:          jobs.StorageModeDirectQuery,
+								WorkspaceName:        "TestWorkspace",
+							},
+							RefreshAfterUpdate: true,
+							Tables: []jobs.PowerBiTable{
+								{
+									Catalog:     "TestCatalog",
+									Name:        "TestTable1",
+									Schema:      "TestSchema",
+									StorageMode: jobs.StorageModeDirectQuery,
+								},
+								{
+									Catalog:     "TestCatalog",
+									Name:        "TestTable2",
+									Schema:      "TestSchema",
+									StorageMode: jobs.StorageModeDual,
+								},
+							},
+							WarehouseId: "12345",
+						},
+					},
+				},
+			}).
+				Return(&jobs.CreateResponse{
+					JobId: 789,
+				}, nil)
+			e.GetByJobId(mock.Anything, int64(789)).Return(&jobs.Job{
+				JobId: 789,
+				Settings: &jobs.JobSettings{
+					Name: "power_bi_task_name",
+					Tasks: []jobs.Task{
+						{
+							TaskKey: "power_bi_task_key",
+							PowerBiTask: &jobs.PowerBiTask{
+								ConnectionResourceName: "test-connection",
+								PowerBiModel: &jobs.PowerBiModel{
+									AuthenticationMethod: jobs.AuthenticationMethodOauth,
+									ModelName:            "TestModel",
+									OverwriteExisting:    true,
+									StorageMode:          jobs.StorageModeDirectQuery,
+									WorkspaceName:        "TestWorkspace",
+								},
+								RefreshAfterUpdate: true,
+								Tables: []jobs.PowerBiTable{
+									{
+										Catalog:     "TestCatalog",
+										Name:        "TestTable1",
+										Schema:      "TestSchema",
+										StorageMode: jobs.StorageModeDirectQuery,
+									},
+									{
+										Catalog:     "TestCatalog",
+										Name:        "TestTable2",
+										Schema:      "TestSchema",
+										StorageMode: jobs.StorageModeDual,
+									},
+								},
+								WarehouseId: "12345",
+							},
+						},
+					},
+				},
+			}, nil)
+		},
+		Create:   true,
+		Resource: ResourceJob(),
+		HCL: `name = "power_bi_task_name"
+		task {
+			task_key = "power_bi_task_key"
+			power_bi_task {
+				connection_resource_name = "test-connection"
+				power_bi_model {
+					authentication_method = "OAUTH"
+					model_name = "TestModel"
+					overwrite_existing = true
+					storage_mode = "DIRECT_QUERY"
+					workspace_name = "TestWorkspace"
+				}
+				refresh_after_update = true
+				tables {
+					catalog = "TestCatalog"
+					name = "TestTable1"
+					schema = "TestSchema"
+					storage_mode = "DIRECT_QUERY"
+				}
+				tables {
+					catalog = "TestCatalog"
+					name = "TestTable2"
+					schema = "TestSchema"
+					storage_mode = "DUAL"
+				}
+				warehouse_id = "12345"
+			}
+		}`,
+	}.Apply(t)
+	assert.NoError(t, err)
+	assert.Equal(t, "789", d.Id())
+}
 func TestResourceJobCreate_JobParameters(t *testing.T) {
 	d, err := qa.ResourceFixture{
 		Fixtures: []qa.HTTPFixture{


Reviews:
user: login: alexott
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T06:21:13Z
user: login: alexott
body: In general it's good. The integration test is missing, but I'm not sure how it could be tested in the current environment.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T18:18:02Z

Review comments:
path: .codegen/_openapi_sha
user: login: alexott
body: Not needed
created_at: 2025-04-15T06:18:38Z
author_association: CONTRIBUTOR
path: go.mod
user: login: alexott
body: it's better to wait until #4645 is merged
created_at: 2025-04-15T06:19:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -145,6 +183,7 @@ type ForEachNestedTask struct {
 	SparkPythonTask *SparkPythonTask    `json:"spark_python_task,omitempty" tf:"group:task_type"`
 	SparkSubmitTask *SparkSubmitTask    `json:"spark_submit_task,omitempty" tf:"group:task_type"`
 	PipelineTask    *PipelineTask       `json:"pipeline_task,omitempty" tf:"group:task_type"`
+	PowerBiTask     *PowerBiTask        `json:"power_bi_task,omitempty" tf:"group:task_type"`
path: jobs/resource_job.go
user: login: alexott
body: Why not use structs from Go SDK?
created_at: 2025-04-15T06:19:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -621,6 +621,130 @@ func TestResourceJobCreate_ForEachTask(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, "789", d.Id())
 }
+func TestResourceJobCreate_PowerBiTask(t *testing.T) {
path: jobs/resource_job_test.go
user: login: alexott
body: Not critical, but it's best to use Mocks from Go SDK. See this PR on how it could be done: https://github.com/databricks/terraform-provider-databricks/pull/4648
created_at: 2025-04-15T18:17:33Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-15T20:34:20Z
body: <!-- INTEGRATION_TESTS_MANUAL -->
If integration tests don't run automatically, an authorized user can run them manually by following the instructions below:

Trigger:
[go/deco-tests-run/terraform](https://go/deco-tests-run/terraform)

Inputs:
* PR number: 4647
* Commit SHA: `1e54f38f509628ab08d65004888bf92725aa02ad`

Checks will be approved automatically on success.

author_association: CONTRIBUTOR
user: login: alexguo-db
created_at: 2025-04-15T20:56:01Z
body: > In general it's good. The integration test is missing, but I'm not sure how it could be tested in the current environment.

Hi, can you help add this to the merge queue when available?
author_association: CONTRIBUTOR