forked from TrueCloudLab/lego
scaleway: use official SDK (#1393)
This commit is contained in:
parent
8bf9613fff
commit
6bd3eae58f
9 changed files with 72 additions and 461 deletions
|
@ -1627,12 +1627,10 @@ func displayDNSHelp(name string) error {
|
|||
|
||||
ew.writeln(`Credentials:`)
|
||||
ew.writeln(` - "SCALEWAY_API_TOKEN": API token`)
|
||||
ew.writeln(` - "SCALEWAY_PROJECT_ID": Project to use (optional)`)
|
||||
ew.writeln()
|
||||
|
||||
ew.writeln(`Additional Configuration:`)
|
||||
ew.writeln(` - "SCALEWAY_API_VERSION": API version`)
|
||||
ew.writeln(` - "SCALEWAY_BASE_URL": API endpoint URL`)
|
||||
ew.writeln(` - "SCALEWAY_HTTP_TIMEOUT": API request timeout`)
|
||||
ew.writeln(` - "SCALEWAY_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||
ew.writeln(` - "SCALEWAY_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
||||
ew.writeln(` - "SCALEWAY_TTL": The TTL of the TXT record used for the DNS challenge`)
|
||||
|
|
|
@ -33,6 +33,7 @@ lego --email myemail@example.com --dns scaleway --domains my.example.org run
|
|||
| Environment Variable Name | Description |
|
||||
|-----------------------|-------------|
|
||||
| `SCALEWAY_API_TOKEN` | API token |
|
||||
| `SCALEWAY_PROJECT_ID` | Project to use (optional) |
|
||||
|
||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
||||
More information [here](/lego/dns/#configuration-and-credentials).
|
||||
|
@ -42,9 +43,6 @@ More information [here](/lego/dns/#configuration-and-credentials).
|
|||
|
||||
| Environment Variable Name | Description |
|
||||
|--------------------------------|-------------|
|
||||
| `SCALEWAY_API_VERSION` | API version |
|
||||
| `SCALEWAY_BASE_URL` | API endpoint URL |
|
||||
| `SCALEWAY_HTTP_TIMEOUT` | API request timeout |
|
||||
| `SCALEWAY_POLLING_INTERVAL` | Time between DNS propagation check |
|
||||
| `SCALEWAY_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
|
||||
| `SCALEWAY_TTL` | The TTL of the TXT record used for the DNS challenge |
|
||||
|
@ -57,7 +55,7 @@ More information [here](/lego/dns/#configuration-and-credentials).
|
|||
|
||||
## More information
|
||||
|
||||
- [API documentation](https://developers.scaleway.com/en/products/domain/api/)
|
||||
- [API documentation](https://developers.scaleway.com/en/products/domain/dns/api/)
|
||||
|
||||
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
|
||||
<!-- providers/dns/scaleway/scaleway.toml -->
|
||||
|
|
1
go.mod
1
go.mod
|
@ -41,6 +41,7 @@ require (
|
|||
github.com/pquerna/otp v1.3.0
|
||||
github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2
|
||||
github.com/sacloud/libsacloud v1.36.2
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/transip/gotransip/v6 v6.2.0
|
||||
github.com/urfave/cli v1.22.4
|
||||
|
|
3
go.sum
3
go.sum
|
@ -110,6 +110,7 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
|
|||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4=
|
||||
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
|
||||
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/dnsimple/dnsimple-go v0.63.0 h1:0doY8VW/ckRIMTmOw4E1vwqo+bhtjDzvh1pU2ZteFGA=
|
||||
github.com/dnsimple/dnsimple-go v0.63.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
|
@ -399,6 +400,8 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
|
|||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sacloud/libsacloud v1.36.2 h1:aosI7clbQ9IU0Hj+3rpk3SKJop5nLPpLThnWCivPqjI=
|
||||
github.com/sacloud/libsacloud v1.36.2/go.mod h1:P7YAOVmnIn3DKHqCZcUKYUXmSwGBm3yS7IBEjKVSrjg=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f h1:WSnaD0/cvbKJgSTYbjAPf4RJXVvNNDAwVm+W8wEmnGE=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
|
|
|
@ -1,228 +0,0 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultEndpoint = "https://api.scaleway.com/domain/v2alpha2"
|
||||
uriUpdateRecords = "/dns-zones/%s/records"
|
||||
operationSet = "set"
|
||||
operationDelete = "delete"
|
||||
operationAdd = "add"
|
||||
)
|
||||
|
||||
// APIError represents an error response from the API.
|
||||
type APIError struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func (a APIError) Error() string {
|
||||
return a.Message
|
||||
}
|
||||
|
||||
// Record represents a DNS record.
|
||||
type Record struct {
|
||||
Data string `json:"data,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Priority uint32 `json:"priority,omitempty"`
|
||||
TTL uint32 `json:"ttl,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
}
|
||||
|
||||
// RecordChangeAdd represents a list of add operations.
|
||||
type RecordChangeAdd struct {
|
||||
Records []*Record `json:"records,omitempty"`
|
||||
}
|
||||
|
||||
// RecordChangeSet represents a list of set operations.
|
||||
type RecordChangeSet struct {
|
||||
Data string `json:"data,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
TTL uint32 `json:"ttl,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Records []*Record `json:"records,omitempty"`
|
||||
}
|
||||
|
||||
// RecordChangeDelete represents a list of delete operations.
|
||||
type RecordChangeDelete struct {
|
||||
Data string `json:"data,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateDNSZoneRecordsRequest represents a request to update DNS records on the API.
|
||||
type UpdateDNSZoneRecordsRequest struct {
|
||||
DNSZone string `json:"dns_zone,omitempty"`
|
||||
Changes []interface{} `json:"changes,omitempty"`
|
||||
ReturnAllRecords bool `json:"return_all_records,omitempty"`
|
||||
}
|
||||
|
||||
// ClientOpts represents options to init client.
|
||||
type ClientOpts struct {
|
||||
BaseURL string
|
||||
Token string
|
||||
}
|
||||
|
||||
// Client represents DNS client.
|
||||
type Client struct {
|
||||
baseURL string
|
||||
token string
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// NewClient returns a client instance.
|
||||
func NewClient(opts ClientOpts, httpClient *http.Client) *Client {
|
||||
baseURL := defaultEndpoint
|
||||
if opts.BaseURL != "" {
|
||||
baseURL = opts.BaseURL
|
||||
}
|
||||
|
||||
if httpClient == nil {
|
||||
httpClient = &http.Client{}
|
||||
}
|
||||
|
||||
return &Client{
|
||||
token: opts.Token,
|
||||
baseURL: baseURL,
|
||||
httpClient: httpClient,
|
||||
}
|
||||
}
|
||||
|
||||
// AddRecord adds Record for given zone.
|
||||
func (c *Client) AddRecord(zone string, record Record) error {
|
||||
changes := map[string]RecordChangeAdd{
|
||||
operationAdd: {
|
||||
Records: []*Record{&record},
|
||||
},
|
||||
}
|
||||
|
||||
request := UpdateDNSZoneRecordsRequest{
|
||||
DNSZone: zone,
|
||||
Changes: []interface{}{changes},
|
||||
ReturnAllRecords: false,
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf(uriUpdateRecords, zone)
|
||||
req, err := c.newRequest(http.MethodPatch, uri, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.do(req)
|
||||
}
|
||||
|
||||
// SetRecord sets a unique Record for given zone.
|
||||
func (c *Client) SetRecord(zone string, record Record) error {
|
||||
changes := map[string]RecordChangeSet{
|
||||
operationSet: {
|
||||
Name: record.Name,
|
||||
Type: record.Type,
|
||||
Records: []*Record{&record},
|
||||
},
|
||||
}
|
||||
|
||||
request := UpdateDNSZoneRecordsRequest{
|
||||
DNSZone: zone,
|
||||
Changes: []interface{}{changes},
|
||||
ReturnAllRecords: false,
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf(uriUpdateRecords, zone)
|
||||
req, err := c.newRequest(http.MethodPatch, uri, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.do(req)
|
||||
}
|
||||
|
||||
// DeleteRecord deletes a Record for given zone.
|
||||
func (c *Client) DeleteRecord(zone string, record Record) error {
|
||||
delRecord := map[string]RecordChangeDelete{
|
||||
operationDelete: {
|
||||
Name: record.Name,
|
||||
Type: record.Type,
|
||||
Data: record.Data,
|
||||
},
|
||||
}
|
||||
|
||||
request := UpdateDNSZoneRecordsRequest{
|
||||
DNSZone: zone,
|
||||
Changes: []interface{}{delRecord},
|
||||
ReturnAllRecords: false,
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf(uriUpdateRecords, zone)
|
||||
req, err := c.newRequest(http.MethodPatch, uri, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.do(req)
|
||||
}
|
||||
|
||||
func (c *Client) newRequest(method, uri string, body interface{}) (*http.Request, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
if body != nil {
|
||||
err := json.NewEncoder(buf).Encode(body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode request body with error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, c.baseURL+uri, buf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create new http request with error: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("X-auth-token", c.token)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (c *Client) do(req *http.Request) error {
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("request failed with error: %w", err)
|
||||
}
|
||||
|
||||
err = checkResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return checkResponse(resp)
|
||||
}
|
||||
|
||||
func checkResponse(resp *http.Response) error {
|
||||
if resp.StatusCode >= http.StatusBadRequest || resp.StatusCode < http.StatusOK {
|
||||
if resp.Body == nil {
|
||||
return fmt.Errorf("request failed with status code %d and empty body", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
apiError := APIError{}
|
||||
err = json.Unmarshal(body, &apiError)
|
||||
if err != nil {
|
||||
return fmt.Errorf("request failed with status code %d, response body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
return fmt.Errorf("request failed with status code %d: %w", resp.StatusCode, apiError)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,176 +0,0 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const fakeToken = "test"
|
||||
|
||||
func setupTest() (*Client, *http.ServeMux, func()) {
|
||||
mux := http.NewServeMux()
|
||||
svr := httptest.NewServer(mux)
|
||||
|
||||
opts := ClientOpts{
|
||||
BaseURL: svr.URL,
|
||||
Token: fakeToken,
|
||||
}
|
||||
client := NewClient(opts, nil)
|
||||
|
||||
return client, mux, func() {
|
||||
svr.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_AddRecord(t *testing.T) {
|
||||
client, mux, tearDown := setupTest()
|
||||
defer tearDown()
|
||||
|
||||
mux.HandleFunc("/dns-zones/zone/records", func(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodPatch {
|
||||
http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
auth := req.Header.Get("X-Auth-Token")
|
||||
if auth != fakeToken {
|
||||
http.Error(rw, fmt.Sprintf("invalid token: %s", auth), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
raw, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
expected := `{"dns_zone":"zone","changes":[{"add":{"records":[{"data":"\"value\"","name":"fqdn","ttl":30,"type":"TXT"}]}}]}`
|
||||
assert.Equal(t, expected+"\n", string(raw))
|
||||
})
|
||||
|
||||
record := Record{
|
||||
Type: "TXT",
|
||||
TTL: 30,
|
||||
Name: "fqdn",
|
||||
Data: fmt.Sprintf(`"%s"`, "value"),
|
||||
}
|
||||
|
||||
err := client.AddRecord("zone", record)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestClient_AddRecord_error(t *testing.T) {
|
||||
client, mux, tearDown := setupTest()
|
||||
defer tearDown()
|
||||
|
||||
mux.HandleFunc("/dns-zones/zone/records", func(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodPatch {
|
||||
http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
auth := req.Header.Get("X-Auth-Token")
|
||||
if auth != fakeToken {
|
||||
http.Error(rw, fmt.Sprintf("invalid token: %s", auth), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
err := json.NewEncoder(rw).Encode(APIError{"oops"})
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
record := Record{
|
||||
Type: "TXT",
|
||||
TTL: 30,
|
||||
Name: "fqdn",
|
||||
Data: fmt.Sprintf(`"%s"`, "value"),
|
||||
}
|
||||
|
||||
err := client.AddRecord("zone", record)
|
||||
require.EqualError(t, err, "request failed with status code 404: oops")
|
||||
}
|
||||
|
||||
func TestClient_SetRecord(t *testing.T) {
|
||||
client, mux, tearDown := setupTest()
|
||||
defer tearDown()
|
||||
|
||||
mux.HandleFunc("/dns-zones/zone/records", func(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodPatch {
|
||||
http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
auth := req.Header.Get("X-Auth-Token")
|
||||
if auth != fakeToken {
|
||||
http.Error(rw, fmt.Sprintf("invalid token: %s", auth), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
raw, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
expected := `{"dns_zone":"zone","changes":[{"set":{"name":"fqdn","type":"TXT","records":[{"data":"\"value\"","name":"fqdn","ttl":30,"type":"TXT"}]}}]}`
|
||||
assert.Equal(t, expected+"\n", string(raw))
|
||||
})
|
||||
|
||||
record := Record{
|
||||
Type: "TXT",
|
||||
TTL: 30,
|
||||
Name: "fqdn",
|
||||
Data: fmt.Sprintf(`"%s"`, "value"),
|
||||
}
|
||||
|
||||
err := client.SetRecord("zone", record)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestClient_DeleteRecord(t *testing.T) {
|
||||
client, mux, tearDown := setupTest()
|
||||
defer tearDown()
|
||||
|
||||
mux.HandleFunc("/dns-zones/zone/records", func(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodPatch {
|
||||
http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
auth := req.Header.Get("X-Auth-Token")
|
||||
if auth != fakeToken {
|
||||
http.Error(rw, fmt.Sprintf("invalid token: %s", auth), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
raw, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
expected := `{"dns_zone":"zone","changes":[{"delete":{"data":"\"value\"","name":"fqdn","type":"TXT"}}]}`
|
||||
assert.Equal(t, expected+"\n", string(raw))
|
||||
})
|
||||
|
||||
record := Record{
|
||||
Type: "TXT",
|
||||
TTL: 30,
|
||||
Name: "fqdn",
|
||||
Data: fmt.Sprintf(`"%s"`, "value"),
|
||||
}
|
||||
|
||||
err := client.DeleteRecord("zone", record)
|
||||
require.NoError(t, err)
|
||||
}
|
|
@ -1,22 +1,19 @@
|
|||
// Package scaleway implements a DNS provider for solving the DNS-01 challenge using Scaleway Domains API.
|
||||
// Scaleway Domain API reference: https://developers.scaleway.com/en/products/domain/api/
|
||||
// Token: https://www.scaleway.com/en/docs/generate-an-api-token/
|
||||
package scaleway
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||
"github.com/go-acme/lego/v4/platform/config/env"
|
||||
"github.com/go-acme/lego/v4/providers/dns/scaleway/internal"
|
||||
scwdomain "github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1"
|
||||
"github.com/scaleway/scaleway-sdk-go/scw"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBaseURL = "https://api.scaleway.com"
|
||||
defaultVersion = "v2alpha2"
|
||||
minTTL = 60
|
||||
defaultPollingInterval = 10 * time.Second
|
||||
defaultPropagationTimeout = 120 * time.Second
|
||||
|
@ -26,49 +23,41 @@ const (
|
|||
const (
|
||||
envNamespace = "SCALEWAY_"
|
||||
|
||||
EnvBaseURL = envNamespace + "BASE_URL"
|
||||
EnvAPIToken = envNamespace + "API_TOKEN"
|
||||
EnvAPIVersion = envNamespace + "API_VERSION"
|
||||
EnvAPIToken = envNamespace + "API_TOKEN"
|
||||
EnvProjectID = envNamespace + "PROJECT_ID"
|
||||
|
||||
EnvTTL = envNamespace + "TTL"
|
||||
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
|
||||
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
|
||||
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider.
|
||||
type Config struct {
|
||||
BaseURL string
|
||||
Version string
|
||||
ProjectID string
|
||||
Token string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider.
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
BaseURL: env.GetOrDefaultString(EnvBaseURL, defaultBaseURL),
|
||||
Version: env.GetOrDefaultString(EnvAPIVersion, defaultVersion),
|
||||
TTL: env.GetOrDefaultInt(EnvTTL, minTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, defaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, defaultPollingInterval),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider implements the challenge.Provider interface.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *internal.Client
|
||||
client *scwdomain.API
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Scaleway Domains API.
|
||||
// API token must be passed in the environment variable SCALEWAY_API_TOKEN.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// SCALEWAY_API_TOKEN, SCALEWAY_PROJECT_ID.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get(EnvAPIToken)
|
||||
if err != nil {
|
||||
|
@ -77,6 +66,7 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
|
||||
config := NewDefaultConfig()
|
||||
config.Token = values[EnvAPIToken]
|
||||
config.ProjectID = env.GetOrFile(EnvProjectID)
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
@ -95,12 +85,22 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
config.TTL = minTTL
|
||||
}
|
||||
|
||||
client := internal.NewClient(internal.ClientOpts{
|
||||
BaseURL: fmt.Sprintf("%s/domain/%s", config.BaseURL, config.Version),
|
||||
Token: config.Token,
|
||||
}, config.HTTPClient)
|
||||
configuration := []scw.ClientOption{
|
||||
scw.WithAuth("SCWXXXXXXXXXXXXXXXXX", config.Token),
|
||||
scw.WithUserAgent("Scaleway Lego's provider"),
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config, client: client}, nil
|
||||
if config.ProjectID != "" {
|
||||
configuration = append(configuration, scw.WithDefaultProjectID(config.ProjectID))
|
||||
}
|
||||
|
||||
// Create a Scaleway client
|
||||
clientScw, err := scw.NewClient(configuration...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("scaleway: %w", err)
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config, client: scwdomain.NewAPI(clientScw)}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the Timeout and interval to use when checking for DNS propagation.
|
||||
|
@ -113,17 +113,27 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
|||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
txtRecord := internal.Record{
|
||||
Type: "TXT",
|
||||
TTL: uint32(d.config.TTL),
|
||||
Name: fqdn,
|
||||
Data: fmt.Sprintf(`"%s"`, value),
|
||||
records := []*scwdomain.Record{{
|
||||
Data: fmt.Sprintf(`"%s"`, value),
|
||||
Name: fqdn,
|
||||
TTL: uint32(d.config.TTL),
|
||||
Type: scwdomain.RecordTypeTXT,
|
||||
Comment: scw.StringPtr("used by lego"),
|
||||
}}
|
||||
|
||||
req := &scwdomain.UpdateDNSZoneRecordsRequest{
|
||||
DNSZone: domain,
|
||||
Changes: []*scwdomain.RecordChange{{
|
||||
Add: &scwdomain.RecordChangeAdd{Records: records},
|
||||
}},
|
||||
ReturnAllRecords: scw.BoolPtr(false),
|
||||
}
|
||||
|
||||
err := d.client.AddRecord(domain, txtRecord)
|
||||
_, err := d.client.UpdateDNSZoneRecords(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("scaleway: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -131,16 +141,24 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
txtRecord := internal.Record{
|
||||
Type: "TXT",
|
||||
TTL: uint32(d.config.TTL),
|
||||
recordIdentifier := &scwdomain.RecordIdentifier{
|
||||
Name: fqdn,
|
||||
Data: fmt.Sprintf(`"%s"`, value),
|
||||
Type: scwdomain.RecordTypeTXT,
|
||||
Data: scw.StringPtr(fmt.Sprintf(`"%s"`, value)),
|
||||
}
|
||||
|
||||
err := d.client.DeleteRecord(domain, txtRecord)
|
||||
req := &scwdomain.UpdateDNSZoneRecordsRequest{
|
||||
DNSZone: domain,
|
||||
Changes: []*scwdomain.RecordChange{{
|
||||
Delete: &scwdomain.RecordChangeDelete{IDFields: recordIdentifier},
|
||||
}},
|
||||
ReturnAllRecords: scw.BoolPtr(false),
|
||||
}
|
||||
|
||||
_, err := d.client.UpdateDNSZoneRecords(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("scaleway: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -12,13 +12,11 @@ lego --email myemail@example.com --dns scaleway --domains my.example.org run
|
|||
[Configuration]
|
||||
[Configuration.Credentials]
|
||||
SCALEWAY_API_TOKEN = "API token"
|
||||
SCALEWAY_PROJECT_ID = "Project to use (optional)"
|
||||
[Configuration.Additional]
|
||||
SCALEWAY_BASE_URL = "API endpoint URL"
|
||||
SCALEWAY_API_VERSION = "API version"
|
||||
SCALEWAY_POLLING_INTERVAL = "Time between DNS propagation check"
|
||||
SCALEWAY_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
|
||||
SCALEWAY_TTL = "The TTL of the TXT record used for the DNS challenge"
|
||||
SCALEWAY_HTTP_TIMEOUT = "API request timeout"
|
||||
|
||||
[Links]
|
||||
API = "https://developers.scaleway.com/en/products/domain/api/"
|
||||
API = "https://developers.scaleway.com/en/products/domain/dns/api/"
|
||||
|
|
|
@ -10,11 +10,12 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const cleanUpDelay = 2 * time.Second
|
||||
const envDomain = envNamespace + "DOMAIN"
|
||||
|
||||
var envTest = tester.NewEnvTest(EnvAPIToken, EnvProjectID).
|
||||
WithDomain(envDomain)
|
||||
|
||||
func TestNewDNSProvider(t *testing.T) {
|
||||
envTest := tester.NewEnvTest(EnvAPIToken, EnvTTL)
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
envVars map[string]string
|
||||
|
@ -23,13 +24,15 @@ func TestNewDNSProvider(t *testing.T) {
|
|||
{
|
||||
desc: "success",
|
||||
envVars: map[string]string{
|
||||
EnvAPIToken: "123",
|
||||
EnvAPIToken: "00000000-0000-0000-0000-000000000000",
|
||||
EnvProjectID: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "missing api key",
|
||||
envVars: map[string]string{
|
||||
EnvAPIToken: "",
|
||||
EnvAPIToken: "",
|
||||
EnvProjectID: "",
|
||||
},
|
||||
expected: fmt.Sprintf("scaleway: some credentials information are missing: %s", EnvAPIToken),
|
||||
},
|
||||
|
@ -65,7 +68,7 @@ func TestNewDNSProviderConfig(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
desc: "success",
|
||||
token: "123",
|
||||
token: "00000000-0000-0000-0000-000000000000",
|
||||
ttl: minTTL,
|
||||
},
|
||||
{
|
||||
|
@ -97,8 +100,6 @@ func TestNewDNSProviderConfig(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLivePresent(t *testing.T) {
|
||||
envTest := tester.NewEnvTest(EnvAPIToken, EnvTTL)
|
||||
|
||||
if !envTest.IsLiveTest() {
|
||||
t.Skip("skipping live test")
|
||||
}
|
||||
|
@ -112,8 +113,6 @@ func TestLivePresent(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLiveCleanUp(t *testing.T) {
|
||||
envTest := tester.NewEnvTest(EnvAPIToken, EnvTTL)
|
||||
|
||||
if !envTest.IsLiveTest() {
|
||||
t.Skip("skipping live test")
|
||||
}
|
||||
|
@ -122,7 +121,7 @@ func TestLiveCleanUp(t *testing.T) {
|
|||
provider, err := NewDNSProvider()
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(cleanUpDelay)
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
err = provider.CleanUp(envTest.GetDomain(), "", "123d==")
|
||||
require.NoError(t, err)
|
||||
|
|
Loading…
Reference in a new issue