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
14 changes: 7 additions & 7 deletions internal/drivers/delegation.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import (
"sort"
"strings"

"github.com/GoCodeAlone/workflow-plugin-hover/internal/hover"
"github.com/GoCodeAlone/workflow-plugin-hover/pkg/hoverclient"
"github.com/GoCodeAlone/workflow/interfaces"
)

// HoverDelegationClient is the subset of *hover.Client that DelegationDriver
// HoverDelegationClient is the subset of *hoverclient.Client that DelegationDriver
// depends on. Injectable for tests.
type HoverDelegationClient interface {
GetDomainDelegation(ctx context.Context, domain string) (*hover.DomainDelegation, error)
GetDomainDelegation(ctx context.Context, domain string) (*hoverclient.DomainDelegation, error)
SetNameservers(ctx context.Context, domain string, ns []string) error
}

Expand All @@ -26,15 +26,15 @@ type HoverDelegationClient interface {
// ProviderID = apex domain name (e.g. "example.com"). One resource = one
// domain. Outputs contain only the desired nameservers as []any
// (structpb-safe). v0.2.0 ships Delete = reset to Hover defaults
// [ns1.hover.com, ns2.hover.com]; restore-from-stash is deferred to
// [ns1.hoverclient.com, ns2.hoverclient.com]; restore-from-stash is deferred to
// v0.3.0 because interfaces.ResourceRef has no state channel.
type DelegationDriver struct {
client HoverDelegationClient
nsResolver func(context.Context, string) ([]string, error)
}

// NewDelegationDriver returns a DelegationDriver bound to a real *hover.Client.
func NewDelegationDriver(c *hover.Client) *DelegationDriver {
// NewDelegationDriver returns a DelegationDriver bound to a real *hoverclient.Client.
func NewDelegationDriver(c *hoverclient.Client) *DelegationDriver {
return &DelegationDriver{client: c, nsResolver: lookupPublicNameservers}
}

Expand Down Expand Up @@ -238,7 +238,7 @@ func (d *DelegationDriver) Update(ctx context.Context, ref interfaces.ResourceRe
// hoverDefaultNameservers is the Delete target for v0.2.0 (per A5).
// ResourceRef has no state channel for previous_nameservers restore;
// that enhancement is v0.3.0 follow-up territory.
var hoverDefaultNameservers = []string{"ns1.hover.com", "ns2.hover.com"}
var hoverDefaultNameservers = []string{"ns1.hoverclient.com", "ns2.hoverclient.com"}

// Delete resets the registrar nameservers to Hover's defaults.
// Operators whose domains had non-default originals must restore
Expand Down
24 changes: 12 additions & 12 deletions internal/drivers/delegation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ import (
"fmt"
"testing"

"github.com/GoCodeAlone/workflow-plugin-hover/internal/hover"
"github.com/GoCodeAlone/workflow-plugin-hover/pkg/hoverclient"
"github.com/GoCodeAlone/workflow/interfaces"
)

type fakeDelegationClient struct {
getResult *hover.DomainDelegation
getResult *hoverclient.DomainDelegation
getErr error
getCalls int
setErr error
lastSetNS []string
}

func (f *fakeDelegationClient) GetDomainDelegation(_ context.Context, _ string) (*hover.DomainDelegation, error) {
func (f *fakeDelegationClient) GetDomainDelegation(_ context.Context, _ string) (*hoverclient.DomainDelegation, error) {
f.getCalls++
return f.getResult, f.getErr
}
Expand Down Expand Up @@ -122,7 +122,7 @@ func TestDelegationDriver_Create_DuplicateNameservers_Rejected(t *testing.T) {

func TestDelegationDriver_Read_HappyPath(t *testing.T) {
fc := &fakeDelegationClient{
getResult: &hover.DomainDelegation{
getResult: &hoverclient.DomainDelegation{
ID: "domain-example.com",
Name: "example.com",
Nameservers: []string{"ns1.do.com", "ns2.do.com"},
Expand Down Expand Up @@ -196,7 +196,7 @@ func TestDelegationDriver_Read_PropagatesError(t *testing.T) {

func TestDelegationDriver_Update_HappyPath(t *testing.T) {
fc := &fakeDelegationClient{
getResult: &hover.DomainDelegation{
getResult: &hoverclient.DomainDelegation{
ID: "domain-example.com", Name: "example.com",
Nameservers: []string{"ns1.do.com", "ns2.do.com"},
},
Expand Down Expand Up @@ -256,8 +256,8 @@ func TestDelegationDriver_Delete_ResetsToHoverDefaults(t *testing.T) {
if err := d.Delete(context.Background(), ref); err != nil {
t.Fatalf("Delete: %v", err)
}
if len(fc.lastSetNS) != 2 || fc.lastSetNS[0] != "ns1.hover.com" || fc.lastSetNS[1] != "ns2.hover.com" {
t.Errorf("Delete set NS = %v, want [ns1.hover.com ns2.hover.com]", fc.lastSetNS)
if len(fc.lastSetNS) != 2 || fc.lastSetNS[0] != "ns1.hoverclient.com" || fc.lastSetNS[1] != "ns2.hoverclient.com" {
t.Errorf("Delete set NS = %v, want [ns1.hoverclient.com ns2.hoverclient.com]", fc.lastSetNS)
}
}

Expand Down Expand Up @@ -380,7 +380,7 @@ func TestDelegationDriver_Diff_DomainChange_NeedsReplace(t *testing.T) {

func TestDelegationDriver_HealthCheck_Healthy(t *testing.T) {
fc := &fakeDelegationClient{
getResult: &hover.DomainDelegation{
getResult: &hoverclient.DomainDelegation{
ID: "domain-example.com", Name: "example.com",
Nameservers: []string{"a.com", "b.com"},
},
Expand Down Expand Up @@ -449,20 +449,20 @@ func TestDelegationDriver_CtxCanceled_AllMethods(t *testing.T) {
}

func TestDelegationDriver_Read_PropagatesErrEmptyNameservers(t *testing.T) {
// Callers using errors.Is(driverErr, hover.ErrEmptyNameservers) to
// Callers using errors.Is(driverErr, hoverclient.ErrEmptyNameservers) to
// distinguish "Hover surfaced 0 nameservers" from other failures need
// the sentinel to survive the driver's error wrap. This test defends
// that contract.
fc := &fakeDelegationClient{
getErr: fmt.Errorf("hover: GetDomainDelegation %q: %w", "example.com", hover.ErrEmptyNameservers),
getErr: fmt.Errorf("hover: GetDomainDelegation %q: %w", "example.com", hoverclient.ErrEmptyNameservers),
}
d := NewDelegationDriverWithClient(fc)
_, err := d.Read(context.Background(), interfaces.ResourceRef{Name: "example.com", ProviderID: "example.com"})
if err == nil {
t.Fatal("expected error")
}
if !errors.Is(err, hover.ErrEmptyNameservers) {
t.Errorf("errors.Is should match hover.ErrEmptyNameservers through driver wrap; got %v", err)
if !errors.Is(err, hoverclient.ErrEmptyNameservers) {
t.Errorf("errors.Is should match hoverclient.ErrEmptyNameservers through driver wrap; got %v", err)
}
}

Expand Down
54 changes: 27 additions & 27 deletions internal/drivers/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//
// Hover exposes NO official API. All endpoints below are derived from
// https://github.com/pjslauta/hover-dyn-dns and browser traffic inspection.
// Endpoint inventory (all relative to https://www.hover.com):
// Endpoint inventory (all relative to https://www.hoverclient.com):
//
// GET /api/domains/<domain>/dns — list records for a zone
// POST /api/dns — create a record (form: domain_id, name, type, content, ttl)
Expand All @@ -20,16 +20,16 @@ import (
"fmt"
"strings"

"github.com/GoCodeAlone/workflow-plugin-hover/internal/hover"
"github.com/GoCodeAlone/workflow-plugin-hover/pkg/hoverclient"
"github.com/GoCodeAlone/workflow/interfaces"
)

// HoverDNSClient is the subset of hover.Client used by DNSDriver (injectable for tests).
// HoverDNSClient is the subset of hoverclient.Client used by DNSDriver (injectable for tests).
type HoverDNSClient interface {
GetDomain(ctx context.Context, domain string) (*hover.Domain, error)
ListRecords(ctx context.Context, domain string) ([]hover.DNSRecord, error)
CreateRecord(ctx context.Context, domainID string, rec hover.DNSRecord) (*hover.DNSRecord, error)
UpdateRecord(ctx context.Context, recordID string, rec hover.DNSRecord) error
GetDomain(ctx context.Context, domain string) (*hoverclient.Domain, error)
ListRecords(ctx context.Context, domain string) ([]hoverclient.DNSRecord, error)
CreateRecord(ctx context.Context, domainID string, rec hoverclient.DNSRecord) (*hoverclient.DNSRecord, error)
UpdateRecord(ctx context.Context, recordID string, rec hoverclient.DNSRecord) error
DeleteRecord(ctx context.Context, recordID string) error
}

Expand All @@ -39,8 +39,8 @@ type DNSDriver struct {
client HoverDNSClient
}

// NewDNSDriver creates a DNSDriver backed by a real hover.Client.
func NewDNSDriver(c *hover.Client) *DNSDriver {
// NewDNSDriver creates a DNSDriver backed by a real hoverclient.Client.
func NewDNSDriver(c *hoverclient.Client) *DNSDriver {
return &DNSDriver{client: c}
}

Expand Down Expand Up @@ -158,7 +158,7 @@ func (d *DNSDriver) Diff(_ context.Context, desired interfaces.ResourceSpec, cur
// slice and matched against desired records by exact Content
// (and TTL when specified) rather than by slice position. Each
// match consumes one candidate to preserve multiset semantics.
currentByKey := make(map[string][]hover.DNSRecord)
currentByKey := make(map[string][]hoverclient.DNSRecord)
for _, r := range currentRecs {
key := recordKey(r.Type, r.Name)
currentByKey[key] = append(currentByKey[key], r)
Expand Down Expand Up @@ -231,7 +231,7 @@ func (d *DNSDriver) readOutput(ctx context.Context, domain, name string) (*inter
return dnsOutput(domain, name, recs), nil
}

func (d *DNSDriver) upsertRecords(ctx context.Context, domain string, desired []hover.DNSRecord) error {
func (d *DNSDriver) upsertRecords(ctx context.Context, domain string, desired []hoverclient.DNSRecord) error {
// An empty desired set means "drop everything" — fall through into
// the prune step rather than short-circuiting. Without this, an
// explicit `records: []` would still leave upstream records intact.
Expand All @@ -245,7 +245,7 @@ func (d *DNSDriver) upsertRecords(ctx context.Context, domain string, desired []
return fmt.Errorf("hover dns resolve domain %q: %w", domain, err)
}

existingByKey := make(map[string][]hover.DNSRecord)
existingByKey := make(map[string][]hoverclient.DNSRecord)
for _, r := range dom.Records {
key := recordKey(r.Type, r.Name)
existingByKey[key] = append(existingByKey[key], r)
Expand All @@ -264,7 +264,7 @@ func (d *DNSDriver) upsertRecords(ctx context.Context, domain string, desired []
// a transient state with two identical records. Diff would still
// converge eventually, but apply would do unnecessary writes and
// could fail under multi-record configs.
deferredUpdates := make([]hover.DNSRecord, 0, len(desired))
deferredUpdates := make([]hoverclient.DNSRecord, 0, len(desired))
for _, dr := range desired {
key := recordKey(dr.Type, dr.Name)
candidates := existingByKey[key]
Expand Down Expand Up @@ -351,15 +351,15 @@ func domainFromConfigIfPresent(config map[string]any) (string, bool, error) {
return s, true, nil
}

// declaredRecords parses config["records"] into a []hover.DNSRecord slice.
// declaredRecords parses config["records"] into a []hoverclient.DNSRecord slice.
//
// `records` is REQUIRED. A missing key errors out — silently coercing
// to an empty slice would let upsertRecords prune every upstream
// record, which is rarely what an operator intends when they forgot
// to set the key. An explicitly empty `records: []` IS allowed (and
// does deliberately prune everything); only the missing-key /
// wrong-type cases are rejected.
func declaredRecords(config map[string]any) ([]hover.DNSRecord, error) {
func declaredRecords(config map[string]any) ([]hoverclient.DNSRecord, error) {
raw, present := config["records"]
if !present {
return nil, fmt.Errorf("hover dns: config.records is required (use an explicit 'records: []' to drop every record)")
Expand All @@ -368,7 +368,7 @@ func declaredRecords(config map[string]any) ([]hover.DNSRecord, error) {
if err != nil {
return nil, fmt.Errorf("hover dns: config.records: %w", err)
}
out := make([]hover.DNSRecord, 0, len(items))
out := make([]hoverclient.DNSRecord, 0, len(items))
for i, m := range items {
rec, err := recordFromMap(i, m)
if err != nil {
Expand Down Expand Up @@ -398,24 +398,24 @@ func toSliceOfMaps(v any) ([]map[string]any, error) {
}
}

func recordFromMap(index int, m map[string]any) (hover.DNSRecord, error) {
func recordFromMap(index int, m map[string]any) (hoverclient.DNSRecord, error) {
typ, err := requiredString(m, "type", index)
if err != nil {
return hover.DNSRecord{}, err
return hoverclient.DNSRecord{}, err
}
name, err := requiredString(m, "name", index)
if err != nil {
return hover.DNSRecord{}, err
return hoverclient.DNSRecord{}, err
}
content, err := requiredString(m, "content", index)
if err != nil {
return hover.DNSRecord{}, err
return hoverclient.DNSRecord{}, err
}
ttl, err := optionalNonNegativeInt(m, "ttl", index)
if err != nil {
return hover.DNSRecord{}, err
return hoverclient.DNSRecord{}, err
}
return hover.DNSRecord{
return hoverclient.DNSRecord{
Type: strings.ToUpper(typ),
Name: name,
Content: content,
Expand Down Expand Up @@ -487,8 +487,8 @@ func recordKey(typ, name string) string {
}

// dnsRecordsFromOutput deserialises the "records" key from a ResourceOutput
// Outputs map back into []hover.DNSRecord for diffing.
func dnsRecordsFromOutput(out *interfaces.ResourceOutput) ([]hover.DNSRecord, error) {
// Outputs map back into []hoverclient.DNSRecord for diffing.
func dnsRecordsFromOutput(out *interfaces.ResourceOutput) ([]hoverclient.DNSRecord, error) {
if out == nil || out.Outputs == nil {
return nil, nil
}
Expand All @@ -500,7 +500,7 @@ func dnsRecordsFromOutput(out *interfaces.ResourceOutput) ([]hover.DNSRecord, er
if err != nil {
return nil, fmt.Errorf("hover dns: outputs.records: %w", err)
}
recs := make([]hover.DNSRecord, 0, len(items))
recs := make([]hoverclient.DNSRecord, 0, len(items))
for _, m := range items {
typ, _ := m["type"].(string)
name, _ := m["name"].(string)
Expand All @@ -510,7 +510,7 @@ func dnsRecordsFromOutput(out *interfaces.ResourceOutput) ([]hover.DNSRecord, er
if typ == "" || name == "" {
continue
}
recs = append(recs, hover.DNSRecord{
recs = append(recs, hoverclient.DNSRecord{
ID: id,
Type: typ,
Name: name,
Expand All @@ -524,7 +524,7 @@ func dnsRecordsFromOutput(out *interfaces.ResourceOutput) ([]hover.DNSRecord, er
// dnsOutput builds the structpb-safe ResourceOutput for a Hover DNS zone.
// All Outputs values are primitive leaves (string/int/bool) — no typed slices.
// Records are encoded as []map[string]any per the structpb-boundary invariant.
func dnsOutput(domain, name string, records []hover.DNSRecord) *interfaces.ResourceOutput {
func dnsOutput(domain, name string, records []hoverclient.DNSRecord) *interfaces.ResourceOutput {
outputs := map[string]any{
"domain": domain,
}
Expand Down
Loading