From dd25cca2290148767799a75d4e92b10b77dbb077 Mon Sep 17 00:00:00 2001 From: Shreyas Goenka Date: Wed, 14 Oct 2026 14:32:35 +0000 Subject: [PATCH] Extract the bind confirmation prompt into confirmBindPlan The plan preview and approval prompt move out of phases.Bind into a helper that reads the action from the plan, so BindResult no longer carries HasChanges and Action. No behavior change; a later change reuses the prompt for DMS bind. Co-authored-by: Isaac --- bundle/direct/bind.go | 21 ++---------- bundle/phases/bind.go | 76 +++++++++++++++++++++++-------------------- 2 files changed, 43 insertions(+), 54 deletions(-) diff --git a/bundle/direct/bind.go b/bundle/direct/bind.go index 9ac921b9c3c..517d641e763 100644 --- a/bundle/direct/bind.go +++ b/bundle/direct/bind.go @@ -36,12 +36,8 @@ func (e ErrResourceAlreadyBound) Error() string { e.ResourceKey, e.ExistingID) } -// BindResult contains the result of a bind operation including any detected changes. +// BindResult contains the result of a bind operation. type BindResult struct { - // HasChanges is true if deploying after bind would make changes to the resource - HasChanges bool - // Action is the planned action for the bound resource (e.g., "skip", "update", "recreate") - Action deployplan.ActionType // Plan contains the full deployment plan for the bound resource Plan *deployplan.Plan // TempStatePath is the path to the temporary state file @@ -201,22 +197,11 @@ func (b *DeploymentBundle) Bind(ctx context.Context, client *databricks.Workspac return nil, err } - // Check if the bound resource has changes - result := &BindResult{ - HasChanges: false, - Action: deployplan.Skip, + return &BindResult{ Plan: plan, TempStatePath: tmpStatePath, StatePath: statePath, - } - - entry = plan.Plan[resourceKey] - if entry != nil { - result.Action = entry.Action - result.HasChanges = result.Action != deployplan.Skip && result.Action != deployplan.Undefined - } - - return result, nil + }, nil } // Finalize completes the bind operation by renaming the temp state to the final location. diff --git a/bundle/phases/bind.go b/bundle/phases/bind.go index d8ff6aeaf3e..7b7d056f879 100644 --- a/bundle/phases/bind.go +++ b/bundle/phases/bind.go @@ -62,42 +62,9 @@ func Bind(ctx context.Context, b *bundle.Bundle, opts *BindOptions, stateDesc *s return } - // If there are changes and auto-approve is not set, show plan and ask for confirmation - if result.HasChanges && !opts.AutoApprove { - // Display the planned changes for the bound resource - cmdio.LogString(ctx, fmt.Sprintf("Plan: %s %s", result.Action, resourceKey)) - - // Show details of what will change - if result.Plan != nil { - if entry, ok := result.Plan.Plan[resourceKey]; ok && entry != nil && len(entry.Changes) > 0 { - cmdio.LogString(ctx, "\nChanges detected:") - for _, field := range slices.Sorted(maps.Keys(entry.Changes)) { - change := entry.Changes[field] - if change.Action != deployplan.Skip { - cmdio.LogString(ctx, fmt.Sprintf(" ~ %s: %v -> %v", field, jsonDump(ctx, change.Remote, field), jsonDump(ctx, change.New, field))) - } - } - cmdio.LogString(ctx, "") - } - } - - if !cmdio.IsPromptSupported(ctx) { - result.Cancel() - logdiag.LogError(ctx, fmt.Errorf("this bind operation requires user confirmation, but the current console does not support prompting.\nTo proceed, use --auto-approve after reviewing the plan above.%s", agent.AgentNotice())) - return - } - - ans, err := cmdio.AskYesOrNo(ctx, "Confirm import changes? Changes will be remotely applied only after running 'bundle deploy'.") - if err != nil { - result.Cancel() - logdiag.LogError(ctx, err) - return - } - if !ans { - result.Cancel() - logdiag.LogError(ctx, errors.New("import aborted")) - return - } + if !confirmBindPlan(ctx, resourceKey, result.Plan, opts.AutoApprove) { + result.Cancel() + return } // Finalize: rename temp state to final location @@ -119,6 +86,43 @@ func jsonDump(ctx context.Context, v any, field string) string { return string(b) } +// confirmBindPlan shows the planned changes to the bound resource and, unless autoApprove, asks +// the user to approve them. It reports whether the bind should proceed. +func confirmBindPlan(ctx context.Context, resourceKey string, plan *deployplan.Plan, autoApprove bool) bool { + entry := plan.Plan[resourceKey] + if autoApprove || entry == nil || entry.Action == deployplan.Skip || entry.Action == deployplan.Undefined { + return true + } + + cmdio.LogString(ctx, fmt.Sprintf("Plan: %s %s", entry.Action, resourceKey)) + if len(entry.Changes) > 0 { + cmdio.LogString(ctx, "\nChanges detected:") + for _, field := range slices.Sorted(maps.Keys(entry.Changes)) { + change := entry.Changes[field] + if change.Action != deployplan.Skip { + cmdio.LogString(ctx, fmt.Sprintf(" ~ %s: %v -> %v", field, jsonDump(ctx, change.Remote, field), jsonDump(ctx, change.New, field))) + } + } + cmdio.LogString(ctx, "") + } + + if !cmdio.IsPromptSupported(ctx) { + logdiag.LogError(ctx, fmt.Errorf("this bind operation requires user confirmation, but the current console does not support prompting.\nTo proceed, use --auto-approve after reviewing the plan above.%s", agent.AgentNotice())) + return false + } + + ans, err := cmdio.AskYesOrNo(ctx, "Confirm import changes? Changes will be remotely applied only after running 'bundle deploy'.") + if err != nil { + logdiag.LogError(ctx, err) + return false + } + if !ans { + logdiag.LogError(ctx, errors.New("import aborted")) + return false + } + return true +} + func Unbind(ctx context.Context, b *bundle.Bundle, group, resourceKey string, engine engine.EngineType) { log.Info(ctx, "Phase: unbind")