Skip to content

fix(purchases): resolve per-account credentials in single-account path (closes #602) - #603

Merged
cristim merged 1 commit into
feat/multicloud-web-frontendfrom
fix/issue-602-single-account-creds
May 20, 2026
Merged

fix(purchases): resolve per-account credentials in single-account path (closes #602)#603
cristim merged 1 commit into
feat/multicloud-web-frontendfrom
fix/issue-602-single-account-creds

Conversation

@cristim

@cristim cristim commented May 20, 2026

Copy link
Copy Markdown
Member

Summary

  • Every Azure (and GCP) direct-execute purchase failed in Lambda with a DefaultAzureCredential chain exhaustion error because executePurchase passed nil as provCfg on the single-account path, triggering ambient credential discovery that has no valid source in a Lambda runtime.
  • The multi-account fan-out path already called resolveAccountProvider correctly; this PR brings the single-account path into parity.
  • Adds resolveSingleAccountProvider helper (extracted to keep executePurchase under gocyclo:10) and singleCloudAccountIDFromRecs helper that derives a shared account ID from recommendation records for the direct-execute case where exec.CloudAccountID is nil.

What changed

  • internal/purchase/execution.go: executePurchase now calls m.resolveSingleAccountProvider before processPurchaseRecommendations; resolution is skipped when no recs are selected (avoids DB round-trips and spurious failures on approval-only flows).
  • internal/purchase/execution.go: New resolveSingleAccountProvider method resolves credentials from exec.CloudAccountID or, when nil, from the single shared cloud_account_id on the recommendations. Errors surface loudly; nil is returned only when no account is identifiable (ambient/AWS-role path).
  • internal/purchase/execution.go: New singleCloudAccountIDFromRecs helper returns the single distinct account ID across all recs, or nil if zero or more than one distinct ID is present.
  • internal/purchase/execution_test.go: Three new tests:

AWS / Azure / GCP impact

  • Azure: direct-execute now resolves ManagedIdentityCredential / service_principal / workload_identity_federation credentials stored in the credential store, instead of falling back to the DefaultAzureCredential chain.
  • GCP: same fix -- direct-execute now uses the stored service-account key / workload-identity token source.
  • AWS: no regression -- when no account is identifiable, resolveSingleAccountProvider returns (nil, nil) and the ambient Lambda role is used exactly as before.

Test plan

  • go test ./internal/purchase/... -count=1 -short -- 132 passed (131 pre-existing + 1 new test function with 9 sub-tests across 3 test functions)
  • go test ./internal/api/... -count=1 -short -- 1153 passed (regression)
  • go build ./... -- success
  • All pre-commit hooks pass (gofmt, go mod tidy, go vet, gocyclo, security scan)
  • QA: approve a direct-execute Azure reservation in the staging environment and confirm no DefaultAzureCredential error in Lambda logs

Closes #602
Refs #453, #597

Summary by CodeRabbit

  • Bug Fixes

    • Enhanced single-account cloud purchase execution to properly resolve credentials for explicitly specified cloud accounts, with improved error handling when credential resolution fails.
  • Tests

    • Added comprehensive test coverage for credential resolution workflows and error scenarios in purchase execution.

Review Change Stack

closes #602)

The single-account execution path in executePurchase was passing nil as
provCfg to processPurchaseRecommendations. For Azure and GCP this caused
CreateAndValidateProvider to fall back to DefaultAzureCredential / ADC,
which then exhausted its entire chain (Environment, WorkloadIdentity,
ManagedIdentity, CLI) and failed in the Lambda runtime where none of
those sources are available.

The multi-account fan-out path (executeForAccount) already called
resolveAccountProvider correctly; this fix brings the single-account
path into parity.

Changes:
- In executePurchase (single-account path), when selected recs exist,
  derive the cloud account ID from exec.CloudAccountID (set for
  plan-with-single-account) or, failing that, from the shared
  cloud_account_id on the recommendations (direct-execute path where
  PlanID is empty and exec.CloudAccountID is nil).
- Call GetCloudAccount + resolveAccountProvider to build a provCfg
  with explicit credentials instead of nil.
- Surface credential-resolution errors loudly (matching the
  multi-account pattern) rather than silently falling back to ambient.
- Skip credential resolution entirely when no recs are selected to avoid
  spurious failures on approval-only flows (recs present for contact-
  email gating but not for purchase).
- Add singleCloudAccountIDFromRecs helper and three new tests:
  - TestExecutePurchase_SingleAccount_AzureUsesResolvedCreds: asserts
    the factory receives a non-nil provCfg for a direct-execute Azure
    purchase (regression for #602).
  - TestExecutePurchase_SingleAccount_CredResolutionError: asserts the
    error surfaces with the account ID when GetCloudAccount fails.
  - TestSingleCloudAccountIDFromRecs: table-driven unit test for the
    new helper covering empty slice, all-nil, single, shared, mixed,
    and multi-account cases.

Fixes QA execution bf7cb732-52c9-4d46-86e2-b1dc208cbe3c.
Refs #453, #597.
@coderabbitai

coderabbitai Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 610d82d3-25f3-4f71-bca0-f156c03bae9a

📥 Commits

Reviewing files that changed from the base of the PR and between cf78ff1 and 1ff3272.

📒 Files selected for processing (2)
  • internal/purchase/execution.go
  • internal/purchase/execution_test.go

📝 Walkthrough

Walkthrough

The PR fixes P0 credential resolution failures in single-account purchase execution by resolving per-account provider credentials instead of passing nil. Two new helpers extract a single cloud account ID from recommendations and resolve the corresponding provider config; the single-account branch now calls these helpers and returns errors when credential resolution fails, enabling Azure and GCP purchases that previously fell back to broken ambient credentials in Lambda.

Changes

Single-account credential resolution

Layer / File(s) Summary
Account identification and provider resolution helpers
internal/purchase/execution.go
New singleCloudAccountIDFromRecs returns a single consistent cloud_account_id when all recommendations agree on exactly one non-empty ID, or nil when absent/empty/conflicting. New resolveSingleAccountProvider derives a provider config from the resolved account, returning (nil, nil) when no single account is identifiable or the account doesn't exist, and returning errors when credential resolution fails.
Integration into executePurchase single-account path
internal/purchase/execution.go
executePurchase single-account branch now calls resolveSingleAccountProvider to resolve per-account credentials and returns immediately on resolution errors, passing the result into processPurchaseRecommendations instead of unconditional nil.
Test coverage for credential resolution
internal/purchase/execution_test.go
Added provider import and three new test functions: TestExecutePurchase_SingleAccount_AzureUsesResolvedCreds validates non-nil provider config with ProviderOverride, TestExecutePurchase_SingleAccount_CredResolutionError validates error message format includes account ID and underlying error, and TestSingleCloudAccountIDFromRecs validates account consensus extraction across empty, nil, matching, and mismatched states.

Sequence Diagram

sequenceDiagram
  participant executePurchase
  participant resolveSingleAccountProvider
  participant singleCloudAccountIDFromRecs
  participant config as m.config
  participant resolveAccountProvider
  participant processPurchaseRecommendations
  executePurchase->>resolveSingleAccountProvider: resolve per-account credentials
  resolveSingleAccountProvider->>singleCloudAccountIDFromRecs: extract account ID from recs
  singleCloudAccountIDFromRecs-->>resolveSingleAccountProvider: single account ID or nil
  resolveSingleAccountProvider->>config: GetCloudAccount(cloudAccountID)
  config-->>resolveSingleAccountProvider: account record or nil/error
  resolveSingleAccountProvider->>resolveAccountProvider: resolve credentials for account
  resolveAccountProvider-->>resolveSingleAccountProvider: provider config or error
  resolveSingleAccountProvider-->>executePurchase: (provCfg, error)
  executePurchase->>processPurchaseRecommendations: process with resolved provider config
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • LeanerCloud/CUDly#233: Both extend executePurchase credential resolution testing, including per-account isolation behavior when credentials fail; the retrieved PR covers multi-account partial failure isolation while this PR adds single-account resolved-credential path coverage.

Suggested labels

effort/m

Poem

🐰 A rabbit hops through purchase clouds,
Credentials tucked in bundles proud—
No more ambient dreams will fail,
Azure, GCP, now set sail!
Single accounts, resolved with care,
Success now blooms in Lambda's lair.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the primary change: resolving per-account credentials in the single-account execution path for purchases, addressing the root cause identified in #602.
Linked Issues check ✅ Passed The code changes comprehensively address all acceptance criteria from #602: Azure/GCP now resolve per-account credentials via new helpers, credential resolution errors surface with account IDs, AWS ambient behavior is preserved, and comprehensive test coverage was added.
Out of Scope Changes check ✅ Passed All changes are directly scoped to resolving per-account credentials in the single-account purchase path as specified in #602; no unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-602-single-account-creds

Warning

Review ran into problems

🔥 Problems

Stopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a @coderabbit review after the pipeline has finished.


Comment @coderabbitai help to get the list of available commands and usage tips.

@cristim cristim added priority/p0 Drop everything; same-day fix severity/critical Major harm when it happens urgency/now Drop other things impact/all-users Affects every user effort/s Hours type/bug Defect triaged Item has been triaged labels May 20, 2026
@cristim

cristim commented May 20, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/s Hours impact/all-users Affects every user priority/p0 Drop everything; same-day fix severity/critical Major harm when it happens triaged Item has been triaged type/bug Defect urgency/now Drop other things

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant