Skip to main content

dxaws-s3 / design

·525 words·3 mins

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.