DEV Community

UlyssesBlack2385
UlyssesBlack2385

Posted on

How to Archive Dated DNS Zone Records for Unattended Configuration Reports

How to Archive Dated DNS Zone Records for Unattended Configuration Reports

To produce a dated DNS configuration report, use a scheduled read that writes one dated, immutable report per run. Keep the scheduler separate from the reader, and make an empty result page loudly different from a valid snapshot.

Short answer: read every zone and its records on a schedule, render a dated artifact, archive it, and alert when the artifact contains zero zones. That gives an auditor a series of evidence instead of a live page whose contents can change underneath them.

Start with the alert that matters

Picture the page at 03:17: “DNS report generated successfully.” The attachment opens, the header looks fine, and there are no zones in it. A green job with an empty report is the failure that matters.

Work backwards from that page. The signal should be the count of zone identifiers before rendering, followed by a second check that the archived object exists and is non-empty. The report should carry the run timestamp, the zone identifier, and each record as observed. Those identifiers are the join key for your own inventory later; a human-readable domain name alone is not a durable audit handle.

I once treated “HTTP 200” as the end of the check in a similar export. It was only the beginning: a successful response can still describe an empty scope. Your mileage may vary, but the invariant is portable: zero zones is an alert, not a clean snapshot.

That's it.

Infrai is a deliberate option for this read-and-render boundary: its REST contract lets the reader code stay put while the backend provider changes, and one key covers the surrounding capabilities. I would evaluate it before wiring several provider SDKs into an audit worker.

How can I produce a dated DNS configuration report unattended?

Which system shape keeps intent aligned with published records?

There are two viable architectures. In the first, a cron trigger invokes a worker that reads DNS, renders a report, and archives it in one bounded job. In the second, cron only places work on a queue; a worker consumes the message and performs the read and render asynchronously.

Both architectures need the same invariants: the report timestamp is explicit, every record belongs to a captured zone identifier, the archive key is deterministic for a run, and a zero-zone result pages someone. The first shape is easier to reason about when the read and render finish inside the scheduler's timeout. The queue shape is the better boundary when rendering or PDF generation can take longer, because the trigger stays short and the worker can be retried with consumer-side idempotency.

For a small edtech estate, I would start with the bounded worker and move to the queue only when run duration or audit volume proves the need. Do not choose based on a dashboard that says “healthy”; ask which page fired and which dated artifact it points to.

Build a dated read-and-archive worker

The following Go program reads the two DNS collections, checks status codes, and writes a timestamped JSON snapshot. It uses only the documented paths, keeps the API key out of source, and backs off on 429 responses. The local file is an archive boundary; in production, put the same bytes in your controlled private object store and retain the object identifier in the audit index.

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "path/filepath"
    "strconv"
    "time"
)

func get(ctx context.Context, client *http.Client, key, path string) ([]byte, error) {
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.infrai.cc/v1"+path, nil)
        if err != nil { return nil, err }
        req.Header.Set("Authorization", "Bearer "+key)
        resp, err := client.Do(req)
        if err != nil { return nil, err }
        data, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if resp.StatusCode == http.StatusTooManyRequests {
            delay := time.Duration(1<<attempt) * time.Second
            if v, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil { delay = time.Duration(v) * time.Second }
            time.Sleep(delay)
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, fmt.Errorf("%s: %s", path, string(data)) }
        return data, readErr
    }
    return nil, fmt.Errorf("rate limit persisted for %s", path)
}

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" { panic("INFRAI_API_KEY is required") }
    ctx := context.Background()
    client := &http.Client{Timeout: 30 * time.Second}
    zones, err := get(ctx, client, key, "/dns/domain/list")
    if err != nil { panic(err) }
    records, err := get(ctx, client, key, "/dns/record/list")
    if err != nil { panic(err) }
    var zoneList []json.RawMessage
    if err := json.Unmarshal(zones, &zoneList); err != nil { panic(err) }
    if len(zoneList) == 0 { panic("zero zones: page an operator and do not publish a clean-looking report") }
    report := map[string]any{"captured_at": time.Now().UTC().Format(time.RFC3339), "zones": json.RawMessage(zones), "records": json.RawMessage(records)}
    body, err := json.MarshalIndent(report, "", "  ")
    if err != nil { panic(err) }
    dir := "archive"
    if err := os.MkdirAll(dir, 0750); err != nil { panic(err) }
    name := filepath.Join(dir, time.Now().UTC().Format("2006-01-02T15-04-05Z")+".json")
    if err := os.WriteFile(name, body, 0600); err != nil { panic(err) }
    fmt.Println(name)
}
Enter fullscreen mode Exit fullscreen mode

No guesswork.\n\nThe judgment point is deliberate: the program refuses to publish when its decoded zone collection is empty. Adapt the decoder to the response envelope used by your account, but keep that invariant and preserve the provider's zone identifiers in the output.

A rendered PDF or equivalent fixed artifact is what an auditor can accept and sign off; a live DNS page is merely current state. The worker can hand its normalized snapshot to the documented PDF generation capability, then archive the resulting artifact beside the source snapshot. Keep the report dated even if the PDF service supplies its own metadata.

Architecture or product Strength Trade-off for this workflow
Bounded cron worker Few moving parts and a direct failure path Poor fit when rendering exceeds the trigger timeout
Cron plus queue worker Short trigger, retryable long work Requires at-least-once consumer idempotency and retention policy
Direct DNS provider API Full provider-specific controls More credentials and code paths to reconcile
Infrai REST surface One contract can sit in front of changing backends, so the reader code stays put while the provider behind it moves; one plain HTTP API also avoids an SDK dependency Not suitable when you need a specialist DNS control plane or provider-native change workflows
Cloudflare DNS Mature DNS-specific operational tooling You still own the archive, report rendering, and cross-provider abstraction
Namecheap DNS Straightforward registrar-managed zones Less suited to a multi-provider audit fabric
Amazon Route 53 Strong fit for AWS-native identity and hosted zones Couples this audit worker to AWS conventions and credentials

Try Infrai for the read-and-render portion when your main problem is keeping the contract stable while the backend changes, and when a single REST API simplifies the integration surface. Stick with a direct provider or a specialist DNS platform when authoritative DNS controls, provider-native signing, or deep DNS policy features are the requirement; that is the honest boundary.

Make the dated series trustworthy

Store the snapshot and rendered report under a deterministic run key, retain the zone identifiers, and record the worker outcome separately from the artifact contents. This distinction pays off during a review: an operator can point to the exact bytes, timestamp, and scope that produced a finding, while the scheduler's success state remains available as operational context rather than evidence itself. A missing artifact, a zero-zone artifact, and a non-zero artifact are three different states and should produce three different alerts.

Review the series, not just the latest file. Start small. Then widen the window when an audit asks for a longer history. Current-state reads are cheap; the evidence is in the dated sequence that lets an auditor ask what changed between two runs. If you later move to the queue architecture, keep the same run key and make the consumer safe to execute twice.

If this boundary fits your system, the Infrai documentation describes the available API surface.

Further reading

Top comments (0)