Skip to content

Commit 248dea3

Browse files
authored
Implement neutral ShowPlanFile wrapper (#3075)
## Changes - Modify deployplan.Action to be deployment method-neutral: use resource group name ("jobs") instead of terraform resource ("databricks_jobs"). - Implement ShowPlanFile, a wrapper for tf.ShowPlanFile which returns neutral []deployplan.Action. - Use this wrapper in deploy and destroy. - Implement deployplan.Filter and deployplan.FilterGroup helper methods and use those. - Do not show auxiliary resources (grants, permissions, secret_acls) in diff output. ## Why This abstracts terraform away, facilitating addition of direct deployment method: #2926 ## Tests Existing tests.
1 parent 64da4dd commit 248dea3

File tree

8 files changed

+135
-76
lines changed

8 files changed

+135
-76
lines changed

acceptance/bundle/deploy/mlops-stacks/output.txt

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,8 @@ Warning: unknown field: description
9090
The following resources will be deleted:
9191
delete job batch_inference_job
9292
delete job model_training_job
93-
delete mlflow_experiment experiment
94-
delete mlflow_model model
95-
delete permissions job_batch_inference_job
96-
delete permissions job_model_training_job
97-
delete permissions mlflow_experiment_experiment
98-
delete permissions mlflow_model_model
93+
delete experiment experiment
94+
delete model model
9995

10096
All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/project_name_[UNIQUE_NAME]/dev
10197

acceptance/bundle/deploy/secret-scope/output.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,6 @@ Deployment complete!
4343

4444
>>> [CLI] bundle destroy --auto-approve
4545
The following resources will be deleted:
46-
delete secret_acl secret_acl_secret_scope1_0
47-
delete secret_acl secret_acl_secret_scope1_1
4846
delete secret_scope secret_scope1
4947

5048
All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/deploy-secret-scope-test-[UNIQUE_NAME]/default

acceptance/bundle/deploy/volume/recreate/output.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ Deployment complete!
6565

6666
>>> [CLI] bundle destroy --auto-approve
6767
The following resources will be deleted:
68-
delete grants volume_foo
6968
delete schema schema1
7069
delete schema schema2
7170
delete volume foo
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package terraform
2+
3+
import (
4+
"context"
5+
6+
"github.com/databricks/cli/bundle/config"
7+
"github.com/databricks/cli/bundle/deployplan"
8+
"github.com/hashicorp/terraform-exec/tfexec"
9+
tfjson "github.com/hashicorp/terraform-json"
10+
)
11+
12+
// GetActions converts Terraform resource changes into deployplan.Action values.
13+
// The returned slice can be filtered using deployplan.Filter and FilterGroup helpers.
14+
func GetActions(changes []*tfjson.ResourceChange) []deployplan.Action {
15+
supported := config.SupportedResources()
16+
typeToGroup := make(map[string]string, len(supported))
17+
for group, desc := range supported {
18+
typeToGroup[desc.TerraformResourceName] = group
19+
}
20+
21+
var result []deployplan.Action
22+
23+
for _, rc := range changes {
24+
if rc.Change == nil {
25+
continue
26+
}
27+
28+
var actionType deployplan.ActionType
29+
switch {
30+
case rc.Change.Actions.Delete():
31+
actionType = deployplan.ActionTypeDelete
32+
case rc.Change.Actions.Replace():
33+
actionType = deployplan.ActionTypeRecreate
34+
case rc.Change.Actions.Create():
35+
actionType = deployplan.ActionTypeCreate
36+
case rc.Change.Actions.Update():
37+
actionType = deployplan.ActionTypeUpdate
38+
default:
39+
continue
40+
}
41+
42+
group, ok := typeToGroup[rc.Type]
43+
if !ok {
44+
// Happens for databricks_grant, databricks_permissions, databricks_secrets_acl.
45+
// These are automatically created by DABs, no need to show them.
46+
continue
47+
}
48+
49+
result = append(result, deployplan.Action{
50+
Action: actionType,
51+
Group: group,
52+
Name: rc.Name,
53+
})
54+
}
55+
56+
return result
57+
}
58+
59+
// ShowPlanFile reads a Terraform plan file located at planPath using the provided tfexec.Terraform handle
60+
// and converts it into a slice of deployplan.Action.
61+
//
62+
// The conversion maps Terraform resource types (e.g. "databricks_pipeline") to bundle configuration
63+
// resource groups (e.g. "pipelines") using config.SupportedResources(). If a resource type is not
64+
// recognised we fall back to the raw Terraform resource type so that the information is not lost.
65+
func ShowPlanFile(ctx context.Context, tf *tfexec.Terraform, planPath string) ([]deployplan.Action, error) {
66+
plan, err := tf.ShowPlanFile(ctx, planPath)
67+
if err != nil {
68+
return nil, err
69+
}
70+
71+
return GetActions(plan.ResourceChanges), nil
72+
}

bundle/deployplan/plan.go

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package deployplan
22

3-
import "strings"
3+
import (
4+
"fmt"
5+
"strings"
6+
)
47

58
type Plan struct {
69
// Path to the plan
@@ -11,20 +14,22 @@ type Plan struct {
1114
}
1215

1316
type Action struct {
14-
// Type and name of the resource
15-
ResourceType string `json:"resource_type"`
16-
ResourceName string `json:"resource_name"`
17+
// Resource group in the config, e.g. "jobs", "pipelines" etc
18+
Group string
19+
20+
// Key of the resource the config
21+
Name string
1722

18-
Action ActionType `json:"action"`
23+
Action ActionType
1924
}
2025

2126
func (a Action) String() string {
22-
// terraform resources have the databricks_ prefix, which is not needed.
23-
rtype := strings.TrimPrefix(a.ResourceType, "databricks_")
24-
return strings.Join([]string{" ", string(a.Action), rtype, a.ResourceName}, " ")
27+
typ, _ := strings.CutSuffix(a.Group, "s")
28+
return fmt.Sprintf(" %s %s %s", a.Action, typ, a.Name)
2529
}
2630

27-
func (c Action) IsInplaceSupported() bool {
31+
// Implements cmdio.Event for cmdio.Log
32+
func (a Action) IsInplaceSupported() bool {
2833
return false
2934
}
3035

@@ -38,7 +43,34 @@ const (
3843
ActionTypeCreate ActionType = "create"
3944
ActionTypeDelete ActionType = "delete"
4045
ActionTypeUpdate ActionType = "update"
41-
ActionTypeNoOp ActionType = "no-op"
42-
ActionTypeRead ActionType = "read"
4346
ActionTypeRecreate ActionType = "recreate"
4447
)
48+
49+
// Filter returns actions that match the specified action type
50+
func Filter(changes []Action, actionType ActionType) []Action {
51+
var result []Action
52+
for _, action := range changes {
53+
if action.Action == actionType {
54+
result = append(result, action)
55+
}
56+
}
57+
return result
58+
}
59+
60+
// FilterGroup returns actions that match the specified group and any of the specified action types
61+
func FilterGroup(changes []Action, group string, actionTypes ...ActionType) []Action {
62+
var result []Action
63+
64+
// Create a set of action types for efficient lookup
65+
actionTypeSet := make(map[ActionType]bool)
66+
for _, actionType := range actionTypes {
67+
actionTypeSet[actionType] = true
68+
}
69+
70+
for _, action := range changes {
71+
if action.Group == group && actionTypeSet[action.Action] {
72+
result = append(result, action)
73+
}
74+
}
75+
return result
76+
}

bundle/phases/deploy.go

Lines changed: 6 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -25,53 +25,25 @@ import (
2525
"github.com/databricks/cli/libs/diag"
2626
"github.com/databricks/cli/libs/log"
2727
"github.com/databricks/cli/libs/sync"
28-
tfjson "github.com/hashicorp/terraform-json"
2928
)
3029

31-
func filterDeleteOrRecreateActions(changes []*tfjson.ResourceChange, resourceType string) []deployplan.Action {
32-
var res []deployplan.Action
33-
for _, rc := range changes {
34-
if rc.Type != resourceType {
35-
continue
36-
}
37-
38-
var actionType deployplan.ActionType
39-
switch {
40-
case rc.Change.Actions.Delete():
41-
actionType = deployplan.ActionTypeDelete
42-
case rc.Change.Actions.Replace():
43-
actionType = deployplan.ActionTypeRecreate
44-
default:
45-
// Filter other action types..
46-
continue
47-
}
48-
49-
res = append(res, deployplan.Action{
50-
Action: actionType,
51-
ResourceType: rc.Type,
52-
ResourceName: rc.Name,
53-
})
54-
}
55-
56-
return res
57-
}
58-
5930
func approvalForDeploy(ctx context.Context, b *bundle.Bundle) (bool, error) {
6031
tf := b.Terraform
6132
if tf == nil {
6233
return false, errors.New("terraform not initialized")
6334
}
6435

6536
// read plan file
66-
plan, err := tf.ShowPlanFile(ctx, b.Plan.Path)
37+
actions, err := terraform.ShowPlanFile(ctx, tf, b.Plan.Path)
6738
if err != nil {
6839
return false, err
6940
}
7041

71-
schemaActions := filterDeleteOrRecreateActions(plan.ResourceChanges, "databricks_schema")
72-
dltActions := filterDeleteOrRecreateActions(plan.ResourceChanges, "databricks_pipeline")
73-
volumeActions := filterDeleteOrRecreateActions(plan.ResourceChanges, "databricks_volume")
74-
dashboardActions := filterDeleteOrRecreateActions(plan.ResourceChanges, "databricks_dashboard")
42+
types := []deployplan.ActionType{deployplan.ActionTypeRecreate, deployplan.ActionTypeDelete}
43+
schemaActions := deployplan.FilterGroup(actions, "schemas", types...)
44+
dltActions := deployplan.FilterGroup(actions, "pipelines", types...)
45+
volumeActions := deployplan.FilterGroup(actions, "volumes", types...)
46+
dashboardActions := deployplan.FilterGroup(actions, "dashboards", types...)
7547

7648
// We don't need to display any prompts in this case.
7749
if len(schemaActions) == 0 && len(dltActions) == 0 && len(volumeActions) == 0 && len(dashboardActions) == 0 {

bundle/phases/deploy_test.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package phases
33
import (
44
"testing"
55

6+
"github.com/databricks/cli/bundle/deploy/terraform"
67
"github.com/databricks/cli/bundle/deployplan"
78
tfjson "github.com/hashicorp/terraform-json"
89
"github.com/stretchr/testify/assert"
@@ -40,18 +41,19 @@ func TestParseTerraformActions(t *testing.T) {
4041
},
4142
}
4243

43-
res := filterDeleteOrRecreateActions(changes, "databricks_pipeline")
44+
actions := terraform.GetActions(changes)
45+
res := deployplan.FilterGroup(actions, "pipelines", deployplan.ActionTypeDelete, deployplan.ActionTypeRecreate)
4446

4547
assert.Equal(t, []deployplan.Action{
4648
{
47-
Action: deployplan.ActionTypeDelete,
48-
ResourceType: "databricks_pipeline",
49-
ResourceName: "delete pipeline",
49+
Action: deployplan.ActionTypeDelete,
50+
Group: "pipelines",
51+
Name: "delete pipeline",
5052
},
5153
{
52-
Action: deployplan.ActionTypeRecreate,
53-
ResourceType: "databricks_pipeline",
54-
ResourceName: "recreate pipeline",
54+
Action: deployplan.ActionTypeRecreate,
55+
Group: "pipelines",
56+
Name: "recreate pipeline",
5557
},
5658
}, res)
5759
}

bundle/phases/destroy.go

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,10 @@ import (
99
"github.com/databricks/cli/bundle/deploy/files"
1010
"github.com/databricks/cli/bundle/deploy/lock"
1111
"github.com/databricks/cli/bundle/deploy/terraform"
12+
"github.com/databricks/cli/bundle/deployplan"
1213
"github.com/databricks/cli/bundle/statemgmt"
13-
1414
"github.com/databricks/cli/libs/cmdio"
1515
"github.com/databricks/cli/libs/diag"
16-
17-
"github.com/databricks/cli/bundle/deployplan"
1816
"github.com/databricks/cli/libs/log"
1917
"github.com/databricks/databricks-sdk-go/apierr"
2018
)
@@ -38,22 +36,12 @@ func approvalForDestroy(ctx context.Context, b *bundle.Bundle) (bool, error) {
3836
return false, errors.New("terraform not initialized")
3937
}
4038

41-
// read plan file
42-
plan, err := tf.ShowPlanFile(ctx, b.Plan.Path)
39+
actions, err := terraform.ShowPlanFile(ctx, tf, b.Plan.Path)
4340
if err != nil {
4441
return false, err
4542
}
4643

47-
var deleteActions []deployplan.Action
48-
for _, rc := range plan.ResourceChanges {
49-
if rc.Change.Actions.Delete() {
50-
deleteActions = append(deleteActions, deployplan.Action{
51-
Action: deployplan.ActionTypeDelete,
52-
ResourceType: rc.Type,
53-
ResourceName: rc.Name,
54-
})
55-
}
56-
}
44+
deleteActions := deployplan.Filter(actions, deployplan.ActionTypeDelete)
5745

5846
if len(deleteActions) > 0 {
5947
cmdio.LogString(ctx, "The following resources will be deleted:")

0 commit comments

Comments
 (0)