Usage

This module is designed to be used as a dxaws primitive: you provide a DnsRecordDesired and the DnsManager converges Route 53 to match.

The most common call pattern is:

  1. Construct a DnsManager

  2. Provide a DnsRecordDesired

  3. Call mgr.execute(desired)


Basic Example: Create / Update / Delete

from dxaws_dns.manager import DnsManager
from dxaws_dns.models import DnsRecordDesired

# AWS Route53 is the default provider
mgr = DnsManager()

# Create or update a TXT record
present = DnsRecordDesired(
    zone_name="dev.dxaws.com",
    record_name="_demo",
    record_type="TXT",
    ttl=60,
    values=["hello from dxaws"],
    state="present",
)

result = mgr.execute(present)
print(result.outcome)  # "applied" or "noop"

# Delete the record
absent = DnsRecordDesired(
    zone_name="dev.dxaws.com",
    record_name="_demo",
    record_type="TXT",
    state="absent",
)

result2 = mgr.execute(absent)
print(result2.outcome)  # "applied" or "noop"

Notes:

  • zone_name is the human-facing identifier and is always required.

  • zone_id is optional; the manager can resolve it via the provider.

  • TXT values may be provided unquoted. The provider/manager normalize quoting to match Route 53 behavior.


Overriding the Provider

Most callers should use DnsManager() directly and treat providers as an internal implementation detail. The manager defaults to the AWS Route53 provider.

Providers exist primarily for dependency injection (testing, alternate backends, recording providers, etc.). If you need to override the provider explicitly you can:

from dxaws_core import AwsSession
from dxaws_dns.manager import DnsManager
from dxaws_dns.providers.aws import Route53Provider

aws = AwsSession(region_name="ca-central-1")
provider = Route53Provider(aws=aws)

mgr = DnsManager(provider=provider)

Application and composition code should normally interact only with the manager surface. If functionality from a provider is needed, it should be exposed through the manager interface rather than accessed directly.

Idempotency

Declarative convergence means you can run the same desired state repeatedly:

result1 = mgr.execute(present)
result2 = mgr.execute(present)

assert result2.outcome == "noop"

If AWS drifts (or you change inputs like TTL), the next run will return "applied" and re-stabilize.


Record Types

The acceptance harness currently validates these record types:

  • TXT

  • A

  • CNAME

Example A record:

present_a = DnsRecordDesired(
    zone_name="dev.dxaws.com",
    record_name="_demo-a",
    record_type="A",
    ttl=60,
    values=["203.0.113.10"],
    state="present",
)

mgr.execute(present_a)

Example CNAME record:

present_cname = DnsRecordDesired(
    zone_name="dev.dxaws.com",
    record_name="_demo-cname",
    record_type="CNAME",
    ttl=60,
    values=["example.com."],
    state="present",
)

mgr.execute(present_cname)

Running Acceptance Tests Safely

Acceptance tests mutate real AWS resources and are treated as contract tests.

They are guarded by:

  • Opt-in: DXAWS_AWS_TESTS=1

  • Explicit confirmation: DXAWS_ACCEPTANCE_CONFIRM must equal the zone name

  • AWS identity printed before mutation

Make target

make accept-dns ZONE=dev.dxaws.com

This target sets:

  • DXAWS_AWS_TESTS=1

  • DXAWS_ACCEPTANCE_ZONE_NAME=$(ZONE)

  • DXAWS_ACCEPTANCE_CONFIRM=$(ZONE)

The test creates and deletes temporary TXT/A/CNAME records and verifies:

  • Create/read/delete lifecycle

  • Idempotency (noop on second apply)

  • TTL drift detection + correction


Troubleshooting

“Value should be enclosed in quotation marks”

Route 53 expects TXT values to be quoted. This module normalizes TXT values, so you should be able to pass unquoted strings in values=[...].

If you see this error, verify you are using Route53Provider from this module and that your running code includes the TXT normalization changes.

“Refusing to run acceptance test…”

Set:

  • DXAWS_ACCEPTANCE_ZONE_NAME=<zone>

  • DXAWS_ACCEPTANCE_CONFIRM=<zone>

…and run again.