Skip to content

Commit 39cc564

Browse files
authored
Add hidden "bundle plan" command (#3329)
## Changes - Add "bundle plan" command that shows resource-level plan. It works for both TF and direct backend. - Make plan in direct deployment sort groups and resources so that output of "bundle plan" is consistent. Depends on #3328 ## Why Going to use this in acceptance tests to verify direct deployment. It'll be extended with an option to show field-level changes as well (most likely for direct deployment only). ## Tests Modified different acceptance tests to show different actions.
1 parent 2973b59 commit 39cc564

File tree

10 files changed

+155
-11
lines changed

10 files changed

+155
-11
lines changed

acceptance/bundle/deploy/pipeline/auto-approve/output.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ Deployment complete!
3535
=== Remove resources from configuration.
3636
>>> rm resources.yml
3737

38+
>>> [CLI] bundle plan
39+
delete jobs.foo
40+
delete pipelines.bar
41+
3842
=== Try to redeploy the bundle - should fail without --auto-approve
3943
>>> errcode [CLI] bundle deploy
4044
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files...

acceptance/bundle/deploy/pipeline/auto-approve/script

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,7 @@ $CLI jobs get "${JOB_ID}" | jq '{name: .settings.name}'
1919
title "Remove resources from configuration."
2020
trace rm resources.yml
2121

22+
trace $CLI bundle plan
23+
2224
title "Try to redeploy the bundle - should fail without --auto-approve"
2325
trace errcode $CLI bundle deploy

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ Deployment complete!
3030
}
3131
}
3232

33+
>>> [CLI] bundle plan --var=catalog=another_catalog
34+
recreate pipelines.foo
35+
recreate schemas.bar
36+
3337
=== Try to redeploy the bundle, pointing the DLT pipeline to a different UC catalog
3438
>>> errcode [CLI] bundle deploy --force-lock --var=catalog=another_catalog
3539
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files...

acceptance/bundle/deploy/pipeline/recreate/script

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,7 @@ title "Assert the pipeline is created"
1111
PIPELINE_ID=$($CLI bundle summary -o json | jq -r '.resources.pipelines.foo.id')
1212
trace $CLI pipelines get "${PIPELINE_ID}" | jq "{spec}"
1313

14+
trace $CLI bundle plan --var="catalog=another_catalog"
15+
1416
title "Try to redeploy the bundle, pointing the DLT pipeline to a different UC catalog"
1517
trace errcode $CLI bundle deploy --force-lock --var="catalog=another_catalog"

acceptance/bundle/resources/jobs/update/output.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11

2+
>>> [CLI] bundle plan
3+
create jobs.foo
4+
25
>>> [CLI] bundle deploy
36
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files...
47
Deploying resources...
58
Updating deployment state...
69
Deployment complete!
710

11+
>>> [CLI] bundle plan
12+
813
>>> print_requests
914
{
1015
"body": {
@@ -44,12 +49,17 @@ jobs foo id='[JOB_ID]' name='foo'
4449
=== Update trigger.periodic.unit and re-deploy
4550
>>> update_file.py databricks.yml DAYS HOURS
4651

52+
>>> [CLI] bundle plan
53+
update jobs.foo
54+
4755
>>> [CLI] bundle deploy
4856
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files...
4957
Deploying resources...
5058
Updating deployment state...
5159
Deployment complete!
5260

61+
>>> [CLI] bundle plan
62+
5363
>>> print_requests
5464
{
5565
"body": {

acceptance/bundle/resources/jobs/update/script

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
echo "*" > .gitignore
2+
trace $CLI bundle plan
23
trace $CLI bundle deploy
4+
trace $CLI bundle plan
35

46
print_requests() {
57
jq --sort-keys 'select(.method != "GET" and (.path | contains("/jobs")))' < out.requests.txt
@@ -11,7 +13,9 @@ trace print_requests
1113

1214
title "Update trigger.periodic.unit and re-deploy"
1315
trace update_file.py databricks.yml DAYS HOURS
16+
trace $CLI bundle plan
1417
trace $CLI bundle deploy
18+
trace $CLI bundle plan
1519
trace print_requests
1620

1721
title "Fetch job ID and verify remote state"

bundle/phases/deploy.go

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,26 +28,27 @@ import (
2828
"github.com/databricks/cli/libs/sync"
2929
)
3030

31-
func approvalForDeploy(ctx context.Context, b *bundle.Bundle) (bool, error) {
31+
func getActions(ctx context.Context, b *bundle.Bundle) ([]deployplan.Action, error) {
3232
var actions []deployplan.Action
3333
var err error
3434

3535
if b.DirectDeployment {
36-
actions, err = terranova.CalculateDeployActions(ctx, b)
37-
if err != nil {
38-
return false, err
39-
}
36+
return terranova.CalculateDeployActions(ctx, b)
4037
} else {
4138
tf := b.Terraform
4239
if tf == nil {
43-
return false, errors.New("terraform not initialized")
40+
return nil, errors.New("terraform not initialized")
4441
}
4542
actions, err = terraform.ShowPlanFile(ctx, tf, b.Plan.TerraformPlanPath)
46-
if err != nil {
47-
return false, err
48-
}
43+
return actions, err
4944
}
45+
}
5046

47+
func approvalForDeploy(ctx context.Context, b *bundle.Bundle) (bool, error) {
48+
actions, err := getActions(ctx, b)
49+
if err != nil {
50+
return false, err
51+
}
5152
b.Plan.Actions = actions
5253

5354
types := []deployplan.ActionType{deployplan.ActionTypeRecreate, deployplan.ActionTypeDelete}
@@ -258,6 +259,32 @@ func Deploy(ctx context.Context, b *bundle.Bundle, outputHandler sync.OutputHand
258259
bundle.ApplyContext(ctx, b, scripts.Execute(config.ScriptPostDeploy))
259260
}
260261

262+
func Diff(ctx context.Context, b *bundle.Bundle) []deployplan.Action {
263+
deployPrepare(ctx, b)
264+
if logdiag.HasError(ctx) {
265+
return nil
266+
}
267+
268+
if !b.DirectDeployment {
269+
bundle.ApplySeqContext(ctx, b,
270+
terraform.Interpolate(),
271+
terraform.Write(),
272+
terraform.Plan(terraform.PlanGoal("deploy")),
273+
)
274+
}
275+
276+
if logdiag.HasError(ctx) {
277+
return nil
278+
}
279+
280+
actions, err := getActions(ctx, b)
281+
if err != nil {
282+
logdiag.LogError(ctx, err)
283+
}
284+
285+
return actions
286+
}
287+
261288
// If there are more than 1 thousand of a resource type, do not
262289
// include more resources.
263290
// Since we have a timeout of 3 seconds, we cap the maximum number of IDs

bundle/terranova/plan.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,9 @@ func CalculateDeployActions(ctx context.Context, b *bundle.Bundle) ([]deployplan
117117
)
118118

119119
// Remained in state are resources that no longer present in the config
120-
for group, groupState := range state {
121-
for name := range groupState {
120+
for _, group := range utils.SortedKeys(state) {
121+
groupData := state[group]
122+
for _, name := range utils.SortedKeys(groupData) {
122123
actions = append(actions, deployplan.Action{
123124
Group: group,
124125
Name: name,

cmd/bundle/bundle.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,6 @@ func New() *cobra.Command {
2828
cmd.AddCommand(newDebugCommand())
2929
cmd.AddCommand(deployment.NewDeploymentCommand())
3030
cmd.AddCommand(newOpenCommand())
31+
cmd.AddCommand(newPlanCommand())
3132
return cmd
3233
}

cmd/bundle/plan.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package bundle
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/databricks/cli/bundle"
8+
"github.com/databricks/cli/bundle/config/validate"
9+
"github.com/databricks/cli/bundle/phases"
10+
"github.com/databricks/cli/cmd/bundle/utils"
11+
"github.com/databricks/cli/cmd/root"
12+
"github.com/databricks/cli/libs/cmdio"
13+
"github.com/databricks/cli/libs/logdiag"
14+
"github.com/spf13/cobra"
15+
)
16+
17+
func newPlanCommand() *cobra.Command {
18+
cmd := &cobra.Command{
19+
Use: "plan",
20+
Short: "Show deployment plan",
21+
Args: root.NoArgs,
22+
23+
// Output format may change without notice; main use case is in acceptance tests.
24+
// Today, this command also uploads libraries, which is not the intent here. We need to refactor
25+
// libraries.Upload() mutator to separate config mutation with actual upload.
26+
Hidden: true,
27+
}
28+
29+
var force bool
30+
var clusterId string
31+
cmd.Flags().BoolVar(&force, "force", false, "Force-override Git branch validation.")
32+
cmd.Flags().StringVar(&clusterId, "compute-id", "", "Override cluster in the deployment with the given compute ID.")
33+
cmd.Flags().StringVarP(&clusterId, "cluster-id", "c", "", "Override cluster in the deployment with the given cluster ID.")
34+
cmd.Flags().MarkDeprecated("compute-id", "use --cluster-id instead")
35+
36+
cmd.RunE = func(cmd *cobra.Command, args []string) error {
37+
ctx := logdiag.InitContext(cmd.Context())
38+
cmd.SetContext(ctx)
39+
40+
b := utils.ConfigureBundleWithVariables(cmd)
41+
if b == nil || logdiag.HasError(ctx) {
42+
return root.ErrAlreadyPrinted
43+
}
44+
45+
bundle.ApplyFuncContext(ctx, b, func(context.Context, *bundle.Bundle) {
46+
b.Config.Bundle.Force = force
47+
48+
if cmd.Flag("compute-id").Changed {
49+
b.Config.Bundle.ClusterId = clusterId
50+
}
51+
52+
if cmd.Flag("cluster-id").Changed {
53+
b.Config.Bundle.ClusterId = clusterId
54+
}
55+
})
56+
57+
phases.Initialize(ctx, b)
58+
59+
if logdiag.HasError(ctx) {
60+
return root.ErrAlreadyPrinted
61+
}
62+
63+
bundle.ApplyContext(ctx, b, validate.FastValidate())
64+
65+
if logdiag.HasError(ctx) {
66+
return root.ErrAlreadyPrinted
67+
}
68+
69+
phases.Build(ctx, b)
70+
71+
if logdiag.HasError(ctx) {
72+
return root.ErrAlreadyPrinted
73+
}
74+
75+
changes := phases.Diff(ctx, b)
76+
77+
for _, change := range changes {
78+
cmdio.LogString(ctx, fmt.Sprintf("%s %s.%s", change.ActionType, change.Group, change.Name))
79+
}
80+
81+
if logdiag.HasError(ctx) {
82+
return root.ErrAlreadyPrinted
83+
}
84+
85+
return nil
86+
}
87+
88+
return cmd
89+
}

0 commit comments

Comments
 (0)