diff --git a/Gopkg.lock b/Gopkg.lock
index 22b19271..ad376567 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -446,6 +446,18 @@
pruneopts = "NUT"
revision = "37e84520dcf74488f67654f9c775b9752c232dc1"
+[[projects]]
+ branch = "master"
+ digest = "1:3b236e8930d31aeb375fe405c15c2afc581e04bd6cb68da4723e1aa8d2e2da37"
+ name = "github.com/transip/gotransip"
+ packages = [
+ ".",
+ "domain",
+ "util",
+ ]
+ pruneopts = "NUT"
+ revision = "1dc93a7db3567a5ccf865106afac88278ba940cf"
+
[[projects]]
digest = "1:5dba68a1600a235630e208cb7196b24e58fcbb77bb7a6bec08fcd23f081b0a58"
name = "github.com/urfave/cli"
@@ -659,6 +671,8 @@
"github.com/stretchr/testify/suite",
"github.com/timewasted/linode",
"github.com/timewasted/linode/dns",
+ "github.com/transip/gotransip",
+ "github.com/transip/gotransip/domain",
"github.com/urfave/cli",
"golang.org/x/crypto/ocsp",
"golang.org/x/net/context",
diff --git a/Gopkg.toml b/Gopkg.toml
index af2670a8..9c365a4c 100644
--- a/Gopkg.toml
+++ b/Gopkg.toml
@@ -85,6 +85,10 @@
branch = "master"
name = "github.com/sacloud/libsacloud"
+[[constraint]]
+ branch = "master"
+ name = "github.com/transip/gotransip"
+
[[constraint]]
version = "0.11.1"
name = "github.com/exoscale/egoscale"
diff --git a/providers/dns/dns_providers.go b/providers/dns/dns_providers.go
index 1d7672a4..4a50a8a3 100644
--- a/providers/dns/dns_providers.go
+++ b/providers/dns/dns_providers.go
@@ -48,6 +48,7 @@ import (
"github.com/xenolf/lego/providers/dns/sakuracloud"
"github.com/xenolf/lego/providers/dns/selectel"
"github.com/xenolf/lego/providers/dns/stackpath"
+ "github.com/xenolf/lego/providers/dns/transip"
"github.com/xenolf/lego/providers/dns/vegadns"
"github.com/xenolf/lego/providers/dns/vultr"
)
@@ -145,6 +146,8 @@ func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error)
return stackpath.NewDNSProvider()
case "selectel":
return selectel.NewDNSProvider()
+ case "transip":
+ return transip.NewDNSProvider()
case "vegadns":
return vegadns.NewDNSProvider()
case "vultr":
diff --git a/providers/dns/transip/fixtures/private.key b/providers/dns/transip/fixtures/private.key
new file mode 100644
index 00000000..e69de29b
diff --git a/providers/dns/transip/transip.go b/providers/dns/transip/transip.go
new file mode 100644
index 00000000..aeb155f8
--- /dev/null
+++ b/providers/dns/transip/transip.go
@@ -0,0 +1,150 @@
+// Package transip implements a DNS provider for solving the DNS-01 challenge using TransIP.
+package transip
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/transip/gotransip"
+ transipdomain "github.com/transip/gotransip/domain"
+ "github.com/xenolf/lego/acme"
+ "github.com/xenolf/lego/platform/config/env"
+)
+
+// Config is used to configure the creation of the DNSProvider
+type Config struct {
+ AccountName string
+ PrivateKeyPath string
+ PropagationTimeout time.Duration
+ PollingInterval time.Duration
+ TTL int64
+}
+
+// NewDefaultConfig returns a default configuration for the DNSProvider
+func NewDefaultConfig() *Config {
+ return &Config{
+ TTL: int64(env.GetOrDefaultInt("TRANSIP_TTL", 10)),
+ PropagationTimeout: env.GetOrDefaultSecond("TRANSIP_PROPAGATION_TIMEOUT", 10*time.Minute),
+ PollingInterval: env.GetOrDefaultSecond("TRANSIP_POLLING_INTERVAL", 10*time.Second),
+ }
+}
+
+// DNSProvider describes a provider for TransIP
+type DNSProvider struct {
+ config *Config
+ client gotransip.SOAPClient
+}
+
+// NewDNSProvider returns a DNSProvider instance configured for TransIP.
+// Credentials must be passed in the environment variables:
+// TRANSIP_ACCOUNTNAME, TRANSIP_PRIVATEKEYPATH.
+func NewDNSProvider() (*DNSProvider, error) {
+ values, err := env.Get("TRANSIP_ACCOUNT_NAME", "TRANSIP_PRIVATE_KEY_PATH")
+ if err != nil {
+ return nil, fmt.Errorf("transip: %v", err)
+ }
+
+ config := NewDefaultConfig()
+ config.AccountName = values["TRANSIP_ACCOUNT_NAME"]
+ config.PrivateKeyPath = values["TRANSIP_PRIVATE_KEY_PATH"]
+
+ return NewDNSProviderConfig(config)
+}
+
+// NewDNSProviderConfig return a DNSProvider instance configured for TransIP.
+func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
+ if config == nil {
+ return nil, errors.New("transip: the configuration of the DNS provider is nil")
+ }
+
+ client, err := gotransip.NewSOAPClient(gotransip.ClientConfig{
+ AccountName: config.AccountName,
+ PrivateKeyPath: config.PrivateKeyPath,
+ })
+ if err != nil {
+ return nil, fmt.Errorf("transip: %v", err)
+ }
+
+ return &DNSProvider{client: client, config: config}, nil
+}
+
+// Timeout returns the timeout and interval to use when checking for DNS propagation.
+// Adjusting here to cope with spikes in propagation times.
+func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
+ return d.config.PropagationTimeout, d.config.PollingInterval
+}
+
+// Present creates a TXT record to fulfill the dns-01 challenge
+func (d *DNSProvider) Present(domain, token, keyAuth string) error {
+ fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
+
+ authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
+ if err != nil {
+ return err
+ }
+
+ domainName := acme.UnFqdn(authZone)
+
+ // get the subDomain
+ subDomain := strings.TrimSuffix(acme.UnFqdn(fqdn), "."+domainName)
+
+ // get all DNS entries
+ info, err := transipdomain.GetInfo(d.client, domainName)
+ if err != nil {
+ return fmt.Errorf("transip: error for %s in Present: %v", domain, err)
+ }
+
+ // 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)
+ if err != nil {
+ return fmt.Errorf("transip: %v", err)
+ }
+ return nil
+}
+
+// CleanUp removes the TXT record matching the specified parameters
+func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
+ fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
+
+ authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
+ if err != nil {
+ return err
+ }
+
+ domainName := acme.UnFqdn(authZone)
+
+ // get the subDomain
+ subDomain := strings.TrimSuffix(acme.UnFqdn(fqdn), "."+domainName)
+
+ // get all DNS entries
+ info, err := transipdomain.GetInfo(d.client, domainName)
+ if err != nil {
+ return fmt.Errorf("transip: error for %s in CleanUp: %v", 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 {
+ return fmt.Errorf("transip: couldn't get Record ID in CleanUp: %sv", err)
+ }
+
+ return nil
+}
diff --git a/providers/dns/transip/transip_test.go b/providers/dns/transip/transip_test.go
new file mode 100644
index 00000000..35c622ca
--- /dev/null
+++ b/providers/dns/transip/transip_test.go
@@ -0,0 +1,164 @@
+package transip
+
+import (
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/require"
+ "github.com/xenolf/lego/platform/tester"
+)
+
+var envTest = tester.NewEnvTest(
+ "TRANSIP_ACCOUNT_NAME",
+ "TRANSIP_PRIVATE_KEY_PATH").
+ WithDomain("TRANSIP_DOMAIN")
+
+func TestNewDNSProvider(t *testing.T) {
+ testCases := []struct {
+ desc string
+ envVars map[string]string
+ expected string
+ }{
+ {
+ desc: "success",
+ envVars: map[string]string{
+ "TRANSIP_ACCOUNT_NAME": "johndoe",
+ "TRANSIP_PRIVATE_KEY_PATH": "./fixtures/private.key",
+ },
+ },
+ {
+ desc: "missing all credentials",
+ envVars: map[string]string{
+ "TRANSIP_ACCOUNT_NAME": "",
+ "TRANSIP_PRIVATE_KEY_PATH": "",
+ },
+ expected: "transip: some credentials information are missing: TRANSIP_ACCOUNT_NAME,TRANSIP_PRIVATE_KEY_PATH",
+ },
+ {
+ desc: "missing account name",
+ envVars: map[string]string{
+ "TRANSIP_ACCOUNT_NAME": "",
+ "TRANSIP_PRIVATE_KEY_PATH": "./fixtures/private.key",
+ },
+ expected: "transip: some credentials information are missing: TRANSIP_ACCOUNT_NAME",
+ },
+ {
+ desc: "missing private key path",
+ envVars: map[string]string{
+ "TRANSIP_ACCOUNT_NAME": "johndoe",
+ "TRANSIP_PRIVATE_KEY_PATH": "",
+ },
+ expected: "transip: some credentials information are missing: TRANSIP_PRIVATE_KEY_PATH",
+ },
+ {
+ desc: "could not open private key path",
+ envVars: map[string]string{
+ "TRANSIP_ACCOUNT_NAME": "johndoe",
+ "TRANSIP_PRIVATE_KEY_PATH": "./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 {
+ t.Run(test.desc, func(t *testing.T) {
+ defer envTest.RestoreEnv()
+ envTest.ClearEnv()
+
+ envTest.Apply(test.envVars)
+
+ p, err := NewDNSProvider()
+
+ if len(test.expected) == 0 {
+ require.NoError(t, err)
+ require.NotNil(t, p)
+ require.NotNil(t, p.config)
+ require.NotNil(t, p.client)
+ } else {
+ require.EqualError(t, err, test.expected)
+ }
+ })
+ }
+}
+
+func TestNewDNSProviderConfig(t *testing.T) {
+ testCases := []struct {
+ desc string
+ accountName string
+ privateKeyPath string
+ expected string
+ }{
+ {
+ desc: "success",
+ accountName: "johndoe",
+ privateKeyPath: "./fixtures/private.key",
+ },
+ {
+ desc: "missing all credentials",
+ expected: "transip: AccountName is required",
+ },
+ {
+ desc: "missing account name",
+ privateKeyPath: "./fixtures/private.key",
+ expected: "transip: AccountName is required",
+ },
+ {
+ 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",
+ },
+ }
+
+ for _, test := range testCases {
+ t.Run(test.desc, func(t *testing.T) {
+ config := NewDefaultConfig()
+ config.AccountName = test.accountName
+ config.PrivateKeyPath = test.privateKeyPath
+
+ p, err := NewDNSProviderConfig(config)
+
+ if len(test.expected) == 0 {
+ require.NoError(t, err)
+ require.NotNil(t, p)
+ require.NotNil(t, p.config)
+ require.NotNil(t, p.client)
+ } else {
+ require.EqualError(t, err, test.expected)
+ }
+ })
+ }
+}
+
+func TestLivePresent(t *testing.T) {
+ if !envTest.IsLiveTest() {
+ t.Skip("skipping live test")
+ }
+
+ envTest.RestoreEnv()
+ provider, err := NewDNSProvider()
+ require.NoError(t, err)
+
+ err = provider.Present(envTest.GetDomain(), "", "123d==")
+ require.NoError(t, err)
+}
+
+func TestLiveCleanUp(t *testing.T) {
+ if !envTest.IsLiveTest() {
+ t.Skip("skipping live test")
+ }
+
+ envTest.RestoreEnv()
+ provider, err := NewDNSProvider()
+ require.NoError(t, err)
+
+ time.Sleep(1 * time.Second)
+
+ err = provider.CleanUp(envTest.GetDomain(), "", "123d==")
+ require.NoError(t, err)
+}
diff --git a/vendor/github.com/transip/gotransip/LICENSE b/vendor/github.com/transip/gotransip/LICENSE
new file mode 100644
index 00000000..352d193e
--- /dev/null
+++ b/vendor/github.com/transip/gotransip/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 TransIP B.V.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/transip/gotransip/api.go b/vendor/github.com/transip/gotransip/api.go
new file mode 100644
index 00000000..46c68448
--- /dev/null
+++ b/vendor/github.com/transip/gotransip/api.go
@@ -0,0 +1,12 @@
+package gotransip
+
+// CancellationTime represents the possible ways of canceling a contract
+type CancellationTime string
+
+var (
+ // CancellationTimeEnd specifies to cancel the contract when the contract was
+ // due to end anyway
+ CancellationTimeEnd CancellationTime = "end"
+ // CancellationTimeImmediately specifies to cancel the contract immediately
+ CancellationTimeImmediately CancellationTime = "immediately"
+)
diff --git a/vendor/github.com/transip/gotransip/client.go b/vendor/github.com/transip/gotransip/client.go
new file mode 100644
index 00000000..0be9c400
--- /dev/null
+++ b/vendor/github.com/transip/gotransip/client.go
@@ -0,0 +1,119 @@
+package gotransip
+
+import (
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "os"
+)
+
+const (
+ transipAPIHost = "api.transip.nl"
+ transipAPINamespace = "http://www.transip.nl/soap"
+)
+
+// APIMode specifies in which mode the API is used. Currently this is only
+// supports either readonly or readwrite
+type APIMode string
+
+var (
+ // APIModeReadOnly specifies that no changes can be made from API calls
+ APIModeReadOnly APIMode = "readonly"
+ // APIModeReadWrite specifies that changes can be made from API calls
+ APIModeReadWrite APIMode = "readwrite"
+)
+
+// ClientConfig is a tool to easily create a new Client object
+type ClientConfig struct {
+ AccountName string
+ PrivateKeyPath string
+ PrivateKeyBody []byte
+ Mode APIMode
+}
+
+// Client is the interface which all clients should implement
+type Client interface {
+ Call(SoapRequest, interface{}) error // execute request on client
+}
+
+// SOAPClient represents a TransIP API SOAP client, implementing the Client
+// interface
+type SOAPClient struct {
+ soapClient soapClient
+}
+
+// Call performs given SOAP request and fills the response into result
+func (c SOAPClient) Call(req SoapRequest, result interface{}) error {
+ return c.soapClient.call(req, result)
+}
+
+// NewSOAPClient returns a new SOAPClient object for given config
+// ClientConfig's PrivateKeyPath will override potentially given PrivateKeyBody
+func NewSOAPClient(c ClientConfig) (SOAPClient, error) {
+ // check account name
+ if len(c.AccountName) == 0 {
+ return SOAPClient{}, errors.New("AccountName is required")
+ }
+
+ // check if private key was given in any form
+ if len(c.PrivateKeyPath) == 0 && len(c.PrivateKeyBody) == 0 {
+ return SOAPClient{}, errors.New("PrivateKeyPath or PrivateKeyBody is required")
+ }
+
+ // if PrivateKeyPath was set, this will override any given PrivateKeyBody
+ if len(c.PrivateKeyPath) > 0 {
+ // try to open private key and read contents
+ if _, err := os.Stat(c.PrivateKeyPath); err != nil {
+ return SOAPClient{}, fmt.Errorf("could not open private key: %s", err.Error())
+ }
+
+ // read private key so we can pass the body to the soapClient
+ var err error
+ c.PrivateKeyBody, err = ioutil.ReadFile(c.PrivateKeyPath)
+ if err != nil {
+ return SOAPClient{}, err
+ }
+ }
+
+ // default to APIMode read/write
+ if len(c.Mode) == 0 {
+ c.Mode = APIModeReadWrite
+ }
+
+ // create soapClient and pass it to a new Client pointer
+ sc := soapClient{
+ Login: c.AccountName,
+ Mode: c.Mode,
+ PrivateKey: c.PrivateKeyBody,
+ }
+
+ return SOAPClient{
+ soapClient: sc,
+ }, nil
+}
+
+// FakeSOAPClient is a client doing nothing except implementing the gotransip.Client
+// interface
+// you can however set a fixture XML body which Call will try to Unmarshal into
+// result
+// useful for testing
+type FakeSOAPClient struct {
+ fixture []byte // preset this fixture so Call can use it to Unmarshal
+}
+
+// FixtureFromFile reads file and sets content as FakeSOAPClient's fixture
+func (f *FakeSOAPClient) FixtureFromFile(file string) (err error) {
+ // read fixture file
+ f.fixture, err = ioutil.ReadFile(file)
+ if err != nil {
+ err = fmt.Errorf("could not read fixture from file %s: %s", file, err.Error())
+ }
+
+ return
+}
+
+// Call doesn't do anything except fill the XML unmarshalled result
+func (f FakeSOAPClient) Call(req SoapRequest, result interface{}) error {
+ // this fake client just parses given fixture as if it was a SOAP response
+ return parseSoapResponse(f.fixture, req.Padding, 200, result)
+}
diff --git a/vendor/github.com/transip/gotransip/domain/api.go b/vendor/github.com/transip/gotransip/domain/api.go
new file mode 100644
index 00000000..f5f90f8e
--- /dev/null
+++ b/vendor/github.com/transip/gotransip/domain/api.go
@@ -0,0 +1,314 @@
+package domain
+
+import (
+ "github.com/transip/gotransip"
+)
+
+// This file holds all DomainService methods directly ported from TransIP API
+
+// BatchCheckAvailability checks the availability of multiple domains
+func BatchCheckAvailability(c gotransip.Client, domainNames []string) ([]CheckResult, error) {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "batchCheckAvailability",
+ }
+ sr.AddArgument("domainNames", domainNames)
+
+ var v struct {
+ V []CheckResult `xml:"item"`
+ }
+
+ err := c.Call(sr, &v)
+ return v.V, err
+}
+
+// CheckAvailability returns the availability status of a domain.
+func CheckAvailability(c gotransip.Client, domainName string) (Status, error) {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "checkAvailability",
+ }
+ sr.AddArgument("domainName", domainName)
+
+ var v Status
+ err := c.Call(sr, &v)
+ return v, err
+}
+
+// GetWhois returns the whois of a domain name
+func GetWhois(c gotransip.Client, domainName string) (string, error) {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "getWhois",
+ }
+ sr.AddArgument("domainName", domainName)
+
+ var v string
+ err := c.Call(sr, &v)
+ return v, err
+}
+
+// GetDomainNames returns list with domain names or error when this failed
+func GetDomainNames(c gotransip.Client) ([]string, error) {
+ var d = struct {
+ D []string `xml:"item"`
+ }{}
+ err := c.Call(gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "getDomainNames",
+ }, &d)
+
+ return d.D, err
+}
+
+// GetInfo returns Domain for given name or error when this failed
+func GetInfo(c gotransip.Client, domainName string) (Domain, error) {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "getInfo",
+ }
+ sr.AddArgument("domainName", domainName)
+
+ var d Domain
+ err := c.Call(sr, &d)
+
+ return d, err
+}
+
+// BatchGetInfo returns array of Domain for given name or error when this failed
+func BatchGetInfo(c gotransip.Client, domainNames []string) ([]Domain, error) {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "batchGetInfo",
+ }
+ sr.AddArgument("domainNames", domainNames)
+
+ var d = struct {
+ D []Domain `xml:"item"`
+ }{}
+ err := c.Call(sr, &d)
+
+ return d.D, err
+}
+
+// GetAuthCode returns the Auth code for a domainName
+func GetAuthCode(c gotransip.Client, domainName string) (string, error) {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "getAuthCode",
+ }
+ sr.AddArgument("domainName", domainName)
+
+ var v string
+ err := c.Call(sr, &v)
+ return v, err
+}
+
+// GetIsLocked returns the lock status for a domainName
+func GetIsLocked(c gotransip.Client, domainName string) (bool, error) {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "getIsLocked",
+ }
+ sr.AddArgument("domainName", domainName)
+
+ var v bool
+ err := c.Call(sr, &v)
+ return v, err
+}
+
+// Register registers a domain name and will automatically create and sign a proposition for it
+func Register(c gotransip.Client, domain string) error {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "register",
+ }
+ sr.AddArgument("domain", domain)
+
+ return c.Call(sr, nil)
+}
+
+// Cancel cancels a domain name, will automatically create and sign a cancellation document
+func Cancel(c gotransip.Client, domainName string, endTime gotransip.CancellationTime) error {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "cancel",
+ }
+ sr.AddArgument("domainName", domainName)
+ sr.AddArgument("endTime", string(endTime))
+
+ return c.Call(sr, nil)
+}
+
+// TransferWithOwnerChange transfers a domain with changing the owner
+func TransferWithOwnerChange(c gotransip.Client, domain, authCode string) error {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "transferWithOwnerChange",
+ }
+ sr.AddArgument("domain", domain)
+ sr.AddArgument("authCode", authCode)
+
+ return c.Call(sr, nil)
+}
+
+// TransferWithoutOwnerChange transfers a domain without changing the owner
+func TransferWithoutOwnerChange(c gotransip.Client, domain, authCode string) error {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "transferWithoutOwnerChange",
+ }
+ sr.AddArgument("domain", domain)
+ sr.AddArgument("authCode", authCode)
+
+ return c.Call(sr, nil)
+}
+
+// SetNameservers starts a nameserver change for this domain, will replace all
+// existing nameservers with the new nameservers
+func SetNameservers(c gotransip.Client, domainName string, nameservers Nameservers) error {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "setNameservers",
+ }
+ sr.AddArgument("domainName", domainName)
+ sr.AddArgument("nameservers", nameservers)
+
+ return c.Call(sr, nil)
+}
+
+// SetLock locks this domain
+func SetLock(c gotransip.Client, domainName string) error {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "setLock",
+ }
+ sr.AddArgument("domainName", domainName)
+
+ return c.Call(sr, nil)
+}
+
+// UnsetLock unlocks this domain
+func UnsetLock(c gotransip.Client, domainName string) error {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "unsetLock",
+ }
+ sr.AddArgument("domainName", domainName)
+
+ return c.Call(sr, nil)
+}
+
+// SetDNSEntries sets the DnsEntries for this Domain, will replace all existing
+// dns entries with the new entries
+func SetDNSEntries(c gotransip.Client, domainName string, dnsEntries DNSEntries) error {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "setDnsEntries",
+ }
+ sr.AddArgument("domainName", domainName)
+ sr.AddArgument("dnsEntries", dnsEntries)
+
+ return c.Call(sr, nil)
+}
+
+// SetOwner starts an owner change of a domain
+func SetOwner(c gotransip.Client, domainName, registrantWhoisContact WhoisContact) error {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "setOwner",
+ }
+ sr.AddArgument("domainName", domainName)
+ // make sure contact is of type registrant
+ registrantWhoisContact.Type = "registrant"
+ sr.AddArgument("registrantWhoisContact", registrantWhoisContact)
+
+ return c.Call(sr, nil)
+}
+
+// SetContacts starts a contact change of a domain, this will replace all existing contacts
+func SetContacts(c gotransip.Client, domainName, contacts WhoisContacts) error {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "setContacts",
+ }
+ sr.AddArgument("domainName", domainName)
+ sr.AddArgument("contacts", contacts)
+
+ return c.Call(sr, nil)
+}
+
+// GetAllTLDInfos returns slice with TLD objects or error when this failed
+func GetAllTLDInfos(c gotransip.Client) ([]TLD, error) {
+ var d = struct {
+ TLD []TLD `xml:"item"`
+ }{}
+ err := c.Call(gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "getAllTldInfos",
+ }, &d)
+
+ return d.TLD, err
+}
+
+// GetTldInfo returns info about a specific TLD
+func GetTldInfo(c gotransip.Client, tldName string) (TLD, error) {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "getTldInfo",
+ }
+ sr.AddArgument("tldName", tldName)
+
+ var v TLD
+ err := c.Call(sr, &v)
+ return v, err
+}
+
+// GetCurrentDomainAction returns info about the action this domain is currently running
+func GetCurrentDomainAction(c gotransip.Client, domainName string) (ActionResult, error) {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "getCurrentDomainAction",
+ }
+ sr.AddArgument("domainName", domainName)
+
+ var v ActionResult
+ err := c.Call(sr, &v)
+ return v, err
+}
+
+// RetryCurrentDomainActionWithNewData retries a failed domain action with new
+// domain data. The Domain.Name field must contain the name of the Domain. The
+// Nameservers, Contacts, DNSEntries fields contain the new data for this domain.
+func RetryCurrentDomainActionWithNewData(c gotransip.Client, domain Domain) error {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "retryCurrentDomainActionWithNewData",
+ }
+ sr.AddArgument("domain", domain)
+
+ return c.Call(sr, nil)
+}
+
+// RetryTransferWithDifferentAuthCode retries a transfer action with a new authcode
+func RetryTransferWithDifferentAuthCode(c gotransip.Client, domain Domain, newAuthCode string) error {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "retryTransferWithDifferentAuthCode",
+ }
+ sr.AddArgument("domain", domain)
+ sr.AddArgument("newAuthCode", newAuthCode)
+
+ return c.Call(sr, nil)
+}
+
+// CancelDomainAction cancels a failed domain action
+func CancelDomainAction(c gotransip.Client, domain string) error {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "cancelDomainAction",
+ }
+ sr.AddArgument("domain", domain)
+
+ return c.Call(sr, nil)
+}
diff --git a/vendor/github.com/transip/gotransip/domain/domain.go b/vendor/github.com/transip/gotransip/domain/domain.go
new file mode 100644
index 00000000..56d7b719
--- /dev/null
+++ b/vendor/github.com/transip/gotransip/domain/domain.go
@@ -0,0 +1,405 @@
+package domain
+
+import (
+ "fmt"
+ "net"
+
+ "github.com/transip/gotransip"
+ "github.com/transip/gotransip/util"
+)
+
+const (
+ serviceName string = "DomainService"
+)
+
+// Domain represents a Transip_Domain object
+// as described at https://api.transip.nl/docs/transip.nl/class-Transip_Domain.html
+type Domain struct {
+ Name string `xml:"name"`
+ Nameservers []Nameserver `xml:"nameservers>item"`
+ Contacts []WhoisContact `xml:"contacts>item"`
+ DNSEntries []DNSEntry `xml:"dnsEntries>item"`
+ Branding Branding `xml:"branding"`
+ AuthorizationCode string `xml:"authCode"`
+ IsLocked bool `xml:"isLocked"`
+ RegistrationDate util.XMLTime `xml:"registrationDate"`
+ RenewalDate util.XMLTime `xml:"renewalDate"`
+}
+
+// EncodeParams returns Domain parameters ready to be used for constructing a signature
+func (d Domain) EncodeParams(prm gotransip.ParamsContainer) {
+ idx := prm.Len()
+ prm.Add(fmt.Sprintf("%d[name]", idx), d.Name)
+ prm.Add(fmt.Sprintf("%d[authCode]", idx), d.AuthorizationCode)
+ prm.Add(fmt.Sprintf("%d[isLocked]", idx), fmt.Sprintf("%t", d.IsLocked))
+ prm.Add(fmt.Sprintf("%d[registrationDate]", idx), d.RegistrationDate.Format("2006-01-02"))
+ prm.Add(fmt.Sprintf("%d[renewalDate]", idx), d.RenewalDate.Format("2006-01-02"))
+ // nameservers
+ for i, e := range d.Nameservers {
+ var ipv4, ipv6 string
+ if e.IPv4Address != nil {
+ ipv4 = e.IPv4Address.String()
+ }
+ if e.IPv6Address != nil {
+ ipv6 = e.IPv6Address.String()
+ }
+ prm.Add(fmt.Sprintf("%d[nameservers][%d][hostname]", idx, i), e.Hostname)
+ prm.Add(fmt.Sprintf("%d[nameservers][%d][ipv4]", idx, i), ipv4)
+ prm.Add(fmt.Sprintf("%d[nameservers][%d][ipv6]", idx, i), ipv6)
+ }
+ // contacts
+ for i, e := range d.Contacts {
+ prm.Add(fmt.Sprintf("%d[contacts][%d][type]", idx, i), e.Type)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][firstName]", idx, i), e.FirstName)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][middleName]", idx, i), e.MiddleName)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][lastName]", idx, i), e.LastName)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][companyName]", idx, i), e.CompanyName)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][companyKvk]", idx, i), e.CompanyKvk)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][companyType]", idx, i), e.CompanyType)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][street]", idx, i), e.Street)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][number]", idx, i), e.Number)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][postalCode]", idx, i), e.PostalCode)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][city]", idx, i), e.City)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][phoneNumber]", idx, i), e.PhoneNumber)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][faxNumber]", idx, i), e.FaxNumber)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][email]", idx, i), e.Email)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][country]", idx, i), e.Country)
+ }
+ // dnsEntries
+ for i, e := range d.DNSEntries {
+ prm.Add(fmt.Sprintf("%d[dnsEntries][%d][name]", idx, i), e.Name)
+ prm.Add(fmt.Sprintf("%d[dnsEntries][%d][expire]", idx, i), fmt.Sprintf("%d", e.TTL))
+ prm.Add(fmt.Sprintf("%d[dnsEntries][%d][type]", idx, i), string(e.Type))
+ prm.Add(fmt.Sprintf("%d[dnsEntries][%d][content]", idx, i), e.Content)
+ }
+ // branding
+ prm.Add(fmt.Sprintf("%d[branding][companyName]", idx), d.Branding.CompanyName)
+ prm.Add(fmt.Sprintf("%d[branding][supportEmail]", idx), d.Branding.SupportEmail)
+ prm.Add(fmt.Sprintf("%d[branding][companyUrl]", idx), d.Branding.CompanyURL)
+ prm.Add(fmt.Sprintf("%d[branding][termsOfUsageUrl]", idx), d.Branding.TermsOfUsageURL)
+ prm.Add(fmt.Sprintf("%d[branding][bannerLine1]", idx), d.Branding.BannerLine1)
+ prm.Add(fmt.Sprintf("%d[branding][bannerLine2]", idx), d.Branding.BannerLine2)
+ prm.Add(fmt.Sprintf("%d[branding][bannerLine3]", idx), d.Branding.BannerLine3)
+}
+
+// EncodeArgs returns Domain XML body ready to be passed in the SOAP call
+func (d Domain) EncodeArgs(key string) string {
+ output := fmt.Sprintf(`<%s xsi:type="ns1:Domain">
+ %s
+ %s
+ %t
+ %s
+ %s`,
+ key, d.Name, d.AuthorizationCode, d.IsLocked,
+ d.RegistrationDate.Format("2006-01-02"), d.RenewalDate.Format("2006-01-02"),
+ ) + "\n"
+
+ output += Nameservers(d.Nameservers).EncodeArgs("nameservers") + "\n"
+ output += WhoisContacts(d.Contacts).EncodeArgs("contacts") + "\n"
+ output += DNSEntries(d.DNSEntries).EncodeArgs("dnsEntries") + "\n"
+ output += d.Branding.EncodeArgs("branding") + "\n"
+
+ return fmt.Sprintf("%s%s>", output, key)
+}
+
+// Capability represents the possible capabilities a TLD can have
+type Capability string
+
+var (
+ // CapabilityRequiresAuthCode defines this TLD requires an auth code
+ // to be transferred
+ CapabilityRequiresAuthCode Capability = "requiresAuthCode"
+ // CapabilityCanRegister defines this TLD can be registered
+ CapabilityCanRegister Capability = "canRegister"
+ // CapabilityCanTransferWithOwnerChange defines this TLD can be transferred
+ // with change of ownership
+ CapabilityCanTransferWithOwnerChange Capability = "canTransferWithOwnerChange"
+ // CapabilityCanTransferWithoutOwnerChange defines this TLD can be
+ // transferred without change of ownership
+ CapabilityCanTransferWithoutOwnerChange Capability = "canTransferWithoutOwnerChange"
+ // CapabilityCanSetLock defines this TLD allows to be locked
+ CapabilityCanSetLock Capability = "canSetLock"
+ // CapabilityCanSetOwner defines this TLD supports setting an owner
+ CapabilityCanSetOwner Capability = "canSetOwner"
+ // CapabilityCanSetContacts defines this TLD supports setting contacts
+ CapabilityCanSetContacts Capability = "canSetContacts"
+ // CapabilityCanSetNameservers defines this TLD supports setting nameservers
+ CapabilityCanSetNameservers Capability = "canSetNameservers"
+)
+
+// TLD represents a Transip_Tld object as described at
+// https://api.transip.nl/docs/transip.nl/class-Transip_Tld.html
+type TLD struct {
+ Name string `xml:"name"`
+ Price float64 `xml:"price"`
+ RenewalPrice float64 `xml:"renewalPrice"`
+ Capabilities []Capability `xml:"capabilities>item"`
+ RegistrationPeriodLength int64 `xml:"registrationPeriodLength"`
+ CancelTimeFrame int64 `xml:"cancelTimeFrame"`
+}
+
+// Nameserver represents a Transip_Nameserver object as described at
+// https://api.transip.nl/docs/transip.nl/class-Transip_Nameserver.html
+type Nameserver struct {
+ Hostname string `xml:"hostname"`
+ IPv4Address net.IP `xml:"ipv4"`
+ IPv6Address net.IP `xml:"ipv6"`
+}
+
+// Nameservers is just an array of Nameserver
+// basically only here so it can implement paramsEncoder
+type Nameservers []Nameserver
+
+// EncodeParams returns Nameservers parameters ready to be used for constructing a signature
+func (n Nameservers) EncodeParams(prm gotransip.ParamsContainer) {
+ idx := prm.Len()
+ for i, e := range n {
+ var ipv4, ipv6 string
+ if e.IPv4Address != nil {
+ ipv4 = e.IPv4Address.String()
+ }
+ if e.IPv6Address != nil {
+ ipv6 = e.IPv6Address.String()
+ }
+ prm.Add(fmt.Sprintf("%d[%d][hostname]", idx, i), e.Hostname)
+ prm.Add(fmt.Sprintf("%d[%d][ipv4]", idx, i), ipv4)
+ prm.Add(fmt.Sprintf("%d[%d][ipv6]", idx, i), ipv6)
+ }
+}
+
+// EncodeArgs returns Nameservers XML body ready to be passed in the SOAP call
+func (n Nameservers) EncodeArgs(key string) string {
+ output := fmt.Sprintf(`<%s SOAP-ENC:arrayType="ns1:Nameserver[%d]" xsi:type="ns1:ArrayOfNameserver">`, key, len(n)) + "\n"
+ for _, e := range n {
+ var ipv4, ipv6 string
+ if e.IPv4Address != nil {
+ ipv4 = e.IPv4Address.String()
+ }
+ if e.IPv6Address != nil {
+ ipv6 = e.IPv6Address.String()
+ }
+ output += fmt.Sprintf(` -
+ %s
+ %s
+ %s
+
`, e.Hostname, ipv4, ipv6) + "\n"
+ }
+
+ return fmt.Sprintf("%s%s>", output, key)
+}
+
+// DNSEntryType represents the possible types of DNS entries
+type DNSEntryType string
+
+var (
+ // DNSEntryTypeA represents an A-record
+ DNSEntryTypeA DNSEntryType = "A"
+ // DNSEntryTypeAAAA represents an AAAA-record
+ DNSEntryTypeAAAA DNSEntryType = "AAAA"
+ // DNSEntryTypeCNAME represents a CNAME-record
+ DNSEntryTypeCNAME DNSEntryType = "CNAME"
+ // DNSEntryTypeMX represents an MX-record
+ DNSEntryTypeMX DNSEntryType = "MX"
+ // DNSEntryTypeNS represents an NS-record
+ DNSEntryTypeNS DNSEntryType = "NS"
+ // DNSEntryTypeTXT represents a TXT-record
+ DNSEntryTypeTXT DNSEntryType = "TXT"
+ // DNSEntryTypeSRV represents an SRV-record
+ DNSEntryTypeSRV DNSEntryType = "SRV"
+)
+
+// DNSEntry represents a Transip_DnsEntry object as described at
+// https://api.transip.nl/docs/transip.nl/class-Transip_DnsEntry.html
+type DNSEntry struct {
+ Name string `xml:"name"`
+ TTL int64 `xml:"expire"`
+ Type DNSEntryType `xml:"type"`
+ Content string `xml:"content"`
+}
+
+// DNSEntries is just an array of DNSEntry
+// basically only here so it can implement paramsEncoder
+type DNSEntries []DNSEntry
+
+// EncodeParams returns DNSEntries parameters ready to be used for constructing a signature
+func (d DNSEntries) EncodeParams(prm gotransip.ParamsContainer) {
+ idx := prm.Len()
+ for i, e := range d {
+ prm.Add(fmt.Sprintf("%d[%d][name]", idx, i), e.Name)
+ prm.Add(fmt.Sprintf("%d[%d][expire]", idx, i), fmt.Sprintf("%d", e.TTL))
+ prm.Add(fmt.Sprintf("%d[%d][type]", idx, i), string(e.Type))
+ prm.Add(fmt.Sprintf("%d[%d][content]", idx, i), e.Content)
+ }
+}
+
+// EncodeArgs returns DNSEntries XML body ready to be passed in the SOAP call
+func (d DNSEntries) EncodeArgs(key string) string {
+ output := fmt.Sprintf(`<%s SOAP-ENC:arrayType="ns1:DnsEntry[%d]" xsi:type="ns1:ArrayOfDnsEntry">`, key, len(d)) + "\n"
+ for _, e := range d {
+ output += fmt.Sprintf(` -
+ %s
+ %d
+ %s
+ %s
+
`, e.Name, e.TTL, e.Type, e.Content) + "\n"
+ }
+
+ return fmt.Sprintf("%s%s>", output, key)
+}
+
+// Status reflects the current status of a domain in a check result
+type Status string
+
+var (
+ // StatusInYourAccount means he domain name is already in your account
+ StatusInYourAccount Status = "inyouraccount"
+ // StatusUnavailable means the domain name is currently unavailable and can not be registered due to unknown reasons.
+ StatusUnavailable Status = "unavailable"
+ // StatusNotFree means the domain name has already been registered
+ StatusNotFree Status = "notfree"
+ // StatusFree means the domain name is currently free, is available and can be registered
+ StatusFree Status = "free"
+ // StatusInternalPull means the domain name is currently registered at TransIP and is available to be pulled from another account to yours.
+ StatusInternalPull Status = "internalpull"
+ // StatusInternalPush means the domain name is currently registered at TransIP in your accounta and is available to be pushed to another account.
+ StatusInternalPush Status = "internalpush"
+)
+
+// CheckResult represents a Transip_DomainCheckResult object as described at
+// https://api.transip.nl/docs/transip.nl/class-Transip_DomainCheckResult.html
+type CheckResult struct {
+ DomainName string `xml:"domainName"`
+ Status Status `xml:"status"`
+ Actions []Action `xml:"actions>item"`
+}
+
+// Branding represents a Transip_DomainBranding object as described at
+// https://api.transip.nl/docs/transip.nl/class-Transip_DomainBranding.html
+type Branding struct {
+ CompanyName string `xml:"companyName"`
+ SupportEmail string `xml:"supportEmail"`
+ CompanyURL string `xml:"companyUrl"`
+ TermsOfUsageURL string `xml:"termsOfUsageUrl"`
+ BannerLine1 string `xml:"bannerLine1"`
+ BannerLine2 string `xml:"bannerLine2"`
+ BannerLine3 string `xml:"bannerLine3"`
+}
+
+// EncodeParams returns WhoisContacts parameters ready to be used for constructing a signature
+func (b Branding) EncodeParams(prm gotransip.ParamsContainer) {
+ idx := prm.Len()
+ prm.Add(fmt.Sprintf("%d[companyName]", idx), b.CompanyName)
+ prm.Add(fmt.Sprintf("%d[supportEmail]", idx), b.SupportEmail)
+ prm.Add(fmt.Sprintf("%d[companyUrl]", idx), b.CompanyURL)
+ prm.Add(fmt.Sprintf("%d[termsOfUsageUrl]", idx), b.TermsOfUsageURL)
+ prm.Add(fmt.Sprintf("%d[bannerLine1]", idx), b.BannerLine1)
+ prm.Add(fmt.Sprintf("%d[bannerLine2]", idx), b.BannerLine2)
+ prm.Add(fmt.Sprintf("%d[bannerLine3]", idx), b.BannerLine3)
+}
+
+// EncodeArgs returns Branding XML body ready to be passed in the SOAP call
+func (b Branding) EncodeArgs(key string) string {
+ return fmt.Sprintf(`
+ %s
+ %s
+ %s
+ %s
+ %s
+ %s
+ %s
+ `, b.CompanyName, b.SupportEmail, b.CompanyURL, b.TermsOfUsageURL, b.BannerLine1, b.BannerLine2, b.BannerLine3)
+}
+
+// Action reflects the available actions to perform on a domain
+type Action string
+
+var (
+ // ActionRegister registers a domain
+ ActionRegister Action = "register"
+ // ActionTransfer transfers a domain to another provider
+ ActionTransfer Action = "transfer"
+ // ActionInternalPull transfers a domain to another account at TransIP
+ ActionInternalPull Action = "internalpull"
+)
+
+// ActionResult represents a Transip_DomainAction object as described at
+// https://api.transip.nl/docs/transip.nl/class-Transip_DomainAction.html
+type ActionResult struct {
+ Name string `xml:"name"`
+ HasFailed bool `xml:"hasFailed"`
+ Message string `xml:"message"`
+}
+
+// WhoisContact represents a TransIP_WhoisContact object
+// as described at https://api.transip.nl/docs/transip.nl/class-Transip_WhoisContact.html
+type WhoisContact struct {
+ Type string `xml:"type"`
+ FirstName string `xml:"firstName"`
+ MiddleName string `xml:"middleName"`
+ LastName string `xml:"lastName"`
+ CompanyName string `xml:"companyName"`
+ CompanyKvk string `xml:"companyKvk"`
+ CompanyType string `xml:"companyType"`
+ Street string `xml:"street"`
+ Number string `xml:"number"`
+ PostalCode string `xml:"postalCode"`
+ City string `xml:"city"`
+ PhoneNumber string `xml:"phoneNumber"`
+ FaxNumber string `xml:"faxNumber"`
+ Email string `xml:"email"`
+ Country string `xml:"country"`
+}
+
+// WhoisContacts is just an array of WhoisContact
+// basically only here so it can implement paramsEncoder
+type WhoisContacts []WhoisContact
+
+// EncodeParams returns WhoisContacts parameters ready to be used for constructing a signature
+func (w WhoisContacts) EncodeParams(prm gotransip.ParamsContainer) {
+ idx := prm.Len()
+ for i, e := range w {
+ prm.Add(fmt.Sprintf("%d[%d][type]", idx, i), e.Type)
+ prm.Add(fmt.Sprintf("%d[%d][firstName]", idx, i), e.FirstName)
+ prm.Add(fmt.Sprintf("%d[%d][middleName]", idx, i), e.MiddleName)
+ prm.Add(fmt.Sprintf("%d[%d][lastName]", idx, i), e.LastName)
+ prm.Add(fmt.Sprintf("%d[%d][companyName]", idx, i), e.CompanyName)
+ prm.Add(fmt.Sprintf("%d[%d][companyKvk]", idx, i), e.CompanyKvk)
+ prm.Add(fmt.Sprintf("%d[%d][companyType]", idx, i), e.CompanyType)
+ prm.Add(fmt.Sprintf("%d[%d][street]", idx, i), e.Street)
+ prm.Add(fmt.Sprintf("%d[%d][number]", idx, i), e.Number)
+ prm.Add(fmt.Sprintf("%d[%d][postalCode]", idx, i), e.PostalCode)
+ prm.Add(fmt.Sprintf("%d[%d][city]", idx, i), e.City)
+ prm.Add(fmt.Sprintf("%d[%d][phoneNumber]", idx, i), e.PhoneNumber)
+ prm.Add(fmt.Sprintf("%d[%d][faxNumber]", idx, i), e.FaxNumber)
+ prm.Add(fmt.Sprintf("%d[%d][email]", idx, i), e.Email)
+ prm.Add(fmt.Sprintf("%d[%d][country]", idx, i), e.Country)
+ }
+}
+
+// EncodeArgs returns WhoisContacts XML body ready to be passed in the SOAP call
+func (w WhoisContacts) EncodeArgs(key string) string {
+ output := fmt.Sprintf(`<%s SOAP-ENC:arrayType="ns1:WhoisContact[%d]" xsi:type="ns1:ArrayOfWhoisContact">`, key, len(w)) + "\n"
+ for _, e := range w {
+ output += fmt.Sprintf(` -
+ %s
+ %s
+ %s
+ %s
+ %s
+ %s
+ %s
+ %s
+ %s
+ %s
+ %s
+ %s
+ %s
+ %s
+ %s
+
`, e.Type, e.FirstName, e.MiddleName, e.LastName, e.CompanyName,
+ e.CompanyKvk, e.CompanyType, e.Street, e.Number, e.PostalCode, e.City,
+ e.PhoneNumber, e.FaxNumber, e.Email, e.Country) + "\n"
+ }
+
+ return output + fmt.Sprintf("%s>", key)
+}
diff --git a/vendor/github.com/transip/gotransip/sign.go b/vendor/github.com/transip/gotransip/sign.go
new file mode 100644
index 00000000..8b7d360c
--- /dev/null
+++ b/vendor/github.com/transip/gotransip/sign.go
@@ -0,0 +1,49 @@
+package gotransip
+
+import (
+ "crypto"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/sha512"
+ "crypto/x509"
+ "encoding/base64"
+ "encoding/pem"
+ "errors"
+ "fmt"
+ "net/url"
+)
+
+var (
+ asn1Header = []byte{
+ 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03,
+ 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40,
+ }
+)
+
+func signWithKey(params *soapParams, key []byte) (string, error) {
+ // create SHA512 hash of given parameters
+ h := sha512.New()
+ h.Write([]byte(params.Encode()))
+
+ // prefix ASN1 header to SHA512 hash
+ digest := append(asn1Header, h.Sum(nil)...)
+
+ // prepare key struct
+ block, _ := pem.Decode(key)
+ if block == nil {
+ return "", errors.New("could not decode private key")
+ }
+ parsed, err := x509.ParsePKCS8PrivateKey(block.Bytes)
+ if err != nil {
+ return "", fmt.Errorf("could not parse private key: %s", err.Error())
+ }
+
+ pkey := parsed.(*rsa.PrivateKey)
+
+ enc, err := rsa.SignPKCS1v15(rand.Reader, pkey, crypto.Hash(0), digest)
+ if err != nil {
+ return "", fmt.Errorf("could not sign data: %s", err.Error())
+ }
+
+ return url.QueryEscape(base64.StdEncoding.EncodeToString(enc)), nil
+}
diff --git a/vendor/github.com/transip/gotransip/soap.go b/vendor/github.com/transip/gotransip/soap.go
new file mode 100644
index 00000000..b893b7f9
--- /dev/null
+++ b/vendor/github.com/transip/gotransip/soap.go
@@ -0,0 +1,419 @@
+package gotransip
+
+import (
+ "bytes"
+ "crypto/tls"
+ "encoding/xml"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "math/rand"
+ "net/http"
+ "net/url"
+ "regexp"
+ "strings"
+ "time"
+)
+
+const (
+ // format for SOAP envelopes
+ soapEnvelopeFixture string = `
+
+ %s
+`
+)
+
+// getSOAPArgs returns XML representing given name and argument as SOAP body
+func getSOAPArgs(name string, input ...string) []byte {
+ var buf bytes.Buffer
+
+ buf.WriteString(fmt.Sprintf("", name))
+ for _, x := range input {
+ buf.WriteString(x)
+ }
+ buf.WriteString(fmt.Sprintf("", name))
+
+ return buf.Bytes()
+}
+
+// getSOAPArg returns XML representing given input argument as SOAP parameters
+// in combination with getSOAPArgs you can build SOAP body
+func getSOAPArg(name string, input interface{}) (output string) {
+ switch input.(type) {
+ case []string:
+ i := input.([]string)
+ output = fmt.Sprintf(`<%s SOAP-ENC:arrayType="xsd:string[%d]" xsi:type="ns1:ArrayOfString">`, name, len(i))
+ for _, x := range i {
+ output = output + fmt.Sprintf(`- %s
`, x)
+ }
+ output = output + fmt.Sprintf("%s>", name)
+ case string:
+ output = fmt.Sprintf(`<%s xsi:type="xsd:string">%s%s>`, name, input, name)
+ case int, int32, int64:
+ output = fmt.Sprintf(`<%s xsi:type="xsd:integer">%d%s>`, name, input, name)
+ }
+
+ return
+}
+
+type soapFault struct {
+ Code string `xml:"faultcode,omitempty"`
+ Description string `xml:"faultstring,omitempty"`
+}
+
+func (s soapFault) String() string {
+ return fmt.Sprintf("SOAP Fault %s: %s", s.Code, s.Description)
+}
+
+// paramsEncoder allows SoapParams to hook into encoding theirselves, useful when
+// fields consist of complex structs
+type paramsEncoder interface {
+ EncodeParams(ParamsContainer)
+ EncodeArgs(string) string
+}
+
+// ParamsContainer is the interface a type should implement to be able to hold
+// SOAP parameters
+type ParamsContainer interface {
+ Len() int
+ Add(string, interface{})
+}
+
+// soapParams is a utility to make sure parameter data is encoded into a query
+// in the same order as we set them. The TransIP API requires this order for
+// verifying the signature
+type soapParams struct {
+ keys []string
+ values []interface{}
+}
+
+// Add adds parameter data to the end of this SoapParams
+func (s *soapParams) Add(k string, v interface{}) {
+ if s.keys == nil {
+ s.keys = make([]string, 0)
+ }
+
+ if s.values == nil {
+ s.values = make([]interface{}, 0)
+ }
+
+ s.keys = append(s.keys, k)
+ s.values = append(s.values, v)
+}
+
+// Len returns amount of parameters set in this SoapParams
+func (s soapParams) Len() int {
+ return len(s.keys)
+}
+
+// Encode returns a URL-like query string that can be used to generate a request's
+// signature. It's similar to url.Values.Encode() but without sorting of the keys
+// and based on the value's type it tries to encode accordingly.
+func (s soapParams) Encode() string {
+ var buf bytes.Buffer
+ var key string
+
+ for i, v := range s.values {
+ // if this is not the first parameter, prefix with &
+ if i > 0 {
+ buf.WriteString("&")
+ }
+
+ // for empty data fields, don't encode anything
+ if v == nil {
+ continue
+ }
+
+ key = s.keys[i]
+
+ switch v.(type) {
+ case []string:
+ c := v.([]string)
+ for j, cc := range c {
+ if j > 0 {
+ buf.WriteString("&")
+ }
+ buf.WriteString(fmt.Sprintf("%s[%d]=", key, j))
+ buf.WriteString(strings.Replace(url.QueryEscape(cc), "+", "%20", -1))
+ }
+ case string:
+ c := v.(string)
+ buf.WriteString(fmt.Sprintf("%s=", key))
+ buf.WriteString(strings.Replace(url.QueryEscape(c), "+", "%20", -1))
+ case int, int8, int16, int32, int64:
+ buf.WriteString(fmt.Sprintf("%s=", key))
+ buf.WriteString(fmt.Sprintf("%d", v))
+ default:
+ continue
+ }
+ }
+
+ return buf.String()
+}
+
+type soapHeader struct {
+ XMLName struct{} `xml:"Header"`
+ Contents []byte `xml:",innerxml"`
+}
+
+type soapBody struct {
+ XMLName struct{} `xml:"Body"`
+ Contents []byte `xml:",innerxml"`
+}
+
+type soapResponse struct {
+ Response struct {
+ InnerXML []byte `xml:",innerxml"`
+ } `xml:"return"`
+}
+
+type soapEnvelope struct {
+ XMLName struct{} `xml:"Envelope"`
+ Header soapHeader
+ Body soapBody
+}
+
+// SoapRequest holds all information for perfoming a SOAP request
+// Arguments to the request can be specified with AddArgument
+// If padding is defined, the SOAP response will be parsed after it being padded
+// with items in Padding in reverse order
+type SoapRequest struct {
+ Service string
+ Method string
+ params *soapParams // params used for creating signature
+ args []string // XML body arguments
+ Padding []string
+}
+
+// AddArgument adds an argument to the SoapRequest; the arguments ared used to
+// fill the XML request body as well as to create a valid signature for the
+// request
+func (sr *SoapRequest) AddArgument(key string, value interface{}) {
+ if sr.params == nil {
+ sr.params = &soapParams{}
+ }
+
+ // check if value implements paramsEncoder
+ if pe, ok := value.(paramsEncoder); ok {
+ sr.args = append(sr.args, pe.EncodeArgs(key))
+ pe.EncodeParams(sr.params)
+ return
+ }
+
+ switch value.(type) {
+ case []string:
+ sr.params.Add(fmt.Sprintf("%d", sr.params.Len()), value)
+ sr.args = append(sr.args, getSOAPArg(key, value))
+ case string:
+ sr.params.Add(fmt.Sprintf("%d", sr.params.Len()), value)
+ sr.args = append(sr.args, getSOAPArg(key, value.(string)))
+ case int, int8, int16, int32, int64:
+ sr.params.Add(fmt.Sprintf("%d", sr.params.Len()), value)
+ sr.args = append(sr.args, getSOAPArg(key, fmt.Sprintf("%d", value)))
+ default:
+ // check if value implements the String interface
+ if str, ok := value.(fmt.Stringer); ok {
+ sr.params.Add(fmt.Sprintf("%d", sr.params.Len()), str.String())
+ sr.args = append(sr.args, getSOAPArg(key, str.String()))
+ }
+ }
+}
+
+func (sr SoapRequest) getEnvelope() string {
+ return fmt.Sprintf(soapEnvelopeFixture, transipAPIHost, getSOAPArgs(sr.Method, sr.args...))
+}
+
+type soapClient struct {
+ Login string
+ Mode APIMode
+ PrivateKey []byte
+}
+
+// httpReqForSoapRequest creates the HTTP request for a specific SoapRequest
+// this includes setting the URL, POST body and cookies
+func (s soapClient) httpReqForSoapRequest(req SoapRequest) (*http.Request, error) {
+ // format URL
+ url := fmt.Sprintf("https://%s/soap/?service=%s", transipAPIHost, req.Service)
+
+ // create HTTP request
+ // TransIP API SOAP requests are always POST requests
+ httpReq, err := http.NewRequest("POST", url, strings.NewReader(req.getEnvelope()))
+ if err != nil {
+ return nil, err
+ }
+
+ // generate a number-used-once, a.k.a. nonce
+ // seeding the RNG is important if we want to do prevent using the same nonce
+ // in 2 sequential requests
+ rand.Seed(time.Now().UnixNano())
+ nonce := fmt.Sprintf("%d", rand.Int())
+ // set time of request, used later for signature as well
+ timestamp := fmt.Sprintf("%d", time.Now().Unix())
+
+ // set cookies required for the request
+ // most of these cookies are used for the signature as well so they should
+ // obviously match
+ httpReq.AddCookie(&http.Cookie{
+ Name: "login",
+ Value: s.Login,
+ })
+ httpReq.AddCookie(&http.Cookie{
+ Name: "mode",
+ Value: string(s.Mode),
+ })
+ httpReq.AddCookie(&http.Cookie{
+ Name: "timestamp",
+ Value: timestamp,
+ })
+ httpReq.AddCookie(&http.Cookie{
+ Name: "nonce",
+ Value: nonce,
+ })
+
+ // add params required for signature to the request parameters
+ if req.params == nil {
+ req.params = &soapParams{}
+ }
+ // TransIP API is quite picky on the order of the parameters
+ // so don't change anything in the order below
+ req.params.Add("__method", req.Method)
+ req.params.Add("__service", req.Service)
+ req.params.Add("__hostname", transipAPIHost)
+ req.params.Add("__timestamp", timestamp)
+ req.params.Add("__nonce", nonce)
+
+ signature, err := signWithKey(req.params, s.PrivateKey)
+ if err != nil {
+ return nil, err
+ }
+
+ // add signature of the request to the cookies as well
+ httpReq.AddCookie(&http.Cookie{
+ Name: "signature",
+ Value: signature,
+ })
+
+ return httpReq, nil
+}
+
+func parseSoapResponse(data []byte, padding []string, statusCode int, result interface{}) error {
+ // try to decode the resulting XML
+ var env soapEnvelope
+ if err := xml.Unmarshal(data, &env); err != nil {
+ return err
+ }
+
+ // try to decode the body to a soapFault
+ var fault soapFault
+ if err := xml.Unmarshal(env.Body.Contents, &fault); err != nil {
+ return err
+ }
+
+ // by checking fault's Code, we can determine if the response body in fact
+ // was a SOAP fault and if it was: return it as an error
+ if len(fault.Code) > 0 {
+ return errors.New(fault.String())
+ }
+
+ // try to decode into soapResponse
+ sr := soapResponse{}
+ if err := xml.Unmarshal(env.Body.Contents, &sr); err != nil {
+ return err
+ }
+
+ // if the response was empty and HTTP status was 200, consider it a success
+ if len(sr.Response.InnerXML) == 0 && statusCode == 200 {
+ return nil
+ }
+
+ // it seems like xml.Unmarshal won't work well on the most outer element
+ // so even when no Padding is defined, always pad with "transip" element
+ p := append([]string{"transip"}, padding...)
+ innerXML := padXMLData(sr.Response.InnerXML, p)
+
+ // try to decode to given result interface
+ return xml.Unmarshal([]byte(innerXML), &result)
+}
+
+func (s *soapClient) call(req SoapRequest, result interface{}) error {
+ // get http request for soap request
+ httpReq, err := s.httpReqForSoapRequest(req)
+ if err != nil {
+ return err
+ }
+
+ // create HTTP client and do the actual request
+ client := &http.Client{Timeout: time.Second * 10}
+ // make sure to verify the validity of remote certificate
+ // this is the default, but adding this flag here makes it easier to toggle
+ // it for testing/debugging
+ client.Transport = &http.Transport{
+ TLSClientConfig: &tls.Config{
+ InsecureSkipVerify: false,
+ },
+ }
+ resp, err := client.Do(httpReq)
+ if err != nil {
+ return fmt.Errorf("request error:\n%s", err.Error())
+ }
+ defer resp.Body.Close()
+
+ // read entire response body
+ b, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return err
+ }
+
+ // parse SOAP response into given result interface
+ return parseSoapResponse(b, req.Padding, resp.StatusCode, result)
+}
+
+// apply given padding around the XML data fed into this function
+// padding is applied in reverse order, so last element of padding is the
+// innermost element in the resulting XML
+func padXMLData(data []byte, padding []string) []byte {
+ // get right information from padding elements by matching to regex
+ re, _ := regexp.Compile("^(?:([^ ]+) )?([^>]+)>?$")
+
+ var prefix, suffix []byte
+ var tag, attr string
+ // go over each padding element
+ for i := len(padding); i > 0; i-- {
+ res := re.FindStringSubmatch(padding[i-1])
+ // no attribute was given
+ if len(res[1]) == 0 {
+ tag = res[2]
+ attr = ""
+ } else {
+ tag = res[1]
+ attr = " " + res[2]
+ }
+
+ prefix = []byte(fmt.Sprintf("<%s%s>", tag, attr))
+ suffix = []byte(fmt.Sprintf("%s>", tag))
+ data = append(append(prefix, data...), suffix...)
+ }
+
+ return data
+}
+
+// TestParamsContainer is only useful for unit testing the ParamsContainer
+// implementation of other type
+type TestParamsContainer struct {
+ Prm string
+}
+
+// Add just makes sure we use Len(), key and value in the result so it can be
+// tested
+func (t *TestParamsContainer) Add(key string, value interface{}) {
+ var prefix string
+ if t.Len() > 0 {
+ prefix = "&"
+ }
+ t.Prm = t.Prm + prefix + fmt.Sprintf("%d%s=%s", t.Len(), key, value)
+}
+
+// Len returns current length of test data in TestParamsContainer
+func (t TestParamsContainer) Len() int {
+ return len(t.Prm)
+}
diff --git a/vendor/github.com/transip/gotransip/util/util.go b/vendor/github.com/transip/gotransip/util/util.go
new file mode 100644
index 00000000..7f1beecf
--- /dev/null
+++ b/vendor/github.com/transip/gotransip/util/util.go
@@ -0,0 +1,37 @@
+package util
+
+import (
+ "encoding/xml"
+ "time"
+)
+
+// KeyValueXML resembles the complex struct for getting key/value pairs from XML
+type KeyValueXML struct {
+ Cont []struct {
+ Item []struct {
+ Key string `xml:"key"`
+ Value string `xml:"value"`
+ } `xml:"item"`
+ } `xml:"item"`
+}
+
+// XMLTime is a custom type to decode XML values to time.Time directly
+type XMLTime struct {
+ time.Time
+}
+
+// UnmarshalXML is implemented to be able act as custom XML type
+// it tries to parse time from given elements value
+func (x *XMLTime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+ var v string
+ if err := d.DecodeElement(&v, &start); err != nil {
+ return err
+ }
+
+ if p, _ := time.Parse("2006-01-02 15:04:05", v); !p.IsZero() {
+ *x = XMLTime{p}
+ } else if p, _ := time.Parse("2006-01-02", v); !p.IsZero() {
+ *x = XMLTime{p}
+ }
+ return nil
+}