Design

Goals

  1. Converge an S3 bucket to a desired spec for the supported features:

    • tags

    • versioning

    • public access block

  2. Stay declarative: Desired → Current → Plan → Apply → Result

  3. Keep the module primitive and composable: no “SDK wrapper” surface

Future scope (not implemented yet): encryption, lifecycle rules, bucket policy templates (e.g., TLS-only), CloudFront-origin/OAC policy convergence.

Architecture

The purpose of this module is to provide a convergent management interface to Amazon S3 buckets.

We do not expose the full boto3 surface area. Instead, we converge a pragmatic subset of bucket features that are commonly needed by dxaws primitives. This keeps the public contract small, testable, and stable.

Declarative Convergence

This module follows a declarative convergence model.

Instead of issuing imperative AWS calls directly, callers describe the desired state. The module then:

  1. Plans what needs to change to reach that state

  2. Applies the plan using provider-backed AWS operations

This separation makes behavior predictable, testable, and idempotent, while keeping AWS-specific logic isolated in providers and making plans easy to unit test.

Key Concepts

Providers

Providers encapsulate AWS SDK (boto3) calls. The rest of the module treats the provider as an interface so that planning/execution can be unit tested without AWS.

Manager (public facade)

manager.py is the public entrypoint. It:

  1. Validates the desired state

  2. Reads the current state from the provider

  3. Calls the planner to compute a plan

  4. Applies the plan via the executor

  5. Returns a result object

Planner / Executor

  • planner.py computes a deterministic plan (a list of actions) by comparing desired vs current state.

  • executor.py applies the plan using provider-backed operations.

planner.py

Planner rules are deterministic and include purpose-driven invariants:

  1. If bucket doesn’t exist → CREATE_BUCKET

  2. Tags drift → PUT_TAGS

  3. Public access block drift → PUT_PUBLIC_ACCESS_BLOCK

  4. Versioning drift → PUT_VERSIONING

These four rules define the current contract that higher-level modules can rely on.

Future planner rules (not implemented yet)

  • Encryption drift → PUT_ENCRYPTION

  • Bucket policy convergence:

    • TLS-only deny statement

    • CloudFront OAC allow-read statement

  • Lifecycle rules / intelligent-tiering / replication templates

executor.py

The executor applies actions in a safe order:

  1. Create bucket (if needed)

  2. Public access block

  3. Versioning

  4. Tags

Returns S3BucketResult from a provider read-back.

providers/base.py (interface)

boto3 methods that will be encapsulated:

  • get_current(name) -> S3BucketCurrent

  • create_bucket(desired)

  • put_tags(name, tags)

  • put_public_access_block(name, config)

  • put_versioning(name, enabled: bool)

  • destroy_bucket(name)

  • get_outputs(name) -> S3BucketResult

AWS implementation in providers/aws.py uses boto3 S3.

constants.py / exceptions.py

Defaults:

  • DEFAULT_ENCRYPTION = "SSE-S3"

  • DEFAULT_BLOCK_PUBLIC_ACCESS = True

  • DEFAULT_ENFORCE_TLS = True

Exceptions:

  • InvalidDesiredStateError

  • BucketNameError

  • MissingCloudFrontInfoError (only if strict mode is enabled)

CLI Surface

The CLI orchestration for S3 lives in dxaws-cli.

This module remains Python-first and focuses on providing a stable manager/provider contract that higher-level tooling can call.

Tradeoffs

Eventual Consistency

Some S3 operations (especially create/delete and feature reads immediately after) can be eventually consistent.

Acceptance tests intentionally include:

  • a visibility wait after create

  • drift + converge checks

  • a delete waiter with small grace delays

This keeps the contract stable while acknowledging AWS propagation behavior.

CloudFront / Policy Convergence

CloudFront-origin and policy convergence (TLS-only, OAC statements, etc.) are intentionally deferred. The current module stays focused on the primitive features we already rely on.