Skip to content
Merged
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
1 change: 1 addition & 0 deletions .nextchanges/bundles/6675.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* `bundle plan` and `deploy` no longer list or count a resource that was already deleted remotely as a deletion, matching `bundle destroy`; applying still cleans up its stale state entry. ([#6675](https://github.com/databricks/cli/pull/6675))
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@

>>> [CLI] bundle deploy --auto-approve
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files...
Deleted jobs.foo
Deleted pipelines.bar
Files: 4 uploaded, 0 deleted
Resources: 0 created, 0 changed, 2 deleted, 0 unchanged
Resources: 0 created, 0 changed, 0 deleted, 0 unchanged
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@

>>> [CLI] bundle plan
delete jobs.foo
delete pipelines.bar

Plan: 0 to add, 0 to change, 2 to delete, 0 unchanged
Plan: 0 to add, 0 to change, 0 to delete, 0 unchanged
9 changes: 9 additions & 0 deletions bundle/deployplan/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ func (a Action) String() string {
return fmt.Sprintf(" %s %s", a.ActionType.StringShort(), a.ResourceKey)
}

// IsStateOnlyDelete reports whether applying this delete only drops the state entry
// without any backend call: the resource is already gone remotely (Gone) or has no
// delete operation (StateOnly). Such deletes are omitted from human output, excluded
// from the resource counts, and need no destructive-action approval. See the same
// method on PlanEntry.
func (a Action) IsStateOnlyDelete() bool {
return a.Gone || a.StateOnly
}

func (a Action) IsChildResource() bool {
// Note, strictly speaking ResourceKey could be resources.jobs["my.job"] but
// we have an assumption in many other places that it's always looks like "resources.jobs.my_job"
Expand Down
13 changes: 11 additions & 2 deletions bundle/deployplan/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@ func (p *Plan) CountActions() ActionCounts {
case Delete:
// A state-only delete touches nothing in the backend and only drops the
// state entry, so it is not a real action: leave it out of the tally
// entirely rather than misreport it as deleted or unchanged.
if entry.StateOnly {
// entirely rather than misreport it as deleted or unchanged. This matches
// how `bundle destroy` counts its own deletions.
if entry.IsStateOnlyDelete() {
continue
}
c.Delete++
Expand Down Expand Up @@ -141,6 +142,14 @@ type PlanEntry struct {
Changes Changes `json:"changes,omitempty"`
}

// IsStateOnlyDelete reports whether applying this delete only drops the state entry
// without any backend call: the resource is already gone remotely (Gone) or has no
// delete operation (StateOnly). Such deletes are omitted from human output, excluded
// from the resource counts, and need no destructive-action approval.
func (e *PlanEntry) IsStateOnlyDelete() bool {
return e.Gone || e.StateOnly
}

type DependsOnEntry struct {
Node string `json:"node"`
Label string `json:"label,omitempty"`
Expand Down
10 changes: 5 additions & 5 deletions bundle/direct/bundle_apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,10 @@ func (b *DeploymentBundle) Apply(ctx context.Context, client *databricks.Workspa
}

if action == deployplan.Delete {
if entry.Gone || entry.StateOnly {
// Either planning confirmed the resource is already deleted remotely
// (Gone), or the resource has no delete operation (StateOnly). Both
// cases only remove it from the state, without calling the delete API.
if entry.IsStateOnlyDelete() {
// The resource is already deleted remotely (Gone) or has no delete
// operation (StateOnly); either way only remove it from the state,
// without calling the delete API.
err = b.StateDB.DeleteState(ctx, resourceKey, false)
} else {
err = d.Destroy(ctx, &b.StateDB)
Expand All @@ -129,7 +129,7 @@ func (b *DeploymentBundle) Apply(ctx context.Context, client *databricks.Workspa
// A state-only delete performs no backend operation, so don't report it,
// consistent with the summary (CountActions excludes it) and the terraform
// path in logDeploySummary.
if reportApplied && !entry.StateOnly {
if reportApplied && !entry.IsStateOnlyDelete() {
cmdio.LogString(ctx, deployplan.AppliedLine(resourceKey, action))
}
return true
Expand Down
10 changes: 4 additions & 6 deletions bundle/phases/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,9 @@ var deployApprovalGroups = []approvalGroup{
func approvalForDeploy(ctx context.Context, b *bundle.Bundle, plan *deployplan.Plan) (bool, error) {
actions := plan.GetActions()

// Deletes that only clean up the state — the resource is already gone remotely
// (Gone) or has no delete operation (StateOnly) — are not destructive and need
// no approval.
actions = slices.DeleteFunc(actions, func(a deployplan.Action) bool { return a.Gone || a.StateOnly })
// Deletes that only clean up the state (already gone remotely, or no delete
// operation) are not destructive and need no approval.
actions = slices.DeleteFunc(actions, func(a deployplan.Action) bool { return a.IsStateOnlyDelete() })

err := checkForPreventDestroy(b, actions)
if err != nil {
Expand Down Expand Up @@ -150,8 +149,7 @@ func logDeploySummary(ctx context.Context, b *bundle.Bundle, plan *deployplan.Pl
if action.ActionType == deployplan.Skip || action.ActionType == deployplan.Undefined {
continue
}
// A state-only delete performs no backend operation, so don't report it.
if action.StateOnly {
if action.IsStateOnlyDelete() {
continue
}
cmdio.LogString(ctx, deployplan.AppliedLine(action.ResourceKey, action.ActionType))
Expand Down
12 changes: 6 additions & 6 deletions bundle/phases/destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,11 @@ func logPipelineDeleteApproval(ctx context.Context, b *bundle.Bundle, actions []
func approvalForDestroy(ctx context.Context, b *bundle.Bundle, plan *deployplan.Plan, engine engine.EngineType) (bool, error) {
deleteActions := plan.GetActions()

// Deletes that only clean up the state — the resource is already gone remotely
// (Gone) or has no delete operation (StateOnly) — are not destructive, so they
// are not listed as deletions and need no approval. In particular this makes
// prevent_destroy inert for state-only resources: nothing is destroyed.
deleteActions = slices.DeleteFunc(deleteActions, func(a deployplan.Action) bool { return a.Gone || a.StateOnly })
// Deletes that only clean up the state (already gone remotely, or no delete
// operation) are not destructive, so they are not listed as deletions and need no
// approval. In particular this makes prevent_destroy inert for state-only
// resources: nothing is destroyed.
deleteActions = slices.DeleteFunc(deleteActions, func(a deployplan.Action) bool { return a.IsStateOnlyDelete() })

err := checkForPreventDestroy(b, deleteActions)
if err != nil {
Expand Down Expand Up @@ -190,7 +190,7 @@ func destroyCore(ctx context.Context, b *bundle.Bundle, plan *deployplan.Plan, e
// a destruction to report.
deleted := 0
for _, a := range plan.GetActions() {
if a.ActionType == deployplan.Delete && !a.IsChildResource() && !a.Gone && !a.StateOnly {
if a.ActionType == deployplan.Delete && !a.IsChildResource() && !a.IsStateOnlyDelete() {
deleted++
}
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/bundle/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ It is useful for previewing changes before running 'bundle deploy'.`,
}
// A state-only delete has no backend effect; keep it in the JSON
// plan but omit it from the human-readable action list.
if action.StateOnly {
if action.IsStateOnlyDelete() {
continue
}
key := strings.TrimPrefix(action.ResourceKey, "resources.")
Expand Down
Loading