Skip to content

Commit

Permalink
Use precomputed terraform plan for bundle deploy (#1640)
Browse files Browse the repository at this point in the history
# Changes
With #1413 we started to compute
and partially print the plan if it contained deletion of UC schemas.
This PR uses the precomputed plan to avoid double planning when actually
doing the terraform plan.

This fixes a performance regression introduced in
#1413.

# Tests

Tested manually.
1. Verified bundle deployment still works and deploys resources.
2. Verified that the precomputed plan is indeed being used by attaching
a debugger and removing the plan file right before the terraform apply
process is spawned and asserting that terraform apply fails because the
plan is not found.
  • Loading branch information
shreyas-goenka authored Jul 31, 2024
1 parent 1fb8e32 commit c454c2f
Show file tree
Hide file tree
Showing 5 changed files with 25 additions and 57 deletions.
21 changes: 12 additions & 9 deletions bundle/deploy/terraform/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"

"github.com/databricks/cli/bundle"
"github.com/databricks/cli/libs/cmdio"
"github.com/databricks/cli/libs/diag"
"github.com/databricks/cli/libs/log"
"github.com/hashicorp/terraform-exec/tfexec"
Expand All @@ -17,28 +16,32 @@ func (w *apply) Name() string {
}

func (w *apply) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
// return early if plan is empty
if b.Plan.IsEmpty {
log.Debugf(ctx, "No changes in plan. Skipping terraform apply.")
return nil
}

tf := b.Terraform
if tf == nil {
return diag.Errorf("terraform not initialized")
}

cmdio.LogString(ctx, "Deploying resources...")

err := tf.Init(ctx, tfexec.Upgrade(true))
if err != nil {
return diag.Errorf("terraform init: %v", err)
if b.Plan.Path == "" {
return diag.Errorf("no plan found")
}

err = tf.Apply(ctx)
// Apply terraform according to the computed plan
err := tf.Apply(ctx, tfexec.DirOrPlan(b.Plan.Path))
if err != nil {
return diag.Errorf("terraform apply: %v", err)
}

log.Infof(ctx, "Resource deployment completed")
log.Infof(ctx, "terraform apply completed")
return nil
}

// Apply returns a [bundle.Mutator] that runs the equivalent of `terraform apply`
// Apply returns a [bundle.Mutator] that runs the equivalent of `terraform apply ./plan`
// from the bundle's ephemeral working directory for Terraform.
func Apply() bundle.Mutator {
return &apply{}
Expand Down
46 changes: 0 additions & 46 deletions bundle/deploy/terraform/destroy.go

This file was deleted.

8 changes: 8 additions & 0 deletions bundle/deploy/terraform/state_push.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package terraform

import (
"context"
"errors"
"io/fs"
"os"
"path/filepath"

Expand Down Expand Up @@ -34,6 +36,12 @@ func (l *statePush) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostic

// Expect the state file to live under dir.
local, err := os.Open(filepath.Join(dir, TerraformStateFileName))
if errors.Is(err, fs.ErrNotExist) {
// The state file can be absent if terraform apply is skipped because
// there are no changes to apply in the plan.
log.Debugf(ctx, "Local terraform state file does not exist.")
return nil
}
if err != nil {
return diag.FromErr(err)
}
Expand Down
5 changes: 4 additions & 1 deletion bundle/phases/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@ func Deploy() bundle.Mutator {
// Core mutators that CRUD resources and modify deployment state. These
// mutators need informed consent if they are potentially destructive.
deployCore := bundle.Defer(
terraform.Apply(),
bundle.Seq(
bundle.LogString("Deploying resources..."),
terraform.Apply(),
),
bundle.Seq(
terraform.StatePush(),
terraform.Load(),
Expand Down
2 changes: 1 addition & 1 deletion bundle/phases/destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func approvalForDestroy(ctx context.Context, b *bundle.Bundle) (bool, error) {
func Destroy() bundle.Mutator {
// Core destructive mutators for destroy. These require informed user consent.
destroyCore := bundle.Seq(
terraform.Destroy(),
terraform.Apply(),
terraform.StatePush(),
files.Delete(),
bundle.LogString("Destroy complete!"),
Expand Down

0 comments on commit c454c2f

Please sign in to comment.