Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 3 additions & 20 deletions bundle/deploy/files/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import (
"github.com/databricks/cli/bundle"
"github.com/databricks/cli/libs/cmdio"
"github.com/databricks/cli/libs/diag"
"github.com/databricks/cli/libs/log"
"github.com/databricks/cli/libs/sync"
"github.com/databricks/databricks-sdk-go/service/workspace"
"github.com/fatih/color"
)

type delete struct{}
Expand All @@ -22,24 +22,7 @@ func (m *delete) Name() string {
}

func (m *delete) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
// Do not delete files if terraform destroy was not consented
if !b.Plan.IsEmpty && !b.Plan.ConfirmApply {
return nil
}

cmdio.LogString(ctx, "Starting deletion of remote bundle files")
cmdio.LogString(ctx, fmt.Sprintf("Bundle remote directory is %s", b.Config.Workspace.RootPath))

red := color.New(color.FgRed).SprintFunc()
if !b.AutoApprove {
proceed, err := cmdio.AskYesOrNo(ctx, fmt.Sprintf("\n%s and all files in it will be %s Proceed?", b.Config.Workspace.RootPath, red("deleted permanently!")))
if err != nil {
return diag.FromErr(err)
}
if !proceed {
return nil
}
}
cmdio.LogString(ctx, "Deleting files...")

err := b.WorkspaceClient().Workspace.Delete(ctx, workspace.Delete{
Path: b.Config.Workspace.RootPath,
Expand All @@ -55,7 +38,7 @@ func (m *delete) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
return diag.FromErr(err)
}

cmdio.LogString(ctx, "Successfully deleted files!")
log.Debugf(ctx, "Successfully deleted files!")
return nil
}

Expand Down
83 changes: 5 additions & 78 deletions bundle/deploy/terraform/destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,14 @@ package terraform

import (
"context"
"fmt"
"strings"

"github.com/databricks/cli/bundle"
"github.com/databricks/cli/libs/cmdio"
"github.com/databricks/cli/libs/diag"
"github.com/fatih/color"
"github.com/databricks/cli/libs/log"
"github.com/hashicorp/terraform-exec/tfexec"
tfjson "github.com/hashicorp/terraform-json"
)

type PlanResourceChange struct {
ResourceType string `json:"resource_type"`
Action string `json:"action"`
ResourceName string `json:"resource_name"`
}

func (c *PlanResourceChange) String() string {
result := strings.Builder{}
switch c.Action {
case "delete":
result.WriteString(" delete ")
default:
result.WriteString(c.Action + " ")
}
switch c.ResourceType {
case "databricks_job":
result.WriteString("job ")
case "databricks_pipeline":
result.WriteString("pipeline ")
default:
result.WriteString(c.ResourceType + " ")
}
result.WriteString(c.ResourceName)
return result.String()
}

func (c *PlanResourceChange) IsInplaceSupported() bool {
return false
}

func logDestroyPlan(ctx context.Context, changes []*tfjson.ResourceChange) error {
cmdio.LogString(ctx, "The following resources will be removed:")
for _, c := range changes {
if c.Change.Actions.Delete() {
cmdio.Log(ctx, &PlanResourceChange{
ResourceType: c.Type,
Action: "delete",
ResourceName: c.Name,
})
}
}
return nil
}

type destroy struct{}

func (w *destroy) Name() string {
Expand All @@ -66,7 +19,7 @@ func (w *destroy) Name() string {
func (w *destroy) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
// return early if plan is empty
if b.Plan.IsEmpty {
cmdio.LogString(ctx, "No resources to destroy in plan. Skipping destroy!")
cmdio.LogString(ctx, "No resources to destroy")
return nil
}

Expand All @@ -75,45 +28,19 @@ func (w *destroy) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics
return diag.Errorf("terraform not initialized")
}

// read plan file
plan, err := tf.ShowPlanFile(ctx, b.Plan.Path)
if err != nil {
return diag.FromErr(err)
}

// print the resources that will be destroyed
err = logDestroyPlan(ctx, plan.ResourceChanges)
if err != nil {
return diag.FromErr(err)
}

// Ask for confirmation, if needed
if !b.Plan.ConfirmApply {
red := color.New(color.FgRed).SprintFunc()
b.Plan.ConfirmApply, err = cmdio.AskYesOrNo(ctx, fmt.Sprintf("\nThis will permanently %s resources! Proceed?", red("destroy")))
if err != nil {
return diag.FromErr(err)
}
}

// return if confirmation was not provided
if !b.Plan.ConfirmApply {
return nil
}

if b.Plan.Path == "" {
return diag.Errorf("no plan found")
}

cmdio.LogString(ctx, "Starting to destroy resources")
log.Debugf(ctx, "Starting to destroy resources")

// Apply terraform according to the computed destroy plan
err = tf.Apply(ctx, tfexec.DirOrPlan(b.Plan.Path))
err := tf.Apply(ctx, tfexec.DirOrPlan(b.Plan.Path))
if err != nil {
return diag.Errorf("terraform destroy: %v", err)
}

cmdio.LogString(ctx, "Successfully destroyed resources!")
log.Debugf(ctx, "Successfully destroyed resources!")
return nil
}

Expand Down
11 changes: 5 additions & 6 deletions bundle/deploy/terraform/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import (
"path/filepath"

"github.com/databricks/cli/bundle"
"github.com/databricks/cli/libs/cmdio"
"github.com/databricks/cli/libs/diag"
"github.com/databricks/cli/libs/log"
"github.com/databricks/cli/libs/terraform"
"github.com/hashicorp/terraform-exec/tfexec"
)
Expand All @@ -33,7 +33,7 @@ func (p *plan) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
return diag.Errorf("terraform not initialized")
}

cmdio.LogString(ctx, "Starting plan computation")
log.Debugf(ctx, "Starting plan computation")

err := tf.Init(ctx, tfexec.Upgrade(true))
if err != nil {
Expand All @@ -55,12 +55,11 @@ func (p *plan) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {

// Set plan in main bundle struct for downstream mutators
b.Plan = &terraform.Plan{
Path: planPath,
ConfirmApply: b.AutoApprove,
IsEmpty: !notEmpty,
Path: planPath,
IsEmpty: !notEmpty,
}

cmdio.LogString(ctx, fmt.Sprintf("Planning complete and persisted at %s\n", planPath))
log.Debugf(ctx, fmt.Sprintf("Planning complete and persisted at %s\n", planPath))
return nil
}

Expand Down
107 changes: 97 additions & 10 deletions bundle/phases/deploy.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package phases

import (
"context"
"fmt"

"github.com/databricks/cli/bundle"
"github.com/databricks/cli/bundle/artifacts"
"github.com/databricks/cli/bundle/config"
Expand All @@ -14,10 +17,99 @@ import (
"github.com/databricks/cli/bundle/permissions"
"github.com/databricks/cli/bundle/python"
"github.com/databricks/cli/bundle/scripts"
"github.com/databricks/cli/libs/cmdio"
terraformlib "github.com/databricks/cli/libs/terraform"
)

func approvalForDeploy(ctx context.Context, b *bundle.Bundle) (bool, error) {
if b.AutoApprove {
return true, nil
}

tf := b.Terraform
if tf == nil {
return false, fmt.Errorf("terraform not initialized")
}

// read plan file
plan, err := tf.ShowPlanFile(ctx, b.Plan.Path)
if err != nil {
return false, err
}

deleteActions := make([]terraformlib.Action, 0)
for _, rc := range plan.ResourceChanges {
if rc.Change.Actions.Delete() {
deleteActions = append(deleteActions, terraformlib.Action{
Action: "delete",
ResourceType: rc.Type,
ResourceName: rc.Name,
})
}
}

recreateActions := make([]terraformlib.Action, 0)
for _, rc := range plan.ResourceChanges {
if rc.Change.Actions.Replace() {
recreateActions = append(recreateActions, terraformlib.Action{
Action: "recreate",
ResourceType: rc.Type,
ResourceName: rc.Name,
})
}
}

// No need for approval if the plan does not include any destructive actions.
if len(deleteActions) == 0 && len(recreateActions) == 0 {
return true, nil
}

if len(deleteActions) > 0 {
cmdio.LogString(ctx, "")
cmdio.LogString(ctx, "The following resources will be deleted:")
for _, a := range deleteActions {
cmdio.Log(ctx, a)
}
}

if len(recreateActions) > 0 {
cmdio.LogString(ctx, "")
cmdio.LogString(ctx, "The following resources will be recreated. Note that recreation can be lossy and may lead to lost metadata or data:")
for _, a := range recreateActions {
cmdio.Log(ctx, a)
}
cmdio.LogString(ctx, "")
}

if !cmdio.IsPromptSupported(ctx) {
return false, fmt.Errorf("the deployment requires destructive actions, but current console does not support prompting. Please specify --auto-approve if you would like to skip prompts and proceed")
}

cmdio.LogString(ctx, "")
approved, err := cmdio.AskYesOrNo(ctx, "Would you like to proceed?")
if err != nil {
return false, err
}

return approved, nil
}

// The deploy phase deploys artifacts and resources.
func Deploy() bundle.Mutator {
// Core mutators that CRUD resources and modify deployment state. These
// mutators need informed consent if they are potentially destructive.
deployCore := bundle.Defer(
terraform.Apply(),
bundle.Seq(
terraform.StatePush(),
terraform.Load(),
metadata.Compute(),
metadata.Upload(),
scripts.Execute(config.ScriptPostDeploy),
bundle.LogString("Deployment complete!"),
),
)

deployMutator := bundle.Seq(
scripts.Execute(config.ScriptPreDeploy),
lock.Acquire(),
Expand All @@ -37,20 +129,15 @@ func Deploy() bundle.Mutator {
terraform.Interpolate(),
terraform.Write(),
terraform.CheckRunningResource(),
bundle.Defer(
terraform.Apply(),
bundle.Seq(
terraform.StatePush(),
terraform.Load(),
metadata.Compute(),
metadata.Upload(),
),
terraform.Plan(terraform.PlanGoal("deploy")),
bundle.If(
approvalForDeploy,
deployCore,
bundle.LogString("Deployment cancelled!"),
),
),
lock.Release(lock.GoalDeploy),
),
scripts.Execute(config.ScriptPostDeploy),
bundle.LogString("Deployment complete!"),
)

return newPhase(
Expand Down
Loading