Skip to content

FEATURE: Init command#4208

Merged
TomOnTime merged 12 commits into
DNSControl:mainfrom
cafferata:init-command
May 14, 2026
Merged

FEATURE: Init command#4208
TomOnTime merged 12 commits into
DNSControl:mainfrom
cafferata:init-command

Conversation

@cafferata
Copy link
Copy Markdown
Member

@cafferata cafferata commented Apr 25, 2026

Add interactive dnscontrol init onboarding wizard

Closes #4205

Summary

A new user can run dnscontrol init and have a working creds.json plus a starter dnsconfig.js within a minute. The wizard asks the right questions for the chosen provider and writes both files for them. Existing workflows are unaffected; users who prefer to hand edit creds.json can keep doing exactly that.

Why

DNSControl's biggest entry barrier is the very first ten minutes. Newcomers have to discover, on their own, what fields belong in creds.json for their provider, where to create the API token, whether a value should be a secret, whether it spans multiple lines. That information lives in three places (the provider documentation, the provider's source, and the integration tests workflow) and rarely all in the same form. After helping multiple developers through that ramp myself, the friction is recognisable every time.

The wizard collapses that into a single guided session. A maintainer describes their provider's credential fields once via RegisterCredsMetadata, and from then on every newcomer reuses that knowledge.

What this gives users

  • A guided start. Pick a DNS provider, optionally a registrar, type or paste your credentials, name a domain. Done.
  • Sensible defaults. Where the DNS provider can also act as a registrar, the wizard offers to reuse the same credentials. Pressing Enter accepts the suggested defaults where they exist. PEM blocks open the user's $EDITOR so multiline values stay intact.
  • Immediate feedback. Right after writing, the wizard offers to compare the user's dnsconfig.js with the live zones at the provider, and to run dnscontrol preview as a sanity check.
  • A community welcome. The flow closes with pointers to the documentation, GitHub Discussions, and the monthly community video call.

What this gives provider maintainers

A small registration block next to the existing RegisterMaintainer call:

providers.RegisterCredsMetadata("MYPROVIDER", providers.CredsMetadata{
    DisplayName: "My Provider",
    Kind:        providers.KindDNS,
    PortalURL:   "https://portal.example.com/api-tokens",
    Fields: []providers.CredsField{
        {Key: "apitoken", Label: "API Token", Required: true, Secret: true},
    },
})

That is enough for the provider to appear in the wizard. Providers with multiple auth methods (TransIP access token versus account name plus private key, or Route53 static keys versus assume role) can branch with Internal and ShowIf.

Three providers ship with metadata in this PR as a starting point and a worked example: NONE, BIND, and TransIP. The remaining providers can land incrementally in follow up PRs, one per maintainer.

Scope and follow ups

  • In scope here: the wizard, the metadata mechanism, three reference providers, documentation, and tests.
  • Follow up: metadata registrations for the remaining providers, landing incrementally one per maintainer.
  • Possibly later: generating the credentials section of provider documentation pages from the same metadata, parked for a later PR.

Try it

Check out the branch:

gh pr checkout 4208
# or, without the GitHub CLI:
git fetch origin pull/4208/head:init-command && git checkout init-command

Build and run the wizard against an empty directory:

go build -o /tmp/dnscontrol .
mkdir /tmp/init-demo && cd /tmp/init-demo
/tmp/dnscontrol init

Try one of the included providers (NONE, BIND, TransIP) to see the full flow including the optional zone comparison and dnscontrol preview step.

Test plan

  • go build ./...
  • go test ./...
  • Manual run with NONE registrar plus BIND DNS in an empty directory.
  • Manual run with TransIP, both auth methods (access token, and account name plus private key with $EDITOR=nano).
  • Reviewer: walk through the GitBook documentation preview below.

Documentation preview (GitBook)

The new and updated pages render through the GitBook Git Sync. Preview URLs:

Linked discussion

Initial proposal and feedback live in #4205. Earlier consensus there: drop DomainsURL, keep the auth method picker, drop the generic fallback for the first version of the PR, and keep the per provider documentation pages intact.

Copy link
Copy Markdown
Collaborator

@chicks-net chicks-net left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very clean and useful.

provider reduces to a few lines:

```go
providers.RegisterCredsMetadata("MYPROVIDER", providers.CredsMetadata{
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example is good and I wouldn't replace it, but it would be nice if the longer example from the github issue were also included. Another way to accomplish this without writing so much in this doc would be pointing at the other working examples you've done.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. Went with your second suggestion (point at the working examples) so the doc stays compact and does not drift when the providers evolve. The bind and transip references are now clickable links plus a one line description of what each example demonstrates (16e5e1f).

Copy link
Copy Markdown
Collaborator

@TomOnTime TomOnTime left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow! This is really going to make onboarding a lot easier!

Comment thread documentation/commands/init.md Outdated
Comment thread pkg/providers/providers.go Outdated
@cafferata cafferata force-pushed the init-command branch 2 times, most recently from 8f7f6bc to fabc46c Compare May 2, 2026 16:49
@cafferata cafferata marked this pull request as draft May 2, 2026 16:55
@cafferata
Copy link
Copy Markdown
Member Author

Heads up: the lint job is failing on three govet inline warnings in commands/ppreviewPush.go. See the failing job: https://github.com/DNSControl/dnscontrol/actions/runs/25256731676/job/74057255560. I'll fix those locally and push an update before this PR is ready for another review pass.

@cafferata
Copy link
Copy Markdown
Member Author

cafferata commented May 2, 2026

@TomOnTime thanks for the wording suggestions on the documentation.

I've processed your textual feedback in 0d23b6b, so the onboarding docs and the dnscontrol init surfacing should now read more naturally and consistently with the rest of the project.

On top of that, I fixed the failing lint job in 371f05e by migrating from golang.org/x/exp/slices to the stdlib slices package in commands/ppreviewPush.go and providers/bunnydns/, which resolves the govet inline check that was failing on CI.

From my side this is ready for another review pass. Let me know if you'd like any further tweaks!

@cafferata cafferata marked this pull request as ready for review May 2, 2026 17:07
@cafferata cafferata requested a review from TomOnTime May 2, 2026 17:07
@TomOnTime
Copy link
Copy Markdown
Collaborator

Ah! We were both working on the golangci-lint issue at the same time! I will revert #4215 and use your solution.

@cafferata
Copy link
Copy Markdown
Member Author

cafferata commented May 3, 2026

@TomOnTime I've re-resolved the merge conflict with #4222 in go.mod and go.sum. The branch is now rebased on top of the latest main.

@cafferata cafferata force-pushed the init-command branch 2 times, most recently from 947a40b to f25ce33 Compare May 3, 2026 18:06
@cafferata
Copy link
Copy Markdown
Member Author

cafferata commented May 3, 2026

@TomOnTime I've added an additional commit f25ce33 to apply the renamed import paths from github.com/StackExchange/dnscontrol/v4 to github.com/DNSControl/dnscontrol/v4 introduced in #4214. The CI failures on the previous run were caused by the init command files still pointing at the old module path.

TomOnTime pushed a commit that referenced this pull request May 12, 2026
I ran into this same issue while working on #4208 and can confirm the
behavior @stefanwascoding describes. The only form that works requires
the provider name explicitly:

```shell
dnscontrol get-zones transip TRANSIP all
```

```text
WARNING: To retain compatibility in future versions, please change "TRANSIP" to "-". See "https://docs.dnscontrol.org/commands/get-zones"
```

But following that suggestion also fails:

```shell
dnscontrol get-zones transip - all
```

```text
Arguments should be: credskey providername zone(s) (Ex: r53 ROUTE53 example.com)
```

So neither the documented 2 argument form (`transip all`) nor the
suggested dash form (`transip - all`) works.

This pull request fixes the argument parsing so that `get-zones` accepts
2 arguments (`credkey zone`), resolving the provider type from the
`TYPE` field in `creds.json`. The old 3 argument form still works but
now shows a deprecation warning with the correct command:

```shell
dnscontrol get-zones transip TRANSIP all
```

```text
WARNING: The provider name argument is deprecated. Please use "dnscontrol get-zones transip all" instead. See "https://docs.dnscontrol.org/commands/get-zones"
```

With 3+ arguments, the code now detects whether the second argument is a
registered provider type to distinguish the deprecated form (`credkey
provider zone...`) from the new multi-zone form (`credkey zone1
zone2...`):

```shell
dnscontrol get-zones --format=nameonly transip dnscontrol.org dnscontrol.nl
```

```text
dnscontrol.org
dnscontrol.nl
```

Additionally:

- CLI help is shortened and links to online documentation
- Examples use descriptive `credkey` names (`my_route53` instead of
`myr53`) so they are self-explanatory without the provider name
- `Documentation:` links are added to `get-zones` and `check-creds`
- `documentation/commands/get-zones.md` is updated to match

Tested against the TransIP provider with the `dnscontrol.org` zone from
#4204.

- [x] `go test ./commands/`
- [x] `dnscontrol get-zones transip all` (2 args, new style)
- [x] `dnscontrol get-zones transip dnscontrol.org` (specific zone)
- [x] `dnscontrol get-zones transip dnscontrol.org dnscontrol.nl`
(multiple zones, new style)
- [x] `dnscontrol get-zones transip TRANSIP all` (3 args, deprecation
warning)
- [x] `dnscontrol get-zones transip` (too few args, error message)
- [x] `dnscontrol get-zones --format=zone transip dnscontrol.org`
- [x] `dnscontrol get-zones --format=tsv transip dnscontrol.org`
- [x] `dnscontrol get-zones --format=nameonly transip all`

<details>
    <summary>CLI diff</summary>

```diff
--- before (v4.37.1)
+++ after
@@ -2,7 +2,7 @@
    dnscontrol get-zones - gets a zone from a provider (stand-alone)
 
 USAGE:
-   dnscontrol get-zones [command options] credkey provider zone [...]
+   dnscontrol get-zones [command options] credkey zone [...]
 
 CATEGORY:
    utility
@@ -11,34 +11,18 @@
    Download a zone from a provider.  This is a stand-alone utility.
 
    ARGUMENTS:
-      credkey:  The name used in creds.json (first parameter to NewDnsProvider() in dnsconfig.js)
+      credkey:  The name used in creds.json
-      provider: The name of the provider (second parameter to NewDnsProvider() in dnsconfig.js)
       zone:     One or more zones (domains) to download; or "all".
 
-   FORMATS:
-      --format=js        dnsconfig.js format (not perfect, just a decent first draft)
-      --format=djs       js with disco commas (leading commas)
-      --format=zone      BIND zonefile format
-      --format=tsv       TAB separated value (useful for AWK)
-      --format=nameonly  Just print the zone names
-
-   The columns in --format=tsv are:
-      FQDN (the label with the domain)
-      ShortName (just the label, "@" if it is the naked domain)
-      TTL
-      Record Type (A, AAAA, CNAME, etc.)
-      Target and arguments (quoted like in a zonefile)
-      Either empty or a comma-separated list of properties like "cloudflare_proxy=true"
-
-   The --ttl flag only applies to zone/js/djs formats.
-
    EXAMPLES:
-      dnscontrol get-zones myr53 ROUTE53 example.com
-      dnscontrol get-zones gmain GANDI_V5 example.com other.com
-      dnscontrol get-zones cfmain CLOUDFLAREAPI all
-      dnscontrol get-zones --format=tsv bind BIND example.com
-      dnscontrol get-zones --format=djs --out=draft.js gcloud GCLOUD example.com
+      dnscontrol get-zones my_route53 example.com
+      dnscontrol get-zones my_gandi example.com other.com
+      dnscontrol get-zones my_cloudflare all
+      dnscontrol get-zones --format=tsv my_bind example.com
+      dnscontrol get-zones --format=djs --out=draft.js my_gcloud example.com
 
+   Documentation: https://docs.dnscontrol.org/commands/get-zones
+
 OPTIONS:
    --creds string   Provider credentials JSON file (or !program to execute program that outputs json) (default: "creds.json")
    --format string  Output format: js djs zone tsv nameonly (default: "zone")
```
</details>

Fixes #4235
@cafferata
Copy link
Copy Markdown
Member Author

@TomOnTime I've resolved the merge conflicts with #4242 (go.mod/go.sum) and #4225 (docs line unwrapping). The branch is now rebased on top of the latest main.

All previous review feedback has been addressed, so from my side this is ready for a final review pass. Let me know if you'd like any further changes!

@cafferata cafferata force-pushed the init-command branch 3 times, most recently from 52794a7 to d40c5cb Compare May 13, 2026 20:13
@TomOnTime
Copy link
Copy Markdown
Collaborator

I'm going to merge this but there are some minor nits that I'd like you to consider for future releases:

(1) When the default BIND values are used, don't store the value in the JSON:

These lines are unnecessary in creds.json:

    "directory": "zones",
    "filenameformat": "%c.zone"

(2) I think it is confusing if the credkey is the same as the provider name.

How about defaulting to transip_primary instead of transip? This way it is obvious that NewDnsProvider("transip_primary") specifies the credkey, not the provider name.

It also future-proofs the creds.json file in case a second transip account is needed. At a previous company, we had a configuration with 3 azure accounts, named azure, azure_dev, azure_prod. It's obvious what the _dev and _prod credkeys were for. I have no idea what azure was other than it was first.

The name scheme that I recommend is $provider_$account where $account is the username we use for the web portal, or the name of the PAT.


A DNS provider hosts the records (A, MX, TXT, CNAME, and so on) for your zones.
Pick NONE if you want to defer this choice.
? Which DNS service provider do you want to configure? CLOUDFLAREAPI
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I run this I don't get CLOUDFLAREAPI as an option. Did a file not get checked in?

cafferata and others added 8 commits May 14, 2026 08:55
Co-authored-by: Tom Limoncelli <tal@whatexit.org>
- When the user accepts the default for an optional `CredsField`, the value is no longer written to `creds.json`. This avoids redundant entries like `"directory": "zones"` and `"filenameformat": "%c.zone"` for the BIND provider.
…nscontrol init`.

- Change `defaultEntryName` from `cloudflareapi` to `cloudflareapi_primary` so the credkey is visually distinct from the provider TYPE and future-proofs for multiple accounts.
- Update flow test and documentation example to match.
@cafferata
Copy link
Copy Markdown
Member Author

The Cloudflare example in the documentation predates the change to only list providers that have registered onboarding metadata. That change was made after feedback from @tomfitzhenry suggesting there should be no generic fallback, and I forgot to update the Cloudflare example afterwards. The two nits from your other comment are addressed in separate commits on this branch:

@cafferata cafferata requested a review from TomOnTime May 14, 2026 06:57
@TomOnTime
Copy link
Copy Markdown
Collaborator

This is such an exciting new feature! I can't wait for more providers to support it!

(Maybe @fm can help communicate to all the providers)

@TomOnTime TomOnTime merged commit a79e872 into DNSControl:main May 14, 2026
12 checks passed
@cafferata cafferata deleted the init-command branch May 14, 2026 11:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Interactive onboarding command: dnscontrol init

3 participants