Skip to content

Commit

Permalink
Update: initial checkin
Browse files Browse the repository at this point in the history
  • Loading branch information
till committed Nov 18, 2024
1 parent 8c25a1d commit c388f64
Show file tree
Hide file tree
Showing 11 changed files with 625 additions and 39 deletions.
4 changes: 4 additions & 0 deletions .envrc-dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export AUTODNS_USERNAME=
export AUTODNS_PASSWORD=
export AUTODNS_CONTEXT=
export TEST_ZONE=
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.envrc
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) <TODO: YEAR AND NAME>
Copyright (c) 2024, Till Klampaeckel

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
53 changes: 36 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,45 @@
**DEVELOPER INSTRUCTIONS:**

This repo is a template for developers to use when creating new [libdns](https://github.com/libdns/libdns) provider implementations.
\<autodns\> for [`libdns`](https://github.com/libdns/libdns)
=======================

Be sure to update:
[![Go Reference](https://pkg.go.dev/badge/test.svg)](https://pkg.go.dev/github.com/libdns/autodns)

- The package name
- The Go module name in go.mod
- The latest `libdns/libdns` version in go.mod
- All comments and documentation, including README below and godocs
- License (must be compatible with Apache/MIT)
- All "TODO:"s is in the code
- All methods that currently do nothing
This package implements the [libdns interfaces](https://github.com/libdns/libdns) for \<autodns\>, allowing you to manage DNS records.

Remove this section from the readme before publishing.
Example:

---
```
package main
\<PROVIDER NAME\> for [`libdns`](https://github.com/libdns/libdns)
=======================
import (
"context"
"os"
"log"
"github.com/libdns/autodns"
)
func main() {
provider := autodns.Provider{
Username: os.Getenv("AUTODNS_USERNAME"),
Password: os.Getenv("AUTODNS_PASSWORD"),
}
records, err := provider.GetRecords(context.TODO(), "zone.example.org")
if err != nil {
log.Fatalf("unexpected error: %s", err)
}
[![Go Reference](https://pkg.go.dev/badge/test.svg)](https://pkg.go.dev/github.com/libdns/TODO:PROVIDER_NAME)
fmt.Printf("%#v", records)
}
```

This package implements the [libdns interfaces](https://github.com/libdns/libdns) for \<PROVIDER\>, allowing you to manage DNS records.
As an alternative, configure the provider struct with the following:

TODO: Show how to configure and use. Explain any caveats.
| Field | Description (default) | Required |
|------------|----------------------------|----------|
| Username | username, empty | yes |
| Password | password, empty | yes |
| Endpoint | https://api.autodns.com/v1 | no |
| Context | 4 | no |
| HttpClient | `&http.Client{}` | no |
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module github.com/libdns/template
module github.com/libdns/autodns

go 1.18

require github.com/libdns/libdns v0.2.1
require github.com/libdns/libdns v0.2.2
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
169 changes: 152 additions & 17 deletions provider.go
Original file line number Diff line number Diff line change
@@ -1,47 +1,182 @@
// Package libdnstemplate implements a DNS record management client compatible
// with the libdns interfaces for <PROVIDER NAME>. TODO: This package is a
// template only. Customize all godocs for actual implementation.
package libdnstemplate
package autodns

import (
"context"
"fmt"
"net/http"
"slices"

"github.com/libdns/libdns"
)

// TODO: Providers must not require additional provisioning steps by the callers; it
// should work simply by populating a struct and calling methods on it. If your DNS
// service requires long-lived state or some extra provisioning step, do it implicitly
// when methods are called; sync.Once can help with this, and/or you can use a
// sync.(RW)Mutex in your Provider struct to synchronize implicit provisioning.
const (
autoDNSendpoint string = "https://api.autodns.com/v1"
autoDNScontext string = "4"
)

// Provider facilitates DNS record manipulation with <TODO: PROVIDER NAME>.
// Provider facilitates DNS record manipulation with Autodns.
type Provider struct {
// TODO: put config fields here (with snake_case json
// struct tags on exported fields), for example:
APIToken string `json:"api_token,omitempty"`
Username string `json:"username"`
Password string `json:"password"`
Endpoint string `json:"Endpoint"`
Context string `json:"context"`
Primary string `json:"primary"`
HttpClient *http.Client `json:"-"`
}

// NewWithDefaults is a convenience method to create the provider with sensible defaults.
func NewWithDefaults(username, password string) *Provider {
return &Provider{
Username: username,
Password: password,
Endpoint: autoDNSendpoint,
Context: autoDNScontext,
HttpClient: &http.Client{},
}
}

// GetRecords lists all the records in the zone.
func (p *Provider) GetRecords(ctx context.Context, zone string) ([]libdns.Record, error) {
return nil, fmt.Errorf("TODO: not implemented")
var records []libdns.Record

zoneInfo, err := p.checkZone(ctx, zone)
if err != nil {
return nil, err
}

result, err := p.getZone(ctx, zoneInfo.Origin, zoneInfo.Nameserver, zone)
if err != nil {
return nil, err
}

for _, r := range result.Data[0].Records {
records = append(records, libdns.Record{
Type: r.Type,
Name: r.Name,
Value: r.Value,
})
}

return records, nil
}

// AppendRecords adds records to the zone. It returns the records that were added.
func (p *Provider) AppendRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) {
return nil, fmt.Errorf("TODO: not implemented")
zoneInfo, err := p.checkZone(ctx, zone)
if err != nil {
return nil, err
}

result, err := p.getZone(ctx, zoneInfo.Origin, zoneInfo.Nameserver, zone)
if err != nil {
return nil, err
}

for _, r := range records {
result.Data[0].Records = append(result.Data[0].Records, ZoneRecord{
Name: r.Name,
Value: r.Value,
Type: r.Type,
})
}

if err := p.updateZone(ctx, zoneInfo.Origin, zoneInfo.Nameserver, result.Data[0]); err != nil {
return nil, err
}

return records, nil
}

// SetRecords sets the records in the zone, either by updating existing records or creating new ones.
// It returns the updated records.
func (p *Provider) SetRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) {
return nil, fmt.Errorf("TODO: not implemented")
zoneInfo, err := p.checkZone(ctx, zone)
if err != nil {
return nil, err
}

result, err := p.getZone(ctx, zoneInfo.Origin, zoneInfo.Nameserver, zone)
if err != nil {
return nil, err
}

var set []libdns.Record

for _, r := range records {
// find record
idx := slices.IndexFunc(
result.Data[0].Records,
func(zr ZoneRecord) bool {
return zr.Name == r.Name && zr.Type == r.Type
})
if idx == -1 {
result.Data[0].Records = append(result.Data[0].Records,
ZoneRecord{
Name: r.Name,
Type: r.Type,
Value: r.Value,
},
)

set = append(set, r)
continue
}

// update existing record
result.Data[0].Records[idx] = ZoneRecord{
Name: r.Name,
Type: r.Type,
Value: r.Value,
}

set = append(set, r)
}

if err := p.updateZone(ctx, zoneInfo.Origin, zoneInfo.Nameserver, result.Data[0]); err != nil {
return nil, err
}

return set, nil
}

// DeleteRecords deletes the records from the zone. It returns the records that were deleted.
func (p *Provider) DeleteRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) {
return nil, fmt.Errorf("TODO: not implemented")
zoneInfo, err := p.checkZone(ctx, zone)
if err != nil {
return nil, err
}

result, err := p.getZone(ctx, zoneInfo.Origin, zoneInfo.Nameserver, zone)
if err != nil {
return nil, err
}

var deleted []libdns.Record

for _, r := range records {
// find record
idx := slices.IndexFunc(
result.Data[0].Records,
func(zr ZoneRecord) bool {
return zr.Name == r.Name && zr.Type == r.Type
})
if idx == -1 {
continue
}

// remove
result.Data[0].Records = append(result.Data[0].Records[:idx], result.Data[0].Records[idx+1:]...)
deleted = append(deleted, r)
}

if err := p.updateZone(ctx, zoneInfo.Origin, zoneInfo.Nameserver, result.Data[0]); err != nil {
if _, ok := err.(*AutoDNSError); ok {
return nil, err
}
return nil, fmt.Errorf("DeleteRecords: %v", err)
}

return deleted, nil
}

// Interface guards
Expand Down
33 changes: 33 additions & 0 deletions provider_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package autodns_test

import (
"context"
"os"
"testing"

"github.com/libdns/autodns"
)

func TestProvider(t *testing.T) {
if os.Getenv("AUTODNS_USERNAME") == "" || os.Getenv("AUTODNS_PASSWORD") == "" {
t.Skip()
}

provider := autodns.NewWithDefaults(os.Getenv("AUTODNS_USERNAME"), os.Getenv("AUTODNS_PASSWORD"))

t.Run("GetRecords", func(t *testing.T) {
if os.Getenv("TEST_ZONE") == "" {
t.Skip()
}

records, err := provider.GetRecords(context.TODO(), os.Getenv("TEST_ZONE"))
if err != nil {
t.Fatalf("unexpected error: %s", err)
}

if len(records) == 0 {
t.Fatalf("expected at least one record: %#v", records)
}
})

}
Loading

0 comments on commit c388f64

Please sign in to comment.