diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 084cdc70..f1dd86ba 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -20,7 +20,7 @@ It is up to you to make a strong point about your proposal and convince us of th
## Pull requests
-Patches, new features and improvements are a great way to help the project.
+Patches, new features and improvements are a great way to help the project.
Please keep them focused on one thing and do not include unrelated commits.
All pull requests which alter the behaviour of the program, add new behaviour or somehow alter code in a non-trivial way should **always** include tests.
@@ -60,6 +60,7 @@ owners to license your work under the terms of the [MIT License](LICENSE).
| Go Daddy | `godaddy` | [documentation](https://developer.godaddy.com/doc/endpoint/domains) | - |
| hosting.de | `hostingde` | [documentation](https://www.hosting.de/api/#dns) | - |
| Internet Initiative Japan | `iij` | [documentation](http://manual.iij.jp/p2/pubapi/) | [Go client](https://github.com/iij/doapi) |
+| INWX | `inwx` | [documentation](https://www.inwx.de/en/help/apidoc) | [Go client](https://github.com/smueller18/goinwx) |
| Lightsail | `lightsail` | ? | [Go client](https://github.com/aws/aws-sdk-go/aws) |
| Linode (deprecated) | `linode` | [documentation](https://www.linode.com/api/dns) | [Go client](https://github.com/timewasted/linode) |
| Linodev4 | `linodev4` | [documentation](https://developers.linode.com/api/v4) | [Go client](https://github.com/linode/linodego) |
diff --git a/Gopkg.lock b/Gopkg.lock
index 1ec36a7d..22b19271 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -191,6 +191,14 @@
revision = "0863d555d5198557e0bf2b61b6c59a873ab0173a"
version = "v0.11.1"
+[[projects]]
+ digest = "1:aa3ed0a71c4e66e4ae6486bf97a3f4cab28edc78df2e50c5ad01dc7d91604b88"
+ name = "github.com/fatih/structs"
+ packages = ["."]
+ pruneopts = "NUT"
+ revision = "4966fc68f5b7593aafa6cbbba2d65ec6e1416047"
+ version = "v1.1.0"
+
[[projects]]
digest = "1:74d9b0a7b4107b41e0ade759fac64502876f82d29fb23d77b3dd24b194ee3dd5"
name = "github.com/go-ini/ini"
@@ -265,6 +273,14 @@
revision = "59fac5042749a5afb9af70e813da1dd5474f0167"
version = "1.0.1"
+[[projects]]
+ branch = "master"
+ digest = "1:ec142582cd3bb5cc29a2bc7181a6e67367b90b19f6a957ce506dcd7d1500bf95"
+ name = "github.com/kolo/xmlrpc"
+ packages = ["."]
+ pruneopts = "NUT"
+ revision = "16bdd962781df9696f40cc2bab924f1a855a7f89"
+
[[projects]]
digest = "1:417193ba917954c4837c6fc48c6ac241b3fefd13fc0889367b4a7e43b69d582c"
name = "github.com/ldez/go-auroradns"
@@ -297,6 +313,14 @@
revision = "ae18d6b8b3205b561c79e8e5f69bff09736185f4"
version = "v1.0.0"
+[[projects]]
+ digest = "1:a45ae66dea4c899d79fceb116accfa1892105c251f0dcd9a217ddc276b42ec68"
+ name = "github.com/mitchellh/mapstructure"
+ packages = ["."]
+ pruneopts = "NUT"
+ revision = "3536a929edddb9a5b34bd6861dc4a9647cb459fe"
+ version = "v1.1.2"
+
[[projects]]
digest = "1:2f42fa12d6911c7b7659738758631bec870b7e9b4c6be5444f963cdcfccc191f"
name = "github.com/modern-go/concurrent"
@@ -382,6 +406,14 @@
revision = "3e01752db0189b9157070a0e1668a620f9a85da2"
version = "v1.0.6"
+[[projects]]
+ branch = "master"
+ digest = "1:94fcec8ba983a96bb3f123ab9690955baf44d6d32de342f95f5a2f665c70457a"
+ name = "github.com/smueller18/goinwx"
+ packages = ["."]
+ pruneopts = "NUT"
+ revision = "5d138389109eca96463f44f692408f0d1c731278"
+
[[projects]]
digest = "1:60a46e2410edbf02b419f833372dd1d24d7aa1b916a990a7370e792fada1eadd"
name = "github.com/stretchr/objx"
@@ -620,6 +652,7 @@
"github.com/rainycape/memcache",
"github.com/sacloud/libsacloud/api",
"github.com/sacloud/libsacloud/sacloud",
+ "github.com/smueller18/goinwx",
"github.com/stretchr/testify/assert",
"github.com/stretchr/testify/mock",
"github.com/stretchr/testify/require",
diff --git a/Gopkg.toml b/Gopkg.toml
index d8473cf9..af2670a8 100644
--- a/Gopkg.toml
+++ b/Gopkg.toml
@@ -53,6 +53,10 @@
branch = "master"
name = "github.com/timewasted/linode"
+[[constraint]]
+ branch = "master"
+ name = "github.com/smueller18/goinwx"
+
[[constraint]]
version = "0.5.1"
name = "github.com/linode/linodego"
diff --git a/cli.go b/cli.go
index 8aa7de15..4c4c7ba1 100644
--- a/cli.go
+++ b/cli.go
@@ -227,6 +227,7 @@ Here is an example bash command using the CloudFlare DNS provider:
fmt.Fprintln(w, "\tgodaddy:\tGODADDY_API_KEY, GODADDY_API_SECRET")
fmt.Fprintln(w, "\thostingde:\tHOSTINGDE_API_KEY, HOSTINGDE_ZONE_NAME")
fmt.Fprintln(w, "\tiij:\tIIJ_API_ACCESS_KEY, IIJ_API_SECRET_KEY, IIJ_DO_SERVICE_CODE")
+ fmt.Fprintln(w, "\tinwx:\tINWX_USERNAME, INWX_PASSWORD")
fmt.Fprintln(w, "\tlightsail:\tAWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, DNS_ZONE")
fmt.Fprintln(w, "\tlinode:\tLINODE_API_KEY")
fmt.Fprintln(w, "\tlinodev4:\tLINODE_TOKEN")
@@ -274,6 +275,7 @@ Here is an example bash command using the CloudFlare DNS provider:
fmt.Fprintln(w, "\tgodaddy:\tGODADDY_POLLING_INTERVAL, GODADDY_PROPAGATION_TIMEOUT, GODADDY_TTL, GODADDY_HTTP_TIMEOUT")
fmt.Fprintln(w, "\thostingde:\tHOSTINGDE_POLLING_INTERVAL, HOSTINGDE_PROPAGATION_TIMEOUT, HOSTINGDE_TTL, HOSTINGDE_HTTP_TIMEOUT")
fmt.Fprintln(w, "\tiij:\tIIJ_POLLING_INTERVAL, IIJ_PROPAGATION_TIMEOUT, IIJ_TTL")
+ fmt.Fprintln(w, "\tinwx:\tINWX_POLLING_INTERVAL, INWX_PROPAGATION_TIMEOUT, INWX_TTL, INWX_SANDBOX")
fmt.Fprintln(w, "\tlightsail:\tLIGHTSAIL_POLLING_INTERVAL, LIGHTSAIL_PROPAGATION_TIMEOUT")
fmt.Fprintln(w, "\tlinode:\tLINODE_POLLING_INTERVAL, LINODE_TTL, LINODE_HTTP_TIMEOUT")
fmt.Fprintln(w, "\tlinodev4:\tLINODE_POLLING_INTERVAL, LINODE_TTL, LINODE_HTTP_TIMEOUT")
diff --git a/providers/dns/dns_providers.go b/providers/dns/dns_providers.go
index c6a8bdcf..1d7672a4 100644
--- a/providers/dns/dns_providers.go
+++ b/providers/dns/dns_providers.go
@@ -29,6 +29,7 @@ import (
"github.com/xenolf/lego/providers/dns/godaddy"
"github.com/xenolf/lego/providers/dns/hostingde"
"github.com/xenolf/lego/providers/dns/iij"
+ "github.com/xenolf/lego/providers/dns/inwx"
"github.com/xenolf/lego/providers/dns/lightsail"
"github.com/xenolf/lego/providers/dns/linode"
"github.com/xenolf/lego/providers/dns/linodev4"
@@ -104,6 +105,8 @@ func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error)
return hostingde.NewDNSProvider()
case "iij":
return iij.NewDNSProvider()
+ case "inwx":
+ return inwx.NewDNSProvider()
case "lightsail":
return lightsail.NewDNSProvider()
case "linode":
diff --git a/providers/dns/inwx/inwx.go b/providers/dns/inwx/inwx.go
new file mode 100644
index 00000000..ba6c8cbe
--- /dev/null
+++ b/providers/dns/inwx/inwx.go
@@ -0,0 +1,166 @@
+// Package inwx implements a DNS provider for solving the DNS-01 challenge using inwx dom robot
+package inwx
+
+import (
+ "errors"
+ "fmt"
+ "time"
+
+ "github.com/smueller18/goinwx"
+ "github.com/xenolf/lego/acme"
+ "github.com/xenolf/lego/log"
+ "github.com/xenolf/lego/platform/config/env"
+)
+
+// Config is used to configure the creation of the DNSProvider
+type Config struct {
+ Username string
+ Password string
+ Sandbox bool
+ PropagationTimeout time.Duration
+ PollingInterval time.Duration
+ TTL int
+}
+
+// NewDefaultConfig returns a default configuration for the DNSProvider
+func NewDefaultConfig() *Config {
+ return &Config{
+ PropagationTimeout: env.GetOrDefaultSecond("INWX_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout),
+ PollingInterval: env.GetOrDefaultSecond("INWX_POLLING_INTERVAL", acme.DefaultPollingInterval),
+ TTL: env.GetOrDefaultInt("INWX_TTL", 300),
+ Sandbox: env.GetOrDefaultBool("INWX_SANDBOX", false),
+ }
+}
+
+// DNSProvider is an implementation of the acme.ChallengeProvider interface
+type DNSProvider struct {
+ config *Config
+ client *goinwx.Client
+}
+
+// NewDNSProvider returns a DNSProvider instance configured for Dyn DNS.
+// Credentials must be passed in the environment variables:
+// INWX_USERNAME and INWX_PASSWORD.
+func NewDNSProvider() (*DNSProvider, error) {
+ values, err := env.Get("INWX_USERNAME", "INWX_PASSWORD")
+ if err != nil {
+ return nil, fmt.Errorf("inwx: %v", err)
+ }
+
+ config := NewDefaultConfig()
+ config.Username = values["INWX_USERNAME"]
+ config.Password = values["INWX_PASSWORD"]
+
+ return NewDNSProviderConfig(config)
+}
+
+// NewDNSProviderConfig return a DNSProvider instance configured for Dyn DNS
+func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
+ if config == nil {
+ return nil, errors.New("inwx: the configuration of the DNS provider is nil")
+ }
+
+ if config.Username == "" || config.Password == "" {
+ return nil, fmt.Errorf("inwx: credentials missing")
+ }
+
+ if config.Sandbox {
+ log.Infof("inwx: sandbox mode is enabled")
+ }
+
+ client := goinwx.NewClient(config.Username, config.Password, &goinwx.ClientOptions{Sandbox: config.Sandbox})
+
+ return &DNSProvider{config: config, client: client}, nil
+}
+
+// Present creates a TXT record using the specified parameters
+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 fmt.Errorf("inwx: %v", err)
+ }
+
+ err = d.client.Account.Login()
+ if err != nil {
+ return fmt.Errorf("inwx: %v", err)
+ }
+
+ defer func() {
+ errL := d.client.Account.Logout()
+ if errL != nil {
+ log.Infof("inwx: failed to logout: %v", errL)
+ }
+ }()
+
+ var request = &goinwx.NameserverRecordRequest{
+ Domain: acme.UnFqdn(authZone),
+ Name: acme.UnFqdn(fqdn),
+ Type: "TXT",
+ Content: value,
+ Ttl: d.config.TTL,
+ }
+
+ _, err = d.client.Nameservers.CreateRecord(request)
+ if err != nil {
+ switch err.(type) {
+ case *goinwx.ErrorResponse:
+ if err.(*goinwx.ErrorResponse).Message == "Object exists" {
+ return nil
+ }
+ return fmt.Errorf("inwx: %v", err)
+ default:
+ return fmt.Errorf("inwx: %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 fmt.Errorf("inwx: %v", err)
+ }
+
+ err = d.client.Account.Login()
+ if err != nil {
+ return fmt.Errorf("inwx: %v", err)
+ }
+
+ defer func() {
+ errL := d.client.Account.Logout()
+ if errL != nil {
+ log.Infof("inwx: failed to logout: %v", errL)
+ }
+ }()
+
+ response, err := d.client.Nameservers.Info(&goinwx.NameserverInfoRequest{
+ Domain: acme.UnFqdn(authZone),
+ Name: acme.UnFqdn(fqdn),
+ Type: "TXT",
+ })
+ if err != nil {
+ return fmt.Errorf("inwx: %v", err)
+ }
+
+ var lastErr error
+ for _, record := range response.Records {
+ err = d.client.Nameservers.DeleteRecord(record.Id)
+ if err != nil {
+ lastErr = fmt.Errorf("inwx: %v", err)
+ }
+ }
+
+ return lastErr
+}
+
+// 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
+}
diff --git a/providers/dns/inwx/inwx_test.go b/providers/dns/inwx/inwx_test.go
new file mode 100644
index 00000000..b4ec74ac
--- /dev/null
+++ b/providers/dns/inwx/inwx_test.go
@@ -0,0 +1,140 @@
+package inwx
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ "github.com/xenolf/lego/platform/tester"
+)
+
+var envTest = tester.NewEnvTest(
+ "INWX_USERNAME",
+ "INWX_PASSWORD",
+ "INWX_SANDBOX",
+ "INWX_TTL").
+ WithDomain("INWX_DOMAIN").
+ WithLiveTestRequirements("INWX_USERNAME", "INWX_PASSWORD", "INWX_DOMAIN")
+
+func TestNewDNSProvider(t *testing.T) {
+ testCases := []struct {
+ desc string
+ envVars map[string]string
+ expected string
+ }{
+ {
+ desc: "success",
+ envVars: map[string]string{
+ "INWX_USERNAME": "123",
+ "INWX_PASSWORD": "456",
+ },
+ },
+ {
+ desc: "missing credentials",
+ envVars: map[string]string{
+ "INWX_USERNAME": "",
+ "INWX_PASSWORD": "",
+ },
+ expected: "inwx: some credentials information are missing: INWX_USERNAME,INWX_PASSWORD",
+ },
+ {
+ desc: "missing username",
+ envVars: map[string]string{
+ "INWX_USERNAME": "",
+ "INWX_PASSWORD": "456",
+ },
+ expected: "inwx: some credentials information are missing: INWX_USERNAME",
+ },
+ {
+ desc: "missing password",
+ envVars: map[string]string{
+ "INWX_USERNAME": "123",
+ "INWX_PASSWORD": "",
+ },
+ expected: "inwx: some credentials information are missing: INWX_PASSWORD",
+ },
+ }
+
+ 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
+ username string
+ password string
+ expected string
+ }{
+ {
+ desc: "success",
+ username: "123",
+ password: "456",
+ },
+ {
+ desc: "missing credentials",
+ expected: "inwx: credentials missing",
+ },
+ }
+
+ for _, test := range testCases {
+ t.Run(test.desc, func(t *testing.T) {
+ config := NewDefaultConfig()
+ config.Username = test.username
+ config.Password = test.password
+
+ 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 TestLivePresentAndCleanup(t *testing.T) {
+ if !envTest.IsLiveTest() {
+ t.Skip("skipping live test")
+ }
+
+ envTest.RestoreEnv()
+ envTest.Apply(map[string]string{
+ "INWX_SANDBOX": "true",
+ "INWX_TTL": "3600", // In sandbox mode, the minimum allowed TTL is 3600
+ })
+ defer envTest.RestoreEnv()
+
+ provider, err := NewDNSProvider()
+ require.NoError(t, err)
+
+ err = provider.Present(envTest.GetDomain(), "", "123d==")
+ require.NoError(t, err)
+
+ // Verify that no error is thrown if record already exists
+ err = provider.Present(envTest.GetDomain(), "", "123d==")
+ require.NoError(t, err)
+
+ err = provider.CleanUp(envTest.GetDomain(), "", "123d==")
+ require.NoError(t, err)
+}
diff --git a/vendor/github.com/fatih/structs/LICENSE b/vendor/github.com/fatih/structs/LICENSE
new file mode 100644
index 00000000..34504e4b
--- /dev/null
+++ b/vendor/github.com/fatih/structs/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Fatih Arslan
+
+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.
\ No newline at end of file
diff --git a/vendor/github.com/fatih/structs/field.go b/vendor/github.com/fatih/structs/field.go
new file mode 100644
index 00000000..e6978323
--- /dev/null
+++ b/vendor/github.com/fatih/structs/field.go
@@ -0,0 +1,141 @@
+package structs
+
+import (
+ "errors"
+ "fmt"
+ "reflect"
+)
+
+var (
+ errNotExported = errors.New("field is not exported")
+ errNotSettable = errors.New("field is not settable")
+)
+
+// Field represents a single struct field that encapsulates high level
+// functions around the field.
+type Field struct {
+ value reflect.Value
+ field reflect.StructField
+ defaultTag string
+}
+
+// Tag returns the value associated with key in the tag string. If there is no
+// such key in the tag, Tag returns the empty string.
+func (f *Field) Tag(key string) string {
+ return f.field.Tag.Get(key)
+}
+
+// Value returns the underlying value of the field. It panics if the field
+// is not exported.
+func (f *Field) Value() interface{} {
+ return f.value.Interface()
+}
+
+// IsEmbedded returns true if the given field is an anonymous field (embedded)
+func (f *Field) IsEmbedded() bool {
+ return f.field.Anonymous
+}
+
+// IsExported returns true if the given field is exported.
+func (f *Field) IsExported() bool {
+ return f.field.PkgPath == ""
+}
+
+// IsZero returns true if the given field is not initialized (has a zero value).
+// It panics if the field is not exported.
+func (f *Field) IsZero() bool {
+ zero := reflect.Zero(f.value.Type()).Interface()
+ current := f.Value()
+
+ return reflect.DeepEqual(current, zero)
+}
+
+// Name returns the name of the given field
+func (f *Field) Name() string {
+ return f.field.Name
+}
+
+// Kind returns the fields kind, such as "string", "map", "bool", etc ..
+func (f *Field) Kind() reflect.Kind {
+ return f.value.Kind()
+}
+
+// Set sets the field to given value v. It returns an error if the field is not
+// settable (not addressable or not exported) or if the given value's type
+// doesn't match the fields type.
+func (f *Field) Set(val interface{}) error {
+ // we can't set unexported fields, so be sure this field is exported
+ if !f.IsExported() {
+ return errNotExported
+ }
+
+ // do we get here? not sure...
+ if !f.value.CanSet() {
+ return errNotSettable
+ }
+
+ given := reflect.ValueOf(val)
+
+ if f.value.Kind() != given.Kind() {
+ return fmt.Errorf("wrong kind. got: %s want: %s", given.Kind(), f.value.Kind())
+ }
+
+ f.value.Set(given)
+ return nil
+}
+
+// Zero sets the field to its zero value. It returns an error if the field is not
+// settable (not addressable or not exported).
+func (f *Field) Zero() error {
+ zero := reflect.Zero(f.value.Type()).Interface()
+ return f.Set(zero)
+}
+
+// Fields returns a slice of Fields. This is particular handy to get the fields
+// of a nested struct . A struct tag with the content of "-" ignores the
+// checking of that particular field. Example:
+//
+// // Field is ignored by this package.
+// Field *http.Request `structs:"-"`
+//
+// It panics if field is not exported or if field's kind is not struct
+func (f *Field) Fields() []*Field {
+ return getFields(f.value, f.defaultTag)
+}
+
+// Field returns the field from a nested struct. It panics if the nested struct
+// is not exported or if the field was not found.
+func (f *Field) Field(name string) *Field {
+ field, ok := f.FieldOk(name)
+ if !ok {
+ panic("field not found")
+ }
+
+ return field
+}
+
+// FieldOk returns the field from a nested struct. The boolean returns whether
+// the field was found (true) or not (false).
+func (f *Field) FieldOk(name string) (*Field, bool) {
+ value := &f.value
+ // value must be settable so we need to make sure it holds the address of the
+ // variable and not a copy, so we can pass the pointer to strctVal instead of a
+ // copy (which is not assigned to any variable, hence not settable).
+ // see "https://blog.golang.org/laws-of-reflection#TOC_8."
+ if f.value.Kind() != reflect.Ptr {
+ a := f.value.Addr()
+ value = &a
+ }
+ v := strctVal(value.Interface())
+ t := v.Type()
+
+ field, ok := t.FieldByName(name)
+ if !ok {
+ return nil, false
+ }
+
+ return &Field{
+ field: field,
+ value: v.FieldByName(name),
+ }, true
+}
diff --git a/vendor/github.com/fatih/structs/structs.go b/vendor/github.com/fatih/structs/structs.go
new file mode 100644
index 00000000..3a877065
--- /dev/null
+++ b/vendor/github.com/fatih/structs/structs.go
@@ -0,0 +1,584 @@
+// Package structs contains various utilities functions to work with structs.
+package structs
+
+import (
+ "fmt"
+
+ "reflect"
+)
+
+var (
+ // DefaultTagName is the default tag name for struct fields which provides
+ // a more granular to tweak certain structs. Lookup the necessary functions
+ // for more info.
+ DefaultTagName = "structs" // struct's field default tag name
+)
+
+// Struct encapsulates a struct type to provide several high level functions
+// around the struct.
+type Struct struct {
+ raw interface{}
+ value reflect.Value
+ TagName string
+}
+
+// New returns a new *Struct with the struct s. It panics if the s's kind is
+// not struct.
+func New(s interface{}) *Struct {
+ return &Struct{
+ raw: s,
+ value: strctVal(s),
+ TagName: DefaultTagName,
+ }
+}
+
+// Map converts the given struct to a map[string]interface{}, where the keys
+// of the map are the field names and the values of the map the associated
+// values of the fields. The default key string is the struct field name but
+// can be changed in the struct field's tag value. The "structs" key in the
+// struct's field tag value is the key name. Example:
+//
+// // Field appears in map as key "myName".
+// Name string `structs:"myName"`
+//
+// A tag value with the content of "-" ignores that particular field. Example:
+//
+// // Field is ignored by this package.
+// Field bool `structs:"-"`
+//
+// A tag value with the content of "string" uses the stringer to get the value. Example:
+//
+// // The value will be output of Animal's String() func.
+// // Map will panic if Animal does not implement String().
+// Field *Animal `structs:"field,string"`
+//
+// A tag value with the option of "flatten" used in a struct field is to flatten its fields
+// in the output map. Example:
+//
+// // The FieldStruct's fields will be flattened into the output map.
+// FieldStruct time.Time `structs:",flatten"`
+//
+// A tag value with the option of "omitnested" stops iterating further if the type
+// is a struct. Example:
+//
+// // Field is not processed further by this package.
+// Field time.Time `structs:"myName,omitnested"`
+// Field *http.Request `structs:",omitnested"`
+//
+// A tag value with the option of "omitempty" ignores that particular field if
+// the field value is empty. Example:
+//
+// // Field appears in map as key "myName", but the field is
+// // skipped if empty.
+// Field string `structs:"myName,omitempty"`
+//
+// // Field appears in map as key "Field" (the default), but
+// // the field is skipped if empty.
+// Field string `structs:",omitempty"`
+//
+// Note that only exported fields of a struct can be accessed, non exported
+// fields will be neglected.
+func (s *Struct) Map() map[string]interface{} {
+ out := make(map[string]interface{})
+ s.FillMap(out)
+ return out
+}
+
+// FillMap is the same as Map. Instead of returning the output, it fills the
+// given map.
+func (s *Struct) FillMap(out map[string]interface{}) {
+ if out == nil {
+ return
+ }
+
+ fields := s.structFields()
+
+ for _, field := range fields {
+ name := field.Name
+ val := s.value.FieldByName(name)
+ isSubStruct := false
+ var finalVal interface{}
+
+ tagName, tagOpts := parseTag(field.Tag.Get(s.TagName))
+ if tagName != "" {
+ name = tagName
+ }
+
+ // if the value is a zero value and the field is marked as omitempty do
+ // not include
+ if tagOpts.Has("omitempty") {
+ zero := reflect.Zero(val.Type()).Interface()
+ current := val.Interface()
+
+ if reflect.DeepEqual(current, zero) {
+ continue
+ }
+ }
+
+ if !tagOpts.Has("omitnested") {
+ finalVal = s.nested(val)
+
+ v := reflect.ValueOf(val.Interface())
+ if v.Kind() == reflect.Ptr {
+ v = v.Elem()
+ }
+
+ switch v.Kind() {
+ case reflect.Map, reflect.Struct:
+ isSubStruct = true
+ }
+ } else {
+ finalVal = val.Interface()
+ }
+
+ if tagOpts.Has("string") {
+ s, ok := val.Interface().(fmt.Stringer)
+ if ok {
+ out[name] = s.String()
+ }
+ continue
+ }
+
+ if isSubStruct && (tagOpts.Has("flatten")) {
+ for k := range finalVal.(map[string]interface{}) {
+ out[k] = finalVal.(map[string]interface{})[k]
+ }
+ } else {
+ out[name] = finalVal
+ }
+ }
+}
+
+// Values converts the given s struct's field values to a []interface{}. A
+// struct tag with the content of "-" ignores the that particular field.
+// Example:
+//
+// // Field is ignored by this package.
+// Field int `structs:"-"`
+//
+// A value with the option of "omitnested" stops iterating further if the type
+// is a struct. Example:
+//
+// // Fields is not processed further by this package.
+// Field time.Time `structs:",omitnested"`
+// Field *http.Request `structs:",omitnested"`
+//
+// A tag value with the option of "omitempty" ignores that particular field and
+// is not added to the values if the field value is empty. Example:
+//
+// // Field is skipped if empty
+// Field string `structs:",omitempty"`
+//
+// Note that only exported fields of a struct can be accessed, non exported
+// fields will be neglected.
+func (s *Struct) Values() []interface{} {
+ fields := s.structFields()
+
+ var t []interface{}
+
+ for _, field := range fields {
+ val := s.value.FieldByName(field.Name)
+
+ _, tagOpts := parseTag(field.Tag.Get(s.TagName))
+
+ // if the value is a zero value and the field is marked as omitempty do
+ // not include
+ if tagOpts.Has("omitempty") {
+ zero := reflect.Zero(val.Type()).Interface()
+ current := val.Interface()
+
+ if reflect.DeepEqual(current, zero) {
+ continue
+ }
+ }
+
+ if tagOpts.Has("string") {
+ s, ok := val.Interface().(fmt.Stringer)
+ if ok {
+ t = append(t, s.String())
+ }
+ continue
+ }
+
+ if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
+ // look out for embedded structs, and convert them to a
+ // []interface{} to be added to the final values slice
+ t = append(t, Values(val.Interface())...)
+ } else {
+ t = append(t, val.Interface())
+ }
+ }
+
+ return t
+}
+
+// Fields returns a slice of Fields. A struct tag with the content of "-"
+// ignores the checking of that particular field. Example:
+//
+// // Field is ignored by this package.
+// Field bool `structs:"-"`
+//
+// It panics if s's kind is not struct.
+func (s *Struct) Fields() []*Field {
+ return getFields(s.value, s.TagName)
+}
+
+// Names returns a slice of field names. A struct tag with the content of "-"
+// ignores the checking of that particular field. Example:
+//
+// // Field is ignored by this package.
+// Field bool `structs:"-"`
+//
+// It panics if s's kind is not struct.
+func (s *Struct) Names() []string {
+ fields := getFields(s.value, s.TagName)
+
+ names := make([]string, len(fields))
+
+ for i, field := range fields {
+ names[i] = field.Name()
+ }
+
+ return names
+}
+
+func getFields(v reflect.Value, tagName string) []*Field {
+ if v.Kind() == reflect.Ptr {
+ v = v.Elem()
+ }
+
+ t := v.Type()
+
+ var fields []*Field
+
+ for i := 0; i < t.NumField(); i++ {
+ field := t.Field(i)
+
+ if tag := field.Tag.Get(tagName); tag == "-" {
+ continue
+ }
+
+ f := &Field{
+ field: field,
+ value: v.FieldByName(field.Name),
+ }
+
+ fields = append(fields, f)
+
+ }
+
+ return fields
+}
+
+// Field returns a new Field struct that provides several high level functions
+// around a single struct field entity. It panics if the field is not found.
+func (s *Struct) Field(name string) *Field {
+ f, ok := s.FieldOk(name)
+ if !ok {
+ panic("field not found")
+ }
+
+ return f
+}
+
+// FieldOk returns a new Field struct that provides several high level functions
+// around a single struct field entity. The boolean returns true if the field
+// was found.
+func (s *Struct) FieldOk(name string) (*Field, bool) {
+ t := s.value.Type()
+
+ field, ok := t.FieldByName(name)
+ if !ok {
+ return nil, false
+ }
+
+ return &Field{
+ field: field,
+ value: s.value.FieldByName(name),
+ defaultTag: s.TagName,
+ }, true
+}
+
+// IsZero returns true if all fields in a struct is a zero value (not
+// initialized) A struct tag with the content of "-" ignores the checking of
+// that particular field. Example:
+//
+// // Field is ignored by this package.
+// Field bool `structs:"-"`
+//
+// A value with the option of "omitnested" stops iterating further if the type
+// is a struct. Example:
+//
+// // Field is not processed further by this package.
+// Field time.Time `structs:"myName,omitnested"`
+// Field *http.Request `structs:",omitnested"`
+//
+// Note that only exported fields of a struct can be accessed, non exported
+// fields will be neglected. It panics if s's kind is not struct.
+func (s *Struct) IsZero() bool {
+ fields := s.structFields()
+
+ for _, field := range fields {
+ val := s.value.FieldByName(field.Name)
+
+ _, tagOpts := parseTag(field.Tag.Get(s.TagName))
+
+ if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
+ ok := IsZero(val.Interface())
+ if !ok {
+ return false
+ }
+
+ continue
+ }
+
+ // zero value of the given field, such as "" for string, 0 for int
+ zero := reflect.Zero(val.Type()).Interface()
+
+ // current value of the given field
+ current := val.Interface()
+
+ if !reflect.DeepEqual(current, zero) {
+ return false
+ }
+ }
+
+ return true
+}
+
+// HasZero returns true if a field in a struct is not initialized (zero value).
+// A struct tag with the content of "-" ignores the checking of that particular
+// field. Example:
+//
+// // Field is ignored by this package.
+// Field bool `structs:"-"`
+//
+// A value with the option of "omitnested" stops iterating further if the type
+// is a struct. Example:
+//
+// // Field is not processed further by this package.
+// Field time.Time `structs:"myName,omitnested"`
+// Field *http.Request `structs:",omitnested"`
+//
+// Note that only exported fields of a struct can be accessed, non exported
+// fields will be neglected. It panics if s's kind is not struct.
+func (s *Struct) HasZero() bool {
+ fields := s.structFields()
+
+ for _, field := range fields {
+ val := s.value.FieldByName(field.Name)
+
+ _, tagOpts := parseTag(field.Tag.Get(s.TagName))
+
+ if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
+ ok := HasZero(val.Interface())
+ if ok {
+ return true
+ }
+
+ continue
+ }
+
+ // zero value of the given field, such as "" for string, 0 for int
+ zero := reflect.Zero(val.Type()).Interface()
+
+ // current value of the given field
+ current := val.Interface()
+
+ if reflect.DeepEqual(current, zero) {
+ return true
+ }
+ }
+
+ return false
+}
+
+// Name returns the structs's type name within its package. For more info refer
+// to Name() function.
+func (s *Struct) Name() string {
+ return s.value.Type().Name()
+}
+
+// structFields returns the exported struct fields for a given s struct. This
+// is a convenient helper method to avoid duplicate code in some of the
+// functions.
+func (s *Struct) structFields() []reflect.StructField {
+ t := s.value.Type()
+
+ var f []reflect.StructField
+
+ for i := 0; i < t.NumField(); i++ {
+ field := t.Field(i)
+ // we can't access the value of unexported fields
+ if field.PkgPath != "" {
+ continue
+ }
+
+ // don't check if it's omitted
+ if tag := field.Tag.Get(s.TagName); tag == "-" {
+ continue
+ }
+
+ f = append(f, field)
+ }
+
+ return f
+}
+
+func strctVal(s interface{}) reflect.Value {
+ v := reflect.ValueOf(s)
+
+ // if pointer get the underlying element≤
+ for v.Kind() == reflect.Ptr {
+ v = v.Elem()
+ }
+
+ if v.Kind() != reflect.Struct {
+ panic("not struct")
+ }
+
+ return v
+}
+
+// Map converts the given struct to a map[string]interface{}. For more info
+// refer to Struct types Map() method. It panics if s's kind is not struct.
+func Map(s interface{}) map[string]interface{} {
+ return New(s).Map()
+}
+
+// FillMap is the same as Map. Instead of returning the output, it fills the
+// given map.
+func FillMap(s interface{}, out map[string]interface{}) {
+ New(s).FillMap(out)
+}
+
+// Values converts the given struct to a []interface{}. For more info refer to
+// Struct types Values() method. It panics if s's kind is not struct.
+func Values(s interface{}) []interface{} {
+ return New(s).Values()
+}
+
+// Fields returns a slice of *Field. For more info refer to Struct types
+// Fields() method. It panics if s's kind is not struct.
+func Fields(s interface{}) []*Field {
+ return New(s).Fields()
+}
+
+// Names returns a slice of field names. For more info refer to Struct types
+// Names() method. It panics if s's kind is not struct.
+func Names(s interface{}) []string {
+ return New(s).Names()
+}
+
+// IsZero returns true if all fields is equal to a zero value. For more info
+// refer to Struct types IsZero() method. It panics if s's kind is not struct.
+func IsZero(s interface{}) bool {
+ return New(s).IsZero()
+}
+
+// HasZero returns true if any field is equal to a zero value. For more info
+// refer to Struct types HasZero() method. It panics if s's kind is not struct.
+func HasZero(s interface{}) bool {
+ return New(s).HasZero()
+}
+
+// IsStruct returns true if the given variable is a struct or a pointer to
+// struct.
+func IsStruct(s interface{}) bool {
+ v := reflect.ValueOf(s)
+ if v.Kind() == reflect.Ptr {
+ v = v.Elem()
+ }
+
+ // uninitialized zero value of a struct
+ if v.Kind() == reflect.Invalid {
+ return false
+ }
+
+ return v.Kind() == reflect.Struct
+}
+
+// Name returns the structs's type name within its package. It returns an
+// empty string for unnamed types. It panics if s's kind is not struct.
+func Name(s interface{}) string {
+ return New(s).Name()
+}
+
+// nested retrieves recursively all types for the given value and returns the
+// nested value.
+func (s *Struct) nested(val reflect.Value) interface{} {
+ var finalVal interface{}
+
+ v := reflect.ValueOf(val.Interface())
+ if v.Kind() == reflect.Ptr {
+ v = v.Elem()
+ }
+
+ switch v.Kind() {
+ case reflect.Struct:
+ n := New(val.Interface())
+ n.TagName = s.TagName
+ m := n.Map()
+
+ // do not add the converted value if there are no exported fields, ie:
+ // time.Time
+ if len(m) == 0 {
+ finalVal = val.Interface()
+ } else {
+ finalVal = m
+ }
+ case reflect.Map:
+ // get the element type of the map
+ mapElem := val.Type()
+ switch val.Type().Kind() {
+ case reflect.Ptr, reflect.Array, reflect.Map,
+ reflect.Slice, reflect.Chan:
+ mapElem = val.Type().Elem()
+ if mapElem.Kind() == reflect.Ptr {
+ mapElem = mapElem.Elem()
+ }
+ }
+
+ // only iterate over struct types, ie: map[string]StructType,
+ // map[string][]StructType,
+ if mapElem.Kind() == reflect.Struct ||
+ (mapElem.Kind() == reflect.Slice &&
+ mapElem.Elem().Kind() == reflect.Struct) {
+ m := make(map[string]interface{}, val.Len())
+ for _, k := range val.MapKeys() {
+ m[k.String()] = s.nested(val.MapIndex(k))
+ }
+ finalVal = m
+ break
+ }
+
+ // TODO(arslan): should this be optional?
+ finalVal = val.Interface()
+ case reflect.Slice, reflect.Array:
+ if val.Type().Kind() == reflect.Interface {
+ finalVal = val.Interface()
+ break
+ }
+
+ // TODO(arslan): should this be optional?
+ // do not iterate of non struct types, just pass the value. Ie: []int,
+ // []string, co... We only iterate further if it's a struct.
+ // i.e []foo or []*foo
+ if val.Type().Elem().Kind() != reflect.Struct &&
+ !(val.Type().Elem().Kind() == reflect.Ptr &&
+ val.Type().Elem().Elem().Kind() == reflect.Struct) {
+ finalVal = val.Interface()
+ break
+ }
+
+ slices := make([]interface{}, val.Len())
+ for x := 0; x < val.Len(); x++ {
+ slices[x] = s.nested(val.Index(x))
+ }
+ finalVal = slices
+ default:
+ finalVal = val.Interface()
+ }
+
+ return finalVal
+}
diff --git a/vendor/github.com/fatih/structs/tags.go b/vendor/github.com/fatih/structs/tags.go
new file mode 100644
index 00000000..136a31eb
--- /dev/null
+++ b/vendor/github.com/fatih/structs/tags.go
@@ -0,0 +1,32 @@
+package structs
+
+import "strings"
+
+// tagOptions contains a slice of tag options
+type tagOptions []string
+
+// Has returns true if the given option is available in tagOptions
+func (t tagOptions) Has(opt string) bool {
+ for _, tagOpt := range t {
+ if tagOpt == opt {
+ return true
+ }
+ }
+
+ return false
+}
+
+// parseTag splits a struct field's tag into its name and a list of options
+// which comes after a name. A tag is in the form of: "name,option1,option2".
+// The name can be neglectected.
+func parseTag(tag string) (string, tagOptions) {
+ // tag is one of followings:
+ // ""
+ // "name"
+ // "name,opt"
+ // "name,opt,opt2"
+ // ",opt"
+
+ res := strings.Split(tag, ",")
+ return res[0], res[1:]
+}
diff --git a/vendor/github.com/kolo/xmlrpc/LICENSE b/vendor/github.com/kolo/xmlrpc/LICENSE
new file mode 100644
index 00000000..8103dd13
--- /dev/null
+++ b/vendor/github.com/kolo/xmlrpc/LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2012 Dmitry Maksimov
+
+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/kolo/xmlrpc/client.go b/vendor/github.com/kolo/xmlrpc/client.go
new file mode 100644
index 00000000..db2eb4f6
--- /dev/null
+++ b/vendor/github.com/kolo/xmlrpc/client.go
@@ -0,0 +1,169 @@
+package xmlrpc
+
+import (
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/http/cookiejar"
+ "net/rpc"
+ "net/url"
+ "sync"
+)
+
+type Client struct {
+ *rpc.Client
+}
+
+// clientCodec is rpc.ClientCodec interface implementation.
+type clientCodec struct {
+ // url presents url of xmlrpc service
+ url *url.URL
+
+ // httpClient works with HTTP protocol
+ httpClient *http.Client
+
+ // cookies stores cookies received on last request
+ cookies http.CookieJar
+
+ // responses presents map of active requests. It is required to return request id, that
+ // rpc.Client can mark them as done.
+ responses map[uint64]*http.Response
+ mutex sync.Mutex
+
+ response *Response
+
+ // ready presents channel, that is used to link request and it`s response.
+ ready chan uint64
+
+ // close notifies codec is closed.
+ close chan uint64
+}
+
+func (codec *clientCodec) WriteRequest(request *rpc.Request, args interface{}) (err error) {
+ httpRequest, err := NewRequest(codec.url.String(), request.ServiceMethod, args)
+
+ if codec.cookies != nil {
+ for _, cookie := range codec.cookies.Cookies(codec.url) {
+ httpRequest.AddCookie(cookie)
+ }
+ }
+
+ if err != nil {
+ return err
+ }
+
+ var httpResponse *http.Response
+ httpResponse, err = codec.httpClient.Do(httpRequest)
+
+ if err != nil {
+ return err
+ }
+
+ if codec.cookies != nil {
+ codec.cookies.SetCookies(codec.url, httpResponse.Cookies())
+ }
+
+ codec.mutex.Lock()
+ codec.responses[request.Seq] = httpResponse
+ codec.mutex.Unlock()
+
+ codec.ready <- request.Seq
+
+ return nil
+}
+
+func (codec *clientCodec) ReadResponseHeader(response *rpc.Response) (err error) {
+ var seq uint64
+
+ select {
+ case seq = <-codec.ready:
+ case <-codec.close:
+ return errors.New("codec is closed")
+ }
+
+ codec.mutex.Lock()
+ httpResponse := codec.responses[seq]
+ codec.mutex.Unlock()
+
+ if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 300 {
+ return fmt.Errorf("request error: bad status code - %d", httpResponse.StatusCode)
+ }
+
+ respData, err := ioutil.ReadAll(httpResponse.Body)
+
+ if err != nil {
+ return err
+ }
+
+ httpResponse.Body.Close()
+
+ resp := NewResponse(respData)
+
+ if resp.Failed() {
+ response.Error = fmt.Sprintf("%v", resp.Err())
+ }
+
+ codec.response = resp
+
+ response.Seq = seq
+
+ codec.mutex.Lock()
+ delete(codec.responses, seq)
+ codec.mutex.Unlock()
+
+ return nil
+}
+
+func (codec *clientCodec) ReadResponseBody(v interface{}) (err error) {
+ if v == nil {
+ return nil
+ }
+
+ if err = codec.response.Unmarshal(v); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (codec *clientCodec) Close() error {
+ transport := codec.httpClient.Transport.(*http.Transport)
+ transport.CloseIdleConnections()
+
+ close(codec.close)
+
+ return nil
+}
+
+// NewClient returns instance of rpc.Client object, that is used to send request to xmlrpc service.
+func NewClient(requrl string, transport http.RoundTripper) (*Client, error) {
+ if transport == nil {
+ transport = http.DefaultTransport
+ }
+
+ httpClient := &http.Client{Transport: transport}
+
+ jar, err := cookiejar.New(nil)
+
+ if err != nil {
+ return nil, err
+ }
+
+ u, err := url.Parse(requrl)
+
+ if err != nil {
+ return nil, err
+ }
+
+ codec := clientCodec{
+ url: u,
+ httpClient: httpClient,
+ close: make(chan uint64),
+ ready: make(chan uint64),
+ responses: make(map[uint64]*http.Response),
+ cookies: jar,
+ }
+
+ return &Client{rpc.NewClientWithCodec(&codec)}, nil
+}
diff --git a/vendor/github.com/kolo/xmlrpc/decoder.go b/vendor/github.com/kolo/xmlrpc/decoder.go
new file mode 100644
index 00000000..b81fff4c
--- /dev/null
+++ b/vendor/github.com/kolo/xmlrpc/decoder.go
@@ -0,0 +1,463 @@
+package xmlrpc
+
+import (
+ "bytes"
+ "encoding/xml"
+ "errors"
+ "fmt"
+ "io"
+ "reflect"
+ "strconv"
+ "strings"
+ "time"
+)
+
+const (
+ iso8601 = "20060102T15:04:05"
+ iso8601Z = "20060102T15:04:05Z07:00"
+ iso8601Hyphen = "2006-01-02T15:04:05"
+ iso8601HyphenZ = "2006-01-02T15:04:05Z07:00"
+)
+
+var (
+ // CharsetReader is a function to generate reader which converts a non UTF-8
+ // charset into UTF-8.
+ CharsetReader func(string, io.Reader) (io.Reader, error)
+
+ timeLayouts = []string{iso8601, iso8601Z, iso8601Hyphen, iso8601HyphenZ}
+ invalidXmlError = errors.New("invalid xml")
+)
+
+type TypeMismatchError string
+
+func (e TypeMismatchError) Error() string { return string(e) }
+
+type decoder struct {
+ *xml.Decoder
+}
+
+func unmarshal(data []byte, v interface{}) (err error) {
+ dec := &decoder{xml.NewDecoder(bytes.NewBuffer(data))}
+
+ if CharsetReader != nil {
+ dec.CharsetReader = CharsetReader
+ }
+
+ var tok xml.Token
+ for {
+ if tok, err = dec.Token(); err != nil {
+ return err
+ }
+
+ if t, ok := tok.(xml.StartElement); ok {
+ if t.Name.Local == "value" {
+ val := reflect.ValueOf(v)
+ if val.Kind() != reflect.Ptr {
+ return errors.New("non-pointer value passed to unmarshal")
+ }
+ if err = dec.decodeValue(val.Elem()); err != nil {
+ return err
+ }
+
+ break
+ }
+ }
+ }
+
+ // read until end of document
+ err = dec.Skip()
+ if err != nil && err != io.EOF {
+ return err
+ }
+
+ return nil
+}
+
+func (dec *decoder) decodeValue(val reflect.Value) error {
+ var tok xml.Token
+ var err error
+
+ if val.Kind() == reflect.Ptr {
+ if val.IsNil() {
+ val.Set(reflect.New(val.Type().Elem()))
+ }
+ val = val.Elem()
+ }
+
+ var typeName string
+ for {
+ if tok, err = dec.Token(); err != nil {
+ return err
+ }
+
+ if t, ok := tok.(xml.EndElement); ok {
+ if t.Name.Local == "value" {
+ return nil
+ } else {
+ return invalidXmlError
+ }
+ }
+
+ if t, ok := tok.(xml.StartElement); ok {
+ typeName = t.Name.Local
+ break
+ }
+
+ // Treat value data without type identifier as string
+ if t, ok := tok.(xml.CharData); ok {
+ if value := strings.TrimSpace(string(t)); value != "" {
+ if err = checkType(val, reflect.String); err != nil {
+ return err
+ }
+
+ val.SetString(value)
+ return nil
+ }
+ }
+ }
+
+ switch typeName {
+ case "struct":
+ ismap := false
+ pmap := val
+ valType := val.Type()
+
+ if err = checkType(val, reflect.Struct); err != nil {
+ if checkType(val, reflect.Map) == nil {
+ if valType.Key().Kind() != reflect.String {
+ return fmt.Errorf("only maps with string key type can be unmarshalled")
+ }
+ ismap = true
+ } else if checkType(val, reflect.Interface) == nil && val.IsNil() {
+ var dummy map[string]interface{}
+ pmap = reflect.New(reflect.TypeOf(dummy)).Elem()
+ valType = pmap.Type()
+ ismap = true
+ } else {
+ return err
+ }
+ }
+
+ var fields map[string]reflect.Value
+
+ if !ismap {
+ fields = make(map[string]reflect.Value)
+
+ for i := 0; i < valType.NumField(); i++ {
+ field := valType.Field(i)
+ fieldVal := val.FieldByName(field.Name)
+
+ if fieldVal.CanSet() {
+ if fn := field.Tag.Get("xmlrpc"); fn != "" {
+ fields[fn] = fieldVal
+ } else {
+ fields[field.Name] = fieldVal
+ }
+ }
+ }
+ } else {
+ // Create initial empty map
+ pmap.Set(reflect.MakeMap(valType))
+ }
+
+ // Process struct members.
+ StructLoop:
+ for {
+ if tok, err = dec.Token(); err != nil {
+ return err
+ }
+ switch t := tok.(type) {
+ case xml.StartElement:
+ if t.Name.Local != "member" {
+ return invalidXmlError
+ }
+
+ tagName, fieldName, err := dec.readTag()
+ if err != nil {
+ return err
+ }
+ if tagName != "name" {
+ return invalidXmlError
+ }
+
+ var fv reflect.Value
+ ok := true
+
+ if !ismap {
+ fv, ok = fields[string(fieldName)]
+ } else {
+ fv = reflect.New(valType.Elem())
+ }
+
+ if ok {
+ for {
+ if tok, err = dec.Token(); err != nil {
+ return err
+ }
+ if t, ok := tok.(xml.StartElement); ok && t.Name.Local == "value" {
+ if err = dec.decodeValue(fv); err != nil {
+ return err
+ }
+
+ //
+ if err = dec.Skip(); err != nil {
+ return err
+ }
+
+ break
+ }
+ }
+ }
+
+ //
+ if err = dec.Skip(); err != nil {
+ return err
+ }
+
+ if ismap {
+ pmap.SetMapIndex(reflect.ValueOf(string(fieldName)), reflect.Indirect(fv))
+ val.Set(pmap)
+ }
+ case xml.EndElement:
+ break StructLoop
+ }
+ }
+ case "array":
+ pslice := val
+ if checkType(val, reflect.Interface) == nil && val.IsNil() {
+ var dummy []interface{}
+ pslice = reflect.New(reflect.TypeOf(dummy)).Elem()
+ } else if err = checkType(val, reflect.Slice); err != nil {
+ return err
+ }
+
+ ArrayLoop:
+ for {
+ if tok, err = dec.Token(); err != nil {
+ return err
+ }
+
+ switch t := tok.(type) {
+ case xml.StartElement:
+ if t.Name.Local != "data" {
+ return invalidXmlError
+ }
+
+ slice := reflect.MakeSlice(pslice.Type(), 0, 0)
+
+ DataLoop:
+ for {
+ if tok, err = dec.Token(); err != nil {
+ return err
+ }
+
+ switch tt := tok.(type) {
+ case xml.StartElement:
+ if tt.Name.Local != "value" {
+ return invalidXmlError
+ }
+
+ v := reflect.New(pslice.Type().Elem())
+ if err = dec.decodeValue(v); err != nil {
+ return err
+ }
+
+ slice = reflect.Append(slice, v.Elem())
+
+ //
+ if err = dec.Skip(); err != nil {
+ return err
+ }
+ case xml.EndElement:
+ pslice.Set(slice)
+ val.Set(pslice)
+ break DataLoop
+ }
+ }
+ case xml.EndElement:
+ break ArrayLoop
+ }
+ }
+ default:
+ if tok, err = dec.Token(); err != nil {
+ return err
+ }
+
+ var data []byte
+
+ switch t := tok.(type) {
+ case xml.EndElement:
+ return nil
+ case xml.CharData:
+ data = []byte(t.Copy())
+ default:
+ return invalidXmlError
+ }
+
+ switch typeName {
+ case "int", "i4", "i8":
+ if checkType(val, reflect.Interface) == nil && val.IsNil() {
+ i, err := strconv.ParseInt(string(data), 10, 64)
+ if err != nil {
+ return err
+ }
+
+ pi := reflect.New(reflect.TypeOf(i)).Elem()
+ pi.SetInt(i)
+ val.Set(pi)
+ } else if err = checkType(val, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64); err != nil {
+ return err
+ } else {
+ i, err := strconv.ParseInt(string(data), 10, val.Type().Bits())
+ if err != nil {
+ return err
+ }
+
+ val.SetInt(i)
+ }
+ case "string", "base64":
+ str := string(data)
+ if checkType(val, reflect.Interface) == nil && val.IsNil() {
+ pstr := reflect.New(reflect.TypeOf(str)).Elem()
+ pstr.SetString(str)
+ val.Set(pstr)
+ } else if err = checkType(val, reflect.String); err != nil {
+ return err
+ } else {
+ val.SetString(str)
+ }
+ case "dateTime.iso8601":
+ var t time.Time
+ var err error
+
+ for _, layout := range timeLayouts {
+ t, err = time.Parse(layout, string(data))
+ if err == nil {
+ break
+ }
+ }
+ if err != nil {
+ return err
+ }
+
+ if checkType(val, reflect.Interface) == nil && val.IsNil() {
+ ptime := reflect.New(reflect.TypeOf(t)).Elem()
+ ptime.Set(reflect.ValueOf(t))
+ val.Set(ptime)
+ } else if _, ok := val.Interface().(time.Time); !ok {
+ return TypeMismatchError(fmt.Sprintf("error: type mismatch error - can't decode %v to time", val.Kind()))
+ } else {
+ val.Set(reflect.ValueOf(t))
+ }
+ case "boolean":
+ v, err := strconv.ParseBool(string(data))
+ if err != nil {
+ return err
+ }
+
+ if checkType(val, reflect.Interface) == nil && val.IsNil() {
+ pv := reflect.New(reflect.TypeOf(v)).Elem()
+ pv.SetBool(v)
+ val.Set(pv)
+ } else if err = checkType(val, reflect.Bool); err != nil {
+ return err
+ } else {
+ val.SetBool(v)
+ }
+ case "double":
+ if checkType(val, reflect.Interface) == nil && val.IsNil() {
+ i, err := strconv.ParseFloat(string(data), 64)
+ if err != nil {
+ return err
+ }
+
+ pdouble := reflect.New(reflect.TypeOf(i)).Elem()
+ pdouble.SetFloat(i)
+ val.Set(pdouble)
+ } else if err = checkType(val, reflect.Float32, reflect.Float64); err != nil {
+ return err
+ } else {
+ i, err := strconv.ParseFloat(string(data), val.Type().Bits())
+ if err != nil {
+ return err
+ }
+
+ val.SetFloat(i)
+ }
+ default:
+ return errors.New("unsupported type")
+ }
+
+ //
+ if err = dec.Skip(); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (dec *decoder) readTag() (string, []byte, error) {
+ var tok xml.Token
+ var err error
+
+ var name string
+ for {
+ if tok, err = dec.Token(); err != nil {
+ return "", nil, err
+ }
+
+ if t, ok := tok.(xml.StartElement); ok {
+ name = t.Name.Local
+ break
+ }
+ }
+
+ value, err := dec.readCharData()
+ if err != nil {
+ return "", nil, err
+ }
+
+ return name, value, dec.Skip()
+}
+
+func (dec *decoder) readCharData() ([]byte, error) {
+ var tok xml.Token
+ var err error
+
+ if tok, err = dec.Token(); err != nil {
+ return nil, err
+ }
+
+ if t, ok := tok.(xml.CharData); ok {
+ return []byte(t.Copy()), nil
+ } else {
+ return nil, invalidXmlError
+ }
+}
+
+func checkType(val reflect.Value, kinds ...reflect.Kind) error {
+ if len(kinds) == 0 {
+ return nil
+ }
+
+ if val.Kind() == reflect.Ptr {
+ val = val.Elem()
+ }
+
+ match := false
+
+ for _, kind := range kinds {
+ if val.Kind() == kind {
+ match = true
+ break
+ }
+ }
+
+ if !match {
+ return TypeMismatchError(fmt.Sprintf("error: type mismatch - can't unmarshal %v to %v",
+ val.Kind(), kinds[0]))
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/kolo/xmlrpc/encoder.go b/vendor/github.com/kolo/xmlrpc/encoder.go
new file mode 100644
index 00000000..bb1285ff
--- /dev/null
+++ b/vendor/github.com/kolo/xmlrpc/encoder.go
@@ -0,0 +1,164 @@
+package xmlrpc
+
+import (
+ "bytes"
+ "encoding/xml"
+ "fmt"
+ "reflect"
+ "strconv"
+ "time"
+)
+
+type encodeFunc func(reflect.Value) ([]byte, error)
+
+func marshal(v interface{}) ([]byte, error) {
+ if v == nil {
+ return []byte{}, nil
+ }
+
+ val := reflect.ValueOf(v)
+ return encodeValue(val)
+}
+
+func encodeValue(val reflect.Value) ([]byte, error) {
+ var b []byte
+ var err error
+
+ if val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface {
+ if val.IsNil() {
+ return []byte(""), nil
+ }
+
+ val = val.Elem()
+ }
+
+ switch val.Kind() {
+ case reflect.Struct:
+ switch val.Interface().(type) {
+ case time.Time:
+ t := val.Interface().(time.Time)
+ b = []byte(fmt.Sprintf("%s", t.Format(iso8601)))
+ default:
+ b, err = encodeStruct(val)
+ }
+ case reflect.Map:
+ b, err = encodeMap(val)
+ case reflect.Slice:
+ b, err = encodeSlice(val)
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ b = []byte(fmt.Sprintf("%s", strconv.FormatInt(val.Int(), 10)))
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ b = []byte(fmt.Sprintf("%s", strconv.FormatUint(val.Uint(), 10)))
+ case reflect.Float32, reflect.Float64:
+ b = []byte(fmt.Sprintf("%s",
+ strconv.FormatFloat(val.Float(), 'g', -1, val.Type().Bits())))
+ case reflect.Bool:
+ if val.Bool() {
+ b = []byte("1")
+ } else {
+ b = []byte("0")
+ }
+ case reflect.String:
+ var buf bytes.Buffer
+
+ xml.Escape(&buf, []byte(val.String()))
+
+ if _, ok := val.Interface().(Base64); ok {
+ b = []byte(fmt.Sprintf("%s", buf.String()))
+ } else {
+ b = []byte(fmt.Sprintf("%s", buf.String()))
+ }
+ default:
+ return nil, fmt.Errorf("xmlrpc encode error: unsupported type")
+ }
+
+ if err != nil {
+ return nil, err
+ }
+
+ return []byte(fmt.Sprintf("%s", string(b))), nil
+}
+
+func encodeStruct(val reflect.Value) ([]byte, error) {
+ var b bytes.Buffer
+
+ b.WriteString("")
+
+ t := val.Type()
+ for i := 0; i < t.NumField(); i++ {
+ b.WriteString("")
+ f := t.Field(i)
+
+ name := f.Tag.Get("xmlrpc")
+ if name == "" {
+ name = f.Name
+ }
+ b.WriteString(fmt.Sprintf("%s", name))
+
+ p, err := encodeValue(val.FieldByName(f.Name))
+ if err != nil {
+ return nil, err
+ }
+ b.Write(p)
+
+ b.WriteString("")
+ }
+
+ b.WriteString("")
+
+ return b.Bytes(), nil
+}
+
+func encodeMap(val reflect.Value) ([]byte, error) {
+ var t = val.Type()
+
+ if t.Key().Kind() != reflect.String {
+ return nil, fmt.Errorf("xmlrpc encode error: only maps with string keys are supported")
+ }
+
+ var b bytes.Buffer
+
+ b.WriteString("")
+
+ keys := val.MapKeys()
+
+ for i := 0; i < val.Len(); i++ {
+ key := keys[i]
+ kval := val.MapIndex(key)
+
+ b.WriteString("")
+ b.WriteString(fmt.Sprintf("%s", key.String()))
+
+ p, err := encodeValue(kval)
+
+ if err != nil {
+ return nil, err
+ }
+
+ b.Write(p)
+ b.WriteString("")
+ }
+
+ b.WriteString("")
+
+ return b.Bytes(), nil
+}
+
+func encodeSlice(val reflect.Value) ([]byte, error) {
+ var b bytes.Buffer
+
+ b.WriteString("")
+
+ for i := 0; i < val.Len(); i++ {
+ p, err := encodeValue(val.Index(i))
+ if err != nil {
+ return nil, err
+ }
+
+ b.Write(p)
+ }
+
+ b.WriteString("")
+
+ return b.Bytes(), nil
+}
diff --git a/vendor/github.com/kolo/xmlrpc/request.go b/vendor/github.com/kolo/xmlrpc/request.go
new file mode 100644
index 00000000..acb8251b
--- /dev/null
+++ b/vendor/github.com/kolo/xmlrpc/request.go
@@ -0,0 +1,57 @@
+package xmlrpc
+
+import (
+ "bytes"
+ "fmt"
+ "net/http"
+)
+
+func NewRequest(url string, method string, args interface{}) (*http.Request, error) {
+ var t []interface{}
+ var ok bool
+ if t, ok = args.([]interface{}); !ok {
+ if args != nil {
+ t = []interface{}{args}
+ }
+ }
+
+ body, err := EncodeMethodCall(method, t...)
+ if err != nil {
+ return nil, err
+ }
+
+ request, err := http.NewRequest("POST", url, bytes.NewReader(body))
+ if err != nil {
+ return nil, err
+ }
+
+ request.Header.Set("Content-Type", "text/xml")
+ request.Header.Set("Content-Length", fmt.Sprintf("%d", len(body)))
+
+ return request, nil
+}
+
+func EncodeMethodCall(method string, args ...interface{}) ([]byte, error) {
+ var b bytes.Buffer
+ b.WriteString(``)
+ b.WriteString(fmt.Sprintf("%s", method))
+
+ if args != nil {
+ b.WriteString("")
+
+ for _, arg := range args {
+ p, err := marshal(arg)
+ if err != nil {
+ return nil, err
+ }
+
+ b.WriteString(fmt.Sprintf("%s", string(p)))
+ }
+
+ b.WriteString("")
+ }
+
+ b.WriteString("")
+
+ return b.Bytes(), nil
+}
diff --git a/vendor/github.com/kolo/xmlrpc/response.go b/vendor/github.com/kolo/xmlrpc/response.go
new file mode 100644
index 00000000..6742a1c7
--- /dev/null
+++ b/vendor/github.com/kolo/xmlrpc/response.go
@@ -0,0 +1,52 @@
+package xmlrpc
+
+import (
+ "regexp"
+)
+
+var (
+ faultRx = regexp.MustCompile(`(\s|\S)+`)
+)
+
+type failedResponse struct {
+ Code int `xmlrpc:"faultCode"`
+ Error string `xmlrpc:"faultString"`
+}
+
+func (r *failedResponse) err() error {
+ return &xmlrpcError{
+ code: r.Code,
+ err: r.Error,
+ }
+}
+
+type Response struct {
+ data []byte
+}
+
+func NewResponse(data []byte) *Response {
+ return &Response{
+ data: data,
+ }
+}
+
+func (r *Response) Failed() bool {
+ return faultRx.Match(r.data)
+}
+
+func (r *Response) Err() error {
+ failedResp := new(failedResponse)
+ if err := unmarshal(r.data, failedResp); err != nil {
+ return err
+ }
+
+ return failedResp.err()
+}
+
+func (r *Response) Unmarshal(v interface{}) error {
+ if err := unmarshal(r.data, v); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/kolo/xmlrpc/xmlrpc.go b/vendor/github.com/kolo/xmlrpc/xmlrpc.go
new file mode 100644
index 00000000..8766403a
--- /dev/null
+++ b/vendor/github.com/kolo/xmlrpc/xmlrpc.go
@@ -0,0 +1,19 @@
+package xmlrpc
+
+import (
+ "fmt"
+)
+
+// xmlrpcError represents errors returned on xmlrpc request.
+type xmlrpcError struct {
+ code int
+ err string
+}
+
+// Error() method implements Error interface
+func (e *xmlrpcError) Error() string {
+ return fmt.Sprintf("error: \"%s\" code: %d", e.err, e.code)
+}
+
+// Base64 represents value in base64 encoding
+type Base64 string
diff --git a/vendor/github.com/mitchellh/mapstructure/LICENSE b/vendor/github.com/mitchellh/mapstructure/LICENSE
new file mode 100644
index 00000000..f9c841a5
--- /dev/null
+++ b/vendor/github.com/mitchellh/mapstructure/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 Mitchell Hashimoto
+
+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/mitchellh/mapstructure/decode_hooks.go b/vendor/github.com/mitchellh/mapstructure/decode_hooks.go
new file mode 100644
index 00000000..1f0abc65
--- /dev/null
+++ b/vendor/github.com/mitchellh/mapstructure/decode_hooks.go
@@ -0,0 +1,217 @@
+package mapstructure
+
+import (
+ "errors"
+ "fmt"
+ "net"
+ "reflect"
+ "strconv"
+ "strings"
+ "time"
+)
+
+// typedDecodeHook takes a raw DecodeHookFunc (an interface{}) and turns
+// it into the proper DecodeHookFunc type, such as DecodeHookFuncType.
+func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc {
+ // Create variables here so we can reference them with the reflect pkg
+ var f1 DecodeHookFuncType
+ var f2 DecodeHookFuncKind
+
+ // Fill in the variables into this interface and the rest is done
+ // automatically using the reflect package.
+ potential := []interface{}{f1, f2}
+
+ v := reflect.ValueOf(h)
+ vt := v.Type()
+ for _, raw := range potential {
+ pt := reflect.ValueOf(raw).Type()
+ if vt.ConvertibleTo(pt) {
+ return v.Convert(pt).Interface()
+ }
+ }
+
+ return nil
+}
+
+// DecodeHookExec executes the given decode hook. This should be used
+// since it'll naturally degrade to the older backwards compatible DecodeHookFunc
+// that took reflect.Kind instead of reflect.Type.
+func DecodeHookExec(
+ raw DecodeHookFunc,
+ from reflect.Type, to reflect.Type,
+ data interface{}) (interface{}, error) {
+ switch f := typedDecodeHook(raw).(type) {
+ case DecodeHookFuncType:
+ return f(from, to, data)
+ case DecodeHookFuncKind:
+ return f(from.Kind(), to.Kind(), data)
+ default:
+ return nil, errors.New("invalid decode hook signature")
+ }
+}
+
+// ComposeDecodeHookFunc creates a single DecodeHookFunc that
+// automatically composes multiple DecodeHookFuncs.
+//
+// The composed funcs are called in order, with the result of the
+// previous transformation.
+func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc {
+ return func(
+ f reflect.Type,
+ t reflect.Type,
+ data interface{}) (interface{}, error) {
+ var err error
+ for _, f1 := range fs {
+ data, err = DecodeHookExec(f1, f, t, data)
+ if err != nil {
+ return nil, err
+ }
+
+ // Modify the from kind to be correct with the new data
+ f = nil
+ if val := reflect.ValueOf(data); val.IsValid() {
+ f = val.Type()
+ }
+ }
+
+ return data, nil
+ }
+}
+
+// StringToSliceHookFunc returns a DecodeHookFunc that converts
+// string to []string by splitting on the given sep.
+func StringToSliceHookFunc(sep string) DecodeHookFunc {
+ return func(
+ f reflect.Kind,
+ t reflect.Kind,
+ data interface{}) (interface{}, error) {
+ if f != reflect.String || t != reflect.Slice {
+ return data, nil
+ }
+
+ raw := data.(string)
+ if raw == "" {
+ return []string{}, nil
+ }
+
+ return strings.Split(raw, sep), nil
+ }
+}
+
+// StringToTimeDurationHookFunc returns a DecodeHookFunc that converts
+// strings to time.Duration.
+func StringToTimeDurationHookFunc() DecodeHookFunc {
+ return func(
+ f reflect.Type,
+ t reflect.Type,
+ data interface{}) (interface{}, error) {
+ if f.Kind() != reflect.String {
+ return data, nil
+ }
+ if t != reflect.TypeOf(time.Duration(5)) {
+ return data, nil
+ }
+
+ // Convert it by parsing
+ return time.ParseDuration(data.(string))
+ }
+}
+
+// StringToIPHookFunc returns a DecodeHookFunc that converts
+// strings to net.IP
+func StringToIPHookFunc() DecodeHookFunc {
+ return func(
+ f reflect.Type,
+ t reflect.Type,
+ data interface{}) (interface{}, error) {
+ if f.Kind() != reflect.String {
+ return data, nil
+ }
+ if t != reflect.TypeOf(net.IP{}) {
+ return data, nil
+ }
+
+ // Convert it by parsing
+ ip := net.ParseIP(data.(string))
+ if ip == nil {
+ return net.IP{}, fmt.Errorf("failed parsing ip %v", data)
+ }
+
+ return ip, nil
+ }
+}
+
+// StringToIPNetHookFunc returns a DecodeHookFunc that converts
+// strings to net.IPNet
+func StringToIPNetHookFunc() DecodeHookFunc {
+ return func(
+ f reflect.Type,
+ t reflect.Type,
+ data interface{}) (interface{}, error) {
+ if f.Kind() != reflect.String {
+ return data, nil
+ }
+ if t != reflect.TypeOf(net.IPNet{}) {
+ return data, nil
+ }
+
+ // Convert it by parsing
+ _, net, err := net.ParseCIDR(data.(string))
+ return net, err
+ }
+}
+
+// StringToTimeHookFunc returns a DecodeHookFunc that converts
+// strings to time.Time.
+func StringToTimeHookFunc(layout string) DecodeHookFunc {
+ return func(
+ f reflect.Type,
+ t reflect.Type,
+ data interface{}) (interface{}, error) {
+ if f.Kind() != reflect.String {
+ return data, nil
+ }
+ if t != reflect.TypeOf(time.Time{}) {
+ return data, nil
+ }
+
+ // Convert it by parsing
+ return time.Parse(layout, data.(string))
+ }
+}
+
+// WeaklyTypedHook is a DecodeHookFunc which adds support for weak typing to
+// the decoder.
+//
+// Note that this is significantly different from the WeaklyTypedInput option
+// of the DecoderConfig.
+func WeaklyTypedHook(
+ f reflect.Kind,
+ t reflect.Kind,
+ data interface{}) (interface{}, error) {
+ dataVal := reflect.ValueOf(data)
+ switch t {
+ case reflect.String:
+ switch f {
+ case reflect.Bool:
+ if dataVal.Bool() {
+ return "1", nil
+ }
+ return "0", nil
+ case reflect.Float32:
+ return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil
+ case reflect.Int:
+ return strconv.FormatInt(dataVal.Int(), 10), nil
+ case reflect.Slice:
+ dataType := dataVal.Type()
+ elemKind := dataType.Elem().Kind()
+ if elemKind == reflect.Uint8 {
+ return string(dataVal.Interface().([]uint8)), nil
+ }
+ case reflect.Uint:
+ return strconv.FormatUint(dataVal.Uint(), 10), nil
+ }
+ }
+
+ return data, nil
+}
diff --git a/vendor/github.com/mitchellh/mapstructure/error.go b/vendor/github.com/mitchellh/mapstructure/error.go
new file mode 100644
index 00000000..47a99e5a
--- /dev/null
+++ b/vendor/github.com/mitchellh/mapstructure/error.go
@@ -0,0 +1,50 @@
+package mapstructure
+
+import (
+ "errors"
+ "fmt"
+ "sort"
+ "strings"
+)
+
+// Error implements the error interface and can represents multiple
+// errors that occur in the course of a single decode.
+type Error struct {
+ Errors []string
+}
+
+func (e *Error) Error() string {
+ points := make([]string, len(e.Errors))
+ for i, err := range e.Errors {
+ points[i] = fmt.Sprintf("* %s", err)
+ }
+
+ sort.Strings(points)
+ return fmt.Sprintf(
+ "%d error(s) decoding:\n\n%s",
+ len(e.Errors), strings.Join(points, "\n"))
+}
+
+// WrappedErrors implements the errwrap.Wrapper interface to make this
+// return value more useful with the errwrap and go-multierror libraries.
+func (e *Error) WrappedErrors() []error {
+ if e == nil {
+ return nil
+ }
+
+ result := make([]error, len(e.Errors))
+ for i, e := range e.Errors {
+ result[i] = errors.New(e)
+ }
+
+ return result
+}
+
+func appendErrors(errors []string, err error) []string {
+ switch e := err.(type) {
+ case *Error:
+ return append(errors, e.Errors...)
+ default:
+ return append(errors, e.Error())
+ }
+}
diff --git a/vendor/github.com/mitchellh/mapstructure/mapstructure.go b/vendor/github.com/mitchellh/mapstructure/mapstructure.go
new file mode 100644
index 00000000..256ee63f
--- /dev/null
+++ b/vendor/github.com/mitchellh/mapstructure/mapstructure.go
@@ -0,0 +1,1149 @@
+// Package mapstructure exposes functionality to convert an arbitrary
+// map[string]interface{} into a native Go structure.
+//
+// The Go structure can be arbitrarily complex, containing slices,
+// other structs, etc. and the decoder will properly decode nested
+// maps and so on into the proper structures in the native Go struct.
+// See the examples to see what the decoder is capable of.
+package mapstructure
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "reflect"
+ "sort"
+ "strconv"
+ "strings"
+)
+
+// DecodeHookFunc is the callback function that can be used for
+// data transformations. See "DecodeHook" in the DecoderConfig
+// struct.
+//
+// The type should be DecodeHookFuncType or DecodeHookFuncKind.
+// Either is accepted. Types are a superset of Kinds (Types can return
+// Kinds) and are generally a richer thing to use, but Kinds are simpler
+// if you only need those.
+//
+// The reason DecodeHookFunc is multi-typed is for backwards compatibility:
+// we started with Kinds and then realized Types were the better solution,
+// but have a promise to not break backwards compat so we now support
+// both.
+type DecodeHookFunc interface{}
+
+// DecodeHookFuncType is a DecodeHookFunc which has complete information about
+// the source and target types.
+type DecodeHookFuncType func(reflect.Type, reflect.Type, interface{}) (interface{}, error)
+
+// DecodeHookFuncKind is a DecodeHookFunc which knows only the Kinds of the
+// source and target types.
+type DecodeHookFuncKind func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error)
+
+// DecoderConfig is the configuration that is used to create a new decoder
+// and allows customization of various aspects of decoding.
+type DecoderConfig struct {
+ // DecodeHook, if set, will be called before any decoding and any
+ // type conversion (if WeaklyTypedInput is on). This lets you modify
+ // the values before they're set down onto the resulting struct.
+ //
+ // If an error is returned, the entire decode will fail with that
+ // error.
+ DecodeHook DecodeHookFunc
+
+ // If ErrorUnused is true, then it is an error for there to exist
+ // keys in the original map that were unused in the decoding process
+ // (extra keys).
+ ErrorUnused bool
+
+ // ZeroFields, if set to true, will zero fields before writing them.
+ // For example, a map will be emptied before decoded values are put in
+ // it. If this is false, a map will be merged.
+ ZeroFields bool
+
+ // If WeaklyTypedInput is true, the decoder will make the following
+ // "weak" conversions:
+ //
+ // - bools to string (true = "1", false = "0")
+ // - numbers to string (base 10)
+ // - bools to int/uint (true = 1, false = 0)
+ // - strings to int/uint (base implied by prefix)
+ // - int to bool (true if value != 0)
+ // - string to bool (accepts: 1, t, T, TRUE, true, True, 0, f, F,
+ // FALSE, false, False. Anything else is an error)
+ // - empty array = empty map and vice versa
+ // - negative numbers to overflowed uint values (base 10)
+ // - slice of maps to a merged map
+ // - single values are converted to slices if required. Each
+ // element is weakly decoded. For example: "4" can become []int{4}
+ // if the target type is an int slice.
+ //
+ WeaklyTypedInput bool
+
+ // Metadata is the struct that will contain extra metadata about
+ // the decoding. If this is nil, then no metadata will be tracked.
+ Metadata *Metadata
+
+ // Result is a pointer to the struct that will contain the decoded
+ // value.
+ Result interface{}
+
+ // The tag name that mapstructure reads for field names. This
+ // defaults to "mapstructure"
+ TagName string
+}
+
+// A Decoder takes a raw interface value and turns it into structured
+// data, keeping track of rich error information along the way in case
+// anything goes wrong. Unlike the basic top-level Decode method, you can
+// more finely control how the Decoder behaves using the DecoderConfig
+// structure. The top-level Decode method is just a convenience that sets
+// up the most basic Decoder.
+type Decoder struct {
+ config *DecoderConfig
+}
+
+// Metadata contains information about decoding a structure that
+// is tedious or difficult to get otherwise.
+type Metadata struct {
+ // Keys are the keys of the structure which were successfully decoded
+ Keys []string
+
+ // Unused is a slice of keys that were found in the raw value but
+ // weren't decoded since there was no matching field in the result interface
+ Unused []string
+}
+
+// Decode takes an input structure and uses reflection to translate it to
+// the output structure. output must be a pointer to a map or struct.
+func Decode(input interface{}, output interface{}) error {
+ config := &DecoderConfig{
+ Metadata: nil,
+ Result: output,
+ }
+
+ decoder, err := NewDecoder(config)
+ if err != nil {
+ return err
+ }
+
+ return decoder.Decode(input)
+}
+
+// WeakDecode is the same as Decode but is shorthand to enable
+// WeaklyTypedInput. See DecoderConfig for more info.
+func WeakDecode(input, output interface{}) error {
+ config := &DecoderConfig{
+ Metadata: nil,
+ Result: output,
+ WeaklyTypedInput: true,
+ }
+
+ decoder, err := NewDecoder(config)
+ if err != nil {
+ return err
+ }
+
+ return decoder.Decode(input)
+}
+
+// DecodeMetadata is the same as Decode, but is shorthand to
+// enable metadata collection. See DecoderConfig for more info.
+func DecodeMetadata(input interface{}, output interface{}, metadata *Metadata) error {
+ config := &DecoderConfig{
+ Metadata: metadata,
+ Result: output,
+ }
+
+ decoder, err := NewDecoder(config)
+ if err != nil {
+ return err
+ }
+
+ return decoder.Decode(input)
+}
+
+// WeakDecodeMetadata is the same as Decode, but is shorthand to
+// enable both WeaklyTypedInput and metadata collection. See
+// DecoderConfig for more info.
+func WeakDecodeMetadata(input interface{}, output interface{}, metadata *Metadata) error {
+ config := &DecoderConfig{
+ Metadata: metadata,
+ Result: output,
+ WeaklyTypedInput: true,
+ }
+
+ decoder, err := NewDecoder(config)
+ if err != nil {
+ return err
+ }
+
+ return decoder.Decode(input)
+}
+
+// NewDecoder returns a new decoder for the given configuration. Once
+// a decoder has been returned, the same configuration must not be used
+// again.
+func NewDecoder(config *DecoderConfig) (*Decoder, error) {
+ val := reflect.ValueOf(config.Result)
+ if val.Kind() != reflect.Ptr {
+ return nil, errors.New("result must be a pointer")
+ }
+
+ val = val.Elem()
+ if !val.CanAddr() {
+ return nil, errors.New("result must be addressable (a pointer)")
+ }
+
+ if config.Metadata != nil {
+ if config.Metadata.Keys == nil {
+ config.Metadata.Keys = make([]string, 0)
+ }
+
+ if config.Metadata.Unused == nil {
+ config.Metadata.Unused = make([]string, 0)
+ }
+ }
+
+ if config.TagName == "" {
+ config.TagName = "mapstructure"
+ }
+
+ result := &Decoder{
+ config: config,
+ }
+
+ return result, nil
+}
+
+// Decode decodes the given raw interface to the target pointer specified
+// by the configuration.
+func (d *Decoder) Decode(input interface{}) error {
+ return d.decode("", input, reflect.ValueOf(d.config.Result).Elem())
+}
+
+// Decodes an unknown data type into a specific reflection value.
+func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) error {
+ var inputVal reflect.Value
+ if input != nil {
+ inputVal = reflect.ValueOf(input)
+
+ // We need to check here if input is a typed nil. Typed nils won't
+ // match the "input == nil" below so we check that here.
+ if inputVal.Kind() == reflect.Ptr && inputVal.IsNil() {
+ input = nil
+ }
+ }
+
+ if input == nil {
+ // If the data is nil, then we don't set anything, unless ZeroFields is set
+ // to true.
+ if d.config.ZeroFields {
+ outVal.Set(reflect.Zero(outVal.Type()))
+
+ if d.config.Metadata != nil && name != "" {
+ d.config.Metadata.Keys = append(d.config.Metadata.Keys, name)
+ }
+ }
+ return nil
+ }
+
+ if !inputVal.IsValid() {
+ // If the input value is invalid, then we just set the value
+ // to be the zero value.
+ outVal.Set(reflect.Zero(outVal.Type()))
+ if d.config.Metadata != nil && name != "" {
+ d.config.Metadata.Keys = append(d.config.Metadata.Keys, name)
+ }
+ return nil
+ }
+
+ if d.config.DecodeHook != nil {
+ // We have a DecodeHook, so let's pre-process the input.
+ var err error
+ input, err = DecodeHookExec(
+ d.config.DecodeHook,
+ inputVal.Type(), outVal.Type(), input)
+ if err != nil {
+ return fmt.Errorf("error decoding '%s': %s", name, err)
+ }
+ }
+
+ var err error
+ outputKind := getKind(outVal)
+ switch outputKind {
+ case reflect.Bool:
+ err = d.decodeBool(name, input, outVal)
+ case reflect.Interface:
+ err = d.decodeBasic(name, input, outVal)
+ case reflect.String:
+ err = d.decodeString(name, input, outVal)
+ case reflect.Int:
+ err = d.decodeInt(name, input, outVal)
+ case reflect.Uint:
+ err = d.decodeUint(name, input, outVal)
+ case reflect.Float32:
+ err = d.decodeFloat(name, input, outVal)
+ case reflect.Struct:
+ err = d.decodeStruct(name, input, outVal)
+ case reflect.Map:
+ err = d.decodeMap(name, input, outVal)
+ case reflect.Ptr:
+ err = d.decodePtr(name, input, outVal)
+ case reflect.Slice:
+ err = d.decodeSlice(name, input, outVal)
+ case reflect.Array:
+ err = d.decodeArray(name, input, outVal)
+ case reflect.Func:
+ err = d.decodeFunc(name, input, outVal)
+ default:
+ // If we reached this point then we weren't able to decode it
+ return fmt.Errorf("%s: unsupported type: %s", name, outputKind)
+ }
+
+ // If we reached here, then we successfully decoded SOMETHING, so
+ // mark the key as used if we're tracking metainput.
+ if d.config.Metadata != nil && name != "" {
+ d.config.Metadata.Keys = append(d.config.Metadata.Keys, name)
+ }
+
+ return err
+}
+
+// This decodes a basic type (bool, int, string, etc.) and sets the
+// value to "data" of that type.
+func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value) error {
+ if val.IsValid() && val.Elem().IsValid() {
+ return d.decode(name, data, val.Elem())
+ }
+
+ dataVal := reflect.ValueOf(data)
+
+ // If the input data is a pointer, and the assigned type is the dereference
+ // of that exact pointer, then indirect it so that we can assign it.
+ // Example: *string to string
+ if dataVal.Kind() == reflect.Ptr && dataVal.Type().Elem() == val.Type() {
+ dataVal = reflect.Indirect(dataVal)
+ }
+
+ if !dataVal.IsValid() {
+ dataVal = reflect.Zero(val.Type())
+ }
+
+ dataValType := dataVal.Type()
+ if !dataValType.AssignableTo(val.Type()) {
+ return fmt.Errorf(
+ "'%s' expected type '%s', got '%s'",
+ name, val.Type(), dataValType)
+ }
+
+ val.Set(dataVal)
+ return nil
+}
+
+func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value) error {
+ dataVal := reflect.Indirect(reflect.ValueOf(data))
+ dataKind := getKind(dataVal)
+
+ converted := true
+ switch {
+ case dataKind == reflect.String:
+ val.SetString(dataVal.String())
+ case dataKind == reflect.Bool && d.config.WeaklyTypedInput:
+ if dataVal.Bool() {
+ val.SetString("1")
+ } else {
+ val.SetString("0")
+ }
+ case dataKind == reflect.Int && d.config.WeaklyTypedInput:
+ val.SetString(strconv.FormatInt(dataVal.Int(), 10))
+ case dataKind == reflect.Uint && d.config.WeaklyTypedInput:
+ val.SetString(strconv.FormatUint(dataVal.Uint(), 10))
+ case dataKind == reflect.Float32 && d.config.WeaklyTypedInput:
+ val.SetString(strconv.FormatFloat(dataVal.Float(), 'f', -1, 64))
+ case dataKind == reflect.Slice && d.config.WeaklyTypedInput,
+ dataKind == reflect.Array && d.config.WeaklyTypedInput:
+ dataType := dataVal.Type()
+ elemKind := dataType.Elem().Kind()
+ switch elemKind {
+ case reflect.Uint8:
+ var uints []uint8
+ if dataKind == reflect.Array {
+ uints = make([]uint8, dataVal.Len(), dataVal.Len())
+ for i := range uints {
+ uints[i] = dataVal.Index(i).Interface().(uint8)
+ }
+ } else {
+ uints = dataVal.Interface().([]uint8)
+ }
+ val.SetString(string(uints))
+ default:
+ converted = false
+ }
+ default:
+ converted = false
+ }
+
+ if !converted {
+ return fmt.Errorf(
+ "'%s' expected type '%s', got unconvertible type '%s'",
+ name, val.Type(), dataVal.Type())
+ }
+
+ return nil
+}
+
+func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) error {
+ dataVal := reflect.Indirect(reflect.ValueOf(data))
+ dataKind := getKind(dataVal)
+ dataType := dataVal.Type()
+
+ switch {
+ case dataKind == reflect.Int:
+ val.SetInt(dataVal.Int())
+ case dataKind == reflect.Uint:
+ val.SetInt(int64(dataVal.Uint()))
+ case dataKind == reflect.Float32:
+ val.SetInt(int64(dataVal.Float()))
+ case dataKind == reflect.Bool && d.config.WeaklyTypedInput:
+ if dataVal.Bool() {
+ val.SetInt(1)
+ } else {
+ val.SetInt(0)
+ }
+ case dataKind == reflect.String && d.config.WeaklyTypedInput:
+ i, err := strconv.ParseInt(dataVal.String(), 0, val.Type().Bits())
+ if err == nil {
+ val.SetInt(i)
+ } else {
+ return fmt.Errorf("cannot parse '%s' as int: %s", name, err)
+ }
+ case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number":
+ jn := data.(json.Number)
+ i, err := jn.Int64()
+ if err != nil {
+ return fmt.Errorf(
+ "error decoding json.Number into %s: %s", name, err)
+ }
+ val.SetInt(i)
+ default:
+ return fmt.Errorf(
+ "'%s' expected type '%s', got unconvertible type '%s'",
+ name, val.Type(), dataVal.Type())
+ }
+
+ return nil
+}
+
+func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) error {
+ dataVal := reflect.Indirect(reflect.ValueOf(data))
+ dataKind := getKind(dataVal)
+
+ switch {
+ case dataKind == reflect.Int:
+ i := dataVal.Int()
+ if i < 0 && !d.config.WeaklyTypedInput {
+ return fmt.Errorf("cannot parse '%s', %d overflows uint",
+ name, i)
+ }
+ val.SetUint(uint64(i))
+ case dataKind == reflect.Uint:
+ val.SetUint(dataVal.Uint())
+ case dataKind == reflect.Float32:
+ f := dataVal.Float()
+ if f < 0 && !d.config.WeaklyTypedInput {
+ return fmt.Errorf("cannot parse '%s', %f overflows uint",
+ name, f)
+ }
+ val.SetUint(uint64(f))
+ case dataKind == reflect.Bool && d.config.WeaklyTypedInput:
+ if dataVal.Bool() {
+ val.SetUint(1)
+ } else {
+ val.SetUint(0)
+ }
+ case dataKind == reflect.String && d.config.WeaklyTypedInput:
+ i, err := strconv.ParseUint(dataVal.String(), 0, val.Type().Bits())
+ if err == nil {
+ val.SetUint(i)
+ } else {
+ return fmt.Errorf("cannot parse '%s' as uint: %s", name, err)
+ }
+ default:
+ return fmt.Errorf(
+ "'%s' expected type '%s', got unconvertible type '%s'",
+ name, val.Type(), dataVal.Type())
+ }
+
+ return nil
+}
+
+func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) error {
+ dataVal := reflect.Indirect(reflect.ValueOf(data))
+ dataKind := getKind(dataVal)
+
+ switch {
+ case dataKind == reflect.Bool:
+ val.SetBool(dataVal.Bool())
+ case dataKind == reflect.Int && d.config.WeaklyTypedInput:
+ val.SetBool(dataVal.Int() != 0)
+ case dataKind == reflect.Uint && d.config.WeaklyTypedInput:
+ val.SetBool(dataVal.Uint() != 0)
+ case dataKind == reflect.Float32 && d.config.WeaklyTypedInput:
+ val.SetBool(dataVal.Float() != 0)
+ case dataKind == reflect.String && d.config.WeaklyTypedInput:
+ b, err := strconv.ParseBool(dataVal.String())
+ if err == nil {
+ val.SetBool(b)
+ } else if dataVal.String() == "" {
+ val.SetBool(false)
+ } else {
+ return fmt.Errorf("cannot parse '%s' as bool: %s", name, err)
+ }
+ default:
+ return fmt.Errorf(
+ "'%s' expected type '%s', got unconvertible type '%s'",
+ name, val.Type(), dataVal.Type())
+ }
+
+ return nil
+}
+
+func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value) error {
+ dataVal := reflect.Indirect(reflect.ValueOf(data))
+ dataKind := getKind(dataVal)
+ dataType := dataVal.Type()
+
+ switch {
+ case dataKind == reflect.Int:
+ val.SetFloat(float64(dataVal.Int()))
+ case dataKind == reflect.Uint:
+ val.SetFloat(float64(dataVal.Uint()))
+ case dataKind == reflect.Float32:
+ val.SetFloat(dataVal.Float())
+ case dataKind == reflect.Bool && d.config.WeaklyTypedInput:
+ if dataVal.Bool() {
+ val.SetFloat(1)
+ } else {
+ val.SetFloat(0)
+ }
+ case dataKind == reflect.String && d.config.WeaklyTypedInput:
+ f, err := strconv.ParseFloat(dataVal.String(), val.Type().Bits())
+ if err == nil {
+ val.SetFloat(f)
+ } else {
+ return fmt.Errorf("cannot parse '%s' as float: %s", name, err)
+ }
+ case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number":
+ jn := data.(json.Number)
+ i, err := jn.Float64()
+ if err != nil {
+ return fmt.Errorf(
+ "error decoding json.Number into %s: %s", name, err)
+ }
+ val.SetFloat(i)
+ default:
+ return fmt.Errorf(
+ "'%s' expected type '%s', got unconvertible type '%s'",
+ name, val.Type(), dataVal.Type())
+ }
+
+ return nil
+}
+
+func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) error {
+ valType := val.Type()
+ valKeyType := valType.Key()
+ valElemType := valType.Elem()
+
+ // By default we overwrite keys in the current map
+ valMap := val
+
+ // If the map is nil or we're purposely zeroing fields, make a new map
+ if valMap.IsNil() || d.config.ZeroFields {
+ // Make a new map to hold our result
+ mapType := reflect.MapOf(valKeyType, valElemType)
+ valMap = reflect.MakeMap(mapType)
+ }
+
+ // Check input type and based on the input type jump to the proper func
+ dataVal := reflect.Indirect(reflect.ValueOf(data))
+ switch dataVal.Kind() {
+ case reflect.Map:
+ return d.decodeMapFromMap(name, dataVal, val, valMap)
+
+ case reflect.Struct:
+ return d.decodeMapFromStruct(name, dataVal, val, valMap)
+
+ case reflect.Array, reflect.Slice:
+ if d.config.WeaklyTypedInput {
+ return d.decodeMapFromSlice(name, dataVal, val, valMap)
+ }
+
+ fallthrough
+
+ default:
+ return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind())
+ }
+}
+
+func (d *Decoder) decodeMapFromSlice(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error {
+ // Special case for BC reasons (covered by tests)
+ if dataVal.Len() == 0 {
+ val.Set(valMap)
+ return nil
+ }
+
+ for i := 0; i < dataVal.Len(); i++ {
+ err := d.decode(
+ fmt.Sprintf("%s[%d]", name, i),
+ dataVal.Index(i).Interface(), val)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error {
+ valType := val.Type()
+ valKeyType := valType.Key()
+ valElemType := valType.Elem()
+
+ // Accumulate errors
+ errors := make([]string, 0)
+
+ // If the input data is empty, then we just match what the input data is.
+ if dataVal.Len() == 0 {
+ if dataVal.IsNil() {
+ if !val.IsNil() {
+ val.Set(dataVal)
+ }
+ } else {
+ // Set to empty allocated value
+ val.Set(valMap)
+ }
+
+ return nil
+ }
+
+ for _, k := range dataVal.MapKeys() {
+ fieldName := fmt.Sprintf("%s[%s]", name, k)
+
+ // First decode the key into the proper type
+ currentKey := reflect.Indirect(reflect.New(valKeyType))
+ if err := d.decode(fieldName, k.Interface(), currentKey); err != nil {
+ errors = appendErrors(errors, err)
+ continue
+ }
+
+ // Next decode the data into the proper type
+ v := dataVal.MapIndex(k).Interface()
+ currentVal := reflect.Indirect(reflect.New(valElemType))
+ if err := d.decode(fieldName, v, currentVal); err != nil {
+ errors = appendErrors(errors, err)
+ continue
+ }
+
+ valMap.SetMapIndex(currentKey, currentVal)
+ }
+
+ // Set the built up map to the value
+ val.Set(valMap)
+
+ // If we had errors, return those
+ if len(errors) > 0 {
+ return &Error{errors}
+ }
+
+ return nil
+}
+
+func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error {
+ typ := dataVal.Type()
+ for i := 0; i < typ.NumField(); i++ {
+ // Get the StructField first since this is a cheap operation. If the
+ // field is unexported, then ignore it.
+ f := typ.Field(i)
+ if f.PkgPath != "" {
+ continue
+ }
+
+ // Next get the actual value of this field and verify it is assignable
+ // to the map value.
+ v := dataVal.Field(i)
+ if !v.Type().AssignableTo(valMap.Type().Elem()) {
+ return fmt.Errorf("cannot assign type '%s' to map value field of type '%s'", v.Type(), valMap.Type().Elem())
+ }
+
+ tagValue := f.Tag.Get(d.config.TagName)
+ tagParts := strings.Split(tagValue, ",")
+
+ // Determine the name of the key in the map
+ keyName := f.Name
+ if tagParts[0] != "" {
+ if tagParts[0] == "-" {
+ continue
+ }
+ keyName = tagParts[0]
+ }
+
+ // If "squash" is specified in the tag, we squash the field down.
+ squash := false
+ for _, tag := range tagParts[1:] {
+ if tag == "squash" {
+ squash = true
+ break
+ }
+ }
+ if squash && v.Kind() != reflect.Struct {
+ return fmt.Errorf("cannot squash non-struct type '%s'", v.Type())
+ }
+
+ switch v.Kind() {
+ // this is an embedded struct, so handle it differently
+ case reflect.Struct:
+ x := reflect.New(v.Type())
+ x.Elem().Set(v)
+
+ vType := valMap.Type()
+ vKeyType := vType.Key()
+ vElemType := vType.Elem()
+ mType := reflect.MapOf(vKeyType, vElemType)
+ vMap := reflect.MakeMap(mType)
+
+ err := d.decode(keyName, x.Interface(), vMap)
+ if err != nil {
+ return err
+ }
+
+ if squash {
+ for _, k := range vMap.MapKeys() {
+ valMap.SetMapIndex(k, vMap.MapIndex(k))
+ }
+ } else {
+ valMap.SetMapIndex(reflect.ValueOf(keyName), vMap)
+ }
+
+ default:
+ valMap.SetMapIndex(reflect.ValueOf(keyName), v)
+ }
+ }
+
+ if val.CanAddr() {
+ val.Set(valMap)
+ }
+
+ return nil
+}
+
+func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) error {
+ // If the input data is nil, then we want to just set the output
+ // pointer to be nil as well.
+ isNil := data == nil
+ if !isNil {
+ switch v := reflect.Indirect(reflect.ValueOf(data)); v.Kind() {
+ case reflect.Chan,
+ reflect.Func,
+ reflect.Interface,
+ reflect.Map,
+ reflect.Ptr,
+ reflect.Slice:
+ isNil = v.IsNil()
+ }
+ }
+ if isNil {
+ if !val.IsNil() && val.CanSet() {
+ nilValue := reflect.New(val.Type()).Elem()
+ val.Set(nilValue)
+ }
+
+ return nil
+ }
+
+ // Create an element of the concrete (non pointer) type and decode
+ // into that. Then set the value of the pointer to this type.
+ valType := val.Type()
+ valElemType := valType.Elem()
+ if val.CanSet() {
+ realVal := val
+ if realVal.IsNil() || d.config.ZeroFields {
+ realVal = reflect.New(valElemType)
+ }
+
+ if err := d.decode(name, data, reflect.Indirect(realVal)); err != nil {
+ return err
+ }
+
+ val.Set(realVal)
+ } else {
+ if err := d.decode(name, data, reflect.Indirect(val)); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (d *Decoder) decodeFunc(name string, data interface{}, val reflect.Value) error {
+ // Create an element of the concrete (non pointer) type and decode
+ // into that. Then set the value of the pointer to this type.
+ dataVal := reflect.Indirect(reflect.ValueOf(data))
+ if val.Type() != dataVal.Type() {
+ return fmt.Errorf(
+ "'%s' expected type '%s', got unconvertible type '%s'",
+ name, val.Type(), dataVal.Type())
+ }
+ val.Set(dataVal)
+ return nil
+}
+
+func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) error {
+ dataVal := reflect.Indirect(reflect.ValueOf(data))
+ dataValKind := dataVal.Kind()
+ valType := val.Type()
+ valElemType := valType.Elem()
+ sliceType := reflect.SliceOf(valElemType)
+
+ valSlice := val
+ if valSlice.IsNil() || d.config.ZeroFields {
+ if d.config.WeaklyTypedInput {
+ switch {
+ // Slice and array we use the normal logic
+ case dataValKind == reflect.Slice, dataValKind == reflect.Array:
+ break
+
+ // Empty maps turn into empty slices
+ case dataValKind == reflect.Map:
+ if dataVal.Len() == 0 {
+ val.Set(reflect.MakeSlice(sliceType, 0, 0))
+ return nil
+ }
+ // Create slice of maps of other sizes
+ return d.decodeSlice(name, []interface{}{data}, val)
+
+ case dataValKind == reflect.String && valElemType.Kind() == reflect.Uint8:
+ return d.decodeSlice(name, []byte(dataVal.String()), val)
+
+ // All other types we try to convert to the slice type
+ // and "lift" it into it. i.e. a string becomes a string slice.
+ default:
+ // Just re-try this function with data as a slice.
+ return d.decodeSlice(name, []interface{}{data}, val)
+ }
+ }
+
+ // Check input type
+ if dataValKind != reflect.Array && dataValKind != reflect.Slice {
+ return fmt.Errorf(
+ "'%s': source data must be an array or slice, got %s", name, dataValKind)
+
+ }
+
+ // If the input value is empty, then don't allocate since non-nil != nil
+ if dataVal.Len() == 0 {
+ return nil
+ }
+
+ // Make a new slice to hold our result, same size as the original data.
+ valSlice = reflect.MakeSlice(sliceType, dataVal.Len(), dataVal.Len())
+ }
+
+ // Accumulate any errors
+ errors := make([]string, 0)
+
+ for i := 0; i < dataVal.Len(); i++ {
+ currentData := dataVal.Index(i).Interface()
+ for valSlice.Len() <= i {
+ valSlice = reflect.Append(valSlice, reflect.Zero(valElemType))
+ }
+ currentField := valSlice.Index(i)
+
+ fieldName := fmt.Sprintf("%s[%d]", name, i)
+ if err := d.decode(fieldName, currentData, currentField); err != nil {
+ errors = appendErrors(errors, err)
+ }
+ }
+
+ // Finally, set the value to the slice we built up
+ val.Set(valSlice)
+
+ // If there were errors, we return those
+ if len(errors) > 0 {
+ return &Error{errors}
+ }
+
+ return nil
+}
+
+func (d *Decoder) decodeArray(name string, data interface{}, val reflect.Value) error {
+ dataVal := reflect.Indirect(reflect.ValueOf(data))
+ dataValKind := dataVal.Kind()
+ valType := val.Type()
+ valElemType := valType.Elem()
+ arrayType := reflect.ArrayOf(valType.Len(), valElemType)
+
+ valArray := val
+
+ if valArray.Interface() == reflect.Zero(valArray.Type()).Interface() || d.config.ZeroFields {
+ // Check input type
+ if dataValKind != reflect.Array && dataValKind != reflect.Slice {
+ if d.config.WeaklyTypedInput {
+ switch {
+ // Empty maps turn into empty arrays
+ case dataValKind == reflect.Map:
+ if dataVal.Len() == 0 {
+ val.Set(reflect.Zero(arrayType))
+ return nil
+ }
+
+ // All other types we try to convert to the array type
+ // and "lift" it into it. i.e. a string becomes a string array.
+ default:
+ // Just re-try this function with data as a slice.
+ return d.decodeArray(name, []interface{}{data}, val)
+ }
+ }
+
+ return fmt.Errorf(
+ "'%s': source data must be an array or slice, got %s", name, dataValKind)
+
+ }
+ if dataVal.Len() > arrayType.Len() {
+ return fmt.Errorf(
+ "'%s': expected source data to have length less or equal to %d, got %d", name, arrayType.Len(), dataVal.Len())
+
+ }
+
+ // Make a new array to hold our result, same size as the original data.
+ valArray = reflect.New(arrayType).Elem()
+ }
+
+ // Accumulate any errors
+ errors := make([]string, 0)
+
+ for i := 0; i < dataVal.Len(); i++ {
+ currentData := dataVal.Index(i).Interface()
+ currentField := valArray.Index(i)
+
+ fieldName := fmt.Sprintf("%s[%d]", name, i)
+ if err := d.decode(fieldName, currentData, currentField); err != nil {
+ errors = appendErrors(errors, err)
+ }
+ }
+
+ // Finally, set the value to the array we built up
+ val.Set(valArray)
+
+ // If there were errors, we return those
+ if len(errors) > 0 {
+ return &Error{errors}
+ }
+
+ return nil
+}
+
+func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) error {
+ dataVal := reflect.Indirect(reflect.ValueOf(data))
+
+ // If the type of the value to write to and the data match directly,
+ // then we just set it directly instead of recursing into the structure.
+ if dataVal.Type() == val.Type() {
+ val.Set(dataVal)
+ return nil
+ }
+
+ dataValKind := dataVal.Kind()
+ switch dataValKind {
+ case reflect.Map:
+ return d.decodeStructFromMap(name, dataVal, val)
+
+ case reflect.Struct:
+ // Not the most efficient way to do this but we can optimize later if
+ // we want to. To convert from struct to struct we go to map first
+ // as an intermediary.
+ m := make(map[string]interface{})
+ mval := reflect.Indirect(reflect.ValueOf(&m))
+ if err := d.decodeMapFromStruct(name, dataVal, mval, mval); err != nil {
+ return err
+ }
+
+ result := d.decodeStructFromMap(name, mval, val)
+ return result
+
+ default:
+ return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind())
+ }
+}
+
+func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) error {
+ dataValType := dataVal.Type()
+ if kind := dataValType.Key().Kind(); kind != reflect.String && kind != reflect.Interface {
+ return fmt.Errorf(
+ "'%s' needs a map with string keys, has '%s' keys",
+ name, dataValType.Key().Kind())
+ }
+
+ dataValKeys := make(map[reflect.Value]struct{})
+ dataValKeysUnused := make(map[interface{}]struct{})
+ for _, dataValKey := range dataVal.MapKeys() {
+ dataValKeys[dataValKey] = struct{}{}
+ dataValKeysUnused[dataValKey.Interface()] = struct{}{}
+ }
+
+ errors := make([]string, 0)
+
+ // This slice will keep track of all the structs we'll be decoding.
+ // There can be more than one struct if there are embedded structs
+ // that are squashed.
+ structs := make([]reflect.Value, 1, 5)
+ structs[0] = val
+
+ // Compile the list of all the fields that we're going to be decoding
+ // from all the structs.
+ type field struct {
+ field reflect.StructField
+ val reflect.Value
+ }
+ fields := []field{}
+ for len(structs) > 0 {
+ structVal := structs[0]
+ structs = structs[1:]
+
+ structType := structVal.Type()
+
+ for i := 0; i < structType.NumField(); i++ {
+ fieldType := structType.Field(i)
+ fieldKind := fieldType.Type.Kind()
+
+ // If "squash" is specified in the tag, we squash the field down.
+ squash := false
+ tagParts := strings.Split(fieldType.Tag.Get(d.config.TagName), ",")
+ for _, tag := range tagParts[1:] {
+ if tag == "squash" {
+ squash = true
+ break
+ }
+ }
+
+ if squash {
+ if fieldKind != reflect.Struct {
+ errors = appendErrors(errors,
+ fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldKind))
+ } else {
+ structs = append(structs, structVal.FieldByName(fieldType.Name))
+ }
+ continue
+ }
+
+ // Normal struct field, store it away
+ fields = append(fields, field{fieldType, structVal.Field(i)})
+ }
+ }
+
+ // for fieldType, field := range fields {
+ for _, f := range fields {
+ field, fieldValue := f.field, f.val
+ fieldName := field.Name
+
+ tagValue := field.Tag.Get(d.config.TagName)
+ tagValue = strings.SplitN(tagValue, ",", 2)[0]
+ if tagValue != "" {
+ fieldName = tagValue
+ }
+
+ rawMapKey := reflect.ValueOf(fieldName)
+ rawMapVal := dataVal.MapIndex(rawMapKey)
+ if !rawMapVal.IsValid() {
+ // Do a slower search by iterating over each key and
+ // doing case-insensitive search.
+ for dataValKey := range dataValKeys {
+ mK, ok := dataValKey.Interface().(string)
+ if !ok {
+ // Not a string key
+ continue
+ }
+
+ if strings.EqualFold(mK, fieldName) {
+ rawMapKey = dataValKey
+ rawMapVal = dataVal.MapIndex(dataValKey)
+ break
+ }
+ }
+
+ if !rawMapVal.IsValid() {
+ // There was no matching key in the map for the value in
+ // the struct. Just ignore.
+ continue
+ }
+ }
+
+ // Delete the key we're using from the unused map so we stop tracking
+ delete(dataValKeysUnused, rawMapKey.Interface())
+
+ if !fieldValue.IsValid() {
+ // This should never happen
+ panic("field is not valid")
+ }
+
+ // If we can't set the field, then it is unexported or something,
+ // and we just continue onwards.
+ if !fieldValue.CanSet() {
+ continue
+ }
+
+ // If the name is empty string, then we're at the root, and we
+ // don't dot-join the fields.
+ if name != "" {
+ fieldName = fmt.Sprintf("%s.%s", name, fieldName)
+ }
+
+ if err := d.decode(fieldName, rawMapVal.Interface(), fieldValue); err != nil {
+ errors = appendErrors(errors, err)
+ }
+ }
+
+ if d.config.ErrorUnused && len(dataValKeysUnused) > 0 {
+ keys := make([]string, 0, len(dataValKeysUnused))
+ for rawKey := range dataValKeysUnused {
+ keys = append(keys, rawKey.(string))
+ }
+ sort.Strings(keys)
+
+ err := fmt.Errorf("'%s' has invalid keys: %s", name, strings.Join(keys, ", "))
+ errors = appendErrors(errors, err)
+ }
+
+ if len(errors) > 0 {
+ return &Error{errors}
+ }
+
+ // Add the unused keys to the list of unused keys if we're tracking metadata
+ if d.config.Metadata != nil {
+ for rawKey := range dataValKeysUnused {
+ key := rawKey.(string)
+ if name != "" {
+ key = fmt.Sprintf("%s.%s", name, key)
+ }
+
+ d.config.Metadata.Unused = append(d.config.Metadata.Unused, key)
+ }
+ }
+
+ return nil
+}
+
+func getKind(val reflect.Value) reflect.Kind {
+ kind := val.Kind()
+
+ switch {
+ case kind >= reflect.Int && kind <= reflect.Int64:
+ return reflect.Int
+ case kind >= reflect.Uint && kind <= reflect.Uint64:
+ return reflect.Uint
+ case kind >= reflect.Float32 && kind <= reflect.Float64:
+ return reflect.Float32
+ default:
+ return kind
+ }
+}
diff --git a/vendor/github.com/smueller18/goinwx/LICENSE b/vendor/github.com/smueller18/goinwx/LICENSE
new file mode 100644
index 00000000..9df6656c
--- /dev/null
+++ b/vendor/github.com/smueller18/goinwx/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 Andrew
+
+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/smueller18/goinwx/account.go b/vendor/github.com/smueller18/goinwx/account.go
new file mode 100644
index 00000000..14b2073a
--- /dev/null
+++ b/vendor/github.com/smueller18/goinwx/account.go
@@ -0,0 +1,54 @@
+package goinwx
+
+const (
+ methodAccountLogin = "account.login"
+ methodAccountLogout = "account.logout"
+ methodAccountLock = "account.lock"
+ methodAccountUnlock = "account.unlock"
+)
+
+type AccountService interface {
+ Login() error
+ Logout() error
+ Lock() error
+ Unlock(tan string) error
+}
+
+type AccountServiceOp struct {
+ client *Client
+}
+
+var _ AccountService = &AccountServiceOp{}
+
+func (s *AccountServiceOp) Login() error {
+ req := s.client.NewRequest(methodAccountLogin, map[string]interface{}{
+ "user": s.client.Username,
+ "pass": s.client.Password,
+ })
+
+ _, err := s.client.Do(*req)
+ return err
+}
+
+func (s *AccountServiceOp) Logout() error {
+ req := s.client.NewRequest(methodAccountLogout, nil)
+
+ _, err := s.client.Do(*req)
+ return err
+}
+
+func (s *AccountServiceOp) Lock() error {
+ req := s.client.NewRequest(methodAccountLock, nil)
+
+ _, err := s.client.Do(*req)
+ return err
+}
+
+func (s *AccountServiceOp) Unlock(tan string) error {
+ req := s.client.NewRequest(methodAccountUnlock, map[string]interface{}{
+ "tan": tan,
+ })
+
+ _, err := s.client.Do(*req)
+ return err
+}
diff --git a/vendor/github.com/smueller18/goinwx/contact.go b/vendor/github.com/smueller18/goinwx/contact.go
new file mode 100644
index 00000000..0ae36f5c
--- /dev/null
+++ b/vendor/github.com/smueller18/goinwx/contact.go
@@ -0,0 +1,150 @@
+package goinwx
+
+import (
+ "github.com/fatih/structs"
+ "github.com/mitchellh/mapstructure"
+)
+
+const (
+ methodContactInfo = "contact.info"
+ methodContactList = "contact.list"
+ methodContactCreate = "contact.create"
+ methodContactDelete = "contact.delete"
+ methodContactUpdate = "contact.update"
+)
+
+type ContactService interface {
+ Create(*ContactCreateRequest) (int, error)
+ Update(*ContactUpdateRequest) error
+ Delete(int) error
+ Info(int) (*ContactInfoResponse, error)
+ List(string) (*ContactListResponse, error)
+}
+
+type ContactServiceOp struct {
+ client *Client
+}
+
+var _ ContactService = &ContactServiceOp{}
+
+type ContactCreateRequest struct {
+ Type string `structs:"type"`
+ Name string `structs:"name"`
+ Org string `structs:"org,omitempty"`
+ Street string `structs:"street"`
+ City string `structs:"city"`
+ PostalCode string `structs:"pc"`
+ StateProvince string `structs:"sp,omitempty"`
+ CountryCode string `structs:"cc"`
+ Voice string `structs:"voice"`
+ Fax string `structs:"fax,omitempty"`
+ Email string `structs:"email"`
+ Remarks string `structs:"remarks,omitempty"`
+ Protection bool `structs:"protection,omitempty"`
+ Testing bool `structs:"testing,omitempty"`
+}
+
+type ContactUpdateRequest struct {
+ Id int `structs:"id"`
+ Name string `structs:"name,omitempty"`
+ Org string `structs:"org,omitempty"`
+ Street string `structs:"street,omitempty"`
+ City string `structs:"city,omitempty"`
+ PostalCode string `structs:"pc,omitempty"`
+ StateProvince string `structs:"sp,omitempty"`
+ CountryCode string `structs:"cc,omitempty"`
+ Voice string `structs:"voice,omitempty"`
+ Fax string `structs:"fax,omitempty"`
+ Email string `structs:"email,omitempty"`
+ Remarks string `structs:"remarks,omitempty"`
+ Protection bool `structs:"protection,omitempty"`
+ Testing bool `structs:"testing,omitempty"`
+}
+
+type ContactInfoResponse struct {
+ Contact Contact `mapstructure:"contact"`
+}
+
+type ContactListResponse struct {
+ Count int
+ Contacts []Contact `mapstructure:"contact"`
+}
+
+func (s *ContactServiceOp) Create(request *ContactCreateRequest) (int, error) {
+ req := s.client.NewRequest(methodContactCreate, structs.Map(request))
+
+ resp, err := s.client.Do(*req)
+ if err != nil {
+ return 0, err
+ }
+
+ var result map[string]int
+ err = mapstructure.Decode(*resp, &result)
+ if err != nil {
+ return 0, err
+ }
+
+ return result["id"], nil
+}
+
+func (s *ContactServiceOp) Delete(roId int) error {
+ req := s.client.NewRequest(methodContactDelete, map[string]interface{}{
+ "id": roId,
+ })
+
+ _, err := s.client.Do(*req)
+ return err
+}
+
+func (s *ContactServiceOp) Update(request *ContactUpdateRequest) error {
+ req := s.client.NewRequest(methodContactUpdate, structs.Map(request))
+
+ _, err := s.client.Do(*req)
+ return err
+}
+
+func (s *ContactServiceOp) Info(contactId int) (*ContactInfoResponse, error) {
+ var requestMap = make(map[string]interface{})
+ requestMap["wide"] = 1
+
+ if contactId != 0 {
+ requestMap["id"] = contactId
+ }
+
+ req := s.client.NewRequest(methodContactInfo, requestMap)
+
+ resp, err := s.client.Do(*req)
+ if err != nil {
+ return nil, err
+ }
+
+ var result ContactInfoResponse
+ err = mapstructure.Decode(*resp, &result)
+ if err != nil {
+ return nil, err
+ }
+
+ return &result, nil
+}
+
+func (s *ContactServiceOp) List(search string) (*ContactListResponse, error) {
+ var requestMap = make(map[string]interface{})
+
+ if search != "" {
+ requestMap["search"] = search
+ }
+ req := s.client.NewRequest(methodContactList, requestMap)
+
+ resp, err := s.client.Do(*req)
+ if err != nil {
+ return nil, err
+ }
+
+ var result ContactListResponse
+ err = mapstructure.Decode(*resp, &result)
+ if err != nil {
+ return nil, err
+ }
+
+ return &result, nil
+}
diff --git a/vendor/github.com/smueller18/goinwx/domain.go b/vendor/github.com/smueller18/goinwx/domain.go
new file mode 100644
index 00000000..ddbc86a2
--- /dev/null
+++ b/vendor/github.com/smueller18/goinwx/domain.go
@@ -0,0 +1,303 @@
+package goinwx
+
+import (
+ "errors"
+ "fmt"
+ "time"
+
+ "github.com/fatih/structs"
+ "github.com/mitchellh/mapstructure"
+)
+
+const (
+ methodDomainCheck = "domain.check"
+ methodDomainCreate = "domain.create"
+ methodDomainDelete = "domain.delete"
+ methodDomainGetPrices = "domain.getPrices"
+ methodDomainGetRules = "domain.getRules"
+ methodDomainInfo = "domain.info"
+ methodDomainList = "domain.list"
+ methodDomainLog = "domain.log"
+ methodDomainPush = "domain.push"
+ methodDomainRenew = "domain.renew"
+ methodDomainRestore = "domain.restore"
+ methodDomainStats = "domain.stats"
+ methodDomainTrade = "domain.trade"
+ methodDomainTransfer = "domain.transfer"
+ methodDomainTransferOut = "domain.transferOut"
+ methodDomainUpdate = "domain.update"
+ methodDomainWhois = "domain.whois"
+)
+
+type DomainService interface {
+ Check(domains []string) ([]DomainCheckResponse, error)
+ Register(request *DomainRegisterRequest) (*DomainRegisterResponse, error)
+ Delete(domain string, scheduledDate time.Time) error
+ Info(domain string, roId int) (*DomainInfoResponse, error)
+ GetPrices(tlds []string) ([]DomainPriceResponse, error)
+ List(*DomainListRequest) (*DomainList, error)
+ Whois(domain string) (string, error)
+}
+
+type DomainServiceOp struct {
+ client *Client
+}
+
+var _ DomainService = &DomainServiceOp{}
+
+type domainCheckResponseRoot struct {
+ Domains []DomainCheckResponse `mapstructure:"domain"`
+}
+type DomainCheckResponse struct {
+ Available int `mapstructure:"avail"`
+ Status string `mapstructure:"status"`
+ Name string `mapstructure:"name"`
+ Domain string `mapstructure:"domain"`
+ TLD string `mapstructure:"tld"`
+ CheckMethod string `mapstructure:"checkmethod"`
+ Price float32 `mapstructure:"price"`
+ CheckTime float32 `mapstructure:"checktime"`
+}
+
+type domainPriceResponseRoot struct {
+ Prices []DomainPriceResponse `mapstructure:"price"`
+}
+type DomainPriceResponse struct {
+ Tld string `mapstructure:"tld"`
+ Currency string `mapstructure:"currency"`
+ CreatePrice float32 `mapstructure:"createPrice"`
+ MonthlyCreatePrice float32 `mapstructure:"monthlyCreatePrice"`
+ TransferPrice float32 `mapstructure:"transferPrice"`
+ RenewalPrice float32 `mapstructure:"renewalPrice"`
+ MonthlyRenewalPrice float32 `mapstructure:"monthlyRenewalPrice"`
+ UpdatePrice float32 `mapstructure:"updatePrice"`
+ TradePrice float32 `mapstructure:"tradePrice"`
+ TrusteePrice float32 `mapstructure:"trusteePrice"`
+ MonthlyTrusteePrice float32 `mapstructure:"monthlyTrusteePrice"`
+ CreatePeriod int `mapstructure:"createPeriod"`
+ TransferPeriod int `mapstructure:"transferPeriod"`
+ RenewalPeriod int `mapstructure:"renewalPeriod"`
+ TradePeriod int `mapstructure:"tradePeriod"`
+}
+
+type DomainRegisterRequest struct {
+ Domain string `structs:"domain"`
+ Period string `structs:"period,omitempty"`
+ Registrant int `structs:"registrant"`
+ Admin int `structs:"admin"`
+ Tech int `structs:"tech"`
+ Billing int `structs:"billing"`
+ Nameservers []string `structs:"ns,omitempty"`
+ TransferLock string `structs:"transferLock,omitempty"`
+ RenewalMode string `structs:"renewalMode,omitempty"`
+ WhoisProvider string `structs:"whoisProvider,omitempty"`
+ WhoisUrl string `structs:"whoisUrl,omitempty"`
+ ScDate string `structs:"scDate,omitempty"`
+ ExtDate string `structs:"extDate,omitempty"`
+ Asynchron string `structs:"asynchron,omitempty"`
+ Voucher string `structs:"voucher,omitempty"`
+ Testing string `structs:"testing,omitempty"`
+}
+
+type DomainRegisterResponse struct {
+ RoId int
+ Price float32
+ Currency string
+}
+
+type DomainInfoResponse struct {
+ RoId int `mapstructure:"roId"`
+ Domain string `mapstructure:"domain"`
+ DomainAce string `mapstructure:"domainAce"`
+ Period string `mapstructure:"period"`
+ CrDate time.Time `mapstructure:"crDate"`
+ ExDate time.Time `mapstructure:"exDate"`
+ UpDate time.Time `mapstructure:"upDate"`
+ ReDate time.Time `mapstructure:"reDate"`
+ ScDate time.Time `mapstructure:"scDate"`
+ TransferLock int `mapstructure:"transferLock"`
+ Status string `mapstructure:"status"`
+ AuthCode string `mapstructure:"authCode"`
+ RenewalMode string `mapstructure:"renewalMode"`
+ TransferMode string `mapstructure:"transferMode"`
+ Registrant int `mapstructure:"registrant"`
+ Admin int `mapstructure:"admin"`
+ Tech int `mapstructure:"tech"`
+ Billing int `mapstructure:"billing"`
+ Nameservers []string `mapstructure:"ns"`
+ NoDelegation string `mapstructure:"noDelegation"`
+ Contacts map[string]Contact `mapstructure:"contact"`
+}
+
+type Contact struct {
+ RoId int
+ Id string
+ Type string
+ Name string
+ Org string
+ Street string
+ City string
+ PostalCode string `mapstructure:"pc"`
+ StateProvince string `mapstructure:"sp"`
+ Country string `mapstructure:"cc"`
+ Phone string `mapstructure:"voice"`
+ Fax string
+ Email string
+ Remarks string
+ Protection string
+}
+
+type DomainListRequest struct {
+ Domain string `structs:"domain,omitempty"`
+ RoId int `structs:"roId,omitempty"`
+ Status int `structs:"status,omitempty"`
+ Registrant int `structs:"registrant,omitempty"`
+ Admin int `structs:"admin,omitempty"`
+ Tech int `structs:"tech,omitempty"`
+ Billing int `structs:"billing,omitempty"`
+ RenewalMode int `structs:"renewalMode,omitempty"`
+ TransferLock int `structs:"transferLock,omitempty"`
+ NoDelegation int `structs:"noDelegation,omitempty"`
+ Tag int `structs:"tag,omitempty"`
+ Order int `structs:"order,omitempty"`
+ Page int `structs:"page,omitempty"`
+ Pagelimit int `structs:"pagelimit,omitempty"`
+}
+
+type DomainList struct {
+ Count int
+ Domains []DomainInfoResponse `mapstructure:"domain"`
+}
+
+func (s *DomainServiceOp) Check(domains []string) ([]DomainCheckResponse, error) {
+ req := s.client.NewRequest(methodDomainCheck, map[string]interface{}{
+ "domain": domains,
+ "wide": "2",
+ })
+
+ resp, err := s.client.Do(*req)
+ if err != nil {
+ return nil, err
+ }
+
+ root := new(domainCheckResponseRoot)
+ err = mapstructure.Decode(*resp, &root)
+ if err != nil {
+ return nil, err
+ }
+
+ return root.Domains, nil
+}
+
+func (s *DomainServiceOp) GetPrices(tlds []string) ([]DomainPriceResponse, error) {
+ req := s.client.NewRequest(methodDomainGetPrices, map[string]interface{}{
+ "tld": tlds,
+ "vat": false,
+ })
+
+ resp, err := s.client.Do(*req)
+ if err != nil {
+ return nil, err
+ }
+
+ root := new(domainPriceResponseRoot)
+ err = mapstructure.Decode(*resp, &root)
+ if err != nil {
+ return nil, err
+ }
+
+ return root.Prices, nil
+}
+
+func (s *DomainServiceOp) Register(request *DomainRegisterRequest) (*DomainRegisterResponse, error) {
+ req := s.client.NewRequest(methodDomainCreate, structs.Map(request))
+
+ resp, err := s.client.Do(*req)
+ if err != nil {
+ return nil, err
+ }
+
+ var result DomainRegisterResponse
+ err = mapstructure.Decode(*resp, &result)
+ if err != nil {
+ return nil, err
+ }
+
+ return &result, nil
+}
+
+func (s *DomainServiceOp) Delete(domain string, scheduledDate time.Time) error {
+ req := s.client.NewRequest(methodDomainDelete, map[string]interface{}{
+ "domain": domain,
+ "scDate": scheduledDate.Format(time.RFC3339),
+ })
+
+ _, err := s.client.Do(*req)
+ return err
+}
+
+func (s *DomainServiceOp) Info(domain string, roId int) (*DomainInfoResponse, error) {
+ req := s.client.NewRequest(methodDomainInfo, map[string]interface{}{
+ "domain": domain,
+ "wide": "2",
+ })
+ if roId != 0 {
+ req.Args["roId"] = roId
+ }
+
+ resp, err := s.client.Do(*req)
+ if err != nil {
+ return nil, err
+ }
+
+ var result DomainInfoResponse
+ err = mapstructure.Decode(*resp, &result)
+ if err != nil {
+ return nil, err
+ }
+ fmt.Println("Response", result)
+
+ return &result, nil
+}
+
+func (s *DomainServiceOp) List(request *DomainListRequest) (*DomainList, error) {
+ if request == nil {
+ return nil, errors.New("Request can't be nil")
+ }
+ requestMap := structs.Map(request)
+ requestMap["wide"] = "2"
+
+ req := s.client.NewRequest(methodDomainList, requestMap)
+
+ resp, err := s.client.Do(*req)
+ if err != nil {
+ return nil, err
+ }
+
+ var result DomainList
+ err = mapstructure.Decode(*resp, &result)
+ if err != nil {
+ return nil, err
+ }
+
+ return &result, nil
+}
+
+func (s *DomainServiceOp) Whois(domain string) (string, error) {
+ req := s.client.NewRequest(methodDomainWhois, map[string]interface{}{
+ "domain": domain,
+ })
+
+ resp, err := s.client.Do(*req)
+ if err != nil {
+ return "", err
+ }
+
+ var result map[string]string
+ err = mapstructure.Decode(*resp, &result)
+ if err != nil {
+ return "", err
+ }
+
+ return result["whois"], nil
+}
diff --git a/vendor/github.com/smueller18/goinwx/goinwx.go b/vendor/github.com/smueller18/goinwx/goinwx.go
new file mode 100644
index 00000000..b4d67d62
--- /dev/null
+++ b/vendor/github.com/smueller18/goinwx/goinwx.go
@@ -0,0 +1,133 @@
+package goinwx
+
+import (
+ "fmt"
+ "net/url"
+
+ "github.com/kolo/xmlrpc"
+)
+
+const (
+ libraryVersion = "0.4.0"
+ APIBaseUrl = "https://api.domrobot.com/xmlrpc/"
+ APISandboxBaseUrl = "https://api.ote.domrobot.com/xmlrpc/"
+ APILanguage = "eng"
+)
+
+// Client manages communication with INWX API.
+type Client struct {
+ // HTTP client used to communicate with the INWX API.
+ RPCClient *xmlrpc.Client
+
+ // Base URL for API requests.
+ BaseURL *url.URL
+
+ // API username
+ Username string
+
+ // API password
+ Password string
+
+ // User agent for client
+ APILanguage string
+
+ // Services used for communicating with the API
+ Account AccountService
+ Domains DomainService
+ Nameservers NameserverService
+ Contacts ContactService
+}
+
+type ClientOptions struct {
+ Sandbox bool
+}
+
+type Request struct {
+ ServiceMethod string
+ Args map[string]interface{}
+}
+
+// Response is a INWX API response. This wraps the standard http.Response returned from INWX.
+type Response struct {
+ Code int `xmlrpc:"code"`
+ Message string `xmlrpc:"msg"`
+ ReasonCode string `xmlrpc:"reasonCode"`
+ Reason string `xmlrpc:"reason"`
+ ResponseData map[string]interface{} `xmlrpc:"resData"`
+}
+
+// An ErrorResponse reports the error caused by an API request
+type ErrorResponse struct {
+ Code int `xmlrpc:"code"`
+ Message string `xmlrpc:"msg"`
+ ReasonCode string `xmlrpc:"reasonCode"`
+ Reason string `xmlrpc:"reason"`
+}
+
+// NewClient returns a new INWX API client.
+func NewClient(username, password string, opts *ClientOptions) *Client {
+ var useSandbox bool
+ if opts != nil {
+ useSandbox = opts.Sandbox
+ }
+
+ var baseURL *url.URL
+
+ if useSandbox {
+ baseURL, _ = url.Parse(APISandboxBaseUrl)
+ } else {
+ baseURL, _ = url.Parse(APIBaseUrl)
+ }
+
+ rpcClient, _ := xmlrpc.NewClient(baseURL.String(), nil)
+
+ client := &Client{RPCClient: rpcClient,
+ BaseURL: baseURL,
+ Username: username,
+ Password: password,
+ }
+
+ client.Account = &AccountServiceOp{client: client}
+ client.Domains = &DomainServiceOp{client: client}
+ client.Nameservers = &NameserverServiceOp{client: client}
+ client.Contacts = &ContactServiceOp{client: client}
+
+ return client
+}
+
+// NewRequest creates an API request.
+func (c *Client) NewRequest(serviceMethod string, args map[string]interface{}) *Request {
+ if args != nil {
+ args["lang"] = APILanguage
+ }
+
+ return &Request{ServiceMethod: serviceMethod, Args: args}
+}
+
+// Do sends an API request and returns the API response.
+func (c *Client) Do(req Request) (*map[string]interface{}, error) {
+ var resp Response
+ err := c.RPCClient.Call(req.ServiceMethod, req.Args, &resp)
+ if err != nil {
+ return nil, err
+ }
+
+ return &resp.ResponseData, CheckResponse(&resp)
+}
+
+func (r *ErrorResponse) Error() string {
+ if r.Reason != "" {
+ return fmt.Sprintf("(%d) %s. Reason: (%s) %s",
+ r.Code, r.Message, r.ReasonCode, r.Reason)
+ }
+ return fmt.Sprintf("(%d) %s", r.Code, r.Message)
+}
+
+// CheckResponse checks the API response for errors, and returns them if present.
+func CheckResponse(r *Response) error {
+ if c := r.Code; c >= 1000 && c <= 1500 {
+ return nil
+ }
+
+ return &ErrorResponse{Code: r.Code, Message: r.Message, Reason: r.Reason, ReasonCode: r.ReasonCode}
+}
diff --git a/vendor/github.com/smueller18/goinwx/nameserver.go b/vendor/github.com/smueller18/goinwx/nameserver.go
new file mode 100644
index 00000000..38269b46
--- /dev/null
+++ b/vendor/github.com/smueller18/goinwx/nameserver.go
@@ -0,0 +1,275 @@
+package goinwx
+
+import (
+ "errors"
+ "fmt"
+ "time"
+
+ "github.com/fatih/structs"
+ "github.com/mitchellh/mapstructure"
+)
+
+const (
+ methodNameserverCheck = "nameserver.check"
+ methodNameserverCreate = "nameserver.create"
+ methodNameserverCreateRecord = "nameserver.createRecord"
+ methodNameserverDelete = "nameserver.delete"
+ methodNameserverDeleteRecord = "nameserver.deleteRecord"
+ methodNameserverInfo = "nameserver.info"
+ methodNameserverList = "nameserver.list"
+ methodNameserverUpdate = "nameserver.update"
+ methodNameserverUpdateRecord = "nameserver.updateRecord"
+)
+
+type NameserverService interface {
+ Check(domain string, nameservers []string) (*NameserverCheckResponse, error)
+ Create(*NameserverCreateRequest) (int, error)
+ Info(*NameserverInfoRequest) (*NamserverInfoResponse, error)
+ List(domain string) (*NamserverListResponse, error)
+ CreateRecord(*NameserverRecordRequest) (int, error)
+ UpdateRecord(recId int, request *NameserverRecordRequest) error
+ DeleteRecord(recId int) error
+ FindRecordById(recId int) (*NameserverRecord, *NameserverDomain, error)
+}
+
+type NameserverServiceOp struct {
+ client *Client
+}
+
+var _ NameserverService = &NameserverServiceOp{}
+
+type NameserverCheckResponse struct {
+ Details []string
+ Status string
+}
+
+type NameserverRecordRequest struct {
+ RoId int `structs:"roId,omitempty"`
+ Domain string `structs:"domain,omitempty"`
+ Type string `structs:"type"`
+ Content string `structs:"content"`
+ Name string `structs:"name,omitempty"`
+ Ttl int `structs:"ttl,omitempty"`
+ Priority int `structs:"prio,omitempty"`
+ UrlRedirectType string `structs:"urlRedirectType,omitempty"`
+ UrlRedirectTitle string `structs:"urlRedirectTitle,omitempty"`
+ UrlRedirectDescription string `structs:"urlRedirectDescription,omitempty"`
+ UrlRedirectFavIcon string `structs:"urlRedirectFavIcon,omitempty"`
+ UrlRedirectKeywords string `structs:"urlRedirectKeywords,omitempty"`
+}
+
+type NameserverCreateRequest struct {
+ Domain string `structs:"domain"`
+ Type string `structs:"type"`
+ Nameservers []string `structs:"ns,omitempty"`
+ MasterIp string `structs:"masterIp,omitempty"`
+ Web string `structs:"web,omitempty"`
+ Mail string `structs:"mail,omitempty"`
+ SoaEmail string `structs:"soaEmail,omitempty"`
+ UrlRedirectType string `structs:"urlRedirectType,omitempty"`
+ UrlRedirectTitle string `structs:"urlRedirectTitle,omitempty"`
+ UrlRedirectDescription string `structs:"urlRedirectDescription,omitempty"`
+ UrlRedirectFavIcon string `structs:"urlRedirectFavIcon,omitempty"`
+ UrlRedirectKeywords string `structs:"urlRedirectKeywords,omitempty"`
+ Testing bool `structs:"testing,omitempty"`
+}
+
+type NameserverInfoRequest struct {
+ Domain string `structs:"domain,omitempty"`
+ RoId int `structs:"roId,omitempty"`
+ RecordId int `structs:"recordId,omitempty"`
+ Type string `structs:"type,omitempty"`
+ Name string `structs:"name,omitempty"`
+ Content string `structs:"content,omitempty"`
+ Ttl int `structs:"ttl,omitempty"`
+ Prio int `structs:"prio,omitempty"`
+}
+
+type NamserverInfoResponse struct {
+ RoId int
+ Domain string
+ Type string
+ MasterIp string
+ LastZoneCheck time.Time
+ SlaveDns interface{}
+ SOAserial string
+ Count int
+ Records []NameserverRecord `mapstructure:"record"`
+}
+
+type NameserverRecord struct {
+ Id int
+ Name string
+ Type string
+ Content string
+ Ttl int
+ Prio int
+ UrlRedirectType string
+ UrlRedirectTitle string
+ UrlRedirectDescription string
+ UrlRedirectKeywords string
+ UrlRedirectFavIcon string
+}
+
+type NamserverListResponse struct {
+ Count int
+ Domains []NameserverDomain `mapstructure:"domains"`
+}
+
+type NameserverDomain struct {
+ RoId int `mapstructure:"roId"`
+ Domain string `mapstructure:"domain"`
+ Type string `mapstructure:"type"`
+ MasterIp string `mapstructure:"masterIp"`
+ Mail string `mapstructure:"mail"`
+ Web string `mapstructure:"web"`
+ Url string `mapstructure:"url"`
+ Ipv4 string `mapstructure:"ipv4"`
+ Ipv6 string `mapstructure:"ipv6"`
+}
+
+func (s *NameserverServiceOp) Check(domain string, nameservers []string) (*NameserverCheckResponse, error) {
+ req := s.client.NewRequest(methodNameserverCheck, map[string]interface{}{
+ "domain": domain,
+ "ns": nameservers,
+ })
+
+ resp, err := s.client.Do(*req)
+ if err != nil {
+ return nil, err
+ }
+
+ var result NameserverCheckResponse
+ err = mapstructure.Decode(*resp, &result)
+ if err != nil {
+ return nil, err
+ }
+
+ return &result, nil
+}
+
+func (s *NameserverServiceOp) Info(request *NameserverInfoRequest) (*NamserverInfoResponse, error) {
+ req := s.client.NewRequest(methodNameserverInfo, structs.Map(request))
+
+ resp, err := s.client.Do(*req)
+ if err != nil {
+ return nil, err
+ }
+ var result NamserverInfoResponse
+ err = mapstructure.Decode(*resp, &result)
+ if err != nil {
+ return nil, err
+ }
+
+ return &result, nil
+}
+
+func (s *NameserverServiceOp) List(domain string) (*NamserverListResponse, error) {
+ requestMap := map[string]interface{}{
+ "domain": "*",
+ "wide": 2,
+ }
+ if domain != "" {
+ requestMap["domain"] = domain
+ }
+ req := s.client.NewRequest(methodNameserverList, requestMap)
+
+ resp, err := s.client.Do(*req)
+ if err != nil {
+ return nil, err
+ }
+ var result NamserverListResponse
+ err = mapstructure.Decode(*resp, &result)
+ if err != nil {
+ return nil, err
+ }
+
+ return &result, nil
+}
+
+func (s *NameserverServiceOp) Create(request *NameserverCreateRequest) (int, error) {
+ req := s.client.NewRequest(methodNameserverCreate, structs.Map(request))
+
+ resp, err := s.client.Do(*req)
+ if err != nil {
+ return 0, err
+ }
+
+ var result map[string]int
+ err = mapstructure.Decode(*resp, &result)
+ if err != nil {
+ return 0, err
+ }
+
+ return result["roId"], nil
+}
+
+func (s *NameserverServiceOp) CreateRecord(request *NameserverRecordRequest) (int, error) {
+ req := s.client.NewRequest(methodNameserverCreateRecord, structs.Map(request))
+
+ resp, err := s.client.Do(*req)
+ if err != nil {
+ return 0, err
+ }
+
+ var result map[string]int
+ err = mapstructure.Decode(*resp, &result)
+ if err != nil {
+ return 0, err
+ }
+
+ return result["id"], nil
+}
+
+func (s *NameserverServiceOp) UpdateRecord(recId int, request *NameserverRecordRequest) error {
+ if request == nil {
+ return errors.New("Request can't be nil")
+ }
+ requestMap := structs.Map(request)
+ requestMap["id"] = recId
+
+ req := s.client.NewRequest(methodNameserverUpdateRecord, requestMap)
+
+ _, err := s.client.Do(*req)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (s *NameserverServiceOp) DeleteRecord(recId int) error {
+ req := s.client.NewRequest(methodNameserverDeleteRecord, map[string]interface{}{
+ "id": recId,
+ })
+
+ _, err := s.client.Do(*req)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (s *NameserverServiceOp) FindRecordById(recId int) (*NameserverRecord, *NameserverDomain, error) {
+ listResp, err := s.client.Nameservers.List("")
+ if err != nil {
+ return nil, nil, err
+ }
+
+ for _, domainItem := range listResp.Domains {
+ resp, err := s.client.Nameservers.Info(&NameserverInfoRequest{RoId: domainItem.RoId})
+ if err != nil {
+ return nil, nil, err
+ }
+
+ for _, record := range resp.Records {
+ if record.Id == recId {
+ return &record, &domainItem, nil
+ }
+ }
+ }
+
+ return nil, nil, fmt.Errorf("couldn't find INWX Record for id %d", recId)
+
+}