diff --git a/Gopkg.lock b/Gopkg.lock
index cc31d6fa5..dbea7b1e4 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -565,6 +565,12 @@
packages = ["."]
revision = "62e9147c64a1ed519147b62a56a14e83e2be02c1"
+[[projects]]
+ name = "github.com/fatih/structs"
+ packages = ["."]
+ revision = "4966fc68f5b7593aafa6cbbba2d65ec6e1416047"
+ version = "v1.1.0"
+
[[projects]]
branch = "master"
name = "github.com/flynn/go-shlex"
@@ -845,6 +851,12 @@
revision = "59fac5042749a5afb9af70e813da1dd5474f0167"
version = "1.0.1"
+[[projects]]
+ branch = "master"
+ name = "github.com/kolo/xmlrpc"
+ packages = ["."]
+ revision = "16bdd962781df9696f40cc2bab924f1a855a7f89"
+
[[projects]]
branch = "master"
name = "github.com/konsorten/go-windows-terminal-sequences"
@@ -989,10 +1001,10 @@
revision = "2bca23e0e452137f789efbc8610126fd8b94f73b"
[[projects]]
- branch = "master"
name = "github.com/mitchellh/mapstructure"
packages = ["."]
- revision = "b4575eea38cca1123ec2dc90c26529b5c5acfcff"
+ revision = "3536a929edddb9a5b34bd6861dc4a9647cb459fe"
+ version = "v1.1.2"
[[projects]]
branch = "master"
@@ -1200,6 +1212,12 @@
revision = "a67f783a3814b8729bd2dac5780b5f78f8dbd64d"
version = "v1.1.0"
+[[projects]]
+ branch = "master"
+ name = "github.com/smueller18/goinwx"
+ packages = ["."]
+ revision = "5d138389109eca96463f44f692408f0d1c731278"
+
[[projects]]
name = "github.com/spf13/pflag"
packages = ["."]
@@ -1247,6 +1265,16 @@
revision = "b2b6a672cf1e5b90748f79b8b81fc8c5cf0571a1"
version = "1.0.2"
+[[projects]]
+ name = "github.com/transip/gotransip"
+ packages = [
+ ".",
+ "domain",
+ "util"
+ ]
+ revision = "1dc93a7db3567a5ccf865106afac88278ba940cf"
+ version = "v5.8.1"
+
[[projects]]
branch = "master"
name = "github.com/tuvistavie/securerandom"
@@ -1362,6 +1390,7 @@
"providers/dns/bluecat",
"providers/dns/cloudflare",
"providers/dns/cloudxns",
+ "providers/dns/conoha",
"providers/dns/digitalocean",
"providers/dns/dnsimple",
"providers/dns/dnsmadeeasy",
@@ -1378,10 +1407,13 @@
"providers/dns/glesys",
"providers/dns/godaddy",
"providers/dns/hostingde",
+ "providers/dns/httpreq",
"providers/dns/iij",
+ "providers/dns/inwx",
"providers/dns/lightsail",
"providers/dns/linode",
"providers/dns/linodev4",
+ "providers/dns/mydnsjp",
"providers/dns/namecheap",
"providers/dns/namedotcom",
"providers/dns/netcup",
@@ -1394,11 +1426,14 @@
"providers/dns/rfc2136",
"providers/dns/route53",
"providers/dns/sakuracloud",
+ "providers/dns/selectel",
"providers/dns/stackpath",
+ "providers/dns/transip",
"providers/dns/vegadns",
+ "providers/dns/vscale",
"providers/dns/vultr"
]
- revision = "1151b4e3befc51b7b215179c87791753721dc6d5"
+ revision = "a5f0a3ff8026e05cbdd11c391c0e25122497c736"
[[projects]]
branch = "master"
diff --git a/docs/configuration/acme.md b/docs/configuration/acme.md
index e25ef3882..12bb6abd0 100644
--- a/docs/configuration/acme.md
+++ b/docs/configuration/acme.md
@@ -282,6 +282,7 @@ Here is a list of supported `provider`s, that can automate the DNS verification,
| [Blue Cat](https://www.bluecatnetworks.com/) | `bluecat` | `BLUECAT_SERVER_URL`, `BLUECAT_USER_NAME`, `BLUECAT_PASSWORD`, `BLUECAT_CONFIG_NAME`, `BLUECAT_DNS_VIEW` | Not tested yet |
| [Cloudflare](https://www.cloudflare.com) | `cloudflare` | `CF_API_EMAIL`, `CF_API_KEY` - The `Global API Key` needs to be used, not the `Origin CA Key` | YES |
| [CloudXNS](https://www.cloudxns.net) | `cloudxns` | `CLOUDXNS_API_KEY`, `CLOUDXNS_SECRET_KEY` | Not tested yet |
+| [ConoHa](https://www.conoha.jp) | `conoha` | `CONOHA_TENANT_ID`, `CONOHA_API_USERNAME`, `CONOHA_API_PASSWORD` | YES |
| [DigitalOcean](https://www.digitalocean.com) | `digitalocean` | `DO_AUTH_TOKEN` | YES |
| [DNSimple](https://dnsimple.com) | `dnsimple` | `DNSIMPLE_OAUTH_TOKEN`, `DNSIMPLE_BASE_URL` | Not tested yet |
| [DNS Made Easy](https://dnsmadeeasy.com) | `dnsmadeeasy` | `DNSMADEEASY_API_KEY`, `DNSMADEEASY_API_SECRET`, `DNSMADEEASY_SANDBOX` | Not tested yet |
@@ -289,7 +290,7 @@ Here is a list of supported `provider`s, that can automate the DNS verification,
| [DreamHost](https://www.dreamhost.com/) | `dreamhost` | `DREAMHOST_API_KEY` | YES |
| [Duck DNS](https://www.duckdns.org/) | `duckdns` | `DUCKDNS_TOKEN` | No |
| [Dyn](https://dyn.com) | `dyn` | `DYN_CUSTOMER_NAME`, `DYN_USER_NAME`, `DYN_PASSWORD` | Not tested yet |
-| External Program | `exec` | `EXEC_PATH` | Not tested yet |
+| External Program | `exec` | `EXEC_PATH` | YES |
| [Exoscale](https://www.exoscale.com) | `exoscale` | `EXOSCALE_API_KEY`, `EXOSCALE_API_SECRET`, `EXOSCALE_ENDPOINT` | YES |
| [Fast DNS](https://www.akamai.com/) | `fastdns` | `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`, `AKAMAI_ACCESS_TOKEN` | Not tested yet |
| [Gandi](https://www.gandi.net) | `gandi` | `GANDI_API_KEY` | Not tested yet |
@@ -298,11 +299,14 @@ Here is a list of supported `provider`s, that can automate the DNS verification,
| [GoDaddy](https://godaddy.com/domains) | `godaddy` | `GODADDY_API_KEY`, `GODADDY_API_SECRET` | Not tested yet |
| [Google Cloud DNS](https://cloud.google.com/dns/docs/) | `gcloud` | `GCE_PROJECT`, `GCE_SERVICE_ACCOUNT_FILE` | YES |
| [hosting.de](https://www.hosting.de) | `hostingde` | `HOSTINGDE_API_KEY`, `HOSTINGDE_ZONE_NAME` | Not tested yet |
+| HTTP request | `httpreq` | `HTTPREQ_ENDPOINT`, `HTTPREQ_MODE`, `HTTPREQ_USERNAME`, `HTTPREQ_PASSWORD` | YES |
| [IIJ](https://www.iij.ad.jp/) | `iij` | `IIJ_API_ACCESS_KEY`, `IIJ_API_SECRET_KEY`, `IIJ_DO_SERVICE_CODE` | Not tested yet |
+| [INWX](https://www.inwx.de/en) | `inwx` | `INWX_USERNAME`, `INWX_PASSWORD` | YES |
| [Lightsail](https://aws.amazon.com/lightsail/) | `lightsail` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `DNS_ZONE` | Not tested yet |
| [Linode](https://www.linode.com) | `linode` | `LINODE_API_KEY` | Not tested yet |
| [Linode v4](https://www.linode.com) | `linodev4` | `LINODE_TOKEN` | Not tested yet |
| manual | - | none, but you need to run Traefik interactively, turn on `acmeLogging` to see instructions and press Enter. | YES |
+| [MyDNS.jp](https://www.mydns.jp/) | `mydnsjp` | `MYDNSJP_MASTER_ID`, `MYDNSJP_PASSWORD` | YES |
| [Namecheap](https://www.namecheap.com) | `namecheap` | `NAMECHEAP_API_USER`, `NAMECHEAP_API_KEY` | YES |
| [name.com](https://www.name.com/) | `namedotcom` | `NAMECOM_USERNAME`, `NAMECOM_API_TOKEN`, `NAMECOM_SERVER` | Not tested yet |
| [Netcup](https://www.netcup.eu/) | `netcup` | `NETCUP_CUSTOMER_NUMBER`, `NETCUP_API_KEY`, `NETCUP_API_PASSWORD` | Not tested yet |
@@ -315,8 +319,11 @@ Here is a list of supported `provider`s, that can automate the DNS verification,
| [RFC2136](https://tools.ietf.org/html/rfc2136) | `rfc2136` | `RFC2136_TSIG_KEY`, `RFC2136_TSIG_SECRET`, `RFC2136_TSIG_ALGORITHM`, `RFC2136_NAMESERVER` | Not tested yet |
| [Route 53](https://aws.amazon.com/route53/) | `route53` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `[AWS_REGION]`, `[AWS_HOSTED_ZONE_ID]` or a configured user/instance IAM profile. | YES |
| [Sakura Cloud](https://cloud.sakura.ad.jp/) | `sakuracloud` | `SAKURACLOUD_ACCESS_TOKEN`, `SAKURACLOUD_ACCESS_TOKEN_SECRET` | Not tested yet |
+| [Selectel](https://selectel.ru/en/) | `selectel` | `SELECTEL_API_TOKEN` | YES |
| [Stackpath](https://www.stackpath.com/) | `stackpath` | `STACKPATH_CLIENT_ID`, `STACKPATH_CLIENT_SECRET`, `STACKPATH_STACK_ID` | Not tested yet |
+| [TransIP](https://www.transip.nl/) | `transip` | `TRANSIP_ACCOUNT_NAME`, `TRANSIP_PRIVATE_KEY_PATH` | YES |
| [VegaDNS](https://github.com/shupp/VegaDNS-API) | `vegadns` | `SECRET_VEGADNS_KEY`, `SECRET_VEGADNS_SECRET`, `VEGADNS_URL` | Not tested yet |
+| [Vscale](https://vscale.io/) | `vscale` | `VSCALE_API_TOKEN` | YES |
| [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` | Not tested yet |
#### `resolvers`
diff --git a/integration/resources/compose/pebble.yml b/integration/resources/compose/pebble.yml
index 2828c66ef..66c358f47 100644
--- a/integration/resources/compose/pebble.yml
+++ b/integration/resources/compose/pebble.yml
@@ -1,5 +1,5 @@
pebble:
- image: letsencrypt/pebble:2018-07-27
+ image: letsencrypt/pebble:2018-11-02
command: pebble --dnsserver ${DOCKER_HOST_IP}:5053
ports:
- 14000:14000
diff --git a/vendor/github.com/fatih/structs/LICENSE b/vendor/github.com/fatih/structs/LICENSE
new file mode 100644
index 000000000..34504e4b3
--- /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 000000000..e69783230
--- /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 000000000..3a8770652
--- /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 000000000..136a31eba
--- /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 000000000..8103dd139
--- /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 000000000..db2eb4f64
--- /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 000000000..b81fff4c0
--- /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 000000000..bb1285ff7
--- /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 000000000..acb8251b2
--- /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 000000000..6742a1c74
--- /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 000000000..8766403af
--- /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/decode_hooks.go b/vendor/github.com/mitchellh/mapstructure/decode_hooks.go
index afcfd5eed..1f0abc65a 100644
--- a/vendor/github.com/mitchellh/mapstructure/decode_hooks.go
+++ b/vendor/github.com/mitchellh/mapstructure/decode_hooks.go
@@ -2,6 +2,8 @@ package mapstructure
import (
"errors"
+ "fmt"
+ "net"
"reflect"
"strconv"
"strings"
@@ -115,6 +117,69 @@ func StringToTimeDurationHookFunc() DecodeHookFunc {
}
}
+// 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.
//
diff --git a/vendor/github.com/mitchellh/mapstructure/mapstructure.go b/vendor/github.com/mitchellh/mapstructure/mapstructure.go
index 39ec1e943..256ee63fb 100644
--- a/vendor/github.com/mitchellh/mapstructure/mapstructure.go
+++ b/vendor/github.com/mitchellh/mapstructure/mapstructure.go
@@ -114,12 +114,12 @@ type Metadata struct {
Unused []string
}
-// Decode takes a map and uses reflection to convert it into the
-// given Go native structure. val must be a pointer to a struct.
-func Decode(m interface{}, rawVal interface{}) error {
+// 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: rawVal,
+ Result: output,
}
decoder, err := NewDecoder(config)
@@ -127,7 +127,7 @@ func Decode(m interface{}, rawVal interface{}) error {
return err
}
- return decoder.Decode(m)
+ return decoder.Decode(input)
}
// WeakDecode is the same as Decode but is shorthand to enable
@@ -147,6 +147,40 @@ func WeakDecode(input, output interface{}) error {
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.
@@ -184,70 +218,91 @@ func NewDecoder(config *DecoderConfig) (*Decoder, error) {
// Decode decodes the given raw interface to the target pointer specified
// by the configuration.
-func (d *Decoder) Decode(raw interface{}) error {
- return d.decode("", raw, reflect.ValueOf(d.config.Result).Elem())
+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, data interface{}, val reflect.Value) error {
- if data == nil {
- // If the data is nil, then we don't set anything.
+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
}
- dataVal := reflect.ValueOf(data)
- if !dataVal.IsValid() {
- // If the data value is invalid, then we just set the value
+ if !inputVal.IsValid() {
+ // If the input value is invalid, then we just set the value
// to be the zero value.
- val.Set(reflect.Zero(val.Type()))
+ 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 data.
+ // We have a DecodeHook, so let's pre-process the input.
var err error
- data, err = DecodeHookExec(
+ input, err = DecodeHookExec(
d.config.DecodeHook,
- dataVal.Type(), val.Type(), data)
+ inputVal.Type(), outVal.Type(), input)
if err != nil {
return fmt.Errorf("error decoding '%s': %s", name, err)
}
}
var err error
- dataKind := getKind(val)
- switch dataKind {
+ outputKind := getKind(outVal)
+ switch outputKind {
case reflect.Bool:
- err = d.decodeBool(name, data, val)
+ err = d.decodeBool(name, input, outVal)
case reflect.Interface:
- err = d.decodeBasic(name, data, val)
+ err = d.decodeBasic(name, input, outVal)
case reflect.String:
- err = d.decodeString(name, data, val)
+ err = d.decodeString(name, input, outVal)
case reflect.Int:
- err = d.decodeInt(name, data, val)
+ err = d.decodeInt(name, input, outVal)
case reflect.Uint:
- err = d.decodeUint(name, data, val)
+ err = d.decodeUint(name, input, outVal)
case reflect.Float32:
- err = d.decodeFloat(name, data, val)
+ err = d.decodeFloat(name, input, outVal)
case reflect.Struct:
- err = d.decodeStruct(name, data, val)
+ err = d.decodeStruct(name, input, outVal)
case reflect.Map:
- err = d.decodeMap(name, data, val)
+ err = d.decodeMap(name, input, outVal)
case reflect.Ptr:
- err = d.decodePtr(name, data, val)
+ err = d.decodePtr(name, input, outVal)
case reflect.Slice:
- err = d.decodeSlice(name, data, val)
+ err = d.decodeSlice(name, input, outVal)
case reflect.Array:
- err = d.decodeArray(name, data, val)
+ err = d.decodeArray(name, input, outVal)
case reflect.Func:
- err = d.decodeFunc(name, data, val)
+ 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, dataKind)
+ 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 metadata.
+ // 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)
}
@@ -258,7 +313,19 @@ func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error
// 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())
}
@@ -275,7 +342,7 @@ func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value)
}
func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value) error {
- dataVal := reflect.ValueOf(data)
+ dataVal := reflect.Indirect(reflect.ValueOf(data))
dataKind := getKind(dataVal)
converted := true
@@ -327,7 +394,7 @@ func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value)
}
func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) error {
- dataVal := reflect.ValueOf(data)
+ dataVal := reflect.Indirect(reflect.ValueOf(data))
dataKind := getKind(dataVal)
dataType := dataVal.Type()
@@ -369,7 +436,7 @@ func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) er
}
func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) error {
- dataVal := reflect.ValueOf(data)
+ dataVal := reflect.Indirect(reflect.ValueOf(data))
dataKind := getKind(dataVal)
switch {
@@ -412,7 +479,7 @@ func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) e
}
func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) error {
- dataVal := reflect.ValueOf(data)
+ dataVal := reflect.Indirect(reflect.ValueOf(data))
dataKind := getKind(dataVal)
switch {
@@ -443,7 +510,7 @@ func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) e
}
func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value) error {
- dataVal := reflect.ValueOf(data)
+ dataVal := reflect.Indirect(reflect.ValueOf(data))
dataKind := getKind(dataVal)
dataType := dataVal.Type()
@@ -499,38 +566,68 @@ func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) er
valMap = reflect.MakeMap(mapType)
}
- // Check input type
+ // Check input type and based on the input type jump to the proper func
dataVal := reflect.Indirect(reflect.ValueOf(data))
- if dataVal.Kind() != reflect.Map {
- // In weak mode, we accept a slice of maps as an input...
+ 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 {
- switch dataVal.Kind() {
- case reflect.Array, reflect.Slice:
- // 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
- }
+ 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)
@@ -563,22 +660,128 @@ func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) er
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)
+ }
- 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
+ }
}
-
- if err := d.decode(name, data, reflect.Indirect(realVal)); err != nil {
- return err
- }
-
- val.Set(realVal)
return nil
}
@@ -604,30 +807,44 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value)
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 {
- if d.config.WeaklyTypedInput {
- switch {
- // Empty maps turn into empty slices
- case dataValKind == reflect.Map:
- if dataVal.Len() == 0 {
- val.Set(reflect.MakeSlice(sliceType, 0, 0))
- return nil
- }
-
- // 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)
- }
- }
-
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())
}
@@ -737,10 +954,29 @@ func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value)
}
dataValKind := dataVal.Kind()
- if dataValKind != reflect.Map {
- return fmt.Errorf("'%s' expected a map, got '%s'", name, dataValKind)
- }
+ 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(
diff --git a/vendor/github.com/smueller18/goinwx/LICENSE b/vendor/github.com/smueller18/goinwx/LICENSE
new file mode 100644
index 000000000..9df6656cd
--- /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 000000000..14b2073ae
--- /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 000000000..0ae36f5c2
--- /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 000000000..ddbc86a20
--- /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 000000000..b4d67d621
--- /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 000000000..38269b461
--- /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)
+
+}
diff --git a/vendor/github.com/transip/gotransip/LICENSE b/vendor/github.com/transip/gotransip/LICENSE
new file mode 100644
index 000000000..352d193e3
--- /dev/null
+++ b/vendor/github.com/transip/gotransip/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 TransIP B.V.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/transip/gotransip/api.go b/vendor/github.com/transip/gotransip/api.go
new file mode 100644
index 000000000..46c684480
--- /dev/null
+++ b/vendor/github.com/transip/gotransip/api.go
@@ -0,0 +1,12 @@
+package gotransip
+
+// CancellationTime represents the possible ways of canceling a contract
+type CancellationTime string
+
+var (
+ // CancellationTimeEnd specifies to cancel the contract when the contract was
+ // due to end anyway
+ CancellationTimeEnd CancellationTime = "end"
+ // CancellationTimeImmediately specifies to cancel the contract immediately
+ CancellationTimeImmediately CancellationTime = "immediately"
+)
diff --git a/vendor/github.com/transip/gotransip/client.go b/vendor/github.com/transip/gotransip/client.go
new file mode 100644
index 000000000..0be9c400a
--- /dev/null
+++ b/vendor/github.com/transip/gotransip/client.go
@@ -0,0 +1,119 @@
+package gotransip
+
+import (
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "os"
+)
+
+const (
+ transipAPIHost = "api.transip.nl"
+ transipAPINamespace = "http://www.transip.nl/soap"
+)
+
+// APIMode specifies in which mode the API is used. Currently this is only
+// supports either readonly or readwrite
+type APIMode string
+
+var (
+ // APIModeReadOnly specifies that no changes can be made from API calls
+ APIModeReadOnly APIMode = "readonly"
+ // APIModeReadWrite specifies that changes can be made from API calls
+ APIModeReadWrite APIMode = "readwrite"
+)
+
+// ClientConfig is a tool to easily create a new Client object
+type ClientConfig struct {
+ AccountName string
+ PrivateKeyPath string
+ PrivateKeyBody []byte
+ Mode APIMode
+}
+
+// Client is the interface which all clients should implement
+type Client interface {
+ Call(SoapRequest, interface{}) error // execute request on client
+}
+
+// SOAPClient represents a TransIP API SOAP client, implementing the Client
+// interface
+type SOAPClient struct {
+ soapClient soapClient
+}
+
+// Call performs given SOAP request and fills the response into result
+func (c SOAPClient) Call(req SoapRequest, result interface{}) error {
+ return c.soapClient.call(req, result)
+}
+
+// NewSOAPClient returns a new SOAPClient object for given config
+// ClientConfig's PrivateKeyPath will override potentially given PrivateKeyBody
+func NewSOAPClient(c ClientConfig) (SOAPClient, error) {
+ // check account name
+ if len(c.AccountName) == 0 {
+ return SOAPClient{}, errors.New("AccountName is required")
+ }
+
+ // check if private key was given in any form
+ if len(c.PrivateKeyPath) == 0 && len(c.PrivateKeyBody) == 0 {
+ return SOAPClient{}, errors.New("PrivateKeyPath or PrivateKeyBody is required")
+ }
+
+ // if PrivateKeyPath was set, this will override any given PrivateKeyBody
+ if len(c.PrivateKeyPath) > 0 {
+ // try to open private key and read contents
+ if _, err := os.Stat(c.PrivateKeyPath); err != nil {
+ return SOAPClient{}, fmt.Errorf("could not open private key: %s", err.Error())
+ }
+
+ // read private key so we can pass the body to the soapClient
+ var err error
+ c.PrivateKeyBody, err = ioutil.ReadFile(c.PrivateKeyPath)
+ if err != nil {
+ return SOAPClient{}, err
+ }
+ }
+
+ // default to APIMode read/write
+ if len(c.Mode) == 0 {
+ c.Mode = APIModeReadWrite
+ }
+
+ // create soapClient and pass it to a new Client pointer
+ sc := soapClient{
+ Login: c.AccountName,
+ Mode: c.Mode,
+ PrivateKey: c.PrivateKeyBody,
+ }
+
+ return SOAPClient{
+ soapClient: sc,
+ }, nil
+}
+
+// FakeSOAPClient is a client doing nothing except implementing the gotransip.Client
+// interface
+// you can however set a fixture XML body which Call will try to Unmarshal into
+// result
+// useful for testing
+type FakeSOAPClient struct {
+ fixture []byte // preset this fixture so Call can use it to Unmarshal
+}
+
+// FixtureFromFile reads file and sets content as FakeSOAPClient's fixture
+func (f *FakeSOAPClient) FixtureFromFile(file string) (err error) {
+ // read fixture file
+ f.fixture, err = ioutil.ReadFile(file)
+ if err != nil {
+ err = fmt.Errorf("could not read fixture from file %s: %s", file, err.Error())
+ }
+
+ return
+}
+
+// Call doesn't do anything except fill the XML unmarshalled result
+func (f FakeSOAPClient) Call(req SoapRequest, result interface{}) error {
+ // this fake client just parses given fixture as if it was a SOAP response
+ return parseSoapResponse(f.fixture, req.Padding, 200, result)
+}
diff --git a/vendor/github.com/transip/gotransip/domain/api.go b/vendor/github.com/transip/gotransip/domain/api.go
new file mode 100644
index 000000000..f5f90f8e2
--- /dev/null
+++ b/vendor/github.com/transip/gotransip/domain/api.go
@@ -0,0 +1,314 @@
+package domain
+
+import (
+ "github.com/transip/gotransip"
+)
+
+// This file holds all DomainService methods directly ported from TransIP API
+
+// BatchCheckAvailability checks the availability of multiple domains
+func BatchCheckAvailability(c gotransip.Client, domainNames []string) ([]CheckResult, error) {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "batchCheckAvailability",
+ }
+ sr.AddArgument("domainNames", domainNames)
+
+ var v struct {
+ V []CheckResult `xml:"item"`
+ }
+
+ err := c.Call(sr, &v)
+ return v.V, err
+}
+
+// CheckAvailability returns the availability status of a domain.
+func CheckAvailability(c gotransip.Client, domainName string) (Status, error) {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "checkAvailability",
+ }
+ sr.AddArgument("domainName", domainName)
+
+ var v Status
+ err := c.Call(sr, &v)
+ return v, err
+}
+
+// GetWhois returns the whois of a domain name
+func GetWhois(c gotransip.Client, domainName string) (string, error) {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "getWhois",
+ }
+ sr.AddArgument("domainName", domainName)
+
+ var v string
+ err := c.Call(sr, &v)
+ return v, err
+}
+
+// GetDomainNames returns list with domain names or error when this failed
+func GetDomainNames(c gotransip.Client) ([]string, error) {
+ var d = struct {
+ D []string `xml:"item"`
+ }{}
+ err := c.Call(gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "getDomainNames",
+ }, &d)
+
+ return d.D, err
+}
+
+// GetInfo returns Domain for given name or error when this failed
+func GetInfo(c gotransip.Client, domainName string) (Domain, error) {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "getInfo",
+ }
+ sr.AddArgument("domainName", domainName)
+
+ var d Domain
+ err := c.Call(sr, &d)
+
+ return d, err
+}
+
+// BatchGetInfo returns array of Domain for given name or error when this failed
+func BatchGetInfo(c gotransip.Client, domainNames []string) ([]Domain, error) {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "batchGetInfo",
+ }
+ sr.AddArgument("domainNames", domainNames)
+
+ var d = struct {
+ D []Domain `xml:"item"`
+ }{}
+ err := c.Call(sr, &d)
+
+ return d.D, err
+}
+
+// GetAuthCode returns the Auth code for a domainName
+func GetAuthCode(c gotransip.Client, domainName string) (string, error) {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "getAuthCode",
+ }
+ sr.AddArgument("domainName", domainName)
+
+ var v string
+ err := c.Call(sr, &v)
+ return v, err
+}
+
+// GetIsLocked returns the lock status for a domainName
+func GetIsLocked(c gotransip.Client, domainName string) (bool, error) {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "getIsLocked",
+ }
+ sr.AddArgument("domainName", domainName)
+
+ var v bool
+ err := c.Call(sr, &v)
+ return v, err
+}
+
+// Register registers a domain name and will automatically create and sign a proposition for it
+func Register(c gotransip.Client, domain string) error {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "register",
+ }
+ sr.AddArgument("domain", domain)
+
+ return c.Call(sr, nil)
+}
+
+// Cancel cancels a domain name, will automatically create and sign a cancellation document
+func Cancel(c gotransip.Client, domainName string, endTime gotransip.CancellationTime) error {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "cancel",
+ }
+ sr.AddArgument("domainName", domainName)
+ sr.AddArgument("endTime", string(endTime))
+
+ return c.Call(sr, nil)
+}
+
+// TransferWithOwnerChange transfers a domain with changing the owner
+func TransferWithOwnerChange(c gotransip.Client, domain, authCode string) error {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "transferWithOwnerChange",
+ }
+ sr.AddArgument("domain", domain)
+ sr.AddArgument("authCode", authCode)
+
+ return c.Call(sr, nil)
+}
+
+// TransferWithoutOwnerChange transfers a domain without changing the owner
+func TransferWithoutOwnerChange(c gotransip.Client, domain, authCode string) error {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "transferWithoutOwnerChange",
+ }
+ sr.AddArgument("domain", domain)
+ sr.AddArgument("authCode", authCode)
+
+ return c.Call(sr, nil)
+}
+
+// SetNameservers starts a nameserver change for this domain, will replace all
+// existing nameservers with the new nameservers
+func SetNameservers(c gotransip.Client, domainName string, nameservers Nameservers) error {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "setNameservers",
+ }
+ sr.AddArgument("domainName", domainName)
+ sr.AddArgument("nameservers", nameservers)
+
+ return c.Call(sr, nil)
+}
+
+// SetLock locks this domain
+func SetLock(c gotransip.Client, domainName string) error {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "setLock",
+ }
+ sr.AddArgument("domainName", domainName)
+
+ return c.Call(sr, nil)
+}
+
+// UnsetLock unlocks this domain
+func UnsetLock(c gotransip.Client, domainName string) error {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "unsetLock",
+ }
+ sr.AddArgument("domainName", domainName)
+
+ return c.Call(sr, nil)
+}
+
+// SetDNSEntries sets the DnsEntries for this Domain, will replace all existing
+// dns entries with the new entries
+func SetDNSEntries(c gotransip.Client, domainName string, dnsEntries DNSEntries) error {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "setDnsEntries",
+ }
+ sr.AddArgument("domainName", domainName)
+ sr.AddArgument("dnsEntries", dnsEntries)
+
+ return c.Call(sr, nil)
+}
+
+// SetOwner starts an owner change of a domain
+func SetOwner(c gotransip.Client, domainName, registrantWhoisContact WhoisContact) error {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "setOwner",
+ }
+ sr.AddArgument("domainName", domainName)
+ // make sure contact is of type registrant
+ registrantWhoisContact.Type = "registrant"
+ sr.AddArgument("registrantWhoisContact", registrantWhoisContact)
+
+ return c.Call(sr, nil)
+}
+
+// SetContacts starts a contact change of a domain, this will replace all existing contacts
+func SetContacts(c gotransip.Client, domainName, contacts WhoisContacts) error {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "setContacts",
+ }
+ sr.AddArgument("domainName", domainName)
+ sr.AddArgument("contacts", contacts)
+
+ return c.Call(sr, nil)
+}
+
+// GetAllTLDInfos returns slice with TLD objects or error when this failed
+func GetAllTLDInfos(c gotransip.Client) ([]TLD, error) {
+ var d = struct {
+ TLD []TLD `xml:"item"`
+ }{}
+ err := c.Call(gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "getAllTldInfos",
+ }, &d)
+
+ return d.TLD, err
+}
+
+// GetTldInfo returns info about a specific TLD
+func GetTldInfo(c gotransip.Client, tldName string) (TLD, error) {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "getTldInfo",
+ }
+ sr.AddArgument("tldName", tldName)
+
+ var v TLD
+ err := c.Call(sr, &v)
+ return v, err
+}
+
+// GetCurrentDomainAction returns info about the action this domain is currently running
+func GetCurrentDomainAction(c gotransip.Client, domainName string) (ActionResult, error) {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "getCurrentDomainAction",
+ }
+ sr.AddArgument("domainName", domainName)
+
+ var v ActionResult
+ err := c.Call(sr, &v)
+ return v, err
+}
+
+// RetryCurrentDomainActionWithNewData retries a failed domain action with new
+// domain data. The Domain.Name field must contain the name of the Domain. The
+// Nameservers, Contacts, DNSEntries fields contain the new data for this domain.
+func RetryCurrentDomainActionWithNewData(c gotransip.Client, domain Domain) error {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "retryCurrentDomainActionWithNewData",
+ }
+ sr.AddArgument("domain", domain)
+
+ return c.Call(sr, nil)
+}
+
+// RetryTransferWithDifferentAuthCode retries a transfer action with a new authcode
+func RetryTransferWithDifferentAuthCode(c gotransip.Client, domain Domain, newAuthCode string) error {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "retryTransferWithDifferentAuthCode",
+ }
+ sr.AddArgument("domain", domain)
+ sr.AddArgument("newAuthCode", newAuthCode)
+
+ return c.Call(sr, nil)
+}
+
+// CancelDomainAction cancels a failed domain action
+func CancelDomainAction(c gotransip.Client, domain string) error {
+ sr := gotransip.SoapRequest{
+ Service: serviceName,
+ Method: "cancelDomainAction",
+ }
+ sr.AddArgument("domain", domain)
+
+ return c.Call(sr, nil)
+}
diff --git a/vendor/github.com/transip/gotransip/domain/domain.go b/vendor/github.com/transip/gotransip/domain/domain.go
new file mode 100644
index 000000000..56d7b719a
--- /dev/null
+++ b/vendor/github.com/transip/gotransip/domain/domain.go
@@ -0,0 +1,405 @@
+package domain
+
+import (
+ "fmt"
+ "net"
+
+ "github.com/transip/gotransip"
+ "github.com/transip/gotransip/util"
+)
+
+const (
+ serviceName string = "DomainService"
+)
+
+// Domain represents a Transip_Domain object
+// as described at https://api.transip.nl/docs/transip.nl/class-Transip_Domain.html
+type Domain struct {
+ Name string `xml:"name"`
+ Nameservers []Nameserver `xml:"nameservers>item"`
+ Contacts []WhoisContact `xml:"contacts>item"`
+ DNSEntries []DNSEntry `xml:"dnsEntries>item"`
+ Branding Branding `xml:"branding"`
+ AuthorizationCode string `xml:"authCode"`
+ IsLocked bool `xml:"isLocked"`
+ RegistrationDate util.XMLTime `xml:"registrationDate"`
+ RenewalDate util.XMLTime `xml:"renewalDate"`
+}
+
+// EncodeParams returns Domain parameters ready to be used for constructing a signature
+func (d Domain) EncodeParams(prm gotransip.ParamsContainer) {
+ idx := prm.Len()
+ prm.Add(fmt.Sprintf("%d[name]", idx), d.Name)
+ prm.Add(fmt.Sprintf("%d[authCode]", idx), d.AuthorizationCode)
+ prm.Add(fmt.Sprintf("%d[isLocked]", idx), fmt.Sprintf("%t", d.IsLocked))
+ prm.Add(fmt.Sprintf("%d[registrationDate]", idx), d.RegistrationDate.Format("2006-01-02"))
+ prm.Add(fmt.Sprintf("%d[renewalDate]", idx), d.RenewalDate.Format("2006-01-02"))
+ // nameservers
+ for i, e := range d.Nameservers {
+ var ipv4, ipv6 string
+ if e.IPv4Address != nil {
+ ipv4 = e.IPv4Address.String()
+ }
+ if e.IPv6Address != nil {
+ ipv6 = e.IPv6Address.String()
+ }
+ prm.Add(fmt.Sprintf("%d[nameservers][%d][hostname]", idx, i), e.Hostname)
+ prm.Add(fmt.Sprintf("%d[nameservers][%d][ipv4]", idx, i), ipv4)
+ prm.Add(fmt.Sprintf("%d[nameservers][%d][ipv6]", idx, i), ipv6)
+ }
+ // contacts
+ for i, e := range d.Contacts {
+ prm.Add(fmt.Sprintf("%d[contacts][%d][type]", idx, i), e.Type)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][firstName]", idx, i), e.FirstName)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][middleName]", idx, i), e.MiddleName)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][lastName]", idx, i), e.LastName)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][companyName]", idx, i), e.CompanyName)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][companyKvk]", idx, i), e.CompanyKvk)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][companyType]", idx, i), e.CompanyType)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][street]", idx, i), e.Street)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][number]", idx, i), e.Number)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][postalCode]", idx, i), e.PostalCode)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][city]", idx, i), e.City)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][phoneNumber]", idx, i), e.PhoneNumber)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][faxNumber]", idx, i), e.FaxNumber)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][email]", idx, i), e.Email)
+ prm.Add(fmt.Sprintf("%d[contacts][%d][country]", idx, i), e.Country)
+ }
+ // dnsEntries
+ for i, e := range d.DNSEntries {
+ prm.Add(fmt.Sprintf("%d[dnsEntries][%d][name]", idx, i), e.Name)
+ prm.Add(fmt.Sprintf("%d[dnsEntries][%d][expire]", idx, i), fmt.Sprintf("%d", e.TTL))
+ prm.Add(fmt.Sprintf("%d[dnsEntries][%d][type]", idx, i), string(e.Type))
+ prm.Add(fmt.Sprintf("%d[dnsEntries][%d][content]", idx, i), e.Content)
+ }
+ // branding
+ prm.Add(fmt.Sprintf("%d[branding][companyName]", idx), d.Branding.CompanyName)
+ prm.Add(fmt.Sprintf("%d[branding][supportEmail]", idx), d.Branding.SupportEmail)
+ prm.Add(fmt.Sprintf("%d[branding][companyUrl]", idx), d.Branding.CompanyURL)
+ prm.Add(fmt.Sprintf("%d[branding][termsOfUsageUrl]", idx), d.Branding.TermsOfUsageURL)
+ prm.Add(fmt.Sprintf("%d[branding][bannerLine1]", idx), d.Branding.BannerLine1)
+ prm.Add(fmt.Sprintf("%d[branding][bannerLine2]", idx), d.Branding.BannerLine2)
+ prm.Add(fmt.Sprintf("%d[branding][bannerLine3]", idx), d.Branding.BannerLine3)
+}
+
+// EncodeArgs returns Domain XML body ready to be passed in the SOAP call
+func (d Domain) EncodeArgs(key string) string {
+ output := fmt.Sprintf(`<%s xsi:type="ns1:Domain">
+ %s
+ %s
+ %t
+ %s
+ %s`,
+ key, d.Name, d.AuthorizationCode, d.IsLocked,
+ d.RegistrationDate.Format("2006-01-02"), d.RenewalDate.Format("2006-01-02"),
+ ) + "\n"
+
+ output += Nameservers(d.Nameservers).EncodeArgs("nameservers") + "\n"
+ output += WhoisContacts(d.Contacts).EncodeArgs("contacts") + "\n"
+ output += DNSEntries(d.DNSEntries).EncodeArgs("dnsEntries") + "\n"
+ output += d.Branding.EncodeArgs("branding") + "\n"
+
+ return fmt.Sprintf("%s%s>", output, key)
+}
+
+// Capability represents the possible capabilities a TLD can have
+type Capability string
+
+var (
+ // CapabilityRequiresAuthCode defines this TLD requires an auth code
+ // to be transferred
+ CapabilityRequiresAuthCode Capability = "requiresAuthCode"
+ // CapabilityCanRegister defines this TLD can be registered
+ CapabilityCanRegister Capability = "canRegister"
+ // CapabilityCanTransferWithOwnerChange defines this TLD can be transferred
+ // with change of ownership
+ CapabilityCanTransferWithOwnerChange Capability = "canTransferWithOwnerChange"
+ // CapabilityCanTransferWithoutOwnerChange defines this TLD can be
+ // transferred without change of ownership
+ CapabilityCanTransferWithoutOwnerChange Capability = "canTransferWithoutOwnerChange"
+ // CapabilityCanSetLock defines this TLD allows to be locked
+ CapabilityCanSetLock Capability = "canSetLock"
+ // CapabilityCanSetOwner defines this TLD supports setting an owner
+ CapabilityCanSetOwner Capability = "canSetOwner"
+ // CapabilityCanSetContacts defines this TLD supports setting contacts
+ CapabilityCanSetContacts Capability = "canSetContacts"
+ // CapabilityCanSetNameservers defines this TLD supports setting nameservers
+ CapabilityCanSetNameservers Capability = "canSetNameservers"
+)
+
+// TLD represents a Transip_Tld object as described at
+// https://api.transip.nl/docs/transip.nl/class-Transip_Tld.html
+type TLD struct {
+ Name string `xml:"name"`
+ Price float64 `xml:"price"`
+ RenewalPrice float64 `xml:"renewalPrice"`
+ Capabilities []Capability `xml:"capabilities>item"`
+ RegistrationPeriodLength int64 `xml:"registrationPeriodLength"`
+ CancelTimeFrame int64 `xml:"cancelTimeFrame"`
+}
+
+// Nameserver represents a Transip_Nameserver object as described at
+// https://api.transip.nl/docs/transip.nl/class-Transip_Nameserver.html
+type Nameserver struct {
+ Hostname string `xml:"hostname"`
+ IPv4Address net.IP `xml:"ipv4"`
+ IPv6Address net.IP `xml:"ipv6"`
+}
+
+// Nameservers is just an array of Nameserver
+// basically only here so it can implement paramsEncoder
+type Nameservers []Nameserver
+
+// EncodeParams returns Nameservers parameters ready to be used for constructing a signature
+func (n Nameservers) EncodeParams(prm gotransip.ParamsContainer) {
+ idx := prm.Len()
+ for i, e := range n {
+ var ipv4, ipv6 string
+ if e.IPv4Address != nil {
+ ipv4 = e.IPv4Address.String()
+ }
+ if e.IPv6Address != nil {
+ ipv6 = e.IPv6Address.String()
+ }
+ prm.Add(fmt.Sprintf("%d[%d][hostname]", idx, i), e.Hostname)
+ prm.Add(fmt.Sprintf("%d[%d][ipv4]", idx, i), ipv4)
+ prm.Add(fmt.Sprintf("%d[%d][ipv6]", idx, i), ipv6)
+ }
+}
+
+// EncodeArgs returns Nameservers XML body ready to be passed in the SOAP call
+func (n Nameservers) EncodeArgs(key string) string {
+ output := fmt.Sprintf(`<%s SOAP-ENC:arrayType="ns1:Nameserver[%d]" xsi:type="ns1:ArrayOfNameserver">`, key, len(n)) + "\n"
+ for _, e := range n {
+ var ipv4, ipv6 string
+ if e.IPv4Address != nil {
+ ipv4 = e.IPv4Address.String()
+ }
+ if e.IPv6Address != nil {
+ ipv6 = e.IPv6Address.String()
+ }
+ output += fmt.Sprintf(` -
+ %s
+ %s
+ %s
+
`, e.Hostname, ipv4, ipv6) + "\n"
+ }
+
+ return fmt.Sprintf("%s%s>", output, key)
+}
+
+// DNSEntryType represents the possible types of DNS entries
+type DNSEntryType string
+
+var (
+ // DNSEntryTypeA represents an A-record
+ DNSEntryTypeA DNSEntryType = "A"
+ // DNSEntryTypeAAAA represents an AAAA-record
+ DNSEntryTypeAAAA DNSEntryType = "AAAA"
+ // DNSEntryTypeCNAME represents a CNAME-record
+ DNSEntryTypeCNAME DNSEntryType = "CNAME"
+ // DNSEntryTypeMX represents an MX-record
+ DNSEntryTypeMX DNSEntryType = "MX"
+ // DNSEntryTypeNS represents an NS-record
+ DNSEntryTypeNS DNSEntryType = "NS"
+ // DNSEntryTypeTXT represents a TXT-record
+ DNSEntryTypeTXT DNSEntryType = "TXT"
+ // DNSEntryTypeSRV represents an SRV-record
+ DNSEntryTypeSRV DNSEntryType = "SRV"
+)
+
+// DNSEntry represents a Transip_DnsEntry object as described at
+// https://api.transip.nl/docs/transip.nl/class-Transip_DnsEntry.html
+type DNSEntry struct {
+ Name string `xml:"name"`
+ TTL int64 `xml:"expire"`
+ Type DNSEntryType `xml:"type"`
+ Content string `xml:"content"`
+}
+
+// DNSEntries is just an array of DNSEntry
+// basically only here so it can implement paramsEncoder
+type DNSEntries []DNSEntry
+
+// EncodeParams returns DNSEntries parameters ready to be used for constructing a signature
+func (d DNSEntries) EncodeParams(prm gotransip.ParamsContainer) {
+ idx := prm.Len()
+ for i, e := range d {
+ prm.Add(fmt.Sprintf("%d[%d][name]", idx, i), e.Name)
+ prm.Add(fmt.Sprintf("%d[%d][expire]", idx, i), fmt.Sprintf("%d", e.TTL))
+ prm.Add(fmt.Sprintf("%d[%d][type]", idx, i), string(e.Type))
+ prm.Add(fmt.Sprintf("%d[%d][content]", idx, i), e.Content)
+ }
+}
+
+// EncodeArgs returns DNSEntries XML body ready to be passed in the SOAP call
+func (d DNSEntries) EncodeArgs(key string) string {
+ output := fmt.Sprintf(`<%s SOAP-ENC:arrayType="ns1:DnsEntry[%d]" xsi:type="ns1:ArrayOfDnsEntry">`, key, len(d)) + "\n"
+ for _, e := range d {
+ output += fmt.Sprintf(` -
+ %s
+ %d
+ %s
+ %s
+
`, e.Name, e.TTL, e.Type, e.Content) + "\n"
+ }
+
+ return fmt.Sprintf("%s%s>", output, key)
+}
+
+// Status reflects the current status of a domain in a check result
+type Status string
+
+var (
+ // StatusInYourAccount means he domain name is already in your account
+ StatusInYourAccount Status = "inyouraccount"
+ // StatusUnavailable means the domain name is currently unavailable and can not be registered due to unknown reasons.
+ StatusUnavailable Status = "unavailable"
+ // StatusNotFree means the domain name has already been registered
+ StatusNotFree Status = "notfree"
+ // StatusFree means the domain name is currently free, is available and can be registered
+ StatusFree Status = "free"
+ // StatusInternalPull means the domain name is currently registered at TransIP and is available to be pulled from another account to yours.
+ StatusInternalPull Status = "internalpull"
+ // StatusInternalPush means the domain name is currently registered at TransIP in your accounta and is available to be pushed to another account.
+ StatusInternalPush Status = "internalpush"
+)
+
+// CheckResult represents a Transip_DomainCheckResult object as described at
+// https://api.transip.nl/docs/transip.nl/class-Transip_DomainCheckResult.html
+type CheckResult struct {
+ DomainName string `xml:"domainName"`
+ Status Status `xml:"status"`
+ Actions []Action `xml:"actions>item"`
+}
+
+// Branding represents a Transip_DomainBranding object as described at
+// https://api.transip.nl/docs/transip.nl/class-Transip_DomainBranding.html
+type Branding struct {
+ CompanyName string `xml:"companyName"`
+ SupportEmail string `xml:"supportEmail"`
+ CompanyURL string `xml:"companyUrl"`
+ TermsOfUsageURL string `xml:"termsOfUsageUrl"`
+ BannerLine1 string `xml:"bannerLine1"`
+ BannerLine2 string `xml:"bannerLine2"`
+ BannerLine3 string `xml:"bannerLine3"`
+}
+
+// EncodeParams returns WhoisContacts parameters ready to be used for constructing a signature
+func (b Branding) EncodeParams(prm gotransip.ParamsContainer) {
+ idx := prm.Len()
+ prm.Add(fmt.Sprintf("%d[companyName]", idx), b.CompanyName)
+ prm.Add(fmt.Sprintf("%d[supportEmail]", idx), b.SupportEmail)
+ prm.Add(fmt.Sprintf("%d[companyUrl]", idx), b.CompanyURL)
+ prm.Add(fmt.Sprintf("%d[termsOfUsageUrl]", idx), b.TermsOfUsageURL)
+ prm.Add(fmt.Sprintf("%d[bannerLine1]", idx), b.BannerLine1)
+ prm.Add(fmt.Sprintf("%d[bannerLine2]", idx), b.BannerLine2)
+ prm.Add(fmt.Sprintf("%d[bannerLine3]", idx), b.BannerLine3)
+}
+
+// EncodeArgs returns Branding XML body ready to be passed in the SOAP call
+func (b Branding) EncodeArgs(key string) string {
+ return fmt.Sprintf(`
+ %s
+ %s
+ %s
+ %s
+ %s
+ %s
+ %s
+ `, b.CompanyName, b.SupportEmail, b.CompanyURL, b.TermsOfUsageURL, b.BannerLine1, b.BannerLine2, b.BannerLine3)
+}
+
+// Action reflects the available actions to perform on a domain
+type Action string
+
+var (
+ // ActionRegister registers a domain
+ ActionRegister Action = "register"
+ // ActionTransfer transfers a domain to another provider
+ ActionTransfer Action = "transfer"
+ // ActionInternalPull transfers a domain to another account at TransIP
+ ActionInternalPull Action = "internalpull"
+)
+
+// ActionResult represents a Transip_DomainAction object as described at
+// https://api.transip.nl/docs/transip.nl/class-Transip_DomainAction.html
+type ActionResult struct {
+ Name string `xml:"name"`
+ HasFailed bool `xml:"hasFailed"`
+ Message string `xml:"message"`
+}
+
+// WhoisContact represents a TransIP_WhoisContact object
+// as described at https://api.transip.nl/docs/transip.nl/class-Transip_WhoisContact.html
+type WhoisContact struct {
+ Type string `xml:"type"`
+ FirstName string `xml:"firstName"`
+ MiddleName string `xml:"middleName"`
+ LastName string `xml:"lastName"`
+ CompanyName string `xml:"companyName"`
+ CompanyKvk string `xml:"companyKvk"`
+ CompanyType string `xml:"companyType"`
+ Street string `xml:"street"`
+ Number string `xml:"number"`
+ PostalCode string `xml:"postalCode"`
+ City string `xml:"city"`
+ PhoneNumber string `xml:"phoneNumber"`
+ FaxNumber string `xml:"faxNumber"`
+ Email string `xml:"email"`
+ Country string `xml:"country"`
+}
+
+// WhoisContacts is just an array of WhoisContact
+// basically only here so it can implement paramsEncoder
+type WhoisContacts []WhoisContact
+
+// EncodeParams returns WhoisContacts parameters ready to be used for constructing a signature
+func (w WhoisContacts) EncodeParams(prm gotransip.ParamsContainer) {
+ idx := prm.Len()
+ for i, e := range w {
+ prm.Add(fmt.Sprintf("%d[%d][type]", idx, i), e.Type)
+ prm.Add(fmt.Sprintf("%d[%d][firstName]", idx, i), e.FirstName)
+ prm.Add(fmt.Sprintf("%d[%d][middleName]", idx, i), e.MiddleName)
+ prm.Add(fmt.Sprintf("%d[%d][lastName]", idx, i), e.LastName)
+ prm.Add(fmt.Sprintf("%d[%d][companyName]", idx, i), e.CompanyName)
+ prm.Add(fmt.Sprintf("%d[%d][companyKvk]", idx, i), e.CompanyKvk)
+ prm.Add(fmt.Sprintf("%d[%d][companyType]", idx, i), e.CompanyType)
+ prm.Add(fmt.Sprintf("%d[%d][street]", idx, i), e.Street)
+ prm.Add(fmt.Sprintf("%d[%d][number]", idx, i), e.Number)
+ prm.Add(fmt.Sprintf("%d[%d][postalCode]", idx, i), e.PostalCode)
+ prm.Add(fmt.Sprintf("%d[%d][city]", idx, i), e.City)
+ prm.Add(fmt.Sprintf("%d[%d][phoneNumber]", idx, i), e.PhoneNumber)
+ prm.Add(fmt.Sprintf("%d[%d][faxNumber]", idx, i), e.FaxNumber)
+ prm.Add(fmt.Sprintf("%d[%d][email]", idx, i), e.Email)
+ prm.Add(fmt.Sprintf("%d[%d][country]", idx, i), e.Country)
+ }
+}
+
+// EncodeArgs returns WhoisContacts XML body ready to be passed in the SOAP call
+func (w WhoisContacts) EncodeArgs(key string) string {
+ output := fmt.Sprintf(`<%s SOAP-ENC:arrayType="ns1:WhoisContact[%d]" xsi:type="ns1:ArrayOfWhoisContact">`, key, len(w)) + "\n"
+ for _, e := range w {
+ output += fmt.Sprintf(` -
+ %s
+ %s
+ %s
+ %s
+ %s
+ %s
+ %s
+ %s
+ %s
+ %s
+ %s
+ %s
+ %s
+ %s
+ %s
+
`, e.Type, e.FirstName, e.MiddleName, e.LastName, e.CompanyName,
+ e.CompanyKvk, e.CompanyType, e.Street, e.Number, e.PostalCode, e.City,
+ e.PhoneNumber, e.FaxNumber, e.Email, e.Country) + "\n"
+ }
+
+ return output + fmt.Sprintf("%s>", key)
+}
diff --git a/vendor/github.com/transip/gotransip/sign.go b/vendor/github.com/transip/gotransip/sign.go
new file mode 100644
index 000000000..8b7d360c1
--- /dev/null
+++ b/vendor/github.com/transip/gotransip/sign.go
@@ -0,0 +1,49 @@
+package gotransip
+
+import (
+ "crypto"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/sha512"
+ "crypto/x509"
+ "encoding/base64"
+ "encoding/pem"
+ "errors"
+ "fmt"
+ "net/url"
+)
+
+var (
+ asn1Header = []byte{
+ 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03,
+ 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40,
+ }
+)
+
+func signWithKey(params *soapParams, key []byte) (string, error) {
+ // create SHA512 hash of given parameters
+ h := sha512.New()
+ h.Write([]byte(params.Encode()))
+
+ // prefix ASN1 header to SHA512 hash
+ digest := append(asn1Header, h.Sum(nil)...)
+
+ // prepare key struct
+ block, _ := pem.Decode(key)
+ if block == nil {
+ return "", errors.New("could not decode private key")
+ }
+ parsed, err := x509.ParsePKCS8PrivateKey(block.Bytes)
+ if err != nil {
+ return "", fmt.Errorf("could not parse private key: %s", err.Error())
+ }
+
+ pkey := parsed.(*rsa.PrivateKey)
+
+ enc, err := rsa.SignPKCS1v15(rand.Reader, pkey, crypto.Hash(0), digest)
+ if err != nil {
+ return "", fmt.Errorf("could not sign data: %s", err.Error())
+ }
+
+ return url.QueryEscape(base64.StdEncoding.EncodeToString(enc)), nil
+}
diff --git a/vendor/github.com/transip/gotransip/soap.go b/vendor/github.com/transip/gotransip/soap.go
new file mode 100644
index 000000000..b893b7f90
--- /dev/null
+++ b/vendor/github.com/transip/gotransip/soap.go
@@ -0,0 +1,419 @@
+package gotransip
+
+import (
+ "bytes"
+ "crypto/tls"
+ "encoding/xml"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "math/rand"
+ "net/http"
+ "net/url"
+ "regexp"
+ "strings"
+ "time"
+)
+
+const (
+ // format for SOAP envelopes
+ soapEnvelopeFixture string = `
+
+ %s
+`
+)
+
+// getSOAPArgs returns XML representing given name and argument as SOAP body
+func getSOAPArgs(name string, input ...string) []byte {
+ var buf bytes.Buffer
+
+ buf.WriteString(fmt.Sprintf("", name))
+ for _, x := range input {
+ buf.WriteString(x)
+ }
+ buf.WriteString(fmt.Sprintf("", name))
+
+ return buf.Bytes()
+}
+
+// getSOAPArg returns XML representing given input argument as SOAP parameters
+// in combination with getSOAPArgs you can build SOAP body
+func getSOAPArg(name string, input interface{}) (output string) {
+ switch input.(type) {
+ case []string:
+ i := input.([]string)
+ output = fmt.Sprintf(`<%s SOAP-ENC:arrayType="xsd:string[%d]" xsi:type="ns1:ArrayOfString">`, name, len(i))
+ for _, x := range i {
+ output = output + fmt.Sprintf(`- %s
`, x)
+ }
+ output = output + fmt.Sprintf("%s>", name)
+ case string:
+ output = fmt.Sprintf(`<%s xsi:type="xsd:string">%s%s>`, name, input, name)
+ case int, int32, int64:
+ output = fmt.Sprintf(`<%s xsi:type="xsd:integer">%d%s>`, name, input, name)
+ }
+
+ return
+}
+
+type soapFault struct {
+ Code string `xml:"faultcode,omitempty"`
+ Description string `xml:"faultstring,omitempty"`
+}
+
+func (s soapFault) String() string {
+ return fmt.Sprintf("SOAP Fault %s: %s", s.Code, s.Description)
+}
+
+// paramsEncoder allows SoapParams to hook into encoding theirselves, useful when
+// fields consist of complex structs
+type paramsEncoder interface {
+ EncodeParams(ParamsContainer)
+ EncodeArgs(string) string
+}
+
+// ParamsContainer is the interface a type should implement to be able to hold
+// SOAP parameters
+type ParamsContainer interface {
+ Len() int
+ Add(string, interface{})
+}
+
+// soapParams is a utility to make sure parameter data is encoded into a query
+// in the same order as we set them. The TransIP API requires this order for
+// verifying the signature
+type soapParams struct {
+ keys []string
+ values []interface{}
+}
+
+// Add adds parameter data to the end of this SoapParams
+func (s *soapParams) Add(k string, v interface{}) {
+ if s.keys == nil {
+ s.keys = make([]string, 0)
+ }
+
+ if s.values == nil {
+ s.values = make([]interface{}, 0)
+ }
+
+ s.keys = append(s.keys, k)
+ s.values = append(s.values, v)
+}
+
+// Len returns amount of parameters set in this SoapParams
+func (s soapParams) Len() int {
+ return len(s.keys)
+}
+
+// Encode returns a URL-like query string that can be used to generate a request's
+// signature. It's similar to url.Values.Encode() but without sorting of the keys
+// and based on the value's type it tries to encode accordingly.
+func (s soapParams) Encode() string {
+ var buf bytes.Buffer
+ var key string
+
+ for i, v := range s.values {
+ // if this is not the first parameter, prefix with &
+ if i > 0 {
+ buf.WriteString("&")
+ }
+
+ // for empty data fields, don't encode anything
+ if v == nil {
+ continue
+ }
+
+ key = s.keys[i]
+
+ switch v.(type) {
+ case []string:
+ c := v.([]string)
+ for j, cc := range c {
+ if j > 0 {
+ buf.WriteString("&")
+ }
+ buf.WriteString(fmt.Sprintf("%s[%d]=", key, j))
+ buf.WriteString(strings.Replace(url.QueryEscape(cc), "+", "%20", -1))
+ }
+ case string:
+ c := v.(string)
+ buf.WriteString(fmt.Sprintf("%s=", key))
+ buf.WriteString(strings.Replace(url.QueryEscape(c), "+", "%20", -1))
+ case int, int8, int16, int32, int64:
+ buf.WriteString(fmt.Sprintf("%s=", key))
+ buf.WriteString(fmt.Sprintf("%d", v))
+ default:
+ continue
+ }
+ }
+
+ return buf.String()
+}
+
+type soapHeader struct {
+ XMLName struct{} `xml:"Header"`
+ Contents []byte `xml:",innerxml"`
+}
+
+type soapBody struct {
+ XMLName struct{} `xml:"Body"`
+ Contents []byte `xml:",innerxml"`
+}
+
+type soapResponse struct {
+ Response struct {
+ InnerXML []byte `xml:",innerxml"`
+ } `xml:"return"`
+}
+
+type soapEnvelope struct {
+ XMLName struct{} `xml:"Envelope"`
+ Header soapHeader
+ Body soapBody
+}
+
+// SoapRequest holds all information for perfoming a SOAP request
+// Arguments to the request can be specified with AddArgument
+// If padding is defined, the SOAP response will be parsed after it being padded
+// with items in Padding in reverse order
+type SoapRequest struct {
+ Service string
+ Method string
+ params *soapParams // params used for creating signature
+ args []string // XML body arguments
+ Padding []string
+}
+
+// AddArgument adds an argument to the SoapRequest; the arguments ared used to
+// fill the XML request body as well as to create a valid signature for the
+// request
+func (sr *SoapRequest) AddArgument(key string, value interface{}) {
+ if sr.params == nil {
+ sr.params = &soapParams{}
+ }
+
+ // check if value implements paramsEncoder
+ if pe, ok := value.(paramsEncoder); ok {
+ sr.args = append(sr.args, pe.EncodeArgs(key))
+ pe.EncodeParams(sr.params)
+ return
+ }
+
+ switch value.(type) {
+ case []string:
+ sr.params.Add(fmt.Sprintf("%d", sr.params.Len()), value)
+ sr.args = append(sr.args, getSOAPArg(key, value))
+ case string:
+ sr.params.Add(fmt.Sprintf("%d", sr.params.Len()), value)
+ sr.args = append(sr.args, getSOAPArg(key, value.(string)))
+ case int, int8, int16, int32, int64:
+ sr.params.Add(fmt.Sprintf("%d", sr.params.Len()), value)
+ sr.args = append(sr.args, getSOAPArg(key, fmt.Sprintf("%d", value)))
+ default:
+ // check if value implements the String interface
+ if str, ok := value.(fmt.Stringer); ok {
+ sr.params.Add(fmt.Sprintf("%d", sr.params.Len()), str.String())
+ sr.args = append(sr.args, getSOAPArg(key, str.String()))
+ }
+ }
+}
+
+func (sr SoapRequest) getEnvelope() string {
+ return fmt.Sprintf(soapEnvelopeFixture, transipAPIHost, getSOAPArgs(sr.Method, sr.args...))
+}
+
+type soapClient struct {
+ Login string
+ Mode APIMode
+ PrivateKey []byte
+}
+
+// httpReqForSoapRequest creates the HTTP request for a specific SoapRequest
+// this includes setting the URL, POST body and cookies
+func (s soapClient) httpReqForSoapRequest(req SoapRequest) (*http.Request, error) {
+ // format URL
+ url := fmt.Sprintf("https://%s/soap/?service=%s", transipAPIHost, req.Service)
+
+ // create HTTP request
+ // TransIP API SOAP requests are always POST requests
+ httpReq, err := http.NewRequest("POST", url, strings.NewReader(req.getEnvelope()))
+ if err != nil {
+ return nil, err
+ }
+
+ // generate a number-used-once, a.k.a. nonce
+ // seeding the RNG is important if we want to do prevent using the same nonce
+ // in 2 sequential requests
+ rand.Seed(time.Now().UnixNano())
+ nonce := fmt.Sprintf("%d", rand.Int())
+ // set time of request, used later for signature as well
+ timestamp := fmt.Sprintf("%d", time.Now().Unix())
+
+ // set cookies required for the request
+ // most of these cookies are used for the signature as well so they should
+ // obviously match
+ httpReq.AddCookie(&http.Cookie{
+ Name: "login",
+ Value: s.Login,
+ })
+ httpReq.AddCookie(&http.Cookie{
+ Name: "mode",
+ Value: string(s.Mode),
+ })
+ httpReq.AddCookie(&http.Cookie{
+ Name: "timestamp",
+ Value: timestamp,
+ })
+ httpReq.AddCookie(&http.Cookie{
+ Name: "nonce",
+ Value: nonce,
+ })
+
+ // add params required for signature to the request parameters
+ if req.params == nil {
+ req.params = &soapParams{}
+ }
+ // TransIP API is quite picky on the order of the parameters
+ // so don't change anything in the order below
+ req.params.Add("__method", req.Method)
+ req.params.Add("__service", req.Service)
+ req.params.Add("__hostname", transipAPIHost)
+ req.params.Add("__timestamp", timestamp)
+ req.params.Add("__nonce", nonce)
+
+ signature, err := signWithKey(req.params, s.PrivateKey)
+ if err != nil {
+ return nil, err
+ }
+
+ // add signature of the request to the cookies as well
+ httpReq.AddCookie(&http.Cookie{
+ Name: "signature",
+ Value: signature,
+ })
+
+ return httpReq, nil
+}
+
+func parseSoapResponse(data []byte, padding []string, statusCode int, result interface{}) error {
+ // try to decode the resulting XML
+ var env soapEnvelope
+ if err := xml.Unmarshal(data, &env); err != nil {
+ return err
+ }
+
+ // try to decode the body to a soapFault
+ var fault soapFault
+ if err := xml.Unmarshal(env.Body.Contents, &fault); err != nil {
+ return err
+ }
+
+ // by checking fault's Code, we can determine if the response body in fact
+ // was a SOAP fault and if it was: return it as an error
+ if len(fault.Code) > 0 {
+ return errors.New(fault.String())
+ }
+
+ // try to decode into soapResponse
+ sr := soapResponse{}
+ if err := xml.Unmarshal(env.Body.Contents, &sr); err != nil {
+ return err
+ }
+
+ // if the response was empty and HTTP status was 200, consider it a success
+ if len(sr.Response.InnerXML) == 0 && statusCode == 200 {
+ return nil
+ }
+
+ // it seems like xml.Unmarshal won't work well on the most outer element
+ // so even when no Padding is defined, always pad with "transip" element
+ p := append([]string{"transip"}, padding...)
+ innerXML := padXMLData(sr.Response.InnerXML, p)
+
+ // try to decode to given result interface
+ return xml.Unmarshal([]byte(innerXML), &result)
+}
+
+func (s *soapClient) call(req SoapRequest, result interface{}) error {
+ // get http request for soap request
+ httpReq, err := s.httpReqForSoapRequest(req)
+ if err != nil {
+ return err
+ }
+
+ // create HTTP client and do the actual request
+ client := &http.Client{Timeout: time.Second * 10}
+ // make sure to verify the validity of remote certificate
+ // this is the default, but adding this flag here makes it easier to toggle
+ // it for testing/debugging
+ client.Transport = &http.Transport{
+ TLSClientConfig: &tls.Config{
+ InsecureSkipVerify: false,
+ },
+ }
+ resp, err := client.Do(httpReq)
+ if err != nil {
+ return fmt.Errorf("request error:\n%s", err.Error())
+ }
+ defer resp.Body.Close()
+
+ // read entire response body
+ b, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return err
+ }
+
+ // parse SOAP response into given result interface
+ return parseSoapResponse(b, req.Padding, resp.StatusCode, result)
+}
+
+// apply given padding around the XML data fed into this function
+// padding is applied in reverse order, so last element of padding is the
+// innermost element in the resulting XML
+func padXMLData(data []byte, padding []string) []byte {
+ // get right information from padding elements by matching to regex
+ re, _ := regexp.Compile("^(?:([^ ]+) )?([^>]+)>?$")
+
+ var prefix, suffix []byte
+ var tag, attr string
+ // go over each padding element
+ for i := len(padding); i > 0; i-- {
+ res := re.FindStringSubmatch(padding[i-1])
+ // no attribute was given
+ if len(res[1]) == 0 {
+ tag = res[2]
+ attr = ""
+ } else {
+ tag = res[1]
+ attr = " " + res[2]
+ }
+
+ prefix = []byte(fmt.Sprintf("<%s%s>", tag, attr))
+ suffix = []byte(fmt.Sprintf("%s>", tag))
+ data = append(append(prefix, data...), suffix...)
+ }
+
+ return data
+}
+
+// TestParamsContainer is only useful for unit testing the ParamsContainer
+// implementation of other type
+type TestParamsContainer struct {
+ Prm string
+}
+
+// Add just makes sure we use Len(), key and value in the result so it can be
+// tested
+func (t *TestParamsContainer) Add(key string, value interface{}) {
+ var prefix string
+ if t.Len() > 0 {
+ prefix = "&"
+ }
+ t.Prm = t.Prm + prefix + fmt.Sprintf("%d%s=%s", t.Len(), key, value)
+}
+
+// Len returns current length of test data in TestParamsContainer
+func (t TestParamsContainer) Len() int {
+ return len(t.Prm)
+}
diff --git a/vendor/github.com/transip/gotransip/util/util.go b/vendor/github.com/transip/gotransip/util/util.go
new file mode 100644
index 000000000..7f1beecf4
--- /dev/null
+++ b/vendor/github.com/transip/gotransip/util/util.go
@@ -0,0 +1,37 @@
+package util
+
+import (
+ "encoding/xml"
+ "time"
+)
+
+// KeyValueXML resembles the complex struct for getting key/value pairs from XML
+type KeyValueXML struct {
+ Cont []struct {
+ Item []struct {
+ Key string `xml:"key"`
+ Value string `xml:"value"`
+ } `xml:"item"`
+ } `xml:"item"`
+}
+
+// XMLTime is a custom type to decode XML values to time.Time directly
+type XMLTime struct {
+ time.Time
+}
+
+// UnmarshalXML is implemented to be able act as custom XML type
+// it tries to parse time from given elements value
+func (x *XMLTime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+ var v string
+ if err := d.DecodeElement(&v, &start); err != nil {
+ return err
+ }
+
+ if p, _ := time.Parse("2006-01-02 15:04:05", v); !p.IsZero() {
+ *x = XMLTime{p}
+ } else if p, _ := time.Parse("2006-01-02", v); !p.IsZero() {
+ *x = XMLTime{p}
+ }
+ return nil
+}
diff --git a/vendor/github.com/xenolf/lego/acme/client.go b/vendor/github.com/xenolf/lego/acme/client.go
index 4908d592c..f9857ebd0 100644
--- a/vendor/github.com/xenolf/lego/acme/client.go
+++ b/vendor/github.com/xenolf/lego/acme/client.go
@@ -665,7 +665,7 @@ func (c *Client) getAuthzForOrder(order orderResource) ([]authorization, error)
go func(authzURL string) {
var authz authorization
- _, err := getJSON(authzURL, &authz)
+ _, err := postAsGet(c.jws, authzURL, &authz)
if err != nil {
errc <- domainError{Domain: authz.Identifier.Value, Error: err}
return
@@ -789,7 +789,7 @@ func (c *Client) requestCertificateForCsr(order orderResource, bundle bool, csr
case <-stopTimer.C:
return nil, errors.New("certificate polling timed out")
case <-retryTick.C:
- _, err := getJSON(order.URL, &retOrder)
+ _, err := postAsGet(c.jws, order.URL, &retOrder)
if err != nil {
return nil, err
}
@@ -813,7 +813,7 @@ func (c *Client) requestCertificateForCsr(order orderResource, bundle bool, csr
func (c *Client) checkCertResponse(order orderMessage, certRes *CertificateResource, bundle bool) (bool, error) {
switch order.Status {
case statusValid:
- resp, err := httpGet(order.Certificate)
+ resp, err := postAsGet(c.jws, order.Certificate, nil)
if err != nil {
return false, err
}
@@ -871,7 +871,7 @@ func (c *Client) checkCertResponse(order orderMessage, certRes *CertificateResou
// getIssuerCertificate requests the issuer certificate
func (c *Client) getIssuerCertificate(url string) ([]byte, error) {
log.Infof("acme: Requesting issuer cert from %s", url)
- resp, err := httpGet(url)
+ resp, err := postAsGet(c.jws, url, nil)
if err != nil {
return nil, err
}
@@ -914,7 +914,10 @@ func parseLinks(links []string) map[string]string {
func validate(j *jws, domain, uri string, c challenge) error {
var chlng challenge
- hdr, err := postJSON(j, uri, c, &chlng)
+ // Challenge initiation is done by sending a JWS payload containing the
+ // trivial JSON object `{}`. We use an empty struct instance as the postJSON
+ // payload here to achieve this result.
+ hdr, err := postJSON(j, uri, struct{}{}, &chlng)
if err != nil {
return err
}
@@ -940,11 +943,15 @@ func validate(j *jws, domain, uri string, c challenge) error {
// If it doesn't, we'll just poll hard.
ra = 5
}
+
time.Sleep(time.Duration(ra) * time.Second)
- hdr, err = getJSON(uri, &chlng)
+ resp, err := postAsGet(j, uri, &chlng)
if err != nil {
return err
}
+ if resp != nil {
+ hdr = resp.Header
+ }
}
}
diff --git a/vendor/github.com/xenolf/lego/acme/http.go b/vendor/github.com/xenolf/lego/acme/http.go
index 2ddbc0f1b..8343b3690 100644
--- a/vendor/github.com/xenolf/lego/acme/http.go
+++ b/vendor/github.com/xenolf/lego/acme/http.go
@@ -42,12 +42,14 @@ var (
)
const (
- // defaultGoUserAgent is the Go HTTP package user agent string. Too
- // bad it isn't exported. If it changes, we should update it here, too.
- defaultGoUserAgent = "Go-http-client/1.1"
-
// ourUserAgent is the User-Agent of this underlying library package.
- ourUserAgent = "xenolf-acme"
+ // NOTE: Update this with each tagged release.
+ ourUserAgent = "xenolf-acme/1.2.1"
+
+ // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package.
+ // values: detach|release
+ // NOTE: Update this with each tagged release.
+ ourUserAgentComment = "detach"
// caCertificatesEnvVar is the environment variable name that can be used to
// specify the path to PEM encoded CA Certificates that can be used to
@@ -151,52 +153,60 @@ func postJSON(j *jws, uri string, reqBody, respBody interface{}) (http.Header, e
return nil, errors.New("failed to marshal network message")
}
- resp, err := j.post(uri, jsonBytes)
- if err != nil {
- return nil, fmt.Errorf("failed to post JWS message. -> %v", err)
+ resp, err := post(j, uri, jsonBytes, respBody)
+ if resp == nil {
+ return nil, err
}
defer resp.Body.Close()
+ return resp.Header, err
+}
+
+func postAsGet(j *jws, uri string, respBody interface{}) (*http.Response, error) {
+ return post(j, uri, []byte{}, respBody)
+}
+
+func post(j *jws, uri string, reqBody []byte, respBody interface{}) (*http.Response, error) {
+ resp, err := j.post(uri, reqBody)
+ if err != nil {
+ return nil, fmt.Errorf("failed to post JWS message. -> %v", err)
+ }
+
if resp.StatusCode >= http.StatusBadRequest {
err = handleHTTPError(resp)
switch err.(type) {
case NonceError:
// Retry once if the nonce was invalidated
- retryResp, errP := j.post(uri, jsonBytes)
+ retryResp, errP := j.post(uri, reqBody)
if errP != nil {
return nil, fmt.Errorf("failed to post JWS message. -> %v", errP)
}
- defer retryResp.Body.Close()
-
if retryResp.StatusCode >= http.StatusBadRequest {
- return retryResp.Header, handleHTTPError(retryResp)
+ return retryResp, handleHTTPError(retryResp)
}
if respBody == nil {
- return retryResp.Header, nil
+ return retryResp, nil
}
- return retryResp.Header, json.NewDecoder(retryResp.Body).Decode(respBody)
-
+ return retryResp, json.NewDecoder(retryResp.Body).Decode(respBody)
default:
- return resp.Header, err
-
+ return resp, err
}
-
}
if respBody == nil {
- return resp.Header, nil
+ return resp, nil
}
- return resp.Header, json.NewDecoder(resp.Body).Decode(respBody)
+ return resp, json.NewDecoder(resp.Body).Decode(respBody)
}
// userAgent builds and returns the User-Agent string to use in requests.
func userAgent() string {
- ua := fmt.Sprintf("%s %s (%s; %s) %s", UserAgent, ourUserAgent, runtime.GOOS, runtime.GOARCH, defaultGoUserAgent)
+ ua := fmt.Sprintf("%s %s (%s; %s; %s)", UserAgent, ourUserAgent, ourUserAgentComment, runtime.GOOS, runtime.GOARCH)
return strings.TrimSpace(ua)
}
diff --git a/vendor/github.com/xenolf/lego/acme/tls_alpn_challenge.go b/vendor/github.com/xenolf/lego/acme/tls_alpn_challenge.go
index d80351999..cc70c3502 100644
--- a/vendor/github.com/xenolf/lego/acme/tls_alpn_challenge.go
+++ b/vendor/github.com/xenolf/lego/acme/tls_alpn_challenge.go
@@ -12,8 +12,8 @@ import (
)
// idPeAcmeIdentifierV1 is the SMI Security for PKIX Certification Extension OID referencing the ACME extension.
-// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-01#section-5.1
-var idPeAcmeIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 30, 1}
+// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05#section-5.1
+var idPeAcmeIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
type tlsALPNChallenge struct {
jws *jws
@@ -58,7 +58,7 @@ func TLSALPNChallengeBlocks(domain, keyAuth string) ([]byte, []byte, error) {
// Add the keyAuth digest as the acmeValidation-v1 extension
// (marked as critical such that it won't be used by non-ACME software).
- // Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-01#section-3
+ // Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05#section-3
extensions := []pkix.Extension{
{
Id: idPeAcmeIdentifierV1,
diff --git a/vendor/github.com/xenolf/lego/providers/dns/cloudflare/cloudflare.go b/vendor/github.com/xenolf/lego/providers/dns/cloudflare/cloudflare.go
index 33c2065e0..92c4b69fa 100644
--- a/vendor/github.com/xenolf/lego/providers/dns/cloudflare/cloudflare.go
+++ b/vendor/github.com/xenolf/lego/providers/dns/cloudflare/cloudflare.go
@@ -126,7 +126,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
TTL: d.config.TTL,
}
- response, _ := d.client.CreateDNSRecord(zoneID, dnsRecord)
+ response, err := d.client.CreateDNSRecord(zoneID, dnsRecord)
if err != nil {
return fmt.Errorf("cloudflare: failed to create TXT record: %v", err)
}
diff --git a/vendor/github.com/xenolf/lego/providers/dns/conoha/client.go b/vendor/github.com/xenolf/lego/providers/dns/conoha/client.go
new file mode 100644
index 000000000..3fa8b5bbb
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/providers/dns/conoha/client.go
@@ -0,0 +1,205 @@
+package conoha
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+)
+
+const (
+ identityBaseURL = "https://identity.%s.conoha.io"
+ dnsServiceBaseURL = "https://dns-service.%s.conoha.io"
+)
+
+// IdentityRequest is an authentication request body.
+type IdentityRequest struct {
+ Auth Auth `json:"auth"`
+}
+
+// Auth is an authentication information.
+type Auth struct {
+ TenantID string `json:"tenantId"`
+ PasswordCredentials PasswordCredentials `json:"passwordCredentials"`
+}
+
+// PasswordCredentials is API-user's credentials.
+type PasswordCredentials struct {
+ Username string `json:"username"`
+ Password string `json:"password"`
+}
+
+// IdentityResponse is an authentication response body.
+type IdentityResponse struct {
+ Access Access `json:"access"`
+}
+
+// Access is an identity information.
+type Access struct {
+ Token Token `json:"token"`
+}
+
+// Token is an api access token.
+type Token struct {
+ ID string `json:"id"`
+}
+
+// DomainListResponse is a response of a domain listing request.
+type DomainListResponse struct {
+ Domains []Domain `json:"domains"`
+}
+
+// Domain is a hosted domain entry.
+type Domain struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+}
+
+// RecordListResponse is a response of record listing request.
+type RecordListResponse struct {
+ Records []Record `json:"records"`
+}
+
+// Record is a record entry.
+type Record struct {
+ ID string `json:"id,omitempty"`
+ Name string `json:"name"`
+ Type string `json:"type"`
+ Data string `json:"data"`
+ TTL int `json:"ttl"`
+}
+
+// Client is a ConoHa API client.
+type Client struct {
+ token string
+ endpoint string
+ httpClient *http.Client
+}
+
+// NewClient returns a client instance logged into the ConoHa service.
+func NewClient(region string, auth Auth, httpClient *http.Client) (*Client, error) {
+ if httpClient == nil {
+ httpClient = &http.Client{}
+ }
+
+ c := &Client{httpClient: httpClient}
+
+ c.endpoint = fmt.Sprintf(identityBaseURL, region)
+
+ identity, err := c.getIdentity(auth)
+ if err != nil {
+ return nil, fmt.Errorf("failed to login: %v", err)
+ }
+
+ c.token = identity.Access.Token.ID
+ c.endpoint = fmt.Sprintf(dnsServiceBaseURL, region)
+
+ return c, nil
+}
+
+func (c *Client) getIdentity(auth Auth) (*IdentityResponse, error) {
+ req := &IdentityRequest{Auth: auth}
+
+ identity := &IdentityResponse{}
+
+ err := c.do(http.MethodPost, "/v2.0/tokens", req, identity)
+ if err != nil {
+ return nil, err
+ }
+
+ return identity, nil
+}
+
+// GetDomainID returns an ID of specified domain.
+func (c *Client) GetDomainID(domainName string) (string, error) {
+ domainList := &DomainListResponse{}
+
+ err := c.do(http.MethodGet, "/v1/domains", nil, domainList)
+ if err != nil {
+ return "", err
+ }
+
+ for _, domain := range domainList.Domains {
+ if domain.Name == domainName {
+ return domain.ID, nil
+ }
+ }
+ return "", fmt.Errorf("no such domain: %s", domainName)
+}
+
+// GetRecordID returns an ID of specified record.
+func (c *Client) GetRecordID(domainID, recordName, recordType, data string) (string, error) {
+ recordList := &RecordListResponse{}
+
+ err := c.do(http.MethodGet, fmt.Sprintf("/v1/domains/%s/records", domainID), nil, recordList)
+ if err != nil {
+ return "", err
+ }
+
+ for _, record := range recordList.Records {
+ if record.Name == recordName && record.Type == recordType && record.Data == data {
+ return record.ID, nil
+ }
+ }
+ return "", errors.New("no such record")
+}
+
+// CreateRecord adds new record.
+func (c *Client) CreateRecord(domainID string, record Record) error {
+ return c.do(http.MethodPost, fmt.Sprintf("/v1/domains/%s/records", domainID), record, nil)
+}
+
+// DeleteRecord removes specified record.
+func (c *Client) DeleteRecord(domainID, recordID string) error {
+ return c.do(http.MethodDelete, fmt.Sprintf("/v1/domains/%s/records/%s", domainID, recordID), nil, nil)
+}
+
+func (c *Client) do(method, path string, payload, result interface{}) error {
+ body := bytes.NewReader(nil)
+
+ if payload != nil {
+ bodyBytes, err := json.Marshal(payload)
+ if err != nil {
+ return err
+ }
+ body = bytes.NewReader(bodyBytes)
+ }
+
+ req, err := http.NewRequest(method, c.endpoint+path, body)
+ if err != nil {
+ return err
+ }
+
+ req.Header.Set("Accept", "application/json")
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("X-Auth-Token", c.token)
+
+ resp, err := c.httpClient.Do(req)
+ if err != nil {
+ return err
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ respBody, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ return fmt.Errorf("HTTP request failed with status code %d: %s", resp.StatusCode, string(respBody))
+ }
+
+ if result != nil {
+ respBody, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ return json.Unmarshal(respBody, result)
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/xenolf/lego/providers/dns/conoha/conoha.go b/vendor/github.com/xenolf/lego/providers/dns/conoha/conoha.go
new file mode 100644
index 000000000..a872789f3
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/providers/dns/conoha/conoha.go
@@ -0,0 +1,148 @@
+// Package conoha implements a DNS provider for solving the DNS-01 challenge
+// using ConoHa DNS.
+package conoha
+
+import (
+ "errors"
+ "fmt"
+ "net/http"
+ "time"
+
+ "github.com/xenolf/lego/acme"
+ "github.com/xenolf/lego/platform/config/env"
+)
+
+// Config is used to configure the creation of the DNSProvider
+type Config struct {
+ Region string
+ TenantID string
+ Username string
+ Password string
+ TTL int
+ PropagationTimeout time.Duration
+ PollingInterval time.Duration
+ HTTPClient *http.Client
+}
+
+// NewDefaultConfig returns a default configuration for the DNSProvider
+func NewDefaultConfig() *Config {
+ return &Config{
+ Region: env.GetOrDefaultString("CONOHA_REGION", "tyo1"),
+ TTL: env.GetOrDefaultInt("CONOHA_TTL", 60),
+ PropagationTimeout: env.GetOrDefaultSecond("CONOHA_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout),
+ PollingInterval: env.GetOrDefaultSecond("CONOHA_POLLING_INTERVAL", acme.DefaultPollingInterval),
+ HTTPClient: &http.Client{
+ Timeout: env.GetOrDefaultSecond("CONOHA_HTTP_TIMEOUT", 30*time.Second),
+ },
+ }
+}
+
+// DNSProvider is an implementation of the acme.ChallengeProvider interface
+type DNSProvider struct {
+ config *Config
+ client *Client
+}
+
+// NewDNSProvider returns a DNSProvider instance configured for ConoHa DNS.
+// Credentials must be passed in the environment variables: CONOHA_TENANT_ID, CONOHA_API_USERNAME, CONOHA_API_PASSWORD
+func NewDNSProvider() (*DNSProvider, error) {
+ values, err := env.Get("CONOHA_TENANT_ID", "CONOHA_API_USERNAME", "CONOHA_API_PASSWORD")
+ if err != nil {
+ return nil, fmt.Errorf("conoha: %v", err)
+ }
+
+ config := NewDefaultConfig()
+ config.TenantID = values["CONOHA_TENANT_ID"]
+ config.Username = values["CONOHA_API_USERNAME"]
+ config.Password = values["CONOHA_API_PASSWORD"]
+
+ return NewDNSProviderConfig(config)
+}
+
+// NewDNSProviderConfig return a DNSProvider instance configured for ConoHa DNS.
+func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
+ if config == nil {
+ return nil, errors.New("conoha: the configuration of the DNS provider is nil")
+ }
+
+ if config.TenantID == "" || config.Username == "" || config.Password == "" {
+ return nil, errors.New("conoha: some credentials information are missing")
+ }
+
+ auth := Auth{
+ TenantID: config.TenantID,
+ PasswordCredentials: PasswordCredentials{
+ Username: config.Username,
+ Password: config.Password,
+ },
+ }
+
+ client, err := NewClient(config.Region, auth, config.HTTPClient)
+ if err != nil {
+ return nil, fmt.Errorf("conoha: failed to create client: %v", err)
+ }
+
+ return &DNSProvider{config: config, client: client}, nil
+}
+
+// Present creates a TXT record to fulfill the dns-01 challenge.
+func (d *DNSProvider) Present(domain, token, keyAuth string) error {
+ fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
+
+ authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
+ if err != nil {
+ return err
+ }
+
+ id, err := d.client.GetDomainID(authZone)
+ if err != nil {
+ return fmt.Errorf("conoha: failed to get domain ID: %v", err)
+ }
+
+ record := Record{
+ Name: fqdn,
+ Type: "TXT",
+ Data: value,
+ TTL: d.config.TTL,
+ }
+
+ err = d.client.CreateRecord(id, record)
+ if err != nil {
+ return fmt.Errorf("conoha: failed to create record: %v", err)
+ }
+
+ return nil
+}
+
+// CleanUp clears ConoHa DNS TXT record
+func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
+ fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
+
+ authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
+ if err != nil {
+ return err
+ }
+
+ domID, err := d.client.GetDomainID(authZone)
+ if err != nil {
+ return fmt.Errorf("conoha: failed to get domain ID: %v", err)
+ }
+
+ recID, err := d.client.GetRecordID(domID, fqdn, "TXT", value)
+ if err != nil {
+ return fmt.Errorf("conoha: failed to get record ID: %v", err)
+ }
+
+ err = d.client.DeleteRecord(domID, recID)
+ if err != nil {
+ return fmt.Errorf("conoha: failed to delete record: %v", err)
+ }
+
+ return nil
+}
+
+// Timeout returns the timeout and interval to use when checking for DNS propagation.
+// Adjusting here to cope with spikes in propagation times.
+func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
+ return d.config.PropagationTimeout, d.config.PollingInterval
+}
diff --git a/vendor/github.com/xenolf/lego/providers/dns/dns_providers.go b/vendor/github.com/xenolf/lego/providers/dns/dns_providers.go
index ff7a7d833..604137d56 100644
--- a/vendor/github.com/xenolf/lego/providers/dns/dns_providers.go
+++ b/vendor/github.com/xenolf/lego/providers/dns/dns_providers.go
@@ -11,6 +11,7 @@ import (
"github.com/xenolf/lego/providers/dns/bluecat"
"github.com/xenolf/lego/providers/dns/cloudflare"
"github.com/xenolf/lego/providers/dns/cloudxns"
+ "github.com/xenolf/lego/providers/dns/conoha"
"github.com/xenolf/lego/providers/dns/digitalocean"
"github.com/xenolf/lego/providers/dns/dnsimple"
"github.com/xenolf/lego/providers/dns/dnsmadeeasy"
@@ -27,10 +28,13 @@ import (
"github.com/xenolf/lego/providers/dns/glesys"
"github.com/xenolf/lego/providers/dns/godaddy"
"github.com/xenolf/lego/providers/dns/hostingde"
+ "github.com/xenolf/lego/providers/dns/httpreq"
"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"
+ "github.com/xenolf/lego/providers/dns/mydnsjp"
"github.com/xenolf/lego/providers/dns/namecheap"
"github.com/xenolf/lego/providers/dns/namedotcom"
"github.com/xenolf/lego/providers/dns/netcup"
@@ -43,8 +47,11 @@ import (
"github.com/xenolf/lego/providers/dns/rfc2136"
"github.com/xenolf/lego/providers/dns/route53"
"github.com/xenolf/lego/providers/dns/sakuracloud"
+ "github.com/xenolf/lego/providers/dns/selectel"
"github.com/xenolf/lego/providers/dns/stackpath"
+ "github.com/xenolf/lego/providers/dns/transip"
"github.com/xenolf/lego/providers/dns/vegadns"
+ "github.com/xenolf/lego/providers/dns/vscale"
"github.com/xenolf/lego/providers/dns/vultr"
)
@@ -65,6 +72,8 @@ func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error)
return cloudflare.NewDNSProvider()
case "cloudxns":
return cloudxns.NewDNSProvider()
+ case "conoha":
+ return conoha.NewDNSProvider()
case "digitalocean":
return digitalocean.NewDNSProvider()
case "dnsimple":
@@ -97,8 +106,12 @@ func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error)
return godaddy.NewDNSProvider()
case "hostingde":
return hostingde.NewDNSProvider()
+ case "httpreq":
+ return httpreq.NewDNSProvider()
case "iij":
return iij.NewDNSProvider()
+ case "inwx":
+ return inwx.NewDNSProvider()
case "lightsail":
return lightsail.NewDNSProvider()
case "linode":
@@ -107,6 +120,8 @@ func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error)
return linodev4.NewDNSProvider()
case "manual":
return acme.NewDNSProviderManual()
+ case "mydnsjp":
+ return mydnsjp.NewDNSProvider()
case "namecheap":
return namecheap.NewDNSProvider()
case "namedotcom":
@@ -133,10 +148,16 @@ func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error)
return sakuracloud.NewDNSProvider()
case "stackpath":
return stackpath.NewDNSProvider()
+ case "selectel":
+ return selectel.NewDNSProvider()
+ case "transip":
+ return transip.NewDNSProvider()
case "vegadns":
return vegadns.NewDNSProvider()
case "vultr":
return vultr.NewDNSProvider()
+ case "vscale":
+ return vscale.NewDNSProvider()
default:
return nil, fmt.Errorf("unrecognised DNS provider: %s", name)
}
diff --git a/vendor/github.com/xenolf/lego/providers/dns/httpreq/httpreq.go b/vendor/github.com/xenolf/lego/providers/dns/httpreq/httpreq.go
new file mode 100644
index 000000000..cf957d41b
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/providers/dns/httpreq/httpreq.go
@@ -0,0 +1,193 @@
+// Package httpreq implements a DNS provider for solving the DNS-01 challenge through a HTTP server.
+package httpreq
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "os"
+ "time"
+
+ "github.com/xenolf/lego/acme"
+ "github.com/xenolf/lego/platform/config/env"
+)
+
+type message struct {
+ FQDN string `json:"fqdn"`
+ Value string `json:"value"`
+}
+
+type messageRaw struct {
+ Domain string `json:"domain"`
+ Token string `json:"token"`
+ KeyAuth string `json:"keyAuth"`
+}
+
+// Config is used to configure the creation of the DNSProvider
+type Config struct {
+ Endpoint *url.URL
+ Mode string
+ Username string
+ Password string
+ PropagationTimeout time.Duration
+ PollingInterval time.Duration
+ HTTPClient *http.Client
+}
+
+// NewDefaultConfig returns a default configuration for the DNSProvider
+func NewDefaultConfig() *Config {
+ return &Config{
+ PropagationTimeout: env.GetOrDefaultSecond("HTTPREQ_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout),
+ PollingInterval: env.GetOrDefaultSecond("HTTPREQ_POLLING_INTERVAL", acme.DefaultPollingInterval),
+ HTTPClient: &http.Client{
+ Timeout: env.GetOrDefaultSecond("HTTPREQ_HTTP_TIMEOUT", 30*time.Second),
+ },
+ }
+}
+
+// DNSProvider describes a provider for acme-proxy
+type DNSProvider struct {
+ config *Config
+}
+
+// NewDNSProvider returns a DNSProvider instance.
+func NewDNSProvider() (*DNSProvider, error) {
+ values, err := env.Get("HTTPREQ_ENDPOINT")
+ if err != nil {
+ return nil, fmt.Errorf("httpreq: %v", err)
+ }
+
+ endpoint, err := url.Parse(values["HTTPREQ_ENDPOINT"])
+ if err != nil {
+ return nil, fmt.Errorf("httpreq: %v", err)
+ }
+
+ config := NewDefaultConfig()
+ config.Mode = os.Getenv("HTTPREQ_MODE")
+ config.Username = os.Getenv("HTTPREQ_USERNAME")
+ config.Password = os.Getenv("HTTPREQ_PASSWORD")
+ config.Endpoint = endpoint
+ return NewDNSProviderConfig(config)
+}
+
+// NewDNSProviderConfig return a DNSProvider .
+func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
+ if config == nil {
+ return nil, errors.New("httpreq: the configuration of the DNS provider is nil")
+ }
+
+ if config.Endpoint == nil {
+ return nil, errors.New("httpreq: the endpoint is missing")
+ }
+
+ return &DNSProvider{config: config}, nil
+}
+
+// Timeout returns the timeout and interval to use when checking for DNS propagation.
+// Adjusting here to cope with spikes in propagation times.
+func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
+ return d.config.PropagationTimeout, d.config.PollingInterval
+}
+
+// Present creates a TXT record to fulfill the dns-01 challenge
+func (d *DNSProvider) Present(domain, token, keyAuth string) error {
+ if d.config.Mode == "RAW" {
+ msg := &messageRaw{
+ Domain: domain,
+ Token: token,
+ KeyAuth: keyAuth,
+ }
+
+ err := d.doPost("/present", msg)
+ if err != nil {
+ return fmt.Errorf("httpreq: %v", err)
+ }
+ return nil
+ }
+
+ fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
+ msg := &message{
+ FQDN: fqdn,
+ Value: value,
+ }
+
+ err := d.doPost("/present", msg)
+ if err != nil {
+ return fmt.Errorf("httpreq: %v", err)
+ }
+ return nil
+}
+
+// CleanUp removes the TXT record matching the specified parameters
+func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
+ if d.config.Mode == "RAW" {
+ msg := &messageRaw{
+ Domain: domain,
+ Token: token,
+ KeyAuth: keyAuth,
+ }
+
+ err := d.doPost("/cleanup", msg)
+ if err != nil {
+ return fmt.Errorf("httpreq: %v", err)
+ }
+ return nil
+ }
+
+ fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
+ msg := &message{
+ FQDN: fqdn,
+ Value: value,
+ }
+
+ err := d.doPost("/cleanup", msg)
+ if err != nil {
+ return fmt.Errorf("httpreq: %v", err)
+ }
+ return nil
+}
+
+func (d *DNSProvider) doPost(uri string, msg interface{}) error {
+ reqBody := &bytes.Buffer{}
+ err := json.NewEncoder(reqBody).Encode(msg)
+ if err != nil {
+ return err
+ }
+
+ endpoint, err := d.config.Endpoint.Parse(uri)
+ if err != nil {
+ return err
+ }
+
+ req, err := http.NewRequest(http.MethodPost, endpoint.String(), reqBody)
+ if err != nil {
+ return err
+ }
+
+ req.Header.Set("Content-Type", "application/json")
+
+ if len(d.config.Username) > 0 && len(d.config.Password) > 0 {
+ req.SetBasicAuth(d.config.Username, d.config.Password)
+ }
+
+ resp, err := d.config.HTTPClient.Do(req)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode >= http.StatusBadRequest {
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return fmt.Errorf("%d: failed to read response body: %v", resp.StatusCode, err)
+ }
+
+ return fmt.Errorf("%d: request failed: %v", resp.StatusCode, string(body))
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/xenolf/lego/providers/dns/inwx/inwx.go b/vendor/github.com/xenolf/lego/providers/dns/inwx/inwx.go
new file mode 100644
index 000000000..ba6c8cbec
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/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/vendor/github.com/xenolf/lego/providers/dns/mydnsjp/mydnsjp.go b/vendor/github.com/xenolf/lego/providers/dns/mydnsjp/mydnsjp.go
new file mode 100644
index 000000000..05cad5686
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/providers/dns/mydnsjp/mydnsjp.go
@@ -0,0 +1,140 @@
+// Package mydnsjp implements a DNS provider for solving the DNS-01
+// challenge using MyDNS.jp.
+package mydnsjp
+
+import (
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "strings"
+ "time"
+
+ "github.com/xenolf/lego/acme"
+ "github.com/xenolf/lego/platform/config/env"
+)
+
+const defaultBaseURL = "https://www.mydns.jp/directedit.html"
+
+// Config is used to configure the creation of the DNSProvider
+type Config struct {
+ MasterID string
+ Password string
+ PropagationTimeout time.Duration
+ PollingInterval time.Duration
+ HTTPClient *http.Client
+}
+
+// NewDefaultConfig returns a default configuration for the DNSProvider
+func NewDefaultConfig() *Config {
+ return &Config{
+ PropagationTimeout: env.GetOrDefaultSecond("MYDNSJP_PROPAGATION_TIMEOUT", 2*time.Minute),
+ PollingInterval: env.GetOrDefaultSecond("MYDNSJP_POLLING_INTERVAL", 2*time.Second),
+ HTTPClient: &http.Client{
+ Timeout: env.GetOrDefaultSecond("MYDNSJP_HTTP_TIMEOUT", 30*time.Second),
+ },
+ }
+}
+
+// DNSProvider is an implementation of the acme.ChallengeProvider interface
+type DNSProvider struct {
+ config *Config
+}
+
+// NewDNSProvider returns a DNSProvider instance configured for MyDNS.jp.
+// Credentials must be passed in the environment variables: MYDNSJP_MASTER_ID and MYDNSJP_PASSWORD.
+func NewDNSProvider() (*DNSProvider, error) {
+ values, err := env.Get("MYDNSJP_MASTER_ID", "MYDNSJP_PASSWORD")
+ if err != nil {
+ return nil, fmt.Errorf("mydnsjp: %v", err)
+ }
+
+ config := NewDefaultConfig()
+ config.MasterID = values["MYDNSJP_MASTER_ID"]
+ config.Password = values["MYDNSJP_PASSWORD"]
+
+ return NewDNSProviderConfig(config)
+}
+
+// NewDNSProviderConfig return a DNSProvider instance configured for MyDNS.jp.
+func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
+ if config == nil {
+ return nil, errors.New("mydnsjp: the configuration of the DNS provider is nil")
+ }
+
+ if config.MasterID == "" || config.Password == "" {
+ return nil, errors.New("mydnsjp: some credentials information are missing")
+ }
+
+ return &DNSProvider{config: config}, nil
+}
+
+// Timeout returns the timeout and interval to use when checking for DNS propagation.
+// Adjusting here to cope with spikes in propagation times.
+func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
+ return d.config.PropagationTimeout, d.config.PollingInterval
+}
+
+// Present creates a TXT record to fulfill the dns-01 challenge
+func (d *DNSProvider) Present(domain, token, keyAuth string) error {
+ _, value, _ := acme.DNS01Record(domain, keyAuth)
+ err := d.doRequest(domain, value, "REGIST")
+ if err != nil {
+ return fmt.Errorf("mydnsjp: %v", err)
+ }
+ return nil
+}
+
+// CleanUp removes the TXT record matching the specified parameters
+func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
+ _, value, _ := acme.DNS01Record(domain, keyAuth)
+ err := d.doRequest(domain, value, "DELETE")
+ if err != nil {
+ return fmt.Errorf("mydnsjp: %v", err)
+ }
+ return nil
+}
+
+func (d *DNSProvider) doRequest(domain, value string, cmd string) error {
+ req, err := d.buildRequest(domain, value, cmd)
+ if err != nil {
+ return err
+ }
+
+ resp, err := d.config.HTTPClient.Do(req)
+ if err != nil {
+ return fmt.Errorf("error querying API: %v", err)
+ }
+
+ defer resp.Body.Close()
+
+ if resp.StatusCode >= 400 {
+ var content []byte
+ content, err = ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return err
+ }
+
+ return fmt.Errorf("request %s failed [status code %d]: %s", req.URL, resp.StatusCode, string(content))
+ }
+
+ return nil
+}
+
+func (d *DNSProvider) buildRequest(domain, value string, cmd string) (*http.Request, error) {
+ params := url.Values{}
+ params.Set("CERTBOT_DOMAIN", domain)
+ params.Set("CERTBOT_VALIDATION", value)
+ params.Set("EDIT_CMD", cmd)
+
+ req, err := http.NewRequest(http.MethodPost, defaultBaseURL, strings.NewReader(params.Encode()))
+ if err != nil {
+ return nil, fmt.Errorf("invalid request: %v", err)
+ }
+
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+ req.SetBasicAuth(d.config.MasterID, d.config.Password)
+
+ return req, nil
+}
diff --git a/vendor/github.com/xenolf/lego/providers/dns/selectel/client.go b/vendor/github.com/xenolf/lego/providers/dns/selectel/client.go
new file mode 100644
index 000000000..cb02230a0
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/providers/dns/selectel/client.go
@@ -0,0 +1,211 @@
+package selectel
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+)
+
+// Domain represents domain name.
+type Domain struct {
+ ID int `json:"id,omitempty"`
+ Name string `json:"name,omitempty"`
+}
+
+// Record represents DNS record.
+type Record struct {
+ ID int `json:"id,omitempty"`
+ Name string `json:"name,omitempty"`
+ Type string `json:"type,omitempty"` // Record type (SOA, NS, A/AAAA, CNAME, SRV, MX, TXT, SPF)
+ TTL int `json:"ttl,omitempty"`
+ Email string `json:"email,omitempty"` // Email of domain's admin (only for SOA records)
+ Content string `json:"content,omitempty"` // Record content (not for SRV)
+}
+
+// APIError API error message
+type APIError struct {
+ Description string `json:"error"`
+ Code int `json:"code"`
+ Field string `json:"field"`
+}
+
+func (a *APIError) Error() string {
+ return fmt.Sprintf("API error: %d - %s - %s", a.Code, a.Description, a.Field)
+}
+
+// ClientOpts represents options to init client.
+type ClientOpts struct {
+ BaseURL string
+ Token string
+ UserAgent string
+ HTTPClient *http.Client
+}
+
+// Client represents DNS client.
+type Client struct {
+ baseURL string
+ token string
+ userAgent string
+ httpClient *http.Client
+}
+
+// NewClient returns a client instance.
+func NewClient(opts ClientOpts) *Client {
+ if opts.HTTPClient == nil {
+ opts.HTTPClient = &http.Client{}
+ }
+
+ return &Client{
+ token: opts.Token,
+ baseURL: opts.BaseURL,
+ httpClient: opts.HTTPClient,
+ userAgent: opts.UserAgent,
+ }
+}
+
+// GetDomainByName gets Domain object by its name.
+func (c *Client) GetDomainByName(domainName string) (*Domain, error) {
+ uri := fmt.Sprintf("/%s", domainName)
+ req, err := c.newRequest(http.MethodGet, uri, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ domain := &Domain{}
+ _, err = c.do(req, domain)
+ if err != nil {
+ return nil, err
+ }
+
+ return domain, nil
+}
+
+// AddRecord adds Record for given domain.
+func (c *Client) AddRecord(domainID int, body Record) (*Record, error) {
+ uri := fmt.Sprintf("/%d/records/", domainID)
+ req, err := c.newRequest(http.MethodPost, uri, body)
+ if err != nil {
+ return nil, err
+ }
+
+ record := &Record{}
+ _, err = c.do(req, record)
+ if err != nil {
+ return nil, err
+ }
+
+ return record, nil
+}
+
+// ListRecords returns list records for specific domain.
+func (c *Client) ListRecords(domainID int) ([]*Record, error) {
+ uri := fmt.Sprintf("/%d/records/", domainID)
+ req, err := c.newRequest(http.MethodGet, uri, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var records []*Record
+ _, err = c.do(req, &records)
+ if err != nil {
+ return nil, err
+ }
+ return records, nil
+}
+
+// DeleteRecord deletes specific record.
+func (c *Client) DeleteRecord(domainID, recordID int) error {
+ uri := fmt.Sprintf("/%d/records/%d", domainID, recordID)
+ req, err := c.newRequest(http.MethodDelete, uri, nil)
+ if err != nil {
+ return err
+ }
+
+ _, err = c.do(req, nil)
+ return err
+}
+
+func (c *Client) newRequest(method, uri string, body interface{}) (*http.Request, error) {
+ buf := new(bytes.Buffer)
+
+ if body != nil {
+ err := json.NewEncoder(buf).Encode(body)
+ if err != nil {
+ return nil, fmt.Errorf("failed to encode request body with error: %v", err)
+ }
+ }
+
+ req, err := http.NewRequest(method, c.baseURL+uri, buf)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create new http request with error: %v", err)
+ }
+
+ req.Header.Add("X-Token", c.token)
+ req.Header.Add("Content-Type", "application/json")
+ req.Header.Add("Accept", "application/json")
+
+ return req, nil
+}
+
+func (c *Client) do(req *http.Request, to interface{}) (*http.Response, error) {
+ resp, err := c.httpClient.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("request failed with error: %v", err)
+ }
+
+ err = checkResponse(resp)
+ if err != nil {
+ return resp, err
+ }
+
+ if to != nil {
+ if err = unmarshalBody(resp, to); err != nil {
+ return resp, err
+ }
+ }
+
+ return resp, nil
+}
+
+func checkResponse(resp *http.Response) error {
+ if resp.StatusCode >= http.StatusBadRequest &&
+ resp.StatusCode <= http.StatusNetworkAuthenticationRequired {
+
+ if resp.Body == nil {
+ return fmt.Errorf("request failed with status code %d and empty body", resp.StatusCode)
+ }
+
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ apiError := APIError{}
+ err = json.Unmarshal(body, &apiError)
+ if err != nil {
+ return fmt.Errorf("request failed with status code %d, response body: %s", resp.StatusCode, string(body))
+ }
+
+ return fmt.Errorf("request failed with status code %d: %v", resp.StatusCode, apiError)
+ }
+
+ return nil
+}
+
+func unmarshalBody(resp *http.Response, to interface{}) error {
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ err = json.Unmarshal(body, to)
+ if err != nil {
+ return fmt.Errorf("unmarshaling error: %v: %s", err, string(body))
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/xenolf/lego/providers/dns/selectel/selectel.go b/vendor/github.com/xenolf/lego/providers/dns/selectel/selectel.go
new file mode 100644
index 000000000..9e48b85dc
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/providers/dns/selectel/selectel.go
@@ -0,0 +1,153 @@
+// Package selectel implements a DNS provider for solving the DNS-01 challenge using Selectel Domains API.
+// Selectel Domain API reference: https://kb.selectel.com/23136054.html
+// Token: https://my.selectel.ru/profile/apikeys
+package selectel
+
+import (
+ "errors"
+ "fmt"
+ "net/http"
+ "time"
+
+ "github.com/xenolf/lego/acme"
+ "github.com/xenolf/lego/platform/config/env"
+)
+
+const (
+ defaultBaseURL = "https://api.selectel.ru/domains/v1"
+ minTTL = 60
+)
+
+const (
+ envNamespace = "SELECTEL_"
+ baseURLEnvVar = envNamespace + "BASE_URL"
+ apiTokenEnvVar = envNamespace + "API_TOKEN"
+ ttlEnvVar = envNamespace + "TTL"
+ propagationTimeoutEnvVar = envNamespace + "PROPAGATION_TIMEOUT"
+ pollingIntervalEnvVar = envNamespace + "POLLING_INTERVAL"
+ httpTimeoutEnvVar = envNamespace + "HTTP_TIMEOUT"
+)
+
+// Config is used to configure the creation of the DNSProvider.
+type Config struct {
+ BaseURL string
+ Token string
+ PropagationTimeout time.Duration
+ PollingInterval time.Duration
+ TTL int
+ HTTPClient *http.Client
+}
+
+// NewDefaultConfig returns a default configuration for the DNSProvider.
+func NewDefaultConfig() *Config {
+ return &Config{
+ BaseURL: env.GetOrDefaultString(baseURLEnvVar, defaultBaseURL),
+ TTL: env.GetOrDefaultInt(ttlEnvVar, minTTL),
+ PropagationTimeout: env.GetOrDefaultSecond(propagationTimeoutEnvVar, 120*time.Second),
+ PollingInterval: env.GetOrDefaultSecond(pollingIntervalEnvVar, 2*time.Second),
+ HTTPClient: &http.Client{
+ Timeout: env.GetOrDefaultSecond(httpTimeoutEnvVar, 30*time.Second),
+ },
+ }
+}
+
+// DNSProvider is an implementation of the acme.ChallengeProvider interface.
+type DNSProvider struct {
+ config *Config
+ client *Client
+}
+
+// NewDNSProvider returns a DNSProvider instance configured for Selectel Domains API.
+// API token must be passed in the environment variable SELECTEL_API_TOKEN.
+func NewDNSProvider() (*DNSProvider, error) {
+ values, err := env.Get(apiTokenEnvVar)
+ if err != nil {
+ return nil, fmt.Errorf("selectel: %v", err)
+ }
+
+ config := NewDefaultConfig()
+ config.Token = values[apiTokenEnvVar]
+
+ return NewDNSProviderConfig(config)
+}
+
+// NewDNSProviderConfig return a DNSProvider instance configured for selectel.
+func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
+ if config == nil {
+ return nil, errors.New("selectel: the configuration of the DNS provider is nil")
+ }
+
+ if config.Token == "" {
+ return nil, errors.New("selectel: credentials missing")
+ }
+
+ if config.TTL < minTTL {
+ return nil, fmt.Errorf("selectel: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL)
+ }
+
+ client := NewClient(ClientOpts{
+ BaseURL: config.BaseURL,
+ Token: config.Token,
+ UserAgent: acme.UserAgent,
+ HTTPClient: config.HTTPClient,
+ })
+
+ return &DNSProvider{config: config, client: client}, nil
+}
+
+// Timeout returns the Timeout and interval to use when checking for DNS propagation.
+// Adjusting here to cope with spikes in propagation times.
+func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
+ return d.config.PropagationTimeout, d.config.PollingInterval
+}
+
+// Present creates a TXT record to fulfill DNS-01 challenge.
+func (d *DNSProvider) Present(domain, token, keyAuth string) error {
+ fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
+
+ domainObj, err := d.client.GetDomainByName(domain)
+ if err != nil {
+ return fmt.Errorf("selectel: %v", err)
+ }
+
+ txtRecord := Record{
+ Type: "TXT",
+ TTL: d.config.TTL,
+ Name: fqdn,
+ Content: value,
+ }
+ _, err = d.client.AddRecord(domainObj.ID, txtRecord)
+ if err != nil {
+ return fmt.Errorf("selectel: %v", err)
+ }
+
+ return nil
+}
+
+// CleanUp removes a TXT record used for DNS-01 challenge.
+func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
+ fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
+
+ domainObj, err := d.client.GetDomainByName(domain)
+ if err != nil {
+ return fmt.Errorf("selectel: %v", err)
+ }
+
+ records, err := d.client.ListRecords(domainObj.ID)
+ if err != nil {
+ return fmt.Errorf("selectel: %v", err)
+ }
+
+ // Delete records with specific FQDN
+ var lastErr error
+ for _, record := range records {
+ if record.Name == fqdn {
+ err = d.client.DeleteRecord(domainObj.ID, record.ID)
+ if err != nil {
+ lastErr = fmt.Errorf("selectel: %v", err)
+ }
+ }
+ }
+
+ return lastErr
+}
diff --git a/vendor/github.com/xenolf/lego/providers/dns/transip/transip.go b/vendor/github.com/xenolf/lego/providers/dns/transip/transip.go
new file mode 100644
index 000000000..aeb155f8f
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/providers/dns/transip/transip.go
@@ -0,0 +1,150 @@
+// Package transip implements a DNS provider for solving the DNS-01 challenge using TransIP.
+package transip
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/transip/gotransip"
+ transipdomain "github.com/transip/gotransip/domain"
+ "github.com/xenolf/lego/acme"
+ "github.com/xenolf/lego/platform/config/env"
+)
+
+// Config is used to configure the creation of the DNSProvider
+type Config struct {
+ AccountName string
+ PrivateKeyPath string
+ PropagationTimeout time.Duration
+ PollingInterval time.Duration
+ TTL int64
+}
+
+// NewDefaultConfig returns a default configuration for the DNSProvider
+func NewDefaultConfig() *Config {
+ return &Config{
+ TTL: int64(env.GetOrDefaultInt("TRANSIP_TTL", 10)),
+ PropagationTimeout: env.GetOrDefaultSecond("TRANSIP_PROPAGATION_TIMEOUT", 10*time.Minute),
+ PollingInterval: env.GetOrDefaultSecond("TRANSIP_POLLING_INTERVAL", 10*time.Second),
+ }
+}
+
+// DNSProvider describes a provider for TransIP
+type DNSProvider struct {
+ config *Config
+ client gotransip.SOAPClient
+}
+
+// NewDNSProvider returns a DNSProvider instance configured for TransIP.
+// Credentials must be passed in the environment variables:
+// TRANSIP_ACCOUNTNAME, TRANSIP_PRIVATEKEYPATH.
+func NewDNSProvider() (*DNSProvider, error) {
+ values, err := env.Get("TRANSIP_ACCOUNT_NAME", "TRANSIP_PRIVATE_KEY_PATH")
+ if err != nil {
+ return nil, fmt.Errorf("transip: %v", err)
+ }
+
+ config := NewDefaultConfig()
+ config.AccountName = values["TRANSIP_ACCOUNT_NAME"]
+ config.PrivateKeyPath = values["TRANSIP_PRIVATE_KEY_PATH"]
+
+ return NewDNSProviderConfig(config)
+}
+
+// NewDNSProviderConfig return a DNSProvider instance configured for TransIP.
+func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
+ if config == nil {
+ return nil, errors.New("transip: the configuration of the DNS provider is nil")
+ }
+
+ client, err := gotransip.NewSOAPClient(gotransip.ClientConfig{
+ AccountName: config.AccountName,
+ PrivateKeyPath: config.PrivateKeyPath,
+ })
+ if err != nil {
+ return nil, fmt.Errorf("transip: %v", err)
+ }
+
+ return &DNSProvider{client: client, config: config}, nil
+}
+
+// Timeout returns the timeout and interval to use when checking for DNS propagation.
+// Adjusting here to cope with spikes in propagation times.
+func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
+ return d.config.PropagationTimeout, d.config.PollingInterval
+}
+
+// Present creates a TXT record to fulfill the dns-01 challenge
+func (d *DNSProvider) Present(domain, token, keyAuth string) error {
+ fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
+
+ authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
+ if err != nil {
+ return err
+ }
+
+ domainName := acme.UnFqdn(authZone)
+
+ // get the subDomain
+ subDomain := strings.TrimSuffix(acme.UnFqdn(fqdn), "."+domainName)
+
+ // get all DNS entries
+ info, err := transipdomain.GetInfo(d.client, domainName)
+ if err != nil {
+ return fmt.Errorf("transip: error for %s in Present: %v", domain, err)
+ }
+
+ // include the new DNS entry
+ dnsEntries := append(info.DNSEntries, transipdomain.DNSEntry{
+ Name: subDomain,
+ TTL: d.config.TTL,
+ Type: transipdomain.DNSEntryTypeTXT,
+ Content: value,
+ })
+
+ // set the updated DNS entries
+ err = transipdomain.SetDNSEntries(d.client, domainName, dnsEntries)
+ if err != nil {
+ return fmt.Errorf("transip: %v", err)
+ }
+ return nil
+}
+
+// CleanUp removes the TXT record matching the specified parameters
+func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
+ fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
+
+ authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
+ if err != nil {
+ return err
+ }
+
+ domainName := acme.UnFqdn(authZone)
+
+ // get the subDomain
+ subDomain := strings.TrimSuffix(acme.UnFqdn(fqdn), "."+domainName)
+
+ // get all DNS entries
+ info, err := transipdomain.GetInfo(d.client, domainName)
+ if err != nil {
+ return fmt.Errorf("transip: error for %s in CleanUp: %v", fqdn, err)
+ }
+
+ // loop through the existing entries and remove the specific record
+ updatedEntries := info.DNSEntries[:0]
+ for _, e := range info.DNSEntries {
+ if e.Name != subDomain {
+ updatedEntries = append(updatedEntries, e)
+ }
+ }
+
+ // set the updated DNS entries
+ err = transipdomain.SetDNSEntries(d.client, domainName, updatedEntries)
+ if err != nil {
+ return fmt.Errorf("transip: couldn't get Record ID in CleanUp: %sv", err)
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/xenolf/lego/providers/dns/vscale/client.go b/vendor/github.com/xenolf/lego/providers/dns/vscale/client.go
new file mode 100644
index 000000000..1f4826f29
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/providers/dns/vscale/client.go
@@ -0,0 +1,211 @@
+package vscale
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+)
+
+// Domain represents domain name.
+type Domain struct {
+ ID int `json:"id,omitempty"`
+ Name string `json:"name,omitempty"`
+}
+
+// Record represents DNS record.
+type Record struct {
+ ID int `json:"id,omitempty"`
+ Name string `json:"name,omitempty"`
+ Type string `json:"type,omitempty"` // Record type (SOA, NS, A/AAAA, CNAME, SRV, MX, TXT, SPF)
+ TTL int `json:"ttl,omitempty"`
+ Email string `json:"email,omitempty"` // Email of domain's admin (only for SOA records)
+ Content string `json:"content,omitempty"` // Record content (not for SRV)
+}
+
+// APIError API error message
+type APIError struct {
+ Description string `json:"error"`
+ Code int `json:"code"`
+ Field string `json:"field"`
+}
+
+func (a *APIError) Error() string {
+ return fmt.Sprintf("API error: %d - %s - %s", a.Code, a.Description, a.Field)
+}
+
+// ClientOpts represents options to init client.
+type ClientOpts struct {
+ BaseURL string
+ Token string
+ UserAgent string
+ HTTPClient *http.Client
+}
+
+// Client represents DNS client.
+type Client struct {
+ baseURL string
+ token string
+ userAgent string
+ httpClient *http.Client
+}
+
+// NewClient returns a client instance.
+func NewClient(opts ClientOpts) *Client {
+ if opts.HTTPClient == nil {
+ opts.HTTPClient = &http.Client{}
+ }
+
+ return &Client{
+ token: opts.Token,
+ baseURL: opts.BaseURL,
+ httpClient: opts.HTTPClient,
+ userAgent: opts.UserAgent,
+ }
+}
+
+// GetDomainByName gets Domain object by its name.
+func (c *Client) GetDomainByName(domainName string) (*Domain, error) {
+ uri := fmt.Sprintf("/%s", domainName)
+ req, err := c.newRequest(http.MethodGet, uri, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ domain := &Domain{}
+ _, err = c.do(req, domain)
+ if err != nil {
+ return nil, err
+ }
+
+ return domain, nil
+}
+
+// AddRecord adds Record for given domain.
+func (c *Client) AddRecord(domainID int, body Record) (*Record, error) {
+ uri := fmt.Sprintf("/%d/records/", domainID)
+ req, err := c.newRequest(http.MethodPost, uri, body)
+ if err != nil {
+ return nil, err
+ }
+
+ record := &Record{}
+ _, err = c.do(req, record)
+ if err != nil {
+ return nil, err
+ }
+
+ return record, nil
+}
+
+// ListRecords returns list records for specific domain.
+func (c *Client) ListRecords(domainID int) ([]*Record, error) {
+ uri := fmt.Sprintf("/%d/records/", domainID)
+ req, err := c.newRequest(http.MethodGet, uri, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var records []*Record
+ _, err = c.do(req, &records)
+ if err != nil {
+ return nil, err
+ }
+ return records, nil
+}
+
+// DeleteRecord deletes specific record.
+func (c *Client) DeleteRecord(domainID, recordID int) error {
+ uri := fmt.Sprintf("/%d/records/%d", domainID, recordID)
+ req, err := c.newRequest(http.MethodDelete, uri, nil)
+ if err != nil {
+ return err
+ }
+
+ _, err = c.do(req, nil)
+ return err
+}
+
+func (c *Client) newRequest(method, uri string, body interface{}) (*http.Request, error) {
+ buf := new(bytes.Buffer)
+
+ if body != nil {
+ err := json.NewEncoder(buf).Encode(body)
+ if err != nil {
+ return nil, fmt.Errorf("failed to encode request body with error: %v", err)
+ }
+ }
+
+ req, err := http.NewRequest(method, c.baseURL+uri, buf)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create new http request with error: %v", err)
+ }
+
+ req.Header.Add("X-Token", c.token)
+ req.Header.Add("Content-Type", "application/json")
+ req.Header.Add("Accept", "application/json")
+
+ return req, nil
+}
+
+func (c *Client) do(req *http.Request, to interface{}) (*http.Response, error) {
+ resp, err := c.httpClient.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("request failed with error: %v", err)
+ }
+
+ err = checkResponse(resp)
+ if err != nil {
+ return resp, err
+ }
+
+ if to != nil {
+ if err = unmarshalBody(resp, to); err != nil {
+ return resp, err
+ }
+ }
+
+ return resp, nil
+}
+
+func checkResponse(resp *http.Response) error {
+ if resp.StatusCode >= http.StatusBadRequest &&
+ resp.StatusCode <= http.StatusNetworkAuthenticationRequired {
+
+ if resp.Body == nil {
+ return fmt.Errorf("request failed with status code %d and empty body", resp.StatusCode)
+ }
+
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ apiError := APIError{}
+ err = json.Unmarshal(body, &apiError)
+ if err != nil {
+ return fmt.Errorf("request failed with status code %d, response body: %s", resp.StatusCode, string(body))
+ }
+
+ return fmt.Errorf("request failed with status code %d: %v", resp.StatusCode, apiError)
+ }
+
+ return nil
+}
+
+func unmarshalBody(resp *http.Response, to interface{}) error {
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ err = json.Unmarshal(body, to)
+ if err != nil {
+ return fmt.Errorf("unmarshaling error: %v: %s", err, string(body))
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/xenolf/lego/providers/dns/vscale/vscale.go b/vendor/github.com/xenolf/lego/providers/dns/vscale/vscale.go
new file mode 100644
index 000000000..b44b159d9
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/providers/dns/vscale/vscale.go
@@ -0,0 +1,153 @@
+// Package selectel implements a DNS provider for solving the DNS-01 challenge using Vscale Domains API.
+// Vscale Domain API reference: https://developers.vscale.io/documentation/api/v1/#api-Domains
+// Token: https://vscale.io/panel/settings/tokens/
+package vscale
+
+import (
+ "errors"
+ "fmt"
+ "net/http"
+ "time"
+
+ "github.com/xenolf/lego/acme"
+ "github.com/xenolf/lego/platform/config/env"
+)
+
+const (
+ defaultBaseURL = "https://api.vscale.io/v1/domains"
+ minTTL = 60
+)
+
+const (
+ envNamespace = "VSCALE_"
+ baseURLEnvVar = envNamespace + "BASE_URL"
+ apiTokenEnvVar = envNamespace + "API_TOKEN"
+ ttlEnvVar = envNamespace + "TTL"
+ propagationTimeoutEnvVar = envNamespace + "PROPAGATION_TIMEOUT"
+ pollingIntervalEnvVar = envNamespace + "POLLING_INTERVAL"
+ httpTimeoutEnvVar = envNamespace + "HTTP_TIMEOUT"
+)
+
+// Config is used to configure the creation of the DNSProvider.
+type Config struct {
+ BaseURL string
+ Token string
+ PropagationTimeout time.Duration
+ PollingInterval time.Duration
+ TTL int
+ HTTPClient *http.Client
+}
+
+// NewDefaultConfig returns a default configuration for the DNSProvider.
+func NewDefaultConfig() *Config {
+ return &Config{
+ BaseURL: env.GetOrDefaultString(baseURLEnvVar, defaultBaseURL),
+ TTL: env.GetOrDefaultInt(ttlEnvVar, minTTL),
+ PropagationTimeout: env.GetOrDefaultSecond(propagationTimeoutEnvVar, 120*time.Second),
+ PollingInterval: env.GetOrDefaultSecond(pollingIntervalEnvVar, 2*time.Second),
+ HTTPClient: &http.Client{
+ Timeout: env.GetOrDefaultSecond(httpTimeoutEnvVar, 30*time.Second),
+ },
+ }
+}
+
+// DNSProvider is an implementation of the acme.ChallengeProvider interface.
+type DNSProvider struct {
+ config *Config
+ client *Client
+}
+
+// NewDNSProvider returns a DNSProvider instance configured for Vscale Domains API.
+// API token must be passed in the environment variable VSCALE_API_TOKEN.
+func NewDNSProvider() (*DNSProvider, error) {
+ values, err := env.Get(apiTokenEnvVar)
+ if err != nil {
+ return nil, fmt.Errorf("vscale: %v", err)
+ }
+
+ config := NewDefaultConfig()
+ config.Token = values[apiTokenEnvVar]
+
+ return NewDNSProviderConfig(config)
+}
+
+// NewDNSProviderConfig return a DNSProvider instance configured for Vscale.
+func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
+ if config == nil {
+ return nil, errors.New("vscale: the configuration of the DNS provider is nil")
+ }
+
+ if config.Token == "" {
+ return nil, errors.New("vscale: credentials missing")
+ }
+
+ if config.TTL < minTTL {
+ return nil, fmt.Errorf("vscale: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL)
+ }
+
+ client := NewClient(ClientOpts{
+ BaseURL: config.BaseURL,
+ Token: config.Token,
+ UserAgent: acme.UserAgent,
+ HTTPClient: config.HTTPClient,
+ })
+
+ return &DNSProvider{config: config, client: client}, nil
+}
+
+// Timeout returns the Timeout and interval to use when checking for DNS propagation.
+// Adjusting here to cope with spikes in propagation times.
+func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
+ return d.config.PropagationTimeout, d.config.PollingInterval
+}
+
+// Present creates a TXT record to fulfill DNS-01 challenge.
+func (d *DNSProvider) Present(domain, token, keyAuth string) error {
+ fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
+
+ domainObj, err := d.client.GetDomainByName(domain)
+ if err != nil {
+ return fmt.Errorf("vscale: %v", err)
+ }
+
+ txtRecord := Record{
+ Type: "TXT",
+ TTL: d.config.TTL,
+ Name: fqdn,
+ Content: value,
+ }
+ _, err = d.client.AddRecord(domainObj.ID, txtRecord)
+ if err != nil {
+ return fmt.Errorf("vscale: %v", err)
+ }
+
+ return nil
+}
+
+// CleanUp removes a TXT record used for DNS-01 challenge.
+func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
+ fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
+
+ domainObj, err := d.client.GetDomainByName(domain)
+ if err != nil {
+ return fmt.Errorf("vscale: %v", err)
+ }
+
+ records, err := d.client.ListRecords(domainObj.ID)
+ if err != nil {
+ return fmt.Errorf("vscale: %v", err)
+ }
+
+ // Delete records with specific FQDN
+ var lastErr error
+ for _, record := range records {
+ if record.Name == fqdn {
+ err = d.client.DeleteRecord(domainObj.ID, record.ID)
+ if err != nil {
+ lastErr = fmt.Errorf("vscale: %v", err)
+ }
+ }
+ }
+
+ return lastErr
+}