# Usage `dxaws-acm` manages **DNS-validated ACM certificates** and can optionally converge the required Route53 validation records. The primary public entry point is the manager: - `AcmManager.execute(desired, options=...)` --- ## Basic example Request (or converge) a DNS-validated certificate for a single domain. ```python from dxaws_acm.manager import AcmManager, ExecuteOptions, ApplyOptions from dxaws_acm.models import AcmDesired # AWS is the default provider. mgr = AcmManager() desired = AcmDesired( domain_name="example.test.dxaws.com", subject_alternative_names=[], # Prefer zone_name; the manager will resolve the hosted zone id. zone_name="test.dxaws.com", # CloudFront certificates must be in us-east-1. region="us-east-1", tags={"dxaws:module": "acm"}, ) opts = ExecuteOptions( apply_options=ApplyOptions( # waiting controls for DNS publication + issuance max_wait_seconds=900, poll_interval_seconds=15, ) ) result = mgr.execute(desired, options=opts) print(result.outputs.certificate_arn) ``` Notes: - The manager waits for ACM to publish DNS validation records, converges the validation records via the DNS integration, then waits for `ISSUED`. - Re-running the same call is idempotent. --- ## With Subject Alternative Names (SANs) ```python desired = AcmDesired( domain_name="example.test.dxaws.com", subject_alternative_names=[ "www.example.test.dxaws.com", "static.example.test.dxaws.com", ], zone_name="test.dxaws.com", # CloudFront certificates must be in us-east-1. region="us-east-1", tags={"dxaws:module": "acm"}, ) result = mgr.execute(desired, options=opts) print(result.outputs.certificate_arn) ``` --- ## Overriding providers Most callers should use `AcmManager()` directly and treat providers as an internal implementation detail. Providers exist primarily for dependency injection (testing, recording providers, alternate backends). If you need to override providers explicitly: ```python from dxaws_core import AwsSession from dxaws_acm.manager import AcmManager from dxaws_acm.models import AcmDesired from dxaws_acm.providers.aws import AwsProvider from dxaws_acm.providers.route53 import Route53RecordProvider aws = AwsSession(region_name="us-east-1") acm = AwsProvider(aws=aws) dns = Route53RecordProvider(aws=aws) mgr = AcmManager(provider=acm, dns_provider=dns) desired = AcmDesired( domain_name="example.test.dxaws.com", subject_alternative_names=[], zone_name="test.dxaws.com", region="us-east-1", tags={"dxaws:module": "acm"}, ) result = mgr.execute(desired) print(result.outputs.certificate_arn) ``` If you find yourself needing direct access to a provider method from application or composition code, prefer exposing that capability via the manager interface instead. --- ## Recommended acceptance workflow (real AWS) Acceptance tests are opt-in and **only** run when `DXAWS_AWS_TESTS=1`. Recommended environment variables (export once in your shell): - `DXAWS_ACCEPTANCE_ACCOUNT` – dedicated test account id - `DXAWS_ACCEPTANCE_ZONE_NAME` – hosted zone used for validation (e.g. `test.dxaws.com`) - `DXAWS_TEST_REGION` – AWS region for the run (e.g. `ca-central-1`, `us-east-1` for cloudfront) Run: ```bash DXAWS_AWS_TESTS=1 make accept-acm ``` To keep resources for debugging: ```bash DXAWS_AWS_TESTS=1 DXAWS_ACCEPTANCE_CLEANUP=0 make accept-acm ``` --- ## Troubleshooting ### Certificate is stuck in PENDING_VALIDATION Common causes: - The hosted zone is wrong (or not delegated correctly) - Route53 validation records were not created - You are requesting in the wrong AWS region What to check: - Confirm the test is running in the expected AWS account (the acceptance output prints STS identity) - Confirm `DXAWS_ACCEPTANCE_ZONE_NAME` resolves to the correct public hosted zone - Confirm the validation CNAME exists in the hosted zone ### ResourceNotFoundException during drift tests ACM is eventually consistent: certificates can appear in list results but fail describe calls briefly. `dxaws-acm` hardens this by skipping certificates that disappear between list/describe when enriching list results. --- # Patch for tests/acceptance/test_website_sequential_acceptance.py assert bucket_name_out_2 == bucket_name_out, ( f"Expected same bucket name on idempotent apply; first={bucket_name_out!r} second={bucket_name_out_2!r}" ) # Step gate: default to stopping after S3 while we build this up incrementally. # Set DXAWS_ACCEPTANCE_STEP=acm (or later steps) to continue. step = (os.getenv("DXAWS_ACCEPTANCE_STEP") or "s3").strip().lower() if step in {"s3", "bucket"}: return