Cherry pick v1.7 into master

This commit is contained in:
Ludovic Fernandez 2018-11-19 16:40:03 +01:00 committed by Traefiker Bot
parent a09dfa3ce1
commit b6498cdcbc
73 changed files with 6573 additions and 186 deletions

47
Gopkg.lock generated
View file

@ -543,6 +543,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"
@ -812,6 +818,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"
@ -956,10 +968,10 @@
revision = "2bca23e0e452137f789efbc8610126fd8b94f73b"
[[projects]]
branch = "master"
name = "github.com/mitchellh/mapstructure"
packages = ["."]
revision = "b4575eea38cca1123ec2dc90c26529b5c5acfcff"
revision = "3536a929edddb9a5b34bd6861dc4a9647cb459fe"
version = "v1.1.2"
[[projects]]
branch = "master"
@ -1167,6 +1179,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 = ["."]
@ -1214,6 +1232,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"
@ -1330,6 +1358,7 @@
"providers/dns/bluecat",
"providers/dns/cloudflare",
"providers/dns/cloudxns",
"providers/dns/conoha",
"providers/dns/digitalocean",
"providers/dns/dnsimple",
"providers/dns/dnsmadeeasy",
@ -1346,10 +1375,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",
@ -1362,11 +1394,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"
@ -1531,8 +1566,8 @@
"ddtrace/opentracer",
"ddtrace/tracer"
]
revision = "d052956664af54dbcff2712d10c67c76fbfc299f"
version = "v1.0.0"
revision = "48eeff27357376bcb31a15674dc4be9078de88b3"
version = "v1.5.0"
[[projects]]
name = "gopkg.in/fsnotify.v1"
@ -1791,6 +1826,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "d4f73c986b64003e14a36894149943e956e0dfa40b8837bfd11bf5fa3ad78c77"
inputs-digest = "817d5ad6a6ae085d57953a73f7b615a223fa3e70b6152bf87382508bfbfbd655"
solver-name = "gps-cdcl"
solver-version = 1

View file

@ -267,4 +267,4 @@
[[constraint]]
name = "gopkg.in/DataDog/dd-trace-go.v1"
version = "1.0.0"
version = "1.5.0"

View file

@ -133,7 +133,7 @@ git clone https://github.com/containous/traefik
## Introductory Videos
Here is a talk given by [Emile Vauge](https://github.com/emilevauge) at [GopherCon 2017](https://gophercon.com/).
Here is a talk given by [Emile Vauge](https://github.com/emilevauge) at GopherCon 2017.
You will learn Traefik basics in less than 10 minutes.
[![Traefik GopherCon 2017](https://img.youtube.com/vi/RgudiksfL-k/0.jpg)](https://www.youtube.com/watch?v=RgudiksfL-k)

View file

@ -743,8 +743,6 @@ If you want to dig into more details, here is the source code of the collecting
By default we anonymize all configuration fields, except fields tagged with `export=true`.
You can check all fields in the [godoc](https://godoc.org/github.com/containous/traefik/configuration#GlobalConfiguration).
### How to enable this ?
You can enable the collecting system by:

View file

@ -275,21 +275,22 @@ Here is a list of supported `provider`s, that can automate the DNS verification,
| Provider Name | Provider Code | Environment Variables | Wildcard & Root Domain Support |
|--------------------------------------------------------|----------------|-------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------|
| [ACME DNS](https://github.com/joohoi/acme-dns) | `acmedns` | `ACME_DNS_API_BASE`, `ACME_DNS_STORAGE_PATH` | Not tested yet |
| [ACME DNS](https://github.com/joohoi/acme-dns) | `acme-dns` | `ACME_DNS_API_BASE`, `ACME_DNS_STORAGE_PATH` | Not tested yet |
| [Alibaba Cloud](https://www.vultr.com) | `alidns` | `ALICLOUD_ACCESS_KEY`, `ALICLOUD_SECRET_KEY`, `ALICLOUD_REGION_ID` | Not tested yet |
| [Auroradns](https://www.pcextreme.com/aurora/dns) | `auroradns` | `AURORA_USER_ID`, `AURORA_KEY`, `AURORA_ENDPOINT` | Not tested yet |
| [Azure](https://azure.microsoft.com/services/dns/) | `azure` | `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_SUBSCRIPTION_ID`, `AZURE_TENANT_ID`, `AZURE_RESOURCE_GROUP`, `[AZURE_METADATA_ENDPOINT]` | Not tested yet |
| [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 |
| [DNSPod](http://www.dnspod.net/) | `dnspod` | `DNSPOD_API_KEY` | Not tested yet |
| [DNSPod](https://www.dnspod.com/) | `dnspod` | `DNSPOD_API_KEY` | Not tested yet |
| [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 <kbd>Enter</kbd>. | 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`

View file

@ -106,10 +106,10 @@ entryPoint = "foo"
entryPoint = "bar"
```
In the above example, you would access a regular path, administration panel, and health-check as follows:
In the above example, you would access a regular path, dashboard, and health-check as follows:
* Regular path: `http://hostname:80/path`
* Admin Panel: `http://hostname:8083/`
* Dashboard: `http://hostname:8083/`
* Ping URL: `http://hostname:8082/ping`
In the above example, it is _very_ important to create a named dedicated entry point, and do **not** include it in `defaultEntryPoints`.

View file

@ -236,11 +236,11 @@ Labels can be used on containers to override default behavior.
| `traefik.backend.loadbalancer.method=drr` | Overrides the default `wrr` load balancer algorithm |
| `traefik.backend.loadbalancer.stickiness=true` | Enables backend sticky sessions |
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Sets the cookie name manually for sticky sessions |
| `traefik.backend.loadbalancer.swarm=true` | Uses Swarm's inbuilt load balancer (only relevant under Swarm Mode). |
| `traefik.backend.loadbalancer.swarm=true` | Uses Swarm's inbuilt load balancer (only relevant under Swarm Mode). [3]. |
| `traefik.backend.maxconn.amount=10` | Sets a maximum number of connections to the backend.<br>Must be used in conjunction with the below label to take effect. |
| `traefik.backend.maxconn.extractorfunc=client.ip` | Sets the function to be used against the request to determine what to limit maximum connections to the backend by.<br>Must be used in conjunction with the above label to take effect. |
| `traefik.frontend.auth.basic=EXPR` | Sets the basic authentication to this frontend in CSV format: `User:Hash,User:Hash` [2] (DEPRECATED). |
| `traefik.frontend.auth.basic.realm=REALM` | Sets the realm of basic authentication to this frontend. |
| `traefik.frontend.auth.basic.realm=REALM` | Sets the realm of basic authentication to this frontend. |
| `traefik.frontend.auth.basic.removeHeader=true` | If set to `true`, removes the `Authorization` header. |
| `traefik.frontend.auth.basic.users=EXPR` | Sets the basic authentication to this frontend in CSV format: `User:Hash,User:Hash` [2]. |
| `traefik.frontend.auth.basic.usersFile=/path/.htpasswd` | Sets the basic authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. |
@ -287,8 +287,8 @@ Labels can be used on containers to override default behavior.
| `traefik.frontend.whiteList.ipStrategy.depth=5` | See [whitelist](/configuration/entrypoints/#white-listing) |
| `traefik.frontend.whiteList.ipStrategy.excludedIPs=127.0.0.1` | See [whitelist](/configuration/entrypoints/#white-listing) |
[1] `traefik.docker.network`:
If a container is linked to several networks, be sure to set the proper network name (you can check with `docker inspect <container_id>`) otherwise it will randomly pick one (depending on how docker is returning them).
[1] `traefik.docker.network`:
If a container is linked to several networks, be sure to set the proper network name (you can check with `docker inspect <container_id>`) otherwise it will randomly pick one (depending on how docker is returning them).
For instance when deploying docker `stack` from compose files, the compose defined networks will be prefixed with the `stack` name.
Or if your service references external network use it's name instead.
@ -297,6 +297,11 @@ To create `user:password` pair, it's possible to use this command:
`echo $(htpasswd -nb user password) | sed -e s/\\$/\\$\\$/g`.
The result will be `user:$$apr1$$9Cv/OMGj$$ZomWQzuQbL.3TRCS81A1g/`, note additional symbol `$` makes escaping.
[3] `traefik.backend.loadbalancer.swarm`:
If you enable this option, Traefik will use the virtual IP provided by docker swarm instead of the containers IPs.
Which means that Traefik will not perform any kind of load balancing and will delegate this task to swarm.
It also means that Traefik will manipulate only one backend, not one backend per container.
#### Custom Headers
| Label | Description |

View file

@ -128,10 +128,11 @@ This will give more flexibility in cloud/dynamic environments.
Traefik automatically requests endpoint information based on the service provided in the ingress spec.
Although traefik will connect directly to the endpoints (pods), it still checks the service port to see if TLS communication is required.
There are 2 ways to configure Traefik to use https to communicate with backend pods:
There are 3 ways to configure Traefik to use https to communicate with backend pods:
1. If the service port defined in the ingress spec is 443 (note that you can still use `targetPort` to use a different port on your pod).
2. If the service port defined in the ingress spec has a name that starts with `https` (such as `https-api`, `https-web` or just `https`).
3. If the ingress spec includes the annotation `ingress.kubernetes.io/protocol: https`.
If either of those configuration options exist, then the backend communication protocol is assumed to be TLS, and will connect via TLS automatically.
@ -169,7 +170,7 @@ The following general annotations are applicable on the Ingress object:
| `traefik.ingress.kubernetes.io/whiteList-ipstrategy=true` | Uses the default IPStrategy.<br>Can be used when there is an existing `clientIPStrategy` but you want the remote address for whitelisting. |
| `traefik.ingress.kubernetes.io/whiteList-ipstrategy-depth=5` | See [whitelist](/configuration/entrypoints/#white-listing) |
| `traefik.ingress.kubernetes.io/whiteList-ipstrategy-excludedIPs=127.0.0. 1` | See [whitelist](/configuration/entrypoints/#white-listing) |
| `ingress.kubernetes.io/protocol: <NAME>` | Set the protocol Traefik will use to communicate with pods. |
| `ingress.kubernetes.io/protocol: <NAME>` | Set the protocol Traefik will use to communicate with pods. Acceptable protocols: http,https,h2c |
<1> `traefik.ingress.kubernetes.io/app-root`:
Non-root paths will not be affected by this annotation and handled normally.

View file

@ -27,10 +27,10 @@ The `/ping` health-check URL is enabled with the command-line `--ping` or config
Thus, if you have a regular path for `/foo` and an entrypoint on `:80`, you would access them as follows:
* Regular path: `http://hostname:80/foo`
* Admin panel: `http://hostname:8080/`
* Dashboard: `http://hostname:8080/`
* Ping URL: `http://hostname:8080/ping`
However, for security reasons, you may want to be able to expose the `/ping` health-check URL to outside health-checkers, e.g. an Internet service or cloud load-balancer, _without_ exposing your administration panel's port.
However, for security reasons, you may want to be able to expose the `/ping` health-check URL to outside health-checkers, e.g. an Internet service or cloud load-balancer, _without_ exposing your dashboard's port.
In many environments, the security staff may not _allow_ you to expose it.
You have two options:
@ -40,7 +40,7 @@ You have two options:
### Ping health check on a regular entry point
To proxy `/ping` from a regular entry point to the administration one without exposing the panel, do the following:
To proxy `/ping` from a regular entry point to the administration one without exposing the dashboard, do the following:
```toml
defaultEntryPoints = ["http"]

View file

@ -176,7 +176,7 @@ Our recommendation would be to see for yourself how simple it is to enable HTTPS
## Resources
Here is a talk given by [Emile Vauge](https://github.com/emilevauge) at [GopherCon 2017](https://gophercon.com).
Here is a talk given by [Emile Vauge](https://github.com/emilevauge) at GopherCon 2017.
You will learn Traefik basics in less than 10 minutes.
[![Traefik GopherCon 2017](https://img.youtube.com/vi/RgudiksfL-k/0.jpg)](https://www.youtube.com/watch?v=RgudiksfL-k)

View file

@ -375,6 +375,14 @@ We should now be able to visit [traefik-ui.minikube](http://traefik-ui.minikube)
For this example to work you need a TLS entrypoint. You don't have to provide a TLS certificate at this point.
For more details see [here](/configuration/entrypoints/).
You can add a TLS entrypoint by adding the following `args` to the container spec:
```yaml
--defaultentrypoints=http,https
--entrypoints=Name:https Address::443 TLS
--entrypoints=Name:http Address::80
```
To setup an HTTPS-protected ingress, you can leverage the TLS feature of the ingress resource.
```yaml

View file

@ -13,6 +13,6 @@ services:
# A container that exposes a simple API
whoami:
image: emilevauge/whoami # A container that exposes an API to show it's IP address
image: emilevauge/whoami # A container that exposes an API to show its IP address
labels:
- "traefik.frontend.rule=Host:whoami.docker.localhost"

View file

@ -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

View file

@ -56,7 +56,7 @@ func getLinesFromFile(filename string) ([]string, error) {
var filteredLines []string
for _, rawLine := range rawLines {
line := strings.TrimSpace(rawLine)
if line != "" {
if line != "" && !strings.HasPrefix(line, "#") {
filteredLines = append(filteredLines, line)
}
}

View file

@ -198,11 +198,21 @@ func TestBasicAuthUsersFromFile(t *testing.T) {
userFileContent: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/\ntest2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0\n",
givenUsers: []string{"test2:$apr1$mK.GtItK$ncnLYvNLek0weXdxo68690"},
expectedUsers: map[string]string{"test": "test", "test2": "overridden"},
realm: "trafikee",
realm: "traefik",
},
{
desc: "Should skip comments",
userFileContent: "#Comment\ntest:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/\ntest2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0\n",
givenUsers: []string{},
expectedUsers: map[string]string{"test": "test", "test2": "test2"},
realm: "traefiker",
},
}
for _, test := range testCases {
if test.desc != "Should skip comments" {
continue
}
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -80,10 +80,10 @@ func TestDigestAuthUsersFromFile(t *testing.T) {
},
{
desc: "Should authenticate the correct user based on the realm",
userFileContent: "test:traefik:a2688e031edb4be6a3797f3882655c05\ntest:traefikee:316a669c158c8b7ab1048b03961a7aa5\n",
userFileContent: "test:traefik:a2688e031edb4be6a3797f3882655c05\ntest:traefiker:a3d334dff2645b914918de78bec50bf4\n",
givenUsers: []string{},
expectedUsers: map[string]string{"test": "test2"},
realm: "traefikee",
realm: "traefiker",
},
}

View file

@ -103,3 +103,88 @@ func TestSecureHeader(t *testing.T) {
})
}
}
func TestSSLForceHost(t *testing.T) {
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.Write([]byte("OK"))
})
testCases := []struct {
desc string
host string
secureMiddleware *secureHeader
expected int
}{
{
desc: "http should return a 301",
host: "http://powpow.example.com",
secureMiddleware: newSecure(next, config.Headers{
SSLRedirect: true,
SSLForceHost: true,
SSLHost: "powpow.example.com",
}),
expected: http.StatusMovedPermanently,
},
{
desc: "http sub domain should return a 301",
host: "http://www.powpow.example.com",
secureMiddleware: newSecure(next, config.Headers{
SSLRedirect: true,
SSLForceHost: true,
SSLHost: "powpow.example.com",
}),
expected: http.StatusMovedPermanently,
},
{
desc: "https should return a 200",
host: "https://powpow.example.com",
secureMiddleware: newSecure(next, config.Headers{
SSLRedirect: true,
SSLForceHost: true,
SSLHost: "powpow.example.com",
}),
expected: http.StatusOK,
},
{
desc: "https sub domain should return a 301",
host: "https://www.powpow.example.com",
secureMiddleware: newSecure(next, config.Headers{
SSLRedirect: true,
SSLForceHost: true,
SSLHost: "powpow.example.com",
}),
expected: http.StatusMovedPermanently,
},
{
desc: "http without force host and sub domain should return a 301",
host: "http://www.powpow.example.com",
secureMiddleware: newSecure(next, config.Headers{
SSLRedirect: true,
SSLForceHost: false,
SSLHost: "powpow.example.com",
}),
expected: http.StatusMovedPermanently,
},
{
desc: "https without force host and sub domain should return a 301",
host: "https://www.powpow.example.com",
secureMiddleware: newSecure(next, config.Headers{
SSLRedirect: true,
SSLForceHost: false,
SSLHost: "powpow.example.com",
}),
expected: http.StatusOK,
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
req := testhelpers.MustNewRequest(http.MethodGet, test.host, nil)
rw := httptest.NewRecorder()
test.secureMiddleware.ServeHTTP(rw, req)
assert.Equal(t, test.expected, rw.Result().StatusCode)
})
}
}

View file

@ -212,10 +212,15 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
}
configuration := p.buildConfiguration(containers)
if configuration != nil {
configurationChan <- types.ConfigMessage{
message := types.ConfigMessage{
ProviderName: "docker",
Configuration: configuration,
}
select {
case configurationChan <- message:
case <-ctx.Done():
}
}
}

21
vendor/github.com/fatih/structs/LICENSE generated vendored Normal file
View file

@ -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.

141
vendor/github.com/fatih/structs/field.go generated vendored Normal file
View file

@ -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
}

584
vendor/github.com/fatih/structs/structs.go generated vendored Normal file
View file

@ -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
}

32
vendor/github.com/fatih/structs/tags.go generated vendored Normal file
View file

@ -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:]
}

19
vendor/github.com/kolo/xmlrpc/LICENSE generated vendored Normal file
View file

@ -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.

169
vendor/github.com/kolo/xmlrpc/client.go generated vendored Normal file
View file

@ -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
}

463
vendor/github.com/kolo/xmlrpc/decoder.go generated vendored Normal file
View file

@ -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
}
// </value>
if err = dec.Skip(); err != nil {
return err
}
break
}
}
}
// </member>
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())
// </value>
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")
}
// </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
}

164
vendor/github.com/kolo/xmlrpc/encoder.go generated vendored Normal file
View file

@ -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("<value/>"), 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("<dateTime.iso8601>%s</dateTime.iso8601>", 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("<int>%s</int>", strconv.FormatInt(val.Int(), 10)))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
b = []byte(fmt.Sprintf("<i4>%s</i4>", strconv.FormatUint(val.Uint(), 10)))
case reflect.Float32, reflect.Float64:
b = []byte(fmt.Sprintf("<double>%s</double>",
strconv.FormatFloat(val.Float(), 'g', -1, val.Type().Bits())))
case reflect.Bool:
if val.Bool() {
b = []byte("<boolean>1</boolean>")
} else {
b = []byte("<boolean>0</boolean>")
}
case reflect.String:
var buf bytes.Buffer
xml.Escape(&buf, []byte(val.String()))
if _, ok := val.Interface().(Base64); ok {
b = []byte(fmt.Sprintf("<base64>%s</base64>", buf.String()))
} else {
b = []byte(fmt.Sprintf("<string>%s</string>", buf.String()))
}
default:
return nil, fmt.Errorf("xmlrpc encode error: unsupported type")
}
if err != nil {
return nil, err
}
return []byte(fmt.Sprintf("<value>%s</value>", string(b))), nil
}
func encodeStruct(val reflect.Value) ([]byte, error) {
var b bytes.Buffer
b.WriteString("<struct>")
t := val.Type()
for i := 0; i < t.NumField(); i++ {
b.WriteString("<member>")
f := t.Field(i)
name := f.Tag.Get("xmlrpc")
if name == "" {
name = f.Name
}
b.WriteString(fmt.Sprintf("<name>%s</name>", name))
p, err := encodeValue(val.FieldByName(f.Name))
if err != nil {
return nil, err
}
b.Write(p)
b.WriteString("</member>")
}
b.WriteString("</struct>")
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("<struct>")
keys := val.MapKeys()
for i := 0; i < val.Len(); i++ {
key := keys[i]
kval := val.MapIndex(key)
b.WriteString("<member>")
b.WriteString(fmt.Sprintf("<name>%s</name>", key.String()))
p, err := encodeValue(kval)
if err != nil {
return nil, err
}
b.Write(p)
b.WriteString("</member>")
}
b.WriteString("</struct>")
return b.Bytes(), nil
}
func encodeSlice(val reflect.Value) ([]byte, error) {
var b bytes.Buffer
b.WriteString("<array><data>")
for i := 0; i < val.Len(); i++ {
p, err := encodeValue(val.Index(i))
if err != nil {
return nil, err
}
b.Write(p)
}
b.WriteString("</data></array>")
return b.Bytes(), nil
}

57
vendor/github.com/kolo/xmlrpc/request.go generated vendored Normal file
View file

@ -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(`<?xml version="1.0" encoding="UTF-8"?>`)
b.WriteString(fmt.Sprintf("<methodCall><methodName>%s</methodName>", method))
if args != nil {
b.WriteString("<params>")
for _, arg := range args {
p, err := marshal(arg)
if err != nil {
return nil, err
}
b.WriteString(fmt.Sprintf("<param>%s</param>", string(p)))
}
b.WriteString("</params>")
}
b.WriteString("</methodCall>")
return b.Bytes(), nil
}

52
vendor/github.com/kolo/xmlrpc/response.go generated vendored Normal file
View file

@ -0,0 +1,52 @@
package xmlrpc
import (
"regexp"
)
var (
faultRx = regexp.MustCompile(`<fault>(\s|\S)+</fault>`)
)
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
}

19
vendor/github.com/kolo/xmlrpc/xmlrpc.go generated vendored Normal file
View file

@ -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

View file

@ -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.
//

View file

@ -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(

21
vendor/github.com/smueller18/goinwx/LICENSE generated vendored Normal file
View file

@ -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.

54
vendor/github.com/smueller18/goinwx/account.go generated vendored Normal file
View file

@ -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
}

150
vendor/github.com/smueller18/goinwx/contact.go generated vendored Normal file
View file

@ -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
}

303
vendor/github.com/smueller18/goinwx/domain.go generated vendored Normal file
View file

@ -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
}

133
vendor/github.com/smueller18/goinwx/goinwx.go generated vendored Normal file
View file

@ -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}
}

275
vendor/github.com/smueller18/goinwx/nameserver.go generated vendored Normal file
View file

@ -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)
}

21
vendor/github.com/transip/gotransip/LICENSE generated vendored Normal file
View file

@ -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.

12
vendor/github.com/transip/gotransip/api.go generated vendored Normal file
View file

@ -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"
)

119
vendor/github.com/transip/gotransip/client.go generated vendored Normal file
View file

@ -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)
}

314
vendor/github.com/transip/gotransip/domain/api.go generated vendored Normal file
View file

@ -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)
}

405
vendor/github.com/transip/gotransip/domain/domain.go generated vendored Normal file
View file

@ -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">
<name xsi:type="xsd:string">%s</name>
<authCode xsi:type="xsd:string">%s</authCode>
<isLocked xsi:type="xsd:boolean">%t</isLocked>
<registrationDate xsi:type="xsd:string">%s</registrationDate>
<renewalDate xsi:type="xsd:string">%s</renewalDate>`,
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(` <item xsi:type="ns1:Nameserver">
<hostname xsi:type="xsd:string">%s</hostname>
<ipv4 xsi:type="xsd:string">%s</ipv4>
<ipv6 xsi:type="xsd:string">%s</ipv6>
</item>`, 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(` <item xsi:type="ns1:DnsEntry">
<name xsi:type="xsd:string">%s</name>
<expire xsi:type="xsd:int">%d</expire>
<type xsi:type="xsd:string">%s</type>
<content xsi:type="xsd:string">%s</content>
</item>`, 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(`<branding xsi:type="ns1:DomainBranding">
<companyName xsi:type="xsd:string">%s</companyName>
<supportEmail xsi:type="xsd:string">%s</supportEmail>
<companyUrl xsi:type="xsd:string">%s</companyUrl>
<termsOfUsageUrl xsi:type="xsd:string">%s</termsOfUsageUrl>
<bannerLine1 xsi:type="xsd:string">%s</bannerLine1>
<bannerLine2 xsi:type="xsd:string">%s</bannerLine2>
<bannerLine3 xsi:type="xsd:string">%s</bannerLine3>
</branding>`, 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(` <item xsi:type="ns1:WhoisContact">
<type xsi:type="xsd:string">%s</type>
<firstName xsi:type="xsd:string">%s</firstName>
<middleName xsi:type="xsd:string">%s</middleName>
<lastName xsi:type="xsd:string">%s</lastName>
<companyName xsi:type="xsd:string">%s</companyName>
<companyKvk xsi:type="xsd:string">%s</companyKvk>
<companyType xsi:type="xsd:string">%s</companyType>
<street xsi:type="xsd:string">%s</street>
<number xsi:type="xsd:string">%s</number>
<postalCode xsi:type="xsd:string">%s</postalCode>
<city xsi:type="xsd:string">%s</city>
<phoneNumber xsi:type="xsd:string">%s</phoneNumber>
<faxNumber xsi:type="xsd:string">%s</faxNumber>
<email xsi:type="xsd:string">%s</email>
<country xsi:type="xsd:string">%s</country>
</item>`, 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)
}

49
vendor/github.com/transip/gotransip/sign.go generated vendored Normal file
View file

@ -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
}

419
vendor/github.com/transip/gotransip/soap.go generated vendored Normal file
View file

@ -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 = `<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="%s">
<SOAP-ENV:Body>%s</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
)
// 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("<ns1:%s>", name))
for _, x := range input {
buf.WriteString(x)
}
buf.WriteString(fmt.Sprintf("</ns1:%s>", 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(`<item xsi:type="xsd:string">%s</item>`, 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)
}

37
vendor/github.com/transip/gotransip/util/util.go generated vendored Normal file
View file

@ -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
}

View file

@ -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
}
}
}

View file

@ -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)
}

View file

@ -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,

View file

@ -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)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -88,6 +88,9 @@ type FinishConfig struct {
// Error holds an optional error that should be set on the span before
// finishing.
Error error
// NoDebugStack will prevent any set errors from generating an attached stack trace tag.
NoDebugStack bool
}
// StartSpanConfig holds the configuration for starting a new span. It is usually passed

View file

@ -1,19 +1,70 @@
package ext // import "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
// App types determine how to categorize a trace in the Datadog application.
// For more fine-grained behaviour, use the SpanType* constants.
const (
// DEPRECATED: Use SpanTypeWeb
// AppTypeWeb specifies the Web span type and can be used as a tag value
// for a span's SpanType tag.
AppTypeWeb = "web"
// AppTypeDB specifies the DB span type and can be used as a tag value
// for a span's SpanType tag.
// for a span's SpanType tag. If possible, use one of the SpanType*
// constants for a more accurate indication.
AppTypeDB = "db"
// AppTypeCache specifies the Cache span type and can be used as a tag value
// for a span's SpanType tag.
// for a span's SpanType tag. If possible, consider using SpanTypeRedis or
// SpanTypeMemcached.
AppTypeCache = "cache"
// AppTypeRPC specifies the RPC span type and can be used as a tag value
// for a span's SpanType tag.
AppTypeRPC = "rpc"
)
// Span types have similar behaviour to "app types" and help categorize
// traces in the Datadog application. They can also help fine grain agent
// level bahviours such as obfuscation and quantization, when these are
// enabled in the agent's configuration.
const (
// SpanTypeWeb marks a span as an HTTP server request.
SpanTypeWeb = "web"
// SpanTypeHTTP marks a span as an HTTP client request.
SpanTypeHTTP = "http"
// SpanTypeSQL marks a span as an SQL operation. These spans may
// have an "sql.command" tag.
SpanTypeSQL = "sql"
// SpanTypeCassandra marks a span as a Cassandra operation. These
// spans may have an "sql.command" tag.
SpanTypeCassandra = "cassandra"
// SpanTypeRedis marks a span as a Redis operation. These spans may
// also have a "redis.raw_command" tag.
SpanTypeRedis = "redis"
// SpanTypeMemcached marks a span as a memcached operation.
SpanTypeMemcached = "memcached"
// SpanTypeMongoDB marks a span as a MongoDB operation.
SpanTypeMongoDB = "mongodb"
// SpanTypeElasticSearch marks a span as an ElasticSearch operation.
// These spans may also have an "elasticsearch.body" tag.
SpanTypeElasticSearch = "elasticsearch"
// SpanTypeLevelDB marks a span as a leveldb operation
SpanTypeLevelDB = "leveldb"
// SpanTypeDNS marks a span as a DNS operation.
SpanTypeDNS = "dns"
// SpanTypeMessageConsumer marks a span as a queue operation
SpanTypeMessageConsumer = "queue"
// SpanTypeMessageProducer marks a span as a queue operation.
SpanTypeMessageProducer = "queue"
)

View file

@ -0,0 +1,16 @@
package ext
const (
// DBApplication indicates the application using the database.
DBApplication = "db.application"
// DBName indicates the database name.
DBName = "db.name"
// DBType indicates the type of Database.
DBType = "db.type"
// DBInstance indicates the instance name of Database.
DBInstance = "db.instance"
// DBUser indicates the user name of Database, e.g. "readonly_user" or "reporting_user".
DBUser = "db.user"
// DBStatement records a database statement for the given database type.
DBStatement = "db.statement"
)

View file

@ -0,0 +1,14 @@
package ext
const (
// PeerHostIPV4 records IPv4 host address of the peer.
PeerHostIPV4 = "peer.ipv4"
// PeerHostIPV6 records the IPv6 host address of the peer.
PeerHostIPV6 = "peer.ipv6"
// PeerService records the service name of the peer service.
PeerService = "peer.service"
// PeerHostname records the host name of the peer.
PeerHostname = "peer.hostname"
// PeerPort records the port number of the peer.
PeerPort = "peer.port"
)

View file

@ -27,6 +27,10 @@ const (
// HTTPURL sets the HTTP URL for a span.
HTTPURL = "http.url"
// TODO: In the next major version, suffix these constants (SpanType, etc)
// with "*Key" (SpanTypeKey, etc) to more easily differentiate between
// constants representing tag values and constants representing keys.
// SpanType defines the Span type (web, db, cache).
SpanType = "span.type"
@ -47,4 +51,7 @@ const (
// ErrorStack specifies the stack dump.
ErrorStack = "error.stack"
// Environment specifies the environment to use with a trace.
Environment = "env"
)

View file

@ -15,6 +15,10 @@ var (
func SetGlobalTracer(t ddtrace.Tracer) {
mu.Lock()
defer mu.Unlock()
if !Testing {
// avoid infinite loop when calling (*mocktracer.Tracer).Stop
globalTracer.Stop()
}
globalTracer = t
}

View file

@ -154,10 +154,19 @@ func FinishTime(t time.Time) FinishOption {
}
}
// WithError adds the given error to the span before marking it as finished. If it is
// nil it will be disregarded.
// WithError marks the span as having had an error. It uses the information from
// err to set tags such as the error message, error type and stack trace.
func WithError(err error) FinishOption {
return func(cfg *ddtrace.FinishConfig) {
cfg.Error = err
}
}
// NoDebugStack prevents any error presented using the WithError finishing option
// from generating a stack trace. This is useful in situations where errors are frequent
// and performance is critical.
func NoDebugStack() FinishOption {
return func(cfg *ddtrace.FinishConfig) {
cfg.NoDebugStack = true
}
}

View file

@ -81,7 +81,7 @@ func (s *span) SetTag(key string, value interface{}) {
return
}
if key == ext.Error {
s.setTagError(value)
s.setTagError(value, true)
return
}
if v, ok := value.(string); ok {
@ -99,7 +99,10 @@ func (s *span) SetTag(key string, value interface{}) {
// setTagError sets the error tag. It accounts for various valid scenarios.
// This method is not safe for concurrent use.
func (s *span) setTagError(value interface{}) {
func (s *span) setTagError(value interface{}, debugStack bool) {
if s.finished {
return
}
switch v := value.(type) {
case bool:
// bool value as per Opentracing spec.
@ -114,7 +117,9 @@ func (s *span) setTagError(value interface{}) {
s.Error = 1
s.Meta[ext.ErrorMsg] = v.Error()
s.Meta[ext.ErrorType] = reflect.TypeOf(v).String()
s.Meta[ext.ErrorStack] = string(debug.Stack())
if debugStack {
s.Meta[ext.ErrorStack] = string(debug.Stack())
}
case nil:
// no error
s.Error = 0
@ -166,7 +171,9 @@ func (s *span) Finish(opts ...ddtrace.FinishOption) {
t = cfg.FinishTime.UnixNano()
}
if cfg.Error != nil {
s.SetTag(ext.Error, cfg.Error)
s.Lock()
s.setTagError(cfg.Error, !cfg.NoDebugStack)
s.Unlock()
}
s.finish(t)
}

View file

@ -63,7 +63,7 @@ func (z *span) DecodeMsg(dc *msgp.Reader) (err error) {
if z.Meta == nil && zb0002 > 0 {
z.Meta = make(map[string]string, zb0002)
} else if len(z.Meta) > 0 {
for key, _ := range z.Meta {
for key := range z.Meta {
delete(z.Meta, key)
}
}
@ -90,7 +90,7 @@ func (z *span) DecodeMsg(dc *msgp.Reader) (err error) {
if z.Metrics == nil && zb0003 > 0 {
z.Metrics = make(map[string]float64, zb0003)
} else if len(z.Metrics) > 0 {
for key, _ := range z.Metrics {
for key := range z.Metrics {
delete(z.Metrics, key)
}
}

View file

@ -17,8 +17,7 @@ var _ TextMapReader = (*HTTPHeadersCarrier)(nil)
// Set implements TextMapWriter.
func (c HTTPHeadersCarrier) Set(key, val string) {
h := http.Header(c)
h.Add(key, val)
http.Header(c).Set(key, val)
}
// ForeachKey implements TextMapReader.
@ -166,12 +165,12 @@ func (p *propagator) extractTextMap(reader TextMapReader) (ddtrace.SpanContext,
key := strings.ToLower(k)
switch key {
case p.cfg.TraceHeader:
ctx.traceID, err = strconv.ParseUint(v, 10, 64)
ctx.traceID, err = parseUint64(v)
if err != nil {
return ErrSpanContextCorrupted
}
case p.cfg.ParentHeader:
ctx.spanID, err = strconv.ParseUint(v, 10, 64)
ctx.spanID, err = parseUint64(v)
if err != nil {
return ErrSpanContextCorrupted
}

View file

@ -64,9 +64,7 @@ func Start(opts ...StartOption) {
if internal.Testing {
return // mock tracer active
}
t := internal.GetGlobalTracer()
internal.SetGlobalTracer(newTracer(opts...))
t.Stop()
}
// Stop stops the started tracer. Subsequent calls are valid but become no-op.
@ -302,16 +300,10 @@ func (t *tracer) flushTraces() {
log.Printf("Sending payload: size: %d traces: %d\n", size, count)
}
err := t.config.transport.send(t.payload)
if err != nil && size > payloadMaxLimit {
// we couldn't send the payload and it is getting too big to be
// accepted by the agent, we have to drop it.
t.payload.reset()
if err != nil {
t.pushError(&dataLossError{context: err, count: count})
}
if err == nil {
// send succeeded
t.payload.reset()
}
t.payload.reset()
}
// flushErrors will process log messages that were queued

View file

@ -10,7 +10,9 @@ import (
"time"
)
var tracerVersion = "v1.0"
// TODO(gbbr): find a more effective way to keep this up to date,
// e.g. via `go generate`
var tracerVersion = "v1.5.0"
const (
defaultHostname = "localhost"
@ -57,16 +59,8 @@ func newHTTPTransport(addr string) *httpTransport {
"Datadog-Meta-Tracer-Version": tracerVersion,
"Content-Type": "application/msgpack",
}
host, port, _ := net.SplitHostPort(addr)
if host == "" {
host = defaultHostname
}
if port == "" {
port = defaultPort
}
addr = fmt.Sprintf("%s:%s", host, port)
return &httpTransport{
traceURL: fmt.Sprintf("http://%s/v0.3/traces", addr),
traceURL: fmt.Sprintf("http://%s/v0.3/traces", resolveAddr(addr)),
client: &http.Client{
// We copy the transport to avoid using the default one, as it might be
// augmented with tracing and we don't want these calls to be recorded.
@ -118,3 +112,20 @@ func (t *httpTransport) send(p *payload) error {
}
return nil
}
// resolveAddr resolves the given agent address and fills in any missing host
// and port using the defaults.
func resolveAddr(addr string) string {
host, port, err := net.SplitHostPort(addr)
if err != nil {
// no port in addr
host = addr
}
if host == "" {
host = defaultHostname
}
if port == "" {
port = defaultPort
}
return fmt.Sprintf("%s:%s", host, port)
}

View file

@ -1,5 +1,10 @@
package tracer
import (
"strconv"
"strings"
)
// toFloat64 attempts to convert value into a float64. If it succeeds it returns
// the value and true, otherwise 0 and false.
func toFloat64(value interface{}) (f float64, ok bool) {
@ -30,3 +35,16 @@ func toFloat64(value interface{}) (f float64, ok bool) {
return 0, false
}
}
// parseUint64 parses a uint64 from either an unsigned 64 bit base-10 string
// or a signed 64 bit base-10 string representing an unsigned integer
func parseUint64(str string) (uint64, error) {
if strings.HasPrefix(str, "-") {
id, err := strconv.ParseInt(str, 10, 64)
if err != nil {
return 0, err
}
return uint64(id), nil
}
return strconv.ParseUint(str, 10, 64)
}