diff --git a/docs/content/dns/zz_gen_transip.md b/docs/content/dns/zz_gen_transip.md index 7cd27919..c34fccfd 100644 --- a/docs/content/dns/zz_gen_transip.md +++ b/docs/content/dns/zz_gen_transip.md @@ -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) diff --git a/go.mod b/go.mod index 25363928..cd910ef4 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index d227a929..1e869741 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/providers/dns/transip/fakeclient_test.go b/providers/dns/transip/fakeclient_test.go new file mode 100644 index 00000000..47132744 --- /dev/null +++ b/providers/dns/transip/fakeclient_test.go @@ -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) +} diff --git a/providers/dns/transip/transip.go b/providers/dns/transip/transip.go index 6de7972c..afa0612b 100644 --- a/providers/dns/transip/transip.go +++ b/providers/dns/transip/transip.go @@ -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. @@ -46,9 +45,8 @@ func NewDefaultConfig() *Config { // DNSProvider describes a provider for TransIP type DNSProvider struct { - config *Config - client gotransip.Client - dnsEntriesMu sync.Mutex + config *Config + 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) + 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 } } - // set the updated DNS entries - err = transipdomain.SetDNSEntries(d.client, domainName, updatedEntries) - if err != nil { - return fmt.Errorf("transip: couldn't get Record ID in CleanUp: %w", err) - } - return nil } diff --git a/providers/dns/transip/transip.toml b/providers/dns/transip/transip.toml index 57bebe9b..2bfc3240 100644 --- a/providers/dns/transip/transip.toml +++ b/providers/dns/transip/transip.toml @@ -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" diff --git a/providers/dns/transip/transip_test.go b/providers/dns/transip/transip_test.go index a4cb6062..dc5367b5 100644 --- a/providers/dns/transip/transip_test.go +++ b/providers/dns/transip/transip_test.go @@ -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, "