Defdo.DDNS.DesiredState
(defdo_ddns v0.5.0)
Copy Markdown
Portable codec for the desired-state file: what DNS should look like.
This is the contract that replaces reading intent out of environment
variables. It is deliberately separate from Defdo.DDNS.RecordSnapshot, which
captures what the runtime observed — conflating the two is how a snapshot of
reality quietly becomes the definition of intent.
Canonical shape:
%{
"version" => 1,
"updated_at" => "2026-07-24T00:00:00Z",
"cloudflare" => %{
"domain_mappings" => %{"example.com" => ["www", "api"]},
"aaaa_domain_mappings" => %{"example.com" => ["www"]},
"cname_records" => [%{"domain" => ..., "name" => ..., "target" => ...,
"proxied" => true, "ttl" => 1}],
"auto_create_missing_records" => true,
"proxy_a_records" => true,
"proxy_exclude" => ["internal.example.com"]
}
}Everything is string-keyed and deterministically ordered so the file diffs
cleanly between runs and String.to_atom/1 is never reachable from input.
Summary
Functions
Build a canonical desired-state document from loose input.
Counts by kind, safe to log.
Validate a decoded document, rejecting anything not in canonical shape.
The current file format version.
Types
@type t() :: map()
Functions
Build a canonical desired-state document from loose input.
Accepts the shape this module emits, the inner cloudflare map on its own, or
a keyword list in the shape config :defdo_ddns, Cloudflare uses — which is
what makes seeding from the existing environment a one-liner.
Counts by kind, safe to log.
Never returns hostnames, targets or flag values — a desired-state file names every host in the estate, so "how many" is the most that may reach a log line.
Validate a decoded document, rejecting anything not in canonical shape.
@spec version() :: pos_integer()
The current file format version.