diff --git a/bundle/deploy/terraform/apply.go b/bundle/deploy/terraform/apply.go index ab868f76..c2f80628 100644 --- a/bundle/deploy/terraform/apply.go +++ b/bundle/deploy/terraform/apply.go @@ -16,19 +16,49 @@ func (w *apply) Name() string { } func (w *apply) Apply(ctx context.Context, b *bundle.Bundle) error { + // Return early if plan is empty + if b.Plan.IsEmpty { + cmdio.LogString(ctx, "Terraform plan is empty. Skipping apply.") + return nil + } + + // Error if terraform is not initialized tf := b.Terraform if tf == nil { return fmt.Errorf("terraform not initialized") } - cmdio.LogString(ctx, "Starting resource deployment") - - err := tf.Init(ctx, tfexec.Upgrade(true)) - if err != nil { - return fmt.Errorf("terraform init: %w", err) + // Error if plan is missing + if b.Plan.Path == "" { + return fmt.Errorf("no plan found") } - err = tf.Apply(ctx) + // Read and log plan file + plan, err := tf.ShowPlanFile(ctx, b.Plan.Path) + if err != nil { + return err + } + err = logPlan(ctx, plan) + if err != nil { + return err + } + + // Ask for confirmation, if needed + if !b.Plan.ConfirmApply { + b.Plan.ConfirmApply, err = cmdio.Ask(ctx, "Proceed with apply? [y/n]: ") + if err != nil { + return err + } + } + if !b.Plan.ConfirmApply { + // return if confirmation was not provided + return nil + } + + cmdio.LogString(ctx, "Starting deployment") + + // Apply terraform according to the plan + err = tf.Apply(ctx, tfexec.DirOrPlan(b.Plan.Path)) if err != nil { return fmt.Errorf("terraform apply: %w", err) } diff --git a/bundle/deploy/terraform/plan.go b/bundle/deploy/terraform/plan.go index a725b4aa..0693142b 100644 --- a/bundle/deploy/terraform/plan.go +++ b/bundle/deploy/terraform/plan.go @@ -9,8 +9,38 @@ import ( "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/terraform" "github.com/hashicorp/terraform-exec/tfexec" + tfjson "github.com/hashicorp/terraform-json" ) +func logPlan(ctx context.Context, plan *tfjson.Plan) error { + for _, change := range plan.ResourceChanges { + tfActions := change.Change.Actions + if tfActions.Read() || tfActions.NoOp() { + continue + } + + var action string + switch { + case tfActions.Update(): + action = "update" + case tfActions.Create(): + action = "create" + case tfActions.Delete(): + action = "delete" + case tfActions.Replace(): + action = "replace" + default: + fmt.Errorf("unknown terraform actions: %s", tfActions) + } + + err := cmdio.RenderWithTemplate(ctx, change, fmt.Sprintf("%s {{.Type}} {{.Name}}\n", action)) + if err != nil { + return err + } + } + return nil +} + type PlanGoal string var ( diff --git a/bundle/phases/deploy.go b/bundle/phases/deploy.go index f2692ea9..116fd38b 100644 --- a/bundle/phases/deploy.go +++ b/bundle/phases/deploy.go @@ -19,6 +19,7 @@ func Deploy() bundle.Mutator { terraform.Interpolate(), terraform.Write(), terraform.StatePull(), + terraform.Plan(terraform.PlanGoal("deploy")), terraform.Apply(), terraform.StatePush(), ),