diff --git a/cmd/recommendation_completeness_proxy_test.go b/cmd/recommendation_completeness_proxy_test.go
index ec8a71b5e..55f5e8880 100644
--- a/cmd/recommendation_completeness_proxy_test.go
+++ b/cmd/recommendation_completeness_proxy_test.go
@@ -7,6 +7,7 @@ import (
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
+ "encoding/json"
"encoding/pem"
"errors"
"fmt"
@@ -26,22 +27,34 @@ import (
)
type completenessProxy struct {
- t *testing.T
- mu sync.Mutex
- handlers sync.WaitGroup
- regions string
- details string
- requests map[string]int
- cert tls.Certificate
+ t *testing.T
+ mu sync.Mutex
+ handlers sync.WaitGroup
+ regions string
+ details string
+ service string
+ requests map[string]int
+ spRequests []completenessSPRequest
+ engines []string
+ cert tls.Certificate
}
-func newCompletenessProxy(t *testing.T, dir, regions, details string) (*completenessProxy, string, string) {
+type completenessSPRequest struct {
+ SavingsPlansType string
+ NextPageToken string
+ TermInYears string
+ PaymentOption string
+ LookbackPeriodInDays string
+ AccountScope string
+}
+
+func newCompletenessProxy(t *testing.T, dir, regions, details, service string) (*completenessProxy, string, string) {
t.Helper()
key, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)
template := &x509.Certificate{
SerialNumber: big.NewInt(54), Subject: pkix.Name{CommonName: "recommendation fixture"},
- DNSNames: []string{"ce.us-east-1.amazonaws.com", "ec2.us-east-1.amazonaws.com", "rds.us-east-1.amazonaws.com"},
+ DNSNames: []string{"ce.us-east-1.amazonaws.com", "ec2.us-east-1.amazonaws.com", "rds.us-east-1.amazonaws.com", "savingsplans.amazonaws.com"},
NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().Add(time.Hour),
IsCA: true, BasicConstraintsValid: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
@@ -52,7 +65,7 @@ func newCompletenessProxy(t *testing.T, dir, regions, details string) (*complete
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
caPath := filepath.Join(dir, "ca.pem")
require.NoError(t, os.WriteFile(caPath, certPEM, 0600))
- fixture := &completenessProxy{t: t, regions: regions, details: details, requests: make(map[string]int),
+ fixture := &completenessProxy{t: t, regions: regions, details: details, service: service, requests: make(map[string]int),
cert: tls.Certificate{Certificate: [][]byte{der}, PrivateKey: key}}
server := httptest.NewServer(http.HandlerFunc(fixture.serveConnect))
t.Cleanup(func() {
@@ -65,7 +78,7 @@ func newCompletenessProxy(t *testing.T, dir, regions, details string) (*complete
func (p *completenessProxy) serveConnect(w http.ResponseWriter, r *http.Request) {
p.handlers.Add(1)
defer p.handlers.Done()
- allowed := r.Host == "ce.us-east-1.amazonaws.com:443" || r.Host == "ec2.us-east-1.amazonaws.com:443" || r.Host == "rds.us-east-1.amazonaws.com:443"
+ allowed := r.Host == "ce.us-east-1.amazonaws.com:443" || r.Host == "ec2.us-east-1.amazonaws.com:443" || r.Host == "rds.us-east-1.amazonaws.com:443" || r.Host == "savingsplans.amazonaws.com:443"
if r.Method != http.MethodConnect || !allowed {
p.t.Errorf("unexpected CONNECT %s %s", r.Method, r.Host)
http.Error(w, "forbidden", http.StatusForbidden)
@@ -121,24 +134,78 @@ func (p *completenessProxy) respond(req *http.Request) *http.Response {
p.t.Error(err)
}
op := req.Header.Get("X-Amz-Target")
- if op == "" {
+ if req.Host == "savingsplans.amazonaws.com" && op == "" {
+ op = strings.TrimPrefix(req.URL.Path, "/")
+ if req.Method != http.MethodPost || req.URL.Path != "/DescribeSavingsPlans" || req.URL.RawQuery != "" {
+ p.t.Errorf("unexpected Savings Plans REST request %s %s", req.Method, req.URL)
+ op = "fixture-rejected"
+ } else {
+ var inventory struct {
+ States []string `json:"states"`
+ MaxResults int `json:"maxResults"`
+ NextToken string `json:"nextToken"`
+ }
+ if decodeErr := json.Unmarshal(body, &inventory); decodeErr != nil || inventory.MaxResults != 100 || inventory.NextToken != "" || strings.Join(inventory.States, ",") != "active,payment-pending,pending-return,queued" {
+ p.t.Errorf("unexpected SP inventory request: %s (%v)", body, decodeErr)
+ op = "fixture-rejected"
+ }
+ }
+ } else if op == "" {
values, parseErr := url.ParseQuery(string(body))
if parseErr != nil {
p.t.Error(parseErr)
}
op = values.Get("Action")
+ if p.service != "rds" && req.Host == "rds.us-east-1.amazonaws.com" {
+ want := url.Values{"Action": {op}, "Version": {"2014-10-31"}}
+ if op == "DescribeDBMajorEngineVersions" {
+ engine := values.Get("Engine")
+ want.Set("Engine", engine)
+ p.mu.Lock()
+ p.engines = append(p.engines, engine)
+ p.mu.Unlock()
+ }
+ if req.Method != http.MethodPost || req.URL.Path != "/" || req.URL.RawQuery != "" || values.Encode() != want.Encode() {
+ p.t.Errorf("unexpected SP ancillary RDS request %s %s: %s", req.Method, req.URL, body)
+ op = "fixture-rejected"
+ }
+ }
}
p.mu.Lock()
p.requests[op]++
call := p.requests[op]
p.mu.Unlock()
status, contentType, payload := p.operation(req.Host, op, call)
+ if p.service != "rds" && req.Host == "ce.us-east-1.amazonaws.com" && op == "AWSInsightsIndexService.GetSavingsPlansPurchaseRecommendation" {
+ status, contentType, payload = p.spRecommendation(body)
+ }
return &http.Response{StatusCode: status, ProtoMajor: 1, ProtoMinor: 1,
Header: http.Header{"Content-Type": []string{contentType}},
Body: io.NopCloser(strings.NewReader(payload)), ContentLength: int64(len(payload))}
}
func (p *completenessProxy) operation(host, op string, call int) (int, string, string) {
+ responses := map[string]string{
+ "DescribeDBInstances": "DBInstances",
+ "DescribeDBMajorEngineVersions": "DBMajorEngineVersions",
+ "DescribeReservedDBInstances": "ReservedDBInstances",
+ }
+ if element, ok := responses[op]; ok && host == "rds.us-east-1.amazonaws.com" && (p.service == "rds" || op != "DescribeReservedDBInstances") {
+ return 200, "text/xml", fmt.Sprintf(`<%sResponse xmlns="http://rds.amazonaws.com/doc/2014-10-31/"><%sResult><%s/>%sResult>%sResponse>`, op, op, element, op, op)
+ }
+ if p.service != "rds" {
+ if host == "ce.us-east-1.amazonaws.com" && op == "AWSInsightsIndexService.GetSavingsPlansPurchaseRecommendation" {
+ return 200, "application/x-amz-json-1.1", ""
+ }
+ if host == "savingsplans.amazonaws.com" && op == "DescribeSavingsPlans" {
+ return 200, "application/json", `{"savingsPlans":[]}`
+ }
+ if host == "ec2.us-east-1.amazonaws.com" && op == "DescribeRegions" {
+ return 200, "text/xml", `- us-east-1
`
+ }
+ p.t.Errorf("unexpected SP operation %s %s", host, op)
+ return 403, "text/plain", "fixture rejected operation"
+ }
if host == "ce.us-east-1.amazonaws.com" && op == "AWSInsightsIndexService.GetReservationCoverage" {
return 200, "application/x-amz-json-1.1", `{"CoveragesByTime":[]}`
}
@@ -164,14 +231,6 @@ func (p *completenessProxy) operation(host, op string, call int) (int, string, s
}
return 200, "text/xml", `- us-east-1
`
}
- responses := map[string]string{
- "DescribeDBInstances": "DBInstances",
- "DescribeDBMajorEngineVersions": "DBMajorEngineVersions",
- "DescribeReservedDBInstances": "ReservedDBInstances",
- }
- if element, ok := responses[op]; ok && host == "rds.us-east-1.amazonaws.com" {
- return 200, "text/xml", fmt.Sprintf(`<%sResponse xmlns="http://rds.amazonaws.com/doc/2014-10-31/"><%sResult><%s/>%sResult>%sResponse>`, op, op, element, op, op)
- }
p.t.Errorf("unexpected operation %s %s", host, op)
return 403, "text/plain", "fixture rejected operation"
}
@@ -181,6 +240,10 @@ func (p *completenessProxy) assertRequests(t *testing.T) {
p.handlers.Wait()
p.mu.Lock()
defer p.mu.Unlock()
+ if p.service != "rds" {
+ p.assertSPRequests(t)
+ return
+ }
ceCalls := 1
if p.regions == "fallback" {
ceCalls = 6
@@ -196,3 +259,85 @@ func (p *completenessProxy) assertRequests(t *testing.T) {
require.Equal(t, wantRegions, p.requests["DescribeRegions"], "region calls")
t.Logf("actual root command, config loader, SDK and CSV; synthetic operations=%v", p.requests)
}
+
+func completenessSPTypes(service string) []string {
+ if service == "savingsplans" {
+ return []string{"COMPUTE_SP", "EC2_INSTANCE_SP", "SAGEMAKER_SP", "DATABASE_SP"}
+ }
+ for _, plan := range []string{"COMPUTE_SP", "EC2_INSTANCE_SP", "SAGEMAKER_SP", "DATABASE_SP"} {
+ if completenessSPService(plan) == strings.Replace(service, "savingsplans-", "savings-plans-", 1) {
+ return []string{plan}
+ }
+ }
+ panic("unexpected fixture service: " + service)
+}
+
+func completenessSPService(plan string) string {
+ return map[string]string{"COMPUTE_SP": "savings-plans-compute", "EC2_INSTANCE_SP": "savings-plans-ec2instance", "SAGEMAKER_SP": "savings-plans-sagemaker", "DATABASE_SP": "savings-plans-database"}[plan]
+}
+
+func (p *completenessProxy) spRecommendation(body []byte) (int, string, string) {
+ var request completenessSPRequest
+ if err := json.Unmarshal(body, &request); err != nil {
+ p.t.Errorf("decode SP request: %v", err)
+ return 403, "application/json", `{}`
+ }
+ p.mu.Lock()
+ p.spRequests = append(p.spRequests, request)
+ p.mu.Unlock()
+ if p.details == "api-error" || (p.details == "failed-type" && (p.service != "savingsplans" || request.SavingsPlansType == "DATABASE_SP")) || request.NextPageToken == "unfinished" {
+ return 400, "application/x-amz-json-1.1", `{"__type":"AccessDeniedException","message":"fixture denied recommendations"}`
+ }
+ const valid = `{"HourlyCommitmentToPurchase":"2","EstimatedMonthlySavingsAmount":"10","UpfrontCost":"3","CurrentAverageHourlyOnDemandSpend":"4"}`
+ const invalid = `{"HourlyCommitmentToPurchase":"not-a-number","EstimatedMonthlySavingsAmount":"10","UpfrontCost":"3"}`
+ details := valid
+ if request.SavingsPlansType == "EC2_INSTANCE_SP" {
+ details = strings.TrimSuffix(valid, "}") + `,"SavingsPlansDetails":{"Region":"us-east-1"}}`
+ }
+ switch p.details {
+ case "mixed":
+ details += "," + invalid
+ case "all-invalid":
+ details = invalid
+ case "empty":
+ details = ""
+ }
+ token := ""
+ if p.details == "late-page" {
+ token = `,"NextPageToken":"unfinished"`
+ }
+ return 200, "application/x-amz-json-1.1", `{"SavingsPlansPurchaseRecommendation":{"SavingsPlansPurchaseRecommendationDetails":[` + details + `]}` + token + `}`
+}
+
+func (p *completenessProxy) assertSPRequests(t *testing.T) {
+ t.Helper()
+ types := completenessSPTypes(p.service)
+ expected := make([]completenessSPRequest, 0, len(types)*2)
+ for _, plan := range types {
+ request := completenessSPRequest{SavingsPlansType: plan, TermInYears: "ONE_YEAR", PaymentOption: "NO_UPFRONT", LookbackPeriodInDays: "SEVEN_DAYS", AccountScope: "LINKED"}
+ expected = append(expected, request)
+ if p.details == "late-page" {
+ request.NextPageToken = "unfinished"
+ expected = append(expected, request)
+ }
+ }
+ require.Equal(t, expected, p.spRequests, "actual SDK SP request tuples")
+ rows := len(types)
+ switch p.details {
+ case "empty", "all-invalid", "api-error":
+ rows = 0
+ case "failed-type":
+ if p.service == "savingsplans" {
+ rows--
+ } else {
+ rows = 0
+ }
+ }
+ operations := map[string]int{"DescribeRegions": 1, "DescribeDBInstances": 1, "DescribeDBMajorEngineVersions": 4, "AWSInsightsIndexService.GetSavingsPlansPurchaseRecommendation": len(expected)}
+ if rows > 0 {
+ operations["DescribeSavingsPlans"] = rows
+ }
+ require.Equal(t, operations, p.requests, "unexpected read or purchase")
+ require.Equal(t, []string{"mysql", "postgres", "aurora-mysql", "aurora-postgresql"}, p.engines, "ancillary RDS engine reads")
+ t.Logf("actual root command, SP SDK and CSV; synthetic operations=%v", p.requests)
+}
diff --git a/cmd/recommendation_completeness_test.go b/cmd/recommendation_completeness_test.go
index 1292cd3fe..f1fa8caba 100644
--- a/cmd/recommendation_completeness_test.go
+++ b/cmd/recommendation_completeness_test.go
@@ -12,6 +12,7 @@ import (
"os"
"os/exec"
"path/filepath"
+ "sort"
"strings"
"testing"
"time"
@@ -30,38 +31,55 @@ func TestRecommendationCompletenessCommand(t *testing.T) {
for _, regionMode := range []string{"explicit", "default", "fallback"} {
for _, details := range []string{"valid", "mixed", "all-invalid", "empty", "api-error"} {
t.Run(regionMode+"/"+details, func(t *testing.T) {
- dir := t.TempDir()
- fixture, proxyURL, caPath := newCompletenessProxy(t, dir, regionMode, details)
- output := filepath.Join(dir, "recommendations.csv")
- ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
- defer cancel()
- child := exec.CommandContext(ctx, os.Args[0], "-test.run=^TestRecommendationCompletenessCommand$", "-test.v")
- child.Env = []string{
- "PATH=/usr/bin:/bin", "HOME=" + dir, "TMPDIR=" + dir,
- "CUDLY_COMPLETENESS_CHILD=1", "CUDLY_COMPLETENESS_OUTPUT=" + output,
- "CUDLY_COMPLETENESS_AUDIT=" + filepath.Join(dir, "audit.jsonl"),
- "CUDLY_COMPLETENESS_REGIONS=" + regionMode,
- "AWS_ACCESS_KEY_ID=synthetic", "AWS_SECRET_ACCESS_KEY=synthetic", "AWS_REGION=us-east-1",
- "AWS_EC2_METADATA_DISABLED=true", "AWS_MAX_ATTEMPTS=1",
- "AWS_CONFIG_FILE=" + filepath.Join(dir, "absent-config"),
- "AWS_SHARED_CREDENTIALS_FILE=" + filepath.Join(dir, "absent-credentials"),
- "HTTPS_PROXY=" + proxyURL, "HTTP_PROXY=" + proxyURL, "AWS_CA_BUNDLE=" + caPath,
- }
- var stdout, stderr bytes.Buffer
- child.Stdout, child.Stderr = &stdout, &stderr
- require.NoError(t, child.Run(), "stdout: %s\nstderr: %s", &stdout, &stderr)
- fixture.assertRequests(t)
- logs := stdout.String() + stderr.String()
- for _, failure := range []string{"Could not check", "Failed to query", "request send failed", "certificate", "TLS handshake"} {
- require.NotContains(t, logs, failure, "unexpected ancillary error")
- }
- assertCompletenessDiagnostics(t, regionMode, details, stdout.String(), stderr.String())
- assertCompletenessCSV(t, output, details, logs)
+ runCompletenessScenario(t, "rds", regionMode, details)
+ })
+ }
+ }
+ for _, service := range []string{"savingsplans-compute", "savingsplans-ec2instance", "savingsplans-sagemaker", "savingsplans-database", "savingsplans"} {
+ for _, details := range []string{"valid", "mixed", "all-invalid", "empty", "api-error", "failed-type", "late-page"} {
+ t.Run(service+"/"+details, func(t *testing.T) {
+ runCompletenessScenario(t, service, "default", details)
})
}
}
}
+func runCompletenessScenario(t *testing.T, service, regionMode, details string) {
+ t.Helper()
+ dir := t.TempDir()
+ fixture, proxyURL, caPath := newCompletenessProxy(t, dir, regionMode, details, service)
+ output := filepath.Join(dir, "recommendations.csv")
+ ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
+ defer cancel()
+ child := exec.CommandContext(ctx, os.Args[0], "-test.run=^TestRecommendationCompletenessCommand$", "-test.v")
+ child.Env = []string{
+ "PATH=/usr/bin:/bin", "HOME=" + dir, "TMPDIR=" + dir,
+ "CUDLY_COMPLETENESS_CHILD=1", "CUDLY_COMPLETENESS_OUTPUT=" + output,
+ "CUDLY_COMPLETENESS_AUDIT=" + filepath.Join(dir, "audit.jsonl"),
+ "CUDLY_COMPLETENESS_REGIONS=" + regionMode,
+ "CUDLY_COMPLETENESS_SERVICE=" + service,
+ "AWS_ACCESS_KEY_ID=synthetic", "AWS_SECRET_ACCESS_KEY=synthetic", "AWS_REGION=us-east-1",
+ "AWS_EC2_METADATA_DISABLED=true", "AWS_MAX_ATTEMPTS=1",
+ "AWS_CONFIG_FILE=" + filepath.Join(dir, "absent-config"),
+ "AWS_SHARED_CREDENTIALS_FILE=" + filepath.Join(dir, "absent-credentials"),
+ "HTTPS_PROXY=" + proxyURL, "HTTP_PROXY=" + proxyURL, "AWS_CA_BUNDLE=" + caPath,
+ }
+ var stdout, stderr bytes.Buffer
+ child.Stdout, child.Stderr = &stdout, &stderr
+ require.NoError(t, child.Run(), "stdout: %s\nstderr: %s", &stdout, &stderr)
+ fixture.assertRequests(t)
+ logs := stdout.String() + stderr.String()
+ for _, failure := range []string{"Could not check", "Failed to query", "request send failed", "certificate", "TLS handshake"} {
+ require.NotContains(t, logs, failure, "unexpected ancillary error")
+ }
+ if service == "rds" {
+ assertCompletenessDiagnostics(t, regionMode, details, stdout.String(), stderr.String())
+ assertCompletenessCSV(t, output, details, logs)
+ } else {
+ assertSPCompleteness(t, output, service, details, logs)
+ }
+}
+
func runCompletenessCommandChild(t *testing.T) {
t.Helper()
ca, err := os.ReadFile(os.Getenv("AWS_CA_BUNDLE"))
@@ -72,7 +90,7 @@ func runCompletenessCommandChild(t *testing.T) {
transport.TLSClientConfig = &tls.Config{RootCAs: roots, MinVersion: tls.VersionTLS12}
http.DefaultTransport = transport
t.Cleanup(transport.CloseIdleConnections)
- args := []string{"--services", "rds", "--term", "1", "--include-extended-support", "--coverage", "100",
+ args := []string{"--services", os.Getenv("CUDLY_COMPLETENESS_SERVICE"), "--term", "1", "--include-extended-support", "--coverage", "100",
"--output", os.Getenv("CUDLY_COMPLETENESS_OUTPUT"), "--audit-log", os.Getenv("CUDLY_COMPLETENESS_AUDIT")}
if os.Getenv("CUDLY_COMPLETENESS_REGIONS") == "explicit" {
args = append(args, "--regions", "us-east-1")
@@ -81,6 +99,72 @@ func runCompletenessCommandChild(t *testing.T) {
require.NoError(t, rootCmd.Execute())
}
+func assertSPCompleteness(t *testing.T, output, service, details, logs string) {
+ t.Helper()
+ types := completenessSPTypes(service)
+ warnings, failedDetails, failedScopes := 0, 0, 0
+ survivors := append([]string(nil), types...)
+ switch details {
+ case "mixed", "all-invalid":
+ warnings, failedDetails = len(types), 1
+ case "late-page":
+ warnings, failedScopes = len(types), 1
+ case "failed-type":
+ if service == "savingsplans" {
+ survivors = types[:3]
+ } else {
+ survivors = nil
+ }
+ }
+ if details == "empty" || details == "all-invalid" || details == "api-error" {
+ survivors = nil
+ }
+ require.Equal(t, warnings, strings.Count(logs, "incomplete AWS recommendations:"), logs)
+ if warnings > 0 {
+ want := fmt.Sprintf("incomplete AWS recommendations: %d failed details, %d failed scopes:", failedDetails, failedScopes)
+ require.Equal(t, warnings, strings.Count(logs, want), logs)
+ if failedDetails > 0 {
+ require.Contains(t, logs, "not-a-number")
+ }
+ }
+ failures := 0
+ switch details {
+ case "api-error":
+ failures = len(types)
+ case "failed-type":
+ failures = 1
+ }
+ require.Equal(t, failures, strings.Count(logs, "Failed to fetch recommendations:"), logs)
+ if failures > 0 || details == "late-page" {
+ require.Contains(t, logs, "fixture denied recommendations")
+ }
+ require.NotContains(t, logs, "Region discovery incomplete")
+ data, err := os.ReadFile(output)
+ if len(survivors) == 0 {
+ require.ErrorIs(t, err, os.ErrNotExist, "unexpected CSV: %s\n%s", data, logs)
+ require.NotContains(t, logs, "CSV report written")
+ return
+ }
+ require.NoError(t, err, logs)
+ rows, err := csv.NewReader(bytes.NewReader(data)).ReadAll()
+ require.NoError(t, err)
+ require.Len(t, rows, len(survivors)+2, logs)
+ require.Equal(t, "TOTAL", rows[len(rows)-1][0])
+ sort.Slice(survivors, func(i, j int) bool {
+ return completenessSPService(survivors[i]) < completenessSPService(survivors[j])
+ })
+ for i, plan := range survivors {
+ columns := make(map[string]string)
+ for j, header := range rows[0] {
+ columns[header] = rows[i+1][j]
+ }
+ for field, want := range map[string]string{"Service": completenessSPService(plan), "Region": "", "ResourceType": "", "Count": "1", "Term": "1yr", "PaymentOption": "no-upfront", "UpfrontPayment": "3.00", "RecurringMonthlyCost": "1460.00", "EstimatedSavings": "10.00", "Success": "true", "Error": ""} {
+ require.Equal(t, want, columns[field], "CSV %s; row=%v", field, rows[i+1])
+ }
+ }
+ require.Contains(t, logs, "CSV report written")
+}
+
func assertCompletenessDiagnostics(t *testing.T, regionMode, details, stdout, stderr string) {
t.Helper()
malformed := details == "mixed" || details == "all-invalid"
diff --git a/go.mod b/go.mod
index e8166a8a4..c973ecf9e 100644
--- a/go.mod
+++ b/go.mod
@@ -83,7 +83,7 @@ require (
require (
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.2.0
github.com/LeanerCloud/cloud-commitments-go/pkg v0.0.0-20260929105827-b3b4cb5e3d80
- github.com/LeanerCloud/cloud-commitments-go/providers/aws v0.0.0-20261002152209-006ef5c8d0a2
+ github.com/LeanerCloud/cloud-commitments-go/providers/aws v0.0.0-20261003204812-9962786e0695
github.com/LeanerCloud/cloud-commitments-go/providers/azure v0.0.0-20260928214714-ce9513612901
github.com/LeanerCloud/cloud-commitments-go/providers/gcp v0.0.0-20260928214714-ce9513612901
github.com/aws/aws-sdk-go-v2/service/organizations v1.45.3
diff --git a/go.sum b/go.sum
index b0f0bd421..86de8e6cb 100644
--- a/go.sum
+++ b/go.sum
@@ -80,8 +80,8 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapp
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
github.com/LeanerCloud/cloud-commitments-go/pkg v0.0.0-20260929105827-b3b4cb5e3d80 h1:wVKlMokfaME/Lw525Qz3F155R3nY4VmuaIyh+6d4sCU=
github.com/LeanerCloud/cloud-commitments-go/pkg v0.0.0-20260929105827-b3b4cb5e3d80/go.mod h1:ApWBliDXe099f3oDXBz41K/I9v4bHvn1dG/BGoRmHlw=
-github.com/LeanerCloud/cloud-commitments-go/providers/aws v0.0.0-20261002152209-006ef5c8d0a2 h1:rYxXq0G0O2lkf8AB1ni9m4pVhIdb6Z0y52sTxS1RKIA=
-github.com/LeanerCloud/cloud-commitments-go/providers/aws v0.0.0-20261002152209-006ef5c8d0a2/go.mod h1:d4nsy61/Ptib0SxqMg0ble3yksVm3+QbtUtmGja82PA=
+github.com/LeanerCloud/cloud-commitments-go/providers/aws v0.0.0-20261003204812-9962786e0695 h1:DfNEBzFS7/MaBZljLGRRRUaarfnXXZyBE/6iozzp9/k=
+github.com/LeanerCloud/cloud-commitments-go/providers/aws v0.0.0-20261003204812-9962786e0695/go.mod h1:d4nsy61/Ptib0SxqMg0ble3yksVm3+QbtUtmGja82PA=
github.com/LeanerCloud/cloud-commitments-go/providers/azure v0.0.0-20260928214714-ce9513612901 h1:iSdHYdmGUjcjtSmgGZxptBzDuLrJ9KR2mh1/x7FNvSY=
github.com/LeanerCloud/cloud-commitments-go/providers/azure v0.0.0-20260928214714-ce9513612901/go.mod h1:zgCL/ozOkcZUDbEC7a2UwW+6MPcLoBlEU2WUZ0TORUs=
github.com/LeanerCloud/cloud-commitments-go/providers/gcp v0.0.0-20260928214714-ce9513612901 h1:i6OwXLUheudN3GfwnYXdKuEq8vPdE9qkNJ+r71LRLKA=