SysEngineering

Domain Expiry Sync

Automatically keeps the Expiration Date on each domain in JSM Assets in sync with the real expiration date from the domain’s registrar. Today it reads GoDaddy; the registrar layer is pluggable so more registrars drop in later.

Source (read) → registrar APIs (GoDaddy) Target (write) → JSM Assets objects, matched by domain name

How it works

  1. Fetch every domain + expiration date from each configured registrar provider. GoDaddy is queried once per account (many accounts supported).
  2. Load the domain objects from JSM Assets. The schema is self-discovered at runtime — the object type and attribute IDs are resolved from their display names, so nothing brittle is hardcoded. If a name is wrong, the first run prints the real available names and exits.
  3. Join assets to registrar data by domain name and classify each one: queued-for-update, already-correct, not-found-at-registrar, unsupported-registrar, or at-registrar-but-not-in-JSM.
  4. Write — only where the calendar date actually differs (idempotent), and only for assets whose Registrar maps to a configured provider. Everything else is reported and left untouched.

Every run writes a JSON log, a markdown report, and a styled standalone HTML report next to the script (all gitignored). On a live run it also publishes a monthly page to Confluence and attaches the HTML report to it (see Confluence publishing).

domain_expiry_sync/
  sync-domain-expiry.js      # orchestrator: fetch → load → join → dry-run/write → report → publish
  lib/
    http.js                  # shared https helper (matches repo idiom)
    dates.js                 # tz-safe calendar-date compare/format (unit-tested)
    assets.js                # JSM Assets client with runtime schema self-discovery
    report_html.js           # styled standalone HTML report (the attached file)
    report_confluence.js     # same report rendered as Confluence storage format
    confluence.js            # Confluence REST client (create/update page, attach file)
    registrars/
      index.js               # provider registry + registrar-name matching
      godaddy.js             # GoDaddy provider (multi-account)
      rdap.js                # keyless RDAP fallback for registrars with no API

Registrars with no API (RDAP fallback)

Some registrars — Network Solutions is the notable one here — offer no customer API, so there’s no key to get. Instead of leaving those domains unsynced, the sync falls back to RDAP (RFC 9083), the registry’s keyless, structured successor to WHOIS. Every gTLD registry is required by ICANN to serve it, so we read the real expiration date straight from the registry regardless of who the registrar is.

To add a real API for a registrar later (better than RDAP where one exists), drop a lib/registrars/<name>.js exporting { key, label, aliases, fetch, isConfigured } and list it in index.js — the orchestrator is unchanged.

Setup

1. Atlassian API token (write access to Assets)

Use an Atlassian account with write permission on the Assets object schema. Create a token at https://id.atlassian.com/manage-profile/security/api-tokens (same token type as okta_audit_log’s CONFLUENCE_API_TOKEN — it can be the same token if that account has Assets write access).

2. GoDaddy production API keys — one per account

GoDaddy restricts API access: production keys only return data for an account holding 10+ domains (you qualify). Have the account owner (Anthony) generate, for each GoDaddy account that holds domains:

Test/OTE keys from the same page point at api.ote-godaddy.com and won’t see production domains — make sure they’re Production keys.

3. Configure

cd integrations/domain_expiry_sync
npm install
cp .env.example .env    # then fill it in

Put every GoDaddy account into GODADDY_CREDENTIALS as a JSON array:

[
  {"account":"56054964","key":"KEY_1","secret":"SECRET_1"},
  {"account":"12345678","key":"KEY_2","secret":"SECRET_2"}
]

(For local runs you can instead drop that array into a gitignored godaddy-accounts.json beside the script.)

First run — validate before writing

Because this writes to production Assets, walk it up carefully:

# 1. Dry run. This also confirms the self-discovered schema is correct.
npm run dry-run
# 2. Write just a couple of objects and eyeball them in Jira first.
node sync-domain-expiry.js --limit 2
# 3. Once you trust it, run the whole set.
npm run run-live

Useful flags

Flag Effect
--dry-run Print the diff, write nothing.
--limit N Cap the number of live updates (safety for first runs).
--registrar godaddy Only process one provider’s assets.

Confluence publishing

On a live run (never on a dry run), the sync creates or updates one page per calendar month and attaches the styled HTML report to it — mirroring how okta_audit_log keeps its monthly pages. This is what the IT and Helpdesk directors read; the page is live, searchable, and permission-controlled by Confluence.

Written against the Confluence v2 API on purpose — v2’s parentId accepts a folder as the parent, which the older v1 ancestors field does not.

Set CONFLUENCE_PUBLISH=false to disable, or unset CONFLUENCE_SPACE_KEY / CONFLUENCE_PARENT_ID to skip publishing without failing. See .env.example.

Scheduling

.github/workflows/domain_expiry_sync.yml runs on the 1st of each month: it writes the live updates to JSM, publishes the monthly Confluence page, and also uploads the report as a build artifact (90-day retention). Manual runs via workflow_dispatch default to dry run (which skips both writes and Confluence).

Add these repository secrets (Settings → Secrets and variables → Actions):

Non-secret config (base URL, schema/attribute names, date format, and the Confluence space key + parent folder id) is set as plain env: in the workflow — adjust there if the .env overrides changed.

Status / caveats

Roadmap (phase 2)