From a64b88b93c7e05dd1f1361a941043e6cb6f9c6d7 Mon Sep 17 00:00:00 2001 From: Wojciech Pratkowiecki Date: Wed, 2 Oct 2024 20:01:01 +0200 Subject: [PATCH 1/4] Add --dry-run option for bundle deplot --- bundle/bundle.go | 3 +++ bundle/phases/deploy.go | 9 +++++++++ cmd/bundle/deploy.go | 3 +++ internal/bundle/deploy_test.go | 30 +++++++++++++++++++++++++++++- 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/bundle/bundle.go b/bundle/bundle.go index 85625568..26065ab4 100644 --- a/bundle/bundle.go +++ b/bundle/bundle.go @@ -81,6 +81,9 @@ type Bundle struct { // files AutoApprove bool + // if true, the deploy changes are presented, but not applied + DryRun bool + // Tagging is used to normalize tag keys and values. // The implementation depends on the cloud being targeted. Tagging tags.Cloud diff --git a/bundle/phases/deploy.go b/bundle/phases/deploy.go index cb0ecf75..7fd231d3 100644 --- a/bundle/phases/deploy.go +++ b/bundle/phases/deploy.go @@ -115,6 +115,15 @@ properties such as the 'catalog' or 'storage' are changed:` return true, nil } + if b.DryRun { + cmdio.LogString(ctx, "Following changes would be deployed:") + _, err := tf.ShowPlanFile(ctx, b.Plan.Path) + if err != nil { + return false, err + } + return false, nil + } + if !cmdio.IsPromptSupported(ctx) { return false, fmt.Errorf("the deployment requires destructive actions, but current console does not support prompting. Please specify --auto-approve if you would like to skip prompts and proceed") } diff --git a/cmd/bundle/deploy.go b/cmd/bundle/deploy.go index f1c85cb3..1dc523dc 100644 --- a/cmd/bundle/deploy.go +++ b/cmd/bundle/deploy.go @@ -26,6 +26,7 @@ func newDeployCommand() *cobra.Command { var failOnActiveRuns bool var clusterId string var autoApprove bool + var dryRyn bool var verbose bool cmd.Flags().BoolVar(&force, "force", false, "Force-override Git branch validation.") cmd.Flags().BoolVar(&forceLock, "force-lock", false, "Force acquisition of deployment lock.") @@ -33,6 +34,7 @@ func newDeployCommand() *cobra.Command { cmd.Flags().StringVar(&clusterId, "compute-id", "", "Override cluster in the deployment with the given compute ID.") cmd.Flags().StringVarP(&clusterId, "cluster-id", "c", "", "Override cluster in the deployment with the given cluster ID.") cmd.Flags().BoolVar(&autoApprove, "auto-approve", false, "Skip interactive approvals that might be required for deployment.") + cmd.Flags().BoolVar(&dryRyn, "dry-run", false, "Present changes that would be deployed without applying.") cmd.Flags().MarkDeprecated("compute-id", "use --cluster-id instead") cmd.Flags().BoolVar(&verbose, "verbose", false, "Enable verbose output.") // Verbose flag currently only affects file sync output, it's used by the vscode extension @@ -47,6 +49,7 @@ func newDeployCommand() *cobra.Command { b.Config.Bundle.Force = force b.Config.Bundle.Deployment.Lock.Force = forceLock b.AutoApprove = autoApprove + b.DryRun = dryRyn if cmd.Flag("compute-id").Changed { b.Config.Bundle.ClusterId = clusterId diff --git a/internal/bundle/deploy_test.go b/internal/bundle/deploy_test.go index 88543585..e1eff60d 100644 --- a/internal/bundle/deploy_test.go +++ b/internal/bundle/deploy_test.go @@ -127,6 +127,34 @@ func TestAccBundleDeployUcSchemaFailsWithoutAutoApprove(t *testing.T) { assert.Contains(t, stdout.String(), "the deployment requires destructive actions, but current console does not support prompting. Please specify --auto-approve if you would like to skip prompts and proceed") } +func TestAccBundleDeployUcSchemaDryRun(t *testing.T) { + ctx, wt := acc.UcWorkspaceTest(t) + w := wt.W + + uniqueId := uuid.New().String() + schemaName := "test-schema-" + uniqueId + catalogName := "main" + + bundleRoot := setupUcSchemaBundle(t, ctx, w, uniqueId) + + // Remove the UC schema from the resource configuration. + err := os.Remove(filepath.Join(bundleRoot, "schema.yml")) + require.NoError(t, err) + + // Redeploy the bundle + t.Setenv("BUNDLE_ROOT", bundleRoot) + t.Setenv("TERM", "dumb") + c := internal.NewCobraTestRunnerWithContext(t, ctx, "bundle", "deploy", "--dry0run") + stdout, _, err := c.Run() + + // Assert the schema is deleted + _, err = w.Schemas.GetByFullName(ctx, strings.Join([]string{catalogName, schemaName}, ".")) + apiErr := &apierr.APIError{} + assert.Contains(t, stdout.String(), "Following changes would be deployed:") + assert.True(t, errors.As(err, &apiErr)) + assert.Equal(t, "SCHEMA_DOES_NOT_EXIST", apiErr.ErrorCode) +} + func TestAccBundlePipelineDeleteWithoutAutoApprove(t *testing.T) { ctx, wt := acc.WorkspaceTest(t) w := wt.W @@ -236,7 +264,7 @@ func TestAccDeployBasicBundleLogs(t *testing.T) { stdout, stderr := blackBoxRun(t, root, "bundle", "deploy") assert.Equal(t, strings.Join([]string{ - fmt.Sprintf("Uploading bundle files to /Workspace/Users/%s/.bundle/%s/files...", currentUser.UserName, uniqueId), + fmt.Sprintf("Uploading bundle files to /Users/%s/.bundle/%s/files...", currentUser.UserName, uniqueId), "Deploying resources...", "Updating deployment state...", "Deployment complete!\n", From 12259b9837e9ef933755dcb2817540e5b8df19a6 Mon Sep 17 00:00:00 2001 From: Wojciech Pratkowiecki Date: Wed, 2 Oct 2024 20:08:31 +0200 Subject: [PATCH 2/4] Add test --- internal/bundle/deploy_test.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/internal/bundle/deploy_test.go b/internal/bundle/deploy_test.go index e1eff60d..923cd633 100644 --- a/internal/bundle/deploy_test.go +++ b/internal/bundle/deploy_test.go @@ -127,7 +127,7 @@ func TestAccBundleDeployUcSchemaFailsWithoutAutoApprove(t *testing.T) { assert.Contains(t, stdout.String(), "the deployment requires destructive actions, but current console does not support prompting. Please specify --auto-approve if you would like to skip prompts and proceed") } -func TestAccBundleDeployUcSchemaDryRun(t *testing.T) { +func TestAccBundleDeployUcSchemaIsNotAppliedWhenDryRun(t *testing.T) { ctx, wt := acc.UcWorkspaceTest(t) w := wt.W @@ -141,18 +141,17 @@ func TestAccBundleDeployUcSchemaDryRun(t *testing.T) { err := os.Remove(filepath.Join(bundleRoot, "schema.yml")) require.NoError(t, err) - // Redeploy the bundle + // Run dry-run for the bundle deployment t.Setenv("BUNDLE_ROOT", bundleRoot) t.Setenv("TERM", "dumb") - c := internal.NewCobraTestRunnerWithContext(t, ctx, "bundle", "deploy", "--dry0run") + c := internal.NewCobraTestRunnerWithContext(t, ctx, "bundle", "deploy", "--dry-run") stdout, _, err := c.Run() + require.NoError(t, err) - // Assert the schema is deleted + // Assert the schema was not deleted _, err = w.Schemas.GetByFullName(ctx, strings.Join([]string{catalogName, schemaName}, ".")) - apiErr := &apierr.APIError{} + require.NoError(t, err) assert.Contains(t, stdout.String(), "Following changes would be deployed:") - assert.True(t, errors.As(err, &apiErr)) - assert.Equal(t, "SCHEMA_DOES_NOT_EXIST", apiErr.ErrorCode) } func TestAccBundlePipelineDeleteWithoutAutoApprove(t *testing.T) { @@ -264,7 +263,7 @@ func TestAccDeployBasicBundleLogs(t *testing.T) { stdout, stderr := blackBoxRun(t, root, "bundle", "deploy") assert.Equal(t, strings.Join([]string{ - fmt.Sprintf("Uploading bundle files to /Users/%s/.bundle/%s/files...", currentUser.UserName, uniqueId), + fmt.Sprintf("Uploading bundle files to /Workspace/Users/%s/.bundle/%s/files...", currentUser.UserName, uniqueId), "Deploying resources...", "Updating deployment state...", "Deployment complete!\n", From 9e53f117d263f3352c42d4f7d608dc4649a67df6 Mon Sep 17 00:00:00 2001 From: Wojciech Pratkowiecki Date: Thu, 3 Oct 2024 08:48:45 +0200 Subject: [PATCH 3/4] Move dry run check before possible reutrns --- bundle/phases/deploy.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/bundle/phases/deploy.go b/bundle/phases/deploy.go index 7fd231d3..9cf5eeaa 100644 --- a/bundle/phases/deploy.go +++ b/bundle/phases/deploy.go @@ -63,6 +63,15 @@ func approvalForDeploy(ctx context.Context, b *bundle.Bundle) (bool, error) { return false, err } + if b.DryRun { + cmdio.LogString(ctx, "Following changes would be deployed:") + _, err := tf.ShowPlanFile(ctx, b.Plan.Path) + if err != nil { + return false, err + } + return false, nil + } + schemaActions := parseTerraformActions(plan.ResourceChanges, func(typ string, actions tfjson.Actions) bool { // Filter in only UC schema resources. if typ != "databricks_schema" { @@ -115,15 +124,6 @@ properties such as the 'catalog' or 'storage' are changed:` return true, nil } - if b.DryRun { - cmdio.LogString(ctx, "Following changes would be deployed:") - _, err := tf.ShowPlanFile(ctx, b.Plan.Path) - if err != nil { - return false, err - } - return false, nil - } - if !cmdio.IsPromptSupported(ctx) { return false, fmt.Errorf("the deployment requires destructive actions, but current console does not support prompting. Please specify --auto-approve if you would like to skip prompts and proceed") } From fd67acbc7f88895bd3c8a81ce189a8bc953bb7f0 Mon Sep 17 00:00:00 2001 From: Wojciech Pratkowiecki Date: Thu, 3 Oct 2024 09:37:11 +0200 Subject: [PATCH 4/4] Move show plan to a function --- bundle/phases/deploy.go | 43 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/bundle/phases/deploy.go b/bundle/phases/deploy.go index 9cf5eeaa..444834ba 100644 --- a/bundle/phases/deploy.go +++ b/bundle/phases/deploy.go @@ -51,6 +51,43 @@ func parseTerraformActions(changes []*tfjson.ResourceChange, toInclude func(typ return res } +func showDryRunChanges(ctx context.Context, plan *tfjson.Plan) { + updateActions := make([]terraformlib.Action, 0) + for _, rc := range plan.ResourceChanges { + if rc.Change.Actions.Update() { + updateActions = append(updateActions, terraformlib.Action{ + Action: terraformlib.ActionTypeUpdate, + ResourceType: rc.Type, + ResourceName: rc.Name, + }) + } + } + createActions := make([]terraformlib.Action, 0) + for _, rc := range plan.ResourceChanges { + if rc.Change.Actions.Create() { + createActions = append(createActions, terraformlib.Action{ + Action: terraformlib.ActionTypeCreate, + ResourceType: rc.Type, + ResourceName: rc.Name, + }) + } + } + if len(updateActions) > 0 { + cmdio.LogString(ctx, "The following resources will be updated:") + for _, a := range updateActions { + cmdio.Log(ctx, a) + } + cmdio.LogString(ctx, "") + } + if len(createActions) > 0 { + cmdio.LogString(ctx, "The following resources will be created:") + for _, a := range createActions { + cmdio.Log(ctx, a) + } + cmdio.LogString(ctx, "") + } +} + func approvalForDeploy(ctx context.Context, b *bundle.Bundle) (bool, error) { tf := b.Terraform if tf == nil { @@ -64,11 +101,7 @@ func approvalForDeploy(ctx context.Context, b *bundle.Bundle) (bool, error) { } if b.DryRun { - cmdio.LogString(ctx, "Following changes would be deployed:") - _, err := tf.ShowPlanFile(ctx, b.Plan.Path) - if err != nil { - return false, err - } + showDryRunChanges(ctx, plan) return false, nil }