github.com/databricks/cli@v0.203.0/bundle/deploy/terraform/destroy.go (about) 1 package terraform 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 8 "github.com/databricks/cli/bundle" 9 "github.com/databricks/cli/libs/cmdio" 10 "github.com/fatih/color" 11 "github.com/hashicorp/terraform-exec/tfexec" 12 tfjson "github.com/hashicorp/terraform-json" 13 ) 14 15 type PlanResourceChange struct { 16 ResourceType string `json:"resource_type"` 17 Action string `json:"action"` 18 ResourceName string `json:"resource_name"` 19 } 20 21 func (c *PlanResourceChange) String() string { 22 result := strings.Builder{} 23 switch c.Action { 24 case "delete": 25 result.WriteString(" delete ") 26 default: 27 result.WriteString(c.Action + " ") 28 } 29 switch c.ResourceType { 30 case "databricks_job": 31 result.WriteString("job ") 32 case "databricks_pipeline": 33 result.WriteString("pipeline ") 34 default: 35 result.WriteString(c.ResourceType + " ") 36 } 37 result.WriteString(c.ResourceName) 38 return result.String() 39 } 40 41 func (c *PlanResourceChange) IsInplaceSupported() bool { 42 return false 43 } 44 45 func logDestroyPlan(ctx context.Context, changes []*tfjson.ResourceChange) error { 46 cmdio.LogString(ctx, "The following resources will be removed:") 47 for _, c := range changes { 48 if c.Change.Actions.Delete() { 49 cmdio.Log(ctx, &PlanResourceChange{ 50 ResourceType: c.Type, 51 Action: "delete", 52 ResourceName: c.Name, 53 }) 54 } 55 } 56 return nil 57 } 58 59 type destroy struct{} 60 61 func (w *destroy) Name() string { 62 return "terraform.Destroy" 63 } 64 65 func (w *destroy) Apply(ctx context.Context, b *bundle.Bundle) error { 66 // return early if plan is empty 67 if b.Plan.IsEmpty { 68 cmdio.LogString(ctx, "No resources to destroy in plan. Skipping destroy!") 69 return nil 70 } 71 72 tf := b.Terraform 73 if tf == nil { 74 return fmt.Errorf("terraform not initialized") 75 } 76 77 // read plan file 78 plan, err := tf.ShowPlanFile(ctx, b.Plan.Path) 79 if err != nil { 80 return err 81 } 82 83 // print the resources that will be destroyed 84 err = logDestroyPlan(ctx, plan.ResourceChanges) 85 if err != nil { 86 return err 87 } 88 89 // Ask for confirmation, if needed 90 if !b.Plan.ConfirmApply { 91 red := color.New(color.FgRed).SprintFunc() 92 b.Plan.ConfirmApply, err = cmdio.Ask(ctx, fmt.Sprintf("\nThis will permanently %s resources! Proceed?", red("destroy"))) 93 if err != nil { 94 return err 95 } 96 } 97 98 // return if confirmation was not provided 99 if !b.Plan.ConfirmApply { 100 return nil 101 } 102 103 if b.Plan.Path == "" { 104 return fmt.Errorf("no plan found") 105 } 106 107 cmdio.LogString(ctx, "Starting to destroy resources") 108 109 // Apply terraform according to the computed destroy plan 110 err = tf.Apply(ctx, tfexec.DirOrPlan(b.Plan.Path)) 111 if err != nil { 112 return fmt.Errorf("terraform destroy: %w", err) 113 } 114 115 cmdio.LogString(ctx, "Successfully destroyed resources!") 116 return nil 117 } 118 119 // Destroy returns a [bundle.Mutator] that runs the conceptual equivalent of 120 // `terraform destroy ./plan` from the bundle's ephemeral working directory for Terraform. 121 func Destroy() bundle.Mutator { 122 return &destroy{} 123 }