Skip to content

Commit f6cd4a5

Browse files
authored
Use the Go SDK for databricks_mws_workspaces (#4633)
## Changes Currently, many resources in the `mws` package are implemented manually in the Terraform provider. Refactoring these to use the Go SDK reduces the amount of code that needs to be maintained in the TF provider and also ensures that new changes are released automatically. This PR should be nearly identical in terms of behavior, but there are some small variations because of subtle limitations in the Go SDK. For example, we may need to fetch the workspace an additional time using the Go SDK because `WaitGetWorkspaceRunning` does not return the fetched workspace in a terminal failure state. ### Refactoring and Improvements: * Refactored `databricks_mws_workspaces` resource and datasource, as well as `databricks_mws_ncc_binding`, to use the Go SDK. ### Caching Mechanisms: * Introduced caching for workspace clients in `DatabricksClient` to improve performance and reduce redundant API calls. Added `cachedWorkspaceClient`, `cachedWorkspaceClients`, and `cachedAccountClient` fields along with associated methods. [[1]](diffhunk://#diff-e24a91e4fac6650a021a72e8527d08f88b5775ee989f109f5c6ead0c3737c7a6R57-R68) [[2]](diffhunk://#diff-e24a91e4fac6650a021a72e8527d08f88b5775ee989f109f5c6ead0c3737c7a6R104-R140) [[3]](diffhunk://#diff-e24a91e4fac6650a021a72e8527d08f88b5775ee989f109f5c6ead0c3737c7a6R175-R180) ### Test Case Updates: * Updated `TestDataSourceMwsWorkspaces` to use the new Go SDK mocks for testing, replacing HTTP fixtures with mock client responses. [[1]](diffhunk://#diff-72f2e86d45da988ff0f665f631b5bc5cc3fc42c3f27baf0074a81eb196fcbe01R4-R25) [[2]](diffhunk://#diff-72f2e86d45da988ff0f665f631b5bc5cc3fc42c3f27baf0074a81eb196fcbe01L41-L44) [[3]](diffhunk://#diff-72f2e86d45da988ff0f665f631b5bc5cc3fc42c3f27baf0074a81eb196fcbe01L54-R56) * Refactored `TestMwsAccWorkspaces_TokenUpdate` to include additional checks and new token update scenarios. * Modified `TestResourceNccBindingCreate` and `TestResourceNccBindingUpdate` to use mock clients for workspace updates. [[1]](diffhunk://#diff-f29f55efc35edd4651c26d2c0d5ce293077c0eb51fc8076e547708dee11f0c2eR5-R31) [[2]](diffhunk://#diff-f29f55efc35edd4651c26d2c0d5ce293077c0eb51fc8076e547708dee11f0c2eL42-R49) ### Test Framework Enhancements: * Added support for multiple mock workspace clients in the `ResourceFixture` struct and validation logic. [[1]](diffhunk://#diff-6b1eb785cebf167edccebd966bbd0625751fc85cea532ce6c5a3eddbca793cc6R83-R84) [[2]](diffhunk://#diff-6b1eb785cebf167edccebd966bbd0625751fc85cea532ce6c5a3eddbca793cc6L206-R211) [[3]](diffhunk://#diff-6b1eb785cebf167edccebd966bbd0625751fc85cea532ce6c5a3eddbca793cc6R235-R242) ## Tests Tests run locally. Verifying with integration tests. ## TODO - [x] Onboard `client.go` changes to code generation. - [ ] Add back unit tests for token changes
1 parent 1e3e8ce commit f6cd4a5

12 files changed

+1709
-1894
lines changed

NEXT_CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@
2121

2222
### Internal Changes
2323

24-
* Add `TestMwsAccGcpWorkspaces` and `TestMwsAccGcpByovpcWorkspaces` to flaky test ([#4624](https://github.com/databricks/terraform-provider-databricks/pull/4624)).
24+
* Refactor `databricks_mws_workspaces` resource and datasource, as well as `databricks_mws_ncc_binding`, to use the Go SDK ([#4633](https://github.com/databricks/terraform-provider-databricks/pull/4633)).
25+
* Add `TestMwsAccGcpWorkspaces` and `TestMwsAccGcpByovpcWorkspaces` to flaky test ([#4624](https://github.com/databricks/terraform-provider-databricks/pull/4624)).

common/client.go

+56-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

common/client_test.go

+98
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,21 @@ package common
22

33
import (
44
"context"
5+
"fmt"
56
"log"
67
"net/http"
78
"path/filepath"
89
"strings"
910
"testing"
1011

12+
"github.com/databricks/databricks-sdk-go"
1113
"github.com/databricks/databricks-sdk-go/client"
1214
"github.com/databricks/databricks-sdk-go/config"
15+
"github.com/databricks/databricks-sdk-go/experimental/mocks"
1316
"github.com/databricks/databricks-sdk-go/service/iam"
17+
"github.com/databricks/databricks-sdk-go/service/provisioning"
1418
"github.com/stretchr/testify/assert"
19+
"github.com/stretchr/testify/mock"
1520
"github.com/stretchr/testify/require"
1621
)
1722

@@ -335,3 +340,96 @@ func TestCachedMe_Me_MakesSingleRequest(t *testing.T) {
335340
cm.Me(context.Background())
336341
assert.Equal(t, 1, mock.count)
337342
}
343+
344+
func TestWorkspaceClientForWorkspace_WorkspaceDoesNotExist(t *testing.T) {
345+
mockAcc := mocks.NewMockAccountClient(t)
346+
mockWorkspacesAPI := mockAcc.GetMockWorkspacesAPI()
347+
348+
// Setup the mock to return an error for non-existent workspace
349+
mockWorkspacesAPI.EXPECT().Get(mock.Anything, provisioning.GetWorkspaceRequest{
350+
WorkspaceId: 12345,
351+
}).Return(nil, fmt.Errorf("workspace not found"))
352+
353+
// Create a DatabricksClient with the mock account client
354+
dc := &DatabricksClient{
355+
DatabricksClient: &client.DatabricksClient{
356+
Config: &config.Config{},
357+
},
358+
}
359+
dc.SetAccountClient(mockAcc.AccountClient)
360+
361+
// Call the method with a non-existent workspace ID
362+
_, err := dc.WorkspaceClientForWorkspace(context.Background(), 12345)
363+
364+
// Verify the error
365+
assert.Error(t, err)
366+
assert.Contains(t, err.Error(), "workspace not found")
367+
}
368+
369+
func TestWorkspaceClientForWorkspace_WorkspaceExistsNotInCache(t *testing.T) {
370+
mockAcc := mocks.NewMockAccountClient(t)
371+
mockAcc.AccountClient.Config = &config.Config{
372+
Token: "dapi123", // Instantiating WorkspaceClient attempts authentication, this allows Configure() to complete quickly.
373+
}
374+
mockWorkspacesAPI := mockAcc.GetMockWorkspacesAPI()
375+
376+
// Create a mock workspace
377+
mockWorkspace := &provisioning.Workspace{
378+
WorkspaceId: 12345,
379+
WorkspaceName: "test-workspace",
380+
DeploymentName: "test-deployment",
381+
}
382+
383+
// Setup the mock to return the workspace
384+
mockWorkspacesAPI.EXPECT().Get(mock.Anything, provisioning.GetWorkspaceRequest{
385+
WorkspaceId: 12345,
386+
}).Return(mockWorkspace, nil)
387+
388+
// Create a DatabricksClient with the mock account client
389+
dc := &DatabricksClient{
390+
DatabricksClient: &client.DatabricksClient{
391+
Config: &config.Config{},
392+
},
393+
}
394+
dc.SetAccountClient(mockAcc.AccountClient)
395+
396+
// Call the method with the workspace ID
397+
workspaceClient, err := dc.WorkspaceClientForWorkspace(context.Background(), 12345)
398+
399+
// Verify no error and client is returned
400+
assert.NoError(t, err)
401+
assert.NotNil(t, workspaceClient)
402+
403+
// Verify the workspace client is configured correctly
404+
assert.Equal(t, fmt.Sprintf("https://%s.cloud.databricks.com", mockWorkspace.DeploymentName), workspaceClient.Config.Host)
405+
406+
// Verify the client is cached
407+
dc.mu.Lock()
408+
cachedClient, exists := dc.cachedWorkspaceClients[12345]
409+
dc.mu.Unlock()
410+
411+
assert.True(t, exists)
412+
assert.Equal(t, workspaceClient, cachedClient)
413+
}
414+
415+
func TestWorkspaceClientForWorkspace_WorkspaceExistsInCache(t *testing.T) {
416+
// Create a mock workspace client
417+
mockWorkspaceClient := &databricks.WorkspaceClient{}
418+
419+
// Create a DatabricksClient with the mock workspace client in cache
420+
dc := &DatabricksClient{
421+
DatabricksClient: &client.DatabricksClient{
422+
Config: &config.Config{},
423+
},
424+
}
425+
426+
// Set the workspace client in cache
427+
dc.SetWorkspaceClientForWorkspace(12345, mockWorkspaceClient)
428+
429+
// Call the method with the workspace ID
430+
workspaceClient, err := dc.WorkspaceClientForWorkspace(context.Background(), 12345)
431+
432+
// Verify no error and the cached client is returned
433+
assert.NoError(t, err)
434+
assert.Equal(t, mockWorkspaceClient, workspaceClient)
435+
}

docs/resources/mws_workspaces.md

+11-7
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ The following arguments are available:
323323
* `deployment_name` - (Optional) part of URL as in `https://<prefix>-<deployment-name>.cloud.databricks.com`. Deployment name cannot be used until a deployment name prefix is defined. Please contact your Databricks representative. Once a new deployment prefix is added/updated, it only will affect the new workspaces created.
324324
* `workspace_name` - name of the workspace, will appear on UI.
325325
* `network_id` - (Optional) `network_id` from [networks](mws_networks.md).
326+
* `credentials_id` - (AWS only) ID of the workspace's credential configuration object.
326327
* `aws_region` - (AWS only) region of VPC.
327328
* `storage_configuration_id` - (AWS only)`storage_configuration_id` from [storage configuration](mws_storage_configurations.md).
328329
* `managed_services_customer_managed_key_id` - (Optional) `customer_managed_key_id` from [customer managed keys](mws_customer_managed_keys.md) with `use_cases` set to `MANAGED_SERVICES`. This is used to encrypt the workspace's notebook and secret data in the control plane.
@@ -331,7 +332,9 @@ The following arguments are available:
331332
* `cloud_resource_container` - (GCP only) A block that specifies GCP workspace configurations, consisting of following blocks:
332333
* `gcp` - A block that consists of the following field:
333334
* `project_id` - The Google Cloud project ID, which the workspace uses to instantiate cloud resources for your workspace.
334-
* `gke_config` - (GCP only) A block that specifies GKE configuration for the Databricks workspace:
335+
* `gcp_managed_network_config` - (GCP only) A block that describes the network configuration for workspaces with Databricks-managed networks.
336+
* `subnet_cidr` - The IP range from which to allocate GKE cluster nodes. No bigger than `/9` and no smaller than `/29`.
337+
* `gke_config` - (GCP only, deprecated) A block that specifies GKE configuration for the Databricks workspace:
335338
* `connectivity_type`: Specifies the network connectivity types for the GKE nodes and the GKE master network. Possible values are: `PRIVATE_NODE_PUBLIC_MASTER`, `PUBLIC_NODE_PUBLIC_MASTER`.
336339
* `master_ip_range`: The IP range from which to allocate GKE cluster master resources. This field will be ignored if GKE private cluster is not enabled. It must be exactly as big as `/28`.
337340
* `private_access_settings_id` - (Optional) Canonical unique identifier of [databricks_mws_private_access_settings](mws_private_access_settings.md) in Databricks Account.
@@ -351,24 +354,25 @@ You can specify a `token` block in the body of the workspace resource, so that T
351354

352355
On AWS, the following arguments could be modified after the workspace is running:
353356

354-
* `network_id` - Modifying [networks on running workspaces](mws_networks.md#modifying-networks-on-running-workspaces-aws-only) would require three separate `terraform apply` steps.
355357
* `credentials_id`
356-
* `storage_customer_managed_key_id`
357-
* `private_access_settings_id`
358358
* `custom_tags`
359+
* `managed_services_customer_managed_key_id`
360+
* `network_id` - Modifying [networks on running workspaces](mws_networks.md#modifying-networks-on-running-workspaces-aws-only) would require three separate `terraform apply` steps.
361+
* `private_access_settings_id`
362+
* `storage_customer_managed_key_id`
359363

360364
## Attribute Reference
361365

362366
In addition to all arguments above, the following attributes are exported:
363367

364368
* `id` - (String) Canonical unique identifier for the workspace, of the format `<account-id>/<workspace-id>`
369+
* `creation_time` - (Integer) time when workspace was created
370+
* `custom_tags` - (Map) Custom Tags (if present) added to workspace
371+
* `gcp_workspace_sa` - (String, GCP only) identifier of a service account created for the workspace in form of `db-<workspace-id>@prod-gcp-<region>.iam.gserviceaccount.com`
365372
* `workspace_id` - (String) workspace id
366373
* `workspace_status_message` - (String) updates on workspace status
367374
* `workspace_status` - (String) workspace status
368-
* `creation_time` - (Integer) time when workspace was created
369375
* `workspace_url` - (String) URL of the workspace
370-
* `custom_tags` - (Map) Custom Tags (if present) added to workspace
371-
* `gcp_workspace_sa` - (String, GCP only) identifier of a service account created for the workspace in form of `db-<workspace-id>@prod-gcp-<region>.iam.gserviceaccount.com`
372376

373377
## Timeouts
374378

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package acceptance
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hashicorp/terraform-plugin-testing/plancheck"
8+
)
9+
10+
type expectNotDestroyed struct {
11+
addr string
12+
}
13+
14+
func ExpectNotDestroyed(addr string) expectNotDestroyed {
15+
return expectNotDestroyed{addr: addr}
16+
}
17+
18+
func (e expectNotDestroyed) CheckPlan(ctx context.Context, req plancheck.CheckPlanRequest, resp *plancheck.CheckPlanResponse) {
19+
for _, resource := range req.Plan.ResourceChanges {
20+
if resource.Address != e.addr {
21+
continue
22+
}
23+
actions := resource.Change.Actions
24+
if actions.DestroyBeforeCreate() || actions.CreateBeforeDestroy() || actions.Delete() {
25+
resp.Error = fmt.Errorf("resource %s is marked for destruction", e.addr)
26+
return
27+
}
28+
}
29+
}

mws/data_mws_workspaces.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,17 @@ func DataSourceMwsWorkspaces() common.Resource {
1616
if c.Config.AccountID == "" {
1717
return fmt.Errorf("provider block is missing `account_id` property")
1818
}
19-
workspaces, err := NewWorkspacesAPI(ctx, c).List(c.Config.AccountID)
19+
a, err := c.AccountClient()
20+
if err != nil {
21+
return err
22+
}
23+
workspaces, err := a.Workspaces.List(ctx)
2024
if err != nil {
2125
return err
2226
}
2327
data.Ids = map[string]int64{}
2428
for _, v := range workspaces {
25-
data.Ids[v.WorkspaceName] = v.WorkspaceID
29+
data.Ids[v.WorkspaceName] = v.WorkspaceId
2630
}
2731
return nil
2832
})

mws/data_mws_workspaces_test.go

+20-24
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,28 @@
11
package mws
22

33
import (
4+
"errors"
45
"testing"
56

7+
"github.com/databricks/databricks-sdk-go/experimental/mocks"
8+
"github.com/databricks/databricks-sdk-go/service/provisioning"
69
"github.com/databricks/terraform-provider-databricks/qa"
10+
"github.com/stretchr/testify/mock"
711
)
812

913
func TestDataSourceMwsWorkspaces(t *testing.T) {
1014
qa.ResourceFixture{
11-
Fixtures: []qa.HTTPFixture{
12-
{
13-
Method: "GET",
14-
Resource: "/api/2.0/accounts/abc/workspaces",
15-
16-
Response: []Workspace{
17-
{
18-
WorkspaceName: "bcd",
19-
WorkspaceID: 123,
20-
},
21-
{
22-
WorkspaceName: "def",
23-
WorkspaceID: 456,
24-
},
15+
MockAccountClientFunc: func(a *mocks.MockAccountClient) {
16+
a.GetMockWorkspacesAPI().EXPECT().List(mock.Anything).Return([]provisioning.Workspace{
17+
{
18+
WorkspaceName: "bcd",
19+
WorkspaceId: 123,
20+
},
21+
{
22+
WorkspaceName: "def",
23+
WorkspaceId: 456,
2524
},
26-
},
25+
}, nil)
2726
},
2827
AccountID: "abc",
2928
Resource: DataSourceMwsWorkspaces(),
@@ -38,10 +37,12 @@ func TestDataSourceMwsWorkspaces(t *testing.T) {
3837
})
3938
}
4039

41-
func TestCatalogsData_Error(t *testing.T) {
40+
func TestDataSourceMwsWorkspaces_Error(t *testing.T) {
4241
qa.ResourceFixture{
42+
MockAccountClientFunc: func(a *mocks.MockAccountClient) {
43+
a.GetMockWorkspacesAPI().EXPECT().List(mock.Anything).Return(nil, errors.New("i'm a teapot"))
44+
},
4345
AccountID: "abc",
44-
Fixtures: qa.HTTPFailures,
4546
Resource: DataSourceMwsWorkspaces(),
4647
Read: true,
4748
NonWritable: true,
@@ -51,13 +52,8 @@ func TestCatalogsData_Error(t *testing.T) {
5152

5253
func TestDataSourceMwsWorkspaces_Empty(t *testing.T) {
5354
qa.ResourceFixture{
54-
Fixtures: []qa.HTTPFixture{
55-
{
56-
Method: "GET",
57-
Resource: "/api/2.0/accounts/abc/workspaces",
58-
59-
Response: []Workspace{},
60-
},
55+
MockAccountClientFunc: func(a *mocks.MockAccountClient) {
56+
a.GetMockWorkspacesAPI().EXPECT().List(mock.Anything).Return([]provisioning.Workspace{}, nil)
6157
},
6258
AccountID: "abc",
6359
Resource: DataSourceMwsWorkspaces(),

0 commit comments

Comments
 (0)