Repository navigation
cloud-waste: build AwsCloudClient, the real client, per ADR-0024 - #82
Merged
Merged
Conversation
A real, boto3/moto-backed implementation of the existing CloudClient Protocol, alongside FakeCloudClient - the address side (describe_addresses/ release_address) unchanged from ADR-0020; the instance side adds describe_instances/stop_instances and one batched GetMetricData call per scan (CPUUtilization + NetworkIn + NetworkOut, Average over IDLE_LOOKBACK_DAYS) mapped onto the existing avg_cpu_percent/ avg_network_bytes fields - no reshaping of Instance/IdleInstanceAdapter/ detector.py, per ADR-0024's locked v1 simplification. Credentials: CloudWasteAwsSettings carries only region/profile_name - boto3's own default credential chain resolves everything else (env vars, a named profile, an IAM role, SSO), never a project-specific secrets class. DryRun gates both mutating calls whenever allow_live=False (always, everywhere in this repo) - confirmed empirically against moto that this matches real AWS's actual DryRunOperation/InvalidAllocationID.NotFound/ InvalidInstanceID.NotFound error codes exactly. Two new client-layer exceptions classify what adapter.py already needs to: LiveActionsDisabledError (DryRun confirmed it would succeed, but policy never lets it - permanent, since the client's own configuration never changes mid-flight) and TransientCloudError (an unclassified AWS-side failure - retryable). Both dormant in every test/demo/CI path today, since FakeCloudClient never raises either. get_released() on a real client can't recover the original public_ip once an address is truly gone (AWS itself discards it) - returns an honest placeholder rather than a fabricated-looking value; the approval's own Snapshot already recorded the real one durably at proposal time. Found and fixed empirically, not assumed: GetMetricData's EndTime landing exactly on datetime.now() can miss the most recent, still-aggregating period's datapoint - confirmed against moto (standing in for CloudWatch's own documented behaviour), fixed with a small buffer. Tested against moto (ADR-0024 grilled item 2), never a live account - boto3-stubs/mypy-boto3-ec2/mypy-boto3-cloudwatch added as dev-only type stubs. demo.py wires in AwsCloudClient when CLOUD_WASTE_AWS_REGION is set, FakeCloudClient otherwise (unchanged default). README documents the least-privilege IAM policy and the opt-in env var; .env.example added. 71 tests passing (was 51), ruff/mypy clean, demo verified unaffected by default. Closes #80
Nabzx
added a commit
that referenced
this pull request
Sep 5, 2026
ROADMAP's Open decisions section and the README's status section both still described the real AWS client as pending. Updated both now it's merged (#82): cloud-waste has a real, boto3-backed CloudClient implementation alongside the fake one, tested against moto, never invoked with real credentials by anything in this repo.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #80.
What
AwsCloudClient, a realboto3-backed implementation of the existingCloudClientProtocol, alongsideFakeCloudClient(unchanged, still the default everywhere).describe_addresses/release_address.describe_instances,stop_instances, and one batchedGetMetricDatacall per scan (CPUUtilization+NetworkIn+NetworkOut,AverageoverIDLE_LOOKBACK_DAYS) mapped onto the existingavg_cpu_percent/avg_network_bytesfields - no reshaping ofInstance/IdleInstanceAdapter/detector.py, per ADR-0024's locked v1 simplification.CloudWasteAwsSettingscarries onlyregion/profile_name-boto3's own default credential chain resolves everything else. No project-specific secrets class.DryRungates both mutating calls wheneverallow_live=False(always, everywhere in this repo) - confirmed empirically againstmotothat this matches real AWS'sDryRunOperation/InvalidAllocationID.NotFound/InvalidInstanceID.NotFounderror codes exactly.adapter.py's classification:LiveActionsDisabledError(permanent -DryRunconfirmed it would succeed, but policy never lets it) andTransientCloudError(retryable - an unclassified AWS-side failure). Both dormant in every test/demo/CI path, sinceFakeCloudClientnever raises either.get_released()on a real client can't recover an address's originalpublic_iponce it's truly gone (AWS itself discards it) - returns an honest placeholder; the approval's own Snapshot already recorded the real value durably at proposal time.Found while building, fixed on the spot
GetMetricData'sEndTimelanding exactly ondatetime.now()can miss the most recent, still-aggregating period's datapoint - confirmed empirically againstmoto(standing in for CloudWatch's own documented behaviour), fixed with a small buffer.Testing
moto(ADR-0024 grilled item 2) - never a live account, never real credentials.boto3-stubs/mypy-boto3-ec2/mypy-boto3-cloudwatchadded as dev-only type stubs soboto3.client("ec2"/"cloudwatch")resolves to real typed clients under strict mypy.demo.pywires inAwsCloudClientwhenCLOUD_WASTE_AWS_REGIONis set,FakeCloudClientotherwise (unchanged default - verified the demo's output is byte-identical to before). README documents the least-privilege IAM policy (ec2:DescribeAddresses,ec2:ReleaseAddress,ec2:DescribeInstances,ec2:StopInstances,cloudwatch:GetMetricData) and the opt-in env var;.env.exampleadded.Verification
ruff check/ruff format --check/mypy .- all clean.pytest -v- 71 passed (was 51: +16AwsCloudClienttests againstmoto, +4 adapter-level exception-classification tests).uv run python -m cloud_waste.demo- output unchanged from before this PR.cloud-waste/- no other package touched.