diff --git a/Gopkg.lock b/Gopkg.lock
index 551137537..2e3bde497 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -190,7 +190,7 @@
revision = "063d875e3c5fd734fa2aa12fac83829f62acfc70"
[[projects]]
- digest = "1:f401d3cc35275af81a9c1c5b960cc335779a1244f79d0559ae3fb56ca823c560"
+ digest = "1:47071ecf8d840dd357ede1b2aed46576bdd0a866adecef3c9e85a00db9672202"
name = "github.com/akamai/AkamaiOPEN-edgegrid-golang"
packages = [
"client-v1",
@@ -199,8 +199,8 @@
"jsonhooks-v1",
]
pruneopts = "NUT"
- revision = "a494eba1efa1f38338393727dff63389a6a66534"
- version = "v0.6.0"
+ revision = "1471ce9c14c6d8c007516e129262962a628fecdf"
+ version = "v0.7.3"
[[projects]]
digest = "1:823e87ae25170339e2bfd1d6f7c2e27554c6bb5655f91c67b37bd5be45bb6b32"
@@ -707,12 +707,12 @@
source = "github.com/containous/check"
[[projects]]
- digest = "1:0c0eac431b07bd0da31ef684aa15b5b5eeeda8a820f666e6523d2bfb8ce868c4"
+ digest = "1:5e92676b56ce4c69edf9ee1f6343c56f637e30af11b9d8b5edd1b6530f3fbc3d"
name = "github.com/go-ini/ini"
packages = ["."]
pruneopts = "NUT"
- revision = "32e4c1e6bc4e7d0d8451aa6b75200d19e37a536a"
- version = "v1.32.0"
+ revision = "6ed8d5f64cd79a498d1f3fab5880cc376ce41bbe"
+ version = "v1.41.0"
[[projects]]
digest = "1:9e53c5e9ee65a2c587d6ade11761ef2f976abfcd9599c5016b7046e63c1f7fb2"
@@ -857,6 +857,14 @@
pruneopts = "NUT"
revision = "bbcb9da2d746f8bdbd6a936686a0a6067ada0ec5"
+[[projects]]
+ digest = "1:1bb197a3b5db4e06e00b7560f8e89836c486627f2a0338332ed37daa003d259e"
+ name = "github.com/google/uuid"
+ packages = ["."]
+ pruneopts = "NUT"
+ revision = "064e2069ce9c359c118179501254f67d7d37ba24"
+ version = "0.2"
+
[[projects]]
digest = "1:3d7c1446fc5c710351b246c0dc6700fae843ca27f5294d0bd9f68bab2a810c44"
name = "github.com/googleapis/gnostic"
@@ -869,6 +877,24 @@
revision = "ee43cbb60db7bd22502942cccbc39059117352ab"
version = "v0.1.0"
+[[projects]]
+ branch = "master"
+ digest = "1:31a16fabf629bd7e0e6ad92939f31847cc57f3fe08898d3c31333da817196c72"
+ name = "github.com/gophercloud/gophercloud"
+ packages = [
+ ".",
+ "openstack",
+ "openstack/dns/v2/recordsets",
+ "openstack/dns/v2/zones",
+ "openstack/identity/v2/tenants",
+ "openstack/identity/v2/tokens",
+ "openstack/identity/v3/tokens",
+ "openstack/utils",
+ "pagination",
+ ]
+ pruneopts = "NUT"
+ revision = "892256c468586d66a6864daeee52441afc0f7e38"
+
[[projects]]
digest = "1:dbd86d229eacaa86a98b10f8fb3e3fc69a1913e0f4e010e7cc1f92bf12edca92"
name = "github.com/gorilla/context"
@@ -1050,14 +1076,6 @@
pruneopts = "NUT"
revision = "b84e30acd515aadc4b783ad4ff83aff3299bdfe0"
-[[projects]]
- digest = "1:417193ba917954c4837c6fc48c6ac241b3fefd13fc0889367b4a7e43b69d582c"
- name = "github.com/ldez/go-auroradns"
- packages = ["."]
- pruneopts = "NUT"
- revision = "b40dfcae7c417f8129579362695dc1f3cfe5928d"
- version = "v2.0.0"
-
[[projects]]
branch = "master"
digest = "1:5a96e1f04259484b3dd183ca95d1e7bff768b1bab36c530e308a8d56243b50c7"
@@ -1250,6 +1268,22 @@
pruneopts = "NUT"
revision = "08470befbe04613bd4b44cb6978b05d50294c4d4"
+[[projects]]
+ digest = "1:417193ba917954c4837c6fc48c6ac241b3fefd13fc0889367b4a7e43b69d582c"
+ name = "github.com/nrdcg/auroradns"
+ packages = ["."]
+ pruneopts = "NUT"
+ revision = "750ca8603f9f2cca2457acb22ea6e44d3f05358c"
+ version = "v1.0.0"
+
+[[projects]]
+ digest = "1:78bc7967454f8c2a1257b3fe4ed1d57227fb1dff46fd74d38b91bd0d0cc2620a"
+ name = "github.com/nrdcg/goinwx"
+ packages = ["."]
+ pruneopts = "NUT"
+ revision = "d8152159450570012552f924a0ae6ab3d8c617e0"
+ version = "v0.6.0"
+
[[projects]]
branch = "master"
digest = "1:95d27e49401b61dd203a4cf8237037bd6cd49599651f855ac1988c4ae27b090e"
@@ -1495,14 +1529,6 @@
revision = "a67f783a3814b8729bd2dac5780b5f78f8dbd64d"
version = "v1.1.0"
-[[projects]]
- branch = "master"
- digest = "1:94fcec8ba983a96bb3f123ab9690955baf44d6d32de342f95f5a2f665c70457a"
- name = "github.com/smueller18/goinwx"
- packages = ["."]
- pruneopts = "NUT"
- revision = "5d138389109eca96463f44f692408f0d1c731278"
-
[[projects]]
digest = "1:f709d7d110053ada282e5ab1eabcb3581bcf3aaa27d22873ab8d8291c7a474bb"
name = "github.com/spf13/pflag"
@@ -1576,14 +1602,6 @@
pruneopts = "NUT"
revision = "1dc93a7db3567a5ccf865106afac88278ba940cf"
-[[projects]]
- branch = "master"
- digest = "1:0aee615b3628dca9530ea6aba07c0ff5ed74ab1d01f6fe6d33c0137d14d44ef0"
- name = "github.com/tuvistavie/securerandom"
- packages = ["."]
- pruneopts = "NUT"
- revision = "15512123a948d62f6361bd84818e11f2ad84059a"
-
[[projects]]
digest = "1:9b2996458a2f7d1f3e0ebf08152acfe8c1106f3fe855d08121c5ee7d801a063f"
name = "github.com/tv42/zbase32"
@@ -1705,7 +1723,7 @@
revision = "0c8571ac0ce161a5feb57375a9cdf148c98c0f70"
[[projects]]
- digest = "1:245afb86e7f760a9ccdfaa4ee052f2af723bdfab5fbeb257fc5d3aebf94a0bd2"
+ digest = "1:2a8f3a3c32e8a622b4053f0653c1e172b0480e75493636d75f0324b77c620805"
name = "github.com/xenolf/lego"
packages = [
"acme",
@@ -1735,6 +1753,7 @@
"providers/dns/cloudxns/internal",
"providers/dns/conoha",
"providers/dns/conoha/internal",
+ "providers/dns/designate",
"providers/dns/digitalocean",
"providers/dns/dnsimple",
"providers/dns/dnsmadeeasy",
@@ -1785,8 +1804,8 @@
"registration",
]
pruneopts = "NUT"
- revision = "00ad82dec10ad0af1e037ee652ad1f1cc7015178"
- version = "v2.1.0"
+ revision = "52e43eb318b07ace590c2144804292c89aa74802"
+ version = "v2.2.0"
[[projects]]
branch = "master"
@@ -2001,14 +2020,6 @@
revision = "5b3e00af70a9484542169a976dcab8d03e601a17"
version = "v1.30.0"
-[[projects]]
- branch = "v1"
- digest = "1:1cc3aaf6317bf5da2659ab96c517e243951b61a5d5fe3bd8255c6260fbad08ee"
- name = "gopkg.in/mattes/go-expand-tilde.v1"
- packages = ["."]
- pruneopts = "NUT"
- revision = "cb884138e64c9a8bf5c7d6106d74b0fca082df0c"
-
[[projects]]
branch = "v2"
digest = "1:f57d15cb0f61c985f61fd2c529245debb439128ecc8c44c06251900d281c68fc"
diff --git a/Gopkg.toml b/Gopkg.toml
index c42ec9c2b..54a47ab9e 100644
--- a/Gopkg.toml
+++ b/Gopkg.toml
@@ -178,9 +178,8 @@
name = "github.com/vulcand/oxy"
[[constraint]]
-# branch = "master"
name = "github.com/xenolf/lego"
- version = "2.1.0"
+ version = "2.2.0"
[[constraint]]
name = "google.golang.org/grpc"
diff --git a/docs/configuration/acme.md b/docs/configuration/acme.md
index 7b53a2f38..cc004443f 100644
--- a/docs/configuration/acme.md
+++ b/docs/configuration/acme.md
@@ -271,61 +271,63 @@ Useful if internal networks block external DNS queries.
##### `provider`
-Here is a list of supported `provider`s, that can automate the DNS verification, along with the required environment variables and their [wildcard & root domain support](/configuration/acme/#wildcard-domains) for each. Do not hesitate to complete it.
+Here is a list of supported `provider`s, that can automate the DNS verification, along with the required environment variables and their [wildcard & root domain support](/configuration/acme/#wildcard-domains) for each.
+Do not hesitate to complete it.
-| Provider Name | Provider Code | Environment Variables | Wildcard & Root Domain Support |
-|--------------------------------------------------------|----------------|-------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------|
-| [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` | YES |
-| [DNS Made Easy](https://dnsmadeeasy.com) | `dnsmadeeasy` | `DNSMADEEASY_API_KEY`, `DNSMADEEASY_API_SECRET`, `DNSMADEEASY_SANDBOX` | 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` | YES |
-| [Dyn](https://dyn.com) | `dyn` | `DYN_CUSTOMER_NAME`, `DYN_USER_NAME`, `DYN_PASSWORD` | 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 |
-| [Gandi v5](http://doc.livedns.gandi.net) | `gandiv5` | `GANDIV5_API_KEY` | YES |
-| [Glesys](https://glesys.com/) | `glesys` | `GLESYS_API_USER`, `GLESYS_API_KEY`, `GLESYS_DOMAIN` | Not tested yet |
-| [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`, Application Default Credentials (2) (3), [`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` (1) | 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 |
-| [NIFCloud](https://cloud.nifty.com/service/dns.htm) | `nifcloud` | `NIFCLOUD_ACCESS_KEY_ID`, `NIFCLOUD_SECRET_ACCESS_KEY` | Not tested yet |
-| [Ns1](https://ns1.com/) | `ns1` | `NS1_API_KEY` | Not tested yet |
-| [Open Telekom Cloud](https://cloud.telekom.de) | `otc` | `OTC_DOMAIN_NAME`, `OTC_USER_NAME`, `OTC_PASSWORD`, `OTC_PROJECT_NAME`, `OTC_IDENTITY_ENDPOINT` | Not tested yet |
-| [OVH](https://www.ovh.com) | `ovh` | `OVH_ENDPOINT`, `OVH_APPLICATION_KEY`, `OVH_APPLICATION_SECRET`, `OVH_CONSUMER_KEY` | YES |
-| [PowerDNS](https://www.powerdns.com) | `pdns` | `PDNS_API_KEY`, `PDNS_API_URL` | Not tested yet |
-| [Rackspace](https://www.rackspace.com/cloud/dns) | `rackspace` | `RACKSPACE_USER`, `RACKSPACE_API_KEY` | Not tested yet |
-| [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 |
-| [Zone.ee](https://www.zone.ee) | `zoneee` | `ZONEEE_API_USER`, `ZONEEE_API_KEY` | YES |
+| Provider Name | Provider Code | Environment Variables | Wildcard & Root Domain Support |
+|-------------------------------------------------------------|----------------|-------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------|
+| [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` | YES |
+| [DNS Made Easy](https://dnsmadeeasy.com) | `dnsmadeeasy` | `DNSMADEEASY_API_KEY`, `DNSMADEEASY_API_SECRET`, `DNSMADEEASY_SANDBOX` | 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` | YES |
+| [Dyn](https://dyn.com) | `dyn` | `DYN_CUSTOMER_NAME`, `DYN_USER_NAME`, `DYN_PASSWORD` | 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` | YES |
+| [Gandi](https://www.gandi.net) | `gandi` | `GANDI_API_KEY` | Not tested yet |
+| [Gandi v5](http://doc.livedns.gandi.net) | `gandiv5` | `GANDIV5_API_KEY` | YES |
+| [Glesys](https://glesys.com/) | `glesys` | `GLESYS_API_USER`, `GLESYS_API_KEY`, `GLESYS_DOMAIN` | Not tested yet |
+| [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`, Application Default Credentials (2) (3), [`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` (1) | 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 |
+| [NIFCloud](https://cloud.nifty.com/service/dns.htm) | `nifcloud` | `NIFCLOUD_ACCESS_KEY_ID`, `NIFCLOUD_SECRET_ACCESS_KEY` | Not tested yet |
+| [Ns1](https://ns1.com/) | `ns1` | `NS1_API_KEY` | Not tested yet |
+| [Open Telekom Cloud](https://cloud.telekom.de) | `otc` | `OTC_DOMAIN_NAME`, `OTC_USER_NAME`, `OTC_PASSWORD`, `OTC_PROJECT_NAME`, `OTC_IDENTITY_ENDPOINT` | Not tested yet |
+| [OVH](https://www.ovh.com) | `ovh` | `OVH_ENDPOINT`, `OVH_APPLICATION_KEY`, `OVH_APPLICATION_SECRET`, `OVH_CONSUMER_KEY` | YES |
+| [Openstack Designate](https://docs.openstack.org/designate) | `designate` | `OS_AUTH_URL`, `OS_USERNAME`, `OS_PASSWORD`, `OS_TENANT_NAME`, `OS_REGION_NAME` | YES |
+| [PowerDNS](https://www.powerdns.com) | `pdns` | `PDNS_API_KEY`, `PDNS_API_URL` | Not tested yet |
+| [Rackspace](https://www.rackspace.com/cloud/dns) | `rackspace` | `RACKSPACE_USER`, `RACKSPACE_API_KEY` | Not tested yet |
+| [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 |
+| [Zone.ee](https://www.zone.ee) | `zoneee` | `ZONEEE_API_USER`, `ZONEEE_API_KEY` | YES |
- (1): more information about the HTTP message format can be found [here](https://github.com/xenolf/lego/blob/master/providers/dns/httpreq/readme.md)
- (2): https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application
diff --git a/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/client-v1/client.go b/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/client-v1/client.go
index b02dba76a..abbbcd4bb 100644
--- a/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/client-v1/client.go
+++ b/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/client-v1/client.go
@@ -6,8 +6,11 @@ import (
"errors"
"io"
"io/ioutil"
+ "mime/multipart"
"net/http"
"net/url"
+ "os"
+ "path/filepath"
"runtime"
"strings"
@@ -16,7 +19,7 @@ import (
)
var (
- libraryVersion = "0.6.0"
+ libraryVersion = "0.6.2"
// UserAgent is the User-Agent value sent for all requests
UserAgent = "Akamai-Open-Edgegrid-golang/" + libraryVersion + " golang/" + strings.TrimPrefix(runtime.Version(), "go")
// Client is the *http.Client to use
@@ -61,13 +64,21 @@ func NewRequest(config edgegrid.Config, method, path string, body io.Reader) (*h
// NewJSONRequest creates an HTTP request that can be sent to the Akamai APIs with a JSON body
// The JSON body is encoded and the Content-Type/Accept headers are set automatically.
func NewJSONRequest(config edgegrid.Config, method, path string, body interface{}) (*http.Request, error) {
- jsonBody, err := jsonhooks.Marshal(body)
- if err != nil {
- return nil, err
+ var req *http.Request
+ var err error
+
+ if body != nil {
+ jsonBody, err := jsonhooks.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+
+ buf := bytes.NewReader(jsonBody)
+ req, err = NewRequest(config, method, path, buf)
+ } else {
+ req, err = NewRequest(config, method, path, nil)
}
- buf := bytes.NewReader(jsonBody)
- req, err := NewRequest(config, method, path, buf)
if err != nil {
return nil, err
}
@@ -78,6 +89,36 @@ func NewJSONRequest(config edgegrid.Config, method, path string, body interface{
return req, nil
}
+// NewMultiPartFormDataRequest creates an HTTP request that uploads a file to the Akamai API
+func NewMultiPartFormDataRequest(config edgegrid.Config, uriPath, filePath string, otherFormParams map[string]string) (*http.Request, error) {
+ file, err := os.Open(filePath)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+
+ body := &bytes.Buffer{}
+ writer := multipart.NewWriter(body)
+ // TODO: make this field name configurable
+ part, err := writer.CreateFormFile("importFile", filepath.Base(filePath))
+ if err != nil {
+ return nil, err
+ }
+ _, err = io.Copy(part, file)
+
+ for key, val := range otherFormParams {
+ _ = writer.WriteField(key, val)
+ }
+ err = writer.Close()
+ if err != nil {
+ return nil, err
+ }
+
+ req, err := NewRequest(config, "POST", uriPath, body)
+ req.Header.Set("Content-Type", writer.FormDataContentType())
+ return req, err
+}
+
// Do performs a given HTTP Request, signed with the Akamai OPEN Edgegrid
// Authorization header. An edgegrid.Response or an error is returned.
func Do(config edgegrid.Config, req *http.Request) (*http.Response, error) {
diff --git a/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/client-v1/errors.go b/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/client-v1/errors.go
index c679963f4..05b852473 100644
--- a/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/client-v1/errors.go
+++ b/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/client-v1/errors.go
@@ -12,22 +12,42 @@ import (
// APIError exposes an Akamai OPEN Edgegrid Error
type APIError struct {
error
- Type string `json:"type"`
- Title string `json:"title"`
- Status int `json:"status"`
- Detail string `json:"detail"`
- Instance string `json:"instance"`
- Method string `json:"method"`
- ServerIP string `json:"serverIp"`
- ClientIP string `json:"clientIp"`
- RequestID string `json:"requestId"`
- RequestTime string `json:"requestTime"`
- Response *http.Response `json:"-"`
- RawBody string `json:"-"`
+ Type string `json:"type"`
+ Title string `json:"title"`
+ Status int `json:"status"`
+ Detail string `json:"detail"`
+ Errors []APIErrorDetail `json:"errors"`
+ Problems []APIErrorDetail `json:"problems"`
+ Instance string `json:"instance"`
+ Method string `json:"method"`
+ ServerIP string `json:"serverIp"`
+ ClientIP string `json:"clientIp"`
+ RequestID string `json:"requestId"`
+ RequestTime string `json:"requestTime"`
+ Response *http.Response `json:"-"`
+ RawBody string `json:"-"`
+}
+
+type APIErrorDetail struct {
+ Type string `json:"type"`
+ Title string `json:"title"`
+ Detail string `json:"detail"`
+ RejectedValue string `json:"rejectedValue"`
}
func (error APIError) Error() string {
- return strings.TrimSpace(fmt.Sprintf("API Error: %d %s %s More Info %s", error.Status, error.Title, error.Detail, error.Type))
+ var errorDetails string
+ if len(error.Errors) > 0 {
+ for _, e := range error.Errors {
+ errorDetails = fmt.Sprintf("%s \n %s", errorDetails, e)
+ }
+ }
+ if len(error.Problems) > 0 {
+ for _, e := range error.Problems {
+ errorDetails = fmt.Sprintf("%s \n %s", errorDetails, e)
+ }
+ }
+ return strings.TrimSpace(fmt.Sprintf("API Error: %d %s %s More Info %s\n %s", error.Status, error.Title, error.Detail, error.Type, errorDetails))
}
// NewAPIError creates a new API error based on a Response,
@@ -45,7 +65,6 @@ func NewAPIError(response *http.Response) APIError {
// other purposes.
func NewAPIErrorFromBody(response *http.Response, body []byte) APIError {
error := APIError{}
-
if err := jsonhooks.Unmarshal(body, &error); err == nil {
error.Status = response.StatusCode
error.Title = response.Status
diff --git a/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v1/record.go b/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v1/record.go
index 1c2dcb48e..7a32fe6b7 100644
--- a/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v1/record.go
+++ b/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v1/record.go
@@ -1323,15 +1323,16 @@ func (record *RrsigRecord) ToMap() map[string]interface{} {
}
type SoaRecord struct {
- fieldMap []string `json:"-"`
- TTL int `json:"ttl,omitempty"`
- Originserver string `json:"originserver,omitempty"`
- Contact string `json:"contact,omitempty"`
- Serial uint `json:"serial,omitempty"`
- Refresh int `json:"refresh,omitempty"`
- Retry int `json:"retry,omitempty"`
- Expire int `json:"expire,omitempty"`
- Minimum uint `json:"minimum,omitempty"`
+ fieldMap []string `json:"-"`
+ originalSerial uint `json:"-"`
+ TTL int `json:"ttl,omitempty"`
+ Originserver string `json:"originserver,omitempty"`
+ Contact string `json:"contact,omitempty"`
+ Serial uint `json:"serial,omitempty"`
+ Refresh int `json:"refresh,omitempty"`
+ Retry int `json:"retry,omitempty"`
+ Expire int `json:"expire,omitempty"`
+ Minimum uint `json:"minimum,omitempty"`
}
func NewSoaRecord() *SoaRecord {
diff --git a/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v1/zone.go b/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v1/zone.go
index 355f81843..65da7bc78 100644
--- a/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v1/zone.go
+++ b/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v1/zone.go
@@ -82,7 +82,7 @@ func GetZone(hostname string) (*Zone, error) {
} else if res.StatusCode == 404 {
return nil, &ZoneError{zoneName: hostname}
} else {
- err = client.BodyJSON(res, &zone)
+ err = client.BodyJSON(res, zone)
if err != nil {
return nil, err
}
@@ -762,11 +762,18 @@ func (zone *Zone) removeTxtRecord(record *TxtRecord) error {
return errors.New("Txt Record not found")
}
-func (zone *Zone) PreMarshalJSON() error {
+func (zone *Zone) PostUnmarshalJSON() error {
if zone.Zone.Soa.Serial > 0 {
- zone.Zone.Soa.Serial = zone.Zone.Soa.Serial + 1
- } else {
+ zone.Zone.Soa.originalSerial = zone.Zone.Soa.Serial
+ }
+ return nil
+}
+
+func (zone *Zone) PreMarshalJSON() error {
+ if zone.Zone.Soa.Serial == 0 {
zone.Zone.Soa.Serial = uint(time.Now().Unix())
+ } else if zone.Zone.Soa.Serial == zone.Zone.Soa.originalSerial {
+ zone.Zone.Soa.Serial = zone.Zone.Soa.Serial + 1
}
return nil
}
@@ -786,21 +793,24 @@ func (zone *Zone) validateCnames() (bool, []name) {
}
func (zone *Zone) removeCnameName(host string) {
- for i, v := range cnameNames {
- if v.name == host {
- r := cnameNames[:i]
- cnameNames = append(r, cnameNames[i+1:]...)
+ var ncn []name
+ for _, v := range cnameNames {
+ if v.name != host {
+ ncn =append(ncn, v)
}
}
+ cnameNames = ncn
}
+
func (zone *Zone) removeNonCnameName(host string) {
- for i, v := range nonCnameNames {
- if v.name == host {
- r := nonCnameNames[:i]
- nonCnameNames = append(r, nonCnameNames[i+1:]...)
+ var ncn []name
+ for _, v := range nonCnameNames {
+ if v.name != host {
+ ncn =append(ncn, v)
}
}
+ nonCnameNames = ncn
}
func (zone *Zone) FindRecords(recordType string, options map[string]interface{}) []DNSRecord {
diff --git a/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid/config.go b/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid/config.go
index b9f5411a5..35a6719c4 100644
--- a/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid/config.go
+++ b/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid/config.go
@@ -7,7 +7,7 @@ import (
"strings"
"github.com/go-ini/ini"
- "gopkg.in/mattes/go-expand-tilde.v1"
+ "github.com/mitchellh/go-homedir"
)
// Config struct provides all the necessary fields to
@@ -86,7 +86,7 @@ func InitEdgeRc(filepath string, section string) (Config, error) {
// Tilde seems to be not working when passing ~/.edgerc as file
// Takes current user and use home dir instead
- path, err := tilde.Expand(filepath)
+ path, err := homedir.Expand(filepath)
if err != nil {
return c, fmt.Errorf(errorMap[ErrHomeDirNotFound], err)
diff --git a/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid/signer.go b/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid/signer.go
index b43e4149e..27e6d0659 100644
--- a/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid/signer.go
+++ b/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid/signer.go
@@ -14,8 +14,8 @@ import (
"time"
"unicode"
+ "github.com/google/uuid"
log "github.com/sirupsen/logrus"
- "github.com/tuvistavie/securerandom"
)
const defaultSection = "DEFAULT"
@@ -49,12 +49,12 @@ func makeEdgeTimeStamp() string {
// It is a random string used to detect replayed request messages.
// A GUID is recommended.
func createNonce() string {
- uuid, err := securerandom.Uuid()
+ uuid, err := uuid.NewRandom()
if err != nil {
log.Errorf(errorMap[ErrUUIDGenerateFailed], err)
return ""
}
- return uuid
+ return uuid.String()
}
func stringMinifier(in string) (out string) {
diff --git a/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/jsonhooks-v1/jsonhooks.go b/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/jsonhooks-v1/jsonhooks.go
index 83dccd5dd..24c5b8bb8 100644
--- a/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/jsonhooks-v1/jsonhooks.go
+++ b/vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/jsonhooks-v1/jsonhooks.go
@@ -44,7 +44,7 @@ type PreJSONMarshaler interface {
// ImplementsPreJSONMarshaler checks for support for the PreMarshalJSON pre-hook
func ImplementsPreJSONMarshaler(v interface{}) bool {
value := reflect.ValueOf(v)
- if value.Kind() == reflect.Ptr && value.IsNil() {
+ if !value.IsValid() {
return false
}
diff --git a/vendor/github.com/go-ini/ini/file.go b/vendor/github.com/go-ini/ini/file.go
index ce26c3b31..0ed0eafd0 100644
--- a/vendor/github.com/go-ini/ini/file.go
+++ b/vendor/github.com/go-ini/ini/file.go
@@ -45,6 +45,9 @@ type File struct {
// newFile initializes File object with given data sources.
func newFile(dataSources []dataSource, opts LoadOptions) *File {
+ if len(opts.KeyValueDelimiters) == 0 {
+ opts.KeyValueDelimiters = "=:"
+ }
return &File{
BlockMode: true,
dataSources: dataSources,
@@ -140,9 +143,14 @@ func (f *File) Section(name string) *Section {
// Section returns list of Section.
func (f *File) Sections() []*Section {
+ if f.BlockMode {
+ f.lock.RLock()
+ defer f.lock.RUnlock()
+ }
+
sections := make([]*Section, len(f.sectionList))
- for i := range f.sectionList {
- sections[i] = f.Section(f.sectionList[i])
+ for i, name := range f.sectionList {
+ sections[i] = f.sections[name]
}
return sections
}
@@ -222,8 +230,9 @@ func (f *File) Append(source interface{}, others ...interface{}) error {
}
func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
- equalSign := "="
- if PrettyFormat {
+ equalSign := DefaultFormatLeft + "=" + DefaultFormatRight
+
+ if PrettyFormat || PrettyEqual {
equalSign = " = "
}
@@ -232,13 +241,18 @@ func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
for i, sname := range f.sectionList {
sec := f.Section(sname)
if len(sec.Comment) > 0 {
- if sec.Comment[0] != '#' && sec.Comment[0] != ';' {
- sec.Comment = "; " + sec.Comment
- } else {
- sec.Comment = sec.Comment[:1] + " " + strings.TrimSpace(sec.Comment[1:])
- }
- if _, err := buf.WriteString(sec.Comment + LineBreak); err != nil {
- return nil, err
+ // Support multiline comments
+ lines := strings.Split(sec.Comment, LineBreak)
+ for i := range lines {
+ if lines[i][0] != '#' && lines[i][0] != ';' {
+ lines[i] = "; " + lines[i]
+ } else {
+ lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
+ }
+
+ if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
+ return nil, err
+ }
}
}
@@ -275,7 +289,7 @@ func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
for _, kname := range sec.keyList {
keyLength := len(kname)
// First case will surround key by ` and second by """
- if strings.ContainsAny(kname, "\"=:") {
+ if strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters) {
keyLength += 2
} else if strings.Contains(kname, "`") {
keyLength += 6
@@ -295,13 +309,19 @@ func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
if len(indent) > 0 && sname != DEFAULT_SECTION {
buf.WriteString(indent)
}
- if key.Comment[0] != '#' && key.Comment[0] != ';' {
- key.Comment = "; " + key.Comment
- } else {
- key.Comment = key.Comment[:1] + " " + strings.TrimSpace(key.Comment[1:])
- }
- if _, err := buf.WriteString(key.Comment + LineBreak); err != nil {
- return nil, err
+
+ // Support multiline comments
+ lines := strings.Split(key.Comment, LineBreak)
+ for i := range lines {
+ if lines[i][0] != '#' && lines[i][0] != ';' {
+ lines[i] = "; " + strings.TrimSpace(lines[i])
+ } else {
+ lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
+ }
+
+ if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
+ return nil, err
+ }
}
}
@@ -312,7 +332,7 @@ func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
switch {
case key.isAutoIncrement:
kname = "-"
- case strings.ContainsAny(kname, "\"=:"):
+ case strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters):
kname = "`" + kname + "`"
case strings.Contains(kname, "`"):
kname = `"""` + kname + `"""`
diff --git a/vendor/github.com/go-ini/ini/ini.go b/vendor/github.com/go-ini/ini/ini.go
index 535d3588a..93424f671 100644
--- a/vendor/github.com/go-ini/ini/ini.go
+++ b/vendor/github.com/go-ini/ini/ini.go
@@ -1,3 +1,5 @@
+// +build go1.6
+
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
@@ -32,7 +34,7 @@ const (
// Maximum allowed depth when recursively substituing variable names.
_DEPTH_VALUES = 99
- _VERSION = "1.32.0"
+ _VERSION = "1.41.0"
)
// Version returns current package version literal.
@@ -46,6 +48,10 @@ var (
// at package init time.
LineBreak = "\n"
+ // Place custom spaces when PrettyFormat and PrettyEqual are both disabled
+ DefaultFormatLeft = ""
+ DefaultFormatRight = ""
+
// Variable regexp pattern: %(variable)s
varPattern = regexp.MustCompile(`%\(([^\)]+)\)s`)
@@ -53,6 +59,9 @@ var (
// or reduce all possible spaces for compact format.
PrettyFormat = true
+ // Place spaces around "=" sign even when PrettyFormat is false
+ PrettyEqual = false
+
// Explicitly write DEFAULT section header
DefaultHeader = false
@@ -129,6 +138,8 @@ type LoadOptions struct {
IgnoreContinuation bool
// IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value.
IgnoreInlineComment bool
+ // SkipUnrecognizableLines indicates whether to skip unrecognizable lines that do not conform to key/value pairs.
+ SkipUnrecognizableLines bool
// AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing.
// This type of keys are mostly used in my.cnf.
AllowBooleanKeys bool
@@ -137,6 +148,16 @@ type LoadOptions struct {
// AllowNestedValues indicates whether to allow AWS-like nested values.
// Docs: http://docs.aws.amazon.com/cli/latest/topic/config-vars.html#nested-values
AllowNestedValues bool
+ // AllowPythonMultilineValues indicates whether to allow Python-like multi-line values.
+ // Docs: https://docs.python.org/3/library/configparser.html#supported-ini-file-structure
+ // Relevant quote: Values can also span multiple lines, as long as they are indented deeper
+ // than the first line of the value.
+ AllowPythonMultilineValues bool
+ // SpaceBeforeInlineComment indicates whether to allow comment symbols (\# and \;) inside value.
+ // Docs: https://docs.python.org/2/library/configparser.html
+ // Quote: Comments may appear on their own in an otherwise empty line, or may be entered in lines holding values or section names.
+ // In the latter case, they need to be preceded by a whitespace character to be recognized as a comment.
+ SpaceBeforeInlineComment bool
// UnescapeValueDoubleQuotes indicates whether to unescape double quotes inside value to regular format
// when value is surrounded by double quotes, e.g. key="a \"value\"" => key=a "value"
UnescapeValueDoubleQuotes bool
@@ -144,9 +165,11 @@ type LoadOptions struct {
// when value is NOT surrounded by any quotes.
// Note: UNSTABLE, behavior might change to only unescape inside double quotes but may noy necessary at all.
UnescapeValueCommentSymbols bool
- // Some INI formats allow group blocks that store a block of raw content that doesn't otherwise
+ // UnparseableSections stores a list of blocks that are allowed with raw content which do not otherwise
// conform to key/value pairs. Specify the names of those blocks here.
UnparseableSections []string
+ // KeyValueDelimiters is the sequence of delimiters that are used to separate key and value. By default, it is "=:".
+ KeyValueDelimiters string
}
func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) {
@@ -187,7 +210,7 @@ func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) {
return LoadSources(LoadOptions{Insensitive: true}, source, others...)
}
-// InsensitiveLoad has exactly same functionality as Load function
+// ShadowLoad has exactly same functionality as Load function
// except it allows have shadow keys.
func ShadowLoad(source interface{}, others ...interface{}) (*File, error) {
return LoadSources(LoadOptions{AllowShadows: true}, source, others...)
diff --git a/vendor/github.com/go-ini/ini/key.go b/vendor/github.com/go-ini/ini/key.go
index 7c8566a1b..0fee0dc7e 100644
--- a/vendor/github.com/go-ini/ini/key.go
+++ b/vendor/github.com/go-ini/ini/key.go
@@ -133,8 +133,7 @@ func (k *Key) transformValue(val string) string {
}
// Take off leading '%(' and trailing ')s'.
- noption := strings.TrimLeft(vr, "%(")
- noption = strings.TrimRight(noption, ")s")
+ noption := vr[2 : len(vr)-2]
// Search in the same section.
nk, err := k.s.GetKey(noption)
@@ -187,23 +186,24 @@ func (k *Key) Float64() (float64, error) {
// Int returns int type value.
func (k *Key) Int() (int, error) {
- return strconv.Atoi(k.String())
+ v, err := strconv.ParseInt(k.String(), 0, 64)
+ return int(v), err
}
// Int64 returns int64 type value.
func (k *Key) Int64() (int64, error) {
- return strconv.ParseInt(k.String(), 10, 64)
+ return strconv.ParseInt(k.String(), 0, 64)
}
// Uint returns uint type valued.
func (k *Key) Uint() (uint, error) {
- u, e := strconv.ParseUint(k.String(), 10, 64)
+ u, e := strconv.ParseUint(k.String(), 0, 64)
return uint(u), e
}
// Uint64 returns uint64 type value.
func (k *Key) Uint64() (uint64, error) {
- return strconv.ParseUint(k.String(), 10, 64)
+ return strconv.ParseUint(k.String(), 0, 64)
}
// Duration returns time.Duration type value.
@@ -668,7 +668,8 @@ func (k *Key) parseFloat64s(strs []string, addInvalid, returnOnInvalid bool) ([]
func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int, error) {
vals := make([]int, 0, len(strs))
for _, str := range strs {
- val, err := strconv.Atoi(str)
+ valInt64, err := strconv.ParseInt(str, 0, 64)
+ val := int(valInt64)
if err != nil && returnOnInvalid {
return nil, err
}
@@ -683,7 +684,7 @@ func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int,
func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]int64, error) {
vals := make([]int64, 0, len(strs))
for _, str := range strs {
- val, err := strconv.ParseInt(str, 10, 64)
+ val, err := strconv.ParseInt(str, 0, 64)
if err != nil && returnOnInvalid {
return nil, err
}
@@ -698,7 +699,7 @@ func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]in
func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uint, error) {
vals := make([]uint, 0, len(strs))
for _, str := range strs {
- val, err := strconv.ParseUint(str, 10, 0)
+ val, err := strconv.ParseUint(str, 0, 0)
if err != nil && returnOnInvalid {
return nil, err
}
@@ -713,7 +714,7 @@ func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uin
func (k *Key) parseUint64s(strs []string, addInvalid, returnOnInvalid bool) ([]uint64, error) {
vals := make([]uint64, 0, len(strs))
for _, str := range strs {
- val, err := strconv.ParseUint(str, 10, 64)
+ val, err := strconv.ParseUint(str, 0, 64)
if err != nil && returnOnInvalid {
return nil, err
}
diff --git a/vendor/github.com/go-ini/ini/parser.go b/vendor/github.com/go-ini/ini/parser.go
index db3af8f00..24cf11c73 100644
--- a/vendor/github.com/go-ini/ini/parser.go
+++ b/vendor/github.com/go-ini/ini/parser.go
@@ -19,11 +19,14 @@ import (
"bytes"
"fmt"
"io"
+ "regexp"
"strconv"
"strings"
"unicode"
)
+var pythonMultiline = regexp.MustCompile("^(\\s+)([^\n]+)")
+
type tokenType int
const (
@@ -97,7 +100,7 @@ func cleanComment(in []byte) ([]byte, bool) {
return in[i:], true
}
-func readKeyName(in []byte) (string, int, error) {
+func readKeyName(delimiters string, in []byte) (string, int, error) {
line := string(in)
// Check if key name surrounded by quotes.
@@ -124,7 +127,7 @@ func readKeyName(in []byte) (string, int, error) {
pos += startIdx
// Find key-value delimiter
- i := strings.IndexAny(line[pos+startIdx:], "=:")
+ i := strings.IndexAny(line[pos+startIdx:], delimiters)
if i < 0 {
return "", -1, ErrDelimiterNotFound{line}
}
@@ -132,7 +135,7 @@ func readKeyName(in []byte) (string, int, error) {
return strings.TrimSpace(line[startIdx:pos]), endIdx + startIdx + 1, nil
}
- endIdx = strings.IndexAny(line, "=:")
+ endIdx = strings.IndexAny(line, delimiters)
if endIdx < 0 {
return "", -1, ErrDelimiterNotFound{line}
}
@@ -194,7 +197,8 @@ func hasSurroundedQuote(in string, quote byte) bool {
}
func (p *parser) readValue(in []byte,
- ignoreContinuation, ignoreInlineComment, unescapeValueDoubleQuotes, unescapeValueCommentSymbols bool) (string, error) {
+ parserBufferSize int,
+ ignoreContinuation, ignoreInlineComment, unescapeValueDoubleQuotes, unescapeValueCommentSymbols, allowPythonMultilines, spaceBeforeInlineComment bool) (string, error) {
line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
if len(line) == 0 {
@@ -224,21 +228,34 @@ func (p *parser) readValue(in []byte,
return line[startIdx : pos+startIdx], nil
}
+ lastChar := line[len(line)-1]
// Won't be able to reach here if value only contains whitespace
line = strings.TrimSpace(line)
+ trimmedLastChar := line[len(line)-1]
// Check continuation lines when desired
- if !ignoreContinuation && line[len(line)-1] == '\\' {
+ if !ignoreContinuation && trimmedLastChar == '\\' {
return p.readContinuationLines(line[:len(line)-1])
}
// Check if ignore inline comment
if !ignoreInlineComment {
- i := strings.IndexAny(line, "#;")
+ var i int
+ if spaceBeforeInlineComment {
+ i = strings.Index(line, " #")
+ if i == -1 {
+ i = strings.Index(line, " ;")
+ }
+
+ } else {
+ i = strings.IndexAny(line, "#;")
+ }
+
if i > -1 {
p.comment.WriteString(line[i:])
line = strings.TrimSpace(line[:i])
}
+
}
// Trim single and double quotes
@@ -252,7 +269,42 @@ func (p *parser) readValue(in []byte,
if strings.Contains(line, `\#`) {
line = strings.Replace(line, `\#`, "#", -1)
}
+ } else if allowPythonMultilines && lastChar == '\n' {
+ parserBufferPeekResult, _ := p.buf.Peek(parserBufferSize)
+ peekBuffer := bytes.NewBuffer(parserBufferPeekResult)
+
+ val := line
+
+ for {
+ peekData, peekErr := peekBuffer.ReadBytes('\n')
+ if peekErr != nil {
+ if peekErr == io.EOF {
+ return val, nil
+ }
+ return "", peekErr
+ }
+
+ peekMatches := pythonMultiline.FindStringSubmatch(string(peekData))
+ if len(peekMatches) != 3 {
+ return val, nil
+ }
+
+ // NOTE: Return if not a python-ini multi-line value.
+ currentIdentSize := len(peekMatches[1])
+ if currentIdentSize <= 0 {
+ return val, nil
+ }
+
+ // NOTE: Just advance the parser reader (buffer) in-sync with the peek buffer.
+ _, err := p.readUntil('\n')
+ if err != nil {
+ return "", err
+ }
+
+ val += fmt.Sprintf("\n%s", peekMatches[2])
+ }
}
+
return line, nil
}
@@ -276,6 +328,28 @@ func (f *File) parse(reader io.Reader) (err error) {
var line []byte
var inUnparseableSection bool
+
+ // NOTE: Iterate and increase `currentPeekSize` until
+ // the size of the parser buffer is found.
+ // TODO(unknwon): When Golang 1.10 is the lowest version supported, replace with `parserBufferSize := p.buf.Size()`.
+ parserBufferSize := 0
+ // NOTE: Peek 1kb at a time.
+ currentPeekSize := 1024
+
+ if f.options.AllowPythonMultilineValues {
+ for {
+ peekBytes, _ := p.buf.Peek(currentPeekSize)
+ peekBytesLength := len(peekBytes)
+
+ if parserBufferSize >= peekBytesLength {
+ break
+ }
+
+ currentPeekSize *= 2
+ parserBufferSize = peekBytesLength
+ }
+ }
+
for !p.isEOF {
line, err = p.readUntil('\n')
if err != nil {
@@ -307,8 +381,7 @@ func (f *File) parse(reader io.Reader) (err error) {
// Section
if line[0] == '[' {
// Read to the next ']' (TODO: support quoted strings)
- // TODO(unknwon): use LastIndexByte when stop supporting Go1.4
- closeIdx := bytes.LastIndex(line, []byte("]"))
+ closeIdx := bytes.LastIndexByte(line, ']')
if closeIdx == -1 {
return fmt.Errorf("unclosed section: %s", line)
}
@@ -347,25 +420,34 @@ func (f *File) parse(reader io.Reader) (err error) {
continue
}
- kname, offset, err := readKeyName(line)
+ kname, offset, err := readKeyName(f.options.KeyValueDelimiters, line)
if err != nil {
// Treat as boolean key when desired, and whole line is key name.
- if IsErrDelimiterNotFound(err) && f.options.AllowBooleanKeys {
- kname, err := p.readValue(line,
- f.options.IgnoreContinuation,
- f.options.IgnoreInlineComment,
- f.options.UnescapeValueDoubleQuotes,
- f.options.UnescapeValueCommentSymbols)
- if err != nil {
- return err
+ if IsErrDelimiterNotFound(err) {
+ switch {
+ case f.options.AllowBooleanKeys:
+ kname, err := p.readValue(line,
+ parserBufferSize,
+ f.options.IgnoreContinuation,
+ f.options.IgnoreInlineComment,
+ f.options.UnescapeValueDoubleQuotes,
+ f.options.UnescapeValueCommentSymbols,
+ f.options.AllowPythonMultilineValues,
+ f.options.SpaceBeforeInlineComment)
+ if err != nil {
+ return err
+ }
+ key, err := section.NewBooleanKey(kname)
+ if err != nil {
+ return err
+ }
+ key.Comment = strings.TrimSpace(p.comment.String())
+ p.comment.Reset()
+ continue
+
+ case f.options.SkipUnrecognizableLines:
+ continue
}
- key, err := section.NewBooleanKey(kname)
- if err != nil {
- return err
- }
- key.Comment = strings.TrimSpace(p.comment.String())
- p.comment.Reset()
- continue
}
return err
}
@@ -379,10 +461,13 @@ func (f *File) parse(reader io.Reader) (err error) {
}
value, err := p.readValue(line[offset:],
+ parserBufferSize,
f.options.IgnoreContinuation,
f.options.IgnoreInlineComment,
f.options.UnescapeValueDoubleQuotes,
- f.options.UnescapeValueCommentSymbols)
+ f.options.UnescapeValueCommentSymbols,
+ f.options.AllowPythonMultilineValues,
+ f.options.SpaceBeforeInlineComment)
if err != nil {
return err
}
diff --git a/vendor/github.com/go-ini/ini/section.go b/vendor/github.com/go-ini/ini/section.go
index d8a402619..bc32c620d 100644
--- a/vendor/github.com/go-ini/ini/section.go
+++ b/vendor/github.com/go-ini/ini/section.go
@@ -82,6 +82,7 @@ func (s *Section) NewKey(name, val string) (*Key, error) {
}
} else {
s.keys[name].value = val
+ s.keysHash[name] = val
}
return s.keys[name], nil
}
@@ -237,6 +238,7 @@ func (s *Section) DeleteKey(name string) {
if k == name {
s.keyList = append(s.keyList[:i], s.keyList[i+1:]...)
delete(s.keys, name)
+ delete(s.keysHash, name)
return
}
}
diff --git a/vendor/github.com/go-ini/ini/struct.go b/vendor/github.com/go-ini/ini/struct.go
index 9719dc698..a9dfed078 100644
--- a/vendor/github.com/go-ini/ini/struct.go
+++ b/vendor/github.com/go-ini/ini/struct.go
@@ -180,7 +180,7 @@ func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim stri
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
durationVal, err := key.Duration()
// Skip zero value
- if err == nil && int(durationVal) > 0 {
+ if err == nil && uint64(durationVal) > 0 {
field.Set(reflect.ValueOf(durationVal))
return nil
}
diff --git a/vendor/github.com/google/uuid/LICENSE b/vendor/github.com/google/uuid/LICENSE
new file mode 100644
index 000000000..5dc68268d
--- /dev/null
+++ b/vendor/github.com/google/uuid/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2009,2014 Google Inc. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/google/uuid/dce.go b/vendor/github.com/google/uuid/dce.go
new file mode 100644
index 000000000..a6479dbae
--- /dev/null
+++ b/vendor/github.com/google/uuid/dce.go
@@ -0,0 +1,80 @@
+// Copyright 2016 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+ "encoding/binary"
+ "fmt"
+ "os"
+)
+
+// A Domain represents a Version 2 domain
+type Domain byte
+
+// Domain constants for DCE Security (Version 2) UUIDs.
+const (
+ Person = Domain(0)
+ Group = Domain(1)
+ Org = Domain(2)
+)
+
+// NewDCESecurity returns a DCE Security (Version 2) UUID.
+//
+// The domain should be one of Person, Group or Org.
+// On a POSIX system the id should be the users UID for the Person
+// domain and the users GID for the Group. The meaning of id for
+// the domain Org or on non-POSIX systems is site defined.
+//
+// For a given domain/id pair the same token may be returned for up to
+// 7 minutes and 10 seconds.
+func NewDCESecurity(domain Domain, id uint32) (UUID, error) {
+ uuid, err := NewUUID()
+ if err == nil {
+ uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
+ uuid[9] = byte(domain)
+ binary.BigEndian.PutUint32(uuid[0:], id)
+ }
+ return uuid, err
+}
+
+// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
+// domain with the id returned by os.Getuid.
+//
+// NewDCEPerson(Person, uint32(os.Getuid()))
+func NewDCEPerson() (UUID, error) {
+ return NewDCESecurity(Person, uint32(os.Getuid()))
+}
+
+// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
+// domain with the id returned by os.Getgid.
+//
+// NewDCEGroup(Group, uint32(os.Getgid()))
+func NewDCEGroup() (UUID, error) {
+ return NewDCESecurity(Group, uint32(os.Getgid()))
+}
+
+// Domain returns the domain for a Version 2 UUID. Domains are only defined
+// for Version 2 UUIDs.
+func (uuid UUID) Domain() Domain {
+ return Domain(uuid[9])
+}
+
+// ID returns the id for a Version 2 UUID. IDs are only defined for Version 2
+// UUIDs.
+func (uuid UUID) ID() uint32 {
+ return binary.BigEndian.Uint32(uuid[0:4])
+}
+
+func (d Domain) String() string {
+ switch d {
+ case Person:
+ return "Person"
+ case Group:
+ return "Group"
+ case Org:
+ return "Org"
+ }
+ return fmt.Sprintf("Domain%d", int(d))
+}
diff --git a/vendor/github.com/google/uuid/doc.go b/vendor/github.com/google/uuid/doc.go
new file mode 100644
index 000000000..5b8a4b9af
--- /dev/null
+++ b/vendor/github.com/google/uuid/doc.go
@@ -0,0 +1,12 @@
+// Copyright 2016 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package uuid generates and inspects UUIDs.
+//
+// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security
+// Services.
+//
+// A UUID is a 16 byte (128 bit) array. UUIDs may be used as keys to
+// maps or compared directly.
+package uuid
diff --git a/vendor/github.com/google/uuid/hash.go b/vendor/github.com/google/uuid/hash.go
new file mode 100644
index 000000000..4fc5a77df
--- /dev/null
+++ b/vendor/github.com/google/uuid/hash.go
@@ -0,0 +1,53 @@
+// Copyright 2016 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+ "crypto/md5"
+ "crypto/sha1"
+ "hash"
+)
+
+// Well known namespace IDs and UUIDs
+var (
+ NameSpaceDNS = Must(Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8"))
+ NameSpaceURL = Must(Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8"))
+ NameSpaceOID = Must(Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8"))
+ NameSpaceX500 = Must(Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8"))
+ Nil UUID // empty UUID, all zeros
+)
+
+// NewHash returns a new UUID derived from the hash of space concatenated with
+// data generated by h. The hash should be at least 16 byte in length. The
+// first 16 bytes of the hash are used to form the UUID. The version of the
+// UUID will be the lower 4 bits of version. NewHash is used to implement
+// NewMD5 and NewSHA1.
+func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
+ h.Reset()
+ h.Write(space[:])
+ h.Write([]byte(data))
+ s := h.Sum(nil)
+ var uuid UUID
+ copy(uuid[:], s)
+ uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
+ uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
+ return uuid
+}
+
+// NewMD5 returns a new MD5 (Version 3) UUID based on the
+// supplied name space and data. It is the same as calling:
+//
+// NewHash(md5.New(), space, data, 3)
+func NewMD5(space UUID, data []byte) UUID {
+ return NewHash(md5.New(), space, data, 3)
+}
+
+// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
+// supplied name space and data. It is the same as calling:
+//
+// NewHash(sha1.New(), space, data, 5)
+func NewSHA1(space UUID, data []byte) UUID {
+ return NewHash(sha1.New(), space, data, 5)
+}
diff --git a/vendor/github.com/google/uuid/marshal.go b/vendor/github.com/google/uuid/marshal.go
new file mode 100644
index 000000000..84bbc5880
--- /dev/null
+++ b/vendor/github.com/google/uuid/marshal.go
@@ -0,0 +1,39 @@
+// Copyright 2016 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import "fmt"
+
+// MarshalText implements encoding.TextMarshaler.
+func (uuid UUID) MarshalText() ([]byte, error) {
+ var js [36]byte
+ encodeHex(js[:], uuid)
+ return js[:], nil
+}
+
+// UnmarshalText implements encoding.TextUnmarshaler.
+func (uuid *UUID) UnmarshalText(data []byte) error {
+ // See comment in ParseBytes why we do this.
+ // id, err := ParseBytes(data)
+ id, err := ParseBytes(data)
+ if err == nil {
+ *uuid = id
+ }
+ return err
+}
+
+// MarshalBinary implements encoding.BinaryMarshaler.
+func (uuid UUID) MarshalBinary() ([]byte, error) {
+ return uuid[:], nil
+}
+
+// UnmarshalBinary implements encoding.BinaryUnmarshaler.
+func (uuid *UUID) UnmarshalBinary(data []byte) error {
+ if len(data) != 16 {
+ return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
+ }
+ copy(uuid[:], data)
+ return nil
+}
diff --git a/vendor/github.com/google/uuid/node.go b/vendor/github.com/google/uuid/node.go
new file mode 100644
index 000000000..5f0156a2e
--- /dev/null
+++ b/vendor/github.com/google/uuid/node.go
@@ -0,0 +1,103 @@
+// Copyright 2016 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+ "net"
+ "sync"
+)
+
+var (
+ nodeMu sync.Mutex
+ interfaces []net.Interface // cached list of interfaces
+ ifname string // name of interface being used
+ nodeID [6]byte // hardware for version 1 UUIDs
+ zeroID [6]byte // nodeID with only 0's
+)
+
+// NodeInterface returns the name of the interface from which the NodeID was
+// derived. The interface "user" is returned if the NodeID was set by
+// SetNodeID.
+func NodeInterface() string {
+ defer nodeMu.Unlock()
+ nodeMu.Lock()
+ return ifname
+}
+
+// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
+// If name is "" then the first usable interface found will be used or a random
+// Node ID will be generated. If a named interface cannot be found then false
+// is returned.
+//
+// SetNodeInterface never fails when name is "".
+func SetNodeInterface(name string) bool {
+ defer nodeMu.Unlock()
+ nodeMu.Lock()
+ return setNodeInterface(name)
+}
+
+func setNodeInterface(name string) bool {
+ if interfaces == nil {
+ var err error
+ interfaces, err = net.Interfaces()
+ if err != nil && name != "" {
+ return false
+ }
+ }
+
+ for _, ifs := range interfaces {
+ if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
+ copy(nodeID[:], ifs.HardwareAddr)
+ ifname = ifs.Name
+ return true
+ }
+ }
+
+ // We found no interfaces with a valid hardware address. If name
+ // does not specify a specific interface generate a random Node ID
+ // (section 4.1.6)
+ if name == "" {
+ randomBits(nodeID[:])
+ return true
+ }
+ return false
+}
+
+// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
+// if not already set.
+func NodeID() []byte {
+ defer nodeMu.Unlock()
+ nodeMu.Lock()
+ if nodeID == zeroID {
+ setNodeInterface("")
+ }
+ nid := nodeID
+ return nid[:]
+}
+
+// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
+// of id are used. If id is less than 6 bytes then false is returned and the
+// Node ID is not set.
+func SetNodeID(id []byte) bool {
+ if len(id) < 6 {
+ return false
+ }
+ defer nodeMu.Unlock()
+ nodeMu.Lock()
+ copy(nodeID[:], id)
+ ifname = "user"
+ return true
+}
+
+// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
+// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
+func (uuid UUID) NodeID() []byte {
+ if len(uuid) != 16 {
+ return nil
+ }
+ var node [6]byte
+ copy(node[:], uuid[10:])
+ return node[:]
+}
diff --git a/vendor/github.com/google/uuid/sql.go b/vendor/github.com/google/uuid/sql.go
new file mode 100644
index 000000000..528ad0de5
--- /dev/null
+++ b/vendor/github.com/google/uuid/sql.go
@@ -0,0 +1,58 @@
+// Copyright 2016 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+ "database/sql/driver"
+ "fmt"
+)
+
+// Scan implements sql.Scanner so UUIDs can be read from databases transparently
+// Currently, database types that map to string and []byte are supported. Please
+// consult database-specific driver documentation for matching types.
+func (uuid *UUID) Scan(src interface{}) error {
+ switch src.(type) {
+ case string:
+ // if an empty UUID comes from a table, we return a null UUID
+ if src.(string) == "" {
+ return nil
+ }
+
+ // see Parse for required string format
+ u, err := Parse(src.(string))
+
+ if err != nil {
+ return fmt.Errorf("Scan: %v", err)
+ }
+
+ *uuid = u
+ case []byte:
+ b := src.([]byte)
+
+ // if an empty UUID comes from a table, we return a null UUID
+ if len(b) == 0 {
+ return nil
+ }
+
+ // assumes a simple slice of bytes if 16 bytes
+ // otherwise attempts to parse
+ if len(b) != 16 {
+ return uuid.Scan(string(b))
+ }
+ copy((*uuid)[:], b)
+
+ default:
+ return fmt.Errorf("Scan: unable to scan type %T into UUID", src)
+ }
+
+ return nil
+}
+
+// Value implements sql.Valuer so that UUIDs can be written to databases
+// transparently. Currently, UUIDs map to strings. Please consult
+// database-specific driver documentation for matching types.
+func (uuid UUID) Value() (driver.Value, error) {
+ return uuid.String(), nil
+}
diff --git a/vendor/github.com/google/uuid/time.go b/vendor/github.com/google/uuid/time.go
new file mode 100644
index 000000000..fd7fe0ac4
--- /dev/null
+++ b/vendor/github.com/google/uuid/time.go
@@ -0,0 +1,123 @@
+// Copyright 2016 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+ "encoding/binary"
+ "sync"
+ "time"
+)
+
+// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
+// 1582.
+type Time int64
+
+const (
+ lillian = 2299160 // Julian day of 15 Oct 1582
+ unix = 2440587 // Julian day of 1 Jan 1970
+ epoch = unix - lillian // Days between epochs
+ g1582 = epoch * 86400 // seconds between epochs
+ g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
+)
+
+var (
+ timeMu sync.Mutex
+ lasttime uint64 // last time we returned
+ clockSeq uint16 // clock sequence for this run
+
+ timeNow = time.Now // for testing
+)
+
+// UnixTime converts t the number of seconds and nanoseconds using the Unix
+// epoch of 1 Jan 1970.
+func (t Time) UnixTime() (sec, nsec int64) {
+ sec = int64(t - g1582ns100)
+ nsec = (sec % 10000000) * 100
+ sec /= 10000000
+ return sec, nsec
+}
+
+// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
+// clock sequence as well as adjusting the clock sequence as needed. An error
+// is returned if the current time cannot be determined.
+func GetTime() (Time, uint16, error) {
+ defer timeMu.Unlock()
+ timeMu.Lock()
+ return getTime()
+}
+
+func getTime() (Time, uint16, error) {
+ t := timeNow()
+
+ // If we don't have a clock sequence already, set one.
+ if clockSeq == 0 {
+ setClockSequence(-1)
+ }
+ now := uint64(t.UnixNano()/100) + g1582ns100
+
+ // If time has gone backwards with this clock sequence then we
+ // increment the clock sequence
+ if now <= lasttime {
+ clockSeq = ((clockSeq + 1) & 0x3fff) | 0x8000
+ }
+ lasttime = now
+ return Time(now), clockSeq, nil
+}
+
+// ClockSequence returns the current clock sequence, generating one if not
+// already set. The clock sequence is only used for Version 1 UUIDs.
+//
+// The uuid package does not use global static storage for the clock sequence or
+// the last time a UUID was generated. Unless SetClockSequence is used, a new
+// random clock sequence is generated the first time a clock sequence is
+// requested by ClockSequence, GetTime, or NewUUID. (section 4.2.1.1)
+func ClockSequence() int {
+ defer timeMu.Unlock()
+ timeMu.Lock()
+ return clockSequence()
+}
+
+func clockSequence() int {
+ if clockSeq == 0 {
+ setClockSequence(-1)
+ }
+ return int(clockSeq & 0x3fff)
+}
+
+// SetClockSeq sets the clock sequence to the lower 14 bits of seq. Setting to
+// -1 causes a new sequence to be generated.
+func SetClockSequence(seq int) {
+ defer timeMu.Unlock()
+ timeMu.Lock()
+ setClockSequence(seq)
+}
+
+func setClockSequence(seq int) {
+ if seq == -1 {
+ var b [2]byte
+ randomBits(b[:]) // clock sequence
+ seq = int(b[0])<<8 | int(b[1])
+ }
+ old_seq := clockSeq
+ clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant
+ if old_seq != clockSeq {
+ lasttime = 0
+ }
+}
+
+// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
+// uuid. The time is only defined for version 1 and 2 UUIDs.
+func (uuid UUID) Time() Time {
+ time := int64(binary.BigEndian.Uint32(uuid[0:4]))
+ time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
+ time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
+ return Time(time)
+}
+
+// ClockSequence returns the clock sequence encoded in uuid.
+// The clock sequence is only well defined for version 1 and 2 UUIDs.
+func (uuid UUID) ClockSequence() int {
+ return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff
+}
diff --git a/vendor/github.com/google/uuid/util.go b/vendor/github.com/google/uuid/util.go
new file mode 100644
index 000000000..5ea6c7378
--- /dev/null
+++ b/vendor/github.com/google/uuid/util.go
@@ -0,0 +1,43 @@
+// Copyright 2016 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+ "io"
+)
+
+// randomBits completely fills slice b with random data.
+func randomBits(b []byte) {
+ if _, err := io.ReadFull(rander, b); err != nil {
+ panic(err.Error()) // rand should never fail
+ }
+}
+
+// xvalues returns the value of a byte as a hexadecimal digit or 255.
+var xvalues = [256]byte{
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
+ 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+}
+
+// xtob converts hex characters x1 and x2 into a byte.
+func xtob(x1, x2 byte) (byte, bool) {
+ b1 := xvalues[x1]
+ b2 := xvalues[x2]
+ return (b1 << 4) | b2, b1 != 255 && b2 != 255
+}
diff --git a/vendor/github.com/google/uuid/uuid.go b/vendor/github.com/google/uuid/uuid.go
new file mode 100644
index 000000000..b7b9ced31
--- /dev/null
+++ b/vendor/github.com/google/uuid/uuid.go
@@ -0,0 +1,191 @@
+// Copyright 2016 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+ "bytes"
+ "crypto/rand"
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "io"
+ "strings"
+)
+
+// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
+// 4122.
+type UUID [16]byte
+
+// A Version represents a UUID's version.
+type Version byte
+
+// A Variant represents a UUID's variant.
+type Variant byte
+
+// Constants returned by Variant.
+const (
+ Invalid = Variant(iota) // Invalid UUID
+ RFC4122 // The variant specified in RFC4122
+ Reserved // Reserved, NCS backward compatibility.
+ Microsoft // Reserved, Microsoft Corporation backward compatibility.
+ Future // Reserved for future definition.
+)
+
+var rander = rand.Reader // random function
+
+// Parse decodes s into a UUID or returns an error. Both the UUID form of
+// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
+// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded.
+func Parse(s string) (UUID, error) {
+ var uuid UUID
+ if len(s) != 36 {
+ if len(s) != 36+9 {
+ return uuid, fmt.Errorf("invalid UUID length: %d", len(s))
+ }
+ if strings.ToLower(s[:9]) != "urn:uuid:" {
+ return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9])
+ }
+ s = s[9:]
+ }
+ if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
+ return uuid, errors.New("invalid UUID format")
+ }
+ for i, x := range [16]int{
+ 0, 2, 4, 6,
+ 9, 11,
+ 14, 16,
+ 19, 21,
+ 24, 26, 28, 30, 32, 34} {
+ if v, ok := xtob(s[x], s[x+1]); !ok {
+ return uuid, errors.New("invalid UUID format")
+ } else {
+ uuid[i] = v
+ }
+ }
+ return uuid, nil
+}
+
+// ParseBytes is like Parse, except it parses a byte slice instead of a string.
+func ParseBytes(b []byte) (UUID, error) {
+ var uuid UUID
+ if len(b) != 36 {
+ if len(b) != 36+9 {
+ return uuid, fmt.Errorf("invalid UUID length: %d", len(b))
+ }
+ if !bytes.Equal(bytes.ToLower(b[:9]), []byte("urn:uuid:")) {
+ return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9])
+ }
+ b = b[9:]
+ }
+ if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' {
+ return uuid, errors.New("invalid UUID format")
+ }
+ for i, x := range [16]int{
+ 0, 2, 4, 6,
+ 9, 11,
+ 14, 16,
+ 19, 21,
+ 24, 26, 28, 30, 32, 34} {
+ if v, ok := xtob(b[x], b[x+1]); !ok {
+ return uuid, errors.New("invalid UUID format")
+ } else {
+ uuid[i] = v
+ }
+ }
+ return uuid, nil
+}
+
+// Must returns uuid if err is nil and panics otherwise.
+func Must(uuid UUID, err error) UUID {
+ if err != nil {
+ panic(err)
+ }
+ return uuid
+}
+
+// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+// , or "" if uuid is invalid.
+func (uuid UUID) String() string {
+ var buf [36]byte
+ encodeHex(buf[:], uuid)
+ return string(buf[:])
+}
+
+// URN returns the RFC 2141 URN form of uuid,
+// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
+func (uuid UUID) URN() string {
+ var buf [36 + 9]byte
+ copy(buf[:], "urn:uuid:")
+ encodeHex(buf[9:], uuid)
+ return string(buf[:])
+}
+
+func encodeHex(dst []byte, uuid UUID) {
+ hex.Encode(dst[:], uuid[:4])
+ dst[8] = '-'
+ hex.Encode(dst[9:13], uuid[4:6])
+ dst[13] = '-'
+ hex.Encode(dst[14:18], uuid[6:8])
+ dst[18] = '-'
+ hex.Encode(dst[19:23], uuid[8:10])
+ dst[23] = '-'
+ hex.Encode(dst[24:], uuid[10:])
+}
+
+// Variant returns the variant encoded in uuid.
+func (uuid UUID) Variant() Variant {
+ switch {
+ case (uuid[8] & 0xc0) == 0x80:
+ return RFC4122
+ case (uuid[8] & 0xe0) == 0xc0:
+ return Microsoft
+ case (uuid[8] & 0xe0) == 0xe0:
+ return Future
+ default:
+ return Reserved
+ }
+}
+
+// Version returns the version of uuid.
+func (uuid UUID) Version() Version {
+ return Version(uuid[6] >> 4)
+}
+
+func (v Version) String() string {
+ if v > 15 {
+ return fmt.Sprintf("BAD_VERSION_%d", v)
+ }
+ return fmt.Sprintf("VERSION_%d", v)
+}
+
+func (v Variant) String() string {
+ switch v {
+ case RFC4122:
+ return "RFC4122"
+ case Reserved:
+ return "Reserved"
+ case Microsoft:
+ return "Microsoft"
+ case Future:
+ return "Future"
+ case Invalid:
+ return "Invalid"
+ }
+ return fmt.Sprintf("BadVariant%d", int(v))
+}
+
+// SetRand sets the random number generator to r, which implents io.Reader.
+// If r.Read returns an error when the package requests random data then
+// a panic will be issued.
+//
+// Calling SetRand with nil sets the random number generator to the default
+// generator.
+func SetRand(r io.Reader) {
+ if r == nil {
+ rander = rand.Reader
+ return
+ }
+ rander = r
+}
diff --git a/vendor/github.com/google/uuid/version1.go b/vendor/github.com/google/uuid/version1.go
new file mode 100644
index 000000000..22dc07cdc
--- /dev/null
+++ b/vendor/github.com/google/uuid/version1.go
@@ -0,0 +1,44 @@
+// Copyright 2016 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+ "encoding/binary"
+)
+
+// NewUUID returns a Version 1 UUID based on the current NodeID and clock
+// sequence, and the current time. If the NodeID has not been set by SetNodeID
+// or SetNodeInterface then it will be set automatically. If the NodeID cannot
+// be set NewUUID returns nil. If clock sequence has not been set by
+// SetClockSequence then it will be set automatically. If GetTime fails to
+// return the current NewUUID returns Nil and an error.
+//
+// In most cases, New should be used.
+func NewUUID() (UUID, error) {
+ nodeMu.Lock()
+ if nodeID == zeroID {
+ setNodeInterface("")
+ }
+ nodeMu.Unlock()
+
+ var uuid UUID
+ now, seq, err := GetTime()
+ if err != nil {
+ return uuid, err
+ }
+
+ timeLow := uint32(now & 0xffffffff)
+ timeMid := uint16((now >> 32) & 0xffff)
+ timeHi := uint16((now >> 48) & 0x0fff)
+ timeHi |= 0x1000 // Version 1
+
+ binary.BigEndian.PutUint32(uuid[0:], timeLow)
+ binary.BigEndian.PutUint16(uuid[4:], timeMid)
+ binary.BigEndian.PutUint16(uuid[6:], timeHi)
+ binary.BigEndian.PutUint16(uuid[8:], seq)
+ copy(uuid[10:], nodeID[:])
+
+ return uuid, nil
+}
diff --git a/vendor/github.com/google/uuid/version4.go b/vendor/github.com/google/uuid/version4.go
new file mode 100644
index 000000000..390dd2cad
--- /dev/null
+++ b/vendor/github.com/google/uuid/version4.go
@@ -0,0 +1,38 @@
+// Copyright 2016 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import "io"
+
+// New is creates a new random UUID or panics. New is equivalent to
+// the expression
+//
+// uuid.Must(uuid.NewRandom())
+func New() UUID {
+ return Must(NewRandom())
+}
+
+// NewRandom returns a Random (Version 4) UUID or panics.
+//
+// The strength of the UUIDs is based on the strength of the crypto/rand
+// package.
+//
+// A note about uniqueness derived from from the UUID Wikipedia entry:
+//
+// Randomly generated UUIDs have 122 random bits. One's annual risk of being
+// hit by a meteorite is estimated to be one chance in 17 billion, that
+// means the probability is about 0.00000000006 (6 × 10−11),
+// equivalent to the odds of creating a few tens of trillions of UUIDs in a
+// year and having one duplicate.
+func NewRandom() (UUID, error) {
+ var uuid UUID
+ _, err := io.ReadFull(rander, uuid[:])
+ if err != nil {
+ return Nil, err
+ }
+ uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
+ uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
+ return uuid, nil
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/LICENSE b/vendor/github.com/gophercloud/gophercloud/LICENSE
new file mode 100644
index 000000000..fbbbc9e4c
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/LICENSE
@@ -0,0 +1,191 @@
+Copyright 2012-2013 Rackspace, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License"); you may not use
+this file except in compliance with the License. You may obtain a copy of the
+License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software distributed
+under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+CONDITIONS OF ANY KIND, either express or implied. See the License for the
+specific language governing permissions and limitations under the License.
+
+------
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
diff --git a/vendor/github.com/gophercloud/gophercloud/auth_options.go b/vendor/github.com/gophercloud/gophercloud/auth_options.go
new file mode 100644
index 000000000..5ffa8d1e0
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/auth_options.go
@@ -0,0 +1,437 @@
+package gophercloud
+
+/*
+AuthOptions stores information needed to authenticate to an OpenStack Cloud.
+You can populate one manually, or use a provider's AuthOptionsFromEnv() function
+to read relevant information from the standard environment variables. Pass one
+to a provider's AuthenticatedClient function to authenticate and obtain a
+ProviderClient representing an active session on that provider.
+
+Its fields are the union of those recognized by each identity implementation and
+provider.
+
+An example of manually providing authentication information:
+
+ opts := gophercloud.AuthOptions{
+ IdentityEndpoint: "https://openstack.example.com:5000/v2.0",
+ Username: "{username}",
+ Password: "{password}",
+ TenantID: "{tenant_id}",
+ }
+
+ provider, err := openstack.AuthenticatedClient(opts)
+
+An example of using AuthOptionsFromEnv(), where the environment variables can
+be read from a file, such as a standard openrc file:
+
+ opts, err := openstack.AuthOptionsFromEnv()
+ provider, err := openstack.AuthenticatedClient(opts)
+*/
+type AuthOptions struct {
+ // IdentityEndpoint specifies the HTTP endpoint that is required to work with
+ // the Identity API of the appropriate version. While it's ultimately needed by
+ // all of the identity services, it will often be populated by a provider-level
+ // function.
+ //
+ // The IdentityEndpoint is typically referred to as the "auth_url" or
+ // "OS_AUTH_URL" in the information provided by the cloud operator.
+ IdentityEndpoint string `json:"-"`
+
+ // Username is required if using Identity V2 API. Consult with your provider's
+ // control panel to discover your account's username. In Identity V3, either
+ // UserID or a combination of Username and DomainID or DomainName are needed.
+ Username string `json:"username,omitempty"`
+ UserID string `json:"-"`
+
+ Password string `json:"password,omitempty"`
+
+ // At most one of DomainID and DomainName must be provided if using Username
+ // with Identity V3. Otherwise, either are optional.
+ DomainID string `json:"-"`
+ DomainName string `json:"name,omitempty"`
+
+ // The TenantID and TenantName fields are optional for the Identity V2 API.
+ // The same fields are known as project_id and project_name in the Identity
+ // V3 API, but are collected as TenantID and TenantName here in both cases.
+ // Some providers allow you to specify a TenantName instead of the TenantId.
+ // Some require both. Your provider's authentication policies will determine
+ // how these fields influence authentication.
+ // If DomainID or DomainName are provided, they will also apply to TenantName.
+ // It is not currently possible to authenticate with Username and a Domain
+ // and scope to a Project in a different Domain by using TenantName. To
+ // accomplish that, the ProjectID will need to be provided as the TenantID
+ // option.
+ TenantID string `json:"tenantId,omitempty"`
+ TenantName string `json:"tenantName,omitempty"`
+
+ // AllowReauth should be set to true if you grant permission for Gophercloud to
+ // cache your credentials in memory, and to allow Gophercloud to attempt to
+ // re-authenticate automatically if/when your token expires. If you set it to
+ // false, it will not cache these settings, but re-authentication will not be
+ // possible. This setting defaults to false.
+ //
+ // NOTE: The reauth function will try to re-authenticate endlessly if left
+ // unchecked. The way to limit the number of attempts is to provide a custom
+ // HTTP client to the provider client and provide a transport that implements
+ // the RoundTripper interface and stores the number of failed retries. For an
+ // example of this, see here:
+ // https://github.com/rackspace/rack/blob/1.0.0/auth/clients.go#L311
+ AllowReauth bool `json:"-"`
+
+ // TokenID allows users to authenticate (possibly as another user) with an
+ // authentication token ID.
+ TokenID string `json:"-"`
+
+ // Scope determines the scoping of the authentication request.
+ Scope *AuthScope `json:"-"`
+
+ // Authentication through Application Credentials requires supplying name, project and secret
+ // For project we can use TenantID
+ ApplicationCredentialID string `json:"-"`
+ ApplicationCredentialName string `json:"-"`
+ ApplicationCredentialSecret string `json:"-"`
+}
+
+// AuthScope allows a created token to be limited to a specific domain or project.
+type AuthScope struct {
+ ProjectID string
+ ProjectName string
+ DomainID string
+ DomainName string
+}
+
+// ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
+// interface in the v2 tokens package
+func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) {
+ // Populate the request map.
+ authMap := make(map[string]interface{})
+
+ if opts.Username != "" {
+ if opts.Password != "" {
+ authMap["passwordCredentials"] = map[string]interface{}{
+ "username": opts.Username,
+ "password": opts.Password,
+ }
+ } else {
+ return nil, ErrMissingInput{Argument: "Password"}
+ }
+ } else if opts.TokenID != "" {
+ authMap["token"] = map[string]interface{}{
+ "id": opts.TokenID,
+ }
+ } else {
+ return nil, ErrMissingInput{Argument: "Username"}
+ }
+
+ if opts.TenantID != "" {
+ authMap["tenantId"] = opts.TenantID
+ }
+ if opts.TenantName != "" {
+ authMap["tenantName"] = opts.TenantName
+ }
+
+ return map[string]interface{}{"auth": authMap}, nil
+}
+
+func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) {
+ type domainReq struct {
+ ID *string `json:"id,omitempty"`
+ Name *string `json:"name,omitempty"`
+ }
+
+ type projectReq struct {
+ Domain *domainReq `json:"domain,omitempty"`
+ Name *string `json:"name,omitempty"`
+ ID *string `json:"id,omitempty"`
+ }
+
+ type userReq struct {
+ ID *string `json:"id,omitempty"`
+ Name *string `json:"name,omitempty"`
+ Password string `json:"password,omitempty"`
+ Domain *domainReq `json:"domain,omitempty"`
+ }
+
+ type passwordReq struct {
+ User userReq `json:"user"`
+ }
+
+ type tokenReq struct {
+ ID string `json:"id"`
+ }
+
+ type applicationCredentialReq struct {
+ ID *string `json:"id,omitempty"`
+ Name *string `json:"name,omitempty"`
+ User *userReq `json:"user,omitempty"`
+ Secret *string `json:"secret,omitempty"`
+ }
+
+ type identityReq struct {
+ Methods []string `json:"methods"`
+ Password *passwordReq `json:"password,omitempty"`
+ Token *tokenReq `json:"token,omitempty"`
+ ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"`
+ }
+
+ type authReq struct {
+ Identity identityReq `json:"identity"`
+ }
+
+ type request struct {
+ Auth authReq `json:"auth"`
+ }
+
+ // Populate the request structure based on the provided arguments. Create and return an error
+ // if insufficient or incompatible information is present.
+ var req request
+
+ if opts.Password == "" {
+ if opts.TokenID != "" {
+ // Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
+ // parameters.
+ if opts.Username != "" {
+ return nil, ErrUsernameWithToken{}
+ }
+ if opts.UserID != "" {
+ return nil, ErrUserIDWithToken{}
+ }
+ if opts.DomainID != "" {
+ return nil, ErrDomainIDWithToken{}
+ }
+ if opts.DomainName != "" {
+ return nil, ErrDomainNameWithToken{}
+ }
+
+ // Configure the request for Token authentication.
+ req.Auth.Identity.Methods = []string{"token"}
+ req.Auth.Identity.Token = &tokenReq{
+ ID: opts.TokenID,
+ }
+
+ } else if opts.ApplicationCredentialID != "" {
+ // Configure the request for ApplicationCredentialID authentication.
+ // https://github.com/openstack/keystoneauth/blob/stable/rocky/keystoneauth1/identity/v3/application_credential.py#L48-L67
+ // There are three kinds of possible application_credential requests
+ // 1. application_credential id + secret
+ // 2. application_credential name + secret + user_id
+ // 3. application_credential name + secret + username + domain_id / domain_name
+ if opts.ApplicationCredentialSecret == "" {
+ return nil, ErrAppCredMissingSecret{}
+ }
+ req.Auth.Identity.Methods = []string{"application_credential"}
+ req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{
+ ID: &opts.ApplicationCredentialID,
+ Secret: &opts.ApplicationCredentialSecret,
+ }
+ } else if opts.ApplicationCredentialName != "" {
+ if opts.ApplicationCredentialSecret == "" {
+ return nil, ErrAppCredMissingSecret{}
+ }
+
+ var userRequest *userReq
+
+ if opts.UserID != "" {
+ // UserID could be used without the domain information
+ userRequest = &userReq{
+ ID: &opts.UserID,
+ }
+ }
+
+ if userRequest == nil && opts.Username == "" {
+ // Make sure that Username or UserID are provided
+ return nil, ErrUsernameOrUserID{}
+ }
+
+ if userRequest == nil && opts.DomainID != "" {
+ userRequest = &userReq{
+ Name: &opts.Username,
+ Domain: &domainReq{ID: &opts.DomainID},
+ }
+ }
+
+ if userRequest == nil && opts.DomainName != "" {
+ userRequest = &userReq{
+ Name: &opts.Username,
+ Domain: &domainReq{Name: &opts.DomainName},
+ }
+ }
+
+ // Make sure that DomainID or DomainName are provided among Username
+ if userRequest == nil {
+ return nil, ErrDomainIDOrDomainName{}
+ }
+
+ req.Auth.Identity.Methods = []string{"application_credential"}
+ req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{
+ Name: &opts.ApplicationCredentialName,
+ User: userRequest,
+ Secret: &opts.ApplicationCredentialSecret,
+ }
+ } else {
+ // If no password or token ID or ApplicationCredential are available, authentication can't continue.
+ return nil, ErrMissingPassword{}
+ }
+ } else {
+ // Password authentication.
+ req.Auth.Identity.Methods = []string{"password"}
+
+ // At least one of Username and UserID must be specified.
+ if opts.Username == "" && opts.UserID == "" {
+ return nil, ErrUsernameOrUserID{}
+ }
+
+ if opts.Username != "" {
+ // If Username is provided, UserID may not be provided.
+ if opts.UserID != "" {
+ return nil, ErrUsernameOrUserID{}
+ }
+
+ // Either DomainID or DomainName must also be specified.
+ if opts.DomainID == "" && opts.DomainName == "" {
+ return nil, ErrDomainIDOrDomainName{}
+ }
+
+ if opts.DomainID != "" {
+ if opts.DomainName != "" {
+ return nil, ErrDomainIDOrDomainName{}
+ }
+
+ // Configure the request for Username and Password authentication with a DomainID.
+ req.Auth.Identity.Password = &passwordReq{
+ User: userReq{
+ Name: &opts.Username,
+ Password: opts.Password,
+ Domain: &domainReq{ID: &opts.DomainID},
+ },
+ }
+ }
+
+ if opts.DomainName != "" {
+ // Configure the request for Username and Password authentication with a DomainName.
+ req.Auth.Identity.Password = &passwordReq{
+ User: userReq{
+ Name: &opts.Username,
+ Password: opts.Password,
+ Domain: &domainReq{Name: &opts.DomainName},
+ },
+ }
+ }
+ }
+
+ if opts.UserID != "" {
+ // If UserID is specified, neither DomainID nor DomainName may be.
+ if opts.DomainID != "" {
+ return nil, ErrDomainIDWithUserID{}
+ }
+ if opts.DomainName != "" {
+ return nil, ErrDomainNameWithUserID{}
+ }
+
+ // Configure the request for UserID and Password authentication.
+ req.Auth.Identity.Password = &passwordReq{
+ User: userReq{ID: &opts.UserID, Password: opts.Password},
+ }
+ }
+ }
+
+ b, err := BuildRequestBody(req, "")
+ if err != nil {
+ return nil, err
+ }
+
+ if len(scope) != 0 {
+ b["auth"].(map[string]interface{})["scope"] = scope
+ }
+
+ return b, nil
+}
+
+func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
+ // For backwards compatibility.
+ // If AuthOptions.Scope was not set, try to determine it.
+ // This works well for common scenarios.
+ if opts.Scope == nil {
+ opts.Scope = new(AuthScope)
+ if opts.TenantID != "" {
+ opts.Scope.ProjectID = opts.TenantID
+ } else {
+ if opts.TenantName != "" {
+ opts.Scope.ProjectName = opts.TenantName
+ opts.Scope.DomainID = opts.DomainID
+ opts.Scope.DomainName = opts.DomainName
+ }
+ }
+ }
+
+ if opts.Scope.ProjectName != "" {
+ // ProjectName provided: either DomainID or DomainName must also be supplied.
+ // ProjectID may not be supplied.
+ if opts.Scope.DomainID == "" && opts.Scope.DomainName == "" {
+ return nil, ErrScopeDomainIDOrDomainName{}
+ }
+ if opts.Scope.ProjectID != "" {
+ return nil, ErrScopeProjectIDOrProjectName{}
+ }
+
+ if opts.Scope.DomainID != "" {
+ // ProjectName + DomainID
+ return map[string]interface{}{
+ "project": map[string]interface{}{
+ "name": &opts.Scope.ProjectName,
+ "domain": map[string]interface{}{"id": &opts.Scope.DomainID},
+ },
+ }, nil
+ }
+
+ if opts.Scope.DomainName != "" {
+ // ProjectName + DomainName
+ return map[string]interface{}{
+ "project": map[string]interface{}{
+ "name": &opts.Scope.ProjectName,
+ "domain": map[string]interface{}{"name": &opts.Scope.DomainName},
+ },
+ }, nil
+ }
+ } else if opts.Scope.ProjectID != "" {
+ // ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
+ if opts.Scope.DomainID != "" {
+ return nil, ErrScopeProjectIDAlone{}
+ }
+ if opts.Scope.DomainName != "" {
+ return nil, ErrScopeProjectIDAlone{}
+ }
+
+ // ProjectID
+ return map[string]interface{}{
+ "project": map[string]interface{}{
+ "id": &opts.Scope.ProjectID,
+ },
+ }, nil
+ } else if opts.Scope.DomainID != "" {
+ // DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
+ if opts.Scope.DomainName != "" {
+ return nil, ErrScopeDomainIDOrDomainName{}
+ }
+
+ // DomainID
+ return map[string]interface{}{
+ "domain": map[string]interface{}{
+ "id": &opts.Scope.DomainID,
+ },
+ }, nil
+ } else if opts.Scope.DomainName != "" {
+ // DomainName
+ return map[string]interface{}{
+ "domain": map[string]interface{}{
+ "name": &opts.Scope.DomainName,
+ },
+ }, nil
+ }
+
+ return nil, nil
+}
+
+func (opts AuthOptions) CanReauth() bool {
+ return opts.AllowReauth
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/auth_result.go b/vendor/github.com/gophercloud/gophercloud/auth_result.go
new file mode 100644
index 000000000..2e4699b97
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/auth_result.go
@@ -0,0 +1,52 @@
+package gophercloud
+
+/*
+AuthResult is the result from the request that was used to obtain a provider
+client's Keystone token. It is returned from ProviderClient.GetAuthResult().
+
+The following types satisfy this interface:
+
+ github.com/gophercloud/gophercloud/openstack/identity/v2/tokens.CreateResult
+ github.com/gophercloud/gophercloud/openstack/identity/v3/tokens.CreateResult
+
+Usage example:
+
+ import (
+ "github.com/gophercloud/gophercloud"
+ tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
+ tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
+ )
+
+ func GetAuthenticatedUserID(providerClient *gophercloud.ProviderClient) (string, error) {
+ r := providerClient.GetAuthResult()
+ if r == nil {
+ //ProviderClient did not use openstack.Authenticate(), e.g. because token
+ //was set manually with ProviderClient.SetToken()
+ return "", errors.New("no AuthResult available")
+ }
+ switch r := r.(type) {
+ case tokens2.CreateResult:
+ u, err := r.ExtractUser()
+ if err != nil {
+ return "", err
+ }
+ return u.ID, nil
+ case tokens3.CreateResult:
+ u, err := r.ExtractUser()
+ if err != nil {
+ return "", err
+ }
+ return u.ID, nil
+ default:
+ panic(fmt.Sprintf("got unexpected AuthResult type %t", r))
+ }
+ }
+
+Both implementing types share a lot of methods by name, like ExtractUser() in
+this example. But those methods cannot be part of the AuthResult interface
+because the return types are different (in this case, type tokens2.User vs.
+type tokens3.User).
+*/
+type AuthResult interface {
+ ExtractTokenID() (string, error)
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/doc.go b/vendor/github.com/gophercloud/gophercloud/doc.go
new file mode 100644
index 000000000..131cc8e30
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/doc.go
@@ -0,0 +1,93 @@
+/*
+Package gophercloud provides a multi-vendor interface to OpenStack-compatible
+clouds. The library has a three-level hierarchy: providers, services, and
+resources.
+
+Authenticating with Providers
+
+Provider structs represent the cloud providers that offer and manage a
+collection of services. You will generally want to create one Provider
+client per OpenStack cloud.
+
+Use your OpenStack credentials to create a Provider client. The
+IdentityEndpoint is typically refered to as "auth_url" or "OS_AUTH_URL" in
+information provided by the cloud operator. Additionally, the cloud may refer to
+TenantID or TenantName as project_id and project_name. Credentials are
+specified like so:
+
+ opts := gophercloud.AuthOptions{
+ IdentityEndpoint: "https://openstack.example.com:5000/v2.0",
+ Username: "{username}",
+ Password: "{password}",
+ TenantID: "{tenant_id}",
+ }
+
+ provider, err := openstack.AuthenticatedClient(opts)
+
+You may also use the openstack.AuthOptionsFromEnv() helper function. This
+function reads in standard environment variables frequently found in an
+OpenStack `openrc` file. Again note that Gophercloud currently uses "tenant"
+instead of "project".
+
+ opts, err := openstack.AuthOptionsFromEnv()
+ provider, err := openstack.AuthenticatedClient(opts)
+
+Service Clients
+
+Service structs are specific to a provider and handle all of the logic and
+operations for a particular OpenStack service. Examples of services include:
+Compute, Object Storage, Block Storage. In order to define one, you need to
+pass in the parent provider, like so:
+
+ opts := gophercloud.EndpointOpts{Region: "RegionOne"}
+
+ client, err := openstack.NewComputeV2(provider, opts)
+
+Resources
+
+Resource structs are the domain models that services make use of in order
+to work with and represent the state of API resources:
+
+ server, err := servers.Get(client, "{serverId}").Extract()
+
+Intermediate Result structs are returned for API operations, which allow
+generic access to the HTTP headers, response body, and any errors associated
+with the network transaction. To turn a result into a usable resource struct,
+you must call the Extract method which is chained to the response, or an
+Extract function from an applicable extension:
+
+ result := servers.Get(client, "{serverId}")
+
+ // Attempt to extract the disk configuration from the OS-DCF disk config
+ // extension:
+ config, err := diskconfig.ExtractGet(result)
+
+All requests that enumerate a collection return a Pager struct that is used to
+iterate through the results one page at a time. Use the EachPage method on that
+Pager to handle each successive Page in a closure, then use the appropriate
+extraction method from that request's package to interpret that Page as a slice
+of results:
+
+ err := servers.List(client, nil).EachPage(func (page pagination.Page) (bool, error) {
+ s, err := servers.ExtractServers(page)
+ if err != nil {
+ return false, err
+ }
+
+ // Handle the []servers.Server slice.
+
+ // Return "false" or an error to prematurely stop fetching new pages.
+ return true, nil
+ })
+
+If you want to obtain the entire collection of pages without doing any
+intermediary processing on each page, you can use the AllPages method:
+
+ allPages, err := servers.List(client, nil).AllPages()
+ allServers, err := servers.ExtractServers(allPages)
+
+This top-level package contains utility functions and data types that are used
+throughout the provider and service packages. Of particular note for end users
+are the AuthOptions and EndpointOpts structs.
+*/
+package gophercloud
diff --git a/vendor/github.com/gophercloud/gophercloud/endpoint_search.go b/vendor/github.com/gophercloud/gophercloud/endpoint_search.go
new file mode 100644
index 000000000..2fbc3c97f
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/endpoint_search.go
@@ -0,0 +1,76 @@
+package gophercloud
+
+// Availability indicates to whom a specific service endpoint is accessible:
+// the internet at large, internal networks only, or only to administrators.
+// Different identity services use different terminology for these. Identity v2
+// lists them as different kinds of URLs within the service catalog ("adminURL",
+// "internalURL", and "publicURL"), while v3 lists them as "Interfaces" in an
+// endpoint's response.
+type Availability string
+
+const (
+ // AvailabilityAdmin indicates that an endpoint is only available to
+ // administrators.
+ AvailabilityAdmin Availability = "admin"
+
+ // AvailabilityPublic indicates that an endpoint is available to everyone on
+ // the internet.
+ AvailabilityPublic Availability = "public"
+
+ // AvailabilityInternal indicates that an endpoint is only available within
+ // the cluster's internal network.
+ AvailabilityInternal Availability = "internal"
+)
+
+// EndpointOpts specifies search criteria used by queries against an
+// OpenStack service catalog. The options must contain enough information to
+// unambiguously identify one, and only one, endpoint within the catalog.
+//
+// Usually, these are passed to service client factory functions in a provider
+// package, like "openstack.NewComputeV2()".
+type EndpointOpts struct {
+ // Type [required] is the service type for the client (e.g., "compute",
+ // "object-store"). Generally, this will be supplied by the service client
+ // function, but a user-given value will be honored if provided.
+ Type string
+
+ // Name [optional] is the service name for the client (e.g., "nova") as it
+ // appears in the service catalog. Services can have the same Type but a
+ // different Name, which is why both Type and Name are sometimes needed.
+ Name string
+
+ // Region [required] is the geographic region in which the endpoint resides,
+ // generally specifying which datacenter should house your resources.
+ // Required only for services that span multiple regions.
+ Region string
+
+ // Availability [optional] is the visibility of the endpoint to be returned.
+ // Valid types include the constants AvailabilityPublic, AvailabilityInternal,
+ // or AvailabilityAdmin from this package.
+ //
+ // Availability is not required, and defaults to AvailabilityPublic. Not all
+ // providers or services offer all Availability options.
+ Availability Availability
+}
+
+/*
+EndpointLocator is an internal function to be used by provider implementations.
+
+It provides an implementation that locates a single endpoint from a service
+catalog for a specific ProviderClient based on user-provided EndpointOpts. The
+provider then uses it to discover related ServiceClients.
+*/
+type EndpointLocator func(EndpointOpts) (string, error)
+
+// ApplyDefaults is an internal method to be used by provider implementations.
+//
+// It sets EndpointOpts fields if not already set, including a default type.
+// Currently, EndpointOpts.Availability defaults to the public endpoint.
+func (eo *EndpointOpts) ApplyDefaults(t string) {
+ if eo.Type == "" {
+ eo.Type = t
+ }
+ if eo.Availability == "" {
+ eo.Availability = AvailabilityPublic
+ }
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/errors.go b/vendor/github.com/gophercloud/gophercloud/errors.go
new file mode 100644
index 000000000..4bf102468
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/errors.go
@@ -0,0 +1,460 @@
+package gophercloud
+
+import (
+ "fmt"
+ "strings"
+)
+
+// BaseError is an error type that all other error types embed.
+type BaseError struct {
+ DefaultErrString string
+ Info string
+}
+
+func (e BaseError) Error() string {
+ e.DefaultErrString = "An error occurred while executing a Gophercloud request."
+ return e.choseErrString()
+}
+
+func (e BaseError) choseErrString() string {
+ if e.Info != "" {
+ return e.Info
+ }
+ return e.DefaultErrString
+}
+
+// ErrMissingInput is the error when input is required in a particular
+// situation but not provided by the user
+type ErrMissingInput struct {
+ BaseError
+ Argument string
+}
+
+func (e ErrMissingInput) Error() string {
+ e.DefaultErrString = fmt.Sprintf("Missing input for argument [%s]", e.Argument)
+ return e.choseErrString()
+}
+
+// ErrInvalidInput is an error type used for most non-HTTP Gophercloud errors.
+type ErrInvalidInput struct {
+ ErrMissingInput
+ Value interface{}
+}
+
+func (e ErrInvalidInput) Error() string {
+ e.DefaultErrString = fmt.Sprintf("Invalid input provided for argument [%s]: [%+v]", e.Argument, e.Value)
+ return e.choseErrString()
+}
+
+// ErrMissingEnvironmentVariable is the error when environment variable is required
+// in a particular situation but not provided by the user
+type ErrMissingEnvironmentVariable struct {
+ BaseError
+ EnvironmentVariable string
+}
+
+func (e ErrMissingEnvironmentVariable) Error() string {
+ e.DefaultErrString = fmt.Sprintf("Missing environment variable [%s]", e.EnvironmentVariable)
+ return e.choseErrString()
+}
+
+// ErrMissingAnyoneOfEnvironmentVariables is the error when anyone of the environment variables
+// is required in a particular situation but not provided by the user
+type ErrMissingAnyoneOfEnvironmentVariables struct {
+ BaseError
+ EnvironmentVariables []string
+}
+
+func (e ErrMissingAnyoneOfEnvironmentVariables) Error() string {
+ e.DefaultErrString = fmt.Sprintf(
+ "Missing one of the following environment variables [%s]",
+ strings.Join(e.EnvironmentVariables, ", "),
+ )
+ return e.choseErrString()
+}
+
+// ErrUnexpectedResponseCode is returned by the Request method when a response code other than
+// those listed in OkCodes is encountered.
+type ErrUnexpectedResponseCode struct {
+ BaseError
+ URL string
+ Method string
+ Expected []int
+ Actual int
+ Body []byte
+}
+
+func (e ErrUnexpectedResponseCode) Error() string {
+ e.DefaultErrString = fmt.Sprintf(
+ "Expected HTTP response code %v when accessing [%s %s], but got %d instead\n%s",
+ e.Expected, e.Method, e.URL, e.Actual, e.Body,
+ )
+ return e.choseErrString()
+}
+
+// ErrDefault400 is the default error type returned on a 400 HTTP response code.
+type ErrDefault400 struct {
+ ErrUnexpectedResponseCode
+}
+
+// ErrDefault401 is the default error type returned on a 401 HTTP response code.
+type ErrDefault401 struct {
+ ErrUnexpectedResponseCode
+}
+
+// ErrDefault403 is the default error type returned on a 403 HTTP response code.
+type ErrDefault403 struct {
+ ErrUnexpectedResponseCode
+}
+
+// ErrDefault404 is the default error type returned on a 404 HTTP response code.
+type ErrDefault404 struct {
+ ErrUnexpectedResponseCode
+}
+
+// ErrDefault405 is the default error type returned on a 405 HTTP response code.
+type ErrDefault405 struct {
+ ErrUnexpectedResponseCode
+}
+
+// ErrDefault408 is the default error type returned on a 408 HTTP response code.
+type ErrDefault408 struct {
+ ErrUnexpectedResponseCode
+}
+
+// ErrDefault429 is the default error type returned on a 429 HTTP response code.
+type ErrDefault429 struct {
+ ErrUnexpectedResponseCode
+}
+
+// ErrDefault500 is the default error type returned on a 500 HTTP response code.
+type ErrDefault500 struct {
+ ErrUnexpectedResponseCode
+}
+
+// ErrDefault503 is the default error type returned on a 503 HTTP response code.
+type ErrDefault503 struct {
+ ErrUnexpectedResponseCode
+}
+
+func (e ErrDefault400) Error() string {
+ e.DefaultErrString = fmt.Sprintf(
+ "Bad request with: [%s %s], error message: %s",
+ e.Method, e.URL, e.Body,
+ )
+ return e.choseErrString()
+}
+func (e ErrDefault401) Error() string {
+ return "Authentication failed"
+}
+func (e ErrDefault403) Error() string {
+ e.DefaultErrString = fmt.Sprintf(
+ "Request forbidden: [%s %s], error message: %s",
+ e.Method, e.URL, e.Body,
+ )
+ return e.choseErrString()
+}
+func (e ErrDefault404) Error() string {
+ return "Resource not found"
+}
+func (e ErrDefault405) Error() string {
+ return "Method not allowed"
+}
+func (e ErrDefault408) Error() string {
+ return "The server timed out waiting for the request"
+}
+func (e ErrDefault429) Error() string {
+ return "Too many requests have been sent in a given amount of time. Pause" +
+ " requests, wait up to one minute, and try again."
+}
+func (e ErrDefault500) Error() string {
+ return "Internal Server Error"
+}
+func (e ErrDefault503) Error() string {
+ return "The service is currently unable to handle the request due to a temporary" +
+ " overloading or maintenance. This is a temporary condition. Try again later."
+}
+
+// Err400er is the interface resource error types implement to override the error message
+// from a 400 error.
+type Err400er interface {
+ Error400(ErrUnexpectedResponseCode) error
+}
+
+// Err401er is the interface resource error types implement to override the error message
+// from a 401 error.
+type Err401er interface {
+ Error401(ErrUnexpectedResponseCode) error
+}
+
+// Err403er is the interface resource error types implement to override the error message
+// from a 403 error.
+type Err403er interface {
+ Error403(ErrUnexpectedResponseCode) error
+}
+
+// Err404er is the interface resource error types implement to override the error message
+// from a 404 error.
+type Err404er interface {
+ Error404(ErrUnexpectedResponseCode) error
+}
+
+// Err405er is the interface resource error types implement to override the error message
+// from a 405 error.
+type Err405er interface {
+ Error405(ErrUnexpectedResponseCode) error
+}
+
+// Err408er is the interface resource error types implement to override the error message
+// from a 408 error.
+type Err408er interface {
+ Error408(ErrUnexpectedResponseCode) error
+}
+
+// Err429er is the interface resource error types implement to override the error message
+// from a 429 error.
+type Err429er interface {
+ Error429(ErrUnexpectedResponseCode) error
+}
+
+// Err500er is the interface resource error types implement to override the error message
+// from a 500 error.
+type Err500er interface {
+ Error500(ErrUnexpectedResponseCode) error
+}
+
+// Err503er is the interface resource error types implement to override the error message
+// from a 503 error.
+type Err503er interface {
+ Error503(ErrUnexpectedResponseCode) error
+}
+
+// ErrTimeOut is the error type returned when an operations times out.
+type ErrTimeOut struct {
+ BaseError
+}
+
+func (e ErrTimeOut) Error() string {
+ e.DefaultErrString = "A time out occurred"
+ return e.choseErrString()
+}
+
+// ErrUnableToReauthenticate is the error type returned when reauthentication fails.
+type ErrUnableToReauthenticate struct {
+ BaseError
+ ErrOriginal error
+}
+
+func (e ErrUnableToReauthenticate) Error() string {
+ e.DefaultErrString = fmt.Sprintf("Unable to re-authenticate: %s", e.ErrOriginal)
+ return e.choseErrString()
+}
+
+// ErrErrorAfterReauthentication is the error type returned when reauthentication
+// succeeds, but an error occurs afterword (usually an HTTP error).
+type ErrErrorAfterReauthentication struct {
+ BaseError
+ ErrOriginal error
+}
+
+func (e ErrErrorAfterReauthentication) Error() string {
+ e.DefaultErrString = fmt.Sprintf("Successfully re-authenticated, but got error executing request: %s", e.ErrOriginal)
+ return e.choseErrString()
+}
+
+// ErrServiceNotFound is returned when no service in a service catalog matches
+// the provided EndpointOpts. This is generally returned by provider service
+// factory methods like "NewComputeV2()" and can mean that a service is not
+// enabled for your account.
+type ErrServiceNotFound struct {
+ BaseError
+}
+
+func (e ErrServiceNotFound) Error() string {
+ e.DefaultErrString = "No suitable service could be found in the service catalog."
+ return e.choseErrString()
+}
+
+// ErrEndpointNotFound is returned when no available endpoints match the
+// provided EndpointOpts. This is also generally returned by provider service
+// factory methods, and usually indicates that a region was specified
+// incorrectly.
+type ErrEndpointNotFound struct {
+ BaseError
+}
+
+func (e ErrEndpointNotFound) Error() string {
+ e.DefaultErrString = "No suitable endpoint could be found in the service catalog."
+ return e.choseErrString()
+}
+
+// ErrResourceNotFound is the error when trying to retrieve a resource's
+// ID by name and the resource doesn't exist.
+type ErrResourceNotFound struct {
+ BaseError
+ Name string
+ ResourceType string
+}
+
+func (e ErrResourceNotFound) Error() string {
+ e.DefaultErrString = fmt.Sprintf("Unable to find %s with name %s", e.ResourceType, e.Name)
+ return e.choseErrString()
+}
+
+// ErrMultipleResourcesFound is the error when trying to retrieve a resource's
+// ID by name and multiple resources have the user-provided name.
+type ErrMultipleResourcesFound struct {
+ BaseError
+ Name string
+ Count int
+ ResourceType string
+}
+
+func (e ErrMultipleResourcesFound) Error() string {
+ e.DefaultErrString = fmt.Sprintf("Found %d %ss matching %s", e.Count, e.ResourceType, e.Name)
+ return e.choseErrString()
+}
+
+// ErrUnexpectedType is the error when an unexpected type is encountered
+type ErrUnexpectedType struct {
+ BaseError
+ Expected string
+ Actual string
+}
+
+func (e ErrUnexpectedType) Error() string {
+ e.DefaultErrString = fmt.Sprintf("Expected %s but got %s", e.Expected, e.Actual)
+ return e.choseErrString()
+}
+
+func unacceptedAttributeErr(attribute string) string {
+ return fmt.Sprintf("The base Identity V3 API does not accept authentication by %s", attribute)
+}
+
+func redundantWithTokenErr(attribute string) string {
+ return fmt.Sprintf("%s may not be provided when authenticating with a TokenID", attribute)
+}
+
+func redundantWithUserID(attribute string) string {
+ return fmt.Sprintf("%s may not be provided when authenticating with a UserID", attribute)
+}
+
+// ErrAPIKeyProvided indicates that an APIKey was provided but can't be used.
+type ErrAPIKeyProvided struct{ BaseError }
+
+func (e ErrAPIKeyProvided) Error() string {
+ return unacceptedAttributeErr("APIKey")
+}
+
+// ErrTenantIDProvided indicates that a TenantID was provided but can't be used.
+type ErrTenantIDProvided struct{ BaseError }
+
+func (e ErrTenantIDProvided) Error() string {
+ return unacceptedAttributeErr("TenantID")
+}
+
+// ErrTenantNameProvided indicates that a TenantName was provided but can't be used.
+type ErrTenantNameProvided struct{ BaseError }
+
+func (e ErrTenantNameProvided) Error() string {
+ return unacceptedAttributeErr("TenantName")
+}
+
+// ErrUsernameWithToken indicates that a Username was provided, but token authentication is being used instead.
+type ErrUsernameWithToken struct{ BaseError }
+
+func (e ErrUsernameWithToken) Error() string {
+ return redundantWithTokenErr("Username")
+}
+
+// ErrUserIDWithToken indicates that a UserID was provided, but token authentication is being used instead.
+type ErrUserIDWithToken struct{ BaseError }
+
+func (e ErrUserIDWithToken) Error() string {
+ return redundantWithTokenErr("UserID")
+}
+
+// ErrDomainIDWithToken indicates that a DomainID was provided, but token authentication is being used instead.
+type ErrDomainIDWithToken struct{ BaseError }
+
+func (e ErrDomainIDWithToken) Error() string {
+ return redundantWithTokenErr("DomainID")
+}
+
+// ErrDomainNameWithToken indicates that a DomainName was provided, but token authentication is being used instead.s
+type ErrDomainNameWithToken struct{ BaseError }
+
+func (e ErrDomainNameWithToken) Error() string {
+ return redundantWithTokenErr("DomainName")
+}
+
+// ErrUsernameOrUserID indicates that neither username nor userID are specified, or both are at once.
+type ErrUsernameOrUserID struct{ BaseError }
+
+func (e ErrUsernameOrUserID) Error() string {
+ return "Exactly one of Username and UserID must be provided for password authentication"
+}
+
+// ErrDomainIDWithUserID indicates that a DomainID was provided, but unnecessary because a UserID is being used.
+type ErrDomainIDWithUserID struct{ BaseError }
+
+func (e ErrDomainIDWithUserID) Error() string {
+ return redundantWithUserID("DomainID")
+}
+
+// ErrDomainNameWithUserID indicates that a DomainName was provided, but unnecessary because a UserID is being used.
+type ErrDomainNameWithUserID struct{ BaseError }
+
+func (e ErrDomainNameWithUserID) Error() string {
+ return redundantWithUserID("DomainName")
+}
+
+// ErrDomainIDOrDomainName indicates that a username was provided, but no domain to scope it.
+// It may also indicate that both a DomainID and a DomainName were provided at once.
+type ErrDomainIDOrDomainName struct{ BaseError }
+
+func (e ErrDomainIDOrDomainName) Error() string {
+ return "You must provide exactly one of DomainID or DomainName to authenticate by Username"
+}
+
+// ErrMissingPassword indicates that no password was provided and no token is available.
+type ErrMissingPassword struct{ BaseError }
+
+func (e ErrMissingPassword) Error() string {
+ return "You must provide a password to authenticate"
+}
+
+// ErrScopeDomainIDOrDomainName indicates that a domain ID or Name was required in a Scope, but not present.
+type ErrScopeDomainIDOrDomainName struct{ BaseError }
+
+func (e ErrScopeDomainIDOrDomainName) Error() string {
+ return "You must provide exactly one of DomainID or DomainName in a Scope with ProjectName"
+}
+
+// ErrScopeProjectIDOrProjectName indicates that both a ProjectID and a ProjectName were provided in a Scope.
+type ErrScopeProjectIDOrProjectName struct{ BaseError }
+
+func (e ErrScopeProjectIDOrProjectName) Error() string {
+ return "You must provide at most one of ProjectID or ProjectName in a Scope"
+}
+
+// ErrScopeProjectIDAlone indicates that a ProjectID was provided with other constraints in a Scope.
+type ErrScopeProjectIDAlone struct{ BaseError }
+
+func (e ErrScopeProjectIDAlone) Error() string {
+ return "ProjectID must be supplied alone in a Scope"
+}
+
+// ErrScopeEmpty indicates that no credentials were provided in a Scope.
+type ErrScopeEmpty struct{ BaseError }
+
+func (e ErrScopeEmpty) Error() string {
+ return "You must provide either a Project or Domain in a Scope"
+}
+
+// ErrAppCredMissingSecret indicates that no Application Credential Secret was provided with Application Credential ID or Name
+type ErrAppCredMissingSecret struct{ BaseError }
+
+func (e ErrAppCredMissingSecret) Error() string {
+ return "You must provide an Application Credential Secret"
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go b/vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go
new file mode 100644
index 000000000..0bb1f4837
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/auth_env.go
@@ -0,0 +1,114 @@
+package openstack
+
+import (
+ "os"
+
+ "github.com/gophercloud/gophercloud"
+)
+
+var nilOptions = gophercloud.AuthOptions{}
+
+/*
+AuthOptionsFromEnv fills out an identity.AuthOptions structure with the
+settings found on the various OpenStack OS_* environment variables.
+
+The following variables provide sources of truth: OS_AUTH_URL, OS_USERNAME,
+OS_PASSWORD, OS_TENANT_ID, and OS_TENANT_NAME.
+
+Of these, OS_USERNAME, OS_PASSWORD, and OS_AUTH_URL must have settings,
+or an error will result. OS_TENANT_ID, OS_TENANT_NAME, OS_PROJECT_ID, and
+OS_PROJECT_NAME are optional.
+
+OS_TENANT_ID and OS_TENANT_NAME are mutually exclusive to OS_PROJECT_ID and
+OS_PROJECT_NAME. If OS_PROJECT_ID and OS_PROJECT_NAME are set, they will
+still be referred as "tenant" in Gophercloud.
+
+To use this function, first set the OS_* environment variables (for example,
+by sourcing an `openrc` file), then:
+
+ opts, err := openstack.AuthOptionsFromEnv()
+ provider, err := openstack.AuthenticatedClient(opts)
+*/
+func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
+ authURL := os.Getenv("OS_AUTH_URL")
+ username := os.Getenv("OS_USERNAME")
+ userID := os.Getenv("OS_USERID")
+ password := os.Getenv("OS_PASSWORD")
+ tenantID := os.Getenv("OS_TENANT_ID")
+ tenantName := os.Getenv("OS_TENANT_NAME")
+ domainID := os.Getenv("OS_DOMAIN_ID")
+ domainName := os.Getenv("OS_DOMAIN_NAME")
+ applicationCredentialID := os.Getenv("OS_APPLICATION_CREDENTIAL_ID")
+ applicationCredentialName := os.Getenv("OS_APPLICATION_CREDENTIAL_NAME")
+ applicationCredentialSecret := os.Getenv("OS_APPLICATION_CREDENTIAL_SECRET")
+
+ // If OS_PROJECT_ID is set, overwrite tenantID with the value.
+ if v := os.Getenv("OS_PROJECT_ID"); v != "" {
+ tenantID = v
+ }
+
+ // If OS_PROJECT_NAME is set, overwrite tenantName with the value.
+ if v := os.Getenv("OS_PROJECT_NAME"); v != "" {
+ tenantName = v
+ }
+
+ if authURL == "" {
+ err := gophercloud.ErrMissingEnvironmentVariable{
+ EnvironmentVariable: "OS_AUTH_URL",
+ }
+ return nilOptions, err
+ }
+
+ if userID == "" && username == "" {
+ // Empty username and userID could be ignored, when applicationCredentialID and applicationCredentialSecret are set
+ if applicationCredentialID == "" && applicationCredentialSecret == "" {
+ err := gophercloud.ErrMissingAnyoneOfEnvironmentVariables{
+ EnvironmentVariables: []string{"OS_USERID", "OS_USERNAME"},
+ }
+ return nilOptions, err
+ }
+ }
+
+ if password == "" && applicationCredentialID == "" && applicationCredentialName == "" {
+ err := gophercloud.ErrMissingEnvironmentVariable{
+ EnvironmentVariable: "OS_PASSWORD",
+ }
+ return nilOptions, err
+ }
+
+ if (applicationCredentialID != "" || applicationCredentialName != "") && applicationCredentialSecret == "" {
+ err := gophercloud.ErrMissingEnvironmentVariable{
+ EnvironmentVariable: "OS_APPLICATION_CREDENTIAL_SECRET",
+ }
+ return nilOptions, err
+ }
+
+ if applicationCredentialID == "" && applicationCredentialName != "" && applicationCredentialSecret != "" {
+ if userID == "" && username == "" {
+ return nilOptions, gophercloud.ErrMissingAnyoneOfEnvironmentVariables{
+ EnvironmentVariables: []string{"OS_USERID", "OS_USERNAME"},
+ }
+ }
+ if username != "" && domainID == "" && domainName == "" {
+ return nilOptions, gophercloud.ErrMissingAnyoneOfEnvironmentVariables{
+ EnvironmentVariables: []string{"OS_DOMAIN_ID", "OS_DOMAIN_NAME"},
+ }
+ }
+ }
+
+ ao := gophercloud.AuthOptions{
+ IdentityEndpoint: authURL,
+ UserID: userID,
+ Username: username,
+ Password: password,
+ TenantID: tenantID,
+ TenantName: tenantName,
+ DomainID: domainID,
+ DomainName: domainName,
+ ApplicationCredentialID: applicationCredentialID,
+ ApplicationCredentialName: applicationCredentialName,
+ ApplicationCredentialSecret: applicationCredentialSecret,
+ }
+
+ return ao, nil
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/client.go b/vendor/github.com/gophercloud/gophercloud/openstack/client.go
new file mode 100644
index 000000000..064a70dbe
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/client.go
@@ -0,0 +1,432 @@
+package openstack
+
+import (
+ "fmt"
+ "reflect"
+
+ "github.com/gophercloud/gophercloud"
+ tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
+ tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
+ "github.com/gophercloud/gophercloud/openstack/utils"
+)
+
+const (
+ // v2 represents Keystone v2.
+ // It should never increase beyond 2.0.
+ v2 = "v2.0"
+
+ // v3 represents Keystone v3.
+ // The version can be anything from v3 to v3.x.
+ v3 = "v3"
+)
+
+/*
+NewClient prepares an unauthenticated ProviderClient instance.
+Most users will probably prefer using the AuthenticatedClient function
+instead.
+
+This is useful if you wish to explicitly control the version of the identity
+service that's used for authentication explicitly, for example.
+
+A basic example of using this would be:
+
+ ao, err := openstack.AuthOptionsFromEnv()
+ provider, err := openstack.NewClient(ao.IdentityEndpoint)
+ client, err := openstack.NewIdentityV3(provider, gophercloud.EndpointOpts{})
+*/
+func NewClient(endpoint string) (*gophercloud.ProviderClient, error) {
+ base, err := utils.BaseEndpoint(endpoint)
+ if err != nil {
+ return nil, err
+ }
+
+ endpoint = gophercloud.NormalizeURL(endpoint)
+ base = gophercloud.NormalizeURL(base)
+
+ p := new(gophercloud.ProviderClient)
+ p.IdentityBase = base
+ p.IdentityEndpoint = endpoint
+ p.UseTokenLock()
+
+ return p, nil
+}
+
+/*
+AuthenticatedClient logs in to an OpenStack cloud found at the identity endpoint
+specified by the options, acquires a token, and returns a Provider Client
+instance that's ready to operate.
+
+If the full path to a versioned identity endpoint was specified (example:
+http://example.com:5000/v3), that path will be used as the endpoint to query.
+
+If a versionless endpoint was specified (example: http://example.com:5000/),
+the endpoint will be queried to determine which versions of the identity service
+are available, then chooses the most recent or most supported version.
+
+Example:
+
+ ao, err := openstack.AuthOptionsFromEnv()
+ provider, err := openstack.AuthenticatedClient(ao)
+ client, err := openstack.NewNetworkV2(client, gophercloud.EndpointOpts{
+ Region: os.Getenv("OS_REGION_NAME"),
+ })
+*/
+func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) {
+ client, err := NewClient(options.IdentityEndpoint)
+ if err != nil {
+ return nil, err
+ }
+
+ err = Authenticate(client, options)
+ if err != nil {
+ return nil, err
+ }
+ return client, nil
+}
+
+// Authenticate or re-authenticate against the most recent identity service
+// supported at the provided endpoint.
+func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
+ versions := []*utils.Version{
+ {ID: v2, Priority: 20, Suffix: "/v2.0/"},
+ {ID: v3, Priority: 30, Suffix: "/v3/"},
+ }
+
+ chosen, endpoint, err := utils.ChooseVersion(client, versions)
+ if err != nil {
+ return err
+ }
+
+ switch chosen.ID {
+ case v2:
+ return v2auth(client, endpoint, options, gophercloud.EndpointOpts{})
+ case v3:
+ return v3auth(client, endpoint, &options, gophercloud.EndpointOpts{})
+ default:
+ // The switch statement must be out of date from the versions list.
+ return fmt.Errorf("Unrecognized identity version: %s", chosen.ID)
+ }
+}
+
+// AuthenticateV2 explicitly authenticates against the identity v2 endpoint.
+func AuthenticateV2(client *gophercloud.ProviderClient, options gophercloud.AuthOptions, eo gophercloud.EndpointOpts) error {
+ return v2auth(client, "", options, eo)
+}
+
+func v2auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions, eo gophercloud.EndpointOpts) error {
+ v2Client, err := NewIdentityV2(client, eo)
+ if err != nil {
+ return err
+ }
+
+ if endpoint != "" {
+ v2Client.Endpoint = endpoint
+ }
+
+ v2Opts := tokens2.AuthOptions{
+ IdentityEndpoint: options.IdentityEndpoint,
+ Username: options.Username,
+ Password: options.Password,
+ TenantID: options.TenantID,
+ TenantName: options.TenantName,
+ AllowReauth: options.AllowReauth,
+ TokenID: options.TokenID,
+ }
+
+ result := tokens2.Create(v2Client, v2Opts)
+
+ err = client.SetTokenAndAuthResult(result)
+ if err != nil {
+ return err
+ }
+
+ catalog, err := result.ExtractServiceCatalog()
+ if err != nil {
+ return err
+ }
+
+ if options.AllowReauth {
+ // here we're creating a throw-away client (tac). it's a copy of the user's provider client, but
+ // with the token and reauth func zeroed out. combined with setting `AllowReauth` to `false`,
+ // this should retry authentication only once
+ tac := *client
+ tac.SetThrowaway(true)
+ tac.ReauthFunc = nil
+ tac.SetTokenAndAuthResult(nil)
+ tao := options
+ tao.AllowReauth = false
+ client.ReauthFunc = func() error {
+ err := v2auth(&tac, endpoint, tao, eo)
+ if err != nil {
+ return err
+ }
+ client.CopyTokenFrom(&tac)
+ return nil
+ }
+ }
+ client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
+ return V2EndpointURL(catalog, opts)
+ }
+
+ return nil
+}
+
+// AuthenticateV3 explicitly authenticates against the identity v3 service.
+func AuthenticateV3(client *gophercloud.ProviderClient, options tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error {
+ return v3auth(client, "", options, eo)
+}
+
+func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error {
+ // Override the generated service endpoint with the one returned by the version endpoint.
+ v3Client, err := NewIdentityV3(client, eo)
+ if err != nil {
+ return err
+ }
+
+ if endpoint != "" {
+ v3Client.Endpoint = endpoint
+ }
+
+ result := tokens3.Create(v3Client, opts)
+
+ err = client.SetTokenAndAuthResult(result)
+ if err != nil {
+ return err
+ }
+
+ catalog, err := result.ExtractServiceCatalog()
+ if err != nil {
+ return err
+ }
+
+ if opts.CanReauth() {
+ // here we're creating a throw-away client (tac). it's a copy of the user's provider client, but
+ // with the token and reauth func zeroed out. combined with setting `AllowReauth` to `false`,
+ // this should retry authentication only once
+ tac := *client
+ tac.SetThrowaway(true)
+ tac.ReauthFunc = nil
+ tac.SetTokenAndAuthResult(nil)
+ var tao tokens3.AuthOptionsBuilder
+ switch ot := opts.(type) {
+ case *gophercloud.AuthOptions:
+ o := *ot
+ o.AllowReauth = false
+ tao = &o
+ case *tokens3.AuthOptions:
+ o := *ot
+ o.AllowReauth = false
+ tao = &o
+ default:
+ tao = opts
+ }
+ client.ReauthFunc = func() error {
+ err := v3auth(&tac, endpoint, tao, eo)
+ if err != nil {
+ return err
+ }
+ client.CopyTokenFrom(&tac)
+ return nil
+ }
+ }
+ client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
+ return V3EndpointURL(catalog, opts)
+ }
+
+ return nil
+}
+
+// NewIdentityV2 creates a ServiceClient that may be used to interact with the
+// v2 identity service.
+func NewIdentityV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+ endpoint := client.IdentityBase + "v2.0/"
+ clientType := "identity"
+ var err error
+ if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
+ eo.ApplyDefaults(clientType)
+ endpoint, err = client.EndpointLocator(eo)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return &gophercloud.ServiceClient{
+ ProviderClient: client,
+ Endpoint: endpoint,
+ Type: clientType,
+ }, nil
+}
+
+// NewIdentityV3 creates a ServiceClient that may be used to access the v3
+// identity service.
+func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+ endpoint := client.IdentityBase + "v3/"
+ clientType := "identity"
+ var err error
+ if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
+ eo.ApplyDefaults(clientType)
+ endpoint, err = client.EndpointLocator(eo)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ // Ensure endpoint still has a suffix of v3.
+ // This is because EndpointLocator might have found a versionless
+ // endpoint or the published endpoint is still /v2.0. In both
+ // cases, we need to fix the endpoint to point to /v3.
+ base, err := utils.BaseEndpoint(endpoint)
+ if err != nil {
+ return nil, err
+ }
+
+ base = gophercloud.NormalizeURL(base)
+
+ endpoint = base + "v3/"
+
+ return &gophercloud.ServiceClient{
+ ProviderClient: client,
+ Endpoint: endpoint,
+ Type: clientType,
+ }, nil
+}
+
+func initClientOpts(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts, clientType string) (*gophercloud.ServiceClient, error) {
+ sc := new(gophercloud.ServiceClient)
+ eo.ApplyDefaults(clientType)
+ url, err := client.EndpointLocator(eo)
+ if err != nil {
+ return sc, err
+ }
+ sc.ProviderClient = client
+ sc.Endpoint = url
+ sc.Type = clientType
+ return sc, nil
+}
+
+// NewBareMetalV1 creates a ServiceClient that may be used with the v1
+// bare metal package.
+func NewBareMetalV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+ return initClientOpts(client, eo, "baremetal")
+}
+
+// NewObjectStorageV1 creates a ServiceClient that may be used with the v1
+// object storage package.
+func NewObjectStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+ return initClientOpts(client, eo, "object-store")
+}
+
+// NewComputeV2 creates a ServiceClient that may be used with the v2 compute
+// package.
+func NewComputeV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+ return initClientOpts(client, eo, "compute")
+}
+
+// NewNetworkV2 creates a ServiceClient that may be used with the v2 network
+// package.
+func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+ sc, err := initClientOpts(client, eo, "network")
+ sc.ResourceBase = sc.Endpoint + "v2.0/"
+ return sc, err
+}
+
+// NewBlockStorageV1 creates a ServiceClient that may be used to access the v1
+// block storage service.
+func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+ return initClientOpts(client, eo, "volume")
+}
+
+// NewBlockStorageV2 creates a ServiceClient that may be used to access the v2
+// block storage service.
+func NewBlockStorageV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+ return initClientOpts(client, eo, "volumev2")
+}
+
+// NewBlockStorageV3 creates a ServiceClient that may be used to access the v3 block storage service.
+func NewBlockStorageV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+ return initClientOpts(client, eo, "volumev3")
+}
+
+// NewSharedFileSystemV2 creates a ServiceClient that may be used to access the v2 shared file system service.
+func NewSharedFileSystemV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+ return initClientOpts(client, eo, "sharev2")
+}
+
+// NewCDNV1 creates a ServiceClient that may be used to access the OpenStack v1
+// CDN service.
+func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+ return initClientOpts(client, eo, "cdn")
+}
+
+// NewOrchestrationV1 creates a ServiceClient that may be used to access the v1
+// orchestration service.
+func NewOrchestrationV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+ return initClientOpts(client, eo, "orchestration")
+}
+
+// NewDBV1 creates a ServiceClient that may be used to access the v1 DB service.
+func NewDBV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+ return initClientOpts(client, eo, "database")
+}
+
+// NewDNSV2 creates a ServiceClient that may be used to access the v2 DNS
+// service.
+func NewDNSV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+ sc, err := initClientOpts(client, eo, "dns")
+ sc.ResourceBase = sc.Endpoint + "v2/"
+ return sc, err
+}
+
+// NewImageServiceV2 creates a ServiceClient that may be used to access the v2
+// image service.
+func NewImageServiceV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+ sc, err := initClientOpts(client, eo, "image")
+ sc.ResourceBase = sc.Endpoint + "v2/"
+ return sc, err
+}
+
+// NewLoadBalancerV2 creates a ServiceClient that may be used to access the v2
+// load balancer service.
+func NewLoadBalancerV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+ sc, err := initClientOpts(client, eo, "load-balancer")
+ sc.ResourceBase = sc.Endpoint + "v2.0/"
+ return sc, err
+}
+
+// NewClusteringV1 creates a ServiceClient that may be used with the v1 clustering
+// package.
+func NewClusteringV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+ return initClientOpts(client, eo, "clustering")
+}
+
+// NewMessagingV2 creates a ServiceClient that may be used with the v2 messaging
+// service.
+func NewMessagingV2(client *gophercloud.ProviderClient, clientID string, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+ sc, err := initClientOpts(client, eo, "messaging")
+ sc.MoreHeaders = map[string]string{"Client-ID": clientID}
+ return sc, err
+}
+
+// NewContainerV1 creates a ServiceClient that may be used with v1 container package
+func NewContainerV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+ return initClientOpts(client, eo, "container")
+}
+
+// NewKeyManagerV1 creates a ServiceClient that may be used with the v1 key
+// manager service.
+func NewKeyManagerV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+ sc, err := initClientOpts(client, eo, "key-manager")
+ sc.ResourceBase = sc.Endpoint + "v1/"
+ return sc, err
+}
+
+// NewContainerInfraV1 creates a ServiceClient that may be used with the v1 container infra management
+// package.
+func NewContainerInfraV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+ return initClientOpts(client, eo, "container-infra")
+}
+
+// NewWorkflowV2 creates a ServiceClient that may be used with the v2 workflow management package.
+func NewWorkflowV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+ return initClientOpts(client, eo, "workflowv2")
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/doc.go
new file mode 100644
index 000000000..617fafa63
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/doc.go
@@ -0,0 +1,54 @@
+/*
+Package recordsets provides information and interaction with the zone API
+resource for the OpenStack DNS service.
+
+Example to List RecordSets by Zone
+
+ listOpts := recordsets.ListOpts{
+ Type: "A",
+ }
+
+ zoneID := "fff121f5-c506-410a-a69e-2d73ef9cbdbd"
+
+ allPages, err := recordsets.ListByZone(dnsClient, zoneID, listOpts).AllPages()
+ if err != nil {
+ panic(err)
+ }
+
+ allRRs, err := recordsets.ExtractRecordSets(allPages()
+ if err != nil {
+ panic(err)
+ }
+
+ for _, rr := range allRRs {
+ fmt.Printf("%+v\n", rr)
+ }
+
+Example to Create a RecordSet
+
+ createOpts := recordsets.CreateOpts{
+ Name: "example.com.",
+ Type: "A",
+ TTL: 3600,
+ Description: "This is a recordset.",
+ Records: []string{"10.1.0.2"},
+ }
+
+ zoneID := "fff121f5-c506-410a-a69e-2d73ef9cbdbd"
+
+ rr, err := recordsets.Create(dnsClient, zoneID, createOpts).Extract()
+ if err != nil {
+ panic(err)
+ }
+
+Example to Delete a RecordSet
+
+ zoneID := "fff121f5-c506-410a-a69e-2d73ef9cbdbd"
+ recordsetID := "d96ed01a-b439-4eb8-9b90-7a9f71017f7b"
+
+ err := recordsets.Delete(dnsClient, zoneID, recordsetID).ExtractErr()
+ if err != nil {
+ panic(err)
+ }
+*/
+package recordsets
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/requests.go
new file mode 100644
index 000000000..2bc457972
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/requests.go
@@ -0,0 +1,166 @@
+package recordsets
+
+import (
+ "github.com/gophercloud/gophercloud"
+ "github.com/gophercloud/gophercloud/pagination"
+)
+
+// ListOptsBuilder allows extensions to add additional parameters to the
+// List request.
+type ListOptsBuilder interface {
+ ToRecordSetListQuery() (string, error)
+}
+
+// ListOpts allows the filtering and sorting of paginated collections through
+// the API. Filtering is achieved by passing in struct field values that map to
+// the server attributes you want to see returned. Marker and Limit are used
+// for pagination.
+// https://developer.openstack.org/api-ref/dns/
+type ListOpts struct {
+ // Integer value for the limit of values to return.
+ Limit int `q:"limit"`
+
+ // UUID of the recordset at which you want to set a marker.
+ Marker string `q:"marker"`
+
+ Data string `q:"data"`
+ Description string `q:"description"`
+ Name string `q:"name"`
+ SortDir string `q:"sort_dir"`
+ SortKey string `q:"sort_key"`
+ Status string `q:"status"`
+ TTL int `q:"ttl"`
+ Type string `q:"type"`
+ ZoneID string `q:"zone_id"`
+}
+
+// ToRecordSetListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToRecordSetListQuery() (string, error) {
+ q, err := gophercloud.BuildQueryString(opts)
+ return q.String(), err
+}
+
+// ListByZone implements the recordset list request.
+func ListByZone(client *gophercloud.ServiceClient, zoneID string, opts ListOptsBuilder) pagination.Pager {
+ url := baseURL(client, zoneID)
+ if opts != nil {
+ query, err := opts.ToRecordSetListQuery()
+ if err != nil {
+ return pagination.Pager{Err: err}
+ }
+ url += query
+ }
+ return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
+ return RecordSetPage{pagination.LinkedPageBase{PageResult: r}}
+ })
+}
+
+// Get implements the recordset Get request.
+func Get(client *gophercloud.ServiceClient, zoneID string, rrsetID string) (r GetResult) {
+ _, r.Err = client.Get(rrsetURL(client, zoneID, rrsetID), &r.Body, nil)
+ return
+}
+
+// CreateOptsBuilder allows extensions to add additional attributes to the
+// Create request.
+type CreateOptsBuilder interface {
+ ToRecordSetCreateMap() (map[string]interface{}, error)
+}
+
+// CreateOpts specifies the base attributes that may be used to create a
+// RecordSet.
+type CreateOpts struct {
+ // Name is the name of the RecordSet.
+ Name string `json:"name" required:"true"`
+
+ // Description is a description of the RecordSet.
+ Description string `json:"description,omitempty"`
+
+ // Records are the DNS records of the RecordSet.
+ Records []string `json:"records,omitempty"`
+
+ // TTL is the time to live of the RecordSet.
+ TTL int `json:"ttl,omitempty"`
+
+ // Type is the RRTYPE of the RecordSet.
+ Type string `json:"type,omitempty"`
+}
+
+// ToRecordSetCreateMap formats an CreateOpts structure into a request body.
+func (opts CreateOpts) ToRecordSetCreateMap() (map[string]interface{}, error) {
+ b, err := gophercloud.BuildRequestBody(opts, "")
+ if err != nil {
+ return nil, err
+ }
+
+ return b, nil
+}
+
+// Create creates a recordset in a given zone.
+func Create(client *gophercloud.ServiceClient, zoneID string, opts CreateOptsBuilder) (r CreateResult) {
+ b, err := opts.ToRecordSetCreateMap()
+ if err != nil {
+ r.Err = err
+ return
+ }
+ _, r.Err = client.Post(baseURL(client, zoneID), &b, &r.Body, &gophercloud.RequestOpts{
+ OkCodes: []int{201, 202},
+ })
+ return
+}
+
+// UpdateOptsBuilder allows extensions to add additional attributes to the
+// Update request.
+type UpdateOptsBuilder interface {
+ ToRecordSetUpdateMap() (map[string]interface{}, error)
+}
+
+// UpdateOpts specifies the base attributes that may be updated on an existing
+// RecordSet.
+type UpdateOpts struct {
+ // Description is a description of the RecordSet.
+ Description *string `json:"description,omitempty"`
+
+ // TTL is the time to live of the RecordSet.
+ TTL int `json:"ttl,omitempty"`
+
+ // Records are the DNS records of the RecordSet.
+ Records []string `json:"records,omitempty"`
+}
+
+// ToRecordSetUpdateMap formats an UpdateOpts structure into a request body.
+func (opts UpdateOpts) ToRecordSetUpdateMap() (map[string]interface{}, error) {
+ b, err := gophercloud.BuildRequestBody(opts, "")
+ if err != nil {
+ return nil, err
+ }
+
+ if opts.TTL > 0 {
+ b["ttl"] = opts.TTL
+ } else {
+ b["ttl"] = nil
+ }
+
+ return b, nil
+}
+
+// Update updates a recordset in a given zone
+func Update(client *gophercloud.ServiceClient, zoneID string, rrsetID string, opts UpdateOptsBuilder) (r UpdateResult) {
+ b, err := opts.ToRecordSetUpdateMap()
+ if err != nil {
+ r.Err = err
+ return
+ }
+ _, r.Err = client.Put(rrsetURL(client, zoneID, rrsetID), &b, &r.Body, &gophercloud.RequestOpts{
+ OkCodes: []int{200, 202},
+ })
+ return
+}
+
+// Delete removes an existing RecordSet.
+func Delete(client *gophercloud.ServiceClient, zoneID string, rrsetID string) (r DeleteResult) {
+ _, r.Err = client.Delete(rrsetURL(client, zoneID, rrsetID), &gophercloud.RequestOpts{
+ OkCodes: []int{202},
+ })
+ return
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/results.go
new file mode 100644
index 000000000..0fdc1fe52
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/results.go
@@ -0,0 +1,147 @@
+package recordsets
+
+import (
+ "encoding/json"
+ "time"
+
+ "github.com/gophercloud/gophercloud"
+ "github.com/gophercloud/gophercloud/pagination"
+)
+
+type commonResult struct {
+ gophercloud.Result
+}
+
+// Extract interprets a GetResult, CreateResult or UpdateResult as a RecordSet.
+// An error is returned if the original call or the extraction failed.
+func (r commonResult) Extract() (*RecordSet, error) {
+ var s *RecordSet
+ err := r.ExtractInto(&s)
+ return s, err
+}
+
+// CreateResult is the result of a Create operation. Call its Extract method to
+// interpret the result as a RecordSet.
+type CreateResult struct {
+ commonResult
+}
+
+// GetResult is the result of a Get operation. Call its Extract method to
+// interpret the result as a RecordSet.
+type GetResult struct {
+ commonResult
+}
+
+// RecordSetPage is a single page of RecordSet results.
+type RecordSetPage struct {
+ pagination.LinkedPageBase
+}
+
+// UpdateResult is result of an Update operation. Call its Extract method to
+// interpret the result as a RecordSet.
+type UpdateResult struct {
+ commonResult
+}
+
+// DeleteResult is result of a Delete operation. Call its ExtractErr method to
+// determine if the operation succeeded or failed.
+type DeleteResult struct {
+ gophercloud.ErrResult
+}
+
+// IsEmpty returns true if the page contains no results.
+func (r RecordSetPage) IsEmpty() (bool, error) {
+ s, err := ExtractRecordSets(r)
+ return len(s) == 0, err
+}
+
+// ExtractRecordSets extracts a slice of RecordSets from a List result.
+func ExtractRecordSets(r pagination.Page) ([]RecordSet, error) {
+ var s struct {
+ RecordSets []RecordSet `json:"recordsets"`
+ }
+ err := (r.(RecordSetPage)).ExtractInto(&s)
+ return s.RecordSets, err
+}
+
+// RecordSet represents a DNS Record Set.
+type RecordSet struct {
+ // ID is the unique ID of the recordset
+ ID string `json:"id"`
+
+ // ZoneID is the ID of the zone the recordset belongs to.
+ ZoneID string `json:"zone_id"`
+
+ // ProjectID is the ID of the project that owns the recordset.
+ ProjectID string `json:"project_id"`
+
+ // Name is the name of the recordset.
+ Name string `json:"name"`
+
+ // ZoneName is the name of the zone the recordset belongs to.
+ ZoneName string `json:"zone_name"`
+
+ // Type is the RRTYPE of the recordset.
+ Type string `json:"type"`
+
+ // Records are the DNS records of the recordset.
+ Records []string `json:"records"`
+
+ // TTL is the time to live of the recordset.
+ TTL int `json:"ttl"`
+
+ // Status is the status of the recordset.
+ Status string `json:"status"`
+
+ // Action is the current action in progress of the recordset.
+ Action string `json:"action"`
+
+ // Description is the description of the recordset.
+ Description string `json:"description"`
+
+ // Version is the revision of the recordset.
+ Version int `json:"version"`
+
+ // CreatedAt is the date when the recordset was created.
+ CreatedAt time.Time `json:"-"`
+
+ // UpdatedAt is the date when the recordset was updated.
+ UpdatedAt time.Time `json:"-"`
+
+ // Links includes HTTP references to the itself,
+ // useful for passing along to other APIs that might want a recordset
+ // reference.
+ Links []gophercloud.Link `json:"-"`
+}
+
+func (r *RecordSet) UnmarshalJSON(b []byte) error {
+ type tmp RecordSet
+ var s struct {
+ tmp
+ CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
+ UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"`
+ Links map[string]interface{} `json:"links"`
+ }
+ err := json.Unmarshal(b, &s)
+ if err != nil {
+ return err
+ }
+ *r = RecordSet(s.tmp)
+
+ r.CreatedAt = time.Time(s.CreatedAt)
+ r.UpdatedAt = time.Time(s.UpdatedAt)
+
+ if s.Links != nil {
+ for rel, href := range s.Links {
+ if v, ok := href.(string); ok {
+ link := gophercloud.Link{
+ Rel: rel,
+ Href: v,
+ }
+ r.Links = append(r.Links, link)
+ }
+ }
+ }
+
+ return err
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/urls.go
new file mode 100644
index 000000000..5ec18d1bb
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets/urls.go
@@ -0,0 +1,11 @@
+package recordsets
+
+import "github.com/gophercloud/gophercloud"
+
+func baseURL(c *gophercloud.ServiceClient, zoneID string) string {
+ return c.ServiceURL("zones", zoneID, "recordsets")
+}
+
+func rrsetURL(c *gophercloud.ServiceClient, zoneID string, rrsetID string) string {
+ return c.ServiceURL("zones", zoneID, "recordsets", rrsetID)
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/doc.go
new file mode 100644
index 000000000..7733155bc
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/doc.go
@@ -0,0 +1,48 @@
+/*
+Package zones provides information and interaction with the zone API
+resource for the OpenStack DNS service.
+
+Example to List Zones
+
+ listOpts := zones.ListOpts{
+ Email: "jdoe@example.com",
+ }
+
+ allPages, err := zones.List(dnsClient, listOpts).AllPages()
+ if err != nil {
+ panic(err)
+ }
+
+ allZones, err := zones.ExtractZones(allPages)
+ if err != nil {
+ panic(err)
+ }
+
+ for _, zone := range allZones {
+ fmt.Printf("%+v\n", zone)
+ }
+
+Example to Create a Zone
+
+ createOpts := zones.CreateOpts{
+ Name: "example.com.",
+ Email: "jdoe@example.com",
+ Type: "PRIMARY",
+ TTL: 7200,
+ Description: "This is a zone.",
+ }
+
+ zone, err := zones.Create(dnsClient, createOpts).Extract()
+ if err != nil {
+ panic(err)
+ }
+
+Example to Delete a Zone
+
+ zoneID := "99d10f68-5623-4491-91a0-6daafa32b60e"
+ err := zones.Delete(dnsClient, zoneID).ExtractErr()
+ if err != nil {
+ panic(err)
+ }
+*/
+package zones
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/requests.go
new file mode 100644
index 000000000..78b08ae4f
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/requests.go
@@ -0,0 +1,174 @@
+package zones
+
+import (
+ "github.com/gophercloud/gophercloud"
+ "github.com/gophercloud/gophercloud/pagination"
+)
+
+// ListOptsBuilder allows extensions to add parameters to the List request.
+type ListOptsBuilder interface {
+ ToZoneListQuery() (string, error)
+}
+
+// ListOpts allows the filtering and sorting of paginated collections through
+// the API. Filtering is achieved by passing in struct field values that map to
+// the server attributes you want to see returned. Marker and Limit are used
+// for pagination.
+// https://developer.openstack.org/api-ref/dns/
+type ListOpts struct {
+ // Integer value for the limit of values to return.
+ Limit int `q:"limit"`
+
+ // UUID of the zone at which you want to set a marker.
+ Marker string `q:"marker"`
+
+ Description string `q:"description"`
+ Email string `q:"email"`
+ Name string `q:"name"`
+ SortDir string `q:"sort_dir"`
+ SortKey string `q:"sort_key"`
+ Status string `q:"status"`
+ TTL int `q:"ttl"`
+ Type string `q:"type"`
+}
+
+// ToZoneListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToZoneListQuery() (string, error) {
+ q, err := gophercloud.BuildQueryString(opts)
+ return q.String(), err
+}
+
+// List implements a zone List request.
+func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
+ url := baseURL(client)
+ if opts != nil {
+ query, err := opts.ToZoneListQuery()
+ if err != nil {
+ return pagination.Pager{Err: err}
+ }
+ url += query
+ }
+ return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
+ return ZonePage{pagination.LinkedPageBase{PageResult: r}}
+ })
+}
+
+// Get returns information about a zone, given its ID.
+func Get(client *gophercloud.ServiceClient, zoneID string) (r GetResult) {
+ _, r.Err = client.Get(zoneURL(client, zoneID), &r.Body, nil)
+ return
+}
+
+// CreateOptsBuilder allows extensions to add additional attributes to the
+// Create request.
+type CreateOptsBuilder interface {
+ ToZoneCreateMap() (map[string]interface{}, error)
+}
+
+// CreateOpts specifies the attributes used to create a zone.
+type CreateOpts struct {
+ // Attributes are settings that supply hints and filters for the zone.
+ Attributes map[string]string `json:"attributes,omitempty"`
+
+ // Email contact of the zone.
+ Email string `json:"email,omitempty"`
+
+ // Description of the zone.
+ Description string `json:"description,omitempty"`
+
+ // Name of the zone.
+ Name string `json:"name" required:"true"`
+
+ // Masters specifies zone masters if this is a secondary zone.
+ Masters []string `json:"masters,omitempty"`
+
+ // TTL is the time to live of the zone.
+ TTL int `json:"-"`
+
+ // Type specifies if this is a primary or secondary zone.
+ Type string `json:"type,omitempty"`
+}
+
+// ToZoneCreateMap formats an CreateOpts structure into a request body.
+func (opts CreateOpts) ToZoneCreateMap() (map[string]interface{}, error) {
+ b, err := gophercloud.BuildRequestBody(opts, "")
+ if err != nil {
+ return nil, err
+ }
+
+ if opts.TTL > 0 {
+ b["ttl"] = opts.TTL
+ }
+
+ return b, nil
+}
+
+// Create implements a zone create request.
+func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
+ b, err := opts.ToZoneCreateMap()
+ if err != nil {
+ r.Err = err
+ return
+ }
+ _, r.Err = client.Post(baseURL(client), &b, &r.Body, &gophercloud.RequestOpts{
+ OkCodes: []int{201, 202},
+ })
+ return
+}
+
+// UpdateOptsBuilder allows extensions to add additional attributes to the
+// Update request.
+type UpdateOptsBuilder interface {
+ ToZoneUpdateMap() (map[string]interface{}, error)
+}
+
+// UpdateOpts specifies the attributes to update a zone.
+type UpdateOpts struct {
+ // Email contact of the zone.
+ Email string `json:"email,omitempty"`
+
+ // TTL is the time to live of the zone.
+ TTL int `json:"-"`
+
+ // Masters specifies zone masters if this is a secondary zone.
+ Masters []string `json:"masters,omitempty"`
+
+ // Description of the zone.
+ Description *string `json:"description,omitempty"`
+}
+
+// ToZoneUpdateMap formats an UpdateOpts structure into a request body.
+func (opts UpdateOpts) ToZoneUpdateMap() (map[string]interface{}, error) {
+ b, err := gophercloud.BuildRequestBody(opts, "")
+ if err != nil {
+ return nil, err
+ }
+
+ if opts.TTL > 0 {
+ b["ttl"] = opts.TTL
+ }
+
+ return b, nil
+}
+
+// Update implements a zone update request.
+func Update(client *gophercloud.ServiceClient, zoneID string, opts UpdateOptsBuilder) (r UpdateResult) {
+ b, err := opts.ToZoneUpdateMap()
+ if err != nil {
+ r.Err = err
+ return
+ }
+ _, r.Err = client.Patch(zoneURL(client, zoneID), &b, &r.Body, &gophercloud.RequestOpts{
+ OkCodes: []int{200, 202},
+ })
+ return
+}
+
+// Delete implements a zone delete request.
+func Delete(client *gophercloud.ServiceClient, zoneID string) (r DeleteResult) {
+ _, r.Err = client.Delete(zoneURL(client, zoneID), &gophercloud.RequestOpts{
+ OkCodes: []int{202},
+ JSONResponse: &r.Body,
+ })
+ return
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/results.go
new file mode 100644
index 000000000..a36eca7e2
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/results.go
@@ -0,0 +1,166 @@
+package zones
+
+import (
+ "encoding/json"
+ "strconv"
+ "time"
+
+ "github.com/gophercloud/gophercloud"
+ "github.com/gophercloud/gophercloud/pagination"
+)
+
+type commonResult struct {
+ gophercloud.Result
+}
+
+// Extract interprets a GetResult, CreateResult or UpdateResult as a Zone.
+// An error is returned if the original call or the extraction failed.
+func (r commonResult) Extract() (*Zone, error) {
+ var s *Zone
+ err := r.ExtractInto(&s)
+ return s, err
+}
+
+// CreateResult is the result of a Create request. Call its Extract method
+// to interpret the result as a Zone.
+type CreateResult struct {
+ commonResult
+}
+
+// GetResult is the result of a Get request. Call its Extract method
+// to interpret the result as a Zone.
+type GetResult struct {
+ commonResult
+}
+
+// UpdateResult is the result of an Update request. Call its Extract method
+// to interpret the result as a Zone.
+type UpdateResult struct {
+ commonResult
+}
+
+// DeleteResult is the result of a Delete request. Call its ExtractErr method
+// to determine if the request succeeded or failed.
+type DeleteResult struct {
+ commonResult
+}
+
+// ZonePage is a single page of Zone results.
+type ZonePage struct {
+ pagination.LinkedPageBase
+}
+
+// IsEmpty returns true if the page contains no results.
+func (r ZonePage) IsEmpty() (bool, error) {
+ s, err := ExtractZones(r)
+ return len(s) == 0, err
+}
+
+// ExtractZones extracts a slice of Zones from a List result.
+func ExtractZones(r pagination.Page) ([]Zone, error) {
+ var s struct {
+ Zones []Zone `json:"zones"`
+ }
+ err := (r.(ZonePage)).ExtractInto(&s)
+ return s.Zones, err
+}
+
+// Zone represents a DNS zone.
+type Zone struct {
+ // ID uniquely identifies this zone amongst all other zones, including those
+ // not accessible to the current tenant.
+ ID string `json:"id"`
+
+ // PoolID is the ID for the pool hosting this zone.
+ PoolID string `json:"pool_id"`
+
+ // ProjectID identifies the project/tenant owning this resource.
+ ProjectID string `json:"project_id"`
+
+ // Name is the DNS Name for the zone.
+ Name string `json:"name"`
+
+ // Email for the zone. Used in SOA records for the zone.
+ Email string `json:"email"`
+
+ // Description for this zone.
+ Description string `json:"description"`
+
+ // TTL is the Time to Live for the zone.
+ TTL int `json:"ttl"`
+
+ // Serial is the current serial number for the zone.
+ Serial int `json:"-"`
+
+ // Status is the status of the resource.
+ Status string `json:"status"`
+
+ // Action is the current action in progress on the resource.
+ Action string `json:"action"`
+
+ // Version of the resource.
+ Version int `json:"version"`
+
+ // Attributes for the zone.
+ Attributes map[string]string `json:"attributes"`
+
+ // Type of zone. Primary is controlled by Designate.
+ // Secondary zones are slaved from another DNS Server.
+ // Defaults to Primary.
+ Type string `json:"type"`
+
+ // Masters is the servers for slave servers to get DNS information from.
+ Masters []string `json:"masters"`
+
+ // CreatedAt is the date when the zone was created.
+ CreatedAt time.Time `json:"-"`
+
+ // UpdatedAt is the date when the last change was made to the zone.
+ UpdatedAt time.Time `json:"-"`
+
+ // TransferredAt is the last time an update was retrieved from the
+ // master servers.
+ TransferredAt time.Time `json:"-"`
+
+ // Links includes HTTP references to the itself, useful for passing along
+ // to other APIs that might want a server reference.
+ Links map[string]interface{} `json:"links"`
+}
+
+func (r *Zone) UnmarshalJSON(b []byte) error {
+ type tmp Zone
+ var s struct {
+ tmp
+ CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
+ UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"`
+ TransferredAt gophercloud.JSONRFC3339MilliNoZ `json:"transferred_at"`
+ Serial interface{} `json:"serial"`
+ }
+ err := json.Unmarshal(b, &s)
+ if err != nil {
+ return err
+ }
+ *r = Zone(s.tmp)
+
+ r.CreatedAt = time.Time(s.CreatedAt)
+ r.UpdatedAt = time.Time(s.UpdatedAt)
+ r.TransferredAt = time.Time(s.TransferredAt)
+
+ switch t := s.Serial.(type) {
+ case float64:
+ r.Serial = int(t)
+ case string:
+ switch t {
+ case "":
+ r.Serial = 0
+ default:
+ serial, err := strconv.ParseFloat(t, 64)
+ if err != nil {
+ return err
+ }
+ r.Serial = int(serial)
+ }
+ }
+
+ return err
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/urls.go
new file mode 100644
index 000000000..9bef70580
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/dns/v2/zones/urls.go
@@ -0,0 +1,11 @@
+package zones
+
+import "github.com/gophercloud/gophercloud"
+
+func baseURL(c *gophercloud.ServiceClient) string {
+ return c.ServiceURL("zones")
+}
+
+func zoneURL(c *gophercloud.ServiceClient, zoneID string) string {
+ return c.ServiceURL("zones", zoneID)
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/doc.go
new file mode 100644
index 000000000..cedf1f4d3
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/doc.go
@@ -0,0 +1,14 @@
+/*
+Package openstack contains resources for the individual OpenStack projects
+supported in Gophercloud. It also includes functions to authenticate to an
+OpenStack cloud and for provisioning various service-level clients.
+
+Example of Creating a Service Client
+
+ ao, err := openstack.AuthOptionsFromEnv()
+ provider, err := openstack.AuthenticatedClient(ao)
+ client, err := openstack.NewNetworkV2(client, gophercloud.EndpointOpts{
+ Region: os.Getenv("OS_REGION_NAME"),
+ })
+*/
+package openstack
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go b/vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go
new file mode 100644
index 000000000..12c8aebcf
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/endpoint_location.go
@@ -0,0 +1,107 @@
+package openstack
+
+import (
+ "github.com/gophercloud/gophercloud"
+ tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
+ tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
+)
+
+/*
+V2EndpointURL discovers the endpoint URL for a specific service from a
+ServiceCatalog acquired during the v2 identity service.
+
+The specified EndpointOpts are used to identify a unique, unambiguous endpoint
+to return. It's an error both when multiple endpoints match the provided
+criteria and when none do. The minimum that can be specified is a Type, but you
+will also often need to specify a Name and/or a Region depending on what's
+available on your OpenStack deployment.
+*/
+func V2EndpointURL(catalog *tokens2.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) {
+ // Extract Endpoints from the catalog entries that match the requested Type, Name if provided, and Region if provided.
+ var endpoints = make([]tokens2.Endpoint, 0, 1)
+ for _, entry := range catalog.Entries {
+ if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) {
+ for _, endpoint := range entry.Endpoints {
+ if opts.Region == "" || endpoint.Region == opts.Region {
+ endpoints = append(endpoints, endpoint)
+ }
+ }
+ }
+ }
+
+ // Report an error if the options were ambiguous.
+ if len(endpoints) > 1 {
+ err := &ErrMultipleMatchingEndpointsV2{}
+ err.Endpoints = endpoints
+ return "", err
+ }
+
+ // Extract the appropriate URL from the matching Endpoint.
+ for _, endpoint := range endpoints {
+ switch opts.Availability {
+ case gophercloud.AvailabilityPublic:
+ return gophercloud.NormalizeURL(endpoint.PublicURL), nil
+ case gophercloud.AvailabilityInternal:
+ return gophercloud.NormalizeURL(endpoint.InternalURL), nil
+ case gophercloud.AvailabilityAdmin:
+ return gophercloud.NormalizeURL(endpoint.AdminURL), nil
+ default:
+ err := &ErrInvalidAvailabilityProvided{}
+ err.Argument = "Availability"
+ err.Value = opts.Availability
+ return "", err
+ }
+ }
+
+ // Report an error if there were no matching endpoints.
+ err := &gophercloud.ErrEndpointNotFound{}
+ return "", err
+}
+
+/*
+V3EndpointURL discovers the endpoint URL for a specific service from a Catalog
+acquired during the v3 identity service.
+
+The specified EndpointOpts are used to identify a unique, unambiguous endpoint
+to return. It's an error both when multiple endpoints match the provided
+criteria and when none do. The minimum that can be specified is a Type, but you
+will also often need to specify a Name and/or a Region depending on what's
+available on your OpenStack deployment.
+*/
+func V3EndpointURL(catalog *tokens3.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) {
+ // Extract Endpoints from the catalog entries that match the requested Type, Interface,
+ // Name if provided, and Region if provided.
+ var endpoints = make([]tokens3.Endpoint, 0, 1)
+ for _, entry := range catalog.Entries {
+ if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) {
+ for _, endpoint := range entry.Endpoints {
+ if opts.Availability != gophercloud.AvailabilityAdmin &&
+ opts.Availability != gophercloud.AvailabilityPublic &&
+ opts.Availability != gophercloud.AvailabilityInternal {
+ err := &ErrInvalidAvailabilityProvided{}
+ err.Argument = "Availability"
+ err.Value = opts.Availability
+ return "", err
+ }
+ if (opts.Availability == gophercloud.Availability(endpoint.Interface)) &&
+ (opts.Region == "" || endpoint.Region == opts.Region || endpoint.RegionID == opts.Region) {
+ endpoints = append(endpoints, endpoint)
+ }
+ }
+ }
+ }
+
+ // Report an error if the options were ambiguous.
+ if len(endpoints) > 1 {
+ return "", ErrMultipleMatchingEndpointsV3{Endpoints: endpoints}
+ }
+
+ // Extract the URL from the matching Endpoint.
+ for _, endpoint := range endpoints {
+ return gophercloud.NormalizeURL(endpoint.URL), nil
+ }
+
+ // Report an error if there were no matching endpoints.
+ err := &gophercloud.ErrEndpointNotFound{}
+ return "", err
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/errors.go b/vendor/github.com/gophercloud/gophercloud/openstack/errors.go
new file mode 100644
index 000000000..df410b1c6
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/errors.go
@@ -0,0 +1,71 @@
+package openstack
+
+import (
+ "fmt"
+
+ "github.com/gophercloud/gophercloud"
+ tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
+ tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
+)
+
+// ErrEndpointNotFound is the error when no suitable endpoint can be found
+// in the user's catalog
+type ErrEndpointNotFound struct{ gophercloud.BaseError }
+
+func (e ErrEndpointNotFound) Error() string {
+ return "No suitable endpoint could be found in the service catalog."
+}
+
+// ErrInvalidAvailabilityProvided is the error when an invalid endpoint
+// availability is provided
+type ErrInvalidAvailabilityProvided struct{ gophercloud.ErrInvalidInput }
+
+func (e ErrInvalidAvailabilityProvided) Error() string {
+ return fmt.Sprintf("Unexpected availability in endpoint query: %s", e.Value)
+}
+
+// ErrMultipleMatchingEndpointsV2 is the error when more than one endpoint
+// for the given options is found in the v2 catalog
+type ErrMultipleMatchingEndpointsV2 struct {
+ gophercloud.BaseError
+ Endpoints []tokens2.Endpoint
+}
+
+func (e ErrMultipleMatchingEndpointsV2) Error() string {
+ return fmt.Sprintf("Discovered %d matching endpoints: %#v", len(e.Endpoints), e.Endpoints)
+}
+
+// ErrMultipleMatchingEndpointsV3 is the error when more than one endpoint
+// for the given options is found in the v3 catalog
+type ErrMultipleMatchingEndpointsV3 struct {
+ gophercloud.BaseError
+ Endpoints []tokens3.Endpoint
+}
+
+func (e ErrMultipleMatchingEndpointsV3) Error() string {
+ return fmt.Sprintf("Discovered %d matching endpoints: %#v", len(e.Endpoints), e.Endpoints)
+}
+
+// ErrNoAuthURL is the error when the OS_AUTH_URL environment variable is not
+// found
+type ErrNoAuthURL struct{ gophercloud.ErrInvalidInput }
+
+func (e ErrNoAuthURL) Error() string {
+ return "Environment variable OS_AUTH_URL needs to be set."
+}
+
+// ErrNoUsername is the error when the OS_USERNAME environment variable is not
+// found
+type ErrNoUsername struct{ gophercloud.ErrInvalidInput }
+
+func (e ErrNoUsername) Error() string {
+ return "Environment variable OS_USERNAME needs to be set."
+}
+
+// ErrNoPassword is the error when the OS_PASSWORD environment variable is not
+// found
+type ErrNoPassword struct{ gophercloud.ErrInvalidInput }
+
+func (e ErrNoPassword) Error() string {
+ return "Environment variable OS_PASSWORD needs to be set."
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/doc.go
new file mode 100644
index 000000000..45623369e
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/doc.go
@@ -0,0 +1,65 @@
+/*
+Package tenants provides information and interaction with the
+tenants API resource for the OpenStack Identity service.
+
+See http://developer.openstack.org/api-ref-identity-v2.html#identity-auth-v2
+and http://developer.openstack.org/api-ref-identity-v2.html#admin-tenants
+for more information.
+
+Example to List Tenants
+
+ listOpts := tenants.ListOpts{
+ Limit: 2,
+ }
+
+ allPages, err := tenants.List(identityClient, listOpts).AllPages()
+ if err != nil {
+ panic(err)
+ }
+
+ allTenants, err := tenants.ExtractTenants(allPages)
+ if err != nil {
+ panic(err)
+ }
+
+ for _, tenant := range allTenants {
+ fmt.Printf("%+v\n", tenant)
+ }
+
+Example to Create a Tenant
+
+ createOpts := tenants.CreateOpts{
+ Name: "tenant_name",
+ Description: "this is a tenant",
+ Enabled: gophercloud.Enabled,
+ }
+
+ tenant, err := tenants.Create(identityClient, createOpts).Extract()
+ if err != nil {
+ panic(err)
+ }
+
+Example to Update a Tenant
+
+ tenantID := "e6db6ed6277c461a853458589063b295"
+
+ updateOpts := tenants.UpdateOpts{
+ Description: "this is a new description",
+ Enabled: gophercloud.Disabled,
+ }
+
+ tenant, err := tenants.Update(identityClient, tenantID, updateOpts).Extract()
+ if err != nil {
+ panic(err)
+ }
+
+Example to Delete a Tenant
+
+ tenantID := "e6db6ed6277c461a853458589063b295"
+
+ err := tenants.Delete(identitYClient, tenantID).ExtractErr()
+ if err != nil {
+ panic(err)
+ }
+*/
+package tenants
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go
new file mode 100644
index 000000000..f21a58f10
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/requests.go
@@ -0,0 +1,116 @@
+package tenants
+
+import (
+ "github.com/gophercloud/gophercloud"
+ "github.com/gophercloud/gophercloud/pagination"
+)
+
+// ListOpts filters the Tenants that are returned by the List call.
+type ListOpts struct {
+ // Marker is the ID of the last Tenant on the previous page.
+ Marker string `q:"marker"`
+
+ // Limit specifies the page size.
+ Limit int `q:"limit"`
+}
+
+// List enumerates the Tenants to which the current token has access.
+func List(client *gophercloud.ServiceClient, opts *ListOpts) pagination.Pager {
+ url := listURL(client)
+ if opts != nil {
+ q, err := gophercloud.BuildQueryString(opts)
+ if err != nil {
+ return pagination.Pager{Err: err}
+ }
+ url += q.String()
+ }
+ return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
+ return TenantPage{pagination.LinkedPageBase{PageResult: r}}
+ })
+}
+
+// CreateOpts represents the options needed when creating new tenant.
+type CreateOpts struct {
+ // Name is the name of the tenant.
+ Name string `json:"name" required:"true"`
+
+ // Description is the description of the tenant.
+ Description string `json:"description,omitempty"`
+
+ // Enabled sets the tenant status to enabled or disabled.
+ Enabled *bool `json:"enabled,omitempty"`
+}
+
+// CreateOptsBuilder enables extensions to add additional parameters to the
+// Create request.
+type CreateOptsBuilder interface {
+ ToTenantCreateMap() (map[string]interface{}, error)
+}
+
+// ToTenantCreateMap assembles a request body based on the contents of
+// a CreateOpts.
+func (opts CreateOpts) ToTenantCreateMap() (map[string]interface{}, error) {
+ return gophercloud.BuildRequestBody(opts, "tenant")
+}
+
+// Create is the operation responsible for creating new tenant.
+func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
+ b, err := opts.ToTenantCreateMap()
+ if err != nil {
+ r.Err = err
+ return
+ }
+ _, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
+ OkCodes: []int{200, 201},
+ })
+ return
+}
+
+// Get requests details on a single tenant by ID.
+func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
+ _, r.Err = client.Get(getURL(client, id), &r.Body, nil)
+ return
+}
+
+// UpdateOptsBuilder allows extensions to add additional parameters to the
+// Update request.
+type UpdateOptsBuilder interface {
+ ToTenantUpdateMap() (map[string]interface{}, error)
+}
+
+// UpdateOpts specifies the base attributes that may be updated on an existing
+// tenant.
+type UpdateOpts struct {
+ // Name is the name of the tenant.
+ Name string `json:"name,omitempty"`
+
+ // Description is the description of the tenant.
+ Description *string `json:"description,omitempty"`
+
+ // Enabled sets the tenant status to enabled or disabled.
+ Enabled *bool `json:"enabled,omitempty"`
+}
+
+// ToTenantUpdateMap formats an UpdateOpts structure into a request body.
+func (opts UpdateOpts) ToTenantUpdateMap() (map[string]interface{}, error) {
+ return gophercloud.BuildRequestBody(opts, "tenant")
+}
+
+// Update is the operation responsible for updating exist tenants by their TenantID.
+func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
+ b, err := opts.ToTenantUpdateMap()
+ if err != nil {
+ r.Err = err
+ return
+ }
+ _, r.Err = client.Put(updateURL(client, id), &b, &r.Body, &gophercloud.RequestOpts{
+ OkCodes: []int{200},
+ })
+ return
+}
+
+// Delete is the operation responsible for permanently deleting a tenant.
+func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
+ _, r.Err = client.Delete(deleteURL(client, id), nil)
+ return
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/results.go
new file mode 100644
index 000000000..bb6c2c6b0
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/results.go
@@ -0,0 +1,91 @@
+package tenants
+
+import (
+ "github.com/gophercloud/gophercloud"
+ "github.com/gophercloud/gophercloud/pagination"
+)
+
+// Tenant is a grouping of users in the identity service.
+type Tenant struct {
+ // ID is a unique identifier for this tenant.
+ ID string `json:"id"`
+
+ // Name is a friendlier user-facing name for this tenant.
+ Name string `json:"name"`
+
+ // Description is a human-readable explanation of this Tenant's purpose.
+ Description string `json:"description"`
+
+ // Enabled indicates whether or not a tenant is active.
+ Enabled bool `json:"enabled"`
+}
+
+// TenantPage is a single page of Tenant results.
+type TenantPage struct {
+ pagination.LinkedPageBase
+}
+
+// IsEmpty determines whether or not a page of Tenants contains any results.
+func (r TenantPage) IsEmpty() (bool, error) {
+ tenants, err := ExtractTenants(r)
+ return len(tenants) == 0, err
+}
+
+// NextPageURL extracts the "next" link from the tenants_links section of the result.
+func (r TenantPage) NextPageURL() (string, error) {
+ var s struct {
+ Links []gophercloud.Link `json:"tenants_links"`
+ }
+ err := r.ExtractInto(&s)
+ if err != nil {
+ return "", err
+ }
+ return gophercloud.ExtractNextURL(s.Links)
+}
+
+// ExtractTenants returns a slice of Tenants contained in a single page of
+// results.
+func ExtractTenants(r pagination.Page) ([]Tenant, error) {
+ var s struct {
+ Tenants []Tenant `json:"tenants"`
+ }
+ err := (r.(TenantPage)).ExtractInto(&s)
+ return s.Tenants, err
+}
+
+type tenantResult struct {
+ gophercloud.Result
+}
+
+// Extract interprets any tenantResults as a Tenant.
+func (r tenantResult) Extract() (*Tenant, error) {
+ var s struct {
+ Tenant *Tenant `json:"tenant"`
+ }
+ err := r.ExtractInto(&s)
+ return s.Tenant, err
+}
+
+// GetResult is the response from a Get request. Call its Extract method to
+// interpret it as a Tenant.
+type GetResult struct {
+ tenantResult
+}
+
+// CreateResult is the response from a Create request. Call its Extract method
+// to interpret it as a Tenant.
+type CreateResult struct {
+ tenantResult
+}
+
+// DeleteResult is the response from a Get request. Call its ExtractErr method
+// to determine if the call succeeded or failed.
+type DeleteResult struct {
+ gophercloud.ErrResult
+}
+
+// UpdateResult is the response from a Update request. Call its Extract method
+// to interpret it as a Tenant.
+type UpdateResult struct {
+ tenantResult
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/urls.go
new file mode 100644
index 000000000..0f0266907
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/urls.go
@@ -0,0 +1,23 @@
+package tenants
+
+import "github.com/gophercloud/gophercloud"
+
+func listURL(client *gophercloud.ServiceClient) string {
+ return client.ServiceURL("tenants")
+}
+
+func getURL(client *gophercloud.ServiceClient, tenantID string) string {
+ return client.ServiceURL("tenants", tenantID)
+}
+
+func createURL(client *gophercloud.ServiceClient) string {
+ return client.ServiceURL("tenants")
+}
+
+func deleteURL(client *gophercloud.ServiceClient, tenantID string) string {
+ return client.ServiceURL("tenants", tenantID)
+}
+
+func updateURL(client *gophercloud.ServiceClient, tenantID string) string {
+ return client.ServiceURL("tenants", tenantID)
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/doc.go
new file mode 100644
index 000000000..5375eea87
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/doc.go
@@ -0,0 +1,46 @@
+/*
+Package tokens provides information and interaction with the token API
+resource for the OpenStack Identity service.
+
+For more information, see:
+http://developer.openstack.org/api-ref-identity-v2.html#identity-auth-v2
+
+Example to Create an Unscoped Token from a Password
+
+ authOpts := gophercloud.AuthOptions{
+ Username: "user",
+ Password: "pass"
+ }
+
+ token, err := tokens.Create(identityClient, authOpts).ExtractToken()
+ if err != nil {
+ panic(err)
+ }
+
+Example to Create a Token from a Tenant ID and Password
+
+ authOpts := gophercloud.AuthOptions{
+ Username: "user",
+ Password: "password",
+ TenantID: "fc394f2ab2df4114bde39905f800dc57"
+ }
+
+ token, err := tokens.Create(identityClient, authOpts).ExtractToken()
+ if err != nil {
+ panic(err)
+ }
+
+Example to Create a Token from a Tenant Name and Password
+
+ authOpts := gophercloud.AuthOptions{
+ Username: "user",
+ Password: "password",
+ TenantName: "tenantname"
+ }
+
+ token, err := tokens.Create(identityClient, authOpts).ExtractToken()
+ if err != nil {
+ panic(err)
+ }
+*/
+package tokens
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go
new file mode 100644
index 000000000..ab32368cc
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go
@@ -0,0 +1,103 @@
+package tokens
+
+import "github.com/gophercloud/gophercloud"
+
+// PasswordCredentialsV2 represents the required options to authenticate
+// with a username and password.
+type PasswordCredentialsV2 struct {
+ Username string `json:"username" required:"true"`
+ Password string `json:"password" required:"true"`
+}
+
+// TokenCredentialsV2 represents the required options to authenticate
+// with a token.
+type TokenCredentialsV2 struct {
+ ID string `json:"id,omitempty" required:"true"`
+}
+
+// AuthOptionsV2 wraps a gophercloud AuthOptions in order to adhere to the
+// AuthOptionsBuilder interface.
+type AuthOptionsV2 struct {
+ PasswordCredentials *PasswordCredentialsV2 `json:"passwordCredentials,omitempty" xor:"TokenCredentials"`
+
+ // The TenantID and TenantName fields are optional for the Identity V2 API.
+ // Some providers allow you to specify a TenantName instead of the TenantId.
+ // Some require both. Your provider's authentication policies will determine
+ // how these fields influence authentication.
+ TenantID string `json:"tenantId,omitempty"`
+ TenantName string `json:"tenantName,omitempty"`
+
+ // TokenCredentials allows users to authenticate (possibly as another user)
+ // with an authentication token ID.
+ TokenCredentials *TokenCredentialsV2 `json:"token,omitempty" xor:"PasswordCredentials"`
+}
+
+// AuthOptionsBuilder allows extensions to add additional parameters to the
+// token create request.
+type AuthOptionsBuilder interface {
+ // ToTokenCreateMap assembles the Create request body, returning an error
+ // if parameters are missing or inconsistent.
+ ToTokenV2CreateMap() (map[string]interface{}, error)
+}
+
+// AuthOptions are the valid options for Openstack Identity v2 authentication.
+// For field descriptions, see gophercloud.AuthOptions.
+type AuthOptions struct {
+ IdentityEndpoint string `json:"-"`
+ Username string `json:"username,omitempty"`
+ Password string `json:"password,omitempty"`
+ TenantID string `json:"tenantId,omitempty"`
+ TenantName string `json:"tenantName,omitempty"`
+ AllowReauth bool `json:"-"`
+ TokenID string
+}
+
+// ToTokenV2CreateMap builds a token request body from the given AuthOptions.
+func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) {
+ v2Opts := AuthOptionsV2{
+ TenantID: opts.TenantID,
+ TenantName: opts.TenantName,
+ }
+
+ if opts.Password != "" {
+ v2Opts.PasswordCredentials = &PasswordCredentialsV2{
+ Username: opts.Username,
+ Password: opts.Password,
+ }
+ } else {
+ v2Opts.TokenCredentials = &TokenCredentialsV2{
+ ID: opts.TokenID,
+ }
+ }
+
+ b, err := gophercloud.BuildRequestBody(v2Opts, "auth")
+ if err != nil {
+ return nil, err
+ }
+ return b, nil
+}
+
+// Create authenticates to the identity service and attempts to acquire a Token.
+// Generally, rather than interact with this call directly, end users should
+// call openstack.AuthenticatedClient(), which abstracts all of the gory details
+// about navigating service catalogs and such.
+func Create(client *gophercloud.ServiceClient, auth AuthOptionsBuilder) (r CreateResult) {
+ b, err := auth.ToTokenV2CreateMap()
+ if err != nil {
+ r.Err = err
+ return
+ }
+ _, r.Err = client.Post(CreateURL(client), b, &r.Body, &gophercloud.RequestOpts{
+ OkCodes: []int{200, 203},
+ MoreHeaders: map[string]string{"X-Auth-Token": ""},
+ })
+ return
+}
+
+// Get validates and retrieves information for user's token.
+func Get(client *gophercloud.ServiceClient, token string) (r GetResult) {
+ _, r.Err = client.Get(GetURL(client, token), &r.Body, &gophercloud.RequestOpts{
+ OkCodes: []int{200, 203},
+ })
+ return
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go
new file mode 100644
index 000000000..ee5da37f4
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/results.go
@@ -0,0 +1,174 @@
+package tokens
+
+import (
+ "time"
+
+ "github.com/gophercloud/gophercloud"
+ "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants"
+)
+
+// Token provides only the most basic information related to an authentication
+// token.
+type Token struct {
+ // ID provides the primary means of identifying a user to the OpenStack API.
+ // OpenStack defines this field as an opaque value, so do not depend on its
+ // content. It is safe, however, to compare for equality.
+ ID string
+
+ // ExpiresAt provides a timestamp in ISO 8601 format, indicating when the
+ // authentication token becomes invalid. After this point in time, future
+ // API requests made using this authentication token will respond with
+ // errors. Either the caller will need to reauthenticate manually, or more
+ // preferably, the caller should exploit automatic re-authentication.
+ // See the AuthOptions structure for more details.
+ ExpiresAt time.Time
+
+ // Tenant provides information about the tenant to which this token grants
+ // access.
+ Tenant tenants.Tenant
+}
+
+// Role is a role for a user.
+type Role struct {
+ Name string `json:"name"`
+}
+
+// User is an OpenStack user.
+type User struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ UserName string `json:"username"`
+ Roles []Role `json:"roles"`
+}
+
+// Endpoint represents a single API endpoint offered by a service.
+// It provides the public and internal URLs, if supported, along with a region
+// specifier, again if provided.
+//
+// The significance of the Region field will depend upon your provider.
+//
+// In addition, the interface offered by the service will have version
+// information associated with it through the VersionId, VersionInfo, and
+// VersionList fields, if provided or supported.
+//
+// In all cases, fields which aren't supported by the provider and service
+// combined will assume a zero-value ("").
+type Endpoint struct {
+ TenantID string `json:"tenantId"`
+ PublicURL string `json:"publicURL"`
+ InternalURL string `json:"internalURL"`
+ AdminURL string `json:"adminURL"`
+ Region string `json:"region"`
+ VersionID string `json:"versionId"`
+ VersionInfo string `json:"versionInfo"`
+ VersionList string `json:"versionList"`
+}
+
+// CatalogEntry provides a type-safe interface to an Identity API V2 service
+// catalog listing.
+//
+// Each class of service, such as cloud DNS or block storage services, will have
+// a single CatalogEntry representing it.
+//
+// Note: when looking for the desired service, try, whenever possible, to key
+// off the type field. Otherwise, you'll tie the representation of the service
+// to a specific provider.
+type CatalogEntry struct {
+ // Name will contain the provider-specified name for the service.
+ Name string `json:"name"`
+
+ // Type will contain a type string if OpenStack defines a type for the
+ // service. Otherwise, for provider-specific services, the provider may assign
+ // their own type strings.
+ Type string `json:"type"`
+
+ // Endpoints will let the caller iterate over all the different endpoints that
+ // may exist for the service.
+ Endpoints []Endpoint `json:"endpoints"`
+}
+
+// ServiceCatalog provides a view into the service catalog from a previous,
+// successful authentication.
+type ServiceCatalog struct {
+ Entries []CatalogEntry
+}
+
+// CreateResult is the response from a Create request. Use ExtractToken() to
+// interpret it as a Token, or ExtractServiceCatalog() to interpret it as a
+// service catalog.
+type CreateResult struct {
+ gophercloud.Result
+}
+
+// GetResult is the deferred response from a Get call, which is the same with a
+// Created token. Use ExtractUser() to interpret it as a User.
+type GetResult struct {
+ CreateResult
+}
+
+// ExtractToken returns the just-created Token from a CreateResult.
+func (r CreateResult) ExtractToken() (*Token, error) {
+ var s struct {
+ Access struct {
+ Token struct {
+ Expires string `json:"expires"`
+ ID string `json:"id"`
+ Tenant tenants.Tenant `json:"tenant"`
+ } `json:"token"`
+ } `json:"access"`
+ }
+
+ err := r.ExtractInto(&s)
+ if err != nil {
+ return nil, err
+ }
+
+ expiresTs, err := time.Parse(gophercloud.RFC3339Milli, s.Access.Token.Expires)
+ if err != nil {
+ return nil, err
+ }
+
+ return &Token{
+ ID: s.Access.Token.ID,
+ ExpiresAt: expiresTs,
+ Tenant: s.Access.Token.Tenant,
+ }, nil
+}
+
+// ExtractTokenID implements the gophercloud.AuthResult interface. The returned
+// string is the same as the ID field of the Token struct returned from
+// ExtractToken().
+func (r CreateResult) ExtractTokenID() (string, error) {
+ var s struct {
+ Access struct {
+ Token struct {
+ ID string `json:"id"`
+ } `json:"token"`
+ } `json:"access"`
+ }
+ err := r.ExtractInto(&s)
+ return s.Access.Token.ID, err
+}
+
+// ExtractServiceCatalog returns the ServiceCatalog that was generated along
+// with the user's Token.
+func (r CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) {
+ var s struct {
+ Access struct {
+ Entries []CatalogEntry `json:"serviceCatalog"`
+ } `json:"access"`
+ }
+ err := r.ExtractInto(&s)
+ return &ServiceCatalog{Entries: s.Access.Entries}, err
+}
+
+// ExtractUser returns the User from a GetResult.
+func (r GetResult) ExtractUser() (*User, error) {
+ var s struct {
+ Access struct {
+ User User `json:"user"`
+ } `json:"access"`
+ }
+ err := r.ExtractInto(&s)
+ return &s.Access.User, err
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/urls.go
new file mode 100644
index 000000000..ee0a28f20
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/urls.go
@@ -0,0 +1,13 @@
+package tokens
+
+import "github.com/gophercloud/gophercloud"
+
+// CreateURL generates the URL used to create new Tokens.
+func CreateURL(client *gophercloud.ServiceClient) string {
+ return client.ServiceURL("tokens")
+}
+
+// GetURL generates the URL used to Validate Tokens.
+func GetURL(client *gophercloud.ServiceClient, token string) string {
+ return client.ServiceURL("tokens", token)
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/doc.go
new file mode 100644
index 000000000..966e128f1
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/doc.go
@@ -0,0 +1,108 @@
+/*
+Package tokens provides information and interaction with the token API
+resource for the OpenStack Identity service.
+
+For more information, see:
+http://developer.openstack.org/api-ref-identity-v3.html#tokens-v3
+
+Example to Create a Token From a Username and Password
+
+ authOptions := tokens.AuthOptions{
+ UserID: "username",
+ Password: "password",
+ }
+
+ token, err := tokens.Create(identityClient, authOptions).ExtractToken()
+ if err != nil {
+ panic(err)
+ }
+
+Example to Create a Token From a Username, Password, and Domain
+
+ authOptions := tokens.AuthOptions{
+ UserID: "username",
+ Password: "password",
+ DomainID: "default",
+ }
+
+ token, err := tokens.Create(identityClient, authOptions).ExtractToken()
+ if err != nil {
+ panic(err)
+ }
+
+ authOptions = tokens.AuthOptions{
+ UserID: "username",
+ Password: "password",
+ DomainName: "default",
+ }
+
+ token, err = tokens.Create(identityClient, authOptions).ExtractToken()
+ if err != nil {
+ panic(err)
+ }
+
+Example to Create a Token From a Token
+
+ authOptions := tokens.AuthOptions{
+ TokenID: "token_id",
+ }
+
+ token, err := tokens.Create(identityClient, authOptions).ExtractToken()
+ if err != nil {
+ panic(err)
+ }
+
+Example to Create a Token from a Username and Password with Project ID Scope
+
+ scope := tokens.Scope{
+ ProjectID: "0fe36e73809d46aeae6705c39077b1b3",
+ }
+
+ authOptions := tokens.AuthOptions{
+ Scope: &scope,
+ UserID: "username",
+ Password: "password",
+ }
+
+ token, err = tokens.Create(identityClient, authOptions).ExtractToken()
+ if err != nil {
+ panic(err)
+ }
+
+Example to Create a Token from a Username and Password with Domain ID Scope
+
+ scope := tokens.Scope{
+ DomainID: "default",
+ }
+
+ authOptions := tokens.AuthOptions{
+ Scope: &scope,
+ UserID: "username",
+ Password: "password",
+ }
+
+ token, err = tokens.Create(identityClient, authOptions).ExtractToken()
+ if err != nil {
+ panic(err)
+ }
+
+Example to Create a Token from a Username and Password with Project Name Scope
+
+ scope := tokens.Scope{
+ ProjectName: "project_name",
+ DomainID: "default",
+ }
+
+ authOptions := tokens.AuthOptions{
+ Scope: &scope,
+ UserID: "username",
+ Password: "password",
+ }
+
+ token, err = tokens.Create(identityClient, authOptions).ExtractToken()
+ if err != nil {
+ panic(err)
+ }
+
+*/
+package tokens
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go
new file mode 100644
index 000000000..2d20fa6f4
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go
@@ -0,0 +1,162 @@
+package tokens
+
+import "github.com/gophercloud/gophercloud"
+
+// Scope allows a created token to be limited to a specific domain or project.
+type Scope struct {
+ ProjectID string
+ ProjectName string
+ DomainID string
+ DomainName string
+}
+
+// AuthOptionsBuilder provides the ability for extensions to add additional
+// parameters to AuthOptions. Extensions must satisfy all required methods.
+type AuthOptionsBuilder interface {
+ // ToTokenV3CreateMap assembles the Create request body, returning an error
+ // if parameters are missing or inconsistent.
+ ToTokenV3CreateMap(map[string]interface{}) (map[string]interface{}, error)
+ ToTokenV3ScopeMap() (map[string]interface{}, error)
+ CanReauth() bool
+}
+
+// AuthOptions represents options for authenticating a user.
+type AuthOptions struct {
+ // IdentityEndpoint specifies the HTTP endpoint that is required to work with
+ // the Identity API of the appropriate version. While it's ultimately needed
+ // by all of the identity services, it will often be populated by a
+ // provider-level function.
+ IdentityEndpoint string `json:"-"`
+
+ // Username is required if using Identity V2 API. Consult with your provider's
+ // control panel to discover your account's username. In Identity V3, either
+ // UserID or a combination of Username and DomainID or DomainName are needed.
+ Username string `json:"username,omitempty"`
+ UserID string `json:"id,omitempty"`
+
+ Password string `json:"password,omitempty"`
+
+ // At most one of DomainID and DomainName must be provided if using Username
+ // with Identity V3. Otherwise, either are optional.
+ DomainID string `json:"-"`
+ DomainName string `json:"name,omitempty"`
+
+ // AllowReauth should be set to true if you grant permission for Gophercloud
+ // to cache your credentials in memory, and to allow Gophercloud to attempt
+ // to re-authenticate automatically if/when your token expires. If you set
+ // it to false, it will not cache these settings, but re-authentication will
+ // not be possible. This setting defaults to false.
+ AllowReauth bool `json:"-"`
+
+ // TokenID allows users to authenticate (possibly as another user) with an
+ // authentication token ID.
+ TokenID string `json:"-"`
+
+ // Authentication through Application Credentials requires supplying name, project and secret
+ // For project we can use TenantID
+ ApplicationCredentialID string `json:"-"`
+ ApplicationCredentialName string `json:"-"`
+ ApplicationCredentialSecret string `json:"-"`
+
+ Scope Scope `json:"-"`
+}
+
+// ToTokenV3CreateMap builds a request body from AuthOptions.
+func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) {
+ gophercloudAuthOpts := gophercloud.AuthOptions{
+ Username: opts.Username,
+ UserID: opts.UserID,
+ Password: opts.Password,
+ DomainID: opts.DomainID,
+ DomainName: opts.DomainName,
+ AllowReauth: opts.AllowReauth,
+ TokenID: opts.TokenID,
+ ApplicationCredentialID: opts.ApplicationCredentialID,
+ ApplicationCredentialName: opts.ApplicationCredentialName,
+ ApplicationCredentialSecret: opts.ApplicationCredentialSecret,
+ }
+
+ return gophercloudAuthOpts.ToTokenV3CreateMap(scope)
+}
+
+// ToTokenV3CreateMap builds a scope request body from AuthOptions.
+func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
+ scope := gophercloud.AuthScope(opts.Scope)
+
+ gophercloudAuthOpts := gophercloud.AuthOptions{
+ Scope: &scope,
+ DomainID: opts.DomainID,
+ DomainName: opts.DomainName,
+ }
+
+ return gophercloudAuthOpts.ToTokenV3ScopeMap()
+}
+
+func (opts *AuthOptions) CanReauth() bool {
+ return opts.AllowReauth
+}
+
+func subjectTokenHeaders(c *gophercloud.ServiceClient, subjectToken string) map[string]string {
+ return map[string]string{
+ "X-Subject-Token": subjectToken,
+ }
+}
+
+// Create authenticates and either generates a new token, or changes the Scope
+// of an existing token.
+func Create(c *gophercloud.ServiceClient, opts AuthOptionsBuilder) (r CreateResult) {
+ scope, err := opts.ToTokenV3ScopeMap()
+ if err != nil {
+ r.Err = err
+ return
+ }
+
+ b, err := opts.ToTokenV3CreateMap(scope)
+ if err != nil {
+ r.Err = err
+ return
+ }
+
+ resp, err := c.Post(tokenURL(c), b, &r.Body, &gophercloud.RequestOpts{
+ MoreHeaders: map[string]string{"X-Auth-Token": ""},
+ })
+ r.Err = err
+ if resp != nil {
+ r.Header = resp.Header
+ }
+ return
+}
+
+// Get validates and retrieves information about another token.
+func Get(c *gophercloud.ServiceClient, token string) (r GetResult) {
+ resp, err := c.Get(tokenURL(c), &r.Body, &gophercloud.RequestOpts{
+ MoreHeaders: subjectTokenHeaders(c, token),
+ OkCodes: []int{200, 203},
+ })
+ if resp != nil {
+ r.Err = err
+ r.Header = resp.Header
+ }
+ return
+}
+
+// Validate determines if a specified token is valid or not.
+func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
+ resp, err := c.Head(tokenURL(c), &gophercloud.RequestOpts{
+ MoreHeaders: subjectTokenHeaders(c, token),
+ OkCodes: []int{200, 204, 404},
+ })
+ if err != nil {
+ return false, err
+ }
+
+ return resp.StatusCode == 200 || resp.StatusCode == 204, nil
+}
+
+// Revoke immediately makes specified token invalid.
+func Revoke(c *gophercloud.ServiceClient, token string) (r RevokeResult) {
+ _, r.Err = c.Delete(tokenURL(c), &gophercloud.RequestOpts{
+ MoreHeaders: subjectTokenHeaders(c, token),
+ })
+ return
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go
new file mode 100644
index 000000000..6f26c96bc
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/results.go
@@ -0,0 +1,178 @@
+package tokens
+
+import (
+ "time"
+
+ "github.com/gophercloud/gophercloud"
+)
+
+// Endpoint represents a single API endpoint offered by a service.
+// It matches either a public, internal or admin URL.
+// If supported, it contains a region specifier, again if provided.
+// The significance of the Region field will depend upon your provider.
+type Endpoint struct {
+ ID string `json:"id"`
+ Region string `json:"region"`
+ RegionID string `json:"region_id"`
+ Interface string `json:"interface"`
+ URL string `json:"url"`
+}
+
+// CatalogEntry provides a type-safe interface to an Identity API V3 service
+// catalog listing. Each class of service, such as cloud DNS or block storage
+// services, could have multiple CatalogEntry representing it (one by interface
+// type, e.g public, admin or internal).
+//
+// Note: when looking for the desired service, try, whenever possible, to key
+// off the type field. Otherwise, you'll tie the representation of the service
+// to a specific provider.
+type CatalogEntry struct {
+ // Service ID
+ ID string `json:"id"`
+
+ // Name will contain the provider-specified name for the service.
+ Name string `json:"name"`
+
+ // Type will contain a type string if OpenStack defines a type for the
+ // service. Otherwise, for provider-specific services, the provider may
+ // assign their own type strings.
+ Type string `json:"type"`
+
+ // Endpoints will let the caller iterate over all the different endpoints that
+ // may exist for the service.
+ Endpoints []Endpoint `json:"endpoints"`
+}
+
+// ServiceCatalog provides a view into the service catalog from a previous,
+// successful authentication.
+type ServiceCatalog struct {
+ Entries []CatalogEntry `json:"catalog"`
+}
+
+// Domain provides information about the domain to which this token grants
+// access.
+type Domain struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+}
+
+// User represents a user resource that exists in the Identity Service.
+type User struct {
+ Domain Domain `json:"domain"`
+ ID string `json:"id"`
+ Name string `json:"name"`
+}
+
+// Role provides information about roles to which User is authorized.
+type Role struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+}
+
+// Project provides information about project to which User is authorized.
+type Project struct {
+ Domain Domain `json:"domain"`
+ ID string `json:"id"`
+ Name string `json:"name"`
+}
+
+// commonResult is the response from a request. A commonResult has various
+// methods which can be used to extract different details about the result.
+type commonResult struct {
+ gophercloud.Result
+}
+
+// Extract is a shortcut for ExtractToken.
+// This function is deprecated and still present for backward compatibility.
+func (r commonResult) Extract() (*Token, error) {
+ return r.ExtractToken()
+}
+
+// ExtractToken interprets a commonResult as a Token.
+func (r commonResult) ExtractToken() (*Token, error) {
+ var s Token
+ err := r.ExtractInto(&s)
+ if err != nil {
+ return nil, err
+ }
+
+ // Parse the token itself from the stored headers.
+ s.ID = r.Header.Get("X-Subject-Token")
+
+ return &s, err
+}
+
+// ExtractTokenID implements the gophercloud.AuthResult interface. The returned
+// string is the same as the ID field of the Token struct returned from
+// ExtractToken().
+func (r CreateResult) ExtractTokenID() (string, error) {
+ return r.Header.Get("X-Subject-Token"), r.Err
+}
+
+// ExtractServiceCatalog returns the ServiceCatalog that was generated along
+// with the user's Token.
+func (r commonResult) ExtractServiceCatalog() (*ServiceCatalog, error) {
+ var s ServiceCatalog
+ err := r.ExtractInto(&s)
+ return &s, err
+}
+
+// ExtractUser returns the User that is the owner of the Token.
+func (r commonResult) ExtractUser() (*User, error) {
+ var s struct {
+ User *User `json:"user"`
+ }
+ err := r.ExtractInto(&s)
+ return s.User, err
+}
+
+// ExtractRoles returns Roles to which User is authorized.
+func (r commonResult) ExtractRoles() ([]Role, error) {
+ var s struct {
+ Roles []Role `json:"roles"`
+ }
+ err := r.ExtractInto(&s)
+ return s.Roles, err
+}
+
+// ExtractProject returns Project to which User is authorized.
+func (r commonResult) ExtractProject() (*Project, error) {
+ var s struct {
+ Project *Project `json:"project"`
+ }
+ err := r.ExtractInto(&s)
+ return s.Project, err
+}
+
+// CreateResult is the response from a Create request. Use ExtractToken()
+// to interpret it as a Token, or ExtractServiceCatalog() to interpret it
+// as a service catalog.
+type CreateResult struct {
+ commonResult
+}
+
+// GetResult is the response from a Get request. Use ExtractToken()
+// to interpret it as a Token, or ExtractServiceCatalog() to interpret it
+// as a service catalog.
+type GetResult struct {
+ commonResult
+}
+
+// RevokeResult is response from a Revoke request.
+type RevokeResult struct {
+ commonResult
+}
+
+// Token is a string that grants a user access to a controlled set of services
+// in an OpenStack provider. Each Token is valid for a set length of time.
+type Token struct {
+ // ID is the issued token.
+ ID string `json:"id"`
+
+ // ExpiresAt is the timestamp at which this token will no longer be accepted.
+ ExpiresAt time.Time `json:"expires_at"`
+}
+
+func (r commonResult) ExtractInto(v interface{}) error {
+ return r.ExtractIntoStructPtr(v, "token")
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/urls.go
new file mode 100644
index 000000000..2f864a31c
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/urls.go
@@ -0,0 +1,7 @@
+package tokens
+
+import "github.com/gophercloud/gophercloud"
+
+func tokenURL(c *gophercloud.ServiceClient) string {
+ return c.ServiceURL("auth", "tokens")
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/utils/base_endpoint.go b/vendor/github.com/gophercloud/gophercloud/openstack/utils/base_endpoint.go
new file mode 100644
index 000000000..40080f7af
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/utils/base_endpoint.go
@@ -0,0 +1,28 @@
+package utils
+
+import (
+ "net/url"
+ "regexp"
+ "strings"
+)
+
+// BaseEndpoint will return a URL without the /vX.Y
+// portion of the URL.
+func BaseEndpoint(endpoint string) (string, error) {
+ u, err := url.Parse(endpoint)
+ if err != nil {
+ return "", err
+ }
+
+ u.RawQuery, u.Fragment = "", ""
+
+ path := u.Path
+ versionRe := regexp.MustCompile("v[0-9.]+/?")
+
+ if version := versionRe.FindString(path); version != "" {
+ versionIndex := strings.Index(path, version)
+ u.Path = path[:versionIndex]
+ }
+
+ return u.String(), nil
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/utils/choose_version.go b/vendor/github.com/gophercloud/gophercloud/openstack/utils/choose_version.go
new file mode 100644
index 000000000..27da19f91
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/utils/choose_version.go
@@ -0,0 +1,111 @@
+package utils
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/gophercloud/gophercloud"
+)
+
+// Version is a supported API version, corresponding to a vN package within the appropriate service.
+type Version struct {
+ ID string
+ Suffix string
+ Priority int
+}
+
+var goodStatus = map[string]bool{
+ "current": true,
+ "supported": true,
+ "stable": true,
+}
+
+// ChooseVersion queries the base endpoint of an API to choose the most recent non-experimental alternative from a service's
+// published versions.
+// It returns the highest-Priority Version among the alternatives that are provided, as well as its corresponding endpoint.
+func ChooseVersion(client *gophercloud.ProviderClient, recognized []*Version) (*Version, string, error) {
+ type linkResp struct {
+ Href string `json:"href"`
+ Rel string `json:"rel"`
+ }
+
+ type valueResp struct {
+ ID string `json:"id"`
+ Status string `json:"status"`
+ Links []linkResp `json:"links"`
+ }
+
+ type versionsResp struct {
+ Values []valueResp `json:"values"`
+ }
+
+ type response struct {
+ Versions versionsResp `json:"versions"`
+ }
+
+ normalize := func(endpoint string) string {
+ if !strings.HasSuffix(endpoint, "/") {
+ return endpoint + "/"
+ }
+ return endpoint
+ }
+ identityEndpoint := normalize(client.IdentityEndpoint)
+
+ // If a full endpoint is specified, check version suffixes for a match first.
+ for _, v := range recognized {
+ if strings.HasSuffix(identityEndpoint, v.Suffix) {
+ return v, identityEndpoint, nil
+ }
+ }
+
+ var resp response
+ _, err := client.Request("GET", client.IdentityBase, &gophercloud.RequestOpts{
+ JSONResponse: &resp,
+ OkCodes: []int{200, 300},
+ })
+
+ if err != nil {
+ return nil, "", err
+ }
+
+ var highest *Version
+ var endpoint string
+
+ for _, value := range resp.Versions.Values {
+ href := ""
+ for _, link := range value.Links {
+ if link.Rel == "self" {
+ href = normalize(link.Href)
+ }
+ }
+
+ for _, version := range recognized {
+ if strings.Contains(value.ID, version.ID) {
+ // Prefer a version that exactly matches the provided endpoint.
+ if href == identityEndpoint {
+ if href == "" {
+ return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", value.ID, client.IdentityBase)
+ }
+ return version, href, nil
+ }
+
+ // Otherwise, find the highest-priority version with a whitelisted status.
+ if goodStatus[strings.ToLower(value.Status)] {
+ if highest == nil || version.Priority > highest.Priority {
+ highest = version
+ endpoint = href
+ }
+ }
+ }
+ }
+ }
+
+ if highest == nil {
+ return nil, "", fmt.Errorf("No supported version available from endpoint %s", client.IdentityBase)
+ }
+ if endpoint == "" {
+ return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", highest.ID, client.IdentityBase)
+ }
+
+ return highest, endpoint, nil
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/pagination/http.go b/vendor/github.com/gophercloud/gophercloud/pagination/http.go
new file mode 100644
index 000000000..757295c42
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/pagination/http.go
@@ -0,0 +1,60 @@
+package pagination
+
+import (
+ "encoding/json"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "strings"
+
+ "github.com/gophercloud/gophercloud"
+)
+
+// PageResult stores the HTTP response that returned the current page of results.
+type PageResult struct {
+ gophercloud.Result
+ url.URL
+}
+
+// PageResultFrom parses an HTTP response as JSON and returns a PageResult containing the
+// results, interpreting it as JSON if the content type indicates.
+func PageResultFrom(resp *http.Response) (PageResult, error) {
+ var parsedBody interface{}
+
+ defer resp.Body.Close()
+ rawBody, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return PageResult{}, err
+ }
+
+ if strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json") {
+ err = json.Unmarshal(rawBody, &parsedBody)
+ if err != nil {
+ return PageResult{}, err
+ }
+ } else {
+ parsedBody = rawBody
+ }
+
+ return PageResultFromParsed(resp, parsedBody), err
+}
+
+// PageResultFromParsed constructs a PageResult from an HTTP response that has already had its
+// body parsed as JSON (and closed).
+func PageResultFromParsed(resp *http.Response, body interface{}) PageResult {
+ return PageResult{
+ Result: gophercloud.Result{
+ Body: body,
+ Header: resp.Header,
+ },
+ URL: *resp.Request.URL,
+ }
+}
+
+// Request performs an HTTP request and extracts the http.Response from the result.
+func Request(client *gophercloud.ServiceClient, headers map[string]string, url string) (*http.Response, error) {
+ return client.Get(url, nil, &gophercloud.RequestOpts{
+ MoreHeaders: headers,
+ OkCodes: []int{200, 204, 300},
+ })
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/pagination/linked.go b/vendor/github.com/gophercloud/gophercloud/pagination/linked.go
new file mode 100644
index 000000000..3656fb7f8
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/pagination/linked.go
@@ -0,0 +1,92 @@
+package pagination
+
+import (
+ "fmt"
+ "reflect"
+
+ "github.com/gophercloud/gophercloud"
+)
+
+// LinkedPageBase may be embedded to implement a page that provides navigational "Next" and "Previous" links within its result.
+type LinkedPageBase struct {
+ PageResult
+
+ // LinkPath lists the keys that should be traversed within a response to arrive at the "next" pointer.
+ // If any link along the path is missing, an empty URL will be returned.
+ // If any link results in an unexpected value type, an error will be returned.
+ // When left as "nil", []string{"links", "next"} will be used as a default.
+ LinkPath []string
+}
+
+// NextPageURL extracts the pagination structure from a JSON response and returns the "next" link, if one is present.
+// It assumes that the links are available in a "links" element of the top-level response object.
+// If this is not the case, override NextPageURL on your result type.
+func (current LinkedPageBase) NextPageURL() (string, error) {
+ var path []string
+ var key string
+
+ if current.LinkPath == nil {
+ path = []string{"links", "next"}
+ } else {
+ path = current.LinkPath
+ }
+
+ submap, ok := current.Body.(map[string]interface{})
+ if !ok {
+ err := gophercloud.ErrUnexpectedType{}
+ err.Expected = "map[string]interface{}"
+ err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body))
+ return "", err
+ }
+
+ for {
+ key, path = path[0], path[1:len(path)]
+
+ value, ok := submap[key]
+ if !ok {
+ return "", nil
+ }
+
+ if len(path) > 0 {
+ submap, ok = value.(map[string]interface{})
+ if !ok {
+ err := gophercloud.ErrUnexpectedType{}
+ err.Expected = "map[string]interface{}"
+ err.Actual = fmt.Sprintf("%v", reflect.TypeOf(value))
+ return "", err
+ }
+ } else {
+ if value == nil {
+ // Actual null element.
+ return "", nil
+ }
+
+ url, ok := value.(string)
+ if !ok {
+ err := gophercloud.ErrUnexpectedType{}
+ err.Expected = "string"
+ err.Actual = fmt.Sprintf("%v", reflect.TypeOf(value))
+ return "", err
+ }
+
+ return url, nil
+ }
+ }
+}
+
+// IsEmpty satisifies the IsEmpty method of the Page interface
+func (current LinkedPageBase) IsEmpty() (bool, error) {
+ if b, ok := current.Body.([]interface{}); ok {
+ return len(b) == 0, nil
+ }
+ err := gophercloud.ErrUnexpectedType{}
+ err.Expected = "[]interface{}"
+ err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body))
+ return true, err
+}
+
+// GetBody returns the linked page's body. This method is needed to satisfy the
+// Page interface.
+func (current LinkedPageBase) GetBody() interface{} {
+ return current.Body
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/pagination/marker.go b/vendor/github.com/gophercloud/gophercloud/pagination/marker.go
new file mode 100644
index 000000000..52e53bae8
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/pagination/marker.go
@@ -0,0 +1,58 @@
+package pagination
+
+import (
+ "fmt"
+ "reflect"
+
+ "github.com/gophercloud/gophercloud"
+)
+
+// MarkerPage is a stricter Page interface that describes additional functionality required for use with NewMarkerPager.
+// For convenience, embed the MarkedPageBase struct.
+type MarkerPage interface {
+ Page
+
+ // LastMarker returns the last "marker" value on this page.
+ LastMarker() (string, error)
+}
+
+// MarkerPageBase is a page in a collection that's paginated by "limit" and "marker" query parameters.
+type MarkerPageBase struct {
+ PageResult
+
+ // Owner is a reference to the embedding struct.
+ Owner MarkerPage
+}
+
+// NextPageURL generates the URL for the page of results after this one.
+func (current MarkerPageBase) NextPageURL() (string, error) {
+ currentURL := current.URL
+
+ mark, err := current.Owner.LastMarker()
+ if err != nil {
+ return "", err
+ }
+
+ q := currentURL.Query()
+ q.Set("marker", mark)
+ currentURL.RawQuery = q.Encode()
+
+ return currentURL.String(), nil
+}
+
+// IsEmpty satisifies the IsEmpty method of the Page interface
+func (current MarkerPageBase) IsEmpty() (bool, error) {
+ if b, ok := current.Body.([]interface{}); ok {
+ return len(b) == 0, nil
+ }
+ err := gophercloud.ErrUnexpectedType{}
+ err.Expected = "[]interface{}"
+ err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body))
+ return true, err
+}
+
+// GetBody returns the linked page's body. This method is needed to satisfy the
+// Page interface.
+func (current MarkerPageBase) GetBody() interface{} {
+ return current.Body
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/pagination/pager.go b/vendor/github.com/gophercloud/gophercloud/pagination/pager.go
new file mode 100644
index 000000000..42c0b2dbe
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/pagination/pager.go
@@ -0,0 +1,251 @@
+package pagination
+
+import (
+ "errors"
+ "fmt"
+ "net/http"
+ "reflect"
+ "strings"
+
+ "github.com/gophercloud/gophercloud"
+)
+
+var (
+ // ErrPageNotAvailable is returned from a Pager when a next or previous page is requested, but does not exist.
+ ErrPageNotAvailable = errors.New("The requested page does not exist.")
+)
+
+// Page must be satisfied by the result type of any resource collection.
+// It allows clients to interact with the resource uniformly, regardless of whether or not or how it's paginated.
+// Generally, rather than implementing this interface directly, implementors should embed one of the concrete PageBase structs,
+// instead.
+// Depending on the pagination strategy of a particular resource, there may be an additional subinterface that the result type
+// will need to implement.
+type Page interface {
+ // NextPageURL generates the URL for the page of data that follows this collection.
+ // Return "" if no such page exists.
+ NextPageURL() (string, error)
+
+ // IsEmpty returns true if this Page has no items in it.
+ IsEmpty() (bool, error)
+
+ // GetBody returns the Page Body. This is used in the `AllPages` method.
+ GetBody() interface{}
+}
+
+// Pager knows how to advance through a specific resource collection, one page at a time.
+type Pager struct {
+ client *gophercloud.ServiceClient
+
+ initialURL string
+
+ createPage func(r PageResult) Page
+
+ firstPage Page
+
+ Err error
+
+ // Headers supplies additional HTTP headers to populate on each paged request.
+ Headers map[string]string
+}
+
+// NewPager constructs a manually-configured pager.
+// Supply the URL for the first page, a function that requests a specific page given a URL, and a function that counts a page.
+func NewPager(client *gophercloud.ServiceClient, initialURL string, createPage func(r PageResult) Page) Pager {
+ return Pager{
+ client: client,
+ initialURL: initialURL,
+ createPage: createPage,
+ }
+}
+
+// WithPageCreator returns a new Pager that substitutes a different page creation function. This is
+// useful for overriding List functions in delegation.
+func (p Pager) WithPageCreator(createPage func(r PageResult) Page) Pager {
+ return Pager{
+ client: p.client,
+ initialURL: p.initialURL,
+ createPage: createPage,
+ }
+}
+
+func (p Pager) fetchNextPage(url string) (Page, error) {
+ resp, err := Request(p.client, p.Headers, url)
+ if err != nil {
+ return nil, err
+ }
+
+ remembered, err := PageResultFrom(resp)
+ if err != nil {
+ return nil, err
+ }
+
+ return p.createPage(remembered), nil
+}
+
+// EachPage iterates over each page returned by a Pager, yielding one at a time to a handler function.
+// Return "false" from the handler to prematurely stop iterating.
+func (p Pager) EachPage(handler func(Page) (bool, error)) error {
+ if p.Err != nil {
+ return p.Err
+ }
+ currentURL := p.initialURL
+ for {
+ var currentPage Page
+
+ // if first page has already been fetched, no need to fetch it again
+ if p.firstPage != nil {
+ currentPage = p.firstPage
+ p.firstPage = nil
+ } else {
+ var err error
+ currentPage, err = p.fetchNextPage(currentURL)
+ if err != nil {
+ return err
+ }
+ }
+
+ empty, err := currentPage.IsEmpty()
+ if err != nil {
+ return err
+ }
+ if empty {
+ return nil
+ }
+
+ ok, err := handler(currentPage)
+ if err != nil {
+ return err
+ }
+ if !ok {
+ return nil
+ }
+
+ currentURL, err = currentPage.NextPageURL()
+ if err != nil {
+ return err
+ }
+ if currentURL == "" {
+ return nil
+ }
+ }
+}
+
+// AllPages returns all the pages from a `List` operation in a single page,
+// allowing the user to retrieve all the pages at once.
+func (p Pager) AllPages() (Page, error) {
+ // pagesSlice holds all the pages until they get converted into as Page Body.
+ var pagesSlice []interface{}
+ // body will contain the final concatenated Page body.
+ var body reflect.Value
+
+ // Grab a first page to ascertain the page body type.
+ firstPage, err := p.fetchNextPage(p.initialURL)
+ if err != nil {
+ return nil, err
+ }
+ // Store the page type so we can use reflection to create a new mega-page of
+ // that type.
+ pageType := reflect.TypeOf(firstPage)
+
+ // if it's a single page, just return the firstPage (first page)
+ if _, found := pageType.FieldByName("SinglePageBase"); found {
+ return firstPage, nil
+ }
+
+ // store the first page to avoid getting it twice
+ p.firstPage = firstPage
+
+ // Switch on the page body type. Recognized types are `map[string]interface{}`,
+ // `[]byte`, and `[]interface{}`.
+ switch pb := firstPage.GetBody().(type) {
+ case map[string]interface{}:
+ // key is the map key for the page body if the body type is `map[string]interface{}`.
+ var key string
+ // Iterate over the pages to concatenate the bodies.
+ err = p.EachPage(func(page Page) (bool, error) {
+ b := page.GetBody().(map[string]interface{})
+ for k, v := range b {
+ // If it's a linked page, we don't want the `links`, we want the other one.
+ if !strings.HasSuffix(k, "links") {
+ // check the field's type. we only want []interface{} (which is really []map[string]interface{})
+ switch vt := v.(type) {
+ case []interface{}:
+ key = k
+ pagesSlice = append(pagesSlice, vt...)
+ }
+ }
+ }
+ return true, nil
+ })
+ if err != nil {
+ return nil, err
+ }
+ // Set body to value of type `map[string]interface{}`
+ body = reflect.MakeMap(reflect.MapOf(reflect.TypeOf(key), reflect.TypeOf(pagesSlice)))
+ body.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(pagesSlice))
+ case []byte:
+ // Iterate over the pages to concatenate the bodies.
+ err = p.EachPage(func(page Page) (bool, error) {
+ b := page.GetBody().([]byte)
+ pagesSlice = append(pagesSlice, b)
+ // seperate pages with a comma
+ pagesSlice = append(pagesSlice, []byte{10})
+ return true, nil
+ })
+ if err != nil {
+ return nil, err
+ }
+ if len(pagesSlice) > 0 {
+ // Remove the trailing comma.
+ pagesSlice = pagesSlice[:len(pagesSlice)-1]
+ }
+ var b []byte
+ // Combine the slice of slices in to a single slice.
+ for _, slice := range pagesSlice {
+ b = append(b, slice.([]byte)...)
+ }
+ // Set body to value of type `bytes`.
+ body = reflect.New(reflect.TypeOf(b)).Elem()
+ body.SetBytes(b)
+ case []interface{}:
+ // Iterate over the pages to concatenate the bodies.
+ err = p.EachPage(func(page Page) (bool, error) {
+ b := page.GetBody().([]interface{})
+ pagesSlice = append(pagesSlice, b...)
+ return true, nil
+ })
+ if err != nil {
+ return nil, err
+ }
+ // Set body to value of type `[]interface{}`
+ body = reflect.MakeSlice(reflect.TypeOf(pagesSlice), len(pagesSlice), len(pagesSlice))
+ for i, s := range pagesSlice {
+ body.Index(i).Set(reflect.ValueOf(s))
+ }
+ default:
+ err := gophercloud.ErrUnexpectedType{}
+ err.Expected = "map[string]interface{}/[]byte/[]interface{}"
+ err.Actual = fmt.Sprintf("%T", pb)
+ return nil, err
+ }
+
+ // Each `Extract*` function is expecting a specific type of page coming back,
+ // otherwise the type assertion in those functions will fail. pageType is needed
+ // to create a type in this method that has the same type that the `Extract*`
+ // function is expecting and set the Body of that object to the concatenated
+ // pages.
+ page := reflect.New(pageType)
+ // Set the page body to be the concatenated pages.
+ page.Elem().FieldByName("Body").Set(body)
+ // Set any additional headers that were pass along. The `objectstorage` pacakge,
+ // for example, passes a Content-Type header.
+ h := make(http.Header)
+ for k, v := range p.Headers {
+ h.Add(k, v)
+ }
+ page.Elem().FieldByName("Header").Set(reflect.ValueOf(h))
+ // Type assert the page to a Page interface so that the type assertion in the
+ // `Extract*` methods will work.
+ return page.Elem().Interface().(Page), err
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/pagination/pkg.go b/vendor/github.com/gophercloud/gophercloud/pagination/pkg.go
new file mode 100644
index 000000000..912daea36
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/pagination/pkg.go
@@ -0,0 +1,4 @@
+/*
+Package pagination contains utilities and convenience structs that implement common pagination idioms within OpenStack APIs.
+*/
+package pagination
diff --git a/vendor/github.com/gophercloud/gophercloud/pagination/single.go b/vendor/github.com/gophercloud/gophercloud/pagination/single.go
new file mode 100644
index 000000000..4251d6491
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/pagination/single.go
@@ -0,0 +1,33 @@
+package pagination
+
+import (
+ "fmt"
+ "reflect"
+
+ "github.com/gophercloud/gophercloud"
+)
+
+// SinglePageBase may be embedded in a Page that contains all of the results from an operation at once.
+type SinglePageBase PageResult
+
+// NextPageURL always returns "" to indicate that there are no more pages to return.
+func (current SinglePageBase) NextPageURL() (string, error) {
+ return "", nil
+}
+
+// IsEmpty satisifies the IsEmpty method of the Page interface
+func (current SinglePageBase) IsEmpty() (bool, error) {
+ if b, ok := current.Body.([]interface{}); ok {
+ return len(b) == 0, nil
+ }
+ err := gophercloud.ErrUnexpectedType{}
+ err.Expected = "[]interface{}"
+ err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body))
+ return true, err
+}
+
+// GetBody returns the single page's body. This method is needed to satisfy the
+// Page interface.
+func (current SinglePageBase) GetBody() interface{} {
+ return current.Body
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/params.go b/vendor/github.com/gophercloud/gophercloud/params.go
new file mode 100644
index 000000000..b9986660c
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/params.go
@@ -0,0 +1,491 @@
+package gophercloud
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/url"
+ "reflect"
+ "strconv"
+ "strings"
+ "time"
+)
+
+/*
+BuildRequestBody builds a map[string]interface from the given `struct`. If
+parent is not an empty string, the final map[string]interface returned will
+encapsulate the built one. For example:
+
+ disk := 1
+ createOpts := flavors.CreateOpts{
+ ID: "1",
+ Name: "m1.tiny",
+ Disk: &disk,
+ RAM: 512,
+ VCPUs: 1,
+ RxTxFactor: 1.0,
+ }
+
+ body, err := gophercloud.BuildRequestBody(createOpts, "flavor")
+
+The above example can be run as-is, however it is recommended to look at how
+BuildRequestBody is used within Gophercloud to more fully understand how it
+fits within the request process as a whole rather than use it directly as shown
+above.
+*/
+func BuildRequestBody(opts interface{}, parent string) (map[string]interface{}, error) {
+ optsValue := reflect.ValueOf(opts)
+ if optsValue.Kind() == reflect.Ptr {
+ optsValue = optsValue.Elem()
+ }
+
+ optsType := reflect.TypeOf(opts)
+ if optsType.Kind() == reflect.Ptr {
+ optsType = optsType.Elem()
+ }
+
+ optsMap := make(map[string]interface{})
+ if optsValue.Kind() == reflect.Struct {
+ //fmt.Printf("optsValue.Kind() is a reflect.Struct: %+v\n", optsValue.Kind())
+ for i := 0; i < optsValue.NumField(); i++ {
+ v := optsValue.Field(i)
+ f := optsType.Field(i)
+
+ if f.Name != strings.Title(f.Name) {
+ //fmt.Printf("Skipping field: %s...\n", f.Name)
+ continue
+ }
+
+ //fmt.Printf("Starting on field: %s...\n", f.Name)
+
+ zero := isZero(v)
+ //fmt.Printf("v is zero?: %v\n", zero)
+
+ // if the field has a required tag that's set to "true"
+ if requiredTag := f.Tag.Get("required"); requiredTag == "true" {
+ //fmt.Printf("Checking required field [%s]:\n\tv: %+v\n\tisZero:%v\n", f.Name, v.Interface(), zero)
+ // if the field's value is zero, return a missing-argument error
+ if zero {
+ // if the field has a 'required' tag, it can't have a zero-value
+ err := ErrMissingInput{}
+ err.Argument = f.Name
+ return nil, err
+ }
+ }
+
+ if xorTag := f.Tag.Get("xor"); xorTag != "" {
+ //fmt.Printf("Checking `xor` tag for field [%s] with value %+v:\n\txorTag: %s\n", f.Name, v, xorTag)
+ xorField := optsValue.FieldByName(xorTag)
+ var xorFieldIsZero bool
+ if reflect.ValueOf(xorField.Interface()) == reflect.Zero(xorField.Type()) {
+ xorFieldIsZero = true
+ } else {
+ if xorField.Kind() == reflect.Ptr {
+ xorField = xorField.Elem()
+ }
+ xorFieldIsZero = isZero(xorField)
+ }
+ if !(zero != xorFieldIsZero) {
+ err := ErrMissingInput{}
+ err.Argument = fmt.Sprintf("%s/%s", f.Name, xorTag)
+ err.Info = fmt.Sprintf("Exactly one of %s and %s must be provided", f.Name, xorTag)
+ return nil, err
+ }
+ }
+
+ if orTag := f.Tag.Get("or"); orTag != "" {
+ //fmt.Printf("Checking `or` tag for field with:\n\tname: %+v\n\torTag:%s\n", f.Name, orTag)
+ //fmt.Printf("field is zero?: %v\n", zero)
+ if zero {
+ orField := optsValue.FieldByName(orTag)
+ var orFieldIsZero bool
+ if reflect.ValueOf(orField.Interface()) == reflect.Zero(orField.Type()) {
+ orFieldIsZero = true
+ } else {
+ if orField.Kind() == reflect.Ptr {
+ orField = orField.Elem()
+ }
+ orFieldIsZero = isZero(orField)
+ }
+ if orFieldIsZero {
+ err := ErrMissingInput{}
+ err.Argument = fmt.Sprintf("%s/%s", f.Name, orTag)
+ err.Info = fmt.Sprintf("At least one of %s and %s must be provided", f.Name, orTag)
+ return nil, err
+ }
+ }
+ }
+
+ jsonTag := f.Tag.Get("json")
+ if jsonTag == "-" {
+ continue
+ }
+
+ if v.Kind() == reflect.Slice || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Slice) {
+ sliceValue := v
+ if sliceValue.Kind() == reflect.Ptr {
+ sliceValue = sliceValue.Elem()
+ }
+
+ for i := 0; i < sliceValue.Len(); i++ {
+ element := sliceValue.Index(i)
+ if element.Kind() == reflect.Struct || (element.Kind() == reflect.Ptr && element.Elem().Kind() == reflect.Struct) {
+ _, err := BuildRequestBody(element.Interface(), "")
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+ }
+ if v.Kind() == reflect.Struct || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct) {
+ if zero {
+ //fmt.Printf("value before change: %+v\n", optsValue.Field(i))
+ if jsonTag != "" {
+ jsonTagPieces := strings.Split(jsonTag, ",")
+ if len(jsonTagPieces) > 1 && jsonTagPieces[1] == "omitempty" {
+ if v.CanSet() {
+ if !v.IsNil() {
+ if v.Kind() == reflect.Ptr {
+ v.Set(reflect.Zero(v.Type()))
+ }
+ }
+ //fmt.Printf("value after change: %+v\n", optsValue.Field(i))
+ }
+ }
+ }
+ continue
+ }
+
+ //fmt.Printf("Calling BuildRequestBody with:\n\tv: %+v\n\tf.Name:%s\n", v.Interface(), f.Name)
+ _, err := BuildRequestBody(v.Interface(), f.Name)
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ //fmt.Printf("opts: %+v \n", opts)
+
+ b, err := json.Marshal(opts)
+ if err != nil {
+ return nil, err
+ }
+
+ //fmt.Printf("string(b): %s\n", string(b))
+
+ err = json.Unmarshal(b, &optsMap)
+ if err != nil {
+ return nil, err
+ }
+
+ //fmt.Printf("optsMap: %+v\n", optsMap)
+
+ if parent != "" {
+ optsMap = map[string]interface{}{parent: optsMap}
+ }
+ //fmt.Printf("optsMap after parent added: %+v\n", optsMap)
+ return optsMap, nil
+ }
+ // Return an error if the underlying type of 'opts' isn't a struct.
+ return nil, fmt.Errorf("Options type is not a struct.")
+}
+
+// EnabledState is a convenience type, mostly used in Create and Update
+// operations. Because the zero value of a bool is FALSE, we need to use a
+// pointer instead to indicate zero-ness.
+type EnabledState *bool
+
+// Convenience vars for EnabledState values.
+var (
+ iTrue = true
+ iFalse = false
+
+ Enabled EnabledState = &iTrue
+ Disabled EnabledState = &iFalse
+)
+
+// IPVersion is a type for the possible IP address versions. Valid instances
+// are IPv4 and IPv6
+type IPVersion int
+
+const (
+ // IPv4 is used for IP version 4 addresses
+ IPv4 IPVersion = 4
+ // IPv6 is used for IP version 6 addresses
+ IPv6 IPVersion = 6
+)
+
+// IntToPointer is a function for converting integers into integer pointers.
+// This is useful when passing in options to operations.
+func IntToPointer(i int) *int {
+ return &i
+}
+
+/*
+MaybeString is an internal function to be used by request methods in individual
+resource packages.
+
+It takes a string that might be a zero value and returns either a pointer to its
+address or nil. This is useful for allowing users to conveniently omit values
+from an options struct by leaving them zeroed, but still pass nil to the JSON
+serializer so they'll be omitted from the request body.
+*/
+func MaybeString(original string) *string {
+ if original != "" {
+ return &original
+ }
+ return nil
+}
+
+/*
+MaybeInt is an internal function to be used by request methods in individual
+resource packages.
+
+Like MaybeString, it accepts an int that may or may not be a zero value, and
+returns either a pointer to its address or nil. It's intended to hint that the
+JSON serializer should omit its field.
+*/
+func MaybeInt(original int) *int {
+ if original != 0 {
+ return &original
+ }
+ return nil
+}
+
+/*
+func isUnderlyingStructZero(v reflect.Value) bool {
+ switch v.Kind() {
+ case reflect.Ptr:
+ return isUnderlyingStructZero(v.Elem())
+ default:
+ return isZero(v)
+ }
+}
+*/
+
+var t time.Time
+
+func isZero(v reflect.Value) bool {
+ //fmt.Printf("\n\nchecking isZero for value: %+v\n", v)
+ switch v.Kind() {
+ case reflect.Ptr:
+ if v.IsNil() {
+ return true
+ }
+ return false
+ case reflect.Func, reflect.Map, reflect.Slice:
+ return v.IsNil()
+ case reflect.Array:
+ z := true
+ for i := 0; i < v.Len(); i++ {
+ z = z && isZero(v.Index(i))
+ }
+ return z
+ case reflect.Struct:
+ if v.Type() == reflect.TypeOf(t) {
+ if v.Interface().(time.Time).IsZero() {
+ return true
+ }
+ return false
+ }
+ z := true
+ for i := 0; i < v.NumField(); i++ {
+ z = z && isZero(v.Field(i))
+ }
+ return z
+ }
+ // Compare other types directly:
+ z := reflect.Zero(v.Type())
+ //fmt.Printf("zero type for value: %+v\n\n\n", z)
+ return v.Interface() == z.Interface()
+}
+
+/*
+BuildQueryString is an internal function to be used by request methods in
+individual resource packages.
+
+It accepts a tagged structure and expands it into a URL struct. Field names are
+converted into query parameters based on a "q" tag. For example:
+
+ type struct Something {
+ Bar string `q:"x_bar"`
+ Baz int `q:"lorem_ipsum"`
+ }
+
+ instance := Something{
+ Bar: "AAA",
+ Baz: "BBB",
+ }
+
+will be converted into "?x_bar=AAA&lorem_ipsum=BBB".
+
+The struct's fields may be strings, integers, or boolean values. Fields left at
+their type's zero value will be omitted from the query.
+*/
+func BuildQueryString(opts interface{}) (*url.URL, error) {
+ optsValue := reflect.ValueOf(opts)
+ if optsValue.Kind() == reflect.Ptr {
+ optsValue = optsValue.Elem()
+ }
+
+ optsType := reflect.TypeOf(opts)
+ if optsType.Kind() == reflect.Ptr {
+ optsType = optsType.Elem()
+ }
+
+ params := url.Values{}
+
+ if optsValue.Kind() == reflect.Struct {
+ for i := 0; i < optsValue.NumField(); i++ {
+ v := optsValue.Field(i)
+ f := optsType.Field(i)
+ qTag := f.Tag.Get("q")
+
+ // if the field has a 'q' tag, it goes in the query string
+ if qTag != "" {
+ tags := strings.Split(qTag, ",")
+
+ // if the field is set, add it to the slice of query pieces
+ if !isZero(v) {
+ loop:
+ switch v.Kind() {
+ case reflect.Ptr:
+ v = v.Elem()
+ goto loop
+ case reflect.String:
+ params.Add(tags[0], v.String())
+ case reflect.Int:
+ params.Add(tags[0], strconv.FormatInt(v.Int(), 10))
+ case reflect.Bool:
+ params.Add(tags[0], strconv.FormatBool(v.Bool()))
+ case reflect.Slice:
+ switch v.Type().Elem() {
+ case reflect.TypeOf(0):
+ for i := 0; i < v.Len(); i++ {
+ params.Add(tags[0], strconv.FormatInt(v.Index(i).Int(), 10))
+ }
+ default:
+ for i := 0; i < v.Len(); i++ {
+ params.Add(tags[0], v.Index(i).String())
+ }
+ }
+ case reflect.Map:
+ if v.Type().Key().Kind() == reflect.String && v.Type().Elem().Kind() == reflect.String {
+ var s []string
+ for _, k := range v.MapKeys() {
+ value := v.MapIndex(k).String()
+ s = append(s, fmt.Sprintf("'%s':'%s'", k.String(), value))
+ }
+ params.Add(tags[0], fmt.Sprintf("{%s}", strings.Join(s, ", ")))
+ }
+ }
+ } else {
+ // if the field has a 'required' tag, it can't have a zero-value
+ if requiredTag := f.Tag.Get("required"); requiredTag == "true" {
+ return &url.URL{}, fmt.Errorf("Required query parameter [%s] not set.", f.Name)
+ }
+ }
+ }
+ }
+
+ return &url.URL{RawQuery: params.Encode()}, nil
+ }
+ // Return an error if the underlying type of 'opts' isn't a struct.
+ return nil, fmt.Errorf("Options type is not a struct.")
+}
+
+/*
+BuildHeaders is an internal function to be used by request methods in
+individual resource packages.
+
+It accepts an arbitrary tagged structure and produces a string map that's
+suitable for use as the HTTP headers of an outgoing request. Field names are
+mapped to header names based in "h" tags.
+
+ type struct Something {
+ Bar string `h:"x_bar"`
+ Baz int `h:"lorem_ipsum"`
+ }
+
+ instance := Something{
+ Bar: "AAA",
+ Baz: "BBB",
+ }
+
+will be converted into:
+
+ map[string]string{
+ "x_bar": "AAA",
+ "lorem_ipsum": "BBB",
+ }
+
+Untagged fields and fields left at their zero values are skipped. Integers,
+booleans and string values are supported.
+*/
+func BuildHeaders(opts interface{}) (map[string]string, error) {
+ optsValue := reflect.ValueOf(opts)
+ if optsValue.Kind() == reflect.Ptr {
+ optsValue = optsValue.Elem()
+ }
+
+ optsType := reflect.TypeOf(opts)
+ if optsType.Kind() == reflect.Ptr {
+ optsType = optsType.Elem()
+ }
+
+ optsMap := make(map[string]string)
+ if optsValue.Kind() == reflect.Struct {
+ for i := 0; i < optsValue.NumField(); i++ {
+ v := optsValue.Field(i)
+ f := optsType.Field(i)
+ hTag := f.Tag.Get("h")
+
+ // if the field has a 'h' tag, it goes in the header
+ if hTag != "" {
+ tags := strings.Split(hTag, ",")
+
+ // if the field is set, add it to the slice of query pieces
+ if !isZero(v) {
+ switch v.Kind() {
+ case reflect.String:
+ optsMap[tags[0]] = v.String()
+ case reflect.Int:
+ optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
+ case reflect.Bool:
+ optsMap[tags[0]] = strconv.FormatBool(v.Bool())
+ }
+ } else {
+ // if the field has a 'required' tag, it can't have a zero-value
+ if requiredTag := f.Tag.Get("required"); requiredTag == "true" {
+ return optsMap, fmt.Errorf("Required header [%s] not set.", f.Name)
+ }
+ }
+ }
+
+ }
+ return optsMap, nil
+ }
+ // Return an error if the underlying type of 'opts' isn't a struct.
+ return optsMap, fmt.Errorf("Options type is not a struct.")
+}
+
+// IDSliceToQueryString takes a slice of elements and converts them into a query
+// string. For example, if name=foo and slice=[]int{20, 40, 60}, then the
+// result would be `?name=20&name=40&name=60'
+func IDSliceToQueryString(name string, ids []int) string {
+ str := ""
+ for k, v := range ids {
+ if k == 0 {
+ str += "?"
+ } else {
+ str += "&"
+ }
+ str += fmt.Sprintf("%s=%s", name, strconv.Itoa(v))
+ }
+ return str
+}
+
+// IntWithinRange returns TRUE if an integer falls within a defined range, and
+// FALSE if not.
+func IntWithinRange(val, min, max int) bool {
+ return val > min && val < max
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/provider_client.go b/vendor/github.com/gophercloud/gophercloud/provider_client.go
new file mode 100644
index 000000000..365b4cbcd
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/provider_client.go
@@ -0,0 +1,487 @@
+package gophercloud
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "strings"
+ "sync"
+)
+
+// DefaultUserAgent is the default User-Agent string set in the request header.
+const DefaultUserAgent = "gophercloud/2.0.0"
+
+// UserAgent represents a User-Agent header.
+type UserAgent struct {
+ // prepend is the slice of User-Agent strings to prepend to DefaultUserAgent.
+ // All the strings to prepend are accumulated and prepended in the Join method.
+ prepend []string
+}
+
+// Prepend prepends a user-defined string to the default User-Agent string. Users
+// may pass in one or more strings to prepend.
+func (ua *UserAgent) Prepend(s ...string) {
+ ua.prepend = append(s, ua.prepend...)
+}
+
+// Join concatenates all the user-defined User-Agend strings with the default
+// Gophercloud User-Agent string.
+func (ua *UserAgent) Join() string {
+ uaSlice := append(ua.prepend, DefaultUserAgent)
+ return strings.Join(uaSlice, " ")
+}
+
+// ProviderClient stores details that are required to interact with any
+// services within a specific provider's API.
+//
+// Generally, you acquire a ProviderClient by calling the NewClient method in
+// the appropriate provider's child package, providing whatever authentication
+// credentials are required.
+type ProviderClient struct {
+ // IdentityBase is the base URL used for a particular provider's identity
+ // service - it will be used when issuing authenticatation requests. It
+ // should point to the root resource of the identity service, not a specific
+ // identity version.
+ IdentityBase string
+
+ // IdentityEndpoint is the identity endpoint. This may be a specific version
+ // of the identity service. If this is the case, this endpoint is used rather
+ // than querying versions first.
+ IdentityEndpoint string
+
+ // TokenID is the ID of the most recently issued valid token.
+ // NOTE: Aside from within a custom ReauthFunc, this field shouldn't be set by an application.
+ // To safely read or write this value, call `Token` or `SetToken`, respectively
+ TokenID string
+
+ // EndpointLocator describes how this provider discovers the endpoints for
+ // its constituent services.
+ EndpointLocator EndpointLocator
+
+ // HTTPClient allows users to interject arbitrary http, https, or other transit behaviors.
+ HTTPClient http.Client
+
+ // UserAgent represents the User-Agent header in the HTTP request.
+ UserAgent UserAgent
+
+ // ReauthFunc is the function used to re-authenticate the user if the request
+ // fails with a 401 HTTP response code. This a needed because there may be multiple
+ // authentication functions for different Identity service versions.
+ ReauthFunc func() error
+
+ // Throwaway determines whether if this client is a throw-away client. It's a copy of user's provider client
+ // with the token and reauth func zeroed. Such client can be used to perform reauthorization.
+ Throwaway bool
+
+ mut *sync.RWMutex
+
+ reauthmut *reauthlock
+
+ authResult AuthResult
+}
+
+type reauthlock struct {
+ sync.RWMutex
+ reauthing bool
+ reauthingErr error
+ done *sync.Cond
+}
+
+// AuthenticatedHeaders returns a map of HTTP headers that are common for all
+// authenticated service requests. Blocks if Reauthenticate is in progress.
+func (client *ProviderClient) AuthenticatedHeaders() (m map[string]string) {
+ if client.IsThrowaway() {
+ return
+ }
+ if client.reauthmut != nil {
+ client.reauthmut.Lock()
+ for client.reauthmut.reauthing {
+ client.reauthmut.done.Wait()
+ }
+ client.reauthmut.Unlock()
+ }
+ t := client.Token()
+ if t == "" {
+ return
+ }
+ return map[string]string{"X-Auth-Token": t}
+}
+
+// UseTokenLock creates a mutex that is used to allow safe concurrent access to the auth token.
+// If the application's ProviderClient is not used concurrently, this doesn't need to be called.
+func (client *ProviderClient) UseTokenLock() {
+ client.mut = new(sync.RWMutex)
+ client.reauthmut = new(reauthlock)
+}
+
+// GetAuthResult returns the result from the request that was used to obtain a
+// provider client's Keystone token.
+//
+// The result is nil when authentication has not yet taken place, when the token
+// was set manually with SetToken(), or when a ReauthFunc was used that does not
+// record the AuthResult.
+func (client *ProviderClient) GetAuthResult() AuthResult {
+ if client.mut != nil {
+ client.mut.RLock()
+ defer client.mut.RUnlock()
+ }
+ return client.authResult
+}
+
+// Token safely reads the value of the auth token from the ProviderClient. Applications should
+// call this method to access the token instead of the TokenID field
+func (client *ProviderClient) Token() string {
+ if client.mut != nil {
+ client.mut.RLock()
+ defer client.mut.RUnlock()
+ }
+ return client.TokenID
+}
+
+// SetToken safely sets the value of the auth token in the ProviderClient. Applications may
+// use this method in a custom ReauthFunc.
+//
+// WARNING: This function is deprecated. Use SetTokenAndAuthResult() instead.
+func (client *ProviderClient) SetToken(t string) {
+ if client.mut != nil {
+ client.mut.Lock()
+ defer client.mut.Unlock()
+ }
+ client.TokenID = t
+ client.authResult = nil
+}
+
+// SetTokenAndAuthResult safely sets the value of the auth token in the
+// ProviderClient and also records the AuthResult that was returned from the
+// token creation request. Applications may call this in a custom ReauthFunc.
+func (client *ProviderClient) SetTokenAndAuthResult(r AuthResult) error {
+ tokenID := ""
+ var err error
+ if r != nil {
+ tokenID, err = r.ExtractTokenID()
+ if err != nil {
+ return err
+ }
+ }
+
+ if client.mut != nil {
+ client.mut.Lock()
+ defer client.mut.Unlock()
+ }
+ client.TokenID = tokenID
+ client.authResult = r
+ return nil
+}
+
+// CopyTokenFrom safely copies the token from another ProviderClient into the
+// this one.
+func (client *ProviderClient) CopyTokenFrom(other *ProviderClient) {
+ if client.mut != nil {
+ client.mut.Lock()
+ defer client.mut.Unlock()
+ }
+ if other.mut != nil && other.mut != client.mut {
+ other.mut.RLock()
+ defer other.mut.RUnlock()
+ }
+ client.TokenID = other.TokenID
+ client.authResult = other.authResult
+}
+
+// IsThrowaway safely reads the value of the client Throwaway field.
+func (client *ProviderClient) IsThrowaway() bool {
+ if client.reauthmut != nil {
+ client.reauthmut.RLock()
+ defer client.reauthmut.RUnlock()
+ }
+ return client.Throwaway
+}
+
+// SetThrowaway safely sets the value of the client Throwaway field.
+func (client *ProviderClient) SetThrowaway(v bool) {
+ if client.reauthmut != nil {
+ client.reauthmut.Lock()
+ defer client.reauthmut.Unlock()
+ }
+ client.Throwaway = v
+}
+
+// Reauthenticate calls client.ReauthFunc in a thread-safe way. If this is
+// called because of a 401 response, the caller may pass the previous token. In
+// this case, the reauthentication can be skipped if another thread has already
+// reauthenticated in the meantime. If no previous token is known, an empty
+// string should be passed instead to force unconditional reauthentication.
+func (client *ProviderClient) Reauthenticate(previousToken string) (err error) {
+ if client.ReauthFunc == nil {
+ return nil
+ }
+
+ if client.mut == nil {
+ return client.ReauthFunc()
+ }
+
+ client.reauthmut.Lock()
+ if client.reauthmut.reauthing {
+ for !client.reauthmut.reauthing {
+ client.reauthmut.done.Wait()
+ }
+ err = client.reauthmut.reauthingErr
+ client.reauthmut.Unlock()
+ return err
+ }
+ client.reauthmut.Unlock()
+
+ client.mut.Lock()
+ defer client.mut.Unlock()
+
+ client.reauthmut.Lock()
+ client.reauthmut.reauthing = true
+ client.reauthmut.done = sync.NewCond(client.reauthmut)
+ client.reauthmut.reauthingErr = nil
+ client.reauthmut.Unlock()
+
+ if previousToken == "" || client.TokenID == previousToken {
+ err = client.ReauthFunc()
+ }
+
+ client.reauthmut.Lock()
+ client.reauthmut.reauthing = false
+ client.reauthmut.reauthingErr = err
+ client.reauthmut.done.Broadcast()
+ client.reauthmut.Unlock()
+ return
+}
+
+// RequestOpts customizes the behavior of the provider.Request() method.
+type RequestOpts struct {
+ // JSONBody, if provided, will be encoded as JSON and used as the body of the HTTP request. The
+ // content type of the request will default to "application/json" unless overridden by MoreHeaders.
+ // It's an error to specify both a JSONBody and a RawBody.
+ JSONBody interface{}
+ // RawBody contains an io.Reader that will be consumed by the request directly. No content-type
+ // will be set unless one is provided explicitly by MoreHeaders.
+ RawBody io.Reader
+ // JSONResponse, if provided, will be populated with the contents of the response body parsed as
+ // JSON.
+ JSONResponse interface{}
+ // OkCodes contains a list of numeric HTTP status codes that should be interpreted as success. If
+ // the response has a different code, an error will be returned.
+ OkCodes []int
+ // MoreHeaders specifies additional HTTP headers to be provide on the request. If a header is
+ // provided with a blank value (""), that header will be *omitted* instead: use this to suppress
+ // the default Accept header or an inferred Content-Type, for example.
+ MoreHeaders map[string]string
+ // ErrorContext specifies the resource error type to return if an error is encountered.
+ // This lets resources override default error messages based on the response status code.
+ ErrorContext error
+}
+
+var applicationJSON = "application/json"
+
+// Request performs an HTTP request using the ProviderClient's current HTTPClient. An authentication
+// header will automatically be provided.
+func (client *ProviderClient) Request(method, url string, options *RequestOpts) (*http.Response, error) {
+ var body io.Reader
+ var contentType *string
+
+ // Derive the content body by either encoding an arbitrary object as JSON, or by taking a provided
+ // io.ReadSeeker as-is. Default the content-type to application/json.
+ if options.JSONBody != nil {
+ if options.RawBody != nil {
+ return nil, errors.New("please provide only one of JSONBody or RawBody to gophercloud.Request()")
+ }
+
+ rendered, err := json.Marshal(options.JSONBody)
+ if err != nil {
+ return nil, err
+ }
+
+ body = bytes.NewReader(rendered)
+ contentType = &applicationJSON
+ }
+
+ if options.RawBody != nil {
+ body = options.RawBody
+ }
+
+ // Construct the http.Request.
+ req, err := http.NewRequest(method, url, body)
+ if err != nil {
+ return nil, err
+ }
+
+ // Populate the request headers. Apply options.MoreHeaders last, to give the caller the chance to
+ // modify or omit any header.
+ if contentType != nil {
+ req.Header.Set("Content-Type", *contentType)
+ }
+ req.Header.Set("Accept", applicationJSON)
+
+ // Set the User-Agent header
+ req.Header.Set("User-Agent", client.UserAgent.Join())
+
+ if options.MoreHeaders != nil {
+ for k, v := range options.MoreHeaders {
+ if v != "" {
+ req.Header.Set(k, v)
+ } else {
+ req.Header.Del(k)
+ }
+ }
+ }
+
+ // get latest token from client
+ for k, v := range client.AuthenticatedHeaders() {
+ req.Header.Set(k, v)
+ }
+
+ // Set connection parameter to close the connection immediately when we've got the response
+ req.Close = true
+
+ prereqtok := req.Header.Get("X-Auth-Token")
+
+ // Issue the request.
+ resp, err := client.HTTPClient.Do(req)
+ if err != nil {
+ return nil, err
+ }
+
+ // Allow default OkCodes if none explicitly set
+ okc := options.OkCodes
+ if okc == nil {
+ okc = defaultOkCodes(method)
+ }
+
+ // Validate the HTTP response status.
+ var ok bool
+ for _, code := range okc {
+ if resp.StatusCode == code {
+ ok = true
+ break
+ }
+ }
+
+ if !ok {
+ body, _ := ioutil.ReadAll(resp.Body)
+ resp.Body.Close()
+ respErr := ErrUnexpectedResponseCode{
+ URL: url,
+ Method: method,
+ Expected: options.OkCodes,
+ Actual: resp.StatusCode,
+ Body: body,
+ }
+
+ errType := options.ErrorContext
+ switch resp.StatusCode {
+ case http.StatusBadRequest:
+ err = ErrDefault400{respErr}
+ if error400er, ok := errType.(Err400er); ok {
+ err = error400er.Error400(respErr)
+ }
+ case http.StatusUnauthorized:
+ if client.ReauthFunc != nil {
+ err = client.Reauthenticate(prereqtok)
+ if err != nil {
+ e := &ErrUnableToReauthenticate{}
+ e.ErrOriginal = respErr
+ return nil, e
+ }
+ if options.RawBody != nil {
+ if seeker, ok := options.RawBody.(io.Seeker); ok {
+ seeker.Seek(0, 0)
+ }
+ }
+ resp, err = client.Request(method, url, options)
+ if err != nil {
+ switch err.(type) {
+ case *ErrUnexpectedResponseCode:
+ e := &ErrErrorAfterReauthentication{}
+ e.ErrOriginal = err.(*ErrUnexpectedResponseCode)
+ return nil, e
+ default:
+ e := &ErrErrorAfterReauthentication{}
+ e.ErrOriginal = err
+ return nil, e
+ }
+ }
+ return resp, nil
+ }
+ err = ErrDefault401{respErr}
+ if error401er, ok := errType.(Err401er); ok {
+ err = error401er.Error401(respErr)
+ }
+ case http.StatusForbidden:
+ err = ErrDefault403{respErr}
+ if error403er, ok := errType.(Err403er); ok {
+ err = error403er.Error403(respErr)
+ }
+ case http.StatusNotFound:
+ err = ErrDefault404{respErr}
+ if error404er, ok := errType.(Err404er); ok {
+ err = error404er.Error404(respErr)
+ }
+ case http.StatusMethodNotAllowed:
+ err = ErrDefault405{respErr}
+ if error405er, ok := errType.(Err405er); ok {
+ err = error405er.Error405(respErr)
+ }
+ case http.StatusRequestTimeout:
+ err = ErrDefault408{respErr}
+ if error408er, ok := errType.(Err408er); ok {
+ err = error408er.Error408(respErr)
+ }
+ case 429:
+ err = ErrDefault429{respErr}
+ if error429er, ok := errType.(Err429er); ok {
+ err = error429er.Error429(respErr)
+ }
+ case http.StatusInternalServerError:
+ err = ErrDefault500{respErr}
+ if error500er, ok := errType.(Err500er); ok {
+ err = error500er.Error500(respErr)
+ }
+ case http.StatusServiceUnavailable:
+ err = ErrDefault503{respErr}
+ if error503er, ok := errType.(Err503er); ok {
+ err = error503er.Error503(respErr)
+ }
+ }
+
+ if err == nil {
+ err = respErr
+ }
+
+ return resp, err
+ }
+
+ // Parse the response body as JSON, if requested to do so.
+ if options.JSONResponse != nil {
+ defer resp.Body.Close()
+ if err := json.NewDecoder(resp.Body).Decode(options.JSONResponse); err != nil {
+ return nil, err
+ }
+ }
+
+ return resp, nil
+}
+
+func defaultOkCodes(method string) []int {
+ switch {
+ case method == "GET":
+ return []int{200}
+ case method == "POST":
+ return []int{201, 202}
+ case method == "PUT":
+ return []int{201, 202}
+ case method == "PATCH":
+ return []int{200, 202, 204}
+ case method == "DELETE":
+ return []int{202, 204}
+ }
+
+ return []int{}
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/results.go b/vendor/github.com/gophercloud/gophercloud/results.go
new file mode 100644
index 000000000..94a16bff0
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/results.go
@@ -0,0 +1,448 @@
+package gophercloud
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "reflect"
+ "strconv"
+ "time"
+)
+
+/*
+Result is an internal type to be used by individual resource packages, but its
+methods will be available on a wide variety of user-facing embedding types.
+
+It acts as a base struct that other Result types, returned from request
+functions, can embed for convenience. All Results capture basic information
+from the HTTP transaction that was performed, including the response body,
+HTTP headers, and any errors that happened.
+
+Generally, each Result type will have an Extract method that can be used to
+further interpret the result's payload in a specific context. Extensions or
+providers can then provide additional extraction functions to pull out
+provider- or extension-specific information as well.
+*/
+type Result struct {
+ // Body is the payload of the HTTP response from the server. In most cases,
+ // this will be the deserialized JSON structure.
+ Body interface{}
+
+ // Header contains the HTTP header structure from the original response.
+ Header http.Header
+
+ // Err is an error that occurred during the operation. It's deferred until
+ // extraction to make it easier to chain the Extract call.
+ Err error
+}
+
+// ExtractInto allows users to provide an object into which `Extract` will extract
+// the `Result.Body`. This would be useful for OpenStack providers that have
+// different fields in the response object than OpenStack proper.
+func (r Result) ExtractInto(to interface{}) error {
+ if r.Err != nil {
+ return r.Err
+ }
+
+ if reader, ok := r.Body.(io.Reader); ok {
+ if readCloser, ok := reader.(io.Closer); ok {
+ defer readCloser.Close()
+ }
+ return json.NewDecoder(reader).Decode(to)
+ }
+
+ b, err := json.Marshal(r.Body)
+ if err != nil {
+ return err
+ }
+ err = json.Unmarshal(b, to)
+
+ return err
+}
+
+func (r Result) extractIntoPtr(to interface{}, label string) error {
+ if label == "" {
+ return r.ExtractInto(&to)
+ }
+
+ var m map[string]interface{}
+ err := r.ExtractInto(&m)
+ if err != nil {
+ return err
+ }
+
+ b, err := json.Marshal(m[label])
+ if err != nil {
+ return err
+ }
+
+ toValue := reflect.ValueOf(to)
+ if toValue.Kind() == reflect.Ptr {
+ toValue = toValue.Elem()
+ }
+
+ switch toValue.Kind() {
+ case reflect.Slice:
+ typeOfV := toValue.Type().Elem()
+ if typeOfV.Kind() == reflect.Struct {
+ if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous {
+ newSlice := reflect.MakeSlice(reflect.SliceOf(typeOfV), 0, 0)
+
+ if mSlice, ok := m[label].([]interface{}); ok {
+ for _, v := range mSlice {
+ // For each iteration of the slice, we create a new struct.
+ // This is to work around a bug where elements of a slice
+ // are reused and not overwritten when the same copy of the
+ // struct is used:
+ //
+ // https://github.com/golang/go/issues/21092
+ // https://github.com/golang/go/issues/24155
+ // https://play.golang.org/p/NHo3ywlPZli
+ newType := reflect.New(typeOfV).Elem()
+
+ b, err := json.Marshal(v)
+ if err != nil {
+ return err
+ }
+
+ // This is needed for structs with an UnmarshalJSON method.
+ // Technically this is just unmarshalling the response into
+ // a struct that is never used, but it's good enough to
+ // trigger the UnmarshalJSON method.
+ for i := 0; i < newType.NumField(); i++ {
+ s := newType.Field(i).Addr().Interface()
+
+ // Unmarshal is used rather than NewDecoder to also work
+ // around the above-mentioned bug.
+ err = json.Unmarshal(b, s)
+ if err != nil {
+ return err
+ }
+ }
+
+ newSlice = reflect.Append(newSlice, newType)
+ }
+ }
+
+ // "to" should now be properly modeled to receive the
+ // JSON response body and unmarshal into all the correct
+ // fields of the struct or composed extension struct
+ // at the end of this method.
+ toValue.Set(newSlice)
+ }
+ }
+ case reflect.Struct:
+ typeOfV := toValue.Type()
+ if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous {
+ for i := 0; i < toValue.NumField(); i++ {
+ toField := toValue.Field(i)
+ if toField.Kind() == reflect.Struct {
+ s := toField.Addr().Interface()
+ err = json.NewDecoder(bytes.NewReader(b)).Decode(s)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ }
+ }
+
+ err = json.Unmarshal(b, &to)
+ return err
+}
+
+// ExtractIntoStructPtr will unmarshal the Result (r) into the provided
+// interface{} (to).
+//
+// NOTE: For internal use only
+//
+// `to` must be a pointer to an underlying struct type
+//
+// If provided, `label` will be filtered out of the response
+// body prior to `r` being unmarshalled into `to`.
+func (r Result) ExtractIntoStructPtr(to interface{}, label string) error {
+ if r.Err != nil {
+ return r.Err
+ }
+
+ t := reflect.TypeOf(to)
+ if k := t.Kind(); k != reflect.Ptr {
+ return fmt.Errorf("Expected pointer, got %v", k)
+ }
+ switch t.Elem().Kind() {
+ case reflect.Struct:
+ return r.extractIntoPtr(to, label)
+ default:
+ return fmt.Errorf("Expected pointer to struct, got: %v", t)
+ }
+}
+
+// ExtractIntoSlicePtr will unmarshal the Result (r) into the provided
+// interface{} (to).
+//
+// NOTE: For internal use only
+//
+// `to` must be a pointer to an underlying slice type
+//
+// If provided, `label` will be filtered out of the response
+// body prior to `r` being unmarshalled into `to`.
+func (r Result) ExtractIntoSlicePtr(to interface{}, label string) error {
+ if r.Err != nil {
+ return r.Err
+ }
+
+ t := reflect.TypeOf(to)
+ if k := t.Kind(); k != reflect.Ptr {
+ return fmt.Errorf("Expected pointer, got %v", k)
+ }
+ switch t.Elem().Kind() {
+ case reflect.Slice:
+ return r.extractIntoPtr(to, label)
+ default:
+ return fmt.Errorf("Expected pointer to slice, got: %v", t)
+ }
+}
+
+// PrettyPrintJSON creates a string containing the full response body as
+// pretty-printed JSON. It's useful for capturing test fixtures and for
+// debugging extraction bugs. If you include its output in an issue related to
+// a buggy extraction function, we will all love you forever.
+func (r Result) PrettyPrintJSON() string {
+ pretty, err := json.MarshalIndent(r.Body, "", " ")
+ if err != nil {
+ panic(err.Error())
+ }
+ return string(pretty)
+}
+
+// ErrResult is an internal type to be used by individual resource packages, but
+// its methods will be available on a wide variety of user-facing embedding
+// types.
+//
+// It represents results that only contain a potential error and
+// nothing else. Usually, if the operation executed successfully, the Err field
+// will be nil; otherwise it will be stocked with a relevant error. Use the
+// ExtractErr method
+// to cleanly pull it out.
+type ErrResult struct {
+ Result
+}
+
+// ExtractErr is a function that extracts error information, or nil, from a result.
+func (r ErrResult) ExtractErr() error {
+ return r.Err
+}
+
+/*
+HeaderResult is an internal type to be used by individual resource packages, but
+its methods will be available on a wide variety of user-facing embedding types.
+
+It represents a result that only contains an error (possibly nil) and an
+http.Header. This is used, for example, by the objectstorage packages in
+openstack, because most of the operations don't return response bodies, but do
+have relevant information in headers.
+*/
+type HeaderResult struct {
+ Result
+}
+
+// ExtractInto allows users to provide an object into which `Extract` will
+// extract the http.Header headers of the result.
+func (r HeaderResult) ExtractInto(to interface{}) error {
+ if r.Err != nil {
+ return r.Err
+ }
+
+ tmpHeaderMap := map[string]string{}
+ for k, v := range r.Header {
+ if len(v) > 0 {
+ tmpHeaderMap[k] = v[0]
+ }
+ }
+
+ b, err := json.Marshal(tmpHeaderMap)
+ if err != nil {
+ return err
+ }
+ err = json.Unmarshal(b, to)
+
+ return err
+}
+
+// RFC3339Milli describes a common time format used by some API responses.
+const RFC3339Milli = "2006-01-02T15:04:05.999999Z"
+
+type JSONRFC3339Milli time.Time
+
+func (jt *JSONRFC3339Milli) UnmarshalJSON(data []byte) error {
+ b := bytes.NewBuffer(data)
+ dec := json.NewDecoder(b)
+ var s string
+ if err := dec.Decode(&s); err != nil {
+ return err
+ }
+ t, err := time.Parse(RFC3339Milli, s)
+ if err != nil {
+ return err
+ }
+ *jt = JSONRFC3339Milli(t)
+ return nil
+}
+
+const RFC3339MilliNoZ = "2006-01-02T15:04:05.999999"
+
+type JSONRFC3339MilliNoZ time.Time
+
+func (jt *JSONRFC3339MilliNoZ) UnmarshalJSON(data []byte) error {
+ var s string
+ if err := json.Unmarshal(data, &s); err != nil {
+ return err
+ }
+ if s == "" {
+ return nil
+ }
+ t, err := time.Parse(RFC3339MilliNoZ, s)
+ if err != nil {
+ return err
+ }
+ *jt = JSONRFC3339MilliNoZ(t)
+ return nil
+}
+
+type JSONRFC1123 time.Time
+
+func (jt *JSONRFC1123) UnmarshalJSON(data []byte) error {
+ var s string
+ if err := json.Unmarshal(data, &s); err != nil {
+ return err
+ }
+ if s == "" {
+ return nil
+ }
+ t, err := time.Parse(time.RFC1123, s)
+ if err != nil {
+ return err
+ }
+ *jt = JSONRFC1123(t)
+ return nil
+}
+
+type JSONUnix time.Time
+
+func (jt *JSONUnix) UnmarshalJSON(data []byte) error {
+ var s string
+ if err := json.Unmarshal(data, &s); err != nil {
+ return err
+ }
+ if s == "" {
+ return nil
+ }
+ unix, err := strconv.ParseInt(s, 10, 64)
+ if err != nil {
+ return err
+ }
+ t = time.Unix(unix, 0)
+ *jt = JSONUnix(t)
+ return nil
+}
+
+// RFC3339NoZ is the time format used in Heat (Orchestration).
+const RFC3339NoZ = "2006-01-02T15:04:05"
+
+type JSONRFC3339NoZ time.Time
+
+func (jt *JSONRFC3339NoZ) UnmarshalJSON(data []byte) error {
+ var s string
+ if err := json.Unmarshal(data, &s); err != nil {
+ return err
+ }
+ if s == "" {
+ return nil
+ }
+ t, err := time.Parse(RFC3339NoZ, s)
+ if err != nil {
+ return err
+ }
+ *jt = JSONRFC3339NoZ(t)
+ return nil
+}
+
+// RFC3339ZNoT is the time format used in Zun (Containers Service).
+const RFC3339ZNoT = "2006-01-02 15:04:05-07:00"
+
+type JSONRFC3339ZNoT time.Time
+
+func (jt *JSONRFC3339ZNoT) UnmarshalJSON(data []byte) error {
+ var s string
+ if err := json.Unmarshal(data, &s); err != nil {
+ return err
+ }
+ if s == "" {
+ return nil
+ }
+ t, err := time.Parse(RFC3339ZNoT, s)
+ if err != nil {
+ return err
+ }
+ *jt = JSONRFC3339ZNoT(t)
+ return nil
+}
+
+// RFC3339ZNoTNoZ is another time format used in Zun (Containers Service).
+const RFC3339ZNoTNoZ = "2006-01-02 15:04:05"
+
+type JSONRFC3339ZNoTNoZ time.Time
+
+func (jt *JSONRFC3339ZNoTNoZ) UnmarshalJSON(data []byte) error {
+ var s string
+ if err := json.Unmarshal(data, &s); err != nil {
+ return err
+ }
+ if s == "" {
+ return nil
+ }
+ t, err := time.Parse(RFC3339ZNoTNoZ, s)
+ if err != nil {
+ return err
+ }
+ *jt = JSONRFC3339ZNoTNoZ(t)
+ return nil
+}
+
+/*
+Link is an internal type to be used in packages of collection resources that are
+paginated in a certain way.
+
+It's a response substructure common to many paginated collection results that is
+used to point to related pages. Usually, the one we care about is the one with
+Rel field set to "next".
+*/
+type Link struct {
+ Href string `json:"href"`
+ Rel string `json:"rel"`
+}
+
+/*
+ExtractNextURL is an internal function useful for packages of collection
+resources that are paginated in a certain way.
+
+It attempts to extract the "next" URL from slice of Link structs, or
+"" if no such URL is present.
+*/
+func ExtractNextURL(links []Link) (string, error) {
+ var url string
+
+ for _, l := range links {
+ if l.Rel == "next" {
+ url = l.Href
+ }
+ }
+
+ if url == "" {
+ return "", nil
+ }
+
+ return url, nil
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/service_client.go b/vendor/github.com/gophercloud/gophercloud/service_client.go
new file mode 100644
index 000000000..c889201f9
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/service_client.go
@@ -0,0 +1,152 @@
+package gophercloud
+
+import (
+ "io"
+ "net/http"
+ "strings"
+)
+
+// ServiceClient stores details required to interact with a specific service API implemented by a provider.
+// Generally, you'll acquire these by calling the appropriate `New` method on a ProviderClient.
+type ServiceClient struct {
+ // ProviderClient is a reference to the provider that implements this service.
+ *ProviderClient
+
+ // Endpoint is the base URL of the service's API, acquired from a service catalog.
+ // It MUST end with a /.
+ Endpoint string
+
+ // ResourceBase is the base URL shared by the resources within a service's API. It should include
+ // the API version and, like Endpoint, MUST end with a / if set. If not set, the Endpoint is used
+ // as-is, instead.
+ ResourceBase string
+
+ // This is the service client type (e.g. compute, sharev2).
+ // NOTE: FOR INTERNAL USE ONLY. DO NOT SET. GOPHERCLOUD WILL SET THIS.
+ // It is only exported because it gets set in a different package.
+ Type string
+
+ // The microversion of the service to use. Set this to use a particular microversion.
+ Microversion string
+
+ // MoreHeaders allows users (or Gophercloud) to set service-wide headers on requests. Put another way,
+ // values set in this field will be set on all the HTTP requests the service client sends.
+ MoreHeaders map[string]string
+}
+
+// ResourceBaseURL returns the base URL of any resources used by this service. It MUST end with a /.
+func (client *ServiceClient) ResourceBaseURL() string {
+ if client.ResourceBase != "" {
+ return client.ResourceBase
+ }
+ return client.Endpoint
+}
+
+// ServiceURL constructs a URL for a resource belonging to this provider.
+func (client *ServiceClient) ServiceURL(parts ...string) string {
+ return client.ResourceBaseURL() + strings.Join(parts, "/")
+}
+
+func (client *ServiceClient) initReqOpts(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) {
+ if v, ok := (JSONBody).(io.Reader); ok {
+ opts.RawBody = v
+ } else if JSONBody != nil {
+ opts.JSONBody = JSONBody
+ }
+
+ if JSONResponse != nil {
+ opts.JSONResponse = JSONResponse
+ }
+
+ if opts.MoreHeaders == nil {
+ opts.MoreHeaders = make(map[string]string)
+ }
+
+ if client.Microversion != "" {
+ client.setMicroversionHeader(opts)
+ }
+}
+
+// Get calls `Request` with the "GET" HTTP verb.
+func (client *ServiceClient) Get(url string, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) {
+ if opts == nil {
+ opts = new(RequestOpts)
+ }
+ client.initReqOpts(url, nil, JSONResponse, opts)
+ return client.Request("GET", url, opts)
+}
+
+// Post calls `Request` with the "POST" HTTP verb.
+func (client *ServiceClient) Post(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) {
+ if opts == nil {
+ opts = new(RequestOpts)
+ }
+ client.initReqOpts(url, JSONBody, JSONResponse, opts)
+ return client.Request("POST", url, opts)
+}
+
+// Put calls `Request` with the "PUT" HTTP verb.
+func (client *ServiceClient) Put(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) {
+ if opts == nil {
+ opts = new(RequestOpts)
+ }
+ client.initReqOpts(url, JSONBody, JSONResponse, opts)
+ return client.Request("PUT", url, opts)
+}
+
+// Patch calls `Request` with the "PATCH" HTTP verb.
+func (client *ServiceClient) Patch(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) {
+ if opts == nil {
+ opts = new(RequestOpts)
+ }
+ client.initReqOpts(url, JSONBody, JSONResponse, opts)
+ return client.Request("PATCH", url, opts)
+}
+
+// Delete calls `Request` with the "DELETE" HTTP verb.
+func (client *ServiceClient) Delete(url string, opts *RequestOpts) (*http.Response, error) {
+ if opts == nil {
+ opts = new(RequestOpts)
+ }
+ client.initReqOpts(url, nil, nil, opts)
+ return client.Request("DELETE", url, opts)
+}
+
+// Head calls `Request` with the "HEAD" HTTP verb.
+func (client *ServiceClient) Head(url string, opts *RequestOpts) (*http.Response, error) {
+ if opts == nil {
+ opts = new(RequestOpts)
+ }
+ client.initReqOpts(url, nil, nil, opts)
+ return client.Request("HEAD", url, opts)
+}
+
+func (client *ServiceClient) setMicroversionHeader(opts *RequestOpts) {
+ switch client.Type {
+ case "compute":
+ opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion
+ case "sharev2":
+ opts.MoreHeaders["X-OpenStack-Manila-API-Version"] = client.Microversion
+ case "volume":
+ opts.MoreHeaders["X-OpenStack-Volume-API-Version"] = client.Microversion
+ case "baremetal":
+ opts.MoreHeaders["X-OpenStack-Ironic-API-Version"] = client.Microversion
+ }
+
+ if client.Type != "" {
+ opts.MoreHeaders["OpenStack-API-Version"] = client.Type + " " + client.Microversion
+ }
+}
+
+// Request carries out the HTTP operation for the service client
+func (client *ServiceClient) Request(method, url string, options *RequestOpts) (*http.Response, error) {
+ if len(client.MoreHeaders) > 0 {
+ if options == nil {
+ options = new(RequestOpts)
+ }
+ for k, v := range client.MoreHeaders {
+ options.MoreHeaders[k] = v
+ }
+ }
+ return client.ProviderClient.Request(method, url, options)
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/util.go b/vendor/github.com/gophercloud/gophercloud/util.go
new file mode 100644
index 000000000..68f9a5d3e
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/util.go
@@ -0,0 +1,102 @@
+package gophercloud
+
+import (
+ "fmt"
+ "net/url"
+ "path/filepath"
+ "strings"
+ "time"
+)
+
+// WaitFor polls a predicate function, once per second, up to a timeout limit.
+// This is useful to wait for a resource to transition to a certain state.
+// To handle situations when the predicate might hang indefinitely, the
+// predicate will be prematurely cancelled after the timeout.
+// Resource packages will wrap this in a more convenient function that's
+// specific to a certain resource, but it can also be useful on its own.
+func WaitFor(timeout int, predicate func() (bool, error)) error {
+ type WaitForResult struct {
+ Success bool
+ Error error
+ }
+
+ start := time.Now().Unix()
+
+ for {
+ // If a timeout is set, and that's been exceeded, shut it down.
+ if timeout >= 0 && time.Now().Unix()-start >= int64(timeout) {
+ return fmt.Errorf("A timeout occurred")
+ }
+
+ time.Sleep(1 * time.Second)
+
+ var result WaitForResult
+ ch := make(chan bool, 1)
+ go func() {
+ defer close(ch)
+ satisfied, err := predicate()
+ result.Success = satisfied
+ result.Error = err
+ }()
+
+ select {
+ case <-ch:
+ if result.Error != nil {
+ return result.Error
+ }
+ if result.Success {
+ return nil
+ }
+ // If the predicate has not finished by the timeout, cancel it.
+ case <-time.After(time.Duration(timeout) * time.Second):
+ return fmt.Errorf("A timeout occurred")
+ }
+ }
+}
+
+// NormalizeURL is an internal function to be used by provider clients.
+//
+// It ensures that each endpoint URL has a closing `/`, as expected by
+// ServiceClient's methods.
+func NormalizeURL(url string) string {
+ if !strings.HasSuffix(url, "/") {
+ return url + "/"
+ }
+ return url
+}
+
+// NormalizePathURL is used to convert rawPath to a fqdn, using basePath as
+// a reference in the filesystem, if necessary. basePath is assumed to contain
+// either '.' when first used, or the file:// type fqdn of the parent resource.
+// e.g. myFavScript.yaml => file://opt/lib/myFavScript.yaml
+func NormalizePathURL(basePath, rawPath string) (string, error) {
+ u, err := url.Parse(rawPath)
+ if err != nil {
+ return "", err
+ }
+ // if a scheme is defined, it must be a fqdn already
+ if u.Scheme != "" {
+ return u.String(), nil
+ }
+ // if basePath is a url, then child resources are assumed to be relative to it
+ bu, err := url.Parse(basePath)
+ if err != nil {
+ return "", err
+ }
+ var basePathSys, absPathSys string
+ if bu.Scheme != "" {
+ basePathSys = filepath.FromSlash(bu.Path)
+ absPathSys = filepath.Join(basePathSys, rawPath)
+ bu.Path = filepath.ToSlash(absPathSys)
+ return bu.String(), nil
+ }
+
+ absPathSys = filepath.Join(basePath, rawPath)
+ u.Path = filepath.ToSlash(absPathSys)
+ if err != nil {
+ return "", err
+ }
+ u.Scheme = "file"
+ return u.String(), nil
+
+}
diff --git a/vendor/github.com/ldez/go-auroradns/LICENSE b/vendor/github.com/nrdcg/auroradns/LICENSE
similarity index 100%
rename from vendor/github.com/ldez/go-auroradns/LICENSE
rename to vendor/github.com/nrdcg/auroradns/LICENSE
diff --git a/vendor/github.com/ldez/go-auroradns/auth.go b/vendor/github.com/nrdcg/auroradns/auth.go
similarity index 100%
rename from vendor/github.com/ldez/go-auroradns/auth.go
rename to vendor/github.com/nrdcg/auroradns/auth.go
diff --git a/vendor/github.com/ldez/go-auroradns/client.go b/vendor/github.com/nrdcg/auroradns/client.go
similarity index 100%
rename from vendor/github.com/ldez/go-auroradns/client.go
rename to vendor/github.com/nrdcg/auroradns/client.go
diff --git a/vendor/github.com/ldez/go-auroradns/records.go b/vendor/github.com/nrdcg/auroradns/records.go
similarity index 100%
rename from vendor/github.com/ldez/go-auroradns/records.go
rename to vendor/github.com/nrdcg/auroradns/records.go
diff --git a/vendor/github.com/ldez/go-auroradns/zones.go b/vendor/github.com/nrdcg/auroradns/zones.go
similarity index 100%
rename from vendor/github.com/ldez/go-auroradns/zones.go
rename to vendor/github.com/nrdcg/auroradns/zones.go
diff --git a/vendor/github.com/smueller18/goinwx/LICENSE b/vendor/github.com/nrdcg/goinwx/LICENSE
similarity index 100%
rename from vendor/github.com/smueller18/goinwx/LICENSE
rename to vendor/github.com/nrdcg/goinwx/LICENSE
diff --git a/vendor/github.com/smueller18/goinwx/account.go b/vendor/github.com/nrdcg/goinwx/account.go
similarity index 59%
rename from vendor/github.com/smueller18/goinwx/account.go
rename to vendor/github.com/nrdcg/goinwx/account.go
index 14b2073ae..28b23f8ff 100644
--- a/vendor/github.com/smueller18/goinwx/account.go
+++ b/vendor/github.com/nrdcg/goinwx/account.go
@@ -7,44 +7,38 @@ const (
methodAccountUnlock = "account.unlock"
)
-type AccountService interface {
- Login() error
- Logout() error
- Lock() error
- Unlock(tan string) error
-}
+// AccountService API access to Account.
+type AccountService service
-type AccountServiceOp struct {
- client *Client
-}
-
-var _ AccountService = &AccountServiceOp{}
-
-func (s *AccountServiceOp) Login() error {
+// Login Account login.
+func (s *AccountService) Login() error {
req := s.client.NewRequest(methodAccountLogin, map[string]interface{}{
- "user": s.client.Username,
- "pass": s.client.Password,
+ "user": s.client.username,
+ "pass": s.client.password,
})
_, err := s.client.Do(*req)
return err
}
-func (s *AccountServiceOp) Logout() error {
+// Logout Account logout.
+func (s *AccountService) Logout() error {
req := s.client.NewRequest(methodAccountLogout, nil)
_, err := s.client.Do(*req)
return err
}
-func (s *AccountServiceOp) Lock() error {
+// Lock Account lock.
+func (s *AccountService) Lock() error {
req := s.client.NewRequest(methodAccountLock, nil)
_, err := s.client.Do(*req)
return err
}
-func (s *AccountServiceOp) Unlock(tan string) error {
+// Unlock Account unlock.
+func (s *AccountService) Unlock(tan string) error {
req := s.client.NewRequest(methodAccountUnlock, map[string]interface{}{
"tan": tan,
})
diff --git a/vendor/github.com/smueller18/goinwx/contact.go b/vendor/github.com/nrdcg/goinwx/contact.go
similarity index 78%
rename from vendor/github.com/smueller18/goinwx/contact.go
rename to vendor/github.com/nrdcg/goinwx/contact.go
index 0ae36f5c2..133bd1634 100644
--- a/vendor/github.com/smueller18/goinwx/contact.go
+++ b/vendor/github.com/nrdcg/goinwx/contact.go
@@ -13,64 +13,11 @@ const (
methodContactUpdate = "contact.update"
)
-type ContactService interface {
- Create(*ContactCreateRequest) (int, error)
- Update(*ContactUpdateRequest) error
- Delete(int) error
- Info(int) (*ContactInfoResponse, error)
- List(string) (*ContactListResponse, error)
-}
+// ContactService API access to Contact.
+type ContactService service
-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) {
+// Create Creates a contact.
+func (s *ContactService) Create(request *ContactCreateRequest) (int, error) {
req := s.client.NewRequest(methodContactCreate, structs.Map(request))
resp, err := s.client.Do(*req)
@@ -87,28 +34,31 @@ func (s *ContactServiceOp) Create(request *ContactCreateRequest) (int, error) {
return result["id"], nil
}
-func (s *ContactServiceOp) Delete(roId int) error {
+// Delete Deletes a contact.
+func (s *ContactService) Delete(roID int) error {
req := s.client.NewRequest(methodContactDelete, map[string]interface{}{
- "id": roId,
+ "id": roID,
})
_, err := s.client.Do(*req)
return err
}
-func (s *ContactServiceOp) Update(request *ContactUpdateRequest) error {
+// Update Updates a contact.
+func (s *ContactService) 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) {
+// Info Get information about a contact.
+func (s *ContactService) Info(contactID int) (*ContactInfoResponse, error) {
var requestMap = make(map[string]interface{})
requestMap["wide"] = 1
- if contactId != 0 {
- requestMap["id"] = contactId
+ if contactID != 0 {
+ requestMap["id"] = contactID
}
req := s.client.NewRequest(methodContactInfo, requestMap)
@@ -127,7 +77,8 @@ func (s *ContactServiceOp) Info(contactId int) (*ContactInfoResponse, error) {
return &result, nil
}
-func (s *ContactServiceOp) List(search string) (*ContactListResponse, error) {
+// List Search contacts.
+func (s *ContactService) List(search string) (*ContactListResponse, error) {
var requestMap = make(map[string]interface{})
if search != "" {
@@ -148,3 +99,50 @@ func (s *ContactServiceOp) List(search string) (*ContactListResponse, error) {
return &result, nil
}
+
+// ContactCreateRequest API model.
+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"`
+}
+
+// ContactUpdateRequest API model.
+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"`
+}
+
+// ContactInfoResponse API model.
+type ContactInfoResponse struct {
+ Contact Contact `mapstructure:"contact"`
+}
+
+// ContactListResponse API model.
+type ContactListResponse struct {
+ Count int
+ Contacts []Contact `mapstructure:"contact"`
+}
diff --git a/vendor/github.com/smueller18/goinwx/domain.go b/vendor/github.com/nrdcg/goinwx/domain.go
similarity index 78%
rename from vendor/github.com/smueller18/goinwx/domain.go
rename to vendor/github.com/nrdcg/goinwx/domain.go
index ddbc86a20..48a3c4c63 100644
--- a/vendor/github.com/smueller18/goinwx/domain.go
+++ b/vendor/github.com/nrdcg/goinwx/domain.go
@@ -29,25 +29,154 @@ const (
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)
+// DomainService API access to Domain.
+type DomainService service
+
+// Check Checks domains.
+func (s *DomainService) 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
}
-type DomainServiceOp struct {
- client *Client
+// GetPrices Gets TLDS prices.
+func (s *DomainService) 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
}
-var _ DomainService = &DomainServiceOp{}
+// Register Register a domain.
+func (s *DomainService) 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
+}
+
+// Delete Deletes a domain.
+func (s *DomainService) 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
+}
+
+// Info Gets information about a domain.
+func (s *DomainService) 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
+}
+
+// List List domains.
+func (s *DomainService) 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
+}
+
+// Whois Whois about a domains.
+func (s *DomainService) 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
+}
type domainCheckResponseRoot struct {
Domains []DomainCheckResponse `mapstructure:"domain"`
}
+
+// DomainCheckResponse API model.
type DomainCheckResponse struct {
Available int `mapstructure:"avail"`
Status string `mapstructure:"status"`
@@ -62,6 +191,8 @@ type DomainCheckResponse struct {
type domainPriceResponseRoot struct {
Prices []DomainPriceResponse `mapstructure:"price"`
}
+
+// DomainPriceResponse API model.
type DomainPriceResponse struct {
Tld string `mapstructure:"tld"`
Currency string `mapstructure:"currency"`
@@ -80,6 +211,7 @@ type DomainPriceResponse struct {
TradePeriod int `mapstructure:"tradePeriod"`
}
+// DomainRegisterRequest API model.
type DomainRegisterRequest struct {
Domain string `structs:"domain"`
Period string `structs:"period,omitempty"`
@@ -91,7 +223,7 @@ type DomainRegisterRequest struct {
TransferLock string `structs:"transferLock,omitempty"`
RenewalMode string `structs:"renewalMode,omitempty"`
WhoisProvider string `structs:"whoisProvider,omitempty"`
- WhoisUrl string `structs:"whoisUrl,omitempty"`
+ WhoisURL string `structs:"whoisUrl,omitempty"`
ScDate string `structs:"scDate,omitempty"`
ExtDate string `structs:"extDate,omitempty"`
Asynchron string `structs:"asynchron,omitempty"`
@@ -99,14 +231,16 @@ type DomainRegisterRequest struct {
Testing string `structs:"testing,omitempty"`
}
+// DomainRegisterResponse API model.
type DomainRegisterResponse struct {
- RoId int
- Price float32
- Currency string
+ RoID int `mapstructure:"roId"`
+ Price float32 `mapstructure:"price"`
+ Currency string `mapstructure:"currency"`
}
+// DomainInfoResponse API model.
type DomainInfoResponse struct {
- RoId int `mapstructure:"roId"`
+ RoID int `mapstructure:"roId"`
Domain string `mapstructure:"domain"`
DomainAce string `mapstructure:"domainAce"`
Period string `mapstructure:"period"`
@@ -129,27 +263,29 @@ type DomainInfoResponse struct {
Contacts map[string]Contact `mapstructure:"contact"`
}
+// Contact API model.
type Contact struct {
- RoId int
- Id string
- Type string
- Name string
- Org string
- Street string
- City string
+ RoID int `mapstructure:"roId"`
+ ID string `mapstructure:"id"`
+ Type string `mapstructure:"type"`
+ Name string `mapstructure:"name"`
+ Org string `mapstructure:"org"`
+ Street string `mapstructure:"street"`
+ City string `mapstructure:"city"`
PostalCode string `mapstructure:"pc"`
StateProvince string `mapstructure:"sp"`
Country string `mapstructure:"cc"`
Phone string `mapstructure:"voice"`
- Fax string
- Email string
- Remarks string
- Protection string
+ Fax string `mapstructure:"fax"`
+ Email string `mapstructure:"email"`
+ Remarks string `mapstructure:"remarks"`
+ Protection string `mapstructure:"protection"`
}
+// DomainListRequest API model.
type DomainListRequest struct {
Domain string `structs:"domain,omitempty"`
- RoId int `structs:"roId,omitempty"`
+ RoID int `structs:"roId,omitempty"`
Status int `structs:"status,omitempty"`
Registrant int `structs:"registrant,omitempty"`
Admin int `structs:"admin,omitempty"`
@@ -161,143 +297,11 @@ type DomainListRequest struct {
Tag int `structs:"tag,omitempty"`
Order int `structs:"order,omitempty"`
Page int `structs:"page,omitempty"`
- Pagelimit int `structs:"pagelimit,omitempty"`
+ PageLimit int `structs:"pagelimit,omitempty"`
}
+// DomainList API model.
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/nrdcg/goinwx/goinwx.go b/vendor/github.com/nrdcg/goinwx/goinwx.go
new file mode 100644
index 000000000..e102c5268
--- /dev/null
+++ b/vendor/github.com/nrdcg/goinwx/goinwx.go
@@ -0,0 +1,112 @@
+package goinwx
+
+import (
+ "net/url"
+
+ "github.com/kolo/xmlrpc"
+)
+
+// API information.
+const (
+ 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 and password
+ username string
+ password string
+
+ common service // Reuse a single struct instead of allocating one for each service on the heap.
+
+ // Services used for communicating with the API
+ Account *AccountService
+ Domains *DomainService
+ Nameservers *NameserverService
+ Contacts *ContactService
+}
+
+type service struct {
+ client *Client
+}
+
+// ClientOptions Options of the API client.
+type ClientOptions struct {
+ Sandbox bool
+}
+
+// Request The representation of an API request.
+type Request struct {
+ ServiceMethod string
+ Args map[string]interface{}
+}
+
+// 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.common.client = client
+ client.Account = (*AccountService)(&client.common)
+ client.Domains = (*DomainService)(&client.common)
+ client.Nameservers = (*NameserverService)(&client.common)
+ client.Contacts = (*ContactService)(&client.common)
+
+ 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)
+}
+
+// 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/nrdcg/goinwx/nameserver.go
similarity index 54%
rename from vendor/github.com/smueller18/goinwx/nameserver.go
rename to vendor/github.com/nrdcg/goinwx/nameserver.go
index 38269b461..cd3979db3 100644
--- a/vendor/github.com/smueller18/goinwx/nameserver.go
+++ b/vendor/github.com/nrdcg/goinwx/nameserver.go
@@ -21,114 +21,11 @@ const (
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)
-}
+// NameserverService API access to Nameservers.
+type NameserverService service
-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) {
+// Check Checks a domain on nameservers.
+func (s *NameserverService) Check(domain string, nameservers []string) (*NameserverCheckResponse, error) {
req := s.client.NewRequest(methodNameserverCheck, map[string]interface{}{
"domain": domain,
"ns": nameservers,
@@ -148,7 +45,8 @@ func (s *NameserverServiceOp) Check(domain string, nameservers []string) (*Names
return &result, nil
}
-func (s *NameserverServiceOp) Info(request *NameserverInfoRequest) (*NamserverInfoResponse, error) {
+// Info Gets informations.
+func (s *NameserverService) Info(request *NameserverInfoRequest) (*NamserverInfoResponse, error) {
req := s.client.NewRequest(methodNameserverInfo, structs.Map(request))
resp, err := s.client.Do(*req)
@@ -164,7 +62,8 @@ func (s *NameserverServiceOp) Info(request *NameserverInfoRequest) (*NamserverIn
return &result, nil
}
-func (s *NameserverServiceOp) List(domain string) (*NamserverListResponse, error) {
+// List List nameservers for a domain.
+func (s *NameserverService) List(domain string) (*NamserverListResponse, error) {
requestMap := map[string]interface{}{
"domain": "*",
"wide": 2,
@@ -187,7 +86,8 @@ func (s *NameserverServiceOp) List(domain string) (*NamserverListResponse, error
return &result, nil
}
-func (s *NameserverServiceOp) Create(request *NameserverCreateRequest) (int, error) {
+// Create Creates a namesever.
+func (s *NameserverService) Create(request *NameserverCreateRequest) (int, error) {
req := s.client.NewRequest(methodNameserverCreate, structs.Map(request))
resp, err := s.client.Do(*req)
@@ -204,7 +104,8 @@ func (s *NameserverServiceOp) Create(request *NameserverCreateRequest) (int, err
return result["roId"], nil
}
-func (s *NameserverServiceOp) CreateRecord(request *NameserverRecordRequest) (int, error) {
+// CreateRecord Creates a DNS record.
+func (s *NameserverService) CreateRecord(request *NameserverRecordRequest) (int, error) {
req := s.client.NewRequest(methodNameserverCreateRecord, structs.Map(request))
resp, err := s.client.Do(*req)
@@ -221,12 +122,13 @@ func (s *NameserverServiceOp) CreateRecord(request *NameserverRecordRequest) (in
return result["id"], nil
}
-func (s *NameserverServiceOp) UpdateRecord(recId int, request *NameserverRecordRequest) error {
+// UpdateRecord Updates a DNS record.
+func (s *NameserverService) UpdateRecord(recID int, request *NameserverRecordRequest) error {
if request == nil {
- return errors.New("Request can't be nil")
+ return errors.New("request can't be nil")
}
requestMap := structs.Map(request)
- requestMap["id"] = recId
+ requestMap["id"] = recID
req := s.client.NewRequest(methodNameserverUpdateRecord, requestMap)
@@ -238,9 +140,10 @@ func (s *NameserverServiceOp) UpdateRecord(recId int, request *NameserverRecordR
return nil
}
-func (s *NameserverServiceOp) DeleteRecord(recId int) error {
+// DeleteRecord Deletes a DNS record.
+func (s *NameserverService) DeleteRecord(recID int) error {
req := s.client.NewRequest(methodNameserverDeleteRecord, map[string]interface{}{
- "id": recId,
+ "id": recID,
})
_, err := s.client.Do(*req)
@@ -251,25 +154,130 @@ func (s *NameserverServiceOp) DeleteRecord(recId int) error {
return nil
}
-func (s *NameserverServiceOp) FindRecordById(recId int) (*NameserverRecord, *NameserverDomain, error) {
+// FindRecordByID Search a DNS record by ID.
+func (s *NameserverService) 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})
+ 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 {
+ if record.ID == recID {
return &record, &domainItem, nil
}
}
}
- return nil, nil, fmt.Errorf("couldn't find INWX Record for id %d", recId)
+ return nil, nil, fmt.Errorf("couldn't find INWX Record for id %d", recID)
}
+
+// NameserverCheckResponse API model.
+type NameserverCheckResponse struct {
+ Details []string
+ Status string
+}
+
+// NameserverRecordRequest API model.
+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"`
+}
+
+// NameserverCreateRequest API model.
+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"`
+}
+
+// NameserverInfoRequest API model.
+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"`
+ Priority int `structs:"prio,omitempty"`
+}
+
+// NamserverInfoResponse API model.
+type NamserverInfoResponse struct {
+ RoID int `mapstructure:"roId"`
+ Domain string `mapstructure:"domain"`
+ Type string `mapstructure:"type"`
+ MasterIP string `mapstructure:"masterIp"`
+ LastZoneCheck time.Time `mapstructure:"lastZoneCheck"`
+ SlaveDNS []SlaveInfo `mapstructure:"slaveDns"`
+ SOASerial string `mapstructure:"SOAserial"`
+ Count int `mapstructure:"count"`
+ Records []NameserverRecord `mapstructure:"record"`
+}
+
+// SlaveInfo API model.
+type SlaveInfo struct {
+ Name string `mapstructure:"name"`
+ IP string `mapstructure:"ip"`
+}
+
+// NameserverRecord API model.
+type NameserverRecord struct {
+ ID int `mapstructure:"id"`
+ Name string `mapstructure:"name"`
+ Type string `mapstructure:"type"`
+ Content string `mapstructure:"content"`
+ TTL int `mapstructure:"TTL"`
+ Priority int `mapstructure:"prio"`
+ URLRedirectType string `mapstructure:"urlRedirectType"`
+ URLRedirectTitle string `mapstructure:"urlRedirectTitle"`
+ URLRedirectDescription string `mapstructure:"urlRedirectDescription"`
+ URLRedirectKeywords string `mapstructure:"urlRedirectKeywords"`
+ URLRedirectFavIcon string `mapstructure:"urlRedirectFavIcon"`
+}
+
+// NamserverListResponse API model.
+type NamserverListResponse struct {
+ Count int
+ Domains []NameserverDomain `mapstructure:"domains"`
+}
+
+// NameserverDomain API model.
+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"`
+}
diff --git a/vendor/github.com/nrdcg/goinwx/response.go b/vendor/github.com/nrdcg/goinwx/response.go
new file mode 100644
index 000000000..feb3d77aa
--- /dev/null
+++ b/vendor/github.com/nrdcg/goinwx/response.go
@@ -0,0 +1,28 @@
+package goinwx
+
+import "fmt"
+
+// 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"`
+}
+
+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)
+}
diff --git a/vendor/github.com/smueller18/goinwx/goinwx.go b/vendor/github.com/smueller18/goinwx/goinwx.go
deleted file mode 100644
index b4d67d621..000000000
--- a/vendor/github.com/smueller18/goinwx/goinwx.go
+++ /dev/null
@@ -1,133 +0,0 @@
-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/tuvistavie/securerandom/srandom.go b/vendor/github.com/tuvistavie/securerandom/srandom.go
deleted file mode 100644
index 30c36f13a..000000000
--- a/vendor/github.com/tuvistavie/securerandom/srandom.go
+++ /dev/null
@@ -1,70 +0,0 @@
-package securerandom
-
-import (
- "bytes"
- "crypto/rand"
- "encoding/base64"
- "encoding/binary"
- "encoding/hex"
- "fmt"
- "strings"
-)
-
-func RandomBytes(n int) ([]byte, error) {
- bytes := make([]byte, n)
- _, err := rand.Read(bytes)
- if err != nil {
- return nil, err
- }
- return bytes, nil
-}
-
-func Base64(n int, padded bool) (string, error) {
- bytes, err := RandomBytes(n)
- if err != nil {
- return "", err
- }
- result := base64.StdEncoding.EncodeToString(bytes)
- result = strings.Replace(result, "\n", "", -1)
- if !padded {
- result = strings.Replace(result, "=", "", -1)
- }
- return result, nil
-}
-
-func UrlSafeBase64(n int, padded bool) (string, error) {
- result, err := Base64(n, padded)
- if err != nil {
- return "", err
- }
- result = strings.Replace(result, "+", "-", -1)
- result = strings.Replace(result, "/", "_", -1)
- return result, nil
-}
-
-func Hex(n int) (string, error) {
- bytes, err := RandomBytes(n)
- if err != nil {
- return "", err
- }
- return hex.EncodeToString(bytes), nil
-}
-
-func Uuid() (string, error) {
- var first, last uint32
- var middle [4]uint16
- randomBytes, err := RandomBytes(16)
- if err != nil {
- return "", err
- }
- buffer := bytes.NewBuffer(randomBytes)
- binary.Read(buffer, binary.BigEndian, &first)
- for i := 0; i < 4; i++ {
- binary.Read(buffer, binary.BigEndian, &middle[i])
- }
- binary.Read(buffer, binary.BigEndian, &last)
- middle[1] = (middle[1] & 0x0fff) | 0x4000
- middle[2] = (middle[2] & 0x3fff) | 0x8000
- return fmt.Sprintf("%08x-%04x-%04x-%04x-%04x%08x",
- first, middle[0], middle[1], middle[2], middle[3], last), nil
-}
diff --git a/vendor/github.com/xenolf/lego/acme/api/api.go b/vendor/github.com/xenolf/lego/acme/api/api.go
index 14b18f52b..a62e49e41 100644
--- a/vendor/github.com/xenolf/lego/acme/api/api.go
+++ b/vendor/github.com/xenolf/lego/acme/api/api.go
@@ -2,12 +2,15 @@ package api
import (
"bytes"
+ "context"
"crypto"
"encoding/json"
"errors"
"fmt"
"net/http"
+ "time"
+ "github.com/cenkalti/backoff"
"github.com/xenolf/lego/acme"
"github.com/xenolf/lego/acme/api/internal/nonces"
"github.com/xenolf/lego/acme/api/internal/secure"
@@ -64,34 +67,46 @@ func (a *Core) post(uri string, reqBody, response interface{}) (*http.Response,
return nil, errors.New("failed to marshal message")
}
- return a.retrievablePost(uri, content, response, 0)
+ return a.retrievablePost(uri, content, response)
}
// postAsGet performs an HTTP POST ("POST-as-GET") request.
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-6.3
func (a *Core) postAsGet(uri string, response interface{}) (*http.Response, error) {
- return a.retrievablePost(uri, []byte{}, response, 0)
+ return a.retrievablePost(uri, []byte{}, response)
}
-func (a *Core) retrievablePost(uri string, content []byte, response interface{}, retry int) (*http.Response, error) {
- resp, err := a.signedPost(uri, content, response)
- if err != nil {
- // during tests, 5 retries allow to support ~50% of bad nonce.
- if retry >= 5 {
- log.Infof("too many retry on a nonce error, retry count: %d", retry)
- return resp, err
- }
- switch err.(type) {
- // Retry once if the nonce was invalidated
- case *acme.NonceError:
- log.Infof("nonce error retry: %s", err)
- resp, err = a.retrievablePost(uri, content, response, retry+1)
- if err != nil {
- return resp, err
+func (a *Core) retrievablePost(uri string, content []byte, response interface{}) (*http.Response, error) {
+ // during tests, allow to support ~90% of bad nonce with a minimum of attempts.
+ bo := backoff.NewExponentialBackOff()
+ bo.InitialInterval = 200 * time.Millisecond
+ bo.MaxInterval = 5 * time.Second
+ bo.MaxElapsedTime = 20 * time.Second
+
+ ctx, cancel := context.WithCancel(context.Background())
+
+ var resp *http.Response
+ operation := func() error {
+ var err error
+ resp, err = a.signedPost(uri, content, response)
+ if err != nil {
+ switch err.(type) {
+ // Retry if the nonce was invalidated
+ case *acme.NonceError:
+ log.Infof("nonce error retry: %s", err)
+ return err
+ default:
+ cancel()
+ return err
}
- default:
- return resp, err
}
+
+ return nil
+ }
+
+ err := backoff.Retry(operation, backoff.WithContext(bo, ctx))
+ if err != nil {
+ return nil, err
}
return resp, nil
diff --git a/vendor/github.com/xenolf/lego/acme/api/internal/sender/useragent.go b/vendor/github.com/xenolf/lego/acme/api/internal/sender/useragent.go
index fa9096990..7f0fec165 100644
--- a/vendor/github.com/xenolf/lego/acme/api/internal/sender/useragent.go
+++ b/vendor/github.com/xenolf/lego/acme/api/internal/sender/useragent.go
@@ -5,7 +5,7 @@ package sender
const (
// ourUserAgent is the User-Agent of this underlying library package.
- ourUserAgent = "xenolf-acme/2.1.0"
+ ourUserAgent = "xenolf-acme/2.2.0"
// ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package.
// values: detach|release
diff --git a/vendor/github.com/xenolf/lego/certcrypto/crypto.go b/vendor/github.com/xenolf/lego/certcrypto/crypto.go
index bb99d7d29..c9d0c1098 100644
--- a/vendor/github.com/xenolf/lego/certcrypto/crypto.go
+++ b/vendor/github.com/xenolf/lego/certcrypto/crypto.go
@@ -124,6 +124,10 @@ func GenerateCSR(privateKey crypto.PrivateKey, domain string, san []string, must
}
func PEMEncode(data interface{}) []byte {
+ return pem.EncodeToMemory(PEMBlock(data))
+}
+
+func PEMBlock(data interface{}) *pem.Block {
var pemBlock *pem.Block
switch key := data.(type) {
case *ecdsa.PrivateKey:
@@ -137,7 +141,7 @@ func PEMEncode(data interface{}) []byte {
pemBlock = &pem.Block{Type: "CERTIFICATE", Bytes: []byte(data.(DERCertificateBytes))}
}
- return pem.EncodeToMemory(pemBlock)
+ return pemBlock
}
func pemDecode(data []byte) (*pem.Block, error) {
diff --git a/vendor/github.com/xenolf/lego/challenge/dns01/cname.go b/vendor/github.com/xenolf/lego/challenge/dns01/cname.go
new file mode 100644
index 000000000..619c84763
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/challenge/dns01/cname.go
@@ -0,0 +1,16 @@
+package dns01
+
+import "github.com/miekg/dns"
+
+// Update FQDN with CNAME if any
+func updateDomainWithCName(r *dns.Msg, fqdn string) string {
+ for _, rr := range r.Answer {
+ if cn, ok := rr.(*dns.CNAME); ok {
+ if cn.Hdr.Name == fqdn {
+ return cn.Target
+ }
+ }
+ }
+
+ return fqdn
+}
diff --git a/vendor/github.com/xenolf/lego/challenge/dns01/dns_challenge.go b/vendor/github.com/xenolf/lego/challenge/dns01/dns_challenge.go
index c9ef2ee3d..68c795899 100644
--- a/vendor/github.com/xenolf/lego/challenge/dns01/dns_challenge.go
+++ b/vendor/github.com/xenolf/lego/challenge/dns01/dns_challenge.go
@@ -4,8 +4,11 @@ import (
"crypto/sha256"
"encoding/base64"
"fmt"
+ "os"
+ "strconv"
"time"
+ "github.com/miekg/dns"
"github.com/xenolf/lego/acme"
"github.com/xenolf/lego/acme/api"
"github.com/xenolf/lego/challenge"
@@ -135,7 +138,7 @@ func (c *Challenge) Solve(authz acme.Authorization) error {
}
chlng.KeyAuthorization = keyAuth
- return c.validate(c.core, authz.Identifier.Value, chlng)
+ return c.validate(c.core, domain, chlng)
}
// CleanUp cleans the challenge.
@@ -172,5 +175,14 @@ func GetRecord(domain, keyAuth string) (fqdn string, value string) {
// base64URL encoding without padding
value = base64.RawURLEncoding.EncodeToString(keyAuthShaBytes[:sha256.Size])
fqdn = fmt.Sprintf("_acme-challenge.%s.", domain)
+
+ if ok, _ := strconv.ParseBool(os.Getenv("LEGO_EXPERIMENTAL_CNAME_SUPPORT")); ok {
+ r, err := dnsQuery(fqdn, dns.TypeCNAME, recursiveNameservers, true)
+ // Check if the domain has CNAME then return that
+ if err == nil && r.Rcode == dns.RcodeSuccess {
+ fqdn = updateDomainWithCName(r, fqdn)
+ }
+ }
+
return
}
diff --git a/vendor/github.com/xenolf/lego/challenge/dns01/precheck.go b/vendor/github.com/xenolf/lego/challenge/dns01/precheck.go
index 63b72cef0..c3389110d 100644
--- a/vendor/github.com/xenolf/lego/challenge/dns01/precheck.go
+++ b/vendor/github.com/xenolf/lego/challenge/dns01/precheck.go
@@ -60,15 +60,7 @@ func (p preCheck) checkDNSPropagation(fqdn, value string) (bool, error) {
}
if r.Rcode == dns.RcodeSuccess {
- // If we see a CNAME here then use the alias
- for _, rr := range r.Answer {
- if cn, ok := rr.(*dns.CNAME); ok {
- if cn.Hdr.Name == fqdn {
- fqdn = cn.Target
- break
- }
- }
- }
+ fqdn = updateDomainWithCName(r, fqdn)
}
authoritativeNss, err := lookupNameservers(fqdn)
diff --git a/vendor/github.com/xenolf/lego/challenge/http01/http_challenge.go b/vendor/github.com/xenolf/lego/challenge/http01/http_challenge.go
index 4176ecae4..861bdbed4 100644
--- a/vendor/github.com/xenolf/lego/challenge/http01/http_challenge.go
+++ b/vendor/github.com/xenolf/lego/challenge/http01/http_challenge.go
@@ -61,5 +61,5 @@ func (c *Challenge) Solve(authz acme.Authorization) error {
}()
chlng.KeyAuthorization = keyAuth
- return c.validate(c.core, authz.Identifier.Value, chlng)
+ return c.validate(c.core, domain, chlng)
}
diff --git a/vendor/github.com/xenolf/lego/challenge/resolver/solver_manager.go b/vendor/github.com/xenolf/lego/challenge/resolver/solver_manager.go
index 55faf77ab..840e19d8f 100644
--- a/vendor/github.com/xenolf/lego/challenge/resolver/solver_manager.go
+++ b/vendor/github.com/xenolf/lego/challenge/resolver/solver_manager.go
@@ -1,12 +1,14 @@
package resolver
import (
+ "context"
"errors"
"fmt"
"sort"
"strconv"
"time"
+ "github.com/cenkalti/backoff"
"github.com/xenolf/lego/acme"
"github.com/xenolf/lego/acme/api"
"github.com/xenolf/lego/challenge"
@@ -90,16 +92,35 @@ func validate(core *api.Core, domain string, chlg acme.Challenge) error {
return nil
}
+ ra, err := strconv.Atoi(chlng.RetryAfter)
+ if err != nil {
+ // The ACME server MUST return a Retry-After.
+ // If it doesn't, we'll just poll hard.
+ // Boulder does not implement the ability to retry challenges or the Retry-After header.
+ // https://github.com/letsencrypt/boulder/blob/master/docs/acme-divergences.md#section-82
+ ra = 5
+ }
+ initialInterval := time.Duration(ra) * time.Second
+
+ bo := backoff.NewExponentialBackOff()
+ bo.InitialInterval = initialInterval
+ bo.MaxInterval = 10 * initialInterval
+ bo.MaxElapsedTime = 100 * initialInterval
+
+ ctx, cancel := context.WithCancel(context.Background())
+
// After the path is sent, the ACME server will access our server.
// Repeatedly check the server for an updated status on our request.
- for {
+ operation := func() error {
authz, err := core.Authorizations.Get(chlng.AuthorizationURL)
if err != nil {
+ cancel()
return err
}
valid, err := checkAuthorizationStatus(authz)
if err != nil {
+ cancel()
return err
}
@@ -108,16 +129,10 @@ func validate(core *api.Core, domain string, chlg acme.Challenge) error {
return nil
}
- ra, err := strconv.Atoi(chlng.RetryAfter)
- if err != nil {
- // The ACME server MUST return a Retry-After.
- // If it doesn't, we'll just poll hard.
- // Boulder does not implement the ability to retry challenges or the Retry-After header.
- // https://github.com/letsencrypt/boulder/blob/master/docs/acme-divergences.md#section-82
- ra = 5
- }
- time.Sleep(time.Duration(ra) * time.Second)
+ return errors.New("the server didn't respond to our request")
}
+
+ return backoff.Retry(operation, backoff.WithContext(bo, ctx))
}
func checkChallengeStatus(chlng acme.ExtendedChallenge) (bool, error) {
diff --git a/vendor/github.com/xenolf/lego/providers/dns/auroradns/auroradns.go b/vendor/github.com/xenolf/lego/providers/dns/auroradns/auroradns.go
index 3b16b6587..d61e30ba9 100644
--- a/vendor/github.com/xenolf/lego/providers/dns/auroradns/auroradns.go
+++ b/vendor/github.com/xenolf/lego/providers/dns/auroradns/auroradns.go
@@ -7,7 +7,7 @@ import (
"sync"
"time"
- auroradns "github.com/ldez/go-auroradns"
+ "github.com/nrdcg/auroradns"
"github.com/xenolf/lego/challenge/dns01"
"github.com/xenolf/lego/platform/config/env"
)
diff --git a/vendor/github.com/xenolf/lego/providers/dns/designate/designate.go b/vendor/github.com/xenolf/lego/providers/dns/designate/designate.go
new file mode 100644
index 000000000..22f970172
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/providers/dns/designate/designate.go
@@ -0,0 +1,249 @@
+// Package designate implements a DNS provider for solving the DNS-01 challenge using the Designate DNSaaS for Openstack.
+package designate
+
+import (
+ "errors"
+ "fmt"
+ "log"
+ "os"
+ "sync"
+ "time"
+
+ "github.com/gophercloud/gophercloud"
+ "github.com/gophercloud/gophercloud/openstack"
+ "github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets"
+ "github.com/gophercloud/gophercloud/openstack/dns/v2/zones"
+ "github.com/xenolf/lego/challenge/dns01"
+ "github.com/xenolf/lego/platform/config/env"
+)
+
+// Config is used to configure the creation of the DNSProvider
+type Config struct {
+ PropagationTimeout time.Duration
+ PollingInterval time.Duration
+ TTL int
+ opts gophercloud.AuthOptions
+}
+
+// NewDefaultConfig returns a default configuration for the DNSProvider
+func NewDefaultConfig() *Config {
+ return &Config{
+ TTL: env.GetOrDefaultInt("DESIGNATE_TTL", 10),
+ PropagationTimeout: env.GetOrDefaultSecond("DESIGNATE_PROPAGATION_TIMEOUT", 10*time.Minute),
+ PollingInterval: env.GetOrDefaultSecond("DESIGNATE_POLLING_INTERVAL", 10*time.Second),
+ }
+}
+
+// DNSProvider describes a provider for Designate
+type DNSProvider struct {
+ config *Config
+ client *gophercloud.ServiceClient
+ dnsEntriesMu sync.Mutex
+}
+
+// NewDNSProvider returns a DNSProvider instance configured for Designate.
+// Credentials must be passed in the environment variables:
+// OS_AUTH_URL, OS_USERNAME, OS_PASSWORD, OS_TENANT_NAME, OS_REGION_NAME.
+func NewDNSProvider() (*DNSProvider, error) {
+ _, err := env.Get("OS_AUTH_URL", "OS_USERNAME", "OS_PASSWORD", "OS_TENANT_NAME", "OS_REGION_NAME")
+ if err != nil {
+ return nil, fmt.Errorf("designate: %v", err)
+ }
+
+ opts, err := openstack.AuthOptionsFromEnv()
+ if err != nil {
+ return nil, fmt.Errorf("designate: %v", err)
+ }
+
+ config := NewDefaultConfig()
+ config.opts = opts
+
+ return NewDNSProviderConfig(config)
+}
+
+// NewDNSProviderConfig return a DNSProvider instance configured for Designate.
+func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
+ if config == nil {
+ return nil, errors.New("designate: the configuration of the DNS provider is nil")
+ }
+
+ provider, err := openstack.AuthenticatedClient(config.opts)
+ if err != nil {
+ return nil, fmt.Errorf("designate: failed to authenticate: %v", err)
+ }
+
+ dnsClient, err := openstack.NewDNSV2(provider, gophercloud.EndpointOpts{
+ Region: os.Getenv("OS_REGION_NAME"),
+ })
+ if err != nil {
+ return nil, fmt.Errorf("designate: failed to get DNS provider: %v", err)
+ }
+
+ return &DNSProvider{client: dnsClient, 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 := dns01.GetRecord(domain, keyAuth)
+
+ authZone, err := dns01.FindZoneByFqdn(fqdn)
+ if err != nil {
+ return fmt.Errorf("designate: couldn't get zone ID in Present: %v", err)
+ }
+
+ zoneID, err := d.getZoneID(authZone)
+ if err != nil {
+ return fmt.Errorf("designate: %v", err)
+ }
+
+ // use mutex to prevent race condition between creating the record and verifying it
+ d.dnsEntriesMu.Lock()
+ defer d.dnsEntriesMu.Unlock()
+
+ existingRecord, err := d.getRecord(zoneID, fqdn)
+ if err != nil {
+ return fmt.Errorf("designate: %v", err)
+ }
+
+ if existingRecord != nil {
+ if contains(existingRecord.Records, value) {
+ log.Printf("designate: the record already exists: %s", value)
+ return nil
+ }
+
+ return d.updateRecord(existingRecord, value)
+ }
+
+ err = d.createRecord(zoneID, fqdn, value)
+ if err != nil {
+ return fmt.Errorf("designate: %v", err)
+ }
+
+ return nil
+}
+
+// CleanUp removes the TXT record matching the specified parameters
+func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
+ fqdn, _ := dns01.GetRecord(domain, keyAuth)
+
+ authZone, err := dns01.FindZoneByFqdn(fqdn)
+ if err != nil {
+ return err
+ }
+
+ zoneID, err := d.getZoneID(authZone)
+ if err != nil {
+ return fmt.Errorf("designate: couldn't get zone ID in CleanUp: %v", err)
+ }
+
+ // use mutex to prevent race condition between getting the record and deleting it
+ d.dnsEntriesMu.Lock()
+ defer d.dnsEntriesMu.Unlock()
+
+ record, err := d.getRecord(zoneID, fqdn)
+ if err != nil {
+ return fmt.Errorf("designate: couldn't get Record ID in CleanUp: %v", err)
+ }
+
+ if record == nil {
+ // Record is already deleted
+ return nil
+ }
+
+ err = recordsets.Delete(d.client, zoneID, record.ID).ExtractErr()
+ if err != nil {
+ return fmt.Errorf("designate: error for %s in CleanUp: %v", fqdn, err)
+ }
+ return nil
+}
+
+func contains(values []string, value string) bool {
+ for _, v := range values {
+ if v == value {
+ return true
+ }
+ }
+ return false
+}
+
+func (d *DNSProvider) createRecord(zoneID, fqdn, value string) error {
+ createOpts := recordsets.CreateOpts{
+ Name: fqdn,
+ Type: "TXT",
+ TTL: d.config.TTL,
+ Description: "ACME verification record",
+ Records: []string{value},
+ }
+
+ actual, err := recordsets.Create(d.client, zoneID, createOpts).Extract()
+ if err != nil {
+ return fmt.Errorf("error for %s in Present while creating record: %v", fqdn, err)
+ }
+
+ if actual.Name != fqdn || actual.TTL != d.config.TTL {
+ return fmt.Errorf("the created record doesn't match what we wanted to create")
+ }
+
+ return nil
+}
+
+func (d *DNSProvider) updateRecord(record *recordsets.RecordSet, value string) error {
+ if contains(record.Records, value) {
+ log.Printf("skip: the record already exists: %s", value)
+ return nil
+ }
+
+ values := append([]string{value}, record.Records...)
+
+ updateOpts := recordsets.UpdateOpts{
+ Description: &record.Description,
+ TTL: record.TTL,
+ Records: values,
+ }
+
+ result := recordsets.Update(d.client, record.ZoneID, record.ID, updateOpts)
+ return result.Err
+}
+
+func (d *DNSProvider) getZoneID(wanted string) (string, error) {
+ allPages, err := zones.List(d.client, nil).AllPages()
+ if err != nil {
+ return "", err
+ }
+ allZones, err := zones.ExtractZones(allPages)
+ if err != nil {
+ return "", err
+ }
+
+ for _, zone := range allZones {
+ if zone.Name == wanted {
+ return zone.ID, nil
+ }
+ }
+ return "", fmt.Errorf("zone id not found for %s", wanted)
+}
+
+func (d *DNSProvider) getRecord(zoneID string, wanted string) (*recordsets.RecordSet, error) {
+ allPages, err := recordsets.ListByZone(d.client, zoneID, nil).AllPages()
+ if err != nil {
+ return nil, err
+ }
+ allRecords, err := recordsets.ExtractRecordSets(allPages)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, record := range allRecords {
+ if record.Name == wanted {
+ return &record, nil
+ }
+ }
+
+ return nil, nil
+}
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 ea8969b7c..404241ec7 100644
--- a/vendor/github.com/xenolf/lego/providers/dns/dns_providers.go
+++ b/vendor/github.com/xenolf/lego/providers/dns/dns_providers.go
@@ -13,6 +13,7 @@ import (
"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/designate"
"github.com/xenolf/lego/providers/dns/digitalocean"
"github.com/xenolf/lego/providers/dns/dnsimple"
"github.com/xenolf/lego/providers/dns/dnsmadeeasy"
@@ -76,6 +77,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) {
return cloudxns.NewDNSProvider()
case "conoha":
return conoha.NewDNSProvider()
+ case "designate":
+ return designate.NewDNSProvider()
case "digitalocean":
return digitalocean.NewDNSProvider()
case "dnsimple":
diff --git a/vendor/github.com/xenolf/lego/providers/dns/fastdns/fastdns.go b/vendor/github.com/xenolf/lego/providers/dns/fastdns/fastdns.go
index a8d94f5d2..4917dab60 100644
--- a/vendor/github.com/xenolf/lego/providers/dns/fastdns/fastdns.go
+++ b/vendor/github.com/xenolf/lego/providers/dns/fastdns/fastdns.go
@@ -89,16 +89,10 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
_ = record.SetField("target", value)
_ = record.SetField("active", true)
- existingRecord := d.findExistingRecord(zone, recordName)
-
- if existingRecord != nil {
- if reflect.DeepEqual(existingRecord.ToMap(), record.ToMap()) {
+ for _, r := range zone.Zone.Txt {
+ if r != nil && reflect.DeepEqual(r.ToMap(), record.ToMap()) {
return nil
}
- err = zone.RemoveRecord(existingRecord)
- if err != nil {
- return fmt.Errorf("fastdns: %v", err)
- }
}
return d.createRecord(zone, record)
@@ -119,13 +113,17 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
return fmt.Errorf("fastdns: %v", err)
}
- existingRecord := d.findExistingRecord(zone, recordName)
-
- if existingRecord != nil {
- err := zone.RemoveRecord(existingRecord)
- if err != nil {
- return fmt.Errorf("fastdns: %v", err)
+ var removed bool
+ for _, r := range zone.Zone.Txt {
+ if r != nil && r.Name == recordName {
+ if zone.RemoveRecord(r) != nil {
+ return fmt.Errorf("fastdns: %v", err)
+ }
+ removed = true
}
+ }
+
+ if removed {
return zone.Save()
}
@@ -150,16 +148,6 @@ func (d *DNSProvider) findZoneAndRecordName(fqdn, domain string) (string, string
return zone, name, nil
}
-func (d *DNSProvider) findExistingRecord(zone *configdns.Zone, recordName string) *configdns.TxtRecord {
- for _, r := range zone.Zone.Txt {
- if r.Name == recordName {
- return r
- }
- }
-
- return nil
-}
-
func (d *DNSProvider) createRecord(zone *configdns.Zone, record *configdns.TxtRecord) error {
err := zone.AddRecord(record)
if err != nil {
diff --git a/vendor/github.com/xenolf/lego/providers/dns/gcloud/googlecloud.go b/vendor/github.com/xenolf/lego/providers/dns/gcloud/googlecloud.go
index d2cd04f41..877b4e97c 100644
--- a/vendor/github.com/xenolf/lego/providers/dns/gcloud/googlecloud.go
+++ b/vendor/github.com/xenolf/lego/providers/dns/gcloud/googlecloud.go
@@ -7,7 +7,6 @@ import (
"fmt"
"io/ioutil"
"net/http"
- "os"
"strconv"
"time"
@@ -53,11 +52,12 @@ type DNSProvider struct {
// NewDNSProvider returns a DNSProvider instance configured for Google Cloud DNS.
// Project name must be passed in the environment variable: GCE_PROJECT.
-// A Service Account file can be passed in the environment variable: GCE_SERVICE_ACCOUNT_FILE
+// A Service Account can be passed in the environment variable: GCE_SERVICE_ACCOUNT
+// or by specifying the keyfile location: GCE_SERVICE_ACCOUNT_FILE
func NewDNSProvider() (*DNSProvider, error) {
// Use a service account file if specified via environment variable.
- if saFile, ok := os.LookupEnv("GCE_SERVICE_ACCOUNT_FILE"); ok {
- return NewDNSProviderServiceAccount(saFile)
+ if saKey := env.GetOrFile("GCE_SERVICE_ACCOUNT"); len(saKey) > 0 {
+ return NewDNSProviderServiceAccountKey([]byte(saKey))
}
// Use default credentials.
@@ -84,16 +84,11 @@ func NewDNSProviderCredentials(project string) (*DNSProvider, error) {
return NewDNSProviderConfig(config)
}
-// NewDNSProviderServiceAccount uses the supplied service account JSON file
+// NewDNSProviderServiceAccountKey uses the supplied service account JSON
// to return a DNSProvider instance configured for Google Cloud DNS.
-func NewDNSProviderServiceAccount(saFile string) (*DNSProvider, error) {
- if saFile == "" {
- return nil, fmt.Errorf("googlecloud: Service Account file missing")
- }
-
- dat, err := ioutil.ReadFile(saFile)
- if err != nil {
- return nil, fmt.Errorf("googlecloud: unable to read Service Account file: %v", err)
+func NewDNSProviderServiceAccountKey(saKey []byte) (*DNSProvider, error) {
+ if len(saKey) == 0 {
+ return nil, fmt.Errorf("googlecloud: Service Account is missing")
}
// If GCE_PROJECT is non-empty it overrides the project in the service
@@ -104,14 +99,14 @@ func NewDNSProviderServiceAccount(saFile string) (*DNSProvider, error) {
var datJSON struct {
ProjectID string `json:"project_id"`
}
- err = json.Unmarshal(dat, &datJSON)
+ err := json.Unmarshal(saKey, &datJSON)
if err != nil || datJSON.ProjectID == "" {
return nil, fmt.Errorf("googlecloud: project ID not found in Google Cloud Service Account file")
}
project = datJSON.ProjectID
}
- conf, err := google.JWTConfigFromJSON(dat, dns.NdevClouddnsReadwriteScope)
+ conf, err := google.JWTConfigFromJSON(saKey, dns.NdevClouddnsReadwriteScope)
if err != nil {
return nil, fmt.Errorf("googlecloud: unable to acquire config: %v", err)
}
@@ -124,6 +119,21 @@ func NewDNSProviderServiceAccount(saFile string) (*DNSProvider, error) {
return NewDNSProviderConfig(config)
}
+// NewDNSProviderServiceAccount uses the supplied service account JSON file
+// to return a DNSProvider instance configured for Google Cloud DNS.
+func NewDNSProviderServiceAccount(saFile string) (*DNSProvider, error) {
+ if saFile == "" {
+ return nil, fmt.Errorf("googlecloud: Service Account file missing")
+ }
+
+ saKey, err := ioutil.ReadFile(saFile)
+ if err != nil {
+ return nil, fmt.Errorf("googlecloud: unable to read Service Account file: %v", err)
+ }
+
+ return NewDNSProviderServiceAccountKey(saKey)
+}
+
// NewDNSProviderConfig return a DNSProvider instance configured for Google Cloud DNS.
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
if config == nil {
diff --git a/vendor/github.com/xenolf/lego/providers/dns/httpreq/httpreq.go b/vendor/github.com/xenolf/lego/providers/dns/httpreq/httpreq.go
index f6a1fe0ce..af1b73a7d 100644
--- a/vendor/github.com/xenolf/lego/providers/dns/httpreq/httpreq.go
+++ b/vendor/github.com/xenolf/lego/providers/dns/httpreq/httpreq.go
@@ -10,6 +10,7 @@ import (
"net/http"
"net/url"
"os"
+ "path"
"time"
"github.com/xenolf/lego/challenge/dns01"
@@ -158,7 +159,8 @@ func (d *DNSProvider) doPost(uri string, msg interface{}) error {
return err
}
- endpoint, err := d.config.Endpoint.Parse(uri)
+ newURI := path.Join(d.config.Endpoint.EscapedPath(), uri)
+ endpoint, err := d.config.Endpoint.Parse(newURI)
if err != nil {
return err
}
diff --git a/vendor/github.com/xenolf/lego/providers/dns/inwx/inwx.go b/vendor/github.com/xenolf/lego/providers/dns/inwx/inwx.go
index dd99a5b1a..be08a0156 100644
--- a/vendor/github.com/xenolf/lego/providers/dns/inwx/inwx.go
+++ b/vendor/github.com/xenolf/lego/providers/dns/inwx/inwx.go
@@ -6,7 +6,7 @@ import (
"fmt"
"time"
- "github.com/smueller18/goinwx"
+ "github.com/nrdcg/goinwx"
"github.com/xenolf/lego/challenge/dns01"
"github.com/xenolf/lego/log"
"github.com/xenolf/lego/platform/config/env"
@@ -99,7 +99,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
Name: dns01.UnFqdn(fqdn),
Type: "TXT",
Content: value,
- Ttl: d.config.TTL,
+ TTL: d.config.TTL,
}
_, err = d.client.Nameservers.CreateRecord(request)
@@ -150,7 +150,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
var lastErr error
for _, record := range response.Records {
- err = d.client.Nameservers.DeleteRecord(record.Id)
+ err = d.client.Nameservers.DeleteRecord(record.ID)
if err != nil {
lastErr = fmt.Errorf("inwx: %v", err)
}
diff --git a/vendor/gopkg.in/mattes/go-expand-tilde.v1/tilde.go b/vendor/gopkg.in/mattes/go-expand-tilde.v1/tilde.go
deleted file mode 100644
index f3bed6090..000000000
--- a/vendor/gopkg.in/mattes/go-expand-tilde.v1/tilde.go
+++ /dev/null
@@ -1,46 +0,0 @@
-package tilde
-
-import (
- "errors"
- "os"
- "path/filepath"
- "runtime"
- "strings"
-)
-
-var (
- ErrNoHome = errors.New("no home found")
-)
-
-func Expand(path string) (string, error) {
- if !strings.HasPrefix(path, "~") {
- return path, nil
- }
-
- home, err := Home()
- if err != nil {
- return "", err
- }
-
- return home + path[1:], nil
-}
-
-func Home() (string, error) {
- home := ""
-
- switch runtime.GOOS {
- case "windows":
- home = filepath.Join(os.Getenv("HomeDrive"), os.Getenv("HomePath"))
- if home == "" {
- home = os.Getenv("UserProfile")
- }
-
- default:
- home = os.Getenv("HOME")
- }
-
- if home == "" {
- return "", ErrNoHome
- }
- return home, nil
-}