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 e623c364..068636ac 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 { @@ -63,6 +100,11 @@ func approvalForDeploy(ctx context.Context, b *bundle.Bundle) (bool, error) { return false, err } + if b.DryRun { + showDryRunChanges(ctx, plan) + return false, nil + } + schemaActions := parseTerraformActions(plan.ResourceChanges, func(typ string, actions tfjson.Actions) bool { // Filter in only UC schema resources. if typ != "databricks_schema" { diff --git a/cmd/bundle/deploy.go b/cmd/bundle/deploy.go index a25e02f6..20468aef 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..923cd633 100644 --- a/internal/bundle/deploy_test.go +++ b/internal/bundle/deploy_test.go @@ -127,6 +127,33 @@ 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 TestAccBundleDeployUcSchemaIsNotAppliedWhenDryRun(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) + + // Run dry-run for the bundle deployment + t.Setenv("BUNDLE_ROOT", bundleRoot) + t.Setenv("TERM", "dumb") + c := internal.NewCobraTestRunnerWithContext(t, ctx, "bundle", "deploy", "--dry-run") + stdout, _, err := c.Run() + require.NoError(t, err) + + // Assert the schema was not deleted + _, err = w.Schemas.GetByFullName(ctx, strings.Join([]string{catalogName, schemaName}, ".")) + require.NoError(t, err) + assert.Contains(t, stdout.String(), "Following changes would be deployed:") +} + func TestAccBundlePipelineDeleteWithoutAutoApprove(t *testing.T) { ctx, wt := acc.WorkspaceTest(t) w := wt.W