diff --git a/CHANGELOG.md b/CHANGELOG.md index 100e661..f531641 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -127,6 +127,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 working. No behaviour change; the wrapped error message is now uniform (`kaswrite: …`). +### Fixed + +- `dns list` now exposes the documented optional `record_id` filter + instead of a non-existent `nameserver` parameter: the flag is renamed + `--nameserver` → `--record-id` and the wire parameter + `nameserver` → `record_id`, matching the KAS `get_dns_settings` + contract (`zone_host` required, `record_id` optional) and the captured + request fixtures. `nameserver` is a real KAS key, but for + `reset_dns_settings`, not `get_dns_settings`; the read slice had + carried it over by mistake. + ### Added - Directory protection write endpoints (#123, #13 write slice): diff --git a/ROADMAP.md b/ROADMAP.md index 220e724..73fd7b6 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -45,7 +45,7 @@ Cross-cutting prerequisites for the v0.2.0 write phase — these are not KAS-API - [x] `domains list` / `domains get ` (`get_domains`, with `domain_name` filter) - [x] `subdomains list` / `subdomains get ` (`get_subdomains`, with `subdomain_name` filter) - [x] `tlds list` (`get_topleveldomains`) -- [x] `dns list --domain [--nameserver ]` (`get_dns_settings`) +- [x] `dns list --domain [--record-id ]` (`get_dns_settings`) - [ ] DNS write paths (`add_dns_settings`, `update_dns_settings`, `delete_dns_settings`, `reset_dns_settings`, #113) - [ ] Domain write paths (`add_domain`, `update_domain`, `delete_domain`, `move_domain`, #111) - [ ] Subdomain write paths (`add_subdomain`, `update_subdomain`, `move_subdomain`, `delete_subdomain`, #112) diff --git a/docs/cli/kasapi-cli_dns_list.md b/docs/cli/kasapi-cli_dns_list.md index aa035a1..c480920 100644 --- a/docs/cli/kasapi-cli_dns_list.md +++ b/docs/cli/kasapi-cli_dns_list.md @@ -9,9 +9,9 @@ kasapi-cli dns list [flags] ### Options ``` - --domain string zone host (required, e.g. example.com) - -h, --help help for list - --nameserver string authoritative nameserver to query; empty uses the KAS default + --domain string zone host (required, e.g. example.com) + -h, --help help for list + --record-id string resource record ID to narrow the result to a single record; empty lists every record ``` ### Options inherited from parent commands diff --git a/internal/cli/dns.go b/internal/cli/dns.go index 0b6ecab..8e4e7f5 100644 --- a/internal/cli/dns.go +++ b/internal/cli/dns.go @@ -11,7 +11,7 @@ import ( ) // NewDNSCmd returns the "kasapi-cli dns" subcommand tree: -// list --domain [--nameserver ] (get_dns_settings). +// list --domain [--record-id ] (get_dns_settings). func NewDNSCmd(opts *RootOptions) *cobra.Command { cmd := &cobra.Command{ Use: "dns", @@ -22,7 +22,7 @@ func NewDNSCmd(opts *RootOptions) *cobra.Command { } func newDNSListCmd(opts *RootOptions) *cobra.Command { - var domainName, nameserver string + var domainName, recordID string cmd := &cobra.Command{ Use: "list", Short: "List DNS records for a zone (get_dns_settings)", @@ -39,11 +39,11 @@ func newDNSListCmd(opts *RootOptions) *cobra.Command { return nil }, RunE: runListE(opts, "get_dns_settings", func(c *api.Client, ctx context.Context) (dns.RecordList, error) { - return dns.NewClient(c).Settings(ctx, domainName, nameserver) + return dns.NewClient(c).Settings(ctx, domainName, recordID) }), } cmd.Flags().StringVar(&domainName, "domain", "", "zone host (required, e.g. example.com)") - cmd.Flags().StringVar(&nameserver, "nameserver", "", - "authoritative nameserver to query; empty uses the KAS default") + cmd.Flags().StringVar(&recordID, "record-id", "", + "resource record ID to narrow the result to a single record; empty lists every record") return cmd } diff --git a/internal/cli/dns_test.go b/internal/cli/dns_test.go index 457a461..4ce6d78 100644 --- a/internal/cli/dns_test.go +++ b/internal/cli/dns_test.go @@ -25,3 +25,27 @@ func TestDNSCmdHelpListsSubcommands(t *testing.T) { t.Errorf("--help output missing %q\n%s", "list", out) } } + +// get_dns_settings takes an optional record_id filter, not a +// nameserver. Guard the flag rename so the wrong parameter cannot +// creep back into the read slice. +func TestDNSListExposesRecordIDFlag(t *testing.T) { + t.Parallel() + root, opts := cli.NewRootCmd() + root.AddCommand(cli.NewDNSCmd(opts)) + + var buf bytes.Buffer + root.SetOut(&buf) + root.SetErr(&buf) + root.SetArgs([]string{"dns", "list", "--help"}) + if err := root.Execute(); err != nil { + t.Fatalf("Execute: %v", err) + } + out := buf.String() + if !strings.Contains(out, "--record-id") { + t.Errorf("dns list --help missing --record-id flag\n%s", out) + } + if strings.Contains(out, "--nameserver") { + t.Errorf("dns list --help still exposes the wrong --nameserver flag\n%s", out) + } +} diff --git a/internal/dns/dns.go b/internal/dns/dns.go index cc056de..243f576 100644 --- a/internal/dns/dns.go +++ b/internal/dns/dns.go @@ -43,16 +43,16 @@ func NewClient(c Caller) *Client { return &Client{API: c} } // Settings calls get_dns_settings for the given zone host and decodes // the response into a RecordList. zoneHost is required (the zone the -// records belong to, e.g. "example.com"). nameserver is optional and -// pinpoints which authoritative NS to query when the zone is served -// by more than one — leave it empty to use the KAS default. -func (c *Client) Settings(ctx context.Context, zoneHost, nameserver string) (RecordList, error) { +// records belong to, e.g. "example.com"). recordID is optional and +// narrows the result to the single resource record with that ID — +// leave it empty to list every record in the zone. +func (c *Client) Settings(ctx context.Context, zoneHost, recordID string) (RecordList, error) { if zoneHost == "" { return nil, fmt.Errorf("dns: zone_host is required") } params := map[string]any{"zone_host": zoneHost} - if nameserver != "" { - params["nameserver"] = nameserver + if recordID != "" { + params["record_id"] = recordID } resp, err := c.API.Call(ctx, "get_dns_settings", params) if err != nil { diff --git a/internal/dns/dns_test.go b/internal/dns/dns_test.go index 833d578..79a5553 100644 --- a/internal/dns/dns_test.go +++ b/internal/dns/dns_test.go @@ -63,23 +63,29 @@ func TestClientSettings(t *testing.T) { if zh, _ := fc.GotParams["zone_host"].(string); zh != "example.com" { t.Errorf("params[zone_host] = %v, want example.com", fc.GotParams["zone_host"]) } - if _, ok := fc.GotParams["nameserver"]; ok { - t.Errorf("params[nameserver] set but nameserver was empty: %v", fc.GotParams) + if _, ok := fc.GotParams["record_id"]; ok { + t.Errorf("params[record_id] set but recordID was empty: %v", fc.GotParams) } if len(list) == 0 { t.Errorf("len = %d, want a non-empty list", len(list)) } } -func TestClientSettingsWithNameserver(t *testing.T) { +// With a record_id the request narrows to a single record; the +// zone_host+record_id fixture returns a one-element ReturnInfo array. +func TestClientSettingsWithRecordID(t *testing.T) { t.Parallel() - resp := testutil.DecodeFixture(t, "dns/get_dns_settings_response_success.xml") + resp := testutil.DecodeFixture(t, "dns/get_dns_settings_zone_host_and_record_id_response_success.xml") fc := &testutil.FakeCaller{Resp: resp} - if _, err := dns.NewClient(fc).Settings(context.Background(), "example.com", "ns.example.com"); err != nil { + list, err := dns.NewClient(fc).Settings(context.Background(), "example.com", "110118416") + if err != nil { t.Fatalf("Settings: %v", err) } - if ns, _ := fc.GotParams["nameserver"].(string); ns != "ns.example.com" { - t.Errorf("params[nameserver] = %v, want ns.example.com", fc.GotParams["nameserver"]) + if id, _ := fc.GotParams["record_id"].(string); id != "110118416" { + t.Errorf("params[record_id] = %v, want 110118416", fc.GotParams["record_id"]) + } + if len(list) != 1 || list[0].ID != "110118416" { + t.Errorf("list = %+v, want a single record with ID 110118416", list) } } diff --git a/testdata/dns/get_dns_settings_request.xml b/testdata/dns/get_dns_settings_request.xml index 60bf6a6..01b53fb 100644 --- a/testdata/dns/get_dns_settings_request.xml +++ b/testdata/dns/get_dns_settings_request.xml @@ -5,8 +5,7 @@ { "KasRequestParams": { - "zone_host": "example.com", - "nameserver": "ns.example.com" + "zone_host": "example.com" }, "kas_action": "get_dns_settings", "kas_auth_data": "REDACTED", diff --git a/testdata/dns/get_dns_settings_response_success.xml b/testdata/dns/get_dns_settings_response_success.xml index b9d7119..8f1502f 100644 --- a/testdata/dns/get_dns_settings_response_success.xml +++ b/testdata/dns/get_dns_settings_response_success.xml @@ -1,5 +1,5 @@ - + @@ -21,10 +21,6 @@ zone_host example.com - - nameserver - ns.example.com - diff --git a/testdata/dns/get_dns_settings_zone_host_and_record_id_request.xml b/testdata/dns/get_dns_settings_zone_host_and_record_id_request.xml new file mode 100644 index 0000000..67bdb90 --- /dev/null +++ b/testdata/dns/get_dns_settings_zone_host_and_record_id_request.xml @@ -0,0 +1,19 @@ + + + + + + { + "KasRequestParams": { + "zone_host": "example.com", + "record_id": "110118416" + }, + "kas_action": "get_dns_settings", + "kas_auth_data": "REDACTED", + "kas_auth_type": "session", + "kas_login": "w0000000" + } + + + + \ No newline at end of file diff --git a/testdata/dns/get_dns_settings_zone_host_and_record_id_response_success.xml b/testdata/dns/get_dns_settings_zone_host_and_record_id_response_success.xml new file mode 100644 index 0000000..3dc945a --- /dev/null +++ b/testdata/dns/get_dns_settings_zone_host_and_record_id_response_success.xml @@ -0,0 +1,87 @@ + + + + + + + Request + + + KasRequestTime + 1780152282 + + + KasRequestType + get_dns_settings + + + KasRequestParams + + + zone_host + example.com + + + record_id + 110118416 + + + + + + + Response + + + KasFloodDelay + 0.5 + + + ReturnString + TRUE + + + ReturnInfo + + + + record_zone + example.com + + + record_name + abc012345678901._domainkey + + + record_type + TXT + + + record_data + v=DKIM1; k=rsa; p=rsa-key + + + record_aux + 0 + + + record_id + 110118416 + + + record_changeable + Y + + + record_deleteable + N + + + + + + + + + + \ No newline at end of file