Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Cross-cutting prerequisites for the v0.2.0 write phase — these are not KAS-API
- [x] `domains list` / `domains get <name>` (`get_domains`, with `domain_name` filter)
- [x] `subdomains list` / `subdomains get <name>` (`get_subdomains`, with `subdomain_name` filter)
- [x] `tlds list` (`get_topleveldomains`)
- [x] `dns list --domain <d> [--nameserver <ns>]` (`get_dns_settings`)
- [x] `dns list --domain <d> [--record-id <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)
Expand Down
6 changes: 3 additions & 3 deletions docs/cli/kasapi-cli_dns_list.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions internal/cli/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

// NewDNSCmd returns the "kasapi-cli dns" subcommand tree:
// list --domain <d> [--nameserver <ns>] (get_dns_settings).
// list --domain <d> [--record-id <id>] (get_dns_settings).
func NewDNSCmd(opts *RootOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "dns",
Expand All @@ -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)",
Expand All @@ -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
}
24 changes: 24 additions & 0 deletions internal/cli/dns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
12 changes: 6 additions & 6 deletions internal/dns/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
20 changes: 13 additions & 7 deletions internal/dns/dns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
3 changes: 1 addition & 2 deletions testdata/dns/get_dns_settings_request.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
<Params>
{
"KasRequestParams": {
"zone_host": "example.com",
"nameserver": "ns.example.com"
"zone_host": "example.com"
},
"kas_action": "get_dns_settings",
"kas_auth_data": "REDACTED",
Expand Down
6 changes: 1 addition & 5 deletions testdata/dns/get_dns_settings_response_success.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="https://kasapi.example.com/soap/KasApi.php" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns2="http://xml.apache.org/xml-soap" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="https://kasapi.kasserver.com/soap/KasApi.php" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns2="http://xml.apache.org/xml-soap" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<ns1:KasApiResponse>
<return xsi:type="ns2:Map">
Expand All @@ -21,10 +21,6 @@
<key xsi:type="xsd:string">zone_host</key>
<value xsi:type="xsd:string">example.com</value>
</item>
<item>
<key xsi:type="xsd:string">nameserver</key>
<value xsi:type="xsd:string">ns.example.com</value>
</item>
</value>
</item>
</value>
Expand Down
19 changes: 19 additions & 0 deletions testdata/dns/get_dns_settings_zone_host_and_record_id_request.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tns="https://kasserver.com/">
<soapenv:Body>
<tns:KasApi>
<Params>
{
"KasRequestParams": {
"zone_host": "example.com",
"record_id": "110118416"
},
"kas_action": "get_dns_settings",
"kas_auth_data": "REDACTED",
"kas_auth_type": "session",
"kas_login": "w0000000"
}
</Params>
</tns:KasApi>
</soapenv:Body>
</soapenv:Envelope>
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="https://kasapi.kasserver.com/soap/KasApi.php" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns2="http://xml.apache.org/xml-soap" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<ns1:KasApiResponse>
<return xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">Request</key>
<value xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">KasRequestTime</key>
<value xsi:type="xsd:int">1780152282</value>
</item>
<item>
<key xsi:type="xsd:string">KasRequestType</key>
<value xsi:type="xsd:string">get_dns_settings</value>
</item>
<item>
<key xsi:type="xsd:string">KasRequestParams</key>
<value xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">zone_host</key>
<value xsi:type="xsd:string">example.com</value>
</item>
<item>
<key xsi:type="xsd:string">record_id</key>
<value xsi:type="xsd:string">110118416</value>
</item>
</value>
</item>
</value>
</item>
<item>
<key xsi:type="xsd:string">Response</key>
<value xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">KasFloodDelay</key>
<value xsi:type="xsd:float">0.5</value>
</item>
<item>
<key xsi:type="xsd:string">ReturnString</key>
<value xsi:type="xsd:string">TRUE</value>
</item>
<item>
<key xsi:type="xsd:string">ReturnInfo</key>
<value SOAP-ENC:arrayType="ns2:Map[1]" xsi:type="SOAP-ENC:Array">
<item xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">record_zone</key>
<value xsi:type="xsd:string">example.com</value>
</item>
<item>
<key xsi:type="xsd:string">record_name</key>
<value xsi:type="xsd:string">abc012345678901._domainkey</value>
</item>
<item>
<key xsi:type="xsd:string">record_type</key>
<value xsi:type="xsd:string">TXT</value>
</item>
<item>
<key xsi:type="xsd:string">record_data</key>
<value xsi:type="xsd:string">v=DKIM1; k=rsa; p=rsa-key</value>
</item>
<item>
<key xsi:type="xsd:string">record_aux</key>
<value xsi:type="xsd:int">0</value>
</item>
<item>
<key xsi:type="xsd:string">record_id</key>
<value xsi:type="xsd:string">110118416</value>
</item>
<item>
<key xsi:type="xsd:string">record_changeable</key>
<value xsi:type="xsd:string">Y</value>
</item>
<item>
<key xsi:type="xsd:string">record_deleteable</key>
<value xsi:type="xsd:string">N</value>
</item>
</item>
</value>
</item>
</value>
</item>
</return>
</ns1:KasApiResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Loading