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  }