diff --git a/provider.go b/provider.go index 973daa9..27109c7 100644 --- a/provider.go +++ b/provider.go @@ -292,6 +292,23 @@ func (p *Provider) DeleteRecords(ctx context.Context, zone string, records []lib return ret, nil } +// ListZones lists all the zones on an account. +func (p *Provider) ListZones(ctx context.Context) ([]libdns.Zone, error) { + // https://desec.readthedocs.io/en/latest/dns/domains.html#listing-domains + desecZones, err := p.listZones(ctx) + if err != nil { + return nil, err + } + + zones := make([]libdns.Zone, len(desecZones)) + for i, zone := range desecZones { + zones[i] = libdns.Zone{ + Name: zone.Name, + } + } + return zones, nil +} + // https://desec.readthedocs.io/en/latest/dns/rrsets.html#rrset-field-reference type rrSet struct { Subname string `json:"subname"` @@ -538,6 +555,25 @@ func (p *Provider) listRRSets(ctx context.Context, zone string) ([]rrSet, error) return out, nil } +// https://desec.readthedocs.io/en/latest/dns/domains.html#domain-field-reference +type domain struct { + Name string `json:"name"` +} + +func (p *Provider) listZones(ctx context.Context) ([]domain, error) { + // https://desec.readthedocs.io/en/latest/dns/domains.html#listing-domains + buf, err := p.httpDo(ctx, "GET", "https://desec.io/api/v1/domains/", nil) + if err != nil { + return nil, err + } + + var out []domain + if err := json.Unmarshal(buf, &out); err != nil { + return nil, fmt.Errorf("decoding json: %v", err) + } + return out, nil +} + func (p *Provider) putRRSets(ctx context.Context, zone string, rrs []rrSet) error { if len(rrs) == 0 { return nil @@ -567,4 +603,5 @@ var ( _ libdns.RecordAppender = (*Provider)(nil) _ libdns.RecordSetter = (*Provider)(nil) _ libdns.RecordDeleter = (*Provider)(nil) + _ libdns.ZoneLister = (*Provider)(nil) ) diff --git a/provider_test.go b/provider_test.go index 822afda..2df47c9 100644 --- a/provider_test.go +++ b/provider_test.go @@ -10,6 +10,7 @@ package desec_test import ( "bytes" + gocmp "cmp" "context" "encoding/json" "flag" @@ -693,3 +694,55 @@ func TestDeleteRecords(t *testing.T) { t.Fatalf("p.GetRecords() unexpected diff [-want +got]: %s", diff) } } + +func TestListZones(t *testing.T) { + if *token == "" || *domain == "" { + t.Skip("skipping integration test; both -token and -domain must be set") + } + + ctx := context.Background() + if deadline, ok := t.Deadline(); ok { + var cancel context.CancelFunc + ctx, cancel = context.WithDeadline(ctx, deadline) + t.Cleanup(cancel) + } + + p := &desec.Provider{ + Token: *token, + } + + testDomains := map[string]bool{ + *domain: true, + "test1-" + *domain: true, + "test2-" + *domain: true, + } + + for testDomain := range testDomains { + if domainExists(ctx, t, testDomain) { + t.Fatalf("domain %q exists, but it should not", testDomain) + } + createDomain(ctx, t, testDomain) + domain := testDomain + t.Cleanup(func() { deleteDomain(ctx, t, domain) }) + } + + got, err := p.ListZones(ctx) + if err != nil { + t.Fatal(err) + } + + var want []libdns.Zone + for domain := range testDomains { + want = append(want, libdns.Zone{Name: domain}) + } + + // We only control a limited number of zones in the test account, there may be preexisting zones + // that are unknown to us. Ignore all unknown zones. + opts := cmp.Options{ + cmpopts.IgnoreSliceElements(func(zone libdns.Zone) bool { return !testDomains[zone.Name] }), + cmpopts.SortSlices(func(a, b libdns.Zone) int { return gocmp.Compare(a.Name, b.Name) }), + } + if diff := cmp.Diff(want, got, opts); diff != "" { + t.Errorf("ListZones() unexpected diff [-want +got]: %s", diff) + } +}