diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 7088cf7aa1..b406a874e8 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -11,6 +11,7 @@ * engine/direct: Fix 400 error when deploying grants with ALL_PRIVILEGES ([#4801](https://github.com/databricks/cli/pull/4801)) * Deduplicate grant entries with duplicate principals or privileges during initialization ([#4801](https://github.com/databricks/cli/pull/4801)) * engine/direct: Fix unwanted recreation of secret scopes when scope_backend_type is not set ([#4834](https://github.com/databricks/cli/pull/4834)) +* engine/direct: Fix bind and unbind for non-Terraform resources ([#4850](https://github.com/databricks/cli/pull/4850)) * engine/direct: Fix deploying removed principals ([#4824](https://github.com/databricks/cli/pull/4824)) ### Dependency updates diff --git a/acceptance/bundle/deployment/bind/catalog/databricks.yml.tmpl b/acceptance/bundle/deployment/bind/catalog/databricks.yml.tmpl new file mode 100644 index 0000000000..c525eab9a2 --- /dev/null +++ b/acceptance/bundle/deployment/bind/catalog/databricks.yml.tmpl @@ -0,0 +1,8 @@ +bundle: + name: test-bundle-$UNIQUE_NAME + +resources: + catalogs: + foo: + name: test-catalog-$UNIQUE_NAME + comment: This is a test catalog diff --git a/acceptance/bundle/deployment/bind/catalog/out.test.toml b/acceptance/bundle/deployment/bind/catalog/out.test.toml new file mode 100644 index 0000000000..f1d40380d0 --- /dev/null +++ b/acceptance/bundle/deployment/bind/catalog/out.test.toml @@ -0,0 +1,6 @@ +Local = true +Cloud = true +RequiresUnityCatalog = true + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/deployment/bind/catalog/output.txt b/acceptance/bundle/deployment/bind/catalog/output.txt new file mode 100644 index 0000000000..d365080f49 --- /dev/null +++ b/acceptance/bundle/deployment/bind/catalog/output.txt @@ -0,0 +1,46 @@ + +>>> [CLI] catalogs create test-catalog-[UNIQUE_NAME] +{ + "comment": null, + "name": "test-catalog-[UNIQUE_NAME]" +} + +>>> [CLI] bundle deployment bind foo test-catalog-[UNIQUE_NAME] --auto-approve +Updating deployment state... +Successfully bound catalog with an id 'test-catalog-[UNIQUE_NAME]' +Run 'bundle deploy' to deploy changes to your workspace + +=== Deploy bundle +>>> [CLI] bundle deploy --force-lock --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] catalogs get test-catalog-[UNIQUE_NAME] +{ + "comment": "This is a test catalog", + "name": "test-catalog-[UNIQUE_NAME]" +} + +=== Unbind catalog +>>> [CLI] bundle deployment unbind foo +Updating deployment state... + +=== Destroy bundle +>>> [CLI] bundle destroy --auto-approve +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! + +=== Read the pre-defined catalog again (expecting it still exists): +>>> [CLI] catalogs get test-catalog-[UNIQUE_NAME] +{ + "comment": "This is a test catalog", + "name": "test-catalog-[UNIQUE_NAME]" +} + +=== Test cleanup +>>> [CLI] catalogs delete test-catalog-[UNIQUE_NAME] --force +0 diff --git a/acceptance/bundle/deployment/bind/catalog/script b/acceptance/bundle/deployment/bind/catalog/script new file mode 100644 index 0000000000..f00c7819e4 --- /dev/null +++ b/acceptance/bundle/deployment/bind/catalog/script @@ -0,0 +1,26 @@ +cleanup() { + title "Test cleanup" + trace $CLI catalogs delete "test-catalog-$UNIQUE_NAME" --force + echo $? +} +trap cleanup EXIT + +envsubst < databricks.yml.tmpl > databricks.yml + +trace $CLI catalogs create "test-catalog-$UNIQUE_NAME" | jq '{comment, name}' + +trace $CLI bundle deployment bind foo "test-catalog-$UNIQUE_NAME" --auto-approve + +title "Deploy bundle" +trace $CLI bundle deploy --force-lock --auto-approve + +trace $CLI catalogs get "test-catalog-$UNIQUE_NAME" | jq '{comment, name}' + +title "Unbind catalog" +trace $CLI bundle deployment unbind foo + +title "Destroy bundle" +trace $CLI bundle destroy --auto-approve + +title "Read the pre-defined catalog again (expecting it still exists): " +trace $CLI catalogs get "test-catalog-$UNIQUE_NAME" | jq '{comment, name}' diff --git a/acceptance/bundle/deployment/bind/catalog/test.toml b/acceptance/bundle/deployment/bind/catalog/test.toml new file mode 100644 index 0000000000..5722b37ccc --- /dev/null +++ b/acceptance/bundle/deployment/bind/catalog/test.toml @@ -0,0 +1,11 @@ +Local = true +Cloud = true +RequiresUnityCatalog = true + +Ignore = [ + ".databricks", + "databricks.yml", +] + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/deployment/bind/external_location/databricks.yml.tmpl b/acceptance/bundle/deployment/bind/external_location/databricks.yml.tmpl new file mode 100644 index 0000000000..70a7160147 --- /dev/null +++ b/acceptance/bundle/deployment/bind/external_location/databricks.yml.tmpl @@ -0,0 +1,10 @@ +bundle: + name: test-bundle-$UNIQUE_NAME + +resources: + external_locations: + test_location: + name: test-location-$UNIQUE_NAME + url: s3://test-bucket/path-dabs + credential_name: test_storage_credential + comment: "Test external location from DABs" diff --git a/acceptance/bundle/deployment/bind/external_location/out.test.toml b/acceptance/bundle/deployment/bind/external_location/out.test.toml new file mode 100644 index 0000000000..54146af564 --- /dev/null +++ b/acceptance/bundle/deployment/bind/external_location/out.test.toml @@ -0,0 +1,5 @@ +Local = true +Cloud = false + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/deployment/bind/external_location/output.txt b/acceptance/bundle/deployment/bind/external_location/output.txt new file mode 100644 index 0000000000..bc2ad4be80 --- /dev/null +++ b/acceptance/bundle/deployment/bind/external_location/output.txt @@ -0,0 +1,49 @@ + +>>> [CLI] external-locations create test-location-[UNIQUE_NAME] s3://test-bucket/path test_storage_credential +{ + "name": "test-location-[UNIQUE_NAME]", + "url": "s3://test-bucket/path", + "credential_name": "test_storage_credential" +} + +>>> [CLI] bundle deployment bind test_location test-location-[UNIQUE_NAME] --auto-approve +Updating deployment state... +Successfully bound external_location with an id 'test-location-[UNIQUE_NAME]' +Run 'bundle deploy' to deploy changes to your workspace + +=== Deploy bundle +>>> [CLI] bundle deploy --force-lock --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] external-locations get test-location-[UNIQUE_NAME] +{ + "name": "test-location-[UNIQUE_NAME]", + "url": "s3://test-bucket/path-dabs", + "credential_name": "test_storage_credential" +} + +=== Unbind external location +>>> [CLI] bundle deployment unbind test_location +Updating deployment state... + +=== Destroy bundle +>>> [CLI] bundle destroy --auto-approve +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! + +=== Read the pre-defined external location again (expecting it still exists): +>>> [CLI] external-locations get test-location-[UNIQUE_NAME] +{ + "name": "test-location-[UNIQUE_NAME]", + "url": "s3://test-bucket/path-dabs", + "credential_name": "test_storage_credential" +} + +=== Test cleanup +>>> [CLI] external-locations delete test-location-[UNIQUE_NAME] +0 diff --git a/acceptance/bundle/deployment/bind/external_location/script b/acceptance/bundle/deployment/bind/external_location/script new file mode 100644 index 0000000000..6f91ac1169 --- /dev/null +++ b/acceptance/bundle/deployment/bind/external_location/script @@ -0,0 +1,26 @@ +cleanup() { + title "Test cleanup" + trace $CLI external-locations delete "test-location-$UNIQUE_NAME" + echo $? +} +trap cleanup EXIT + +envsubst < databricks.yml.tmpl > databricks.yml + +trace $CLI external-locations create "test-location-$UNIQUE_NAME" "s3://test-bucket/path" "test_storage_credential" | jq '{name, url, credential_name}' + +trace $CLI bundle deployment bind test_location "test-location-$UNIQUE_NAME" --auto-approve + +title "Deploy bundle" +trace $CLI bundle deploy --force-lock --auto-approve + +trace $CLI external-locations get "test-location-$UNIQUE_NAME" | jq '{name, url, credential_name}' + +title "Unbind external location" +trace $CLI bundle deployment unbind test_location + +title "Destroy bundle" +trace $CLI bundle destroy --auto-approve + +title "Read the pre-defined external location again (expecting it still exists): " +trace $CLI external-locations get "test-location-$UNIQUE_NAME" | jq '{name, url, credential_name}' diff --git a/acceptance/bundle/deployment/bind/external_location/test.toml b/acceptance/bundle/deployment/bind/external_location/test.toml new file mode 100644 index 0000000000..63db7b2888 --- /dev/null +++ b/acceptance/bundle/deployment/bind/external_location/test.toml @@ -0,0 +1,10 @@ +Local = true +Cloud = false + +Ignore = [ + ".databricks", + "databricks.yml", +] + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/bundle/phases/bind.go b/bundle/phases/bind.go index 715f97f528..a8f99b28e8 100644 --- a/bundle/phases/bind.go +++ b/bundle/phases/bind.go @@ -39,7 +39,10 @@ func Bind(ctx context.Context, b *bundle.Bundle, opts *terraform.BindOptions) { if engine.IsDirect() { // Direct engine: import into temp state, run plan, check for changes // This follows the same pattern as terraform import - groupName := terraform.TerraformToGroupName[opts.ResourceType] + groupName, ok := terraform.TerraformToGroupName[opts.ResourceType] + if !ok { + groupName = opts.ResourceType + } resourceKey := fmt.Sprintf("resources.%s.%s", groupName, opts.ResourceKey) _, statePath := b.StateFilenameDirect(ctx) @@ -136,7 +139,10 @@ func Unbind(ctx context.Context, b *bundle.Bundle, bundleType, tfResourceType, r }() if engine.IsDirect() { - groupName := terraform.TerraformToGroupName[tfResourceType] + groupName, ok := terraform.TerraformToGroupName[tfResourceType] + if !ok { + groupName = tfResourceType + } fullResourceKey := fmt.Sprintf("resources.%s.%s", groupName, resourceKey) _, statePath := b.StateFilenameDirect(ctx) err := b.DeploymentBundle.Unbind(ctx, statePath, fullResourceKey) diff --git a/cmd/apps/import.go b/cmd/apps/import.go index d32112f5c5..de6529cf3c 100644 --- a/cmd/apps/import.go +++ b/cmd/apps/import.go @@ -323,7 +323,10 @@ func runImport(ctx context.Context, w *databricks.WorkspaceClient, appName, outp } // Bind the resource - tfName := terraform.GroupToTerraformName[resource.ResourceDescription().PluralName] + tfName, ok := terraform.GroupToTerraformName[resource.ResourceDescription().PluralName] + if !ok { + tfName = resource.ResourceDescription().PluralName + } phases.Bind(ctx, b, &terraform.BindOptions{ AutoApprove: true, ResourceType: tfName, diff --git a/cmd/bundle/deployment/bind_resource.go b/cmd/bundle/deployment/bind_resource.go index 9171a33a9c..4fcee8b07f 100644 --- a/cmd/bundle/deployment/bind_resource.go +++ b/cmd/bundle/deployment/bind_resource.go @@ -43,7 +43,10 @@ func BindResource(cmd *cobra.Command, resourceKey, resourceId string, autoApprov return fmt.Errorf("%s with an id '%s' is not found", resource.ResourceDescription().SingularName, resourceId) } - tfName := terraform.GroupToTerraformName[resource.ResourceDescription().PluralName] + tfName, ok := terraform.GroupToTerraformName[resource.ResourceDescription().PluralName] + if !ok { + tfName = resource.ResourceDescription().PluralName + } phases.Bind(ctx, b, &terraform.BindOptions{ AutoApprove: autoApprove, ResourceType: tfName, diff --git a/cmd/bundle/deployment/unbind.go b/cmd/bundle/deployment/unbind.go index 7fe16646d6..e382a0d942 100644 --- a/cmd/bundle/deployment/unbind.go +++ b/cmd/bundle/deployment/unbind.go @@ -69,7 +69,10 @@ To re-bind the resource later, use: } rd := resource.ResourceDescription() - tfName := terraform.GroupToTerraformName[rd.PluralName] + tfName, ok := terraform.GroupToTerraformName[rd.PluralName] + if !ok { + tfName = rd.PluralName + } phases.Unbind(ctx, b, rd.SingularName, tfName, args[0]) if logdiag.HasError(ctx) { return root.ErrAlreadyPrinted