transip: updated the client to v6 (#1104)

This commit is contained in:
svenwiltink 2020-04-19 14:21:42 +02:00 committed by GitHub
parent b58c9499ca
commit b9f530e352
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 218 additions and 152 deletions

View file

@ -18,9 +18,13 @@ Configuration for [TransIP](https://www.transip.nl/).
- Code: `transip`
{{% notice note %}}
_Please contribute by adding a CLI example._
{{% /notice %}}
Here is an example bash command using the TransIP provider:
```bash
TRANSIP_ACCOUNT_NAME = "Account name" \
TRANSIP_PRIVATE_KEY_PATH = "transip.key" \
lego --dns transip --domains my.domain.com --email my@email.com run
```
@ -52,7 +56,7 @@ More information [here](/lego/dns/#configuration-and-credentials).
## More information
- [API documentation](https://api.transip.nl/docs/transip.nl/package-Transip.html)
- [API documentation](https://api.transip.eu/rest/docs.html)
- [Go client](https://github.com/transip/gotransip)
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->

2
go.mod
View file

@ -38,7 +38,7 @@ require (
github.com/sacloud/libsacloud v1.26.1
github.com/stretchr/testify v1.5.1
github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7
github.com/transip/gotransip v0.0.0-20190812104329-6d8d9179b66f
github.com/transip/gotransip/v6 v6.0.2
github.com/urfave/cli v1.22.1
github.com/vultr/govultr v0.1.4
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073

6
go.sum
View file

@ -313,8 +313,8 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7 h1:CpHxIaZzVy26GqJn8ptRyto8fuoYOd1v0fXm9bG3wQ8=
github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7/go.mod h1:imsgLplxEC/etjIhdr3dNzV3JeT27LbVu5pYWm0JCBY=
github.com/transip/gotransip v0.0.0-20190812104329-6d8d9179b66f h1:clyOmELPZd2LuFEyuo1mP6RXpbAW75PwD+RfDj4kBm0=
github.com/transip/gotransip v0.0.0-20190812104329-6d8d9179b66f/go.mod h1:i0f4R4o2HM0m3DZYQWsj6/MEowD57VzoH0v3d7igeFY=
github.com/transip/gotransip/v6 v6.0.2 h1:rOCMY607PYF+YvMHHtJt7eZRd0mx/uhyz6dsXWPmn+4=
github.com/transip/gotransip/v6 v6.0.2/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g=
github.com/uber-go/atomic v1.3.2 h1:Azu9lPBWRNKzYXSIwRfgRuDuS0YKsK4NFhiQv98gkxo=
github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
@ -583,6 +583,8 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View file

@ -0,0 +1,123 @@
package transip
import (
"encoding/json"
"fmt"
"time"
"github.com/transip/gotransip/v6/domain"
"github.com/transip/gotransip/v6/rest"
)
type dnsEntryWrapper struct {
DNSEntry domain.DNSEntry `json:"dnsEntry"`
}
type dnsEntriesWrapper struct {
DNSEntries []domain.DNSEntry `json:"dnsEntries"`
}
type fakeClient struct {
dnsEntries []domain.DNSEntry
setDNSEntriesLatency time.Duration
getInfoLatency time.Duration
domainName string
}
func (f *fakeClient) Get(request rest.Request, dest interface{}) error {
if f.getInfoLatency != 0 {
time.Sleep(f.getInfoLatency)
}
if request.Endpoint != fmt.Sprintf("/domains/%s/dns", f.domainName) {
return fmt.Errorf("function GET for endpoint %s not implemented", request.Endpoint)
}
entries := dnsEntriesWrapper{DNSEntries: f.dnsEntries}
body, err := json.Marshal(entries)
if err != nil {
return fmt.Errorf("can't encode json: %w", err)
}
err = json.Unmarshal(body, dest)
if err != nil {
return fmt.Errorf("can't decode json: %w", err)
}
return nil
}
func (f *fakeClient) Put(request rest.Request) error {
if f.getInfoLatency != 0 {
time.Sleep(f.getInfoLatency)
}
return fmt.Errorf("function PUT for endpoint %s not implemented", request.Endpoint)
}
func (f *fakeClient) Post(request rest.Request) error {
if f.getInfoLatency != 0 {
time.Sleep(f.getInfoLatency)
}
if request.Endpoint != fmt.Sprintf("/domains/%s/dns", f.domainName) {
return fmt.Errorf("function POST for endpoint %s not implemented", request.Endpoint)
}
body, err := request.GetJSONBody()
if err != nil {
return fmt.Errorf("unable get request body")
}
var entry dnsEntryWrapper
if err := json.Unmarshal(body, &entry); err != nil {
return fmt.Errorf("unable to decode request body")
}
f.dnsEntries = append(f.dnsEntries, entry.DNSEntry)
return nil
}
func (f *fakeClient) Delete(request rest.Request) error {
if f.getInfoLatency != 0 {
time.Sleep(f.getInfoLatency)
}
if request.Endpoint != fmt.Sprintf("/domains/%s/dns", f.domainName) {
return fmt.Errorf("function DELETE for endpoint %s not implemented", request.Endpoint)
}
body, err := request.GetJSONBody()
if err != nil {
return fmt.Errorf("unable get request body")
}
var entry dnsEntryWrapper
if err := json.Unmarshal(body, &entry); err != nil {
return fmt.Errorf("unable to decode request body")
}
cp := make([]domain.DNSEntry, 0)
for _, e := range f.dnsEntries {
if e.Name == entry.DNSEntry.Name {
continue
}
cp = append(cp, e)
}
f.dnsEntries = cp
return nil
}
func (f *fakeClient) Patch(request rest.Request) error {
if f.getInfoLatency != 0 {
time.Sleep(f.getInfoLatency)
}
return fmt.Errorf("function PATCH for endpoint %s not implemented", request.Endpoint)
}

View file

@ -5,13 +5,12 @@ import (
"errors"
"fmt"
"strings"
"sync"
"time"
"github.com/go-acme/lego/v3/challenge/dns01"
"github.com/go-acme/lego/v3/platform/config/env"
"github.com/transip/gotransip"
transipdomain "github.com/transip/gotransip/domain"
"github.com/transip/gotransip/v6"
transipdomain "github.com/transip/gotransip/v6/domain"
)
// Environment variables names.
@ -47,8 +46,7 @@ func NewDefaultConfig() *Config {
// DNSProvider describes a provider for TransIP
type DNSProvider struct {
config *Config
client gotransip.Client
dnsEntriesMu sync.Mutex
repository transipdomain.Repository
}
// NewDNSProvider returns a DNSProvider instance configured for TransIP.
@ -73,7 +71,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
return nil, errors.New("transip: the configuration of the DNS provider is nil")
}
client, err := gotransip.NewSOAPClient(gotransip.ClientConfig{
client, err := gotransip.NewClient(gotransip.ClientConfiguration{
AccountName: config.AccountName,
PrivateKeyPath: config.PrivateKeyPath,
})
@ -81,7 +79,9 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
return nil, fmt.Errorf("transip: %w", err)
}
return &DNSProvider{client: client, config: config}, nil
repo := transipdomain.Repository{Client: client}
return &DNSProvider{repository: repo, config: config}, nil
}
// Timeout returns the timeout and interval to use when checking for DNS propagation.
@ -104,35 +104,24 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
// get the subDomain
subDomain := strings.TrimSuffix(dns01.UnFqdn(fqdn), "."+domainName)
// use mutex to prevent race condition from GetInfo until SetDNSEntries
d.dnsEntriesMu.Lock()
defer d.dnsEntriesMu.Unlock()
// get all DNS entries
info, err := transipdomain.GetInfo(d.client, domainName)
if err != nil {
return fmt.Errorf("transip: error for %s in Present: %w", domain, err)
entry := transipdomain.DNSEntry{
Name: subDomain,
Expire: int(d.config.TTL),
Type: "TXT",
Content: value,
}
// include the new DNS entry
dnsEntries := append(info.DNSEntries, transipdomain.DNSEntry{
Name: subDomain,
TTL: d.config.TTL,
Type: transipdomain.DNSEntryTypeTXT,
Content: value,
})
// set the updated DNS entries
err = transipdomain.SetDNSEntries(d.client, domainName, dnsEntries)
err = d.repository.AddDNSEntry(domainName, entry)
if err != nil {
return fmt.Errorf("transip: %w", err)
}
return nil
}
// CleanUp removes the TXT record matching the specified parameters
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _ := dns01.GetRecord(domain, keyAuth)
fqdn, value := dns01.GetRecord(domain, keyAuth)
authZone, err := dns01.FindZoneByFqdn(fqdn)
if err != nil {
@ -144,29 +133,21 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
// get the subDomain
subDomain := strings.TrimSuffix(dns01.UnFqdn(fqdn), "."+domainName)
// use mutex to prevent race condition from GetInfo until SetDNSEntries
d.dnsEntriesMu.Lock()
defer d.dnsEntriesMu.Unlock()
// get all DNS entries
info, err := transipdomain.GetInfo(d.client, domainName)
dnsEntries, err := d.repository.GetDNSEntries(domainName)
if err != nil {
return fmt.Errorf("transip: error for %s in CleanUp: %w", fqdn, err)
}
// loop through the existing entries and remove the specific record
updatedEntries := info.DNSEntries[:0]
for _, e := range info.DNSEntries {
if e.Name != subDomain {
updatedEntries = append(updatedEntries, e)
}
}
// set the updated DNS entries
err = transipdomain.SetDNSEntries(d.client, domainName, updatedEntries)
if err != nil {
for _, entry := range dnsEntries {
if entry.Name == subDomain && entry.Content == value {
if err = d.repository.RemoveDNSEntry(domainName, entry); err != nil {
return fmt.Errorf("transip: couldn't get Record ID in CleanUp: %w", err)
}
return nil
}
}
return nil
}

View file

@ -4,7 +4,11 @@ URL = "https://www.transip.nl/"
Code = "transip"
Since = "v2.0.0"
Example = ''''''
Example = '''
TRANSIP_ACCOUNT_NAME = "Account name" \
TRANSIP_PRIVATE_KEY_PATH = "transip.key" \
lego --dns transip --domains my.domain.com --email my@email.com run
'''
[Configuration]
[Configuration.Credentials]
@ -16,6 +20,6 @@ Example = ''''''
TRANSIP_TTL = "The TTL of the TXT record used for the DNS challenge"
[Links]
API = "https://api.transip.nl/docs/transip.nl/package-Transip.html"
API = "https://api.transip.eu/rest/docs.html"
GoClient = "https://github.com/transip/gotransip"

View file

@ -1,82 +1,20 @@
package transip
import (
"encoding/xml"
"errors"
"fmt"
"reflect"
"os"
"strings"
"sync"
"testing"
"time"
"github.com/go-acme/lego/v3/log"
"github.com/go-acme/lego/v3/platform/tester"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/transip/gotransip"
"github.com/transip/gotransip/domain"
"github.com/transip/gotransip/v6/domain"
)
type argDNSEntries struct {
Items domain.DNSEntries `xml:"item"`
}
type argDomainName struct {
DomainName string `xml:",chardata"`
}
type fakeClient struct {
dnsEntries []domain.DNSEntry
setDNSEntriesLatency time.Duration
getInfoLatency time.Duration
}
func (f *fakeClient) Call(r gotransip.SoapRequest, b interface{}) error {
switch r.Method {
case "getInfo":
d := b.(*domain.Domain)
cp := f.dnsEntries
if f.getInfoLatency != 0 {
time.Sleep(f.getInfoLatency)
}
d.DNSEntries = cp
log.Printf("getInfo: %+v\n", d.DNSEntries)
return nil
case "setDnsEntries":
var domainName argDomainName
var dnsEntries argDNSEntries
args := readArgs(r)
for _, arg := range args {
if strings.HasPrefix(arg, "<domainName") {
err := xml.Unmarshal([]byte(arg), &domainName)
if err != nil {
panic(err)
}
} else if strings.HasPrefix(arg, "<dnsEntries") {
err := xml.Unmarshal([]byte(arg), &dnsEntries)
if err != nil {
panic(err)
}
}
}
log.Printf("setDnsEntries domainName: %+v\n", domainName)
log.Printf("setDnsEntries dnsEntries: %+v\n", dnsEntries)
if f.setDNSEntriesLatency != 0 {
time.Sleep(f.setDNSEntriesLatency)
}
f.dnsEntries = dnsEntries.Items
return nil
default:
return nil
}
}
const envDomain = envNamespace + "DOMAIN"
var envTest = tester.NewEnvTest(
@ -121,14 +59,6 @@ func TestNewDNSProvider(t *testing.T) {
},
expected: "transip: some credentials information are missing: TRANSIP_PRIVATE_KEY_PATH",
},
{
desc: "could not open private key path",
envVars: map[string]string{
EnvAccountName: "johndoe",
EnvPrivateKeyPath: "./fixtures/non/existent/private.key",
},
expected: "transip: could not open private key: stat ./fixtures/non/existent/private.key: no such file or directory",
},
}
for _, test := range testCases {
@ -144,12 +74,29 @@ func TestNewDNSProvider(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, p)
require.NotNil(t, p.config)
require.NotNil(t, p.client)
require.NotNil(t, p.repository)
} else {
require.EqualError(t, err, test.expected)
}
})
}
// The error message for a file not existing is different on Windows and Linux.
// Therefore we test if the error type is the same.
t.Run("could not open private key path", func(t *testing.T) {
defer envTest.RestoreEnv()
envTest.ClearEnv()
envTest.Apply(map[string]string{
EnvAccountName: "johndoe",
EnvPrivateKeyPath: "./fixtures/non/existent/private.key",
})
_, err := NewDNSProvider()
if !errors.Is(err, os.ErrNotExist) {
t.Fatalf("Expected an os.ErrNotExists error, actual: %v", err)
}
})
}
func TestNewDNSProviderConfig(t *testing.T) {
@ -176,13 +123,7 @@ func TestNewDNSProviderConfig(t *testing.T) {
{
desc: "missing private key path",
accountName: "johndoe",
expected: "transip: PrivateKeyPath or PrivateKeyBody is required",
},
{
desc: "could not open private key path",
accountName: "johndoe",
privateKeyPath: "./fixtures/non/existent/private.key",
expected: "transip: could not open private key: stat ./fixtures/non/existent/private.key: no such file or directory",
expected: "transip: PrivateKeyReader, token or PrivateKeyReader is required",
},
}
@ -198,23 +139,40 @@ func TestNewDNSProviderConfig(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, p)
require.NotNil(t, p.config)
require.NotNil(t, p.client)
require.NotNil(t, p.repository)
} else {
require.EqualError(t, err, test.expected)
}
})
}
// The error message for a file not existing is different on Windows and Linux.
// Therefore we test if the error type is the same.
t.Run("could not open private key path", func(t *testing.T) {
config := NewDefaultConfig()
config.AccountName = "johndoe"
config.PrivateKeyPath = "./fixtures/non/existent/private.key"
_, err := NewDNSProviderConfig(config)
if !errors.Is(err, os.ErrNotExist) {
t.Fatalf("Expected an os.ErrNotExists error, actual: %v", err)
}
})
}
func TestDNSProvider_concurrentGetInfo(t *testing.T) {
func TestDNSProvider_concurrentGetDNSEntries(t *testing.T) {
client := &fakeClient{
getInfoLatency: 50 * time.Millisecond,
setDNSEntriesLatency: 500 * time.Millisecond,
domainName: "lego.wtf",
}
repo := domain.Repository{Client: client}
p := &DNSProvider{
config: NewDefaultConfig(),
client: client,
repository: repo,
}
var wg sync.WaitGroup
@ -222,12 +180,14 @@ func TestDNSProvider_concurrentGetInfo(t *testing.T) {
solve := func(domain1 string, suffix string, timeoutPresent time.Duration, timeoutSolve time.Duration, timeoutCleanup time.Duration) error {
time.Sleep(timeoutPresent)
err := p.Present(domain1, "", "")
if err != nil {
return err
}
time.Sleep(timeoutSolve)
var found bool
for _, entry := range client.dnsEntries {
if strings.HasSuffix(entry.Name, suffix) {
@ -239,6 +199,7 @@ func TestDNSProvider_concurrentGetInfo(t *testing.T) {
}
time.Sleep(timeoutCleanup)
return p.CleanUp(domain1, "", "")
}
@ -259,12 +220,15 @@ func TestDNSProvider_concurrentGetInfo(t *testing.T) {
assert.Empty(t, client.dnsEntries)
}
func TestDNSProvider_concurrentSetDNSEntries(t *testing.T) {
client := &fakeClient{}
func TestDNSProvider_concurrentAddDNSEntry(t *testing.T) {
client := &fakeClient{
domainName: "lego.wtf",
}
repo := domain.Repository{Client: client}
p := &DNSProvider{
config: NewDefaultConfig(),
client: client,
repository: repo,
}
var wg sync.WaitGroup
@ -298,18 +262,6 @@ func TestDNSProvider_concurrentSetDNSEntries(t *testing.T) {
assert.Empty(t, client.dnsEntries)
}
func readArgs(req gotransip.SoapRequest) []string {
v := reflect.ValueOf(req)
f := v.FieldByName("args")
var args []string
for i := 0; i < f.Len(); i++ {
args = append(args, f.Slice(0, f.Len()).Index(i).String())
}
return args
}
func TestLivePresent(t *testing.T) {
if !envTest.IsLiveTest() {
t.Skip("skipping live test")