Skip to content

feat(purchases): extend IdempotencyToken to all commitment executors (AWS RDS/ElastiCache/MemoryDB/OpenSearch/Redshift, Azure, GCP) — blocks #639 #641

Description

@cristim

All commitment executors except EC2 RI + Savings Plans silently ignore IdempotencyToken

internal/purchase/execution.go derives a deterministic recOpts.IdempotencyToken per rec (added by #636), but only two executors honor it:

Every other PurchaseCommitment implementation reads opts but never opts.IdempotencyToken, so a re-drive / scheduler retry creates a SECOND commitment:

AWS (5 services): RDS, ElastiCache, MemoryDB, OpenSearch, Redshift — confirmed zero IdempotencyToken reads across all five.

Azure: all reservation purchases mint a fresh random reservationOrderID (uuid.New() per call; the search client uses time.Now().Unix() at providers/azure/services/search/client.go:239). No IdempotencyToken read anywhere in providers/azure.

GCP: providers/gcp/services/computeengine/client.go:422 names commitments cud-<unix-second> and never reads opts.IdempotencyToken. (The other GCP services are separately broken — see the GCP-executor issue.)

Why this matters now

This is a financial double-purchase exposure on its own. It also blocks #639 (flip RecoverStrandedApprovals to idempotent auto-re-drive): the recovery sweep would re-drive a stranded approved row and double-purchase on every provider/service except EC2 RI + SP. #639 must not ship until this is closed.

Fix

Extend the #636 idempotency mechanism to every executor:

  • AWS RDS/ElastiCache/MemoryDB/OpenSearch/Redshift: apply the EC2-style tag-guard (DescribeReserved* filtered by tag:cudly-idempotency-token + active/payment-pending, short-circuit if found, tag after purchase) per service. Where the AWS API offers a native ClientToken, prefer it.
  • Azure: derive reservationOrderID deterministically from the IdempotencyToken (Azure reservation PUT is idempotent on a stable order ID) instead of uuid.New().
  • GCP Compute: use the IdempotencyToken as the commitment name / requestId so a repeat is rejected/deduped.

Regression test per provider: same token on re-drive does not create a second commitment.

Dedup

#636/#639 are explicitly scoped to EC2 RI + SP only; #632 is the strand bug. The Azure cluster (#553-#596) covers SP/exchange/multi-account/pricing, not reservation-purchase idempotency. New. Surfaced in the full purchase-workflow trace.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions