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

t()

@type t() :: map()

Functions

decode(binary)

@spec decode(binary()) :: {:ok, t()} | {:error, term()}

encode(doc)

@spec encode(t()) :: {:ok, binary()} | {:error, term()}

new(input, opts \\ [])

@spec new(
  map() | keyword(),
  keyword()
) :: {:ok, t()} | {:error, term()}

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.

safe_summary(doc)

@spec safe_summary(t()) :: map()

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(doc)

@spec validate(t()) :: {:ok, t()} | {:error, term()}

Validate a decoded document, rejecting anything not in canonical shape.

version()

@spec version() :: pos_integer()

The current file format version.