Design Notes for dxaws-cloudfront¶
This document is the architecture/design reference for dxaws-cloudfront.
The goal is to keep the module contract stable while allowing the internals (planner comparisons, provider implementations, config shapes) to evolve.
Purpose and Scope¶
dxaws-cloudfront converges a single CloudFront distribution to a desired state.
MVP scope:
One CloudFront distribution
One S3 origin
Origin Access Control (OAC)
Optional aliases (CNAMEs)
Optional ACM certificate (must be in
us-east-1for CloudFront)Default cache behavior (MVP subset)
Optional SPA-friendly custom error responses
Deterministic create/update/wait/destroy lifecycle
Non-goals (for now):
Multiple origins / origin groups
Multiple cache behaviors
Cache policies / origin request policies
Logging / realtime logs
WAF / Shield configuration
Lambda@Edge / CloudFront Functions
How this fits into dxaws¶
dxaws modules are primitive building blocks that expose stable, testable
interfaces. Higher-level orchestration (e.g., dxaws-website, Step Functions,
runbooks) composes primitives but should not reach into their internals.
This module is intended to be used by:
dxaws-website(CloudFront distribution fronting an S3 website origin)DNS modules (Route53 alias record creation using distribution domain + hosted zone id)
Runbooks / automation flows (idempotent apply + explicit outputs)
Declarative Convergence¶
This module follows a declarative convergence model.
Callers provide a Desired state. The module:
Discovers the Current state (minimal normalized snapshot)
Plans a list of Actions required to converge
Executes those Actions via a Provider
Waits when necessary for eventual consistency
Key properties:
Idempotent: repeat runs converge to the same result
Deterministic: planning is pure and does not call AWS
Provider-isolated: AWS API details live in providers
Stable contracts: models + provider protocol change cautiously
Module Layers¶
Models (stable contract)¶
models.py defines the stable data model:
DistributionDesired(target state)present: boolis the lifecycle switchpresent=True=> ensure distribution existspresent=False=> ensure distribution is deleted (destroy)
DistributionCurrent(observed state)PlanandAction(what will be done)DistributionResult(outputs)
Rule: Keep these models small, explicit, and backwards-compatible.
Planner (pure)¶
planner.py converts:
(desired, current, oac_id)→Plan(actions=[...])
Planner rules:
No AWS calls
Deterministic config synthesis
Compare using summarized fields only (avoid AWS response drift)
Destroy planning (present=False):
If absent already → noop
Else → DISABLE → WAIT_DEPLOYED → DELETE
Executor (boring)¶
executor.py is a thin action dispatcher:
Executes actions in order
Calls provider methods
Avoids any planning decisions
Provider¶
Providers implement the AWS (or mock) behavior behind a stable protocol.
providers/base.py defines the provider contract.
providers/aws.py implements it using boto3/botocore.
Provider rules:
AWS-only logic lives here
Handle strict AWS request shapes (CloudFront is strict)
Handle eventual consistency where required
notably disable/delete sequencing
Manager¶
manager.py is the orchestrator:
get_current()discovery (by alias)plan()emits plan eventsapply()executes and waitsexecute()is the primary entry pointdestroy(desired)is a convenience wrapper that preserves aliases
Discovery Strategy¶
Discovery is currently alias-driven:
get_current()attemptsprovider.find_distribution_by_alias(aliases[0])If found, pulls config + etag and normalizes into
DistributionCurrent
Implications:
Callers should provide aliases for deterministic discovery.
In future, we can optionally support tag/comment-based discovery.
Naming and Idempotency¶
Stable comment¶
We use a stable comment (dxaws-cloudfront:<name>) to support deterministic
config synthesis and optional discovery.
OAC name length¶
CloudFront enforces strict resource name limits.
We use truncate_with_hash() to generate stable, bounded OAC names.
Waiting and Eventual Consistency¶
CloudFront is eventually consistent.
We model waiting explicitly:
WAIT_DEPLOYEDaction (planned for create/update and optionally wait-only)Manager-level wait for
current.status != Deployed
Destroy requires a stricter sequence:
Disable distribution
Wait until:
status is
Deployedconfig shows
Enabled=False
Delete distribution
The AWS provider is responsible for waiting until CloudFront reflects the disabled state before proceeding.
Outputs¶
DistributionResult includes:
distribution id
distribution domain name
hosted zone id (constant) for Route53 alias record creation
Observability (o11y)¶
Manager emits structured events via dxaws_core.o11y.O11y:
manager.plan.start/manager.plan.donemanager.apply.start/manager.apply.progress/manager.apply.donemanager.execute.done(outcome: noop/applied/failed)
These events are designed to be stable enough for:
CLI output shaping
runbook audit trails
eventual EventBridge integration
Tests¶
We aim for three layers:
Unit tests: planner comparisons, config shape synthesis
Integration tests: manager + stub provider behavior
Acceptance tests: real AWS (slow) for lifecycle correctness
Acceptance tests intentionally reuse persistent resources (e.g., origin bucket) so repeated runs are cheap and predictable.
Extension Guidelines¶
When extending this module:
Prefer adding new optional fields to
DistributionDesiredKeep planner comparisons summarized and stable
Extend the provider protocol only when necessary, and add tests
Maintain backwards compatibility whenever possible
If a change would break existing callers, introduce a new optional field or a new action type instead of changing semantics.