diff --git a/cmd/helpers.go b/cmd/helpers.go index ebb0a0eb5..e8a4cf450 100644 --- a/cmd/helpers.go +++ b/cmd/helpers.go @@ -227,7 +227,8 @@ func ConfirmPurchase(totalInstances int, totalSavings float64, skipConfirmation return response == "yes" || response == "y" } -// CheckAuditLogWritable reports whether the audit log at path can be appended to. +// CheckAuditLogWritable reports whether the audit log and its immediate parent +// directories satisfy the read, append, and durability requirements. // Thin wrapper over common.CheckAuditLogWritable; kept so cmd's existing call sites // and tests are unchanged. func CheckAuditLogWritable(path string) error { return common.CheckAuditLogWritable(path) } diff --git a/cmd/multi_service.go b/cmd/multi_service.go index c3ed439eb..5b0b357f5 100644 --- a/cmd/multi_service.go +++ b/cmd/multi_service.go @@ -122,9 +122,9 @@ func runToolMultiService(ctx context.Context, cfg Config) { go func() { <-sigCh; shutdownRequested.Store(true) }() defer signal.Stop(sigCh) - // Verify audit log is writable before making any cloud API calls. + // Verify the audit log and its immediate parents before making cloud API calls. if err := CheckAuditLogWritable(cfg.AuditLog); err != nil { - log.Fatalf("Cannot write audit log: %v", err) //nolint:gocritic // exitAfterDefer: intentional startup fatal before cleanup matters + log.Fatalf("Cannot prepare durable audit log: %v", err) //nolint:gocritic // exitAfterDefer: intentional startup fatal before cleanup matters } printRunMode(isDryRun) diff --git a/docs/cli/purchase-safety.md b/docs/cli/purchase-safety.md index 9b8ac37af..4652c7d5b 100644 --- a/docs/cli/purchase-safety.md +++ b/docs/cli/purchase-safety.md @@ -68,7 +68,7 @@ Every recommendation - whether purchased or dry-run - gets a JSON line in the au - Whether the run was a dry run - Purchase source (`cli`) -cudly verifies that the audit log path is writable before making any cloud API calls. If it is not writable (e.g. the directory does not exist), the command exits immediately with an error. +cudly verifies that the audit log is readable and writable and that the immediate configured and resolved parent directories can be opened and synced before making any cloud API calls. Higher ancestors need search permission; the immediate parent directories also need read permission and a filesystem that supports directory `fsync`. If the parent does not exist or these durability checks fail, the command exits immediately with an error. ```bash # Write audit records to a shared directory @@ -135,7 +135,7 @@ Setting this environment variable skips the between-purchase delay. This is an i Before any real purchase run: 1. Run without `--purchase` first to review the dry-run CSV output. -2. Check that `--audit-log` points to a writable, durable location. +2. Check that `--audit-log` points to a readable, writable, durable location. 3. If using `--target-coverage`, verify `--rebuy-window-days` is set appropriately for your RI renewal cadence. 4. Narrow the scope with `--include-regions`, `--include-accounts`, or `--min-savings-pct` before buying across all services. 5. Consider `--max-instances` as a final safety cap for a first run. diff --git a/go.mod b/go.mod index 820581201..3c748b3c8 100644 --- a/go.mod +++ b/go.mod @@ -82,10 +82,10 @@ 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-20260928074610-6168f8b5360d - github.com/LeanerCloud/cloud-commitments-go/providers/aws v0.0.0-20260928074610-6168f8b5360d - github.com/LeanerCloud/cloud-commitments-go/providers/azure v0.0.0-20260928074610-6168f8b5360d - github.com/LeanerCloud/cloud-commitments-go/providers/gcp v0.0.0-20260928074610-6168f8b5360d + github.com/LeanerCloud/cloud-commitments-go/pkg v0.0.0-20260928132534-fe940a89483d + github.com/LeanerCloud/cloud-commitments-go/providers/aws v0.0.0-20260928132534-fe940a89483d + github.com/LeanerCloud/cloud-commitments-go/providers/azure v0.0.0-20260928132534-fe940a89483d + github.com/LeanerCloud/cloud-commitments-go/providers/gcp v0.0.0-20260928132534-fe940a89483d github.com/aws/aws-sdk-go-v2/service/organizations v1.45.3 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.40.3 github.com/google/uuid v1.6.0 diff --git a/go.sum b/go.sum index 2e8d6f80b..a0ea401e7 100644 --- a/go.sum +++ b/go.sum @@ -78,14 +78,14 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0 github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 h1:s0WlVbf9qpvkh1c/uDAPElam0WrL7fHRIidgZJ7UqZI= 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-20260928074610-6168f8b5360d h1:kWXws3XgZUwRWZj1dZTP9KxrFrAZKoxi6JsvPIyGqhs= -github.com/LeanerCloud/cloud-commitments-go/pkg v0.0.0-20260928074610-6168f8b5360d/go.mod h1:pYpkdSOCe6cnmhe7t+3B6S0Txjnbm/2hyc/EAlQn8PI= -github.com/LeanerCloud/cloud-commitments-go/providers/aws v0.0.0-20260928074610-6168f8b5360d h1:XtyLtFR23EjV1jRK+bSuEimq6a+s7VMw2pbw2ZD9QSA= -github.com/LeanerCloud/cloud-commitments-go/providers/aws v0.0.0-20260928074610-6168f8b5360d/go.mod h1:CTwoaiQJNefXp5W0AoQcGokMMcCDJQ9m+ML5PUHU6KQ= -github.com/LeanerCloud/cloud-commitments-go/providers/azure v0.0.0-20260928074610-6168f8b5360d h1:UnZ/7Hz3/UdWtIR8HSBptnEoFjPo36DyYdJLALg6zFQ= -github.com/LeanerCloud/cloud-commitments-go/providers/azure v0.0.0-20260928074610-6168f8b5360d/go.mod h1:SPzd/neHw+jTyyKY0rrCVgh38DsYSs0VVQAvc2OJy+s= -github.com/LeanerCloud/cloud-commitments-go/providers/gcp v0.0.0-20260928074610-6168f8b5360d h1:dxfx+OpaGKy/7dzr+XaUpICyWFxEb85uV63rpl7ApCE= -github.com/LeanerCloud/cloud-commitments-go/providers/gcp v0.0.0-20260928074610-6168f8b5360d/go.mod h1:KNRux6gPe5WpG0U3P0wCEEe6+Su00+dnaXpooNBKQGk= +github.com/LeanerCloud/cloud-commitments-go/pkg v0.0.0-20260928132534-fe940a89483d h1:cdVqYayr8z0juNLKM5y3IopcbT8o1vMkouIDcIDN2tw= +github.com/LeanerCloud/cloud-commitments-go/pkg v0.0.0-20260928132534-fe940a89483d/go.mod h1:ApWBliDXe099f3oDXBz41K/I9v4bHvn1dG/BGoRmHlw= +github.com/LeanerCloud/cloud-commitments-go/providers/aws v0.0.0-20260928132534-fe940a89483d h1:vL3eBWycBeAYV1vwJxs4knDzse9v6n9VgrbfN+vkl38= +github.com/LeanerCloud/cloud-commitments-go/providers/aws v0.0.0-20260928132534-fe940a89483d/go.mod h1:CTwoaiQJNefXp5W0AoQcGokMMcCDJQ9m+ML5PUHU6KQ= +github.com/LeanerCloud/cloud-commitments-go/providers/azure v0.0.0-20260928132534-fe940a89483d h1:UnZ5LTWNZoT1xlehBXwhoehTOcQsh4bPk6i0Qx5P/1E= +github.com/LeanerCloud/cloud-commitments-go/providers/azure v0.0.0-20260928132534-fe940a89483d/go.mod h1:SPzd/neHw+jTyyKY0rrCVgh38DsYSs0VVQAvc2OJy+s= +github.com/LeanerCloud/cloud-commitments-go/providers/gcp v0.0.0-20260928132534-fe940a89483d h1:Exy2yM3fcguKVEuHlEwM7+WHhdrhzfxX8MysNCI9IM4= +github.com/LeanerCloud/cloud-commitments-go/providers/gcp v0.0.0-20260928132534-fe940a89483d/go.mod h1:KNRux6gPe5WpG0U3P0wCEEe6+Su00+dnaXpooNBKQGk= github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY= github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= github.com/aws/aws-sdk-go-v2/config v1.29.12 h1:Y/2a+jLPrPbHpFkpAAYkVEtJmxORlXoo5k2g1fa2sUo=