forked from TrueCloudLab/lego
liquidweb: detect zone automatically (#2031)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
This commit is contained in:
parent
2140e6befe
commit
8afdc9d01c
8 changed files with 471 additions and 133 deletions
|
@ -1635,9 +1635,8 @@ func displayDNSHelp(w io.Writer, name string) error {
|
|||
ew.writeln()
|
||||
|
||||
ew.writeln(`Credentials:`)
|
||||
ew.writeln(` - "LIQUID_WEB_PASSWORD": Storm API Password`)
|
||||
ew.writeln(` - "LIQUID_WEB_USERNAME": Storm API Username`)
|
||||
ew.writeln(` - "LIQUID_WEB_ZONE": DNS Zone`)
|
||||
ew.writeln(` - "LIQUID_WEB_PASSWORD": Liquid Web API Password`)
|
||||
ew.writeln(` - "LIQUID_WEB_USERNAME": Liquid Web API Username`)
|
||||
ew.writeln()
|
||||
|
||||
ew.writeln(`Additional Configuration:`)
|
||||
|
@ -1645,7 +1644,8 @@ func displayDNSHelp(w io.Writer, name string) error {
|
|||
ew.writeln(` - "LIQUID_WEB_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||
ew.writeln(` - "LIQUID_WEB_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
||||
ew.writeln(` - "LIQUID_WEB_TTL": The TTL of the TXT record used for the DNS challenge`)
|
||||
ew.writeln(` - "LIQUID_WEB_URL": Storm API endpoint`)
|
||||
ew.writeln(` - "LIQUID_WEB_URL": Liquid Web API endpoint`)
|
||||
ew.writeln(` - "LIQUID_WEB_ZONE": DNS Zone`)
|
||||
|
||||
ew.writeln()
|
||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/liquidweb`)
|
||||
|
|
|
@ -28,7 +28,6 @@ Here is an example bash command using the Liquid Web provider:
|
|||
```bash
|
||||
LIQUID_WEB_USERNAME=someuser \
|
||||
LIQUID_WEB_PASSWORD=somepass \
|
||||
LIQUID_WEB_ZONE=tacoman.com.net \
|
||||
lego --email you@example.com --dns liquidweb --domains my.example.org run
|
||||
```
|
||||
|
||||
|
@ -39,9 +38,8 @@ lego --email you@example.com --dns liquidweb --domains my.example.org run
|
|||
|
||||
| Environment Variable Name | Description |
|
||||
|-----------------------|-------------|
|
||||
| `LIQUID_WEB_PASSWORD` | Storm API Password |
|
||||
| `LIQUID_WEB_USERNAME` | Storm API Username |
|
||||
| `LIQUID_WEB_ZONE` | DNS Zone |
|
||||
| `LIQUID_WEB_PASSWORD` | Liquid Web API Password |
|
||||
| `LIQUID_WEB_USERNAME` | Liquid Web API Username |
|
||||
|
||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
||||
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
|
||||
|
@ -55,7 +53,8 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
|
|||
| `LIQUID_WEB_POLLING_INTERVAL` | Time between DNS propagation check |
|
||||
| `LIQUID_WEB_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
|
||||
| `LIQUID_WEB_TTL` | The TTL of the TXT record used for the DNS challenge |
|
||||
| `LIQUID_WEB_URL` | Storm API endpoint |
|
||||
| `LIQUID_WEB_URL` | Liquid Web API endpoint |
|
||||
| `LIQUID_WEB_ZONE` | DNS Zone |
|
||||
|
||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
||||
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
|
||||
|
@ -65,7 +64,7 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
|
|||
|
||||
## More information
|
||||
|
||||
- [API documentation](https://cart.liquidweb.com/storm/api/docs/v1/)
|
||||
- [API documentation](https://api.liquidweb.com/docs/)
|
||||
- [Go client](https://github.com/liquidweb/liquidweb-go)
|
||||
|
||||
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
|
||||
|
|
3
go.mod
3
go.mod
|
@ -40,7 +40,7 @@ require (
|
|||
github.com/infobloxopen/infoblox-go-client v1.1.1
|
||||
github.com/labbsr0x/bindman-dns-webhook v1.0.2
|
||||
github.com/linode/linodego v1.17.2
|
||||
github.com/liquidweb/liquidweb-go v1.6.3
|
||||
github.com/liquidweb/liquidweb-go v1.6.4
|
||||
github.com/mattn/go-isatty v0.0.19
|
||||
github.com/miekg/dns v1.1.55
|
||||
github.com/mimuret/golang-iij-dpf v0.9.1
|
||||
|
@ -134,7 +134,6 @@ require (
|
|||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/labbsr0x/goh v1.0.1 // indirect
|
||||
github.com/liquidweb/go-lwApi v0.0.5 // indirect
|
||||
github.com/liquidweb/liquidweb-cli v0.6.9 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
|
|
6
go.sum
6
go.sum
|
@ -386,12 +386,10 @@ github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmt
|
|||
github.com/linode/linodego v1.17.2 h1:b32dj4662PGG5P9qVa6nBezccWdqgukndlMIuPGq1CQ=
|
||||
github.com/linode/linodego v1.17.2/go.mod h1:C2iyT3Vg2O2sPxkWka4XAQ5WSUtm5LmTZ3Adw43Ra7Q=
|
||||
github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs=
|
||||
github.com/liquidweb/go-lwApi v0.0.5 h1:CT4cdXzJXmo0bon298kS7NeSk+Gt8/UHpWBBol1NGCA=
|
||||
github.com/liquidweb/go-lwApi v0.0.5/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs=
|
||||
github.com/liquidweb/liquidweb-cli v0.6.9 h1:acbIvdRauiwbxIsOCEMXGwF75aSJDbDiyAWPjVnwoYM=
|
||||
github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ=
|
||||
github.com/liquidweb/liquidweb-go v1.6.3 h1:NVHvcnX3eb3BltiIoA+gLYn15nOpkYkdizOEYGSKrk4=
|
||||
github.com/liquidweb/liquidweb-go v1.6.3/go.mod h1:SuXXp+thr28LnjEw18AYtWwIbWMHSUiajPQs8T9c/Rc=
|
||||
github.com/liquidweb/liquidweb-go v1.6.4 h1:6S0m3hHSpiLqGD7AFSb7lH/W/qr1wx+tKil9fgIbjMc=
|
||||
github.com/liquidweb/liquidweb-go v1.6.4/go.mod h1:B934JPIIcdA+uTq2Nz5PgOtG6CuCaEvQKe/Ge/5GgZ4=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
|
|
|
@ -4,7 +4,9 @@ package liquidweb
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -14,7 +16,7 @@ import (
|
|||
"github.com/liquidweb/liquidweb-go/network"
|
||||
)
|
||||
|
||||
const defaultBaseURL = "https://api.stormondemand.com"
|
||||
const defaultBaseURL = "https://api.liquidweb.com"
|
||||
|
||||
// Environment variables names.
|
||||
const (
|
||||
|
@ -45,15 +47,13 @@ type Config struct {
|
|||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider.
|
||||
func NewDefaultConfig() *Config {
|
||||
config := &Config{
|
||||
return &Config{
|
||||
BaseURL: defaultBaseURL,
|
||||
TTL: env.GetOrDefaultInt(EnvTTL, 300),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 2*time.Second),
|
||||
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 1*time.Minute),
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// DNSProvider implements the challenge.Provider interface.
|
||||
|
@ -66,7 +66,7 @@ type DNSProvider struct {
|
|||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Liquid Web.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get(EnvUsername, EnvPassword, EnvZone)
|
||||
values, err := env.Get(EnvUsername, EnvPassword)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("liquidweb: %w", err)
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
config.BaseURL = env.GetOrFile(EnvURL)
|
||||
config.Username = values[EnvUsername]
|
||||
config.Password = values[EnvPassword]
|
||||
config.Zone = values[EnvZone]
|
||||
config.Zone = env.GetOrDefaultString(EnvZone, "")
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
@ -90,19 +90,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
config.BaseURL = defaultBaseURL
|
||||
}
|
||||
|
||||
if config.Zone == "" {
|
||||
return nil, errors.New("liquidweb: zone is missing")
|
||||
}
|
||||
|
||||
if config.Username == "" {
|
||||
return nil, errors.New("liquidweb: username is missing")
|
||||
}
|
||||
|
||||
if config.Password == "" {
|
||||
return nil, errors.New("liquidweb: password is missing")
|
||||
}
|
||||
|
||||
// Initialize LW client.
|
||||
client, err := lw.NewAPI(config.Username, config.Password, config.BaseURL, int(config.HTTPTimeout.Seconds()))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("liquidweb: could not create Liquid Web API client: %w", err)
|
||||
|
@ -133,6 +120,15 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
TTL: d.config.TTL,
|
||||
}
|
||||
|
||||
if params.Zone == "" {
|
||||
bestZone, err := d.findZone(params.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("liquidweb: %w", err)
|
||||
}
|
||||
|
||||
params.Zone = bestZone
|
||||
}
|
||||
|
||||
dnsEntry, err := d.client.NetworkDNS.Create(params)
|
||||
if err != nil {
|
||||
return fmt.Errorf("liquidweb: could not create TXT record: %w", err)
|
||||
|
@ -167,3 +163,31 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) findZone(domain string) (string, error) {
|
||||
zones, err := d.client.NetworkDNSZone.ListAll()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to retrieve zones for account: %w", err)
|
||||
}
|
||||
|
||||
// filter the zones on the account to only ones that match
|
||||
var zs []network.DNSZone
|
||||
for _, item := range zones.Items {
|
||||
if strings.HasSuffix(domain, item.Name) {
|
||||
zs = append(zs, item)
|
||||
}
|
||||
}
|
||||
|
||||
if len(zs) < 1 {
|
||||
return "", fmt.Errorf("no valid zone in account for certificate '%s'", domain)
|
||||
}
|
||||
|
||||
// powerdns _only_ looks for records on the longest matching subdomain zone aka,
|
||||
// for test.sub.example.com if sub.example.com exists,
|
||||
// it will look there it will not look atexample.com even if it also exists
|
||||
sort.Slice(zs, func(i, j int) bool {
|
||||
return len(zs[i].Name) > len(zs[j].Name)
|
||||
})
|
||||
|
||||
return zs[0].Name, nil
|
||||
}
|
||||
|
|
|
@ -7,22 +7,21 @@ Since = "v3.1.0"
|
|||
Example = '''
|
||||
LIQUID_WEB_USERNAME=someuser \
|
||||
LIQUID_WEB_PASSWORD=somepass \
|
||||
LIQUID_WEB_ZONE=tacoman.com.net \
|
||||
lego --email you@example.com --dns liquidweb --domains my.example.org run
|
||||
'''
|
||||
|
||||
[Configuration]
|
||||
[Configuration.Credentials]
|
||||
LIQUID_WEB_USERNAME = "Storm API Username"
|
||||
LIQUID_WEB_PASSWORD = "Storm API Password"
|
||||
LIQUID_WEB_ZONE = "DNS Zone"
|
||||
LIQUID_WEB_USERNAME = "Liquid Web API Username"
|
||||
LIQUID_WEB_PASSWORD = "Liquid Web API Password"
|
||||
[Configuration.Additional]
|
||||
LIQUID_WEB_URL = "Storm API endpoint"
|
||||
LIQUID_WEB_ZONE = "DNS Zone"
|
||||
LIQUID_WEB_URL = "Liquid Web API endpoint"
|
||||
LIQUID_WEB_TTL = "The TTL of the TXT record used for the DNS challenge"
|
||||
LIQUID_WEB_POLLING_INTERVAL = "Time between DNS propagation check"
|
||||
LIQUID_WEB_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
|
||||
LIQUID_WEB_HTTP_TIMEOUT = "Maximum waiting time for the DNS records to be created (not verified)"
|
||||
|
||||
[Links]
|
||||
API = "https://cart.liquidweb.com/storm/api/docs/v1/"
|
||||
API = "https://api.liquidweb.com/docs/"
|
||||
GoClient = "https://github.com/liquidweb/liquidweb-go"
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
package liquidweb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/platform/tester"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/liquidweb/liquidweb-go/network"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -22,23 +18,20 @@ var envTest = tester.NewEnvTest(
|
|||
EnvZone).
|
||||
WithDomain(envDomain)
|
||||
|
||||
func setupTest(t *testing.T) (*DNSProvider, *http.ServeMux) {
|
||||
func setupTest(t *testing.T, initRecs ...network.DNSRecord) *DNSProvider {
|
||||
t.Helper()
|
||||
|
||||
mux := http.NewServeMux()
|
||||
server := httptest.NewServer(mux)
|
||||
t.Cleanup(server.Close)
|
||||
serverURL := mockAPIServer(t, initRecs)
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Username = "blars"
|
||||
config.Password = "tacoman"
|
||||
config.BaseURL = server.URL
|
||||
config.Zone = "tacoman.com"
|
||||
config.BaseURL = serverURL
|
||||
|
||||
provider, err := NewDNSProviderConfig(config)
|
||||
require.NoError(t, err)
|
||||
|
||||
return provider, mux
|
||||
return provider
|
||||
}
|
||||
|
||||
func TestNewDNSProvider(t *testing.T) {
|
||||
|
@ -48,7 +41,14 @@ func TestNewDNSProvider(t *testing.T) {
|
|||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "success",
|
||||
desc: "minimum-success",
|
||||
envVars: map[string]string{
|
||||
EnvUsername: "blars",
|
||||
EnvPassword: "tacoman",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "set-everything",
|
||||
envVars: map[string]string{
|
||||
EnvURL: "https://storm.com",
|
||||
EnvUsername: "blars",
|
||||
|
@ -59,7 +59,7 @@ func TestNewDNSProvider(t *testing.T) {
|
|||
{
|
||||
desc: "missing credentials",
|
||||
envVars: map[string]string{},
|
||||
expected: "liquidweb: some credentials information are missing: LIQUID_WEB_USERNAME,LIQUID_WEB_PASSWORD,LIQUID_WEB_ZONE",
|
||||
expected: "liquidweb: some credentials information are missing: LIQUID_WEB_USERNAME,LIQUID_WEB_PASSWORD",
|
||||
},
|
||||
{
|
||||
desc: "missing username",
|
||||
|
@ -74,14 +74,8 @@ func TestNewDNSProvider(t *testing.T) {
|
|||
envVars: map[string]string{
|
||||
EnvUsername: "blars",
|
||||
EnvZone: "blars.com",
|
||||
}, expected: "liquidweb: some credentials information are missing: LIQUID_WEB_PASSWORD",
|
||||
},
|
||||
{
|
||||
desc: "missing zone",
|
||||
envVars: map[string]string{
|
||||
EnvUsername: "blars",
|
||||
EnvPassword: "tacoman",
|
||||
}, expected: "liquidweb: some credentials information are missing: LIQUID_WEB_ZONE",
|
||||
expected: "liquidweb: some credentials information are missing: LIQUID_WEB_PASSWORD",
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -126,28 +120,21 @@ func TestNewDNSProviderConfig(t *testing.T) {
|
|||
username: "",
|
||||
password: "",
|
||||
zone: "",
|
||||
expected: "liquidweb: zone is missing",
|
||||
expected: "liquidweb: could not create Liquid Web API client: provided username is empty",
|
||||
},
|
||||
{
|
||||
desc: "missing username",
|
||||
username: "",
|
||||
password: "secret",
|
||||
zone: "example.com",
|
||||
expected: "liquidweb: username is missing",
|
||||
expected: "liquidweb: could not create Liquid Web API client: provided username is empty",
|
||||
},
|
||||
{
|
||||
desc: "missing password",
|
||||
username: "acme",
|
||||
password: "",
|
||||
zone: "example.com",
|
||||
expected: "liquidweb: password is missing",
|
||||
},
|
||||
{
|
||||
desc: "missing zone",
|
||||
username: "acme",
|
||||
password: "secret",
|
||||
zone: "",
|
||||
expected: "liquidweb: zone is missing",
|
||||
expected: "liquidweb: could not create Liquid Web API client: provided password is empty",
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -174,75 +161,102 @@ func TestNewDNSProviderConfig(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestDNSProvider_Present(t *testing.T) {
|
||||
provider, mux := setupTest(t)
|
||||
|
||||
mux.HandleFunc("/v1/Network/DNS/Record/create", func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, http.MethodPost, r.Method)
|
||||
|
||||
username, password, ok := r.BasicAuth()
|
||||
assert.Equal(t, "blars", username)
|
||||
assert.Equal(t, "tacoman", password)
|
||||
assert.True(t, ok)
|
||||
|
||||
reqBody, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
expectedReqBody := `
|
||||
{
|
||||
"params": {
|
||||
"name": "_acme-challenge.tacoman.com",
|
||||
"rdata": "\"47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU\"",
|
||||
"ttl": 300,
|
||||
"type": "TXT",
|
||||
"zone": "tacoman.com"
|
||||
}
|
||||
}`
|
||||
assert.JSONEq(t, expectedReqBody, string(reqBody))
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err = fmt.Fprintf(w, `{
|
||||
"type": "TXT",
|
||||
"name": "_acme-challenge.tacoman.com",
|
||||
"rdata": "\"47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU\"",
|
||||
"ttl": 300,
|
||||
"id": 1234567,
|
||||
"prio": null
|
||||
}`)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
})
|
||||
provider := setupTest(t)
|
||||
|
||||
err := provider.Present("tacoman.com", "", "")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestDNSProvider_CleanUp(t *testing.T) {
|
||||
provider, mux := setupTest(t)
|
||||
|
||||
mux.HandleFunc("/v1/Network/DNS/Record/delete", func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, http.MethodPost, r.Method)
|
||||
|
||||
username, password, ok := r.BasicAuth()
|
||||
assert.Equal(t, "blars", username)
|
||||
assert.Equal(t, "tacoman", password)
|
||||
assert.True(t, ok)
|
||||
|
||||
_, err := fmt.Fprintf(w, `{"deleted": "123"}`)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
provider := setupTest(t, network.DNSRecord{
|
||||
Name: "_acme-challenge.tacoman.com",
|
||||
RData: "123d==",
|
||||
Type: "TXT",
|
||||
TTL: 300,
|
||||
ID: 1234567,
|
||||
ZoneID: 42,
|
||||
})
|
||||
|
||||
provider.recordIDs["123"] = 1234567
|
||||
provider.recordIDs["123d=="] = 1234567
|
||||
|
||||
err := provider.CleanUp("tacoman.com.", "123", "")
|
||||
require.NoError(t, err, "fail to remove TXT record")
|
||||
err := provider.CleanUp("tacoman.com.", "123d==", "")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestDNSProvider(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
initRecs []network.DNSRecord
|
||||
domain string
|
||||
token string
|
||||
keyAuth string
|
||||
present bool
|
||||
expPresentErr string
|
||||
cleanup bool
|
||||
}{
|
||||
{
|
||||
desc: "expected successful",
|
||||
domain: "tacoman.com",
|
||||
token: "123",
|
||||
keyAuth: "456",
|
||||
present: true,
|
||||
cleanup: true,
|
||||
},
|
||||
{
|
||||
desc: "other successful",
|
||||
domain: "banana.com",
|
||||
token: "123",
|
||||
keyAuth: "456",
|
||||
present: true,
|
||||
cleanup: true,
|
||||
},
|
||||
{
|
||||
desc: "zone not on account",
|
||||
domain: "huckleberry.com",
|
||||
token: "123",
|
||||
keyAuth: "456",
|
||||
present: true,
|
||||
expPresentErr: "no valid zone in account for certificate '_acme-challenge.huckleberry.com'",
|
||||
cleanup: false,
|
||||
},
|
||||
{
|
||||
desc: "ssl for domain",
|
||||
domain: "sundae.cherry.com",
|
||||
token: "5847953",
|
||||
keyAuth: "34872934",
|
||||
present: true,
|
||||
cleanup: true,
|
||||
},
|
||||
{
|
||||
desc: "complicated domain",
|
||||
domain: "always.money.stand.banana.com",
|
||||
token: "5847953",
|
||||
keyAuth: "there is always money in the banana stand",
|
||||
present: true,
|
||||
cleanup: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
provider := setupTest(t, test.initRecs...)
|
||||
|
||||
if test.present {
|
||||
err := provider.Present(test.domain, test.token, test.keyAuth)
|
||||
if test.expPresentErr == "" {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.ErrorContains(t, err, test.expPresentErr)
|
||||
}
|
||||
}
|
||||
|
||||
if test.cleanup {
|
||||
err := provider.CleanUp(test.domain, test.token, test.keyAuth)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLivePresent(t *testing.T) {
|
||||
|
|
305
providers/dns/liquidweb/servermock_test.go
Normal file
305
providers/dns/liquidweb/servermock_test.go
Normal file
|
@ -0,0 +1,305 @@
|
|||
package liquidweb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/liquidweb/liquidweb-go/network"
|
||||
"github.com/liquidweb/liquidweb-go/types"
|
||||
)
|
||||
|
||||
func mockAPIServer(t *testing.T, initRecs []network.DNSRecord) string {
|
||||
t.Helper()
|
||||
|
||||
recs := make(map[int]network.DNSRecord)
|
||||
|
||||
for _, rec := range initRecs {
|
||||
recs[int(rec.ID)] = rec
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/v1/Network/DNS/Record/delete", mockAPIDelete(recs))
|
||||
mux.Handle("/v1/Network/DNS/Record/create", mockAPICreate(recs))
|
||||
mux.Handle("/v1/Network/DNS/Zone/list", mockAPIListZones())
|
||||
mux.Handle("/bleed/Network/DNS/Record/delete", mockAPIDelete(recs))
|
||||
mux.Handle("/bleed/Network/DNS/Record/create", mockAPICreate(recs))
|
||||
mux.Handle("/bleed/Network/DNS/Zone/list", mockAPIListZones())
|
||||
|
||||
server := httptest.NewServer(requireBasicAuth(requireJSON(mux)))
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
return server.URL
|
||||
}
|
||||
|
||||
func requireBasicAuth(next http.Handler) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
username, password, ok := r.BasicAuth()
|
||||
if ok && username == "blars" && password == "tacoman" {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
http.Error(w, "invalid auth", http.StatusForbidden)
|
||||
}
|
||||
}
|
||||
|
||||
func requireJSON(next http.Handler) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
_, err := buf.ReadFrom(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "malformed request - json required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
r.Body = io.NopCloser(buf)
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func mockAPICreate(recs map[int]network.DNSRecord) http.HandlerFunc {
|
||||
_, mockAPIServerZones := makeMockZones()
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "invalid request", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
req := struct {
|
||||
Params network.DNSRecord `json:"params"`
|
||||
}{}
|
||||
|
||||
if err = json.Unmarshal(body, &req); err != nil {
|
||||
http.Error(w, makeEncodingError(body), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
req.Params.ID = types.FlexInt(rand.Intn(10000000))
|
||||
req.Params.ZoneID = types.FlexInt(mockAPIServerZones[req.Params.Name])
|
||||
|
||||
if _, exists := recs[int(req.Params.ID)]; exists {
|
||||
http.Error(w, "dns record already exists", http.StatusTeapot)
|
||||
return
|
||||
}
|
||||
recs[int(req.Params.ID)] = req.Params
|
||||
|
||||
resp, err := json.Marshal(req.Params)
|
||||
if err != nil {
|
||||
http.Error(w, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
http.Error(w, string(resp), http.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
func mockAPIDelete(recs map[int]network.DNSRecord) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "invalid request", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
req := struct {
|
||||
Params struct {
|
||||
Name string `json:"name"`
|
||||
ID int `json:"id"`
|
||||
} `json:"params"`
|
||||
}{}
|
||||
|
||||
if err := json.Unmarshal(body, &req); err != nil {
|
||||
http.Error(w, makeEncodingError(body), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Params.ID == 0 {
|
||||
http.Error(w, `{"error":"","error_class":"LW::Exception::Input::Multiple","errors":[{"error":"","error_class":"LW::Exception::Input::Required","field":"id","full_message":"The required field 'id' was missing a value.","position":null}],"field":["id"],"full_message":"The following input errors occurred:\nThe required field 'id' was missing a value.","type":null}`, http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok := recs[req.Params.ID]; !ok {
|
||||
http.Error(w, fmt.Sprintf(`{"error":"","error_class":"LW::Exception::RecordNotFound","field":"network_dns_rr","full_message":"Record 'network_dns_rr: %d' not found","input":"%d","public_message":null}`, req.Params.ID, req.Params.ID), http.StatusOK)
|
||||
return
|
||||
}
|
||||
delete(recs, req.Params.ID)
|
||||
http.Error(w, fmt.Sprintf("{\"deleted\":%d}", req.Params.ID), http.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
func mockAPIListZones() http.HandlerFunc {
|
||||
mockZones, mockAPIServerZones := makeMockZones()
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "invalid request", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
req := struct {
|
||||
Params struct {
|
||||
PageNum int `json:"page_num"`
|
||||
} `json:"params"`
|
||||
}{}
|
||||
|
||||
if err = json.Unmarshal(body, &req); err != nil {
|
||||
http.Error(w, makeEncodingError(body), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
switch {
|
||||
case req.Params.PageNum < 1:
|
||||
req.Params.PageNum = 1
|
||||
case req.Params.PageNum > len(mockZones):
|
||||
req.Params.PageNum = len(mockZones)
|
||||
}
|
||||
resp := mockZones[req.Params.PageNum]
|
||||
resp.ItemTotal = types.FlexInt(len(mockAPIServerZones))
|
||||
resp.PageNum = types.FlexInt(req.Params.PageNum)
|
||||
resp.PageSize = 5
|
||||
resp.PageTotal = types.FlexInt(len(mockZones))
|
||||
|
||||
var respBody []byte
|
||||
if respBody, err = json.Marshal(resp); err == nil {
|
||||
http.Error(w, string(respBody), http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
http.Error(w, "", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func makeEncodingError(buf []byte) string {
|
||||
return fmt.Sprintf(`{"data":"%q","encoding":"JSON","error":"unexpected end of string while parsing JSON string, at character offset 32 (before \"(end of string)\") at /usr/local/lp/libs/perl/LW/Base/Role/Serializer.pm line 16.\n","error_class":"LW::Exception::Deserialize","full_message":"Could not deserialize \"%q\" from JSON: unexpected end of string while parsing JSON string, at character offset 32 (before \"(end of string)\") at /usr/local/lp/libs/perl/LW/Base/Role/Serializer.pm line 16.\n"}⏎`, string(buf), string(buf))
|
||||
}
|
||||
|
||||
func makeMockZones() (map[int]network.DNSZoneList, map[string]int) {
|
||||
mockZones := map[int]network.DNSZoneList{
|
||||
1: {
|
||||
Items: []network.DNSZone{
|
||||
{
|
||||
ID: 1,
|
||||
Name: "blars.com",
|
||||
Active: 1,
|
||||
DelegationStatus: "CORRECT",
|
||||
PrimaryNameserver: "ns.liquidweb.com",
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
Name: "tacoman.com",
|
||||
Active: 1,
|
||||
DelegationStatus: "CORRECT",
|
||||
PrimaryNameserver: "ns.liquidweb.com",
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
Name: "storm.com",
|
||||
Active: 1,
|
||||
DelegationStatus: "CORRECT",
|
||||
PrimaryNameserver: "ns.liquidweb.com",
|
||||
},
|
||||
{
|
||||
ID: 4,
|
||||
Name: "not-apple.com",
|
||||
Active: 1,
|
||||
DelegationStatus: "BAD_NAMESERVERS",
|
||||
PrimaryNameserver: "ns.liquidweb.com",
|
||||
},
|
||||
{
|
||||
ID: 5,
|
||||
Name: "example.com",
|
||||
Active: 1,
|
||||
DelegationStatus: "BAD_NAMESERVERS",
|
||||
PrimaryNameserver: "ns.liquidweb.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
2: {
|
||||
Items: []network.DNSZone{
|
||||
{
|
||||
ID: 6,
|
||||
Name: "banana.com",
|
||||
Active: 1,
|
||||
DelegationStatus: "NXDOMAIN",
|
||||
PrimaryNameserver: "ns.liquidweb.com",
|
||||
},
|
||||
{
|
||||
ID: 7,
|
||||
Name: "cherry.com",
|
||||
Active: 1,
|
||||
DelegationStatus: "SERVFAIL",
|
||||
PrimaryNameserver: "ns.liquidweb.com",
|
||||
},
|
||||
{
|
||||
ID: 8,
|
||||
Name: "dates.com",
|
||||
Active: 1,
|
||||
DelegationStatus: "SERVFAIL",
|
||||
PrimaryNameserver: "ns.liquidweb.com",
|
||||
},
|
||||
{
|
||||
ID: 9,
|
||||
Name: "eggplant.com",
|
||||
Active: 1,
|
||||
DelegationStatus: "SERVFAIL",
|
||||
PrimaryNameserver: "ns.liquidweb.com",
|
||||
},
|
||||
{
|
||||
ID: 10,
|
||||
Name: "fig.com",
|
||||
Active: 1,
|
||||
DelegationStatus: "UNKNOWN",
|
||||
PrimaryNameserver: "ns.liquidweb.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
3: {
|
||||
Items: []network.DNSZone{
|
||||
{
|
||||
ID: 11,
|
||||
Name: "grapes.com",
|
||||
Active: 1,
|
||||
DelegationStatus: "UNKNOWN",
|
||||
PrimaryNameserver: "ns.liquidweb.com",
|
||||
},
|
||||
{
|
||||
ID: 12,
|
||||
Name: "money.banana.com",
|
||||
Active: 1,
|
||||
DelegationStatus: "UNKNOWN",
|
||||
PrimaryNameserver: "ns.liquidweb.com",
|
||||
},
|
||||
{
|
||||
ID: 13,
|
||||
Name: "money.stand.banana.com",
|
||||
Active: 1,
|
||||
DelegationStatus: "UNKNOWN",
|
||||
PrimaryNameserver: "ns.liquidweb.com",
|
||||
},
|
||||
{
|
||||
ID: 14,
|
||||
Name: "stand.banana.com",
|
||||
Active: 1,
|
||||
DelegationStatus: "UNKNOWN",
|
||||
PrimaryNameserver: "ns.liquidweb.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mockAPIServerZones := make(map[string]int)
|
||||
for _, page := range mockZones {
|
||||
for _, zone := range page.Items {
|
||||
mockAPIServerZones[zone.Name] = int(zone.ID)
|
||||
}
|
||||
}
|
||||
return mockZones, mockAPIServerZones
|
||||
}
|
Loading…
Reference in a new issue