mirror of https://github.com/databricks/cli.git
149 lines
3.8 KiB
Go
149 lines
3.8 KiB
Go
package plan
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"path/filepath"
|
|
"slices"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/databricks/cli/libs/log"
|
|
"github.com/hashicorp/terraform-exec/tfexec"
|
|
tfjson "github.com/hashicorp/terraform-json"
|
|
)
|
|
|
|
const planDirName = "plan"
|
|
|
|
// TODO: start using this. Also clean up the fields in the struct below.
|
|
type TerraformPlanOpts struct {
|
|
// If true, the plan will be a IsDestroy plan.
|
|
IsDestroy bool
|
|
|
|
// Path where the terraform project we'll compute the plan for is rooted at.
|
|
Root string
|
|
|
|
// Executable executable to compute the plan with.
|
|
Executable *tfexec.Terraform
|
|
}
|
|
|
|
// TODO: Remove any unnecessary fields in here.
|
|
type terraformPlan struct {
|
|
ctx context.Context
|
|
|
|
opts TerraformPlanOpts
|
|
|
|
// Path to the computed plan.
|
|
planPath string
|
|
|
|
// In memory representation of the computed plan, obtained by running terraform show.
|
|
tfPlan *tfjson.Plan
|
|
|
|
// TODO: Can we invert this?
|
|
notEmpty bool
|
|
}
|
|
|
|
// NewTerraformPlan creates a new Terraform plan for a terraform project rooted
|
|
// at the given path. If destroy is true, the plan will be a destroy plan.
|
|
func NewTerraformPlan(ctx context.Context, opts TerraformPlanOpts) (Plan, error) {
|
|
plan := &terraformPlan{
|
|
ctx: ctx,
|
|
opts: opts,
|
|
}
|
|
|
|
if plan.opts.Executable == nil {
|
|
return nil, fmt.Errorf("terraform executable not provided")
|
|
}
|
|
if plan.opts.Root == "" {
|
|
return nil, fmt.Errorf("root path to compute the terraform plan not provided")
|
|
}
|
|
|
|
// Initialize the terraform project
|
|
err := opts.Executable.Init(ctx, tfexec.Upgrade(true))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("terraform init: %w", err)
|
|
}
|
|
|
|
// Path where the computed plan will be persisted.
|
|
planPath := filepath.Join(opts.Root, planDirName)
|
|
|
|
log.Debugf(ctx, "Computing terraform plan")
|
|
plan.notEmpty, err = opts.Executable.Plan(ctx, tfexec.Destroy(opts.IsDestroy), tfexec.Out(planPath))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("terraform plan: %w", err)
|
|
}
|
|
|
|
log.Debugf(ctx, "Loading terraform plan by running terraform show")
|
|
plan.tfPlan, err = opts.Executable.ShowPlanFile(ctx, planPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("terraform show: %w", err)
|
|
}
|
|
|
|
// Persist the plan path
|
|
plan.planPath = planPath
|
|
|
|
return plan, nil
|
|
}
|
|
|
|
func (p *terraformPlan) Path() string {
|
|
return p.planPath
|
|
}
|
|
|
|
// TODO: Add tests for this functionality.
|
|
func (p *terraformPlan) ActionsByTypes(query ...ActionType) []Action {
|
|
actions := make([]Action, 0)
|
|
for _, rc := range p.tfPlan.ResourceChanges {
|
|
if rc.Type == "databricks_permissions" {
|
|
// We don't need to print permissions changes.
|
|
continue
|
|
}
|
|
|
|
if rc.Type == "databricks_grants" {
|
|
// We don't need to print grants changes.
|
|
continue
|
|
}
|
|
|
|
tfActions := rc.Change.Actions
|
|
switch {
|
|
case slices.Contains(query, ActionTypeDelete) && tfActions.Delete():
|
|
actions = append(actions, Action{
|
|
rtype: strings.TrimPrefix(rc.Type, "databricks_"),
|
|
rname: rc.Name,
|
|
atype: ActionTypeDelete,
|
|
})
|
|
case slices.Contains(query, ActionTypeRecreate) && tfActions.Replace():
|
|
actions = append(actions, Action{
|
|
rtype: strings.TrimPrefix(rc.Type, "databricks_"),
|
|
rname: rc.Name,
|
|
atype: ActionTypeRecreate,
|
|
})
|
|
default:
|
|
// We don't need to track other action types yet.
|
|
}
|
|
}
|
|
|
|
// Sort by action type first, then by resource type, and finally by resource name
|
|
sort.Slice(actions, func(i, j int) bool {
|
|
if actions[i].atype != actions[j].atype {
|
|
return actions[i].atype < actions[j].atype
|
|
}
|
|
if actions[i].rtype != actions[j].rtype {
|
|
return actions[i].rtype < actions[j].rtype
|
|
}
|
|
return actions[i].rname < actions[j].rname
|
|
})
|
|
return actions
|
|
}
|
|
|
|
func (p *terraformPlan) IsEmpty() bool {
|
|
return !p.notEmpty
|
|
}
|
|
|
|
func (p *terraformPlan) Apply() error {
|
|
err := p.opts.Executable.Apply(p.ctx, tfexec.DirOrPlan(p.planPath))
|
|
if err != nil {
|
|
return fmt.Errorf("terraform apply: %w", err)
|
|
}
|
|
return nil
|
|
}
|