mirror of https://github.com/databricks/cli.git
Use precomputed terraform plan for `bundle deploy` (#1640)
# Changes With https://github.com/databricks/cli/pull/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 https://github.com/databricks/cli/pull/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.
This commit is contained in:
parent
1fb8e324d5
commit
c454c2fd10
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/databricks/cli/bundle"
|
"github.com/databricks/cli/bundle"
|
||||||
"github.com/databricks/cli/libs/cmdio"
|
|
||||||
"github.com/databricks/cli/libs/diag"
|
"github.com/databricks/cli/libs/diag"
|
||||||
"github.com/databricks/cli/libs/log"
|
"github.com/databricks/cli/libs/log"
|
||||||
"github.com/hashicorp/terraform-exec/tfexec"
|
"github.com/hashicorp/terraform-exec/tfexec"
|
||||||
|
@ -17,28 +16,32 @@ func (w *apply) Name() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *apply) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
|
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
|
tf := b.Terraform
|
||||||
if tf == nil {
|
if tf == nil {
|
||||||
return diag.Errorf("terraform not initialized")
|
return diag.Errorf("terraform not initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdio.LogString(ctx, "Deploying resources...")
|
if b.Plan.Path == "" {
|
||||||
|
return diag.Errorf("no plan found")
|
||||||
err := tf.Init(ctx, tfexec.Upgrade(true))
|
|
||||||
if err != nil {
|
|
||||||
return diag.Errorf("terraform init: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tf.Apply(ctx)
|
// Apply terraform according to the computed plan
|
||||||
|
err := tf.Apply(ctx, tfexec.DirOrPlan(b.Plan.Path))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return diag.Errorf("terraform apply: %v", err)
|
return diag.Errorf("terraform apply: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof(ctx, "Resource deployment completed")
|
log.Infof(ctx, "terraform apply completed")
|
||||||
return nil
|
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.
|
// from the bundle's ephemeral working directory for Terraform.
|
||||||
func Apply() bundle.Mutator {
|
func Apply() bundle.Mutator {
|
||||||
return &apply{}
|
return &apply{}
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
package terraform
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/databricks/cli/bundle"
|
|
||||||
"github.com/databricks/cli/libs/diag"
|
|
||||||
"github.com/databricks/cli/libs/log"
|
|
||||||
"github.com/hashicorp/terraform-exec/tfexec"
|
|
||||||
)
|
|
||||||
|
|
||||||
type destroy struct{}
|
|
||||||
|
|
||||||
func (w *destroy) Name() string {
|
|
||||||
return "terraform.Destroy"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *destroy) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
|
|
||||||
// return early if plan is empty
|
|
||||||
if b.Plan.IsEmpty {
|
|
||||||
log.Debugf(ctx, "No resources to destroy in plan. Skipping destroy.")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
tf := b.Terraform
|
|
||||||
if tf == nil {
|
|
||||||
return diag.Errorf("terraform not initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.Plan.Path == "" {
|
|
||||||
return diag.Errorf("no plan found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply terraform according to the computed destroy plan
|
|
||||||
err := tf.Apply(ctx, tfexec.DirOrPlan(b.Plan.Path))
|
|
||||||
if err != nil {
|
|
||||||
return diag.Errorf("terraform destroy: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destroy returns a [bundle.Mutator] that runs the conceptual equivalent of
|
|
||||||
// `terraform destroy ./plan` from the bundle's ephemeral working directory for Terraform.
|
|
||||||
func Destroy() bundle.Mutator {
|
|
||||||
return &destroy{}
|
|
||||||
}
|
|
|
@ -2,6 +2,8 @@ package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
@ -34,6 +36,12 @@ func (l *statePush) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostic
|
||||||
|
|
||||||
// Expect the state file to live under dir.
|
// Expect the state file to live under dir.
|
||||||
local, err := os.Open(filepath.Join(dir, TerraformStateFileName))
|
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 {
|
if err != nil {
|
||||||
return diag.FromErr(err)
|
return diag.FromErr(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,7 +92,10 @@ func Deploy() bundle.Mutator {
|
||||||
// Core mutators that CRUD resources and modify deployment state. These
|
// Core mutators that CRUD resources and modify deployment state. These
|
||||||
// mutators need informed consent if they are potentially destructive.
|
// mutators need informed consent if they are potentially destructive.
|
||||||
deployCore := bundle.Defer(
|
deployCore := bundle.Defer(
|
||||||
terraform.Apply(),
|
bundle.Seq(
|
||||||
|
bundle.LogString("Deploying resources..."),
|
||||||
|
terraform.Apply(),
|
||||||
|
),
|
||||||
bundle.Seq(
|
bundle.Seq(
|
||||||
terraform.StatePush(),
|
terraform.StatePush(),
|
||||||
terraform.Load(),
|
terraform.Load(),
|
||||||
|
|
|
@ -82,7 +82,7 @@ func approvalForDestroy(ctx context.Context, b *bundle.Bundle) (bool, error) {
|
||||||
func Destroy() bundle.Mutator {
|
func Destroy() bundle.Mutator {
|
||||||
// Core destructive mutators for destroy. These require informed user consent.
|
// Core destructive mutators for destroy. These require informed user consent.
|
||||||
destroyCore := bundle.Seq(
|
destroyCore := bundle.Seq(
|
||||||
terraform.Destroy(),
|
terraform.Apply(),
|
||||||
terraform.StatePush(),
|
terraform.StatePush(),
|
||||||
files.Delete(),
|
files.Delete(),
|
||||||
bundle.LogString("Destroy complete!"),
|
bundle.LogString("Destroy complete!"),
|
||||||
|
|
Loading…
Reference in New Issue