diff --git a/Gopkg.lock b/Gopkg.lock index dab6fda7b..70e7c3933 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -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 diff --git a/Gopkg.toml b/Gopkg.toml index b2d951ebe..2bbf8951f 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -267,4 +267,4 @@ [[constraint]] name = "gopkg.in/DataDog/dd-trace-go.v1" - version = "1.0.0" + version = "1.5.0" diff --git a/README.md b/README.md index 64b7d9e61..f83e42837 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/docs/basics.md b/docs/basics.md index 971f4e41c..936b7bd71 100644 --- a/docs/basics.md +++ b/docs/basics.md @@ -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: diff --git a/docs/configuration/acme.md b/docs/configuration/acme.md index 9864625f1..992b6dcb7 100644 --- a/docs/configuration/acme.md +++ b/docs/configuration/acme.md @@ -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 Enter. | YES | +| [MyDNS.jp](https://www.mydns.jp/) | `mydnsjp` | `MYDNSJP_MASTER_ID`, `MYDNSJP_PASSWORD` | YES | | [Namecheap](https://www.namecheap.com) | `namecheap` | `NAMECHEAP_API_USER`, `NAMECHEAP_API_KEY` | YES | | [name.com](https://www.name.com/) | `namedotcom` | `NAMECOM_USERNAME`, `NAMECOM_API_TOKEN`, `NAMECOM_SERVER` | Not tested yet | | [Netcup](https://www.netcup.eu/) | `netcup` | `NETCUP_CUSTOMER_NUMBER`, `NETCUP_API_KEY`, `NETCUP_API_PASSWORD` | Not tested yet | @@ -315,8 +319,11 @@ Here is a list of supported `provider`s, that can automate the DNS verification, | [RFC2136](https://tools.ietf.org/html/rfc2136) | `rfc2136` | `RFC2136_TSIG_KEY`, `RFC2136_TSIG_SECRET`, `RFC2136_TSIG_ALGORITHM`, `RFC2136_NAMESERVER` | Not tested yet | | [Route 53](https://aws.amazon.com/route53/) | `route53` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `[AWS_REGION]`, `[AWS_HOSTED_ZONE_ID]` or a configured user/instance IAM profile. | YES | | [Sakura Cloud](https://cloud.sakura.ad.jp/) | `sakuracloud` | `SAKURACLOUD_ACCESS_TOKEN`, `SAKURACLOUD_ACCESS_TOKEN_SECRET` | Not tested yet | +| [Selectel](https://selectel.ru/en/) | `selectel` | `SELECTEL_API_TOKEN` | YES | | [Stackpath](https://www.stackpath.com/) | `stackpath` | `STACKPATH_CLIENT_ID`, `STACKPATH_CLIENT_SECRET`, `STACKPATH_STACK_ID` | Not tested yet | +| [TransIP](https://www.transip.nl/) | `transip` | `TRANSIP_ACCOUNT_NAME`, `TRANSIP_PRIVATE_KEY_PATH` | YES | | [VegaDNS](https://github.com/shupp/VegaDNS-API) | `vegadns` | `SECRET_VEGADNS_KEY`, `SECRET_VEGADNS_SECRET`, `VEGADNS_URL` | Not tested yet | +| [Vscale](https://vscale.io/) | `vscale` | `VSCALE_API_TOKEN` | YES | | [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` | Not tested yet | #### `resolvers` diff --git a/docs/configuration/api.md b/docs/configuration/api.md index 62e1a9537..7360ea8ec 100644 --- a/docs/configuration/api.md +++ b/docs/configuration/api.md @@ -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`. diff --git a/docs/configuration/backends/docker.md b/docs/configuration/backends/docker.md index 5edb33016..43bbb63b5 100644 --- a/docs/configuration/backends/docker.md +++ b/docs/configuration/backends/docker.md @@ -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.
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.
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 `) 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 `) 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 | diff --git a/docs/configuration/backends/kubernetes.md b/docs/configuration/backends/kubernetes.md index 160b5e16a..24c24c4ac 100644 --- a/docs/configuration/backends/kubernetes.md +++ b/docs/configuration/backends/kubernetes.md @@ -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.
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: ` | Set the protocol Traefik will use to communicate with pods. | +| `ingress.kubernetes.io/protocol: ` | 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. diff --git a/docs/configuration/ping.md b/docs/configuration/ping.md index 8c523f4c0..7ec13d7f3 100644 --- a/docs/configuration/ping.md +++ b/docs/configuration/ping.md @@ -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"] diff --git a/docs/index.md b/docs/index.md index 4f606be86..9fb64ceaa 100644 --- a/docs/index.md +++ b/docs/index.md @@ -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) diff --git a/docs/user-guide/kubernetes.md b/docs/user-guide/kubernetes.md index e8b196760..a9a63d5f9 100644 --- a/docs/user-guide/kubernetes.md +++ b/docs/user-guide/kubernetes.md @@ -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 diff --git a/examples/quickstart/docker-compose.yml b/examples/quickstart/docker-compose.yml index 4d9cfc608..0ea14102e 100644 --- a/examples/quickstart/docker-compose.yml +++ b/examples/quickstart/docker-compose.yml @@ -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" diff --git a/integration/resources/compose/pebble.yml b/integration/resources/compose/pebble.yml index 2828c66ef..66c358f47 100644 --- a/integration/resources/compose/pebble.yml +++ b/integration/resources/compose/pebble.yml @@ -1,5 +1,5 @@ pebble: - image: letsencrypt/pebble:2018-07-27 + image: letsencrypt/pebble:2018-11-02 command: pebble --dnsserver ${DOCKER_HOST_IP}:5053 ports: - 14000:14000 diff --git a/middlewares/auth/auth.go b/middlewares/auth/auth.go index c2373f0e5..c5891199f 100644 --- a/middlewares/auth/auth.go +++ b/middlewares/auth/auth.go @@ -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) } } diff --git a/middlewares/auth/basic_auth_test.go b/middlewares/auth/basic_auth_test.go index 917bc59b7..554f98223 100644 --- a/middlewares/auth/basic_auth_test.go +++ b/middlewares/auth/basic_auth_test.go @@ -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() diff --git a/middlewares/auth/digest_auth_test.go b/middlewares/auth/digest_auth_test.go index b2a6e9840..b2000113d 100644 --- a/middlewares/auth/digest_auth_test.go +++ b/middlewares/auth/digest_auth_test.go @@ -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", }, } diff --git a/middlewares/headers/headers_test.go b/middlewares/headers/headers_test.go index e788f7b67..373efafa6 100644 --- a/middlewares/headers/headers_test.go +++ b/middlewares/headers/headers_test.go @@ -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) + }) + } +} diff --git a/old/provider/docker/docker.go b/old/provider/docker/docker.go index 0997cdb50..84449ef1d 100644 --- a/old/provider/docker/docker.go +++ b/old/provider/docker/docker.go @@ -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(): + } + } } diff --git a/vendor/github.com/fatih/structs/LICENSE b/vendor/github.com/fatih/structs/LICENSE new file mode 100644 index 000000000..34504e4b3 --- /dev/null +++ b/vendor/github.com/fatih/structs/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Fatih Arslan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/fatih/structs/field.go b/vendor/github.com/fatih/structs/field.go new file mode 100644 index 000000000..e69783230 --- /dev/null +++ b/vendor/github.com/fatih/structs/field.go @@ -0,0 +1,141 @@ +package structs + +import ( + "errors" + "fmt" + "reflect" +) + +var ( + errNotExported = errors.New("field is not exported") + errNotSettable = errors.New("field is not settable") +) + +// Field represents a single struct field that encapsulates high level +// functions around the field. +type Field struct { + value reflect.Value + field reflect.StructField + defaultTag string +} + +// Tag returns the value associated with key in the tag string. If there is no +// such key in the tag, Tag returns the empty string. +func (f *Field) Tag(key string) string { + return f.field.Tag.Get(key) +} + +// Value returns the underlying value of the field. It panics if the field +// is not exported. +func (f *Field) Value() interface{} { + return f.value.Interface() +} + +// IsEmbedded returns true if the given field is an anonymous field (embedded) +func (f *Field) IsEmbedded() bool { + return f.field.Anonymous +} + +// IsExported returns true if the given field is exported. +func (f *Field) IsExported() bool { + return f.field.PkgPath == "" +} + +// IsZero returns true if the given field is not initialized (has a zero value). +// It panics if the field is not exported. +func (f *Field) IsZero() bool { + zero := reflect.Zero(f.value.Type()).Interface() + current := f.Value() + + return reflect.DeepEqual(current, zero) +} + +// Name returns the name of the given field +func (f *Field) Name() string { + return f.field.Name +} + +// Kind returns the fields kind, such as "string", "map", "bool", etc .. +func (f *Field) Kind() reflect.Kind { + return f.value.Kind() +} + +// Set sets the field to given value v. It returns an error if the field is not +// settable (not addressable or not exported) or if the given value's type +// doesn't match the fields type. +func (f *Field) Set(val interface{}) error { + // we can't set unexported fields, so be sure this field is exported + if !f.IsExported() { + return errNotExported + } + + // do we get here? not sure... + if !f.value.CanSet() { + return errNotSettable + } + + given := reflect.ValueOf(val) + + if f.value.Kind() != given.Kind() { + return fmt.Errorf("wrong kind. got: %s want: %s", given.Kind(), f.value.Kind()) + } + + f.value.Set(given) + return nil +} + +// Zero sets the field to its zero value. It returns an error if the field is not +// settable (not addressable or not exported). +func (f *Field) Zero() error { + zero := reflect.Zero(f.value.Type()).Interface() + return f.Set(zero) +} + +// Fields returns a slice of Fields. This is particular handy to get the fields +// of a nested struct . A struct tag with the content of "-" ignores the +// checking of that particular field. Example: +// +// // Field is ignored by this package. +// Field *http.Request `structs:"-"` +// +// It panics if field is not exported or if field's kind is not struct +func (f *Field) Fields() []*Field { + return getFields(f.value, f.defaultTag) +} + +// Field returns the field from a nested struct. It panics if the nested struct +// is not exported or if the field was not found. +func (f *Field) Field(name string) *Field { + field, ok := f.FieldOk(name) + if !ok { + panic("field not found") + } + + return field +} + +// FieldOk returns the field from a nested struct. The boolean returns whether +// the field was found (true) or not (false). +func (f *Field) FieldOk(name string) (*Field, bool) { + value := &f.value + // value must be settable so we need to make sure it holds the address of the + // variable and not a copy, so we can pass the pointer to strctVal instead of a + // copy (which is not assigned to any variable, hence not settable). + // see "https://blog.golang.org/laws-of-reflection#TOC_8." + if f.value.Kind() != reflect.Ptr { + a := f.value.Addr() + value = &a + } + v := strctVal(value.Interface()) + t := v.Type() + + field, ok := t.FieldByName(name) + if !ok { + return nil, false + } + + return &Field{ + field: field, + value: v.FieldByName(name), + }, true +} diff --git a/vendor/github.com/fatih/structs/structs.go b/vendor/github.com/fatih/structs/structs.go new file mode 100644 index 000000000..3a8770652 --- /dev/null +++ b/vendor/github.com/fatih/structs/structs.go @@ -0,0 +1,584 @@ +// Package structs contains various utilities functions to work with structs. +package structs + +import ( + "fmt" + + "reflect" +) + +var ( + // DefaultTagName is the default tag name for struct fields which provides + // a more granular to tweak certain structs. Lookup the necessary functions + // for more info. + DefaultTagName = "structs" // struct's field default tag name +) + +// Struct encapsulates a struct type to provide several high level functions +// around the struct. +type Struct struct { + raw interface{} + value reflect.Value + TagName string +} + +// New returns a new *Struct with the struct s. It panics if the s's kind is +// not struct. +func New(s interface{}) *Struct { + return &Struct{ + raw: s, + value: strctVal(s), + TagName: DefaultTagName, + } +} + +// Map converts the given struct to a map[string]interface{}, where the keys +// of the map are the field names and the values of the map the associated +// values of the fields. The default key string is the struct field name but +// can be changed in the struct field's tag value. The "structs" key in the +// struct's field tag value is the key name. Example: +// +// // Field appears in map as key "myName". +// Name string `structs:"myName"` +// +// A tag value with the content of "-" ignores that particular field. Example: +// +// // Field is ignored by this package. +// Field bool `structs:"-"` +// +// A tag value with the content of "string" uses the stringer to get the value. Example: +// +// // The value will be output of Animal's String() func. +// // Map will panic if Animal does not implement String(). +// Field *Animal `structs:"field,string"` +// +// A tag value with the option of "flatten" used in a struct field is to flatten its fields +// in the output map. Example: +// +// // The FieldStruct's fields will be flattened into the output map. +// FieldStruct time.Time `structs:",flatten"` +// +// A tag value with the option of "omitnested" stops iterating further if the type +// is a struct. Example: +// +// // Field is not processed further by this package. +// Field time.Time `structs:"myName,omitnested"` +// Field *http.Request `structs:",omitnested"` +// +// A tag value with the option of "omitempty" ignores that particular field if +// the field value is empty. Example: +// +// // Field appears in map as key "myName", but the field is +// // skipped if empty. +// Field string `structs:"myName,omitempty"` +// +// // Field appears in map as key "Field" (the default), but +// // the field is skipped if empty. +// Field string `structs:",omitempty"` +// +// Note that only exported fields of a struct can be accessed, non exported +// fields will be neglected. +func (s *Struct) Map() map[string]interface{} { + out := make(map[string]interface{}) + s.FillMap(out) + return out +} + +// FillMap is the same as Map. Instead of returning the output, it fills the +// given map. +func (s *Struct) FillMap(out map[string]interface{}) { + if out == nil { + return + } + + fields := s.structFields() + + for _, field := range fields { + name := field.Name + val := s.value.FieldByName(name) + isSubStruct := false + var finalVal interface{} + + tagName, tagOpts := parseTag(field.Tag.Get(s.TagName)) + if tagName != "" { + name = tagName + } + + // if the value is a zero value and the field is marked as omitempty do + // not include + if tagOpts.Has("omitempty") { + zero := reflect.Zero(val.Type()).Interface() + current := val.Interface() + + if reflect.DeepEqual(current, zero) { + continue + } + } + + if !tagOpts.Has("omitnested") { + finalVal = s.nested(val) + + v := reflect.ValueOf(val.Interface()) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + + switch v.Kind() { + case reflect.Map, reflect.Struct: + isSubStruct = true + } + } else { + finalVal = val.Interface() + } + + if tagOpts.Has("string") { + s, ok := val.Interface().(fmt.Stringer) + if ok { + out[name] = s.String() + } + continue + } + + if isSubStruct && (tagOpts.Has("flatten")) { + for k := range finalVal.(map[string]interface{}) { + out[k] = finalVal.(map[string]interface{})[k] + } + } else { + out[name] = finalVal + } + } +} + +// Values converts the given s struct's field values to a []interface{}. A +// struct tag with the content of "-" ignores the that particular field. +// Example: +// +// // Field is ignored by this package. +// Field int `structs:"-"` +// +// A value with the option of "omitnested" stops iterating further if the type +// is a struct. Example: +// +// // Fields is not processed further by this package. +// Field time.Time `structs:",omitnested"` +// Field *http.Request `structs:",omitnested"` +// +// A tag value with the option of "omitempty" ignores that particular field and +// is not added to the values if the field value is empty. Example: +// +// // Field is skipped if empty +// Field string `structs:",omitempty"` +// +// Note that only exported fields of a struct can be accessed, non exported +// fields will be neglected. +func (s *Struct) Values() []interface{} { + fields := s.structFields() + + var t []interface{} + + for _, field := range fields { + val := s.value.FieldByName(field.Name) + + _, tagOpts := parseTag(field.Tag.Get(s.TagName)) + + // if the value is a zero value and the field is marked as omitempty do + // not include + if tagOpts.Has("omitempty") { + zero := reflect.Zero(val.Type()).Interface() + current := val.Interface() + + if reflect.DeepEqual(current, zero) { + continue + } + } + + if tagOpts.Has("string") { + s, ok := val.Interface().(fmt.Stringer) + if ok { + t = append(t, s.String()) + } + continue + } + + if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") { + // look out for embedded structs, and convert them to a + // []interface{} to be added to the final values slice + t = append(t, Values(val.Interface())...) + } else { + t = append(t, val.Interface()) + } + } + + return t +} + +// Fields returns a slice of Fields. A struct tag with the content of "-" +// ignores the checking of that particular field. Example: +// +// // Field is ignored by this package. +// Field bool `structs:"-"` +// +// It panics if s's kind is not struct. +func (s *Struct) Fields() []*Field { + return getFields(s.value, s.TagName) +} + +// Names returns a slice of field names. A struct tag with the content of "-" +// ignores the checking of that particular field. Example: +// +// // Field is ignored by this package. +// Field bool `structs:"-"` +// +// It panics if s's kind is not struct. +func (s *Struct) Names() []string { + fields := getFields(s.value, s.TagName) + + names := make([]string, len(fields)) + + for i, field := range fields { + names[i] = field.Name() + } + + return names +} + +func getFields(v reflect.Value, tagName string) []*Field { + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + + t := v.Type() + + var fields []*Field + + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + + if tag := field.Tag.Get(tagName); tag == "-" { + continue + } + + f := &Field{ + field: field, + value: v.FieldByName(field.Name), + } + + fields = append(fields, f) + + } + + return fields +} + +// Field returns a new Field struct that provides several high level functions +// around a single struct field entity. It panics if the field is not found. +func (s *Struct) Field(name string) *Field { + f, ok := s.FieldOk(name) + if !ok { + panic("field not found") + } + + return f +} + +// FieldOk returns a new Field struct that provides several high level functions +// around a single struct field entity. The boolean returns true if the field +// was found. +func (s *Struct) FieldOk(name string) (*Field, bool) { + t := s.value.Type() + + field, ok := t.FieldByName(name) + if !ok { + return nil, false + } + + return &Field{ + field: field, + value: s.value.FieldByName(name), + defaultTag: s.TagName, + }, true +} + +// IsZero returns true if all fields in a struct is a zero value (not +// initialized) A struct tag with the content of "-" ignores the checking of +// that particular field. Example: +// +// // Field is ignored by this package. +// Field bool `structs:"-"` +// +// A value with the option of "omitnested" stops iterating further if the type +// is a struct. Example: +// +// // Field is not processed further by this package. +// Field time.Time `structs:"myName,omitnested"` +// Field *http.Request `structs:",omitnested"` +// +// Note that only exported fields of a struct can be accessed, non exported +// fields will be neglected. It panics if s's kind is not struct. +func (s *Struct) IsZero() bool { + fields := s.structFields() + + for _, field := range fields { + val := s.value.FieldByName(field.Name) + + _, tagOpts := parseTag(field.Tag.Get(s.TagName)) + + if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") { + ok := IsZero(val.Interface()) + if !ok { + return false + } + + continue + } + + // zero value of the given field, such as "" for string, 0 for int + zero := reflect.Zero(val.Type()).Interface() + + // current value of the given field + current := val.Interface() + + if !reflect.DeepEqual(current, zero) { + return false + } + } + + return true +} + +// HasZero returns true if a field in a struct is not initialized (zero value). +// A struct tag with the content of "-" ignores the checking of that particular +// field. Example: +// +// // Field is ignored by this package. +// Field bool `structs:"-"` +// +// A value with the option of "omitnested" stops iterating further if the type +// is a struct. Example: +// +// // Field is not processed further by this package. +// Field time.Time `structs:"myName,omitnested"` +// Field *http.Request `structs:",omitnested"` +// +// Note that only exported fields of a struct can be accessed, non exported +// fields will be neglected. It panics if s's kind is not struct. +func (s *Struct) HasZero() bool { + fields := s.structFields() + + for _, field := range fields { + val := s.value.FieldByName(field.Name) + + _, tagOpts := parseTag(field.Tag.Get(s.TagName)) + + if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") { + ok := HasZero(val.Interface()) + if ok { + return true + } + + continue + } + + // zero value of the given field, such as "" for string, 0 for int + zero := reflect.Zero(val.Type()).Interface() + + // current value of the given field + current := val.Interface() + + if reflect.DeepEqual(current, zero) { + return true + } + } + + return false +} + +// Name returns the structs's type name within its package. For more info refer +// to Name() function. +func (s *Struct) Name() string { + return s.value.Type().Name() +} + +// structFields returns the exported struct fields for a given s struct. This +// is a convenient helper method to avoid duplicate code in some of the +// functions. +func (s *Struct) structFields() []reflect.StructField { + t := s.value.Type() + + var f []reflect.StructField + + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + // we can't access the value of unexported fields + if field.PkgPath != "" { + continue + } + + // don't check if it's omitted + if tag := field.Tag.Get(s.TagName); tag == "-" { + continue + } + + f = append(f, field) + } + + return f +} + +func strctVal(s interface{}) reflect.Value { + v := reflect.ValueOf(s) + + // if pointer get the underlying element≤ + for v.Kind() == reflect.Ptr { + v = v.Elem() + } + + if v.Kind() != reflect.Struct { + panic("not struct") + } + + return v +} + +// Map converts the given struct to a map[string]interface{}. For more info +// refer to Struct types Map() method. It panics if s's kind is not struct. +func Map(s interface{}) map[string]interface{} { + return New(s).Map() +} + +// FillMap is the same as Map. Instead of returning the output, it fills the +// given map. +func FillMap(s interface{}, out map[string]interface{}) { + New(s).FillMap(out) +} + +// Values converts the given struct to a []interface{}. For more info refer to +// Struct types Values() method. It panics if s's kind is not struct. +func Values(s interface{}) []interface{} { + return New(s).Values() +} + +// Fields returns a slice of *Field. For more info refer to Struct types +// Fields() method. It panics if s's kind is not struct. +func Fields(s interface{}) []*Field { + return New(s).Fields() +} + +// Names returns a slice of field names. For more info refer to Struct types +// Names() method. It panics if s's kind is not struct. +func Names(s interface{}) []string { + return New(s).Names() +} + +// IsZero returns true if all fields is equal to a zero value. For more info +// refer to Struct types IsZero() method. It panics if s's kind is not struct. +func IsZero(s interface{}) bool { + return New(s).IsZero() +} + +// HasZero returns true if any field is equal to a zero value. For more info +// refer to Struct types HasZero() method. It panics if s's kind is not struct. +func HasZero(s interface{}) bool { + return New(s).HasZero() +} + +// IsStruct returns true if the given variable is a struct or a pointer to +// struct. +func IsStruct(s interface{}) bool { + v := reflect.ValueOf(s) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + + // uninitialized zero value of a struct + if v.Kind() == reflect.Invalid { + return false + } + + return v.Kind() == reflect.Struct +} + +// Name returns the structs's type name within its package. It returns an +// empty string for unnamed types. It panics if s's kind is not struct. +func Name(s interface{}) string { + return New(s).Name() +} + +// nested retrieves recursively all types for the given value and returns the +// nested value. +func (s *Struct) nested(val reflect.Value) interface{} { + var finalVal interface{} + + v := reflect.ValueOf(val.Interface()) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + + switch v.Kind() { + case reflect.Struct: + n := New(val.Interface()) + n.TagName = s.TagName + m := n.Map() + + // do not add the converted value if there are no exported fields, ie: + // time.Time + if len(m) == 0 { + finalVal = val.Interface() + } else { + finalVal = m + } + case reflect.Map: + // get the element type of the map + mapElem := val.Type() + switch val.Type().Kind() { + case reflect.Ptr, reflect.Array, reflect.Map, + reflect.Slice, reflect.Chan: + mapElem = val.Type().Elem() + if mapElem.Kind() == reflect.Ptr { + mapElem = mapElem.Elem() + } + } + + // only iterate over struct types, ie: map[string]StructType, + // map[string][]StructType, + if mapElem.Kind() == reflect.Struct || + (mapElem.Kind() == reflect.Slice && + mapElem.Elem().Kind() == reflect.Struct) { + m := make(map[string]interface{}, val.Len()) + for _, k := range val.MapKeys() { + m[k.String()] = s.nested(val.MapIndex(k)) + } + finalVal = m + break + } + + // TODO(arslan): should this be optional? + finalVal = val.Interface() + case reflect.Slice, reflect.Array: + if val.Type().Kind() == reflect.Interface { + finalVal = val.Interface() + break + } + + // TODO(arslan): should this be optional? + // do not iterate of non struct types, just pass the value. Ie: []int, + // []string, co... We only iterate further if it's a struct. + // i.e []foo or []*foo + if val.Type().Elem().Kind() != reflect.Struct && + !(val.Type().Elem().Kind() == reflect.Ptr && + val.Type().Elem().Elem().Kind() == reflect.Struct) { + finalVal = val.Interface() + break + } + + slices := make([]interface{}, val.Len()) + for x := 0; x < val.Len(); x++ { + slices[x] = s.nested(val.Index(x)) + } + finalVal = slices + default: + finalVal = val.Interface() + } + + return finalVal +} diff --git a/vendor/github.com/fatih/structs/tags.go b/vendor/github.com/fatih/structs/tags.go new file mode 100644 index 000000000..136a31eba --- /dev/null +++ b/vendor/github.com/fatih/structs/tags.go @@ -0,0 +1,32 @@ +package structs + +import "strings" + +// tagOptions contains a slice of tag options +type tagOptions []string + +// Has returns true if the given option is available in tagOptions +func (t tagOptions) Has(opt string) bool { + for _, tagOpt := range t { + if tagOpt == opt { + return true + } + } + + return false +} + +// parseTag splits a struct field's tag into its name and a list of options +// which comes after a name. A tag is in the form of: "name,option1,option2". +// The name can be neglectected. +func parseTag(tag string) (string, tagOptions) { + // tag is one of followings: + // "" + // "name" + // "name,opt" + // "name,opt,opt2" + // ",opt" + + res := strings.Split(tag, ",") + return res[0], res[1:] +} diff --git a/vendor/github.com/kolo/xmlrpc/LICENSE b/vendor/github.com/kolo/xmlrpc/LICENSE new file mode 100644 index 000000000..8103dd139 --- /dev/null +++ b/vendor/github.com/kolo/xmlrpc/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2012 Dmitry Maksimov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/kolo/xmlrpc/client.go b/vendor/github.com/kolo/xmlrpc/client.go new file mode 100644 index 000000000..db2eb4f64 --- /dev/null +++ b/vendor/github.com/kolo/xmlrpc/client.go @@ -0,0 +1,169 @@ +package xmlrpc + +import ( + "errors" + "fmt" + "io/ioutil" + "net/http" + "net/http/cookiejar" + "net/rpc" + "net/url" + "sync" +) + +type Client struct { + *rpc.Client +} + +// clientCodec is rpc.ClientCodec interface implementation. +type clientCodec struct { + // url presents url of xmlrpc service + url *url.URL + + // httpClient works with HTTP protocol + httpClient *http.Client + + // cookies stores cookies received on last request + cookies http.CookieJar + + // responses presents map of active requests. It is required to return request id, that + // rpc.Client can mark them as done. + responses map[uint64]*http.Response + mutex sync.Mutex + + response *Response + + // ready presents channel, that is used to link request and it`s response. + ready chan uint64 + + // close notifies codec is closed. + close chan uint64 +} + +func (codec *clientCodec) WriteRequest(request *rpc.Request, args interface{}) (err error) { + httpRequest, err := NewRequest(codec.url.String(), request.ServiceMethod, args) + + if codec.cookies != nil { + for _, cookie := range codec.cookies.Cookies(codec.url) { + httpRequest.AddCookie(cookie) + } + } + + if err != nil { + return err + } + + var httpResponse *http.Response + httpResponse, err = codec.httpClient.Do(httpRequest) + + if err != nil { + return err + } + + if codec.cookies != nil { + codec.cookies.SetCookies(codec.url, httpResponse.Cookies()) + } + + codec.mutex.Lock() + codec.responses[request.Seq] = httpResponse + codec.mutex.Unlock() + + codec.ready <- request.Seq + + return nil +} + +func (codec *clientCodec) ReadResponseHeader(response *rpc.Response) (err error) { + var seq uint64 + + select { + case seq = <-codec.ready: + case <-codec.close: + return errors.New("codec is closed") + } + + codec.mutex.Lock() + httpResponse := codec.responses[seq] + codec.mutex.Unlock() + + if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 300 { + return fmt.Errorf("request error: bad status code - %d", httpResponse.StatusCode) + } + + respData, err := ioutil.ReadAll(httpResponse.Body) + + if err != nil { + return err + } + + httpResponse.Body.Close() + + resp := NewResponse(respData) + + if resp.Failed() { + response.Error = fmt.Sprintf("%v", resp.Err()) + } + + codec.response = resp + + response.Seq = seq + + codec.mutex.Lock() + delete(codec.responses, seq) + codec.mutex.Unlock() + + return nil +} + +func (codec *clientCodec) ReadResponseBody(v interface{}) (err error) { + if v == nil { + return nil + } + + if err = codec.response.Unmarshal(v); err != nil { + return err + } + + return nil +} + +func (codec *clientCodec) Close() error { + transport := codec.httpClient.Transport.(*http.Transport) + transport.CloseIdleConnections() + + close(codec.close) + + return nil +} + +// NewClient returns instance of rpc.Client object, that is used to send request to xmlrpc service. +func NewClient(requrl string, transport http.RoundTripper) (*Client, error) { + if transport == nil { + transport = http.DefaultTransport + } + + httpClient := &http.Client{Transport: transport} + + jar, err := cookiejar.New(nil) + + if err != nil { + return nil, err + } + + u, err := url.Parse(requrl) + + if err != nil { + return nil, err + } + + codec := clientCodec{ + url: u, + httpClient: httpClient, + close: make(chan uint64), + ready: make(chan uint64), + responses: make(map[uint64]*http.Response), + cookies: jar, + } + + return &Client{rpc.NewClientWithCodec(&codec)}, nil +} diff --git a/vendor/github.com/kolo/xmlrpc/decoder.go b/vendor/github.com/kolo/xmlrpc/decoder.go new file mode 100644 index 000000000..b81fff4c0 --- /dev/null +++ b/vendor/github.com/kolo/xmlrpc/decoder.go @@ -0,0 +1,463 @@ +package xmlrpc + +import ( + "bytes" + "encoding/xml" + "errors" + "fmt" + "io" + "reflect" + "strconv" + "strings" + "time" +) + +const ( + iso8601 = "20060102T15:04:05" + iso8601Z = "20060102T15:04:05Z07:00" + iso8601Hyphen = "2006-01-02T15:04:05" + iso8601HyphenZ = "2006-01-02T15:04:05Z07:00" +) + +var ( + // CharsetReader is a function to generate reader which converts a non UTF-8 + // charset into UTF-8. + CharsetReader func(string, io.Reader) (io.Reader, error) + + timeLayouts = []string{iso8601, iso8601Z, iso8601Hyphen, iso8601HyphenZ} + invalidXmlError = errors.New("invalid xml") +) + +type TypeMismatchError string + +func (e TypeMismatchError) Error() string { return string(e) } + +type decoder struct { + *xml.Decoder +} + +func unmarshal(data []byte, v interface{}) (err error) { + dec := &decoder{xml.NewDecoder(bytes.NewBuffer(data))} + + if CharsetReader != nil { + dec.CharsetReader = CharsetReader + } + + var tok xml.Token + for { + if tok, err = dec.Token(); err != nil { + return err + } + + if t, ok := tok.(xml.StartElement); ok { + if t.Name.Local == "value" { + val := reflect.ValueOf(v) + if val.Kind() != reflect.Ptr { + return errors.New("non-pointer value passed to unmarshal") + } + if err = dec.decodeValue(val.Elem()); err != nil { + return err + } + + break + } + } + } + + // read until end of document + err = dec.Skip() + if err != nil && err != io.EOF { + return err + } + + return nil +} + +func (dec *decoder) decodeValue(val reflect.Value) error { + var tok xml.Token + var err error + + if val.Kind() == reflect.Ptr { + if val.IsNil() { + val.Set(reflect.New(val.Type().Elem())) + } + val = val.Elem() + } + + var typeName string + for { + if tok, err = dec.Token(); err != nil { + return err + } + + if t, ok := tok.(xml.EndElement); ok { + if t.Name.Local == "value" { + return nil + } else { + return invalidXmlError + } + } + + if t, ok := tok.(xml.StartElement); ok { + typeName = t.Name.Local + break + } + + // Treat value data without type identifier as string + if t, ok := tok.(xml.CharData); ok { + if value := strings.TrimSpace(string(t)); value != "" { + if err = checkType(val, reflect.String); err != nil { + return err + } + + val.SetString(value) + return nil + } + } + } + + switch typeName { + case "struct": + ismap := false + pmap := val + valType := val.Type() + + if err = checkType(val, reflect.Struct); err != nil { + if checkType(val, reflect.Map) == nil { + if valType.Key().Kind() != reflect.String { + return fmt.Errorf("only maps with string key type can be unmarshalled") + } + ismap = true + } else if checkType(val, reflect.Interface) == nil && val.IsNil() { + var dummy map[string]interface{} + pmap = reflect.New(reflect.TypeOf(dummy)).Elem() + valType = pmap.Type() + ismap = true + } else { + return err + } + } + + var fields map[string]reflect.Value + + if !ismap { + fields = make(map[string]reflect.Value) + + for i := 0; i < valType.NumField(); i++ { + field := valType.Field(i) + fieldVal := val.FieldByName(field.Name) + + if fieldVal.CanSet() { + if fn := field.Tag.Get("xmlrpc"); fn != "" { + fields[fn] = fieldVal + } else { + fields[field.Name] = fieldVal + } + } + } + } else { + // Create initial empty map + pmap.Set(reflect.MakeMap(valType)) + } + + // Process struct members. + StructLoop: + for { + if tok, err = dec.Token(); err != nil { + return err + } + switch t := tok.(type) { + case xml.StartElement: + if t.Name.Local != "member" { + return invalidXmlError + } + + tagName, fieldName, err := dec.readTag() + if err != nil { + return err + } + if tagName != "name" { + return invalidXmlError + } + + var fv reflect.Value + ok := true + + if !ismap { + fv, ok = fields[string(fieldName)] + } else { + fv = reflect.New(valType.Elem()) + } + + if ok { + for { + if tok, err = dec.Token(); err != nil { + return err + } + if t, ok := tok.(xml.StartElement); ok && t.Name.Local == "value" { + if err = dec.decodeValue(fv); err != nil { + return err + } + + // + if err = dec.Skip(); err != nil { + return err + } + + break + } + } + } + + // + if err = dec.Skip(); err != nil { + return err + } + + if ismap { + pmap.SetMapIndex(reflect.ValueOf(string(fieldName)), reflect.Indirect(fv)) + val.Set(pmap) + } + case xml.EndElement: + break StructLoop + } + } + case "array": + pslice := val + if checkType(val, reflect.Interface) == nil && val.IsNil() { + var dummy []interface{} + pslice = reflect.New(reflect.TypeOf(dummy)).Elem() + } else if err = checkType(val, reflect.Slice); err != nil { + return err + } + + ArrayLoop: + for { + if tok, err = dec.Token(); err != nil { + return err + } + + switch t := tok.(type) { + case xml.StartElement: + if t.Name.Local != "data" { + return invalidXmlError + } + + slice := reflect.MakeSlice(pslice.Type(), 0, 0) + + DataLoop: + for { + if tok, err = dec.Token(); err != nil { + return err + } + + switch tt := tok.(type) { + case xml.StartElement: + if tt.Name.Local != "value" { + return invalidXmlError + } + + v := reflect.New(pslice.Type().Elem()) + if err = dec.decodeValue(v); err != nil { + return err + } + + slice = reflect.Append(slice, v.Elem()) + + // + if err = dec.Skip(); err != nil { + return err + } + case xml.EndElement: + pslice.Set(slice) + val.Set(pslice) + break DataLoop + } + } + case xml.EndElement: + break ArrayLoop + } + } + default: + if tok, err = dec.Token(); err != nil { + return err + } + + var data []byte + + switch t := tok.(type) { + case xml.EndElement: + return nil + case xml.CharData: + data = []byte(t.Copy()) + default: + return invalidXmlError + } + + switch typeName { + case "int", "i4", "i8": + if checkType(val, reflect.Interface) == nil && val.IsNil() { + i, err := strconv.ParseInt(string(data), 10, 64) + if err != nil { + return err + } + + pi := reflect.New(reflect.TypeOf(i)).Elem() + pi.SetInt(i) + val.Set(pi) + } else if err = checkType(val, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64); err != nil { + return err + } else { + i, err := strconv.ParseInt(string(data), 10, val.Type().Bits()) + if err != nil { + return err + } + + val.SetInt(i) + } + case "string", "base64": + str := string(data) + if checkType(val, reflect.Interface) == nil && val.IsNil() { + pstr := reflect.New(reflect.TypeOf(str)).Elem() + pstr.SetString(str) + val.Set(pstr) + } else if err = checkType(val, reflect.String); err != nil { + return err + } else { + val.SetString(str) + } + case "dateTime.iso8601": + var t time.Time + var err error + + for _, layout := range timeLayouts { + t, err = time.Parse(layout, string(data)) + if err == nil { + break + } + } + if err != nil { + return err + } + + if checkType(val, reflect.Interface) == nil && val.IsNil() { + ptime := reflect.New(reflect.TypeOf(t)).Elem() + ptime.Set(reflect.ValueOf(t)) + val.Set(ptime) + } else if _, ok := val.Interface().(time.Time); !ok { + return TypeMismatchError(fmt.Sprintf("error: type mismatch error - can't decode %v to time", val.Kind())) + } else { + val.Set(reflect.ValueOf(t)) + } + case "boolean": + v, err := strconv.ParseBool(string(data)) + if err != nil { + return err + } + + if checkType(val, reflect.Interface) == nil && val.IsNil() { + pv := reflect.New(reflect.TypeOf(v)).Elem() + pv.SetBool(v) + val.Set(pv) + } else if err = checkType(val, reflect.Bool); err != nil { + return err + } else { + val.SetBool(v) + } + case "double": + if checkType(val, reflect.Interface) == nil && val.IsNil() { + i, err := strconv.ParseFloat(string(data), 64) + if err != nil { + return err + } + + pdouble := reflect.New(reflect.TypeOf(i)).Elem() + pdouble.SetFloat(i) + val.Set(pdouble) + } else if err = checkType(val, reflect.Float32, reflect.Float64); err != nil { + return err + } else { + i, err := strconv.ParseFloat(string(data), val.Type().Bits()) + if err != nil { + return err + } + + val.SetFloat(i) + } + default: + return errors.New("unsupported type") + } + + // + if err = dec.Skip(); err != nil { + return err + } + } + + return nil +} + +func (dec *decoder) readTag() (string, []byte, error) { + var tok xml.Token + var err error + + var name string + for { + if tok, err = dec.Token(); err != nil { + return "", nil, err + } + + if t, ok := tok.(xml.StartElement); ok { + name = t.Name.Local + break + } + } + + value, err := dec.readCharData() + if err != nil { + return "", nil, err + } + + return name, value, dec.Skip() +} + +func (dec *decoder) readCharData() ([]byte, error) { + var tok xml.Token + var err error + + if tok, err = dec.Token(); err != nil { + return nil, err + } + + if t, ok := tok.(xml.CharData); ok { + return []byte(t.Copy()), nil + } else { + return nil, invalidXmlError + } +} + +func checkType(val reflect.Value, kinds ...reflect.Kind) error { + if len(kinds) == 0 { + return nil + } + + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + + match := false + + for _, kind := range kinds { + if val.Kind() == kind { + match = true + break + } + } + + if !match { + return TypeMismatchError(fmt.Sprintf("error: type mismatch - can't unmarshal %v to %v", + val.Kind(), kinds[0])) + } + + return nil +} diff --git a/vendor/github.com/kolo/xmlrpc/encoder.go b/vendor/github.com/kolo/xmlrpc/encoder.go new file mode 100644 index 000000000..bb1285ff7 --- /dev/null +++ b/vendor/github.com/kolo/xmlrpc/encoder.go @@ -0,0 +1,164 @@ +package xmlrpc + +import ( + "bytes" + "encoding/xml" + "fmt" + "reflect" + "strconv" + "time" +) + +type encodeFunc func(reflect.Value) ([]byte, error) + +func marshal(v interface{}) ([]byte, error) { + if v == nil { + return []byte{}, nil + } + + val := reflect.ValueOf(v) + return encodeValue(val) +} + +func encodeValue(val reflect.Value) ([]byte, error) { + var b []byte + var err error + + if val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface { + if val.IsNil() { + return []byte(""), nil + } + + val = val.Elem() + } + + switch val.Kind() { + case reflect.Struct: + switch val.Interface().(type) { + case time.Time: + t := val.Interface().(time.Time) + b = []byte(fmt.Sprintf("%s", t.Format(iso8601))) + default: + b, err = encodeStruct(val) + } + case reflect.Map: + b, err = encodeMap(val) + case reflect.Slice: + b, err = encodeSlice(val) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + b = []byte(fmt.Sprintf("%s", strconv.FormatInt(val.Int(), 10))) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + b = []byte(fmt.Sprintf("%s", strconv.FormatUint(val.Uint(), 10))) + case reflect.Float32, reflect.Float64: + b = []byte(fmt.Sprintf("%s", + strconv.FormatFloat(val.Float(), 'g', -1, val.Type().Bits()))) + case reflect.Bool: + if val.Bool() { + b = []byte("1") + } else { + b = []byte("0") + } + case reflect.String: + var buf bytes.Buffer + + xml.Escape(&buf, []byte(val.String())) + + if _, ok := val.Interface().(Base64); ok { + b = []byte(fmt.Sprintf("%s", buf.String())) + } else { + b = []byte(fmt.Sprintf("%s", buf.String())) + } + default: + return nil, fmt.Errorf("xmlrpc encode error: unsupported type") + } + + if err != nil { + return nil, err + } + + return []byte(fmt.Sprintf("%s", string(b))), nil +} + +func encodeStruct(val reflect.Value) ([]byte, error) { + var b bytes.Buffer + + b.WriteString("") + + t := val.Type() + for i := 0; i < t.NumField(); i++ { + b.WriteString("") + f := t.Field(i) + + name := f.Tag.Get("xmlrpc") + if name == "" { + name = f.Name + } + b.WriteString(fmt.Sprintf("%s", name)) + + p, err := encodeValue(val.FieldByName(f.Name)) + if err != nil { + return nil, err + } + b.Write(p) + + b.WriteString("") + } + + b.WriteString("") + + return b.Bytes(), nil +} + +func encodeMap(val reflect.Value) ([]byte, error) { + var t = val.Type() + + if t.Key().Kind() != reflect.String { + return nil, fmt.Errorf("xmlrpc encode error: only maps with string keys are supported") + } + + var b bytes.Buffer + + b.WriteString("") + + keys := val.MapKeys() + + for i := 0; i < val.Len(); i++ { + key := keys[i] + kval := val.MapIndex(key) + + b.WriteString("") + b.WriteString(fmt.Sprintf("%s", key.String())) + + p, err := encodeValue(kval) + + if err != nil { + return nil, err + } + + b.Write(p) + b.WriteString("") + } + + b.WriteString("") + + return b.Bytes(), nil +} + +func encodeSlice(val reflect.Value) ([]byte, error) { + var b bytes.Buffer + + b.WriteString("") + + for i := 0; i < val.Len(); i++ { + p, err := encodeValue(val.Index(i)) + if err != nil { + return nil, err + } + + b.Write(p) + } + + b.WriteString("") + + return b.Bytes(), nil +} diff --git a/vendor/github.com/kolo/xmlrpc/request.go b/vendor/github.com/kolo/xmlrpc/request.go new file mode 100644 index 000000000..acb8251b2 --- /dev/null +++ b/vendor/github.com/kolo/xmlrpc/request.go @@ -0,0 +1,57 @@ +package xmlrpc + +import ( + "bytes" + "fmt" + "net/http" +) + +func NewRequest(url string, method string, args interface{}) (*http.Request, error) { + var t []interface{} + var ok bool + if t, ok = args.([]interface{}); !ok { + if args != nil { + t = []interface{}{args} + } + } + + body, err := EncodeMethodCall(method, t...) + if err != nil { + return nil, err + } + + request, err := http.NewRequest("POST", url, bytes.NewReader(body)) + if err != nil { + return nil, err + } + + request.Header.Set("Content-Type", "text/xml") + request.Header.Set("Content-Length", fmt.Sprintf("%d", len(body))) + + return request, nil +} + +func EncodeMethodCall(method string, args ...interface{}) ([]byte, error) { + var b bytes.Buffer + b.WriteString(``) + b.WriteString(fmt.Sprintf("%s", method)) + + if args != nil { + b.WriteString("") + + for _, arg := range args { + p, err := marshal(arg) + if err != nil { + return nil, err + } + + b.WriteString(fmt.Sprintf("%s", string(p))) + } + + b.WriteString("") + } + + b.WriteString("") + + return b.Bytes(), nil +} diff --git a/vendor/github.com/kolo/xmlrpc/response.go b/vendor/github.com/kolo/xmlrpc/response.go new file mode 100644 index 000000000..6742a1c74 --- /dev/null +++ b/vendor/github.com/kolo/xmlrpc/response.go @@ -0,0 +1,52 @@ +package xmlrpc + +import ( + "regexp" +) + +var ( + faultRx = regexp.MustCompile(`(\s|\S)+`) +) + +type failedResponse struct { + Code int `xmlrpc:"faultCode"` + Error string `xmlrpc:"faultString"` +} + +func (r *failedResponse) err() error { + return &xmlrpcError{ + code: r.Code, + err: r.Error, + } +} + +type Response struct { + data []byte +} + +func NewResponse(data []byte) *Response { + return &Response{ + data: data, + } +} + +func (r *Response) Failed() bool { + return faultRx.Match(r.data) +} + +func (r *Response) Err() error { + failedResp := new(failedResponse) + if err := unmarshal(r.data, failedResp); err != nil { + return err + } + + return failedResp.err() +} + +func (r *Response) Unmarshal(v interface{}) error { + if err := unmarshal(r.data, v); err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/kolo/xmlrpc/xmlrpc.go b/vendor/github.com/kolo/xmlrpc/xmlrpc.go new file mode 100644 index 000000000..8766403af --- /dev/null +++ b/vendor/github.com/kolo/xmlrpc/xmlrpc.go @@ -0,0 +1,19 @@ +package xmlrpc + +import ( + "fmt" +) + +// xmlrpcError represents errors returned on xmlrpc request. +type xmlrpcError struct { + code int + err string +} + +// Error() method implements Error interface +func (e *xmlrpcError) Error() string { + return fmt.Sprintf("error: \"%s\" code: %d", e.err, e.code) +} + +// Base64 represents value in base64 encoding +type Base64 string diff --git a/vendor/github.com/mitchellh/mapstructure/decode_hooks.go b/vendor/github.com/mitchellh/mapstructure/decode_hooks.go index afcfd5eed..1f0abc65a 100644 --- a/vendor/github.com/mitchellh/mapstructure/decode_hooks.go +++ b/vendor/github.com/mitchellh/mapstructure/decode_hooks.go @@ -2,6 +2,8 @@ package mapstructure import ( "errors" + "fmt" + "net" "reflect" "strconv" "strings" @@ -115,6 +117,69 @@ func StringToTimeDurationHookFunc() DecodeHookFunc { } } +// StringToIPHookFunc returns a DecodeHookFunc that converts +// strings to net.IP +func StringToIPHookFunc() DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(net.IP{}) { + return data, nil + } + + // Convert it by parsing + ip := net.ParseIP(data.(string)) + if ip == nil { + return net.IP{}, fmt.Errorf("failed parsing ip %v", data) + } + + return ip, nil + } +} + +// StringToIPNetHookFunc returns a DecodeHookFunc that converts +// strings to net.IPNet +func StringToIPNetHookFunc() DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(net.IPNet{}) { + return data, nil + } + + // Convert it by parsing + _, net, err := net.ParseCIDR(data.(string)) + return net, err + } +} + +// StringToTimeHookFunc returns a DecodeHookFunc that converts +// strings to time.Time. +func StringToTimeHookFunc(layout string) DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(time.Time{}) { + return data, nil + } + + // Convert it by parsing + return time.Parse(layout, data.(string)) + } +} + // WeaklyTypedHook is a DecodeHookFunc which adds support for weak typing to // the decoder. // diff --git a/vendor/github.com/mitchellh/mapstructure/mapstructure.go b/vendor/github.com/mitchellh/mapstructure/mapstructure.go index 39ec1e943..256ee63fb 100644 --- a/vendor/github.com/mitchellh/mapstructure/mapstructure.go +++ b/vendor/github.com/mitchellh/mapstructure/mapstructure.go @@ -114,12 +114,12 @@ type Metadata struct { Unused []string } -// Decode takes a map and uses reflection to convert it into the -// given Go native structure. val must be a pointer to a struct. -func Decode(m interface{}, rawVal interface{}) error { +// Decode takes an input structure and uses reflection to translate it to +// the output structure. output must be a pointer to a map or struct. +func Decode(input interface{}, output interface{}) error { config := &DecoderConfig{ Metadata: nil, - Result: rawVal, + Result: output, } decoder, err := NewDecoder(config) @@ -127,7 +127,7 @@ func Decode(m interface{}, rawVal interface{}) error { return err } - return decoder.Decode(m) + return decoder.Decode(input) } // WeakDecode is the same as Decode but is shorthand to enable @@ -147,6 +147,40 @@ func WeakDecode(input, output interface{}) error { return decoder.Decode(input) } +// DecodeMetadata is the same as Decode, but is shorthand to +// enable metadata collection. See DecoderConfig for more info. +func DecodeMetadata(input interface{}, output interface{}, metadata *Metadata) error { + config := &DecoderConfig{ + Metadata: metadata, + Result: output, + } + + decoder, err := NewDecoder(config) + if err != nil { + return err + } + + return decoder.Decode(input) +} + +// WeakDecodeMetadata is the same as Decode, but is shorthand to +// enable both WeaklyTypedInput and metadata collection. See +// DecoderConfig for more info. +func WeakDecodeMetadata(input interface{}, output interface{}, metadata *Metadata) error { + config := &DecoderConfig{ + Metadata: metadata, + Result: output, + WeaklyTypedInput: true, + } + + decoder, err := NewDecoder(config) + if err != nil { + return err + } + + return decoder.Decode(input) +} + // NewDecoder returns a new decoder for the given configuration. Once // a decoder has been returned, the same configuration must not be used // again. @@ -184,70 +218,91 @@ func NewDecoder(config *DecoderConfig) (*Decoder, error) { // Decode decodes the given raw interface to the target pointer specified // by the configuration. -func (d *Decoder) Decode(raw interface{}) error { - return d.decode("", raw, reflect.ValueOf(d.config.Result).Elem()) +func (d *Decoder) Decode(input interface{}) error { + return d.decode("", input, reflect.ValueOf(d.config.Result).Elem()) } // Decodes an unknown data type into a specific reflection value. -func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error { - if data == nil { - // If the data is nil, then we don't set anything. +func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) error { + var inputVal reflect.Value + if input != nil { + inputVal = reflect.ValueOf(input) + + // We need to check here if input is a typed nil. Typed nils won't + // match the "input == nil" below so we check that here. + if inputVal.Kind() == reflect.Ptr && inputVal.IsNil() { + input = nil + } + } + + if input == nil { + // If the data is nil, then we don't set anything, unless ZeroFields is set + // to true. + if d.config.ZeroFields { + outVal.Set(reflect.Zero(outVal.Type())) + + if d.config.Metadata != nil && name != "" { + d.config.Metadata.Keys = append(d.config.Metadata.Keys, name) + } + } return nil } - dataVal := reflect.ValueOf(data) - if !dataVal.IsValid() { - // If the data value is invalid, then we just set the value + if !inputVal.IsValid() { + // If the input value is invalid, then we just set the value // to be the zero value. - val.Set(reflect.Zero(val.Type())) + outVal.Set(reflect.Zero(outVal.Type())) + if d.config.Metadata != nil && name != "" { + d.config.Metadata.Keys = append(d.config.Metadata.Keys, name) + } return nil } if d.config.DecodeHook != nil { - // We have a DecodeHook, so let's pre-process the data. + // We have a DecodeHook, so let's pre-process the input. var err error - data, err = DecodeHookExec( + input, err = DecodeHookExec( d.config.DecodeHook, - dataVal.Type(), val.Type(), data) + inputVal.Type(), outVal.Type(), input) if err != nil { return fmt.Errorf("error decoding '%s': %s", name, err) } } var err error - dataKind := getKind(val) - switch dataKind { + outputKind := getKind(outVal) + switch outputKind { case reflect.Bool: - err = d.decodeBool(name, data, val) + err = d.decodeBool(name, input, outVal) case reflect.Interface: - err = d.decodeBasic(name, data, val) + err = d.decodeBasic(name, input, outVal) case reflect.String: - err = d.decodeString(name, data, val) + err = d.decodeString(name, input, outVal) case reflect.Int: - err = d.decodeInt(name, data, val) + err = d.decodeInt(name, input, outVal) case reflect.Uint: - err = d.decodeUint(name, data, val) + err = d.decodeUint(name, input, outVal) case reflect.Float32: - err = d.decodeFloat(name, data, val) + err = d.decodeFloat(name, input, outVal) case reflect.Struct: - err = d.decodeStruct(name, data, val) + err = d.decodeStruct(name, input, outVal) case reflect.Map: - err = d.decodeMap(name, data, val) + err = d.decodeMap(name, input, outVal) case reflect.Ptr: - err = d.decodePtr(name, data, val) + err = d.decodePtr(name, input, outVal) case reflect.Slice: - err = d.decodeSlice(name, data, val) + err = d.decodeSlice(name, input, outVal) case reflect.Array: - err = d.decodeArray(name, data, val) + err = d.decodeArray(name, input, outVal) case reflect.Func: - err = d.decodeFunc(name, data, val) + err = d.decodeFunc(name, input, outVal) default: // If we reached this point then we weren't able to decode it - return fmt.Errorf("%s: unsupported type: %s", name, dataKind) + return fmt.Errorf("%s: unsupported type: %s", name, outputKind) } // If we reached here, then we successfully decoded SOMETHING, so - // mark the key as used if we're tracking metadata. + // mark the key as used if we're tracking metainput. if d.config.Metadata != nil && name != "" { d.config.Metadata.Keys = append(d.config.Metadata.Keys, name) } @@ -258,7 +313,19 @@ func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error // This decodes a basic type (bool, int, string, etc.) and sets the // value to "data" of that type. func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value) error { + if val.IsValid() && val.Elem().IsValid() { + return d.decode(name, data, val.Elem()) + } + dataVal := reflect.ValueOf(data) + + // If the input data is a pointer, and the assigned type is the dereference + // of that exact pointer, then indirect it so that we can assign it. + // Example: *string to string + if dataVal.Kind() == reflect.Ptr && dataVal.Type().Elem() == val.Type() { + dataVal = reflect.Indirect(dataVal) + } + if !dataVal.IsValid() { dataVal = reflect.Zero(val.Type()) } @@ -275,7 +342,7 @@ func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value) } func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value) error { - dataVal := reflect.ValueOf(data) + dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) converted := true @@ -327,7 +394,7 @@ func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value) } func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) error { - dataVal := reflect.ValueOf(data) + dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) dataType := dataVal.Type() @@ -369,7 +436,7 @@ func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) er } func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) error { - dataVal := reflect.ValueOf(data) + dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) switch { @@ -412,7 +479,7 @@ func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) e } func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) error { - dataVal := reflect.ValueOf(data) + dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) switch { @@ -443,7 +510,7 @@ func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) e } func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value) error { - dataVal := reflect.ValueOf(data) + dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) dataType := dataVal.Type() @@ -499,38 +566,68 @@ func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) er valMap = reflect.MakeMap(mapType) } - // Check input type + // Check input type and based on the input type jump to the proper func dataVal := reflect.Indirect(reflect.ValueOf(data)) - if dataVal.Kind() != reflect.Map { - // In weak mode, we accept a slice of maps as an input... + switch dataVal.Kind() { + case reflect.Map: + return d.decodeMapFromMap(name, dataVal, val, valMap) + + case reflect.Struct: + return d.decodeMapFromStruct(name, dataVal, val, valMap) + + case reflect.Array, reflect.Slice: if d.config.WeaklyTypedInput { - switch dataVal.Kind() { - case reflect.Array, reflect.Slice: - // Special case for BC reasons (covered by tests) - if dataVal.Len() == 0 { - val.Set(valMap) - return nil - } - - for i := 0; i < dataVal.Len(); i++ { - err := d.decode( - fmt.Sprintf("%s[%d]", name, i), - dataVal.Index(i).Interface(), val) - if err != nil { - return err - } - } - - return nil - } + return d.decodeMapFromSlice(name, dataVal, val, valMap) } + fallthrough + + default: return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind()) } +} + +func (d *Decoder) decodeMapFromSlice(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error { + // Special case for BC reasons (covered by tests) + if dataVal.Len() == 0 { + val.Set(valMap) + return nil + } + + for i := 0; i < dataVal.Len(); i++ { + err := d.decode( + fmt.Sprintf("%s[%d]", name, i), + dataVal.Index(i).Interface(), val) + if err != nil { + return err + } + } + + return nil +} + +func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error { + valType := val.Type() + valKeyType := valType.Key() + valElemType := valType.Elem() // Accumulate errors errors := make([]string, 0) + // If the input data is empty, then we just match what the input data is. + if dataVal.Len() == 0 { + if dataVal.IsNil() { + if !val.IsNil() { + val.Set(dataVal) + } + } else { + // Set to empty allocated value + val.Set(valMap) + } + + return nil + } + for _, k := range dataVal.MapKeys() { fieldName := fmt.Sprintf("%s[%s]", name, k) @@ -563,22 +660,128 @@ func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) er return nil } +func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error { + typ := dataVal.Type() + for i := 0; i < typ.NumField(); i++ { + // Get the StructField first since this is a cheap operation. If the + // field is unexported, then ignore it. + f := typ.Field(i) + if f.PkgPath != "" { + continue + } + + // Next get the actual value of this field and verify it is assignable + // to the map value. + v := dataVal.Field(i) + if !v.Type().AssignableTo(valMap.Type().Elem()) { + return fmt.Errorf("cannot assign type '%s' to map value field of type '%s'", v.Type(), valMap.Type().Elem()) + } + + tagValue := f.Tag.Get(d.config.TagName) + tagParts := strings.Split(tagValue, ",") + + // Determine the name of the key in the map + keyName := f.Name + if tagParts[0] != "" { + if tagParts[0] == "-" { + continue + } + keyName = tagParts[0] + } + + // If "squash" is specified in the tag, we squash the field down. + squash := false + for _, tag := range tagParts[1:] { + if tag == "squash" { + squash = true + break + } + } + if squash && v.Kind() != reflect.Struct { + return fmt.Errorf("cannot squash non-struct type '%s'", v.Type()) + } + + switch v.Kind() { + // this is an embedded struct, so handle it differently + case reflect.Struct: + x := reflect.New(v.Type()) + x.Elem().Set(v) + + vType := valMap.Type() + vKeyType := vType.Key() + vElemType := vType.Elem() + mType := reflect.MapOf(vKeyType, vElemType) + vMap := reflect.MakeMap(mType) + + err := d.decode(keyName, x.Interface(), vMap) + if err != nil { + return err + } + + if squash { + for _, k := range vMap.MapKeys() { + valMap.SetMapIndex(k, vMap.MapIndex(k)) + } + } else { + valMap.SetMapIndex(reflect.ValueOf(keyName), vMap) + } + + default: + valMap.SetMapIndex(reflect.ValueOf(keyName), v) + } + } + + if val.CanAddr() { + val.Set(valMap) + } + + return nil +} + func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) error { + // If the input data is nil, then we want to just set the output + // pointer to be nil as well. + isNil := data == nil + if !isNil { + switch v := reflect.Indirect(reflect.ValueOf(data)); v.Kind() { + case reflect.Chan, + reflect.Func, + reflect.Interface, + reflect.Map, + reflect.Ptr, + reflect.Slice: + isNil = v.IsNil() + } + } + if isNil { + if !val.IsNil() && val.CanSet() { + nilValue := reflect.New(val.Type()).Elem() + val.Set(nilValue) + } + + return nil + } + // Create an element of the concrete (non pointer) type and decode // into that. Then set the value of the pointer to this type. valType := val.Type() valElemType := valType.Elem() + if val.CanSet() { + realVal := val + if realVal.IsNil() || d.config.ZeroFields { + realVal = reflect.New(valElemType) + } - realVal := val - if realVal.IsNil() || d.config.ZeroFields { - realVal = reflect.New(valElemType) + if err := d.decode(name, data, reflect.Indirect(realVal)); err != nil { + return err + } + + val.Set(realVal) + } else { + if err := d.decode(name, data, reflect.Indirect(val)); err != nil { + return err + } } - - if err := d.decode(name, data, reflect.Indirect(realVal)); err != nil { - return err - } - - val.Set(realVal) return nil } @@ -604,30 +807,44 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) valSlice := val if valSlice.IsNil() || d.config.ZeroFields { + if d.config.WeaklyTypedInput { + switch { + // Slice and array we use the normal logic + case dataValKind == reflect.Slice, dataValKind == reflect.Array: + break + + // Empty maps turn into empty slices + case dataValKind == reflect.Map: + if dataVal.Len() == 0 { + val.Set(reflect.MakeSlice(sliceType, 0, 0)) + return nil + } + // Create slice of maps of other sizes + return d.decodeSlice(name, []interface{}{data}, val) + + case dataValKind == reflect.String && valElemType.Kind() == reflect.Uint8: + return d.decodeSlice(name, []byte(dataVal.String()), val) + + // All other types we try to convert to the slice type + // and "lift" it into it. i.e. a string becomes a string slice. + default: + // Just re-try this function with data as a slice. + return d.decodeSlice(name, []interface{}{data}, val) + } + } + // Check input type if dataValKind != reflect.Array && dataValKind != reflect.Slice { - if d.config.WeaklyTypedInput { - switch { - // Empty maps turn into empty slices - case dataValKind == reflect.Map: - if dataVal.Len() == 0 { - val.Set(reflect.MakeSlice(sliceType, 0, 0)) - return nil - } - - // All other types we try to convert to the slice type - // and "lift" it into it. i.e. a string becomes a string slice. - default: - // Just re-try this function with data as a slice. - return d.decodeSlice(name, []interface{}{data}, val) - } - } - return fmt.Errorf( "'%s': source data must be an array or slice, got %s", name, dataValKind) } + // If the input value is empty, then don't allocate since non-nil != nil + if dataVal.Len() == 0 { + return nil + } + // Make a new slice to hold our result, same size as the original data. valSlice = reflect.MakeSlice(sliceType, dataVal.Len(), dataVal.Len()) } @@ -737,10 +954,29 @@ func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) } dataValKind := dataVal.Kind() - if dataValKind != reflect.Map { - return fmt.Errorf("'%s' expected a map, got '%s'", name, dataValKind) - } + switch dataValKind { + case reflect.Map: + return d.decodeStructFromMap(name, dataVal, val) + case reflect.Struct: + // Not the most efficient way to do this but we can optimize later if + // we want to. To convert from struct to struct we go to map first + // as an intermediary. + m := make(map[string]interface{}) + mval := reflect.Indirect(reflect.ValueOf(&m)) + if err := d.decodeMapFromStruct(name, dataVal, mval, mval); err != nil { + return err + } + + result := d.decodeStructFromMap(name, mval, val) + return result + + default: + return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind()) + } +} + +func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) error { dataValType := dataVal.Type() if kind := dataValType.Key().Kind(); kind != reflect.String && kind != reflect.Interface { return fmt.Errorf( diff --git a/vendor/github.com/smueller18/goinwx/LICENSE b/vendor/github.com/smueller18/goinwx/LICENSE new file mode 100644 index 000000000..9df6656cd --- /dev/null +++ b/vendor/github.com/smueller18/goinwx/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Andrew + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/smueller18/goinwx/account.go b/vendor/github.com/smueller18/goinwx/account.go new file mode 100644 index 000000000..14b2073ae --- /dev/null +++ b/vendor/github.com/smueller18/goinwx/account.go @@ -0,0 +1,54 @@ +package goinwx + +const ( + methodAccountLogin = "account.login" + methodAccountLogout = "account.logout" + methodAccountLock = "account.lock" + methodAccountUnlock = "account.unlock" +) + +type AccountService interface { + Login() error + Logout() error + Lock() error + Unlock(tan string) error +} + +type AccountServiceOp struct { + client *Client +} + +var _ AccountService = &AccountServiceOp{} + +func (s *AccountServiceOp) Login() error { + req := s.client.NewRequest(methodAccountLogin, map[string]interface{}{ + "user": s.client.Username, + "pass": s.client.Password, + }) + + _, err := s.client.Do(*req) + return err +} + +func (s *AccountServiceOp) Logout() error { + req := s.client.NewRequest(methodAccountLogout, nil) + + _, err := s.client.Do(*req) + return err +} + +func (s *AccountServiceOp) Lock() error { + req := s.client.NewRequest(methodAccountLock, nil) + + _, err := s.client.Do(*req) + return err +} + +func (s *AccountServiceOp) Unlock(tan string) error { + req := s.client.NewRequest(methodAccountUnlock, map[string]interface{}{ + "tan": tan, + }) + + _, err := s.client.Do(*req) + return err +} diff --git a/vendor/github.com/smueller18/goinwx/contact.go b/vendor/github.com/smueller18/goinwx/contact.go new file mode 100644 index 000000000..0ae36f5c2 --- /dev/null +++ b/vendor/github.com/smueller18/goinwx/contact.go @@ -0,0 +1,150 @@ +package goinwx + +import ( + "github.com/fatih/structs" + "github.com/mitchellh/mapstructure" +) + +const ( + methodContactInfo = "contact.info" + methodContactList = "contact.list" + methodContactCreate = "contact.create" + methodContactDelete = "contact.delete" + methodContactUpdate = "contact.update" +) + +type ContactService interface { + Create(*ContactCreateRequest) (int, error) + Update(*ContactUpdateRequest) error + Delete(int) error + Info(int) (*ContactInfoResponse, error) + List(string) (*ContactListResponse, error) +} + +type ContactServiceOp struct { + client *Client +} + +var _ ContactService = &ContactServiceOp{} + +type ContactCreateRequest struct { + Type string `structs:"type"` + Name string `structs:"name"` + Org string `structs:"org,omitempty"` + Street string `structs:"street"` + City string `structs:"city"` + PostalCode string `structs:"pc"` + StateProvince string `structs:"sp,omitempty"` + CountryCode string `structs:"cc"` + Voice string `structs:"voice"` + Fax string `structs:"fax,omitempty"` + Email string `structs:"email"` + Remarks string `structs:"remarks,omitempty"` + Protection bool `structs:"protection,omitempty"` + Testing bool `structs:"testing,omitempty"` +} + +type ContactUpdateRequest struct { + Id int `structs:"id"` + Name string `structs:"name,omitempty"` + Org string `structs:"org,omitempty"` + Street string `structs:"street,omitempty"` + City string `structs:"city,omitempty"` + PostalCode string `structs:"pc,omitempty"` + StateProvince string `structs:"sp,omitempty"` + CountryCode string `structs:"cc,omitempty"` + Voice string `structs:"voice,omitempty"` + Fax string `structs:"fax,omitempty"` + Email string `structs:"email,omitempty"` + Remarks string `structs:"remarks,omitempty"` + Protection bool `structs:"protection,omitempty"` + Testing bool `structs:"testing,omitempty"` +} + +type ContactInfoResponse struct { + Contact Contact `mapstructure:"contact"` +} + +type ContactListResponse struct { + Count int + Contacts []Contact `mapstructure:"contact"` +} + +func (s *ContactServiceOp) Create(request *ContactCreateRequest) (int, error) { + req := s.client.NewRequest(methodContactCreate, structs.Map(request)) + + resp, err := s.client.Do(*req) + if err != nil { + return 0, err + } + + var result map[string]int + err = mapstructure.Decode(*resp, &result) + if err != nil { + return 0, err + } + + return result["id"], nil +} + +func (s *ContactServiceOp) Delete(roId int) error { + req := s.client.NewRequest(methodContactDelete, map[string]interface{}{ + "id": roId, + }) + + _, err := s.client.Do(*req) + return err +} + +func (s *ContactServiceOp) Update(request *ContactUpdateRequest) error { + req := s.client.NewRequest(methodContactUpdate, structs.Map(request)) + + _, err := s.client.Do(*req) + return err +} + +func (s *ContactServiceOp) Info(contactId int) (*ContactInfoResponse, error) { + var requestMap = make(map[string]interface{}) + requestMap["wide"] = 1 + + if contactId != 0 { + requestMap["id"] = contactId + } + + req := s.client.NewRequest(methodContactInfo, requestMap) + + resp, err := s.client.Do(*req) + if err != nil { + return nil, err + } + + var result ContactInfoResponse + err = mapstructure.Decode(*resp, &result) + if err != nil { + return nil, err + } + + return &result, nil +} + +func (s *ContactServiceOp) List(search string) (*ContactListResponse, error) { + var requestMap = make(map[string]interface{}) + + if search != "" { + requestMap["search"] = search + } + req := s.client.NewRequest(methodContactList, requestMap) + + resp, err := s.client.Do(*req) + if err != nil { + return nil, err + } + + var result ContactListResponse + err = mapstructure.Decode(*resp, &result) + if err != nil { + return nil, err + } + + return &result, nil +} diff --git a/vendor/github.com/smueller18/goinwx/domain.go b/vendor/github.com/smueller18/goinwx/domain.go new file mode 100644 index 000000000..ddbc86a20 --- /dev/null +++ b/vendor/github.com/smueller18/goinwx/domain.go @@ -0,0 +1,303 @@ +package goinwx + +import ( + "errors" + "fmt" + "time" + + "github.com/fatih/structs" + "github.com/mitchellh/mapstructure" +) + +const ( + methodDomainCheck = "domain.check" + methodDomainCreate = "domain.create" + methodDomainDelete = "domain.delete" + methodDomainGetPrices = "domain.getPrices" + methodDomainGetRules = "domain.getRules" + methodDomainInfo = "domain.info" + methodDomainList = "domain.list" + methodDomainLog = "domain.log" + methodDomainPush = "domain.push" + methodDomainRenew = "domain.renew" + methodDomainRestore = "domain.restore" + methodDomainStats = "domain.stats" + methodDomainTrade = "domain.trade" + methodDomainTransfer = "domain.transfer" + methodDomainTransferOut = "domain.transferOut" + methodDomainUpdate = "domain.update" + methodDomainWhois = "domain.whois" +) + +type DomainService interface { + Check(domains []string) ([]DomainCheckResponse, error) + Register(request *DomainRegisterRequest) (*DomainRegisterResponse, error) + Delete(domain string, scheduledDate time.Time) error + Info(domain string, roId int) (*DomainInfoResponse, error) + GetPrices(tlds []string) ([]DomainPriceResponse, error) + List(*DomainListRequest) (*DomainList, error) + Whois(domain string) (string, error) +} + +type DomainServiceOp struct { + client *Client +} + +var _ DomainService = &DomainServiceOp{} + +type domainCheckResponseRoot struct { + Domains []DomainCheckResponse `mapstructure:"domain"` +} +type DomainCheckResponse struct { + Available int `mapstructure:"avail"` + Status string `mapstructure:"status"` + Name string `mapstructure:"name"` + Domain string `mapstructure:"domain"` + TLD string `mapstructure:"tld"` + CheckMethod string `mapstructure:"checkmethod"` + Price float32 `mapstructure:"price"` + CheckTime float32 `mapstructure:"checktime"` +} + +type domainPriceResponseRoot struct { + Prices []DomainPriceResponse `mapstructure:"price"` +} +type DomainPriceResponse struct { + Tld string `mapstructure:"tld"` + Currency string `mapstructure:"currency"` + CreatePrice float32 `mapstructure:"createPrice"` + MonthlyCreatePrice float32 `mapstructure:"monthlyCreatePrice"` + TransferPrice float32 `mapstructure:"transferPrice"` + RenewalPrice float32 `mapstructure:"renewalPrice"` + MonthlyRenewalPrice float32 `mapstructure:"monthlyRenewalPrice"` + UpdatePrice float32 `mapstructure:"updatePrice"` + TradePrice float32 `mapstructure:"tradePrice"` + TrusteePrice float32 `mapstructure:"trusteePrice"` + MonthlyTrusteePrice float32 `mapstructure:"monthlyTrusteePrice"` + CreatePeriod int `mapstructure:"createPeriod"` + TransferPeriod int `mapstructure:"transferPeriod"` + RenewalPeriod int `mapstructure:"renewalPeriod"` + TradePeriod int `mapstructure:"tradePeriod"` +} + +type DomainRegisterRequest struct { + Domain string `structs:"domain"` + Period string `structs:"period,omitempty"` + Registrant int `structs:"registrant"` + Admin int `structs:"admin"` + Tech int `structs:"tech"` + Billing int `structs:"billing"` + Nameservers []string `structs:"ns,omitempty"` + TransferLock string `structs:"transferLock,omitempty"` + RenewalMode string `structs:"renewalMode,omitempty"` + WhoisProvider string `structs:"whoisProvider,omitempty"` + WhoisUrl string `structs:"whoisUrl,omitempty"` + ScDate string `structs:"scDate,omitempty"` + ExtDate string `structs:"extDate,omitempty"` + Asynchron string `structs:"asynchron,omitempty"` + Voucher string `structs:"voucher,omitempty"` + Testing string `structs:"testing,omitempty"` +} + +type DomainRegisterResponse struct { + RoId int + Price float32 + Currency string +} + +type DomainInfoResponse struct { + RoId int `mapstructure:"roId"` + Domain string `mapstructure:"domain"` + DomainAce string `mapstructure:"domainAce"` + Period string `mapstructure:"period"` + CrDate time.Time `mapstructure:"crDate"` + ExDate time.Time `mapstructure:"exDate"` + UpDate time.Time `mapstructure:"upDate"` + ReDate time.Time `mapstructure:"reDate"` + ScDate time.Time `mapstructure:"scDate"` + TransferLock int `mapstructure:"transferLock"` + Status string `mapstructure:"status"` + AuthCode string `mapstructure:"authCode"` + RenewalMode string `mapstructure:"renewalMode"` + TransferMode string `mapstructure:"transferMode"` + Registrant int `mapstructure:"registrant"` + Admin int `mapstructure:"admin"` + Tech int `mapstructure:"tech"` + Billing int `mapstructure:"billing"` + Nameservers []string `mapstructure:"ns"` + NoDelegation string `mapstructure:"noDelegation"` + Contacts map[string]Contact `mapstructure:"contact"` +} + +type Contact struct { + RoId int + Id string + Type string + Name string + Org string + Street string + City string + PostalCode string `mapstructure:"pc"` + StateProvince string `mapstructure:"sp"` + Country string `mapstructure:"cc"` + Phone string `mapstructure:"voice"` + Fax string + Email string + Remarks string + Protection string +} + +type DomainListRequest struct { + Domain string `structs:"domain,omitempty"` + RoId int `structs:"roId,omitempty"` + Status int `structs:"status,omitempty"` + Registrant int `structs:"registrant,omitempty"` + Admin int `structs:"admin,omitempty"` + Tech int `structs:"tech,omitempty"` + Billing int `structs:"billing,omitempty"` + RenewalMode int `structs:"renewalMode,omitempty"` + TransferLock int `structs:"transferLock,omitempty"` + NoDelegation int `structs:"noDelegation,omitempty"` + Tag int `structs:"tag,omitempty"` + Order int `structs:"order,omitempty"` + Page int `structs:"page,omitempty"` + Pagelimit int `structs:"pagelimit,omitempty"` +} + +type DomainList struct { + Count int + Domains []DomainInfoResponse `mapstructure:"domain"` +} + +func (s *DomainServiceOp) Check(domains []string) ([]DomainCheckResponse, error) { + req := s.client.NewRequest(methodDomainCheck, map[string]interface{}{ + "domain": domains, + "wide": "2", + }) + + resp, err := s.client.Do(*req) + if err != nil { + return nil, err + } + + root := new(domainCheckResponseRoot) + err = mapstructure.Decode(*resp, &root) + if err != nil { + return nil, err + } + + return root.Domains, nil +} + +func (s *DomainServiceOp) GetPrices(tlds []string) ([]DomainPriceResponse, error) { + req := s.client.NewRequest(methodDomainGetPrices, map[string]interface{}{ + "tld": tlds, + "vat": false, + }) + + resp, err := s.client.Do(*req) + if err != nil { + return nil, err + } + + root := new(domainPriceResponseRoot) + err = mapstructure.Decode(*resp, &root) + if err != nil { + return nil, err + } + + return root.Prices, nil +} + +func (s *DomainServiceOp) Register(request *DomainRegisterRequest) (*DomainRegisterResponse, error) { + req := s.client.NewRequest(methodDomainCreate, structs.Map(request)) + + resp, err := s.client.Do(*req) + if err != nil { + return nil, err + } + + var result DomainRegisterResponse + err = mapstructure.Decode(*resp, &result) + if err != nil { + return nil, err + } + + return &result, nil +} + +func (s *DomainServiceOp) Delete(domain string, scheduledDate time.Time) error { + req := s.client.NewRequest(methodDomainDelete, map[string]interface{}{ + "domain": domain, + "scDate": scheduledDate.Format(time.RFC3339), + }) + + _, err := s.client.Do(*req) + return err +} + +func (s *DomainServiceOp) Info(domain string, roId int) (*DomainInfoResponse, error) { + req := s.client.NewRequest(methodDomainInfo, map[string]interface{}{ + "domain": domain, + "wide": "2", + }) + if roId != 0 { + req.Args["roId"] = roId + } + + resp, err := s.client.Do(*req) + if err != nil { + return nil, err + } + + var result DomainInfoResponse + err = mapstructure.Decode(*resp, &result) + if err != nil { + return nil, err + } + fmt.Println("Response", result) + + return &result, nil +} + +func (s *DomainServiceOp) List(request *DomainListRequest) (*DomainList, error) { + if request == nil { + return nil, errors.New("Request can't be nil") + } + requestMap := structs.Map(request) + requestMap["wide"] = "2" + + req := s.client.NewRequest(methodDomainList, requestMap) + + resp, err := s.client.Do(*req) + if err != nil { + return nil, err + } + + var result DomainList + err = mapstructure.Decode(*resp, &result) + if err != nil { + return nil, err + } + + return &result, nil +} + +func (s *DomainServiceOp) Whois(domain string) (string, error) { + req := s.client.NewRequest(methodDomainWhois, map[string]interface{}{ + "domain": domain, + }) + + resp, err := s.client.Do(*req) + if err != nil { + return "", err + } + + var result map[string]string + err = mapstructure.Decode(*resp, &result) + if err != nil { + return "", err + } + + return result["whois"], nil +} diff --git a/vendor/github.com/smueller18/goinwx/goinwx.go b/vendor/github.com/smueller18/goinwx/goinwx.go new file mode 100644 index 000000000..b4d67d621 --- /dev/null +++ b/vendor/github.com/smueller18/goinwx/goinwx.go @@ -0,0 +1,133 @@ +package goinwx + +import ( + "fmt" + "net/url" + + "github.com/kolo/xmlrpc" +) + +const ( + libraryVersion = "0.4.0" + APIBaseUrl = "https://api.domrobot.com/xmlrpc/" + APISandboxBaseUrl = "https://api.ote.domrobot.com/xmlrpc/" + APILanguage = "eng" +) + +// Client manages communication with INWX API. +type Client struct { + // HTTP client used to communicate with the INWX API. + RPCClient *xmlrpc.Client + + // Base URL for API requests. + BaseURL *url.URL + + // API username + Username string + + // API password + Password string + + // User agent for client + APILanguage string + + // Services used for communicating with the API + Account AccountService + Domains DomainService + Nameservers NameserverService + Contacts ContactService +} + +type ClientOptions struct { + Sandbox bool +} + +type Request struct { + ServiceMethod string + Args map[string]interface{} +} + +// Response is a INWX API response. This wraps the standard http.Response returned from INWX. +type Response struct { + Code int `xmlrpc:"code"` + Message string `xmlrpc:"msg"` + ReasonCode string `xmlrpc:"reasonCode"` + Reason string `xmlrpc:"reason"` + ResponseData map[string]interface{} `xmlrpc:"resData"` +} + +// An ErrorResponse reports the error caused by an API request +type ErrorResponse struct { + Code int `xmlrpc:"code"` + Message string `xmlrpc:"msg"` + ReasonCode string `xmlrpc:"reasonCode"` + Reason string `xmlrpc:"reason"` +} + +// NewClient returns a new INWX API client. +func NewClient(username, password string, opts *ClientOptions) *Client { + var useSandbox bool + if opts != nil { + useSandbox = opts.Sandbox + } + + var baseURL *url.URL + + if useSandbox { + baseURL, _ = url.Parse(APISandboxBaseUrl) + } else { + baseURL, _ = url.Parse(APIBaseUrl) + } + + rpcClient, _ := xmlrpc.NewClient(baseURL.String(), nil) + + client := &Client{RPCClient: rpcClient, + BaseURL: baseURL, + Username: username, + Password: password, + } + + client.Account = &AccountServiceOp{client: client} + client.Domains = &DomainServiceOp{client: client} + client.Nameservers = &NameserverServiceOp{client: client} + client.Contacts = &ContactServiceOp{client: client} + + return client +} + +// NewRequest creates an API request. +func (c *Client) NewRequest(serviceMethod string, args map[string]interface{}) *Request { + if args != nil { + args["lang"] = APILanguage + } + + return &Request{ServiceMethod: serviceMethod, Args: args} +} + +// Do sends an API request and returns the API response. +func (c *Client) Do(req Request) (*map[string]interface{}, error) { + var resp Response + err := c.RPCClient.Call(req.ServiceMethod, req.Args, &resp) + if err != nil { + return nil, err + } + + return &resp.ResponseData, CheckResponse(&resp) +} + +func (r *ErrorResponse) Error() string { + if r.Reason != "" { + return fmt.Sprintf("(%d) %s. Reason: (%s) %s", + r.Code, r.Message, r.ReasonCode, r.Reason) + } + return fmt.Sprintf("(%d) %s", r.Code, r.Message) +} + +// CheckResponse checks the API response for errors, and returns them if present. +func CheckResponse(r *Response) error { + if c := r.Code; c >= 1000 && c <= 1500 { + return nil + } + + return &ErrorResponse{Code: r.Code, Message: r.Message, Reason: r.Reason, ReasonCode: r.ReasonCode} +} diff --git a/vendor/github.com/smueller18/goinwx/nameserver.go b/vendor/github.com/smueller18/goinwx/nameserver.go new file mode 100644 index 000000000..38269b461 --- /dev/null +++ b/vendor/github.com/smueller18/goinwx/nameserver.go @@ -0,0 +1,275 @@ +package goinwx + +import ( + "errors" + "fmt" + "time" + + "github.com/fatih/structs" + "github.com/mitchellh/mapstructure" +) + +const ( + methodNameserverCheck = "nameserver.check" + methodNameserverCreate = "nameserver.create" + methodNameserverCreateRecord = "nameserver.createRecord" + methodNameserverDelete = "nameserver.delete" + methodNameserverDeleteRecord = "nameserver.deleteRecord" + methodNameserverInfo = "nameserver.info" + methodNameserverList = "nameserver.list" + methodNameserverUpdate = "nameserver.update" + methodNameserverUpdateRecord = "nameserver.updateRecord" +) + +type NameserverService interface { + Check(domain string, nameservers []string) (*NameserverCheckResponse, error) + Create(*NameserverCreateRequest) (int, error) + Info(*NameserverInfoRequest) (*NamserverInfoResponse, error) + List(domain string) (*NamserverListResponse, error) + CreateRecord(*NameserverRecordRequest) (int, error) + UpdateRecord(recId int, request *NameserverRecordRequest) error + DeleteRecord(recId int) error + FindRecordById(recId int) (*NameserverRecord, *NameserverDomain, error) +} + +type NameserverServiceOp struct { + client *Client +} + +var _ NameserverService = &NameserverServiceOp{} + +type NameserverCheckResponse struct { + Details []string + Status string +} + +type NameserverRecordRequest struct { + RoId int `structs:"roId,omitempty"` + Domain string `structs:"domain,omitempty"` + Type string `structs:"type"` + Content string `structs:"content"` + Name string `structs:"name,omitempty"` + Ttl int `structs:"ttl,omitempty"` + Priority int `structs:"prio,omitempty"` + UrlRedirectType string `structs:"urlRedirectType,omitempty"` + UrlRedirectTitle string `structs:"urlRedirectTitle,omitempty"` + UrlRedirectDescription string `structs:"urlRedirectDescription,omitempty"` + UrlRedirectFavIcon string `structs:"urlRedirectFavIcon,omitempty"` + UrlRedirectKeywords string `structs:"urlRedirectKeywords,omitempty"` +} + +type NameserverCreateRequest struct { + Domain string `structs:"domain"` + Type string `structs:"type"` + Nameservers []string `structs:"ns,omitempty"` + MasterIp string `structs:"masterIp,omitempty"` + Web string `structs:"web,omitempty"` + Mail string `structs:"mail,omitempty"` + SoaEmail string `structs:"soaEmail,omitempty"` + UrlRedirectType string `structs:"urlRedirectType,omitempty"` + UrlRedirectTitle string `structs:"urlRedirectTitle,omitempty"` + UrlRedirectDescription string `structs:"urlRedirectDescription,omitempty"` + UrlRedirectFavIcon string `structs:"urlRedirectFavIcon,omitempty"` + UrlRedirectKeywords string `structs:"urlRedirectKeywords,omitempty"` + Testing bool `structs:"testing,omitempty"` +} + +type NameserverInfoRequest struct { + Domain string `structs:"domain,omitempty"` + RoId int `structs:"roId,omitempty"` + RecordId int `structs:"recordId,omitempty"` + Type string `structs:"type,omitempty"` + Name string `structs:"name,omitempty"` + Content string `structs:"content,omitempty"` + Ttl int `structs:"ttl,omitempty"` + Prio int `structs:"prio,omitempty"` +} + +type NamserverInfoResponse struct { + RoId int + Domain string + Type string + MasterIp string + LastZoneCheck time.Time + SlaveDns interface{} + SOAserial string + Count int + Records []NameserverRecord `mapstructure:"record"` +} + +type NameserverRecord struct { + Id int + Name string + Type string + Content string + Ttl int + Prio int + UrlRedirectType string + UrlRedirectTitle string + UrlRedirectDescription string + UrlRedirectKeywords string + UrlRedirectFavIcon string +} + +type NamserverListResponse struct { + Count int + Domains []NameserverDomain `mapstructure:"domains"` +} + +type NameserverDomain struct { + RoId int `mapstructure:"roId"` + Domain string `mapstructure:"domain"` + Type string `mapstructure:"type"` + MasterIp string `mapstructure:"masterIp"` + Mail string `mapstructure:"mail"` + Web string `mapstructure:"web"` + Url string `mapstructure:"url"` + Ipv4 string `mapstructure:"ipv4"` + Ipv6 string `mapstructure:"ipv6"` +} + +func (s *NameserverServiceOp) Check(domain string, nameservers []string) (*NameserverCheckResponse, error) { + req := s.client.NewRequest(methodNameserverCheck, map[string]interface{}{ + "domain": domain, + "ns": nameservers, + }) + + resp, err := s.client.Do(*req) + if err != nil { + return nil, err + } + + var result NameserverCheckResponse + err = mapstructure.Decode(*resp, &result) + if err != nil { + return nil, err + } + + return &result, nil +} + +func (s *NameserverServiceOp) Info(request *NameserverInfoRequest) (*NamserverInfoResponse, error) { + req := s.client.NewRequest(methodNameserverInfo, structs.Map(request)) + + resp, err := s.client.Do(*req) + if err != nil { + return nil, err + } + var result NamserverInfoResponse + err = mapstructure.Decode(*resp, &result) + if err != nil { + return nil, err + } + + return &result, nil +} + +func (s *NameserverServiceOp) List(domain string) (*NamserverListResponse, error) { + requestMap := map[string]interface{}{ + "domain": "*", + "wide": 2, + } + if domain != "" { + requestMap["domain"] = domain + } + req := s.client.NewRequest(methodNameserverList, requestMap) + + resp, err := s.client.Do(*req) + if err != nil { + return nil, err + } + var result NamserverListResponse + err = mapstructure.Decode(*resp, &result) + if err != nil { + return nil, err + } + + return &result, nil +} + +func (s *NameserverServiceOp) Create(request *NameserverCreateRequest) (int, error) { + req := s.client.NewRequest(methodNameserverCreate, structs.Map(request)) + + resp, err := s.client.Do(*req) + if err != nil { + return 0, err + } + + var result map[string]int + err = mapstructure.Decode(*resp, &result) + if err != nil { + return 0, err + } + + return result["roId"], nil +} + +func (s *NameserverServiceOp) CreateRecord(request *NameserverRecordRequest) (int, error) { + req := s.client.NewRequest(methodNameserverCreateRecord, structs.Map(request)) + + resp, err := s.client.Do(*req) + if err != nil { + return 0, err + } + + var result map[string]int + err = mapstructure.Decode(*resp, &result) + if err != nil { + return 0, err + } + + return result["id"], nil +} + +func (s *NameserverServiceOp) UpdateRecord(recId int, request *NameserverRecordRequest) error { + if request == nil { + return errors.New("Request can't be nil") + } + requestMap := structs.Map(request) + requestMap["id"] = recId + + req := s.client.NewRequest(methodNameserverUpdateRecord, requestMap) + + _, err := s.client.Do(*req) + if err != nil { + return err + } + + return nil +} + +func (s *NameserverServiceOp) DeleteRecord(recId int) error { + req := s.client.NewRequest(methodNameserverDeleteRecord, map[string]interface{}{ + "id": recId, + }) + + _, err := s.client.Do(*req) + if err != nil { + return err + } + + return nil +} + +func (s *NameserverServiceOp) FindRecordById(recId int) (*NameserverRecord, *NameserverDomain, error) { + listResp, err := s.client.Nameservers.List("") + if err != nil { + return nil, nil, err + } + + for _, domainItem := range listResp.Domains { + resp, err := s.client.Nameservers.Info(&NameserverInfoRequest{RoId: domainItem.RoId}) + if err != nil { + return nil, nil, err + } + + for _, record := range resp.Records { + if record.Id == recId { + return &record, &domainItem, nil + } + } + } + + return nil, nil, fmt.Errorf("couldn't find INWX Record for id %d", recId) + +} diff --git a/vendor/github.com/transip/gotransip/LICENSE b/vendor/github.com/transip/gotransip/LICENSE new file mode 100644 index 000000000..352d193e3 --- /dev/null +++ b/vendor/github.com/transip/gotransip/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 TransIP B.V. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/transip/gotransip/api.go b/vendor/github.com/transip/gotransip/api.go new file mode 100644 index 000000000..46c684480 --- /dev/null +++ b/vendor/github.com/transip/gotransip/api.go @@ -0,0 +1,12 @@ +package gotransip + +// CancellationTime represents the possible ways of canceling a contract +type CancellationTime string + +var ( + // CancellationTimeEnd specifies to cancel the contract when the contract was + // due to end anyway + CancellationTimeEnd CancellationTime = "end" + // CancellationTimeImmediately specifies to cancel the contract immediately + CancellationTimeImmediately CancellationTime = "immediately" +) diff --git a/vendor/github.com/transip/gotransip/client.go b/vendor/github.com/transip/gotransip/client.go new file mode 100644 index 000000000..0be9c400a --- /dev/null +++ b/vendor/github.com/transip/gotransip/client.go @@ -0,0 +1,119 @@ +package gotransip + +import ( + "errors" + "fmt" + "io/ioutil" + "os" +) + +const ( + transipAPIHost = "api.transip.nl" + transipAPINamespace = "http://www.transip.nl/soap" +) + +// APIMode specifies in which mode the API is used. Currently this is only +// supports either readonly or readwrite +type APIMode string + +var ( + // APIModeReadOnly specifies that no changes can be made from API calls + APIModeReadOnly APIMode = "readonly" + // APIModeReadWrite specifies that changes can be made from API calls + APIModeReadWrite APIMode = "readwrite" +) + +// ClientConfig is a tool to easily create a new Client object +type ClientConfig struct { + AccountName string + PrivateKeyPath string + PrivateKeyBody []byte + Mode APIMode +} + +// Client is the interface which all clients should implement +type Client interface { + Call(SoapRequest, interface{}) error // execute request on client +} + +// SOAPClient represents a TransIP API SOAP client, implementing the Client +// interface +type SOAPClient struct { + soapClient soapClient +} + +// Call performs given SOAP request and fills the response into result +func (c SOAPClient) Call(req SoapRequest, result interface{}) error { + return c.soapClient.call(req, result) +} + +// NewSOAPClient returns a new SOAPClient object for given config +// ClientConfig's PrivateKeyPath will override potentially given PrivateKeyBody +func NewSOAPClient(c ClientConfig) (SOAPClient, error) { + // check account name + if len(c.AccountName) == 0 { + return SOAPClient{}, errors.New("AccountName is required") + } + + // check if private key was given in any form + if len(c.PrivateKeyPath) == 0 && len(c.PrivateKeyBody) == 0 { + return SOAPClient{}, errors.New("PrivateKeyPath or PrivateKeyBody is required") + } + + // if PrivateKeyPath was set, this will override any given PrivateKeyBody + if len(c.PrivateKeyPath) > 0 { + // try to open private key and read contents + if _, err := os.Stat(c.PrivateKeyPath); err != nil { + return SOAPClient{}, fmt.Errorf("could not open private key: %s", err.Error()) + } + + // read private key so we can pass the body to the soapClient + var err error + c.PrivateKeyBody, err = ioutil.ReadFile(c.PrivateKeyPath) + if err != nil { + return SOAPClient{}, err + } + } + + // default to APIMode read/write + if len(c.Mode) == 0 { + c.Mode = APIModeReadWrite + } + + // create soapClient and pass it to a new Client pointer + sc := soapClient{ + Login: c.AccountName, + Mode: c.Mode, + PrivateKey: c.PrivateKeyBody, + } + + return SOAPClient{ + soapClient: sc, + }, nil +} + +// FakeSOAPClient is a client doing nothing except implementing the gotransip.Client +// interface +// you can however set a fixture XML body which Call will try to Unmarshal into +// result +// useful for testing +type FakeSOAPClient struct { + fixture []byte // preset this fixture so Call can use it to Unmarshal +} + +// FixtureFromFile reads file and sets content as FakeSOAPClient's fixture +func (f *FakeSOAPClient) FixtureFromFile(file string) (err error) { + // read fixture file + f.fixture, err = ioutil.ReadFile(file) + if err != nil { + err = fmt.Errorf("could not read fixture from file %s: %s", file, err.Error()) + } + + return +} + +// Call doesn't do anything except fill the XML unmarshalled result +func (f FakeSOAPClient) Call(req SoapRequest, result interface{}) error { + // this fake client just parses given fixture as if it was a SOAP response + return parseSoapResponse(f.fixture, req.Padding, 200, result) +} diff --git a/vendor/github.com/transip/gotransip/domain/api.go b/vendor/github.com/transip/gotransip/domain/api.go new file mode 100644 index 000000000..f5f90f8e2 --- /dev/null +++ b/vendor/github.com/transip/gotransip/domain/api.go @@ -0,0 +1,314 @@ +package domain + +import ( + "github.com/transip/gotransip" +) + +// This file holds all DomainService methods directly ported from TransIP API + +// BatchCheckAvailability checks the availability of multiple domains +func BatchCheckAvailability(c gotransip.Client, domainNames []string) ([]CheckResult, error) { + sr := gotransip.SoapRequest{ + Service: serviceName, + Method: "batchCheckAvailability", + } + sr.AddArgument("domainNames", domainNames) + + var v struct { + V []CheckResult `xml:"item"` + } + + err := c.Call(sr, &v) + return v.V, err +} + +// CheckAvailability returns the availability status of a domain. +func CheckAvailability(c gotransip.Client, domainName string) (Status, error) { + sr := gotransip.SoapRequest{ + Service: serviceName, + Method: "checkAvailability", + } + sr.AddArgument("domainName", domainName) + + var v Status + err := c.Call(sr, &v) + return v, err +} + +// GetWhois returns the whois of a domain name +func GetWhois(c gotransip.Client, domainName string) (string, error) { + sr := gotransip.SoapRequest{ + Service: serviceName, + Method: "getWhois", + } + sr.AddArgument("domainName", domainName) + + var v string + err := c.Call(sr, &v) + return v, err +} + +// GetDomainNames returns list with domain names or error when this failed +func GetDomainNames(c gotransip.Client) ([]string, error) { + var d = struct { + D []string `xml:"item"` + }{} + err := c.Call(gotransip.SoapRequest{ + Service: serviceName, + Method: "getDomainNames", + }, &d) + + return d.D, err +} + +// GetInfo returns Domain for given name or error when this failed +func GetInfo(c gotransip.Client, domainName string) (Domain, error) { + sr := gotransip.SoapRequest{ + Service: serviceName, + Method: "getInfo", + } + sr.AddArgument("domainName", domainName) + + var d Domain + err := c.Call(sr, &d) + + return d, err +} + +// BatchGetInfo returns array of Domain for given name or error when this failed +func BatchGetInfo(c gotransip.Client, domainNames []string) ([]Domain, error) { + sr := gotransip.SoapRequest{ + Service: serviceName, + Method: "batchGetInfo", + } + sr.AddArgument("domainNames", domainNames) + + var d = struct { + D []Domain `xml:"item"` + }{} + err := c.Call(sr, &d) + + return d.D, err +} + +// GetAuthCode returns the Auth code for a domainName +func GetAuthCode(c gotransip.Client, domainName string) (string, error) { + sr := gotransip.SoapRequest{ + Service: serviceName, + Method: "getAuthCode", + } + sr.AddArgument("domainName", domainName) + + var v string + err := c.Call(sr, &v) + return v, err +} + +// GetIsLocked returns the lock status for a domainName +func GetIsLocked(c gotransip.Client, domainName string) (bool, error) { + sr := gotransip.SoapRequest{ + Service: serviceName, + Method: "getIsLocked", + } + sr.AddArgument("domainName", domainName) + + var v bool + err := c.Call(sr, &v) + return v, err +} + +// Register registers a domain name and will automatically create and sign a proposition for it +func Register(c gotransip.Client, domain string) error { + sr := gotransip.SoapRequest{ + Service: serviceName, + Method: "register", + } + sr.AddArgument("domain", domain) + + return c.Call(sr, nil) +} + +// Cancel cancels a domain name, will automatically create and sign a cancellation document +func Cancel(c gotransip.Client, domainName string, endTime gotransip.CancellationTime) error { + sr := gotransip.SoapRequest{ + Service: serviceName, + Method: "cancel", + } + sr.AddArgument("domainName", domainName) + sr.AddArgument("endTime", string(endTime)) + + return c.Call(sr, nil) +} + +// TransferWithOwnerChange transfers a domain with changing the owner +func TransferWithOwnerChange(c gotransip.Client, domain, authCode string) error { + sr := gotransip.SoapRequest{ + Service: serviceName, + Method: "transferWithOwnerChange", + } + sr.AddArgument("domain", domain) + sr.AddArgument("authCode", authCode) + + return c.Call(sr, nil) +} + +// TransferWithoutOwnerChange transfers a domain without changing the owner +func TransferWithoutOwnerChange(c gotransip.Client, domain, authCode string) error { + sr := gotransip.SoapRequest{ + Service: serviceName, + Method: "transferWithoutOwnerChange", + } + sr.AddArgument("domain", domain) + sr.AddArgument("authCode", authCode) + + return c.Call(sr, nil) +} + +// SetNameservers starts a nameserver change for this domain, will replace all +// existing nameservers with the new nameservers +func SetNameservers(c gotransip.Client, domainName string, nameservers Nameservers) error { + sr := gotransip.SoapRequest{ + Service: serviceName, + Method: "setNameservers", + } + sr.AddArgument("domainName", domainName) + sr.AddArgument("nameservers", nameservers) + + return c.Call(sr, nil) +} + +// SetLock locks this domain +func SetLock(c gotransip.Client, domainName string) error { + sr := gotransip.SoapRequest{ + Service: serviceName, + Method: "setLock", + } + sr.AddArgument("domainName", domainName) + + return c.Call(sr, nil) +} + +// UnsetLock unlocks this domain +func UnsetLock(c gotransip.Client, domainName string) error { + sr := gotransip.SoapRequest{ + Service: serviceName, + Method: "unsetLock", + } + sr.AddArgument("domainName", domainName) + + return c.Call(sr, nil) +} + +// SetDNSEntries sets the DnsEntries for this Domain, will replace all existing +// dns entries with the new entries +func SetDNSEntries(c gotransip.Client, domainName string, dnsEntries DNSEntries) error { + sr := gotransip.SoapRequest{ + Service: serviceName, + Method: "setDnsEntries", + } + sr.AddArgument("domainName", domainName) + sr.AddArgument("dnsEntries", dnsEntries) + + return c.Call(sr, nil) +} + +// SetOwner starts an owner change of a domain +func SetOwner(c gotransip.Client, domainName, registrantWhoisContact WhoisContact) error { + sr := gotransip.SoapRequest{ + Service: serviceName, + Method: "setOwner", + } + sr.AddArgument("domainName", domainName) + // make sure contact is of type registrant + registrantWhoisContact.Type = "registrant" + sr.AddArgument("registrantWhoisContact", registrantWhoisContact) + + return c.Call(sr, nil) +} + +// SetContacts starts a contact change of a domain, this will replace all existing contacts +func SetContacts(c gotransip.Client, domainName, contacts WhoisContacts) error { + sr := gotransip.SoapRequest{ + Service: serviceName, + Method: "setContacts", + } + sr.AddArgument("domainName", domainName) + sr.AddArgument("contacts", contacts) + + return c.Call(sr, nil) +} + +// GetAllTLDInfos returns slice with TLD objects or error when this failed +func GetAllTLDInfos(c gotransip.Client) ([]TLD, error) { + var d = struct { + TLD []TLD `xml:"item"` + }{} + err := c.Call(gotransip.SoapRequest{ + Service: serviceName, + Method: "getAllTldInfos", + }, &d) + + return d.TLD, err +} + +// GetTldInfo returns info about a specific TLD +func GetTldInfo(c gotransip.Client, tldName string) (TLD, error) { + sr := gotransip.SoapRequest{ + Service: serviceName, + Method: "getTldInfo", + } + sr.AddArgument("tldName", tldName) + + var v TLD + err := c.Call(sr, &v) + return v, err +} + +// GetCurrentDomainAction returns info about the action this domain is currently running +func GetCurrentDomainAction(c gotransip.Client, domainName string) (ActionResult, error) { + sr := gotransip.SoapRequest{ + Service: serviceName, + Method: "getCurrentDomainAction", + } + sr.AddArgument("domainName", domainName) + + var v ActionResult + err := c.Call(sr, &v) + return v, err +} + +// RetryCurrentDomainActionWithNewData retries a failed domain action with new +// domain data. The Domain.Name field must contain the name of the Domain. The +// Nameservers, Contacts, DNSEntries fields contain the new data for this domain. +func RetryCurrentDomainActionWithNewData(c gotransip.Client, domain Domain) error { + sr := gotransip.SoapRequest{ + Service: serviceName, + Method: "retryCurrentDomainActionWithNewData", + } + sr.AddArgument("domain", domain) + + return c.Call(sr, nil) +} + +// RetryTransferWithDifferentAuthCode retries a transfer action with a new authcode +func RetryTransferWithDifferentAuthCode(c gotransip.Client, domain Domain, newAuthCode string) error { + sr := gotransip.SoapRequest{ + Service: serviceName, + Method: "retryTransferWithDifferentAuthCode", + } + sr.AddArgument("domain", domain) + sr.AddArgument("newAuthCode", newAuthCode) + + return c.Call(sr, nil) +} + +// CancelDomainAction cancels a failed domain action +func CancelDomainAction(c gotransip.Client, domain string) error { + sr := gotransip.SoapRequest{ + Service: serviceName, + Method: "cancelDomainAction", + } + sr.AddArgument("domain", domain) + + return c.Call(sr, nil) +} diff --git a/vendor/github.com/transip/gotransip/domain/domain.go b/vendor/github.com/transip/gotransip/domain/domain.go new file mode 100644 index 000000000..56d7b719a --- /dev/null +++ b/vendor/github.com/transip/gotransip/domain/domain.go @@ -0,0 +1,405 @@ +package domain + +import ( + "fmt" + "net" + + "github.com/transip/gotransip" + "github.com/transip/gotransip/util" +) + +const ( + serviceName string = "DomainService" +) + +// Domain represents a Transip_Domain object +// as described at https://api.transip.nl/docs/transip.nl/class-Transip_Domain.html +type Domain struct { + Name string `xml:"name"` + Nameservers []Nameserver `xml:"nameservers>item"` + Contacts []WhoisContact `xml:"contacts>item"` + DNSEntries []DNSEntry `xml:"dnsEntries>item"` + Branding Branding `xml:"branding"` + AuthorizationCode string `xml:"authCode"` + IsLocked bool `xml:"isLocked"` + RegistrationDate util.XMLTime `xml:"registrationDate"` + RenewalDate util.XMLTime `xml:"renewalDate"` +} + +// EncodeParams returns Domain parameters ready to be used for constructing a signature +func (d Domain) EncodeParams(prm gotransip.ParamsContainer) { + idx := prm.Len() + prm.Add(fmt.Sprintf("%d[name]", idx), d.Name) + prm.Add(fmt.Sprintf("%d[authCode]", idx), d.AuthorizationCode) + prm.Add(fmt.Sprintf("%d[isLocked]", idx), fmt.Sprintf("%t", d.IsLocked)) + prm.Add(fmt.Sprintf("%d[registrationDate]", idx), d.RegistrationDate.Format("2006-01-02")) + prm.Add(fmt.Sprintf("%d[renewalDate]", idx), d.RenewalDate.Format("2006-01-02")) + // nameservers + for i, e := range d.Nameservers { + var ipv4, ipv6 string + if e.IPv4Address != nil { + ipv4 = e.IPv4Address.String() + } + if e.IPv6Address != nil { + ipv6 = e.IPv6Address.String() + } + prm.Add(fmt.Sprintf("%d[nameservers][%d][hostname]", idx, i), e.Hostname) + prm.Add(fmt.Sprintf("%d[nameservers][%d][ipv4]", idx, i), ipv4) + prm.Add(fmt.Sprintf("%d[nameservers][%d][ipv6]", idx, i), ipv6) + } + // contacts + for i, e := range d.Contacts { + prm.Add(fmt.Sprintf("%d[contacts][%d][type]", idx, i), e.Type) + prm.Add(fmt.Sprintf("%d[contacts][%d][firstName]", idx, i), e.FirstName) + prm.Add(fmt.Sprintf("%d[contacts][%d][middleName]", idx, i), e.MiddleName) + prm.Add(fmt.Sprintf("%d[contacts][%d][lastName]", idx, i), e.LastName) + prm.Add(fmt.Sprintf("%d[contacts][%d][companyName]", idx, i), e.CompanyName) + prm.Add(fmt.Sprintf("%d[contacts][%d][companyKvk]", idx, i), e.CompanyKvk) + prm.Add(fmt.Sprintf("%d[contacts][%d][companyType]", idx, i), e.CompanyType) + prm.Add(fmt.Sprintf("%d[contacts][%d][street]", idx, i), e.Street) + prm.Add(fmt.Sprintf("%d[contacts][%d][number]", idx, i), e.Number) + prm.Add(fmt.Sprintf("%d[contacts][%d][postalCode]", idx, i), e.PostalCode) + prm.Add(fmt.Sprintf("%d[contacts][%d][city]", idx, i), e.City) + prm.Add(fmt.Sprintf("%d[contacts][%d][phoneNumber]", idx, i), e.PhoneNumber) + prm.Add(fmt.Sprintf("%d[contacts][%d][faxNumber]", idx, i), e.FaxNumber) + prm.Add(fmt.Sprintf("%d[contacts][%d][email]", idx, i), e.Email) + prm.Add(fmt.Sprintf("%d[contacts][%d][country]", idx, i), e.Country) + } + // dnsEntries + for i, e := range d.DNSEntries { + prm.Add(fmt.Sprintf("%d[dnsEntries][%d][name]", idx, i), e.Name) + prm.Add(fmt.Sprintf("%d[dnsEntries][%d][expire]", idx, i), fmt.Sprintf("%d", e.TTL)) + prm.Add(fmt.Sprintf("%d[dnsEntries][%d][type]", idx, i), string(e.Type)) + prm.Add(fmt.Sprintf("%d[dnsEntries][%d][content]", idx, i), e.Content) + } + // branding + prm.Add(fmt.Sprintf("%d[branding][companyName]", idx), d.Branding.CompanyName) + prm.Add(fmt.Sprintf("%d[branding][supportEmail]", idx), d.Branding.SupportEmail) + prm.Add(fmt.Sprintf("%d[branding][companyUrl]", idx), d.Branding.CompanyURL) + prm.Add(fmt.Sprintf("%d[branding][termsOfUsageUrl]", idx), d.Branding.TermsOfUsageURL) + prm.Add(fmt.Sprintf("%d[branding][bannerLine1]", idx), d.Branding.BannerLine1) + prm.Add(fmt.Sprintf("%d[branding][bannerLine2]", idx), d.Branding.BannerLine2) + prm.Add(fmt.Sprintf("%d[branding][bannerLine3]", idx), d.Branding.BannerLine3) +} + +// EncodeArgs returns Domain XML body ready to be passed in the SOAP call +func (d Domain) EncodeArgs(key string) string { + output := fmt.Sprintf(`<%s xsi:type="ns1:Domain"> + %s + %s + %t + %s + %s`, + key, d.Name, d.AuthorizationCode, d.IsLocked, + d.RegistrationDate.Format("2006-01-02"), d.RenewalDate.Format("2006-01-02"), + ) + "\n" + + output += Nameservers(d.Nameservers).EncodeArgs("nameservers") + "\n" + output += WhoisContacts(d.Contacts).EncodeArgs("contacts") + "\n" + output += DNSEntries(d.DNSEntries).EncodeArgs("dnsEntries") + "\n" + output += d.Branding.EncodeArgs("branding") + "\n" + + return fmt.Sprintf("%s", output, key) +} + +// Capability represents the possible capabilities a TLD can have +type Capability string + +var ( + // CapabilityRequiresAuthCode defines this TLD requires an auth code + // to be transferred + CapabilityRequiresAuthCode Capability = "requiresAuthCode" + // CapabilityCanRegister defines this TLD can be registered + CapabilityCanRegister Capability = "canRegister" + // CapabilityCanTransferWithOwnerChange defines this TLD can be transferred + // with change of ownership + CapabilityCanTransferWithOwnerChange Capability = "canTransferWithOwnerChange" + // CapabilityCanTransferWithoutOwnerChange defines this TLD can be + // transferred without change of ownership + CapabilityCanTransferWithoutOwnerChange Capability = "canTransferWithoutOwnerChange" + // CapabilityCanSetLock defines this TLD allows to be locked + CapabilityCanSetLock Capability = "canSetLock" + // CapabilityCanSetOwner defines this TLD supports setting an owner + CapabilityCanSetOwner Capability = "canSetOwner" + // CapabilityCanSetContacts defines this TLD supports setting contacts + CapabilityCanSetContacts Capability = "canSetContacts" + // CapabilityCanSetNameservers defines this TLD supports setting nameservers + CapabilityCanSetNameservers Capability = "canSetNameservers" +) + +// TLD represents a Transip_Tld object as described at +// https://api.transip.nl/docs/transip.nl/class-Transip_Tld.html +type TLD struct { + Name string `xml:"name"` + Price float64 `xml:"price"` + RenewalPrice float64 `xml:"renewalPrice"` + Capabilities []Capability `xml:"capabilities>item"` + RegistrationPeriodLength int64 `xml:"registrationPeriodLength"` + CancelTimeFrame int64 `xml:"cancelTimeFrame"` +} + +// Nameserver represents a Transip_Nameserver object as described at +// https://api.transip.nl/docs/transip.nl/class-Transip_Nameserver.html +type Nameserver struct { + Hostname string `xml:"hostname"` + IPv4Address net.IP `xml:"ipv4"` + IPv6Address net.IP `xml:"ipv6"` +} + +// Nameservers is just an array of Nameserver +// basically only here so it can implement paramsEncoder +type Nameservers []Nameserver + +// EncodeParams returns Nameservers parameters ready to be used for constructing a signature +func (n Nameservers) EncodeParams(prm gotransip.ParamsContainer) { + idx := prm.Len() + for i, e := range n { + var ipv4, ipv6 string + if e.IPv4Address != nil { + ipv4 = e.IPv4Address.String() + } + if e.IPv6Address != nil { + ipv6 = e.IPv6Address.String() + } + prm.Add(fmt.Sprintf("%d[%d][hostname]", idx, i), e.Hostname) + prm.Add(fmt.Sprintf("%d[%d][ipv4]", idx, i), ipv4) + prm.Add(fmt.Sprintf("%d[%d][ipv6]", idx, i), ipv6) + } +} + +// EncodeArgs returns Nameservers XML body ready to be passed in the SOAP call +func (n Nameservers) EncodeArgs(key string) string { + output := fmt.Sprintf(`<%s SOAP-ENC:arrayType="ns1:Nameserver[%d]" xsi:type="ns1:ArrayOfNameserver">`, key, len(n)) + "\n" + for _, e := range n { + var ipv4, ipv6 string + if e.IPv4Address != nil { + ipv4 = e.IPv4Address.String() + } + if e.IPv6Address != nil { + ipv6 = e.IPv6Address.String() + } + output += fmt.Sprintf(` + %s + %s + %s + `, e.Hostname, ipv4, ipv6) + "\n" + } + + return fmt.Sprintf("%s", output, key) +} + +// DNSEntryType represents the possible types of DNS entries +type DNSEntryType string + +var ( + // DNSEntryTypeA represents an A-record + DNSEntryTypeA DNSEntryType = "A" + // DNSEntryTypeAAAA represents an AAAA-record + DNSEntryTypeAAAA DNSEntryType = "AAAA" + // DNSEntryTypeCNAME represents a CNAME-record + DNSEntryTypeCNAME DNSEntryType = "CNAME" + // DNSEntryTypeMX represents an MX-record + DNSEntryTypeMX DNSEntryType = "MX" + // DNSEntryTypeNS represents an NS-record + DNSEntryTypeNS DNSEntryType = "NS" + // DNSEntryTypeTXT represents a TXT-record + DNSEntryTypeTXT DNSEntryType = "TXT" + // DNSEntryTypeSRV represents an SRV-record + DNSEntryTypeSRV DNSEntryType = "SRV" +) + +// DNSEntry represents a Transip_DnsEntry object as described at +// https://api.transip.nl/docs/transip.nl/class-Transip_DnsEntry.html +type DNSEntry struct { + Name string `xml:"name"` + TTL int64 `xml:"expire"` + Type DNSEntryType `xml:"type"` + Content string `xml:"content"` +} + +// DNSEntries is just an array of DNSEntry +// basically only here so it can implement paramsEncoder +type DNSEntries []DNSEntry + +// EncodeParams returns DNSEntries parameters ready to be used for constructing a signature +func (d DNSEntries) EncodeParams(prm gotransip.ParamsContainer) { + idx := prm.Len() + for i, e := range d { + prm.Add(fmt.Sprintf("%d[%d][name]", idx, i), e.Name) + prm.Add(fmt.Sprintf("%d[%d][expire]", idx, i), fmt.Sprintf("%d", e.TTL)) + prm.Add(fmt.Sprintf("%d[%d][type]", idx, i), string(e.Type)) + prm.Add(fmt.Sprintf("%d[%d][content]", idx, i), e.Content) + } +} + +// EncodeArgs returns DNSEntries XML body ready to be passed in the SOAP call +func (d DNSEntries) EncodeArgs(key string) string { + output := fmt.Sprintf(`<%s SOAP-ENC:arrayType="ns1:DnsEntry[%d]" xsi:type="ns1:ArrayOfDnsEntry">`, key, len(d)) + "\n" + for _, e := range d { + output += fmt.Sprintf(` + %s + %d + %s + %s + `, e.Name, e.TTL, e.Type, e.Content) + "\n" + } + + return fmt.Sprintf("%s", output, key) +} + +// Status reflects the current status of a domain in a check result +type Status string + +var ( + // StatusInYourAccount means he domain name is already in your account + StatusInYourAccount Status = "inyouraccount" + // StatusUnavailable means the domain name is currently unavailable and can not be registered due to unknown reasons. + StatusUnavailable Status = "unavailable" + // StatusNotFree means the domain name has already been registered + StatusNotFree Status = "notfree" + // StatusFree means the domain name is currently free, is available and can be registered + StatusFree Status = "free" + // StatusInternalPull means the domain name is currently registered at TransIP and is available to be pulled from another account to yours. + StatusInternalPull Status = "internalpull" + // StatusInternalPush means the domain name is currently registered at TransIP in your accounta and is available to be pushed to another account. + StatusInternalPush Status = "internalpush" +) + +// CheckResult represents a Transip_DomainCheckResult object as described at +// https://api.transip.nl/docs/transip.nl/class-Transip_DomainCheckResult.html +type CheckResult struct { + DomainName string `xml:"domainName"` + Status Status `xml:"status"` + Actions []Action `xml:"actions>item"` +} + +// Branding represents a Transip_DomainBranding object as described at +// https://api.transip.nl/docs/transip.nl/class-Transip_DomainBranding.html +type Branding struct { + CompanyName string `xml:"companyName"` + SupportEmail string `xml:"supportEmail"` + CompanyURL string `xml:"companyUrl"` + TermsOfUsageURL string `xml:"termsOfUsageUrl"` + BannerLine1 string `xml:"bannerLine1"` + BannerLine2 string `xml:"bannerLine2"` + BannerLine3 string `xml:"bannerLine3"` +} + +// EncodeParams returns WhoisContacts parameters ready to be used for constructing a signature +func (b Branding) EncodeParams(prm gotransip.ParamsContainer) { + idx := prm.Len() + prm.Add(fmt.Sprintf("%d[companyName]", idx), b.CompanyName) + prm.Add(fmt.Sprintf("%d[supportEmail]", idx), b.SupportEmail) + prm.Add(fmt.Sprintf("%d[companyUrl]", idx), b.CompanyURL) + prm.Add(fmt.Sprintf("%d[termsOfUsageUrl]", idx), b.TermsOfUsageURL) + prm.Add(fmt.Sprintf("%d[bannerLine1]", idx), b.BannerLine1) + prm.Add(fmt.Sprintf("%d[bannerLine2]", idx), b.BannerLine2) + prm.Add(fmt.Sprintf("%d[bannerLine3]", idx), b.BannerLine3) +} + +// EncodeArgs returns Branding XML body ready to be passed in the SOAP call +func (b Branding) EncodeArgs(key string) string { + return fmt.Sprintf(` + %s + %s + %s + %s + %s + %s + %s + `, b.CompanyName, b.SupportEmail, b.CompanyURL, b.TermsOfUsageURL, b.BannerLine1, b.BannerLine2, b.BannerLine3) +} + +// Action reflects the available actions to perform on a domain +type Action string + +var ( + // ActionRegister registers a domain + ActionRegister Action = "register" + // ActionTransfer transfers a domain to another provider + ActionTransfer Action = "transfer" + // ActionInternalPull transfers a domain to another account at TransIP + ActionInternalPull Action = "internalpull" +) + +// ActionResult represents a Transip_DomainAction object as described at +// https://api.transip.nl/docs/transip.nl/class-Transip_DomainAction.html +type ActionResult struct { + Name string `xml:"name"` + HasFailed bool `xml:"hasFailed"` + Message string `xml:"message"` +} + +// WhoisContact represents a TransIP_WhoisContact object +// as described at https://api.transip.nl/docs/transip.nl/class-Transip_WhoisContact.html +type WhoisContact struct { + Type string `xml:"type"` + FirstName string `xml:"firstName"` + MiddleName string `xml:"middleName"` + LastName string `xml:"lastName"` + CompanyName string `xml:"companyName"` + CompanyKvk string `xml:"companyKvk"` + CompanyType string `xml:"companyType"` + Street string `xml:"street"` + Number string `xml:"number"` + PostalCode string `xml:"postalCode"` + City string `xml:"city"` + PhoneNumber string `xml:"phoneNumber"` + FaxNumber string `xml:"faxNumber"` + Email string `xml:"email"` + Country string `xml:"country"` +} + +// WhoisContacts is just an array of WhoisContact +// basically only here so it can implement paramsEncoder +type WhoisContacts []WhoisContact + +// EncodeParams returns WhoisContacts parameters ready to be used for constructing a signature +func (w WhoisContacts) EncodeParams(prm gotransip.ParamsContainer) { + idx := prm.Len() + for i, e := range w { + prm.Add(fmt.Sprintf("%d[%d][type]", idx, i), e.Type) + prm.Add(fmt.Sprintf("%d[%d][firstName]", idx, i), e.FirstName) + prm.Add(fmt.Sprintf("%d[%d][middleName]", idx, i), e.MiddleName) + prm.Add(fmt.Sprintf("%d[%d][lastName]", idx, i), e.LastName) + prm.Add(fmt.Sprintf("%d[%d][companyName]", idx, i), e.CompanyName) + prm.Add(fmt.Sprintf("%d[%d][companyKvk]", idx, i), e.CompanyKvk) + prm.Add(fmt.Sprintf("%d[%d][companyType]", idx, i), e.CompanyType) + prm.Add(fmt.Sprintf("%d[%d][street]", idx, i), e.Street) + prm.Add(fmt.Sprintf("%d[%d][number]", idx, i), e.Number) + prm.Add(fmt.Sprintf("%d[%d][postalCode]", idx, i), e.PostalCode) + prm.Add(fmt.Sprintf("%d[%d][city]", idx, i), e.City) + prm.Add(fmt.Sprintf("%d[%d][phoneNumber]", idx, i), e.PhoneNumber) + prm.Add(fmt.Sprintf("%d[%d][faxNumber]", idx, i), e.FaxNumber) + prm.Add(fmt.Sprintf("%d[%d][email]", idx, i), e.Email) + prm.Add(fmt.Sprintf("%d[%d][country]", idx, i), e.Country) + } +} + +// EncodeArgs returns WhoisContacts XML body ready to be passed in the SOAP call +func (w WhoisContacts) EncodeArgs(key string) string { + output := fmt.Sprintf(`<%s SOAP-ENC:arrayType="ns1:WhoisContact[%d]" xsi:type="ns1:ArrayOfWhoisContact">`, key, len(w)) + "\n" + for _, e := range w { + output += fmt.Sprintf(` + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + %s + `, e.Type, e.FirstName, e.MiddleName, e.LastName, e.CompanyName, + e.CompanyKvk, e.CompanyType, e.Street, e.Number, e.PostalCode, e.City, + e.PhoneNumber, e.FaxNumber, e.Email, e.Country) + "\n" + } + + return output + fmt.Sprintf("", key) +} diff --git a/vendor/github.com/transip/gotransip/sign.go b/vendor/github.com/transip/gotransip/sign.go new file mode 100644 index 000000000..8b7d360c1 --- /dev/null +++ b/vendor/github.com/transip/gotransip/sign.go @@ -0,0 +1,49 @@ +package gotransip + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha512" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "errors" + "fmt" + "net/url" +) + +var ( + asn1Header = []byte{ + 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, + 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40, + } +) + +func signWithKey(params *soapParams, key []byte) (string, error) { + // create SHA512 hash of given parameters + h := sha512.New() + h.Write([]byte(params.Encode())) + + // prefix ASN1 header to SHA512 hash + digest := append(asn1Header, h.Sum(nil)...) + + // prepare key struct + block, _ := pem.Decode(key) + if block == nil { + return "", errors.New("could not decode private key") + } + parsed, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return "", fmt.Errorf("could not parse private key: %s", err.Error()) + } + + pkey := parsed.(*rsa.PrivateKey) + + enc, err := rsa.SignPKCS1v15(rand.Reader, pkey, crypto.Hash(0), digest) + if err != nil { + return "", fmt.Errorf("could not sign data: %s", err.Error()) + } + + return url.QueryEscape(base64.StdEncoding.EncodeToString(enc)), nil +} diff --git a/vendor/github.com/transip/gotransip/soap.go b/vendor/github.com/transip/gotransip/soap.go new file mode 100644 index 000000000..b893b7f90 --- /dev/null +++ b/vendor/github.com/transip/gotransip/soap.go @@ -0,0 +1,419 @@ +package gotransip + +import ( + "bytes" + "crypto/tls" + "encoding/xml" + "errors" + "fmt" + "io/ioutil" + "math/rand" + "net/http" + "net/url" + "regexp" + "strings" + "time" +) + +const ( + // format for SOAP envelopes + soapEnvelopeFixture string = ` + + %s +` +) + +// getSOAPArgs returns XML representing given name and argument as SOAP body +func getSOAPArgs(name string, input ...string) []byte { + var buf bytes.Buffer + + buf.WriteString(fmt.Sprintf("", name)) + for _, x := range input { + buf.WriteString(x) + } + buf.WriteString(fmt.Sprintf("", name)) + + return buf.Bytes() +} + +// getSOAPArg returns XML representing given input argument as SOAP parameters +// in combination with getSOAPArgs you can build SOAP body +func getSOAPArg(name string, input interface{}) (output string) { + switch input.(type) { + case []string: + i := input.([]string) + output = fmt.Sprintf(`<%s SOAP-ENC:arrayType="xsd:string[%d]" xsi:type="ns1:ArrayOfString">`, name, len(i)) + for _, x := range i { + output = output + fmt.Sprintf(`%s`, x) + } + output = output + fmt.Sprintf("", name) + case string: + output = fmt.Sprintf(`<%s xsi:type="xsd:string">%s`, name, input, name) + case int, int32, int64: + output = fmt.Sprintf(`<%s xsi:type="xsd:integer">%d`, 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("", tag)) + data = append(append(prefix, data...), suffix...) + } + + return data +} + +// TestParamsContainer is only useful for unit testing the ParamsContainer +// implementation of other type +type TestParamsContainer struct { + Prm string +} + +// Add just makes sure we use Len(), key and value in the result so it can be +// tested +func (t *TestParamsContainer) Add(key string, value interface{}) { + var prefix string + if t.Len() > 0 { + prefix = "&" + } + t.Prm = t.Prm + prefix + fmt.Sprintf("%d%s=%s", t.Len(), key, value) +} + +// Len returns current length of test data in TestParamsContainer +func (t TestParamsContainer) Len() int { + return len(t.Prm) +} diff --git a/vendor/github.com/transip/gotransip/util/util.go b/vendor/github.com/transip/gotransip/util/util.go new file mode 100644 index 000000000..7f1beecf4 --- /dev/null +++ b/vendor/github.com/transip/gotransip/util/util.go @@ -0,0 +1,37 @@ +package util + +import ( + "encoding/xml" + "time" +) + +// KeyValueXML resembles the complex struct for getting key/value pairs from XML +type KeyValueXML struct { + Cont []struct { + Item []struct { + Key string `xml:"key"` + Value string `xml:"value"` + } `xml:"item"` + } `xml:"item"` +} + +// XMLTime is a custom type to decode XML values to time.Time directly +type XMLTime struct { + time.Time +} + +// UnmarshalXML is implemented to be able act as custom XML type +// it tries to parse time from given elements value +func (x *XMLTime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var v string + if err := d.DecodeElement(&v, &start); err != nil { + return err + } + + if p, _ := time.Parse("2006-01-02 15:04:05", v); !p.IsZero() { + *x = XMLTime{p} + } else if p, _ := time.Parse("2006-01-02", v); !p.IsZero() { + *x = XMLTime{p} + } + return nil +} diff --git a/vendor/github.com/xenolf/lego/acme/client.go b/vendor/github.com/xenolf/lego/acme/client.go index 4908d592c..f9857ebd0 100644 --- a/vendor/github.com/xenolf/lego/acme/client.go +++ b/vendor/github.com/xenolf/lego/acme/client.go @@ -665,7 +665,7 @@ func (c *Client) getAuthzForOrder(order orderResource) ([]authorization, error) go func(authzURL string) { var authz authorization - _, err := getJSON(authzURL, &authz) + _, err := postAsGet(c.jws, authzURL, &authz) if err != nil { errc <- domainError{Domain: authz.Identifier.Value, Error: err} return @@ -789,7 +789,7 @@ func (c *Client) requestCertificateForCsr(order orderResource, bundle bool, csr case <-stopTimer.C: return nil, errors.New("certificate polling timed out") case <-retryTick.C: - _, err := getJSON(order.URL, &retOrder) + _, err := postAsGet(c.jws, order.URL, &retOrder) if err != nil { return nil, err } @@ -813,7 +813,7 @@ func (c *Client) requestCertificateForCsr(order orderResource, bundle bool, csr func (c *Client) checkCertResponse(order orderMessage, certRes *CertificateResource, bundle bool) (bool, error) { switch order.Status { case statusValid: - resp, err := httpGet(order.Certificate) + resp, err := postAsGet(c.jws, order.Certificate, nil) if err != nil { return false, err } @@ -871,7 +871,7 @@ func (c *Client) checkCertResponse(order orderMessage, certRes *CertificateResou // getIssuerCertificate requests the issuer certificate func (c *Client) getIssuerCertificate(url string) ([]byte, error) { log.Infof("acme: Requesting issuer cert from %s", url) - resp, err := httpGet(url) + resp, err := postAsGet(c.jws, url, nil) if err != nil { return nil, err } @@ -914,7 +914,10 @@ func parseLinks(links []string) map[string]string { func validate(j *jws, domain, uri string, c challenge) error { var chlng challenge - hdr, err := postJSON(j, uri, c, &chlng) + // Challenge initiation is done by sending a JWS payload containing the + // trivial JSON object `{}`. We use an empty struct instance as the postJSON + // payload here to achieve this result. + hdr, err := postJSON(j, uri, struct{}{}, &chlng) if err != nil { return err } @@ -940,11 +943,15 @@ func validate(j *jws, domain, uri string, c challenge) error { // If it doesn't, we'll just poll hard. ra = 5 } + time.Sleep(time.Duration(ra) * time.Second) - hdr, err = getJSON(uri, &chlng) + resp, err := postAsGet(j, uri, &chlng) if err != nil { return err } + if resp != nil { + hdr = resp.Header + } } } diff --git a/vendor/github.com/xenolf/lego/acme/http.go b/vendor/github.com/xenolf/lego/acme/http.go index 2ddbc0f1b..8343b3690 100644 --- a/vendor/github.com/xenolf/lego/acme/http.go +++ b/vendor/github.com/xenolf/lego/acme/http.go @@ -42,12 +42,14 @@ var ( ) const ( - // defaultGoUserAgent is the Go HTTP package user agent string. Too - // bad it isn't exported. If it changes, we should update it here, too. - defaultGoUserAgent = "Go-http-client/1.1" - // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "xenolf-acme" + // NOTE: Update this with each tagged release. + ourUserAgent = "xenolf-acme/1.2.1" + + // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. + // values: detach|release + // NOTE: Update this with each tagged release. + ourUserAgentComment = "detach" // caCertificatesEnvVar is the environment variable name that can be used to // specify the path to PEM encoded CA Certificates that can be used to @@ -151,52 +153,60 @@ func postJSON(j *jws, uri string, reqBody, respBody interface{}) (http.Header, e return nil, errors.New("failed to marshal network message") } - resp, err := j.post(uri, jsonBytes) - if err != nil { - return nil, fmt.Errorf("failed to post JWS message. -> %v", err) + resp, err := post(j, uri, jsonBytes, respBody) + if resp == nil { + return nil, err } defer resp.Body.Close() + return resp.Header, err +} + +func postAsGet(j *jws, uri string, respBody interface{}) (*http.Response, error) { + return post(j, uri, []byte{}, respBody) +} + +func post(j *jws, uri string, reqBody []byte, respBody interface{}) (*http.Response, error) { + resp, err := j.post(uri, reqBody) + if err != nil { + return nil, fmt.Errorf("failed to post JWS message. -> %v", err) + } + if resp.StatusCode >= http.StatusBadRequest { err = handleHTTPError(resp) switch err.(type) { case NonceError: // Retry once if the nonce was invalidated - retryResp, errP := j.post(uri, jsonBytes) + retryResp, errP := j.post(uri, reqBody) if errP != nil { return nil, fmt.Errorf("failed to post JWS message. -> %v", errP) } - defer retryResp.Body.Close() - if retryResp.StatusCode >= http.StatusBadRequest { - return retryResp.Header, handleHTTPError(retryResp) + return retryResp, handleHTTPError(retryResp) } if respBody == nil { - return retryResp.Header, nil + return retryResp, nil } - return retryResp.Header, json.NewDecoder(retryResp.Body).Decode(respBody) - + return retryResp, json.NewDecoder(retryResp.Body).Decode(respBody) default: - return resp.Header, err - + return resp, err } - } if respBody == nil { - return resp.Header, nil + return resp, nil } - return resp.Header, json.NewDecoder(resp.Body).Decode(respBody) + return resp, json.NewDecoder(resp.Body).Decode(respBody) } // userAgent builds and returns the User-Agent string to use in requests. func userAgent() string { - ua := fmt.Sprintf("%s %s (%s; %s) %s", UserAgent, ourUserAgent, runtime.GOOS, runtime.GOARCH, defaultGoUserAgent) + ua := fmt.Sprintf("%s %s (%s; %s; %s)", UserAgent, ourUserAgent, ourUserAgentComment, runtime.GOOS, runtime.GOARCH) return strings.TrimSpace(ua) } diff --git a/vendor/github.com/xenolf/lego/acme/tls_alpn_challenge.go b/vendor/github.com/xenolf/lego/acme/tls_alpn_challenge.go index d80351999..cc70c3502 100644 --- a/vendor/github.com/xenolf/lego/acme/tls_alpn_challenge.go +++ b/vendor/github.com/xenolf/lego/acme/tls_alpn_challenge.go @@ -12,8 +12,8 @@ import ( ) // idPeAcmeIdentifierV1 is the SMI Security for PKIX Certification Extension OID referencing the ACME extension. -// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-01#section-5.1 -var idPeAcmeIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 30, 1} +// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05#section-5.1 +var idPeAcmeIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31} type tlsALPNChallenge struct { jws *jws @@ -58,7 +58,7 @@ func TLSALPNChallengeBlocks(domain, keyAuth string) ([]byte, []byte, error) { // Add the keyAuth digest as the acmeValidation-v1 extension // (marked as critical such that it won't be used by non-ACME software). - // Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-01#section-3 + // Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05#section-3 extensions := []pkix.Extension{ { Id: idPeAcmeIdentifierV1, diff --git a/vendor/github.com/xenolf/lego/providers/dns/cloudflare/cloudflare.go b/vendor/github.com/xenolf/lego/providers/dns/cloudflare/cloudflare.go index 33c2065e0..92c4b69fa 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/cloudflare/cloudflare.go +++ b/vendor/github.com/xenolf/lego/providers/dns/cloudflare/cloudflare.go @@ -126,7 +126,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { TTL: d.config.TTL, } - response, _ := d.client.CreateDNSRecord(zoneID, dnsRecord) + response, err := d.client.CreateDNSRecord(zoneID, dnsRecord) if err != nil { return fmt.Errorf("cloudflare: failed to create TXT record: %v", err) } diff --git a/vendor/github.com/xenolf/lego/providers/dns/conoha/client.go b/vendor/github.com/xenolf/lego/providers/dns/conoha/client.go new file mode 100644 index 000000000..3fa8b5bbb --- /dev/null +++ b/vendor/github.com/xenolf/lego/providers/dns/conoha/client.go @@ -0,0 +1,205 @@ +package conoha + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" +) + +const ( + identityBaseURL = "https://identity.%s.conoha.io" + dnsServiceBaseURL = "https://dns-service.%s.conoha.io" +) + +// IdentityRequest is an authentication request body. +type IdentityRequest struct { + Auth Auth `json:"auth"` +} + +// Auth is an authentication information. +type Auth struct { + TenantID string `json:"tenantId"` + PasswordCredentials PasswordCredentials `json:"passwordCredentials"` +} + +// PasswordCredentials is API-user's credentials. +type PasswordCredentials struct { + Username string `json:"username"` + Password string `json:"password"` +} + +// IdentityResponse is an authentication response body. +type IdentityResponse struct { + Access Access `json:"access"` +} + +// Access is an identity information. +type Access struct { + Token Token `json:"token"` +} + +// Token is an api access token. +type Token struct { + ID string `json:"id"` +} + +// DomainListResponse is a response of a domain listing request. +type DomainListResponse struct { + Domains []Domain `json:"domains"` +} + +// Domain is a hosted domain entry. +type Domain struct { + ID string `json:"id"` + Name string `json:"name"` +} + +// RecordListResponse is a response of record listing request. +type RecordListResponse struct { + Records []Record `json:"records"` +} + +// Record is a record entry. +type Record struct { + ID string `json:"id,omitempty"` + Name string `json:"name"` + Type string `json:"type"` + Data string `json:"data"` + TTL int `json:"ttl"` +} + +// Client is a ConoHa API client. +type Client struct { + token string + endpoint string + httpClient *http.Client +} + +// NewClient returns a client instance logged into the ConoHa service. +func NewClient(region string, auth Auth, httpClient *http.Client) (*Client, error) { + if httpClient == nil { + httpClient = &http.Client{} + } + + c := &Client{httpClient: httpClient} + + c.endpoint = fmt.Sprintf(identityBaseURL, region) + + identity, err := c.getIdentity(auth) + if err != nil { + return nil, fmt.Errorf("failed to login: %v", err) + } + + c.token = identity.Access.Token.ID + c.endpoint = fmt.Sprintf(dnsServiceBaseURL, region) + + return c, nil +} + +func (c *Client) getIdentity(auth Auth) (*IdentityResponse, error) { + req := &IdentityRequest{Auth: auth} + + identity := &IdentityResponse{} + + err := c.do(http.MethodPost, "/v2.0/tokens", req, identity) + if err != nil { + return nil, err + } + + return identity, nil +} + +// GetDomainID returns an ID of specified domain. +func (c *Client) GetDomainID(domainName string) (string, error) { + domainList := &DomainListResponse{} + + err := c.do(http.MethodGet, "/v1/domains", nil, domainList) + if err != nil { + return "", err + } + + for _, domain := range domainList.Domains { + if domain.Name == domainName { + return domain.ID, nil + } + } + return "", fmt.Errorf("no such domain: %s", domainName) +} + +// GetRecordID returns an ID of specified record. +func (c *Client) GetRecordID(domainID, recordName, recordType, data string) (string, error) { + recordList := &RecordListResponse{} + + err := c.do(http.MethodGet, fmt.Sprintf("/v1/domains/%s/records", domainID), nil, recordList) + if err != nil { + return "", err + } + + for _, record := range recordList.Records { + if record.Name == recordName && record.Type == recordType && record.Data == data { + return record.ID, nil + } + } + return "", errors.New("no such record") +} + +// CreateRecord adds new record. +func (c *Client) CreateRecord(domainID string, record Record) error { + return c.do(http.MethodPost, fmt.Sprintf("/v1/domains/%s/records", domainID), record, nil) +} + +// DeleteRecord removes specified record. +func (c *Client) DeleteRecord(domainID, recordID string) error { + return c.do(http.MethodDelete, fmt.Sprintf("/v1/domains/%s/records/%s", domainID, recordID), nil, nil) +} + +func (c *Client) do(method, path string, payload, result interface{}) error { + body := bytes.NewReader(nil) + + if payload != nil { + bodyBytes, err := json.Marshal(payload) + if err != nil { + return err + } + body = bytes.NewReader(bodyBytes) + } + + req, err := http.NewRequest(method, c.endpoint+path, body) + if err != nil { + return err + } + + req.Header.Set("Accept", "application/json") + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Auth-Token", c.token) + + resp, err := c.httpClient.Do(req) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusOK { + respBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + defer resp.Body.Close() + + return fmt.Errorf("HTTP request failed with status code %d: %s", resp.StatusCode, string(respBody)) + } + + if result != nil { + respBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + defer resp.Body.Close() + + return json.Unmarshal(respBody, result) + } + + return nil +} diff --git a/vendor/github.com/xenolf/lego/providers/dns/conoha/conoha.go b/vendor/github.com/xenolf/lego/providers/dns/conoha/conoha.go new file mode 100644 index 000000000..a872789f3 --- /dev/null +++ b/vendor/github.com/xenolf/lego/providers/dns/conoha/conoha.go @@ -0,0 +1,148 @@ +// Package conoha implements a DNS provider for solving the DNS-01 challenge +// using ConoHa DNS. +package conoha + +import ( + "errors" + "fmt" + "net/http" + "time" + + "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" +) + +// Config is used to configure the creation of the DNSProvider +type Config struct { + Region string + TenantID string + Username string + Password string + TTL int + PropagationTimeout time.Duration + PollingInterval time.Duration + HTTPClient *http.Client +} + +// NewDefaultConfig returns a default configuration for the DNSProvider +func NewDefaultConfig() *Config { + return &Config{ + Region: env.GetOrDefaultString("CONOHA_REGION", "tyo1"), + TTL: env.GetOrDefaultInt("CONOHA_TTL", 60), + PropagationTimeout: env.GetOrDefaultSecond("CONOHA_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout), + PollingInterval: env.GetOrDefaultSecond("CONOHA_POLLING_INTERVAL", acme.DefaultPollingInterval), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond("CONOHA_HTTP_TIMEOUT", 30*time.Second), + }, + } +} + +// DNSProvider is an implementation of the acme.ChallengeProvider interface +type DNSProvider struct { + config *Config + client *Client +} + +// NewDNSProvider returns a DNSProvider instance configured for ConoHa DNS. +// Credentials must be passed in the environment variables: CONOHA_TENANT_ID, CONOHA_API_USERNAME, CONOHA_API_PASSWORD +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get("CONOHA_TENANT_ID", "CONOHA_API_USERNAME", "CONOHA_API_PASSWORD") + if err != nil { + return nil, fmt.Errorf("conoha: %v", err) + } + + config := NewDefaultConfig() + config.TenantID = values["CONOHA_TENANT_ID"] + config.Username = values["CONOHA_API_USERNAME"] + config.Password = values["CONOHA_API_PASSWORD"] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for ConoHa DNS. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("conoha: the configuration of the DNS provider is nil") + } + + if config.TenantID == "" || config.Username == "" || config.Password == "" { + return nil, errors.New("conoha: some credentials information are missing") + } + + auth := Auth{ + TenantID: config.TenantID, + PasswordCredentials: PasswordCredentials{ + Username: config.Username, + Password: config.Password, + }, + } + + client, err := NewClient(config.Region, auth, config.HTTPClient) + if err != nil { + return nil, fmt.Errorf("conoha: failed to create client: %v", err) + } + + return &DNSProvider{config: config, client: client}, nil +} + +// Present creates a TXT record to fulfill the dns-01 challenge. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + fqdn, value, _ := acme.DNS01Record(domain, keyAuth) + + authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) + if err != nil { + return err + } + + id, err := d.client.GetDomainID(authZone) + if err != nil { + return fmt.Errorf("conoha: failed to get domain ID: %v", err) + } + + record := Record{ + Name: fqdn, + Type: "TXT", + Data: value, + TTL: d.config.TTL, + } + + err = d.client.CreateRecord(id, record) + if err != nil { + return fmt.Errorf("conoha: failed to create record: %v", err) + } + + return nil +} + +// CleanUp clears ConoHa DNS TXT record +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + fqdn, value, _ := acme.DNS01Record(domain, keyAuth) + + authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) + if err != nil { + return err + } + + domID, err := d.client.GetDomainID(authZone) + if err != nil { + return fmt.Errorf("conoha: failed to get domain ID: %v", err) + } + + recID, err := d.client.GetRecordID(domID, fqdn, "TXT", value) + if err != nil { + return fmt.Errorf("conoha: failed to get record ID: %v", err) + } + + err = d.client.DeleteRecord(domID, recID) + if err != nil { + return fmt.Errorf("conoha: failed to delete record: %v", err) + } + + return nil +} + +// Timeout returns the timeout and interval to use when checking for DNS propagation. +// Adjusting here to cope with spikes in propagation times. +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} diff --git a/vendor/github.com/xenolf/lego/providers/dns/dns_providers.go b/vendor/github.com/xenolf/lego/providers/dns/dns_providers.go index ff7a7d833..604137d56 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/dns_providers.go +++ b/vendor/github.com/xenolf/lego/providers/dns/dns_providers.go @@ -11,6 +11,7 @@ import ( "github.com/xenolf/lego/providers/dns/bluecat" "github.com/xenolf/lego/providers/dns/cloudflare" "github.com/xenolf/lego/providers/dns/cloudxns" + "github.com/xenolf/lego/providers/dns/conoha" "github.com/xenolf/lego/providers/dns/digitalocean" "github.com/xenolf/lego/providers/dns/dnsimple" "github.com/xenolf/lego/providers/dns/dnsmadeeasy" @@ -27,10 +28,13 @@ import ( "github.com/xenolf/lego/providers/dns/glesys" "github.com/xenolf/lego/providers/dns/godaddy" "github.com/xenolf/lego/providers/dns/hostingde" + "github.com/xenolf/lego/providers/dns/httpreq" "github.com/xenolf/lego/providers/dns/iij" + "github.com/xenolf/lego/providers/dns/inwx" "github.com/xenolf/lego/providers/dns/lightsail" "github.com/xenolf/lego/providers/dns/linode" "github.com/xenolf/lego/providers/dns/linodev4" + "github.com/xenolf/lego/providers/dns/mydnsjp" "github.com/xenolf/lego/providers/dns/namecheap" "github.com/xenolf/lego/providers/dns/namedotcom" "github.com/xenolf/lego/providers/dns/netcup" @@ -43,8 +47,11 @@ import ( "github.com/xenolf/lego/providers/dns/rfc2136" "github.com/xenolf/lego/providers/dns/route53" "github.com/xenolf/lego/providers/dns/sakuracloud" + "github.com/xenolf/lego/providers/dns/selectel" "github.com/xenolf/lego/providers/dns/stackpath" + "github.com/xenolf/lego/providers/dns/transip" "github.com/xenolf/lego/providers/dns/vegadns" + "github.com/xenolf/lego/providers/dns/vscale" "github.com/xenolf/lego/providers/dns/vultr" ) @@ -65,6 +72,8 @@ func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error) return cloudflare.NewDNSProvider() case "cloudxns": return cloudxns.NewDNSProvider() + case "conoha": + return conoha.NewDNSProvider() case "digitalocean": return digitalocean.NewDNSProvider() case "dnsimple": @@ -97,8 +106,12 @@ func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error) return godaddy.NewDNSProvider() case "hostingde": return hostingde.NewDNSProvider() + case "httpreq": + return httpreq.NewDNSProvider() case "iij": return iij.NewDNSProvider() + case "inwx": + return inwx.NewDNSProvider() case "lightsail": return lightsail.NewDNSProvider() case "linode": @@ -107,6 +120,8 @@ func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error) return linodev4.NewDNSProvider() case "manual": return acme.NewDNSProviderManual() + case "mydnsjp": + return mydnsjp.NewDNSProvider() case "namecheap": return namecheap.NewDNSProvider() case "namedotcom": @@ -133,10 +148,16 @@ func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error) return sakuracloud.NewDNSProvider() case "stackpath": return stackpath.NewDNSProvider() + case "selectel": + return selectel.NewDNSProvider() + case "transip": + return transip.NewDNSProvider() case "vegadns": return vegadns.NewDNSProvider() case "vultr": return vultr.NewDNSProvider() + case "vscale": + return vscale.NewDNSProvider() default: return nil, fmt.Errorf("unrecognised DNS provider: %s", name) } diff --git a/vendor/github.com/xenolf/lego/providers/dns/httpreq/httpreq.go b/vendor/github.com/xenolf/lego/providers/dns/httpreq/httpreq.go new file mode 100644 index 000000000..cf957d41b --- /dev/null +++ b/vendor/github.com/xenolf/lego/providers/dns/httpreq/httpreq.go @@ -0,0 +1,193 @@ +// Package httpreq implements a DNS provider for solving the DNS-01 challenge through a HTTP server. +package httpreq + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "os" + "time" + + "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" +) + +type message struct { + FQDN string `json:"fqdn"` + Value string `json:"value"` +} + +type messageRaw struct { + Domain string `json:"domain"` + Token string `json:"token"` + KeyAuth string `json:"keyAuth"` +} + +// Config is used to configure the creation of the DNSProvider +type Config struct { + Endpoint *url.URL + Mode string + Username string + Password string + PropagationTimeout time.Duration + PollingInterval time.Duration + HTTPClient *http.Client +} + +// NewDefaultConfig returns a default configuration for the DNSProvider +func NewDefaultConfig() *Config { + return &Config{ + PropagationTimeout: env.GetOrDefaultSecond("HTTPREQ_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout), + PollingInterval: env.GetOrDefaultSecond("HTTPREQ_POLLING_INTERVAL", acme.DefaultPollingInterval), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond("HTTPREQ_HTTP_TIMEOUT", 30*time.Second), + }, + } +} + +// DNSProvider describes a provider for acme-proxy +type DNSProvider struct { + config *Config +} + +// NewDNSProvider returns a DNSProvider instance. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get("HTTPREQ_ENDPOINT") + if err != nil { + return nil, fmt.Errorf("httpreq: %v", err) + } + + endpoint, err := url.Parse(values["HTTPREQ_ENDPOINT"]) + if err != nil { + return nil, fmt.Errorf("httpreq: %v", err) + } + + config := NewDefaultConfig() + config.Mode = os.Getenv("HTTPREQ_MODE") + config.Username = os.Getenv("HTTPREQ_USERNAME") + config.Password = os.Getenv("HTTPREQ_PASSWORD") + config.Endpoint = endpoint + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider . +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("httpreq: the configuration of the DNS provider is nil") + } + + if config.Endpoint == nil { + return nil, errors.New("httpreq: the endpoint is missing") + } + + return &DNSProvider{config: config}, nil +} + +// Timeout returns the timeout and interval to use when checking for DNS propagation. +// Adjusting here to cope with spikes in propagation times. +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} + +// Present creates a TXT record to fulfill the dns-01 challenge +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + if d.config.Mode == "RAW" { + msg := &messageRaw{ + Domain: domain, + Token: token, + KeyAuth: keyAuth, + } + + err := d.doPost("/present", msg) + if err != nil { + return fmt.Errorf("httpreq: %v", err) + } + return nil + } + + fqdn, value, _ := acme.DNS01Record(domain, keyAuth) + msg := &message{ + FQDN: fqdn, + Value: value, + } + + err := d.doPost("/present", msg) + if err != nil { + return fmt.Errorf("httpreq: %v", err) + } + return nil +} + +// CleanUp removes the TXT record matching the specified parameters +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + if d.config.Mode == "RAW" { + msg := &messageRaw{ + Domain: domain, + Token: token, + KeyAuth: keyAuth, + } + + err := d.doPost("/cleanup", msg) + if err != nil { + return fmt.Errorf("httpreq: %v", err) + } + return nil + } + + fqdn, value, _ := acme.DNS01Record(domain, keyAuth) + msg := &message{ + FQDN: fqdn, + Value: value, + } + + err := d.doPost("/cleanup", msg) + if err != nil { + return fmt.Errorf("httpreq: %v", err) + } + return nil +} + +func (d *DNSProvider) doPost(uri string, msg interface{}) error { + reqBody := &bytes.Buffer{} + err := json.NewEncoder(reqBody).Encode(msg) + if err != nil { + return err + } + + endpoint, err := d.config.Endpoint.Parse(uri) + if err != nil { + return err + } + + req, err := http.NewRequest(http.MethodPost, endpoint.String(), reqBody) + if err != nil { + return err + } + + req.Header.Set("Content-Type", "application/json") + + if len(d.config.Username) > 0 && len(d.config.Password) > 0 { + req.SetBasicAuth(d.config.Username, d.config.Password) + } + + resp, err := d.config.HTTPClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode >= http.StatusBadRequest { + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("%d: failed to read response body: %v", resp.StatusCode, err) + } + + return fmt.Errorf("%d: request failed: %v", resp.StatusCode, string(body)) + } + + return nil +} diff --git a/vendor/github.com/xenolf/lego/providers/dns/inwx/inwx.go b/vendor/github.com/xenolf/lego/providers/dns/inwx/inwx.go new file mode 100644 index 000000000..ba6c8cbec --- /dev/null +++ b/vendor/github.com/xenolf/lego/providers/dns/inwx/inwx.go @@ -0,0 +1,166 @@ +// Package inwx implements a DNS provider for solving the DNS-01 challenge using inwx dom robot +package inwx + +import ( + "errors" + "fmt" + "time" + + "github.com/smueller18/goinwx" + "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/log" + "github.com/xenolf/lego/platform/config/env" +) + +// Config is used to configure the creation of the DNSProvider +type Config struct { + Username string + Password string + Sandbox bool + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int +} + +// NewDefaultConfig returns a default configuration for the DNSProvider +func NewDefaultConfig() *Config { + return &Config{ + PropagationTimeout: env.GetOrDefaultSecond("INWX_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout), + PollingInterval: env.GetOrDefaultSecond("INWX_POLLING_INTERVAL", acme.DefaultPollingInterval), + TTL: env.GetOrDefaultInt("INWX_TTL", 300), + Sandbox: env.GetOrDefaultBool("INWX_SANDBOX", false), + } +} + +// DNSProvider is an implementation of the acme.ChallengeProvider interface +type DNSProvider struct { + config *Config + client *goinwx.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for Dyn DNS. +// Credentials must be passed in the environment variables: +// INWX_USERNAME and INWX_PASSWORD. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get("INWX_USERNAME", "INWX_PASSWORD") + if err != nil { + return nil, fmt.Errorf("inwx: %v", err) + } + + config := NewDefaultConfig() + config.Username = values["INWX_USERNAME"] + config.Password = values["INWX_PASSWORD"] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Dyn DNS +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("inwx: the configuration of the DNS provider is nil") + } + + if config.Username == "" || config.Password == "" { + return nil, fmt.Errorf("inwx: credentials missing") + } + + if config.Sandbox { + log.Infof("inwx: sandbox mode is enabled") + } + + client := goinwx.NewClient(config.Username, config.Password, &goinwx.ClientOptions{Sandbox: config.Sandbox}) + + return &DNSProvider{config: config, client: client}, nil +} + +// Present creates a TXT record using the specified parameters +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + fqdn, value, _ := acme.DNS01Record(domain, keyAuth) + + authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) + if err != nil { + return fmt.Errorf("inwx: %v", err) + } + + err = d.client.Account.Login() + if err != nil { + return fmt.Errorf("inwx: %v", err) + } + + defer func() { + errL := d.client.Account.Logout() + if errL != nil { + log.Infof("inwx: failed to logout: %v", errL) + } + }() + + var request = &goinwx.NameserverRecordRequest{ + Domain: acme.UnFqdn(authZone), + Name: acme.UnFqdn(fqdn), + Type: "TXT", + Content: value, + Ttl: d.config.TTL, + } + + _, err = d.client.Nameservers.CreateRecord(request) + if err != nil { + switch err.(type) { + case *goinwx.ErrorResponse: + if err.(*goinwx.ErrorResponse).Message == "Object exists" { + return nil + } + return fmt.Errorf("inwx: %v", err) + default: + return fmt.Errorf("inwx: %v", err) + } + } + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) + + authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) + if err != nil { + return fmt.Errorf("inwx: %v", err) + } + + err = d.client.Account.Login() + if err != nil { + return fmt.Errorf("inwx: %v", err) + } + + defer func() { + errL := d.client.Account.Logout() + if errL != nil { + log.Infof("inwx: failed to logout: %v", errL) + } + }() + + response, err := d.client.Nameservers.Info(&goinwx.NameserverInfoRequest{ + Domain: acme.UnFqdn(authZone), + Name: acme.UnFqdn(fqdn), + Type: "TXT", + }) + if err != nil { + return fmt.Errorf("inwx: %v", err) + } + + var lastErr error + for _, record := range response.Records { + err = d.client.Nameservers.DeleteRecord(record.Id) + if err != nil { + lastErr = fmt.Errorf("inwx: %v", err) + } + } + + return lastErr +} + +// Timeout returns the timeout and interval to use when checking for DNS propagation. +// Adjusting here to cope with spikes in propagation times. +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} diff --git a/vendor/github.com/xenolf/lego/providers/dns/mydnsjp/mydnsjp.go b/vendor/github.com/xenolf/lego/providers/dns/mydnsjp/mydnsjp.go new file mode 100644 index 000000000..05cad5686 --- /dev/null +++ b/vendor/github.com/xenolf/lego/providers/dns/mydnsjp/mydnsjp.go @@ -0,0 +1,140 @@ +// Package mydnsjp implements a DNS provider for solving the DNS-01 +// challenge using MyDNS.jp. +package mydnsjp + +import ( + "errors" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strings" + "time" + + "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" +) + +const defaultBaseURL = "https://www.mydns.jp/directedit.html" + +// Config is used to configure the creation of the DNSProvider +type Config struct { + MasterID string + Password string + PropagationTimeout time.Duration + PollingInterval time.Duration + HTTPClient *http.Client +} + +// NewDefaultConfig returns a default configuration for the DNSProvider +func NewDefaultConfig() *Config { + return &Config{ + PropagationTimeout: env.GetOrDefaultSecond("MYDNSJP_PROPAGATION_TIMEOUT", 2*time.Minute), + PollingInterval: env.GetOrDefaultSecond("MYDNSJP_POLLING_INTERVAL", 2*time.Second), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond("MYDNSJP_HTTP_TIMEOUT", 30*time.Second), + }, + } +} + +// DNSProvider is an implementation of the acme.ChallengeProvider interface +type DNSProvider struct { + config *Config +} + +// NewDNSProvider returns a DNSProvider instance configured for MyDNS.jp. +// Credentials must be passed in the environment variables: MYDNSJP_MASTER_ID and MYDNSJP_PASSWORD. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get("MYDNSJP_MASTER_ID", "MYDNSJP_PASSWORD") + if err != nil { + return nil, fmt.Errorf("mydnsjp: %v", err) + } + + config := NewDefaultConfig() + config.MasterID = values["MYDNSJP_MASTER_ID"] + config.Password = values["MYDNSJP_PASSWORD"] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for MyDNS.jp. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("mydnsjp: the configuration of the DNS provider is nil") + } + + if config.MasterID == "" || config.Password == "" { + return nil, errors.New("mydnsjp: some credentials information are missing") + } + + return &DNSProvider{config: config}, nil +} + +// Timeout returns the timeout and interval to use when checking for DNS propagation. +// Adjusting here to cope with spikes in propagation times. +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} + +// Present creates a TXT record to fulfill the dns-01 challenge +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + _, value, _ := acme.DNS01Record(domain, keyAuth) + err := d.doRequest(domain, value, "REGIST") + if err != nil { + return fmt.Errorf("mydnsjp: %v", err) + } + return nil +} + +// CleanUp removes the TXT record matching the specified parameters +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + _, value, _ := acme.DNS01Record(domain, keyAuth) + err := d.doRequest(domain, value, "DELETE") + if err != nil { + return fmt.Errorf("mydnsjp: %v", err) + } + return nil +} + +func (d *DNSProvider) doRequest(domain, value string, cmd string) error { + req, err := d.buildRequest(domain, value, cmd) + if err != nil { + return err + } + + resp, err := d.config.HTTPClient.Do(req) + if err != nil { + return fmt.Errorf("error querying API: %v", err) + } + + defer resp.Body.Close() + + if resp.StatusCode >= 400 { + var content []byte + content, err = ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + + return fmt.Errorf("request %s failed [status code %d]: %s", req.URL, resp.StatusCode, string(content)) + } + + return nil +} + +func (d *DNSProvider) buildRequest(domain, value string, cmd string) (*http.Request, error) { + params := url.Values{} + params.Set("CERTBOT_DOMAIN", domain) + params.Set("CERTBOT_VALIDATION", value) + params.Set("EDIT_CMD", cmd) + + req, err := http.NewRequest(http.MethodPost, defaultBaseURL, strings.NewReader(params.Encode())) + if err != nil { + return nil, fmt.Errorf("invalid request: %v", err) + } + + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.SetBasicAuth(d.config.MasterID, d.config.Password) + + return req, nil +} diff --git a/vendor/github.com/xenolf/lego/providers/dns/selectel/client.go b/vendor/github.com/xenolf/lego/providers/dns/selectel/client.go new file mode 100644 index 000000000..cb02230a0 --- /dev/null +++ b/vendor/github.com/xenolf/lego/providers/dns/selectel/client.go @@ -0,0 +1,211 @@ +package selectel + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" +) + +// Domain represents domain name. +type Domain struct { + ID int `json:"id,omitempty"` + Name string `json:"name,omitempty"` +} + +// Record represents DNS record. +type Record struct { + ID int `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` // Record type (SOA, NS, A/AAAA, CNAME, SRV, MX, TXT, SPF) + TTL int `json:"ttl,omitempty"` + Email string `json:"email,omitempty"` // Email of domain's admin (only for SOA records) + Content string `json:"content,omitempty"` // Record content (not for SRV) +} + +// APIError API error message +type APIError struct { + Description string `json:"error"` + Code int `json:"code"` + Field string `json:"field"` +} + +func (a *APIError) Error() string { + return fmt.Sprintf("API error: %d - %s - %s", a.Code, a.Description, a.Field) +} + +// ClientOpts represents options to init client. +type ClientOpts struct { + BaseURL string + Token string + UserAgent string + HTTPClient *http.Client +} + +// Client represents DNS client. +type Client struct { + baseURL string + token string + userAgent string + httpClient *http.Client +} + +// NewClient returns a client instance. +func NewClient(opts ClientOpts) *Client { + if opts.HTTPClient == nil { + opts.HTTPClient = &http.Client{} + } + + return &Client{ + token: opts.Token, + baseURL: opts.BaseURL, + httpClient: opts.HTTPClient, + userAgent: opts.UserAgent, + } +} + +// GetDomainByName gets Domain object by its name. +func (c *Client) GetDomainByName(domainName string) (*Domain, error) { + uri := fmt.Sprintf("/%s", domainName) + req, err := c.newRequest(http.MethodGet, uri, nil) + if err != nil { + return nil, err + } + + domain := &Domain{} + _, err = c.do(req, domain) + if err != nil { + return nil, err + } + + return domain, nil +} + +// AddRecord adds Record for given domain. +func (c *Client) AddRecord(domainID int, body Record) (*Record, error) { + uri := fmt.Sprintf("/%d/records/", domainID) + req, err := c.newRequest(http.MethodPost, uri, body) + if err != nil { + return nil, err + } + + record := &Record{} + _, err = c.do(req, record) + if err != nil { + return nil, err + } + + return record, nil +} + +// ListRecords returns list records for specific domain. +func (c *Client) ListRecords(domainID int) ([]*Record, error) { + uri := fmt.Sprintf("/%d/records/", domainID) + req, err := c.newRequest(http.MethodGet, uri, nil) + if err != nil { + return nil, err + } + + var records []*Record + _, err = c.do(req, &records) + if err != nil { + return nil, err + } + return records, nil +} + +// DeleteRecord deletes specific record. +func (c *Client) DeleteRecord(domainID, recordID int) error { + uri := fmt.Sprintf("/%d/records/%d", domainID, recordID) + req, err := c.newRequest(http.MethodDelete, uri, nil) + if err != nil { + return err + } + + _, err = c.do(req, nil) + return err +} + +func (c *Client) newRequest(method, uri string, body interface{}) (*http.Request, error) { + buf := new(bytes.Buffer) + + if body != nil { + err := json.NewEncoder(buf).Encode(body) + if err != nil { + return nil, fmt.Errorf("failed to encode request body with error: %v", err) + } + } + + req, err := http.NewRequest(method, c.baseURL+uri, buf) + if err != nil { + return nil, fmt.Errorf("failed to create new http request with error: %v", err) + } + + req.Header.Add("X-Token", c.token) + req.Header.Add("Content-Type", "application/json") + req.Header.Add("Accept", "application/json") + + return req, nil +} + +func (c *Client) do(req *http.Request, to interface{}) (*http.Response, error) { + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("request failed with error: %v", err) + } + + err = checkResponse(resp) + if err != nil { + return resp, err + } + + if to != nil { + if err = unmarshalBody(resp, to); err != nil { + return resp, err + } + } + + return resp, nil +} + +func checkResponse(resp *http.Response) error { + if resp.StatusCode >= http.StatusBadRequest && + resp.StatusCode <= http.StatusNetworkAuthenticationRequired { + + if resp.Body == nil { + return fmt.Errorf("request failed with status code %d and empty body", resp.StatusCode) + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + defer resp.Body.Close() + + apiError := APIError{} + err = json.Unmarshal(body, &apiError) + if err != nil { + return fmt.Errorf("request failed with status code %d, response body: %s", resp.StatusCode, string(body)) + } + + return fmt.Errorf("request failed with status code %d: %v", resp.StatusCode, apiError) + } + + return nil +} + +func unmarshalBody(resp *http.Response, to interface{}) error { + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + defer resp.Body.Close() + + err = json.Unmarshal(body, to) + if err != nil { + return fmt.Errorf("unmarshaling error: %v: %s", err, string(body)) + } + + return nil +} diff --git a/vendor/github.com/xenolf/lego/providers/dns/selectel/selectel.go b/vendor/github.com/xenolf/lego/providers/dns/selectel/selectel.go new file mode 100644 index 000000000..9e48b85dc --- /dev/null +++ b/vendor/github.com/xenolf/lego/providers/dns/selectel/selectel.go @@ -0,0 +1,153 @@ +// Package selectel implements a DNS provider for solving the DNS-01 challenge using Selectel Domains API. +// Selectel Domain API reference: https://kb.selectel.com/23136054.html +// Token: https://my.selectel.ru/profile/apikeys +package selectel + +import ( + "errors" + "fmt" + "net/http" + "time" + + "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" +) + +const ( + defaultBaseURL = "https://api.selectel.ru/domains/v1" + minTTL = 60 +) + +const ( + envNamespace = "SELECTEL_" + baseURLEnvVar = envNamespace + "BASE_URL" + apiTokenEnvVar = envNamespace + "API_TOKEN" + ttlEnvVar = envNamespace + "TTL" + propagationTimeoutEnvVar = envNamespace + "PROPAGATION_TIMEOUT" + pollingIntervalEnvVar = envNamespace + "POLLING_INTERVAL" + httpTimeoutEnvVar = envNamespace + "HTTP_TIMEOUT" +) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + BaseURL string + Token string + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPClient *http.Client +} + +// NewDefaultConfig returns a default configuration for the DNSProvider. +func NewDefaultConfig() *Config { + return &Config{ + BaseURL: env.GetOrDefaultString(baseURLEnvVar, defaultBaseURL), + TTL: env.GetOrDefaultInt(ttlEnvVar, minTTL), + PropagationTimeout: env.GetOrDefaultSecond(propagationTimeoutEnvVar, 120*time.Second), + PollingInterval: env.GetOrDefaultSecond(pollingIntervalEnvVar, 2*time.Second), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(httpTimeoutEnvVar, 30*time.Second), + }, + } +} + +// DNSProvider is an implementation of the acme.ChallengeProvider interface. +type DNSProvider struct { + config *Config + client *Client +} + +// NewDNSProvider returns a DNSProvider instance configured for Selectel Domains API. +// API token must be passed in the environment variable SELECTEL_API_TOKEN. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(apiTokenEnvVar) + if err != nil { + return nil, fmt.Errorf("selectel: %v", err) + } + + config := NewDefaultConfig() + config.Token = values[apiTokenEnvVar] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for selectel. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("selectel: the configuration of the DNS provider is nil") + } + + if config.Token == "" { + return nil, errors.New("selectel: credentials missing") + } + + if config.TTL < minTTL { + return nil, fmt.Errorf("selectel: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL) + } + + client := NewClient(ClientOpts{ + BaseURL: config.BaseURL, + Token: config.Token, + UserAgent: acme.UserAgent, + HTTPClient: config.HTTPClient, + }) + + return &DNSProvider{config: config, client: client}, nil +} + +// Timeout returns the Timeout and interval to use when checking for DNS propagation. +// Adjusting here to cope with spikes in propagation times. +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} + +// Present creates a TXT record to fulfill DNS-01 challenge. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + fqdn, value, _ := acme.DNS01Record(domain, keyAuth) + + domainObj, err := d.client.GetDomainByName(domain) + if err != nil { + return fmt.Errorf("selectel: %v", err) + } + + txtRecord := Record{ + Type: "TXT", + TTL: d.config.TTL, + Name: fqdn, + Content: value, + } + _, err = d.client.AddRecord(domainObj.ID, txtRecord) + if err != nil { + return fmt.Errorf("selectel: %v", err) + } + + return nil +} + +// CleanUp removes a TXT record used for DNS-01 challenge. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) + + domainObj, err := d.client.GetDomainByName(domain) + if err != nil { + return fmt.Errorf("selectel: %v", err) + } + + records, err := d.client.ListRecords(domainObj.ID) + if err != nil { + return fmt.Errorf("selectel: %v", err) + } + + // Delete records with specific FQDN + var lastErr error + for _, record := range records { + if record.Name == fqdn { + err = d.client.DeleteRecord(domainObj.ID, record.ID) + if err != nil { + lastErr = fmt.Errorf("selectel: %v", err) + } + } + } + + return lastErr +} diff --git a/vendor/github.com/xenolf/lego/providers/dns/transip/transip.go b/vendor/github.com/xenolf/lego/providers/dns/transip/transip.go new file mode 100644 index 000000000..aeb155f8f --- /dev/null +++ b/vendor/github.com/xenolf/lego/providers/dns/transip/transip.go @@ -0,0 +1,150 @@ +// Package transip implements a DNS provider for solving the DNS-01 challenge using TransIP. +package transip + +import ( + "errors" + "fmt" + "strings" + "time" + + "github.com/transip/gotransip" + transipdomain "github.com/transip/gotransip/domain" + "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" +) + +// Config is used to configure the creation of the DNSProvider +type Config struct { + AccountName string + PrivateKeyPath string + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int64 +} + +// NewDefaultConfig returns a default configuration for the DNSProvider +func NewDefaultConfig() *Config { + return &Config{ + TTL: int64(env.GetOrDefaultInt("TRANSIP_TTL", 10)), + PropagationTimeout: env.GetOrDefaultSecond("TRANSIP_PROPAGATION_TIMEOUT", 10*time.Minute), + PollingInterval: env.GetOrDefaultSecond("TRANSIP_POLLING_INTERVAL", 10*time.Second), + } +} + +// DNSProvider describes a provider for TransIP +type DNSProvider struct { + config *Config + client gotransip.SOAPClient +} + +// NewDNSProvider returns a DNSProvider instance configured for TransIP. +// Credentials must be passed in the environment variables: +// TRANSIP_ACCOUNTNAME, TRANSIP_PRIVATEKEYPATH. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get("TRANSIP_ACCOUNT_NAME", "TRANSIP_PRIVATE_KEY_PATH") + if err != nil { + return nil, fmt.Errorf("transip: %v", err) + } + + config := NewDefaultConfig() + config.AccountName = values["TRANSIP_ACCOUNT_NAME"] + config.PrivateKeyPath = values["TRANSIP_PRIVATE_KEY_PATH"] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for TransIP. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("transip: the configuration of the DNS provider is nil") + } + + client, err := gotransip.NewSOAPClient(gotransip.ClientConfig{ + AccountName: config.AccountName, + PrivateKeyPath: config.PrivateKeyPath, + }) + if err != nil { + return nil, fmt.Errorf("transip: %v", err) + } + + return &DNSProvider{client: client, config: config}, nil +} + +// Timeout returns the timeout and interval to use when checking for DNS propagation. +// Adjusting here to cope with spikes in propagation times. +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} + +// Present creates a TXT record to fulfill the dns-01 challenge +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + fqdn, value, _ := acme.DNS01Record(domain, keyAuth) + + authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) + if err != nil { + return err + } + + domainName := acme.UnFqdn(authZone) + + // get the subDomain + subDomain := strings.TrimSuffix(acme.UnFqdn(fqdn), "."+domainName) + + // get all DNS entries + info, err := transipdomain.GetInfo(d.client, domainName) + if err != nil { + return fmt.Errorf("transip: error for %s in Present: %v", domain, err) + } + + // include the new DNS entry + dnsEntries := append(info.DNSEntries, transipdomain.DNSEntry{ + Name: subDomain, + TTL: d.config.TTL, + Type: transipdomain.DNSEntryTypeTXT, + Content: value, + }) + + // set the updated DNS entries + err = transipdomain.SetDNSEntries(d.client, domainName, dnsEntries) + if err != nil { + return fmt.Errorf("transip: %v", err) + } + return nil +} + +// CleanUp removes the TXT record matching the specified parameters +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) + + authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) + if err != nil { + return err + } + + domainName := acme.UnFqdn(authZone) + + // get the subDomain + subDomain := strings.TrimSuffix(acme.UnFqdn(fqdn), "."+domainName) + + // get all DNS entries + info, err := transipdomain.GetInfo(d.client, domainName) + if err != nil { + return fmt.Errorf("transip: error for %s in CleanUp: %v", fqdn, err) + } + + // loop through the existing entries and remove the specific record + updatedEntries := info.DNSEntries[:0] + for _, e := range info.DNSEntries { + if e.Name != subDomain { + updatedEntries = append(updatedEntries, e) + } + } + + // set the updated DNS entries + err = transipdomain.SetDNSEntries(d.client, domainName, updatedEntries) + if err != nil { + return fmt.Errorf("transip: couldn't get Record ID in CleanUp: %sv", err) + } + + return nil +} diff --git a/vendor/github.com/xenolf/lego/providers/dns/vscale/client.go b/vendor/github.com/xenolf/lego/providers/dns/vscale/client.go new file mode 100644 index 000000000..1f4826f29 --- /dev/null +++ b/vendor/github.com/xenolf/lego/providers/dns/vscale/client.go @@ -0,0 +1,211 @@ +package vscale + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" +) + +// Domain represents domain name. +type Domain struct { + ID int `json:"id,omitempty"` + Name string `json:"name,omitempty"` +} + +// Record represents DNS record. +type Record struct { + ID int `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` // Record type (SOA, NS, A/AAAA, CNAME, SRV, MX, TXT, SPF) + TTL int `json:"ttl,omitempty"` + Email string `json:"email,omitempty"` // Email of domain's admin (only for SOA records) + Content string `json:"content,omitempty"` // Record content (not for SRV) +} + +// APIError API error message +type APIError struct { + Description string `json:"error"` + Code int `json:"code"` + Field string `json:"field"` +} + +func (a *APIError) Error() string { + return fmt.Sprintf("API error: %d - %s - %s", a.Code, a.Description, a.Field) +} + +// ClientOpts represents options to init client. +type ClientOpts struct { + BaseURL string + Token string + UserAgent string + HTTPClient *http.Client +} + +// Client represents DNS client. +type Client struct { + baseURL string + token string + userAgent string + httpClient *http.Client +} + +// NewClient returns a client instance. +func NewClient(opts ClientOpts) *Client { + if opts.HTTPClient == nil { + opts.HTTPClient = &http.Client{} + } + + return &Client{ + token: opts.Token, + baseURL: opts.BaseURL, + httpClient: opts.HTTPClient, + userAgent: opts.UserAgent, + } +} + +// GetDomainByName gets Domain object by its name. +func (c *Client) GetDomainByName(domainName string) (*Domain, error) { + uri := fmt.Sprintf("/%s", domainName) + req, err := c.newRequest(http.MethodGet, uri, nil) + if err != nil { + return nil, err + } + + domain := &Domain{} + _, err = c.do(req, domain) + if err != nil { + return nil, err + } + + return domain, nil +} + +// AddRecord adds Record for given domain. +func (c *Client) AddRecord(domainID int, body Record) (*Record, error) { + uri := fmt.Sprintf("/%d/records/", domainID) + req, err := c.newRequest(http.MethodPost, uri, body) + if err != nil { + return nil, err + } + + record := &Record{} + _, err = c.do(req, record) + if err != nil { + return nil, err + } + + return record, nil +} + +// ListRecords returns list records for specific domain. +func (c *Client) ListRecords(domainID int) ([]*Record, error) { + uri := fmt.Sprintf("/%d/records/", domainID) + req, err := c.newRequest(http.MethodGet, uri, nil) + if err != nil { + return nil, err + } + + var records []*Record + _, err = c.do(req, &records) + if err != nil { + return nil, err + } + return records, nil +} + +// DeleteRecord deletes specific record. +func (c *Client) DeleteRecord(domainID, recordID int) error { + uri := fmt.Sprintf("/%d/records/%d", domainID, recordID) + req, err := c.newRequest(http.MethodDelete, uri, nil) + if err != nil { + return err + } + + _, err = c.do(req, nil) + return err +} + +func (c *Client) newRequest(method, uri string, body interface{}) (*http.Request, error) { + buf := new(bytes.Buffer) + + if body != nil { + err := json.NewEncoder(buf).Encode(body) + if err != nil { + return nil, fmt.Errorf("failed to encode request body with error: %v", err) + } + } + + req, err := http.NewRequest(method, c.baseURL+uri, buf) + if err != nil { + return nil, fmt.Errorf("failed to create new http request with error: %v", err) + } + + req.Header.Add("X-Token", c.token) + req.Header.Add("Content-Type", "application/json") + req.Header.Add("Accept", "application/json") + + return req, nil +} + +func (c *Client) do(req *http.Request, to interface{}) (*http.Response, error) { + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("request failed with error: %v", err) + } + + err = checkResponse(resp) + if err != nil { + return resp, err + } + + if to != nil { + if err = unmarshalBody(resp, to); err != nil { + return resp, err + } + } + + return resp, nil +} + +func checkResponse(resp *http.Response) error { + if resp.StatusCode >= http.StatusBadRequest && + resp.StatusCode <= http.StatusNetworkAuthenticationRequired { + + if resp.Body == nil { + return fmt.Errorf("request failed with status code %d and empty body", resp.StatusCode) + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + defer resp.Body.Close() + + apiError := APIError{} + err = json.Unmarshal(body, &apiError) + if err != nil { + return fmt.Errorf("request failed with status code %d, response body: %s", resp.StatusCode, string(body)) + } + + return fmt.Errorf("request failed with status code %d: %v", resp.StatusCode, apiError) + } + + return nil +} + +func unmarshalBody(resp *http.Response, to interface{}) error { + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + defer resp.Body.Close() + + err = json.Unmarshal(body, to) + if err != nil { + return fmt.Errorf("unmarshaling error: %v: %s", err, string(body)) + } + + return nil +} diff --git a/vendor/github.com/xenolf/lego/providers/dns/vscale/vscale.go b/vendor/github.com/xenolf/lego/providers/dns/vscale/vscale.go new file mode 100644 index 000000000..b44b159d9 --- /dev/null +++ b/vendor/github.com/xenolf/lego/providers/dns/vscale/vscale.go @@ -0,0 +1,153 @@ +// Package selectel implements a DNS provider for solving the DNS-01 challenge using Vscale Domains API. +// Vscale Domain API reference: https://developers.vscale.io/documentation/api/v1/#api-Domains +// Token: https://vscale.io/panel/settings/tokens/ +package vscale + +import ( + "errors" + "fmt" + "net/http" + "time" + + "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" +) + +const ( + defaultBaseURL = "https://api.vscale.io/v1/domains" + minTTL = 60 +) + +const ( + envNamespace = "VSCALE_" + baseURLEnvVar = envNamespace + "BASE_URL" + apiTokenEnvVar = envNamespace + "API_TOKEN" + ttlEnvVar = envNamespace + "TTL" + propagationTimeoutEnvVar = envNamespace + "PROPAGATION_TIMEOUT" + pollingIntervalEnvVar = envNamespace + "POLLING_INTERVAL" + httpTimeoutEnvVar = envNamespace + "HTTP_TIMEOUT" +) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + BaseURL string + Token string + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPClient *http.Client +} + +// NewDefaultConfig returns a default configuration for the DNSProvider. +func NewDefaultConfig() *Config { + return &Config{ + BaseURL: env.GetOrDefaultString(baseURLEnvVar, defaultBaseURL), + TTL: env.GetOrDefaultInt(ttlEnvVar, minTTL), + PropagationTimeout: env.GetOrDefaultSecond(propagationTimeoutEnvVar, 120*time.Second), + PollingInterval: env.GetOrDefaultSecond(pollingIntervalEnvVar, 2*time.Second), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(httpTimeoutEnvVar, 30*time.Second), + }, + } +} + +// DNSProvider is an implementation of the acme.ChallengeProvider interface. +type DNSProvider struct { + config *Config + client *Client +} + +// NewDNSProvider returns a DNSProvider instance configured for Vscale Domains API. +// API token must be passed in the environment variable VSCALE_API_TOKEN. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(apiTokenEnvVar) + if err != nil { + return nil, fmt.Errorf("vscale: %v", err) + } + + config := NewDefaultConfig() + config.Token = values[apiTokenEnvVar] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Vscale. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("vscale: the configuration of the DNS provider is nil") + } + + if config.Token == "" { + return nil, errors.New("vscale: credentials missing") + } + + if config.TTL < minTTL { + return nil, fmt.Errorf("vscale: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL) + } + + client := NewClient(ClientOpts{ + BaseURL: config.BaseURL, + Token: config.Token, + UserAgent: acme.UserAgent, + HTTPClient: config.HTTPClient, + }) + + return &DNSProvider{config: config, client: client}, nil +} + +// Timeout returns the Timeout and interval to use when checking for DNS propagation. +// Adjusting here to cope with spikes in propagation times. +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} + +// Present creates a TXT record to fulfill DNS-01 challenge. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + fqdn, value, _ := acme.DNS01Record(domain, keyAuth) + + domainObj, err := d.client.GetDomainByName(domain) + if err != nil { + return fmt.Errorf("vscale: %v", err) + } + + txtRecord := Record{ + Type: "TXT", + TTL: d.config.TTL, + Name: fqdn, + Content: value, + } + _, err = d.client.AddRecord(domainObj.ID, txtRecord) + if err != nil { + return fmt.Errorf("vscale: %v", err) + } + + return nil +} + +// CleanUp removes a TXT record used for DNS-01 challenge. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) + + domainObj, err := d.client.GetDomainByName(domain) + if err != nil { + return fmt.Errorf("vscale: %v", err) + } + + records, err := d.client.ListRecords(domainObj.ID) + if err != nil { + return fmt.Errorf("vscale: %v", err) + } + + // Delete records with specific FQDN + var lastErr error + for _, record := range records { + if record.Name == fqdn { + err = d.client.DeleteRecord(domainObj.ID, record.ID) + if err != nil { + lastErr = fmt.Errorf("vscale: %v", err) + } + } + } + + return lastErr +} diff --git a/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ddtrace.go b/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ddtrace.go index ea7874548..bfd40561e 100644 --- a/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ddtrace.go +++ b/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ddtrace.go @@ -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 diff --git a/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext/app_types.go b/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext/app_types.go index 7fb18e586..77a26fbfc 100644 --- a/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext/app_types.go +++ b/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext/app_types.go @@ -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" +) diff --git a/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext/db.go b/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext/db.go new file mode 100644 index 000000000..d30741216 --- /dev/null +++ b/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext/db.go @@ -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" +) diff --git a/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext/peer.go b/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext/peer.go new file mode 100644 index 000000000..3b32cef46 --- /dev/null +++ b/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext/peer.go @@ -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" +) diff --git a/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext/tags.go b/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext/tags.go index 24c8f717c..6775e2aac 100644 --- a/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext/tags.go +++ b/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext/tags.go @@ -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" ) diff --git a/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/internal/globaltracer.go b/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/internal/globaltracer.go index d718bad04..008f5f033 100644 --- a/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/internal/globaltracer.go +++ b/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/internal/globaltracer.go @@ -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 } diff --git a/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/option.go b/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/option.go index b297fe34c..25e02a9cb 100644 --- a/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/option.go +++ b/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/option.go @@ -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 + } +} diff --git a/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/span.go b/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/span.go index 65cb400e8..0be348b9c 100644 --- a/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/span.go +++ b/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/span.go @@ -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) } diff --git a/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/span_msgp.go b/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/span_msgp.go index 477d6876d..39cac84a0 100644 --- a/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/span_msgp.go +++ b/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/span_msgp.go @@ -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) } } diff --git a/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/textmap.go b/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/textmap.go index 5051ee01e..30c4c78e6 100644 --- a/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/textmap.go +++ b/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/textmap.go @@ -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 } diff --git a/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/tracer.go b/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/tracer.go index a5b6021b3..6ef9d4db5 100644 --- a/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/tracer.go +++ b/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/tracer.go @@ -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 diff --git a/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/transport.go b/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/transport.go index 3b700fa48..711dc4298 100644 --- a/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/transport.go +++ b/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/transport.go @@ -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) +} diff --git a/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/util.go b/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/util.go index 58f4eae7c..c21297783 100644 --- a/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/util.go +++ b/vendor/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer/util.go @@ -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) +}