diff --git a/CHANGELOG.md b/CHANGELOG.md index ab3111b57..6c3ef84b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ # Change Log +## [v1.6.3](https://github.com/containous/traefik/tree/v1.6.3) (2018-06-05) +[All Commits](https://github.com/containous/traefik/compare/v1.6.2...v1.6.3) + +**Enhancements:** +- **[acme]** Add user agent for ACME ([#3431](https://github.com/containous/traefik/pull/3431) by [ldez](https://github.com/ldez)) +- **[acme]** Use to the stable version of Lego ([#3418](https://github.com/containous/traefik/pull/3418) by [ldez](https://github.com/ldez)) + +**Bug fixes:** +- **[acme,cluster]** Improve ACME account registration URI management ([#3398](https://github.com/containous/traefik/pull/3398) by [nmengin](https://github.com/nmengin)) +- **[acme,cluster]** Remove ACME empty certificates from KV store ([#3389](https://github.com/containous/traefik/pull/3389) by [nmengin](https://github.com/nmengin)) +- **[consulcatalog]** Reflect changes in catalog healthy nodes in healthCheck watch ([#3390](https://github.com/containous/traefik/pull/3390) by [thebinary](https://github.com/thebinary)) +- **[consulcatalog]** Detect change when service or node are in maintenance mode ([#3434](https://github.com/containous/traefik/pull/3434) by [mmatur](https://github.com/mmatur)) +- **[k8s]** Update Kubernetes provider to support IPv6 Backends ([#3432](https://github.com/containous/traefik/pull/3432) by [dtomcej](https://github.com/dtomcej)) +- **[logs,middleware]** Add URL and Host for some access logs. ([#3430](https://github.com/containous/traefik/pull/3430) by [ldez](https://github.com/ldez)) +- **[metrics]** Improve Prometheus metrics removal ([#3287](https://github.com/containous/traefik/pull/3287) by [marco-jantke](https://github.com/marco-jantke)) +- **[middleware]** Whitelist and XFF. ([#3411](https://github.com/containous/traefik/pull/3411) by [ldez](https://github.com/ldez)) +- **[middleware]** Error pages and header merge ([#3394](https://github.com/containous/traefik/pull/3394) by [ldez](https://github.com/ldez)) +- **[websocket]** Includes the headers in the HTTP response of a websocket request ([#3425](https://github.com/containous/traefik/pull/3425) by [geraldcroes](https://github.com/geraldcroes)) +- **[webui]** Webui Whitelist overflow. ([#3412](https://github.com/containous/traefik/pull/3412) by [ldez](https://github.com/ldez)) + +**Documentation:** +- **[acme]** Docs: ACME Overhaul ([#3421](https://github.com/containous/traefik/pull/3421) by [Dargmuesli](https://github.com/Dargmuesli)) +- **[acme]** Minor documentation changes ([#3405](https://github.com/containous/traefik/pull/3405) by [amincheloh](https://github.com/amincheloh)) +- **[k8s]** Helm installation using values ([#3392](https://github.com/containous/traefik/pull/3392) by [erikaulin](https://github.com/erikaulin)) +- **[k8s]** Update Kubernetes Port Documentation ([#3368](https://github.com/containous/traefik/pull/3368) by [dtomcej](https://github.com/dtomcej)) + ## [v1.6.2](https://github.com/containous/traefik/tree/v1.6.2) (2018-05-22) [All Commits](https://github.com/containous/traefik/compare/v1.6.1...v1.6.2) diff --git a/Gopkg.lock b/Gopkg.lock index 5f72d3e29..390412112 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -318,9 +318,10 @@ version = "v3.2.0" [[projects]] + branch = "master" name = "github.com/dnsimple/dnsimple-go" packages = ["dnsimple"] - revision = "f2d9b723cc9547d182e24ac2e527ae25d25fc93f" + revision = "bbe1a2c87affea187478e24d3aea3cac25f870b3" [[projects]] name = "github.com/docker/cli" @@ -757,6 +758,7 @@ version = "v1.3.7" [[projects]] + branch = "master" name = "github.com/jjcollinge/servicefabric" packages = ["."] revision = "8eebe170fa1ba25d3dfb928b3f86a7313b13b9fe" @@ -987,9 +989,10 @@ revision = "1f5c07e90700ae93ddcba0c7af7d9c7201646ccc" [[projects]] + branch = "master" name = "github.com/ovh/go-ovh" packages = ["ovh"] - revision = "4b1fea467323b74c5f462f0947f402b428ca0626" + revision = "91b7eb631d2eced3e706932a0b36ee8b5ee22e92" [[projects]] branch = "master" @@ -1212,7 +1215,7 @@ "roundrobin", "utils" ] - revision = "6956548a7fa4272adeadf828455109c53933ea86" + revision = "7a2284ad8d6f4d362a6b38f3cdcc812291dce293" [[projects]] name = "github.com/vulcand/predicate" @@ -1238,10 +1241,11 @@ revision = "0c8571ac0ce161a5feb57375a9cdf148c98c0f70" [[projects]] - branch = "containous-fork" + branch = "master" name = "github.com/xenolf/lego" packages = [ - "acmev2", + "acme", + "log", "providers/dns", "providers/dns/auroradns", "providers/dns/azure", @@ -1259,9 +1263,9 @@ "providers/dns/fastdns", "providers/dns/gandi", "providers/dns/gandiv5", + "providers/dns/gcloud", "providers/dns/glesys", "providers/dns/godaddy", - "providers/dns/googlecloud", "providers/dns/lightsail", "providers/dns/linode", "providers/dns/namecheap", @@ -1275,8 +1279,7 @@ "providers/dns/route53", "providers/dns/vultr" ] - revision = "3d653ee2ee38f1d71beb5f09b37b23344eff0ab3" - source = "github.com/containous/lego" + revision = "7fedfd1388f016c7ca7ed92a7f2024d06a7e20d8" [[projects]] branch = "master" @@ -1316,6 +1319,7 @@ revision = "22ae77b79946ea320088417e4d50825671d82d57" [[projects]] + branch = "master" name = "golang.org/x/oauth2" packages = [ ".", @@ -1324,7 +1328,7 @@ "jws", "jwt" ] - revision = "7fdf09982454086d5570c7db3e11f360194830ca" + revision = "ec22f46f877b4505e0117eeaab541714644fdd28" [[projects]] branch = "master" @@ -1363,6 +1367,7 @@ revision = "8be79e1e0910c292df4e79c241bb7e8f7e725959" [[projects]] + branch = "master" name = "google.golang.org/api" packages = [ "dns/v1", @@ -1370,7 +1375,7 @@ "googleapi", "googleapi/internal/uritemplates" ] - revision = "1575df15c1bb8b18ad4d9bc5ca495cc85b0764fe" + revision = "de943baf05a022a8f921b544b7827bacaba1aed5" [[projects]] name = "google.golang.org/appengine" @@ -1451,6 +1456,7 @@ revision = "cb884138e64c9a8bf5c7d6106d74b0fca082df0c" [[projects]] + branch = "v2" name = "gopkg.in/ns1/ns1-go.v2" packages = [ "rest", @@ -1460,7 +1466,7 @@ "rest/model/filter", "rest/model/monitor" ] - revision = "c563826f4cbef9c11bebeb9f20a3f7afe9c1e2f4" + revision = "a5bcac82d3f637d3928d30476610891935b2d691" [[projects]] name = "gopkg.in/square/go-jose.v2" @@ -1681,6 +1687,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "4d18e76b6d54a2698d3f1acd9f43110c609e3f61e72d480b9acd2327720dcc5c" + inputs-digest = "f9bdfe14aca106f6bfc75da6f7282791c12c503b65581797bbb484566858a2f3" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 5c77d51ed..8a496605a 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -181,9 +181,9 @@ name = "github.com/vulcand/oxy" [[constraint]] - branch = "containous-fork" + branch = "master" name = "github.com/xenolf/lego" - source = "github.com/containous/lego" +# version = "1.0.0" [[constraint]] name = "google.golang.org/grpc" diff --git a/acme/account.go b/acme/account.go index 39efb4733..571caf9dd 100644 --- a/acme/account.go +++ b/acme/account.go @@ -8,6 +8,7 @@ import ( "crypto/x509" "fmt" "reflect" + "regexp" "sort" "strings" "sync" @@ -16,7 +17,7 @@ import ( "github.com/containous/traefik/log" acmeprovider "github.com/containous/traefik/provider/acme" "github.com/containous/traefik/types" - acme "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) // Account is used to store lets encrypt registration info @@ -44,6 +45,11 @@ func (a *Account) Init() error { return err } + err = a.RemoveAccountV1Values() + if err != nil { + log.Errorf("Unable to remove ACME Account V1 values during account initialization: %v", err) + } + for _, cert := range a.ChallengeCerts { if cert.certificate == nil { certificate, err := tls.X509KeyPair(cert.Certificate, cert.PrivateKey) @@ -108,6 +114,29 @@ func (a *Account) GetPrivateKey() crypto.PrivateKey { return nil } +// RemoveAccountV1Values removes ACME account V1 values +func (a *Account) RemoveAccountV1Values() error { + // Check if ACME Account is in ACME V1 format + if a.Registration != nil { + isOldRegistration, err := regexp.MatchString(acmeprovider.RegistrationURLPathV1Regexp, a.Registration.URI) + if err != nil { + return err + } + + if isOldRegistration { + a.reset() + } + } + return nil +} + +func (a *Account) reset() { + log.Debug("Reset ACME account object.") + a.Email = "" + a.Registration = nil + a.PrivateKey = nil +} + // Certificate is used to store certificate info type Certificate struct { Domain string @@ -157,11 +186,23 @@ func (dc *DomainsCertificates) removeDuplicates() { } } +func (dc *DomainsCertificates) removeEmpty() { + certs := []*DomainsCertificate{} + for _, cert := range dc.Certs { + if cert.Certificate != nil && len(cert.Certificate.Certificate) > 0 && len(cert.Certificate.PrivateKey) > 0 { + certs = append(certs, cert) + } + } + dc.Certs = certs +} + // Init DomainsCertificates func (dc *DomainsCertificates) Init() error { dc.lock.Lock() defer dc.lock.Unlock() + dc.removeEmpty() + for _, domainsCertificate := range dc.Certs { tlsCert, err := tls.X509KeyPair(domainsCertificate.Certificate.Certificate, domainsCertificate.Certificate.PrivateKey) if err != nil { diff --git a/acme/acme.go b/acme/acme.go index 1f42f21b6..cee66924d 100644 --- a/acme/acme.go +++ b/acme/acme.go @@ -25,8 +25,10 @@ import ( "github.com/containous/traefik/safe" "github.com/containous/traefik/tls/generate" "github.com/containous/traefik/types" + "github.com/containous/traefik/version" "github.com/eapache/channels" - acme "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" + legolog "github.com/xenolf/lego/log" "github.com/xenolf/lego/providers/dns" ) @@ -42,7 +44,7 @@ type ACME struct { Domains []types.Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"` Storage string `description:"File or key used for certificates storage."` StorageFile string // Deprecated - OnDemand bool `description:"(Deprecated) Enable on demand certificate generation. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."` //deprecated + OnDemand bool `description:"(Deprecated) Enable on demand certificate generation. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."` // Deprecated OnHostRule bool `description:"Enable certificate generation on frontends Host rules."` CAServer string `description:"CA server to use."` EntryPoint string `description:"Entrypoint to proxy acme challenge to."` @@ -64,10 +66,11 @@ type ACME struct { } func (a *ACME) init() error { + acme.UserAgent = fmt.Sprintf("containous-traefik/%s", version.Version) if a.ACMELogging { - acme.Logger = fmtlog.New(os.Stderr, "legolog: ", fmtlog.LstdFlags) + legolog.Logger = fmtlog.New(os.Stderr, "legolog: ", fmtlog.LstdFlags) } else { - acme.Logger = fmtlog.New(ioutil.Discard, "", 0) + legolog.Logger = fmtlog.New(ioutil.Discard, "", 0) } // no certificates in TLS config, so we add a default one cert, err := generate.DefaultCertificate() @@ -180,6 +183,10 @@ func (a *ACME) leadershipListener(elected bool) error { account := object.(*Account) account.Init() + // Reset Account values if caServer changed, thus registration URI can be updated + if account != nil && account.Registration != nil && !strings.HasPrefix(account.Registration.URI, a.CAServer) { + account.reset() + } var needRegister bool if account == nil || len(account.Email) == 0 { diff --git a/acme/acme_test.go b/acme/acme_test.go index 8c7c78280..9e3d2ace4 100644 --- a/acme/acme_test.go +++ b/acme/acme_test.go @@ -6,6 +6,7 @@ import ( "net/http" "net/http/httptest" "reflect" + "sort" "sync" "testing" "time" @@ -14,7 +15,7 @@ import ( "github.com/containous/traefik/tls/generate" "github.com/containous/traefik/types" "github.com/stretchr/testify/assert" - acme "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) func TestDomainsSet(t *testing.T) { @@ -550,3 +551,268 @@ func TestAcme_getCertificateForDomain(t *testing.T) { }) } } + +func TestRemoveEmptyCertificates(t *testing.T) { + now := time.Now() + fooCert, fooKey, _ := generate.KeyPair("foo.com", now) + acmeCert, acmeKey, _ := generate.KeyPair("acme.wtf", now.Add(24*time.Hour)) + barCert, barKey, _ := generate.KeyPair("bar.com", now) + testCases := []struct { + desc string + dc *DomainsCertificates + expectedDc *DomainsCertificates + }{ + { + desc: "No empty certificate", + dc: &DomainsCertificates{ + Certs: []*DomainsCertificate{ + { + Certificate: &Certificate{ + Certificate: fooCert, + PrivateKey: fooKey, + }, + Domains: types.Domain{ + Main: "foo.com", + }, + }, + { + Certificate: &Certificate{ + Certificate: acmeCert, + PrivateKey: acmeKey, + }, + Domains: types.Domain{ + Main: "acme.wtf", + }, + }, + { + Certificate: &Certificate{ + Certificate: barCert, + PrivateKey: barKey, + }, + Domains: types.Domain{ + Main: "bar.com", + }, + }, + }, + }, + expectedDc: &DomainsCertificates{ + Certs: []*DomainsCertificate{ + { + Certificate: &Certificate{ + Certificate: fooCert, + PrivateKey: fooKey, + }, + Domains: types.Domain{ + Main: "foo.com", + }, + }, + { + Certificate: &Certificate{ + Certificate: acmeCert, + PrivateKey: acmeKey, + }, + Domains: types.Domain{ + Main: "acme.wtf", + }, + }, + { + Certificate: &Certificate{ + Certificate: barCert, + PrivateKey: barKey, + }, + Domains: types.Domain{ + Main: "bar.com", + }, + }, + }, + }, + }, + { + desc: "First certificate is nil", + dc: &DomainsCertificates{ + Certs: []*DomainsCertificate{ + { + Domains: types.Domain{ + Main: "foo.com", + }, + }, + { + Certificate: &Certificate{ + Certificate: acmeCert, + PrivateKey: acmeKey, + }, + Domains: types.Domain{ + Main: "acme.wtf", + }, + }, + { + Certificate: &Certificate{ + Certificate: barCert, + PrivateKey: barKey, + }, + Domains: types.Domain{ + Main: "bar.com", + }, + }, + }, + }, + expectedDc: &DomainsCertificates{ + Certs: []*DomainsCertificate{ + { + Certificate: &Certificate{ + Certificate: acmeCert, + PrivateKey: acmeKey, + }, + Domains: types.Domain{ + Main: "acme.wtf", + }, + }, + { + Certificate: &Certificate{ + Certificate: nil, + PrivateKey: barKey, + }, + Domains: types.Domain{ + Main: "bar.com", + }, + }, + }, + }, + }, + { + desc: "Last certificate is empty", + dc: &DomainsCertificates{ + Certs: []*DomainsCertificate{ + { + Certificate: &Certificate{ + Certificate: fooCert, + PrivateKey: fooKey, + }, + Domains: types.Domain{ + Main: "foo.com", + }, + }, + { + Certificate: &Certificate{ + Certificate: acmeCert, + PrivateKey: acmeKey, + }, + Domains: types.Domain{ + Main: "acme.wtf", + }, + }, + { + Certificate: &Certificate{}, + Domains: types.Domain{ + Main: "bar.com", + }, + }, + }, + }, + expectedDc: &DomainsCertificates{ + Certs: []*DomainsCertificate{ + { + Certificate: &Certificate{ + Certificate: fooCert, + PrivateKey: fooKey, + }, + Domains: types.Domain{ + Main: "foo.com", + }, + }, + { + Certificate: &Certificate{ + Certificate: acmeCert, + PrivateKey: acmeKey, + }, + Domains: types.Domain{ + Main: "acme.wtf", + }, + }, + }, + }, + }, + { + desc: "First and last certificates are nil or empty", + dc: &DomainsCertificates{ + Certs: []*DomainsCertificate{ + { + Domains: types.Domain{ + Main: "foo.com", + }, + }, + { + Certificate: &Certificate{ + Certificate: acmeCert, + PrivateKey: acmeKey, + }, + Domains: types.Domain{ + Main: "acme.wtf", + }, + }, + { + Certificate: &Certificate{}, + Domains: types.Domain{ + Main: "bar.com", + }, + }, + }, + }, + expectedDc: &DomainsCertificates{ + Certs: []*DomainsCertificate{ + { + Certificate: &Certificate{ + Certificate: acmeCert, + PrivateKey: acmeKey, + }, + Domains: types.Domain{ + Main: "acme.wtf", + }, + }, + }, + }, + }, + { + desc: "All certificates are nil or empty", + dc: &DomainsCertificates{ + Certs: []*DomainsCertificate{ + { + Domains: types.Domain{ + Main: "foo.com", + }, + }, + { + Domains: types.Domain{ + Main: "foo24.com", + }, + }, + { + Certificate: &Certificate{}, + Domains: types.Domain{ + Main: "bar.com", + }, + }, + }, + }, + expectedDc: &DomainsCertificates{ + Certs: []*DomainsCertificate{}, + }, + }, + } + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + a := &Account{DomainsCertificate: *test.dc} + a.Init() + + assert.Equal(t, len(test.expectedDc.Certs), len(a.DomainsCertificate.Certs)) + sort.Sort(&a.DomainsCertificate) + sort.Sort(test.expectedDc) + for key, value := range test.expectedDc.Certs { + assert.Equal(t, value.Domains.Main, a.DomainsCertificate.Certs[key].Domains.Main) + } + }) + } +} diff --git a/acme/challenge_http_provider.go b/acme/challenge_http_provider.go index 6d29a04d7..901fa5594 100644 --- a/acme/challenge_http_provider.go +++ b/acme/challenge_http_provider.go @@ -9,7 +9,7 @@ import ( "github.com/containous/traefik/cluster" "github.com/containous/traefik/log" "github.com/containous/traefik/safe" - acme "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) var _ acme.ChallengeProviderTimeout = (*challengeHTTPProvider)(nil) diff --git a/acme/localStore.go b/acme/localStore.go index 63397338d..b6883b38a 100644 --- a/acme/localStore.go +++ b/acme/localStore.go @@ -4,7 +4,6 @@ import ( "encoding/json" "io/ioutil" "os" - "regexp" "github.com/containous/traefik/log" "github.com/containous/traefik/provider/acme" @@ -51,25 +50,6 @@ func (s *LocalStore) Get() (*Account, error) { return account, nil } -// RemoveAccountV1Values removes ACME account V1 values -func RemoveAccountV1Values(account *Account) error { - // Check if ACME Account is in ACME V1 format - if account != nil && account.Registration != nil { - isOldRegistration, err := regexp.MatchString(acme.RegistrationURLPathV1Regexp, account.Registration.URI) - if err != nil { - return err - } - - if isOldRegistration { - account.Email = "" - account.Registration = nil - account.PrivateKey = nil - account.KeyType = "RSA4096" - } - } - return nil -} - // ConvertToNewFormat converts old acme.json format to the new one and store the result into the file (used for the backward compatibility) func ConvertToNewFormat(fileName string) { localStore := acme.NewLocalStore(fileName) @@ -100,13 +80,13 @@ func ConvertToNewFormat(fileName string) { if account != nil && len(account.Email) > 0 { err = backupACMEFile(fileName, account) if err != nil { - log.Errorf("Unable to create a backup for the V1 formatted ACME file: %s", err.Error()) + log.Errorf("Unable to create a backup for the V1 formatted ACME file: %v", err) return } - err = RemoveAccountV1Values(account) + err = account.RemoveAccountV1Values() if err != nil { - log.Errorf("Unable to remove ACME Account V1 values: %s", err.Error()) + log.Errorf("Unable to remove ACME Account V1 values during format conversion: %v", err) return } diff --git a/cmd/storeconfig/storeconfig.go b/cmd/storeconfig/storeconfig.go index 0dda148f7..412aff25c 100644 --- a/cmd/storeconfig/storeconfig.go +++ b/cmd/storeconfig/storeconfig.go @@ -145,7 +145,7 @@ func migrateACMEData(fileName string) (*acme.Account, error) { return nil, err } - err = acme.RemoveAccountV1Values(account) + err = account.RemoveAccountV1Values() if err != nil { return nil, err } diff --git a/docs/configuration/acme.md b/docs/configuration/acme.md index 6853ce419..cb0b4c6d3 100644 --- a/docs/configuration/acme.md +++ b/docs/configuration/acme.md @@ -1,6 +1,6 @@ -# ACME (Let's Encrypt) configuration +# ACME (Let's Encrypt) Configuration -See also [Let's Encrypt examples](/user-guide/examples/#lets-encrypt-support) and [Docker & Let's Encrypt user guide](/user-guide/docker-and-lets-encrypt). +See [Let's Encrypt examples](/user-guide/examples/#lets-encrypt-support) and [Docker & Let's Encrypt user guide](/user-guide/docker-and-lets-encrypt) as well. ## Configuration @@ -70,14 +70,14 @@ entryPoint = "https" # # overrideCertificates = true -# Enable on demand certificate generation. +# Deprecated. Enable on demand certificate generation. # -# Optional (Deprecated) +# Optional # Default: false # # onDemand = true -# Enable certificate generation on frontends Host rules. +# Enable certificate generation on frontends host rules. # # Optional # Default: false @@ -85,8 +85,8 @@ entryPoint = "https" # onHostRule = true # CA server to use. -# - Uncomment the line to run on the staging let's encrypt server. -# - Leave comment to go to prod. +# Uncomment the line to use Let's Encrypt's staging server, +# leave commented to go to prod. # # Optional # Default: "https://acme-v02.api.letsencrypt.org/directory" @@ -110,15 +110,13 @@ entryPoint = "https" # sans = ["test1.local1.com", "test2.local1.com"] # [[acme.domains]] # main = "local2.com" -# sans = ["test1.local2.com", "test2.local2.com"] # [[acme.domains]] -# main = "local3.com" -# [[acme.domains]] -# main = "local4.com" +# main = "*.local3.com" +# sans = ["local3.com", "test1.test1.local3.com"] -# Use a HTTP-01 acme challenge. +# Use a HTTP-01 ACME challenge. # -# Optional but recommend +# Optional (but recommended) # [acme.httpChallenge] @@ -128,21 +126,21 @@ entryPoint = "https" # entryPoint = "http" -# Use a DNS-01/DNS-01 acme challenge rather than HTTP-01 challenge. -# Note : Mandatory for wildcard certificates generation. +# Use a DNS-01 ACME challenge rather than HTTP-01 challenge. +# Note: mandatory for wildcard certificate generation. # # Optional # # [acme.dnsChallenge] - # Provider used. + # DNS provider used. # # Required # # provider = "digitalocean" # By default, the provider will verify the TXT DNS challenge record before letting ACME verify. - # If delayBeforeCheck is greater than zero, avoid this & instead just wait so many seconds. + # If delayBeforeCheck is greater than zero, this check is delayed for the configured duration in seconds. # Useful if internal networks block external DNS queries. # # Optional @@ -151,98 +149,134 @@ entryPoint = "https" # delayBeforeCheck = 0 ``` -!!! note - If `HTTP-01` challenge is used, `acme.httpChallenge.entryPoint` has to be defined and reachable by Let's Encrypt through the port 80. - These are Let's Encrypt limitations as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72). +### `caServer` -!!! note - Wildcard certificates can be generated only if `acme.dnsChallenge` -option is enable. +The CA server to use. -### Let's Encrypt downtime - -Let's Encrypt functionality will be limited until Træfik is restarted. - -If Let's Encrypt is not reachable, these certificates will be used : - - - ACME certificates already generated before downtime - - Expired ACME certificates - - Provided certificates - -!!! note - Default Træfik certificate will be used instead of ACME certificates for new (sub)domains (which need Let's Encrypt challenge). - -### `storage` +This example shows the usage of Let's Encrypt's staging server: ```toml [acme] # ... -storage = "acme.json" +caServer = "https://acme-staging-v02.api.letsencrypt.org/directory" # ... ``` -The `storage` option sets where are stored your ACME certificates. +### `dnsChallenge` -There are two kind of `storage` : +Use the `DNS-01` challenge to generate and renew ACME certificates by provisioning a DNS record. -- a JSON file, -- a KV store entry. +```toml +[acme] +# ... +[acme.dnsChallenge] + provider = "digitalocean" + delayBeforeCheck = 0 +# ... +``` -!!! danger "DEPRECATED" - `storage` replaces `storageFile` which is deprecated. +#### `delayBeforeCheck` + +By default, the `provider` will verify the TXT DNS challenge record before letting ACME verify. +If `delayBeforeCheck` is greater than zero, this check is delayed for the configured duration in seconds. + +Useful if internal networks block external DNS queries. !!! note - During Træfik configuration migration from a configuration file to a KV store (thanks to `storeconfig` subcommand as described [here](/user-guide/kv-config/#store-configuration-in-key-value-store)), if ACME certificates have to be migrated too, use both `storageFile` and `storage`. + A `provider` is mandatory. - - `storageFile` will contain the path to the `acme.json` file to migrate. - - `storage` will contain the key where the certificates will be stored. +#### `provider` -#### Store data in a file +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. -ACME certificates can be stored in a JSON file which with the `600` right mode. +| Provider Name | Provider Code | Environment Variables | Wildcard & Root Domain Support | +|--------------------------------------------------------|----------------|-----------------------------------------------------------------------------------------------------------------------------|--------------------------------| +| [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` | 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` | `CLOUDFLARE_EMAIL`, `CLOUDFLARE_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 | +| [DigitalOcean](https://www.digitalocean.com) | `digitalocean` | `DO_AUTH_TOKEN` | YES | +| [DNSimple](https://dnsimple.com) | `dnsimple` | `DNSIMPLE_OAUTH_TOKEN`, `DNSIMPLE_BASE_URL` | Not tested yet | +| [DNS Made Easy](https://dnsmadeeasy.com) | `dnsmadeeasy` | `DNSMADEEASY_API_KEY`, `DNSMADEEASY_API_SECRET`, `DNSMADEEASY_SANDBOX` | Not tested yet | +| [DNSPod](http://www.dnspod.net/) | `dnspod` | `DNSPOD_API_KEY` | Not tested yet | +| [Duck DNS](https://www.duckdns.org/) | `duckdns` | `DUCKDNS_TOKEN` | Not tested yet | +| [Dyn](https://dyn.com) | `dyn` | `DYN_CUSTOMER_NAME`, `DYN_USER_NAME`, `DYN_PASSWORD` | Not tested yet | +| External Program | `exec` | `EXEC_PATH` | Not tested yet | +| [Exoscale](https://www.exoscale.ch) | `exoscale` | `EXOSCALE_API_KEY`, `EXOSCALE_API_SECRET`, `EXOSCALE_ENDPOINT` | Not tested yet | +| [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` | Not tested yet | +| [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`, `GCE_SERVICE_ACCOUNT_FILE` | 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 | +| manual | - | none, but you need to run Træfik interactively, turn on `acmeLogging` to see instructions and press Enter. | YES | +| [Namecheap](https://www.namecheap.com) | `namecheap` | `NAMECHEAP_API_USER`, `NAMECHEAP_API_KEY` | Not tested yet | +| [name.com](https://www.name.com/) | `namedotcom` | `NAMECOM_USERNAME`, `NAMECOM_API_TOKEN`, `NAMECOM_SERVER` | Not tested yet | +| [Ns1](https://ns1.com/) | `ns1` | `NS1_API_KEY` | Not tested yet | +| [Open Telekom Cloud](https://cloud.telekom.de/en/) | `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 | +| [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` | Not tested yet | -There are two ways to store ACME certificates in a file from Docker: +### `domains` + +You can provide SANs (alternative domains) to each main domain. +All domains must have A/AAAA records pointing to Træfik. +Each domain & SAN will lead to a certificate request. -- create a file on your host and mount it as a volume: ```toml -storage = "acme.json" -``` -```bash -docker run -v "/my/host/acme.json:acme.json" traefik -``` -- mount the folder containing the file as a volume -```toml -storage = "/etc/traefik/acme/acme.json" -``` -```bash -docker run -v "/my/host/acme:/etc/traefik/acme" traefik +[acme] +# ... +[[acme.domains]] + main = "local1.com" + sans = ["test1.local1.com", "test2.local1.com"] +[[acme.domains]] + main = "local2.com" +[[acme.domains]] + main = "*.local3.com" + sans = ["local3.com", "test1.test1.local3.com"] +# ... ``` !!! warning - This file cannot be shared per many instances of Træfik at the same time. - If you have to use Træfik cluster mode, please use [a KV Store entry](/configuration/acme/#storage-kv-entry). - -#### Store data in a KV store entry - -ACME certificates can be stored in a KV Store entry. - -```toml -storage = "traefik/acme/account" -``` - -**This kind of storage is mandatory in cluster mode.** - -Because KV stores (like Consul) have limited entries size, the certificates list is compressed before to be set in a KV store entry. + Take note that Let's Encrypt applies [rate limiting](https://letsencrypt.org/docs/rate-limits). !!! note - It's possible to store up to approximately 100 ACME certificates in Consul. + Wildcard certificates can only be verified through a `DNS-01` challenge. + +#### Wildcard Domains + +[ACME V2](https://community.letsencrypt.org/t/acme-v2-and-wildcard-certificate-support-is-live/55579) allows wildcard certificate support. +As described in [Let's Encrypt's post](https://community.letsencrypt.org/t/staging-endpoint-for-acme-v2/49605) wildcard certificates can only be generated through a [`DNS-01` challenge](/configuration/acme/#dnschallenge). + +```toml +[acme] +# ... +[[acme.domains]] + main = "*.local1.com" + sans = ["local1.com"] +# ... +``` + +It is not possible to request a double wildcard certificate for a domain (for example `*.*.local.com`). +Due to ACME limitation it is not possible to define wildcards in SANs (alternative domains). Thus, the wildcard domain has to be defined as a main domain. +Most likely the root domain should receive a certificate too, so it needs to be specified as SAN and 2 `DNS-01` challenges are executed. +In this case the generated DNS TXT record for both domains is the same. +Eventhough this behaviour is [DNS RFC](https://community.letsencrypt.org/t/wildcard-issuance-two-txt-records-for-the-same-name/54528/2) compliant, it can lead to problems as all DNS providers keep DNS records cached for a certain time (TTL) and this TTL can be superior to the challenge timeout making the `DNS-01` challenge fail. +The Træfik ACME client library [LEGO](https://github.com/xenolf/lego) supports some but not all DNS providers to work around this issue. +The [`provider` table](/configuration/acme/#provider) indicates if they allow generating certificates for a wildcard domain and its root domain. ### `httpChallenge` -Use `HTTP-01` challenge to generate/renew ACME certificates. +Use the `HTTP-01` challenge to generate and renew ACME certificates by provisioning a HTTP resource under a well-known URI. -The redirection is fully compatible with the HTTP-01 challenge. -You can use redirection with HTTP-01 challenge without problem. +Redirection is fully compatible with the `HTTP-01` challenge. ```toml [acme] @@ -252,6 +286,10 @@ entryPoint = "https" entryPoint = "http" ``` +!!! note + If the `HTTP-01` challenge is used, `acme.httpChallenge.entryPoint` has to be defined and reachable by Let's Encrypt through port 80. + This is a Let's Encrypt limitation as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72). + #### `entryPoint` Specify the entryPoint to use during the challenges. @@ -275,73 +313,7 @@ defaultEntryPoints = ["http", "https"] ``` !!! note - `acme.httpChallenge.entryPoint` has to be reachable by Let's Encrypt through the port 80. - It's a Let's Encrypt limitation as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72). - -### `dnsChallenge` - -Use `DNS-01/DNS-01` challenge to generate/renew ACME certificates. - -```toml -[acme] -# ... -[acme.dnsChallenge] - provider = "digitalocean" - delayBeforeCheck = 0 -# ... -``` - -!!! note - ACME wildcard certificates can only be generated thanks to a `DNS-01` challenge. - -#### `provider` - -Select the provider that matches the DNS domain that will host the challenge TXT record, and provide environment variables to enable setting it: - -| Provider Name | Provider code | Configuration | -|--------------------------------------------------------|----------------|---------------------------------------------------------------------------------------------------------------------------| -| [Auroradns](https://www.pcextreme.com/aurora/dns) | `auroradns` | `AURORA_USER_ID`, `AURORA_KEY`, `AURORA_ENDPOINT` | -| [Azure](https://azure.microsoft.com/services/dns/) | `azure` | `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_SUBSCRIPTION_ID`, `AZURE_TENANT_ID`, `AZURE_RESOURCE_GROUP` | -| [Blue Cat](https://www.bluecatnetworks.com/) | `bluecat` | `BLUECAT_SERVER_URL`, `BLUECAT_USER_NAME`, `BLUECAT_PASSWORD`, `BLUECAT_CONFIG_NAME`, `BLUECAT_DNS_VIEW` | -| [Cloudflare](https://www.cloudflare.com) | `cloudflare` | `CLOUDFLARE_EMAIL`, `CLOUDFLARE_API_KEY` - The Cloudflare `Global API Key` needs to be used and not the `Origin CA Key` | -| [CloudXNS](https://www.cloudxns.net) | `cloudxns` | `CLOUDXNS_API_KEY`, `CLOUDXNS_SECRET_KEY` | -| [DigitalOcean](https://www.digitalocean.com) | `digitalocean` | `DO_AUTH_TOKEN` | -| [DNSimple](https://dnsimple.com) | `dnsimple` | `DNSIMPLE_OAUTH_TOKEN`, `DNSIMPLE_BASE_URL` | -| [DNS Made Easy](https://dnsmadeeasy.com) | `dnsmadeeasy` | `DNSMADEEASY_API_KEY`, `DNSMADEEASY_API_SECRET`, `DNSMADEEASY_SANDBOX` | -| [DNSPod](http://www.dnspod.net/) | `dnspod` | `DNSPOD_API_KEY` | -| [Duck DNS](https://www.duckdns.org/) | `duckdns` | `DUCKDNS_TOKEN` | -| [Dyn](https://dyn.com) | `dyn` | `DYN_CUSTOMER_NAME`, `DYN_USER_NAME`, `DYN_PASSWORD` | -| External Program | `exec` | `EXEC_PATH` | -| [Exoscale](https://www.exoscale.ch) | `exoscale` | `EXOSCALE_API_KEY`, `EXOSCALE_API_SECRET`, `EXOSCALE_ENDPOINT` | -| [Fast DNS](https://www.akamai.com/) | `fastdns` | `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`, `AKAMAI_ACCESS_TOKEN` | -| [Gandi](https://www.gandi.net) | `gandi` | `GANDI_API_KEY` | -| [Gandi V5](http://doc.livedns.gandi.net) | `gandiv5` | `GANDIV5_API_KEY` | -| [Glesys](https://glesys.com/) | `glesys` | `GLESYS_API_USER`, `GLESYS_API_KEY`, `GLESYS_DOMAIN` | -| [GoDaddy](https://godaddy.com/domains) | `godaddy` | `GODADDY_API_KEY`, `GODADDY_API_SECRET` | -| [Google Cloud DNS](https://cloud.google.com/dns/docs/) | `gcloud` | `GCE_PROJECT`, `GCE_SERVICE_ACCOUNT_FILE` | -| [Lightsail](https://aws.amazon.com/lightsail/) | `lightsail` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `DNS_ZONE` | -| [Linode](https://www.linode.com) | `linode` | `LINODE_API_KEY` | -| manual | - | none, but run Træfik interactively & turn on `acmeLogging` to see instructions & press Enter. | -| [Namecheap](https://www.namecheap.com) | `namecheap` | `NAMECHEAP_API_USER`, `NAMECHEAP_API_KEY` | -| [name.com](https://www.name.com/) | `namedotcom` | `NAMECOM_USERNAME`, `NAMECOM_API_TOKEN`, `NAMECOM_SERVER` | -| [Ns1](https://ns1.com/) | `ns1` | `NS1_API_KEY` | -| [Open Telekom Cloud](https://cloud.telekom.de/en/) | `otc` | `OTC_DOMAIN_NAME`, `OTC_USER_NAME`, `OTC_PASSWORD`, `OTC_PROJECT_NAME`, `OTC_IDENTITY_ENDPOINT` | -| [OVH](https://www.ovh.com) | `ovh` | `OVH_ENDPOINT`, `OVH_APPLICATION_KEY`, `OVH_APPLICATION_SECRET`, `OVH_CONSUMER_KEY` | -| [PowerDNS](https://www.powerdns.com) | `pdns` | `PDNS_API_KEY`, `PDNS_API_URL` | -| [Rackspace](https://www.rackspace.com/cloud/dns) | `rackspace` | `RACKSPACE_USER`, `RACKSPACE_API_KEY` | -| [RFC2136](https://tools.ietf.org/html/rfc2136) | `rfc2136` | `RFC2136_TSIG_KEY`, `RFC2136_TSIG_SECRET`, `RFC2136_TSIG_ALGORITHM`, `RFC2136_NAMESERVER` | -| [Route 53](https://aws.amazon.com/route53/) | `route53` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, `AWS_HOSTED_ZONE_ID` or configured user/instance IAM profile. | -| [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` | - -#### `delayBeforeCheck` - -By default, the `provider` will verify the TXT DNS challenge record before letting ACME verify. -If `delayBeforeCheck` is greater than zero, avoid this & instead just wait so many seconds. - -Useful if internal networks block external DNS queries. - -!!! note - This field has no sense if a `provider` is not defined. + `acme.httpChallenge.entryPoint` has to be reachable through port 80. It's a Let's Encrypt limitation as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72). ### `onDemand` (Deprecated) @@ -355,15 +327,15 @@ onDemand = true # ... ``` -Enable on demand certificate. +Enable on demand certificate generation. -This will request a certificate from Let's Encrypt during the first TLS handshake for a host name that does not yet have a certificate. +This will request certificates from Let's Encrypt during the first TLS handshake for host names that do not yet have certificates. !!! warning - TLS handshakes will be slow when requesting a host name certificate for the first time, this can lead to DoS attacks. + TLS handshakes are slow when requesting a host name certificate for the first time. This can lead to DoS attacks! !!! warning - Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits). + Take note that Let's Encrypt applies [rate limiting](https://letsencrypt.org/docs/rate-limits). ### `onHostRule` @@ -374,199 +346,94 @@ onHostRule = true # ... ``` -Enable certificate generation on frontends `Host` rules (for frontends wired on the `acme.entryPoint`). +Enable certificate generation on frontend `Host` rules (for frontends wired to the `acme.entryPoint`). This will request a certificate from Let's Encrypt for each frontend with a Host rule. -For example, a rule `Host:test1.traefik.io,test2.traefik.io` will request a certificate with main domain `test1.traefik.io` and SAN `test2.traefik.io`. +For example, the rule `Host:test1.traefik.io,test2.traefik.io` will request a certificate with main domain `test1.traefik.io` and SAN `test2.traefik.io`. !!! warning `onHostRule` option can not be used to generate wildcard certificates. - Refer to [the wildcard generation section](/configuration/acme/#wildcard-domain) for more information. + Refer to [wildcard generation](/configuration/acme/#wildcard-domain) for further information. -### `caServer` +### `storage` + +The `storage` option sets the location where your ACME certificates are saved to. ```toml [acme] # ... -caServer = "https://acme-staging-v02.api.letsencrypt.org/directory" +storage = "acme.json" # ... ``` -CA server to use. +The value can refer to two kinds of storage: -- Uncomment the line to run on the staging Let's Encrypt server. -- Leave comment to go to prod. +- a JSON file +- a KV store entry -### `domains` +!!! danger "DEPRECATED" + `storage` replaces `storageFile` which is deprecated. + +!!! note + During migration to a KV store use both `storageFile` and `storage` to migrate ACME certificates too. See [`storeconfig` subcommand](/user-guide/kv-config/#store-configuration-in-key-value-store) for further information. + +#### As a File + +ACME certificates can be stored in a JSON file that needs to have file mode `600`. + +In Docker you can either mount the JSON file or the folder containing it: + +```bash +docker run -v "/my/host/acme.json:acme.json" traefik +``` +```bash +docker run -v "/my/host/acme:/etc/traefik/acme" traefik +``` + +!!! warning + This file cannot be shared across multiple instances of Træfik at the same time. Please use a [KV Store entry](/configuration/acme/#as-a-key-value-store-entry) instead. + +#### As a Key Value Store Entry + +ACME certificates can be stored in a KV Store entry. This kind of storage is **mandatory in cluster mode**. ```toml -[acme] -# ... -[[acme.domains]] - main = "local1.com" - sans = ["test1.local1.com", "test2.local1.com"] -[[acme.domains]] - main = "local2.com" - sans = ["test1.local2.com", "test2.local2.com"] -[[acme.domains]] - main = "local3.com" -[[acme.domains]] - main = "*.local4.com" - sans = ["local4.com", "test1.test1.local4.com"] -# ... +storage = "traefik/acme/account" ``` -#### Wildcard domains +Because KV stores (like Consul) have limited entry size the certificates list is compressed before it is saved as KV store entry. -Wildcard domain has to be defined as a main domain. -All domains must have A/AAAA records pointing to Træfik. +!!! note + It is possible to store up to approximately 100 ACME certificates in Consul. -Due to ACME limitation, it's not possible to define a wildcard as a SAN (alternative domains). -It's neither possible to define a wildcard on a wildcard domain (for example `*.*.local.com`). +#### ACME v2 Migration -!!! warning - Note that Let's Encrypt has [rate limiting](https://letsencrypt.org/docs/rate-limits). +During migration from ACME v1 to ACME v2, using a storage file, a backup of the original file is created in the same place as the latter (with a `.bak` extension). -Each domain & SANs will lead to a certificate request. +For example: if `acme.storage`'s value is `/etc/traefik/acme/acme.json`, the backup file will be `/etc/traefik/acme/acme.json.bak`. -#### Others domains - -You can provide SANs (alternative domains) to each main domain. -All domains must have A/AAAA records pointing to Træfik. - -!!! warning - Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits). - -Each domain & SANs will lead to a certificate request. +!!! note + When Træfik is launched in a container, the storage file's parent directory needs to be mounted to be able to access the backup file on the host. + Otherwise the backup file will be deleted when the container is stopped. Træfik will only generate it once! ### `dnsProvider` (Deprecated) !!! danger "DEPRECATED" - This option is deprecated, use [dnsChallenge.provider](/configuration/acme/#dnschallenge) instead. + This option is deprecated. Please use [dnsChallenge.provider](/configuration/acme/#provider) instead. ### `delayDontCheckDNS` (Deprecated) !!! danger "DEPRECATED" - This option is deprecated, use [dnsChallenge.delayBeforeCheck](/configuration/acme/#dnschallenge) instead. + This option is deprecated. Please use [dnsChallenge.delayBeforeCheck](/configuration/acme/#dnschallenge) instead. -## Wildcard certificates +## Fallbacks -[ACME V2](https://community.letsencrypt.org/t/acme-v2-and-wildcard-certificate-support-is-live/55579) allows wildcard certificate support. -However, this feature needs a specific configuration. +If Let's Encrypt is not reachable, these certificates will be used: -### DNS-01 Challenge - -As described in [Let's Encrypt post](https://community.letsencrypt.org/t/staging-endpoint-for-acme-v2/49605), wildcard certificates can only be generated through a `DNS-01` Challenge. -This challenge is linked to the Træfik option `acme.dnsChallenge`. - -```toml -[acme] -# ... -[acme.dnsChallenge] - provider = "digitalocean" - delayBeforeCheck = 0 -# ... -``` - -For more information about this option, please refer to the [dnsChallenge section](/configuration/acme/#dnschallenge). - -### Wildcard domain - -Wildcard domains can currently be provided only by to the `acme.domains` option. - -```toml -[acme] -# ... -[[acme.domains]] - main = "*.local1.com" - sans = ["local1.com"] -[[acme.domains]] - main = "*.local2.com" -# ... -``` - -For more information about this option, please refer to the [domains section](/configuration/acme/#domains). - -### Limitations - -Let's Encrypt wildcard support have some limitations to take into account : - -- Wildcard domain can not be a SAN (alternative domain), -- Wildcard domain on a wildcard domain is forbidden (for example `*.*.local.com`), -- A DNS-01 Challenge is executed for each domain (CN and SANs), DNS provider can not manage correctly this behavior as explained in the [DNS provider support section](/configuration/acme/#dns-provider-support) - - -### DNS provider support - -All DNS providers allow creating ACME wildcard certificates. -However, many troubles can appear for wildcard domains with SANs. - -If a wildcard domain is defined with it root domain as SAN, as described below, 2 DNS-01 Challenges will be executed. - -```toml -[acme] -# ... -[[acme.domains]] - main = "*.local1.com" - sans = ["local1.com"] -# ... -``` - -When a DNS-01 Challenge is done, Let's Encrypt checks if a TXT record is created with a given name and a given value. -When a certificate is generated for a wildcard domain is defined with it root domain as SAN, the requested TXT record name for both the wildcard domain and the root domain is the same. - -The [DNS RFC](https://community.letsencrypt.org/t/wildcard-issuance-two-txt-records-for-the-same-name/54528/2) allows this behavior. -But all DNS providers keep TXT records values in a cache with a TTL. -In function of the parameters given by the Træfik ACME client library ([LEGO](https://github.com/xenolf/lego)), the TXT record TTL can be superior to challenge Timeout. -In that event, the DNS-01 Challenge will not work correctly. - -[LEGO](https://github.com/xenolf/lego) will involve in the way to be adapted to all of DNS providers. -Meanwhile, the table described below contains all the DNS providers supported by Træfik and indicates if they allow generating certificates for a wildcard domain and its root domain. -Do not hesitate to complete it. - -| Provider Name | Provider code | Wildcard and Root Domain Support | -|--------------------------------------------------------|----------------|----------------------------------| -| [Auroradns](https://www.pcextreme.com/aurora/dns) | `auroradns` | Not tested yet | -| [Azure](https://azure.microsoft.com/services/dns/) | `azure` | Not tested yet | -| [Blue Cat](https://www.bluecatnetworks.com/) | `bluecat` | Not tested yet | -| [Cloudflare](https://www.cloudflare.com) | `cloudflare` | YES | -| [CloudXNS](https://www.cloudxns.net) | `cloudxns` | Not tested yet | -| [DigitalOcean](https://www.digitalocean.com) | `digitalocean` | YES | -| [DNSimple](https://dnsimple.com) | `dnsimple` | Not tested yet | -| [DNS Made Easy](https://dnsmadeeasy.com) | `dnsmadeeasy` | Not tested yet | -| [DNSPod](http://www.dnspod.net/) | `dnspod` | Not tested yet | -| [Duck DNS](https://www.duckdns.org/) | `duckdns` | Not tested yet | -| [Dyn](https://dyn.com) | `dyn` | Not tested yet | -| External Program | `exec` | Not tested yet | -| [Exoscale](https://www.exoscale.ch) | `exoscale` | Not tested yet | -| [Fast DNS](https://www.akamai.com/) | `fastdns` | Not tested yet | -| [Gandi](https://www.gandi.net) | `gandi` | Not tested yet | -| [Gandi V5](http://doc.livedns.gandi.net) | `gandiv5` | Not tested yet | -| [Glesys](https://glesys.com/) | `glesys` | Not tested yet | -| [GoDaddy](https://godaddy.com/domains) | `godaddy` | Not tested yet | -| [Google Cloud DNS](https://cloud.google.com/dns/docs/) | `gcloud` | YES | -| [Lightsail](https://aws.amazon.com/lightsail/) | `lightsail` | Not tested yet | -| [Linode](https://www.linode.com) | `linode` | Not tested yet | -| manual | - | YES | -| [Namecheap](https://www.namecheap.com) | `namecheap` | Not tested yet | -| [name.com](https://www.name.com/) | `namedotcom` | Not tested yet | -| [Ns1](https://ns1.com/) | `ns1` | Not tested yet | -| [Open Telekom Cloud](https://cloud.telekom.de/en/) | `otc` | Not tested yet | -| [OVH](https://www.ovh.com) | `ovh` | YES | -| [PowerDNS](https://www.powerdns.com) | `pdns` | Not tested yet | -| [Rackspace](https://www.rackspace.com/cloud/dns) | `rackspace` | Not tested yet | -| [RFC2136](https://tools.ietf.org/html/rfc2136) | `rfc2136` | Not tested yet | -| [Route 53](https://aws.amazon.com/route53/) | `route53` | YES | -| [VULTR](https://www.vultr.com) | `vultr` | Not tested yet | - -## ACME V2 migration - -During migration from ACME V1 to ACME V2 with a storage file, a backup is created with the content of the ACME V1 file. -To obtain the name of the backup file, Træfik concatenates the option `acme.storage` and the suffix `.bak`. - -For example : if `acme.storage` value is `/etc/traefik/acme/acme.json`, the backup file will be named `/etc/traefik/acme/acme.json.bak`. + 1. ACME certificates already generated before downtime + 1. Expired ACME certificates + 1. Provided certificates !!! note - When Træfik is launched in a container, do not forget to create a volume of the parent folder to get the backup file on the host. - Otherwise, the backup file will be deleted when the container will be stopped and Træfik will not generate it again. \ No newline at end of file + For new (sub)domains which need Let's Encrypt authentification, the default Træfik certificate will be used until Træfik is restarted. diff --git a/docs/user-guide/kubernetes.md b/docs/user-guide/kubernetes.md index 5e26ba6c6..2cd6f2c5f 100644 --- a/docs/user-guide/kubernetes.md +++ b/docs/user-guide/kubernetes.md @@ -81,9 +81,11 @@ For namespaced restrictions, one RoleBinding is required per watched namespace a It is possible to use Træfik with a [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) or a [DaemonSet](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/) object, whereas both options have their own pros and cons: -- The scalability is much better when using a Deployment, because you will have a Single-Pod-per-Node model when using the DaemonSet. -- It is possible to exclusively run a Service on a dedicated set of machines using taints and tolerations with a DaemonSet. -- On the other hand the DaemonSet allows you to access any Node directly on Port 80 and 443, where you have to setup a [Service](https://kubernetes.io/docs/concepts/services-networking/service/) object with a Deployment. +- The scalability can be much better when using a Deployment, because you will have a Single-Pod-per-Node model when using a DaemonSet, whereas you may need less replicas based on your environment when using a Deployment. +- DaemonSets automatically scale to new nodes, when the nodes join the cluster, whereas Deployment pods are only scheduled on new nodes if required. +- DaemonSets ensure that only one replica of pods run on any single node. Deployments require affinity settings if you want to ensure that two pods don't end up on the same node. +- DaemonSets can be run with the `NET_BIND_SERVICE` capability, which will allow it to bind to port 80/443/etc on each host. This will allow bypassing the kube-proxy, and reduce traffic hops. Note that this is against the Kubernetes Best Practices [Guidelines](https://kubernetes.io/docs/concepts/configuration/overview/#services), and raises the potential for scheduling/scaling issues. Despite potential issues, this remains the choice for most ingress controllers. +- If you are unsure which to choose, start with the Daemonset. The Deployment objects looks like this: @@ -118,6 +120,11 @@ spec: containers: - image: traefik name: traefik-ingress-lb + ports: + - name: http + containerPort: 80 + - name: admin + containerPort: 8080 args: - --api - --kubernetes @@ -172,7 +179,6 @@ spec: spec: serviceAccountName: traefik-ingress-controller terminationGracePeriodSeconds: 60 - hostNetwork: true containers: - image: traefik name: traefik-ingress-lb @@ -208,11 +214,13 @@ spec: - protocol: TCP port: 8080 name: admin - type: NodePort ``` [examples/k8s/traefik-ds.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/traefik-ds.yaml) +!!! note + This will create a Daemonset that uses privileged ports 80/8080 on the host. This may not work on all providers, but illustrates the static (non-NodePort) hostPort binding. The `traefik-ingress-service` can still be used inside the cluster to access the DaemonSet pods. + To deploy Træfik to your cluster start by submitting one of the YAML files to the cluster with `kubectl`: ```shell @@ -293,7 +301,21 @@ Install the Træfik chart by: ```shell helm install stable/traefik ``` +Install the Træfik chart using a values.yaml file. +```shell +helm install --values values.yaml stable/traefik +``` + +```yaml +dashboard: + enabled: true + domain: traefik-ui.minikube +kubernetes: + namespaces: + - default + - kube-system +``` For more information, check out [the documentation](https://github.com/kubernetes/charts/tree/master/stable/traefik). ## Submitting an Ingress to the Cluster diff --git a/examples/k8s/traefik-deployment.yaml b/examples/k8s/traefik-deployment.yaml index ccf18bfb1..1ad9f0d4e 100644 --- a/examples/k8s/traefik-deployment.yaml +++ b/examples/k8s/traefik-deployment.yaml @@ -28,6 +28,11 @@ spec: containers: - image: traefik name: traefik-ingress-lb + ports: + - name: http + containerPort: 80 + - name: admin + containerPort: 8080 args: - --api - --kubernetes diff --git a/examples/k8s/traefik-ds.yaml b/examples/k8s/traefik-ds.yaml index eda9bc088..177736938 100644 --- a/examples/k8s/traefik-ds.yaml +++ b/examples/k8s/traefik-ds.yaml @@ -21,7 +21,6 @@ spec: spec: serviceAccountName: traefik-ingress-controller terminationGracePeriodSeconds: 60 - hostNetwork: true containers: - image: traefik name: traefik-ingress-lb @@ -31,6 +30,7 @@ spec: hostPort: 80 - name: admin containerPort: 8080 + hostport: 8080 securityContext: capabilities: drop: @@ -57,4 +57,3 @@ spec: - protocol: TCP port: 8080 name: admin - type: NodePort diff --git a/integration/access_log_test.go b/integration/access_log_test.go index 19aef0473..9e950e526 100644 --- a/integration/access_log_test.go +++ b/integration/access_log_test.go @@ -30,7 +30,7 @@ type accessLogValue struct { code string user string frontendName string - backendName string + backendURL string } func (s *AccessLogSuite) SetUpSuite(c *check.C) { @@ -103,7 +103,7 @@ func (s *AccessLogSuite) TestAccessLogAuthFrontend(c *check.C) { code: "401", user: "-", frontendName: "Auth for frontend-Host-frontend-auth-docker-local", - backendName: "-", + backendURL: "/", }, } @@ -151,7 +151,7 @@ func (s *AccessLogSuite) TestAccessLogAuthEntrypoint(c *check.C) { code: "401", user: "-", frontendName: "Auth for entrypoint", - backendName: "-", + backendURL: "/", }, } @@ -199,7 +199,7 @@ func (s *AccessLogSuite) TestAccessLogAuthEntrypointSuccess(c *check.C) { code: "200", user: "test", frontendName: "Host-entrypoint-auth-docker", - backendName: "http://172.17.0", + backendURL: "http://172.17.0", }, } @@ -247,14 +247,14 @@ func (s *AccessLogSuite) TestAccessLogDigestAuthEntrypoint(c *check.C) { code: "401", user: "-", frontendName: "Auth for entrypoint", - backendName: "-", + backendURL: "/", }, { formatOnly: false, code: "200", user: "test", frontendName: "Host-entrypoint-digest-auth-docker", - backendName: "http://172.17.0", + backendURL: "http://172.17.0", }, } @@ -355,7 +355,7 @@ func (s *AccessLogSuite) TestAccessLogEntrypointRedirect(c *check.C) { code: "302", user: "-", frontendName: "entrypoint redirect for frontend-", - backendName: "-", + backendURL: "/", }, { formatOnly: true, @@ -405,7 +405,7 @@ func (s *AccessLogSuite) TestAccessLogFrontendRedirect(c *check.C) { code: "302", user: "-", frontendName: "frontend redirect for frontend-Path-", - backendName: "-", + backendURL: "/", }, { formatOnly: true, @@ -461,7 +461,7 @@ func (s *AccessLogSuite) TestAccessLogRateLimit(c *check.C) { code: "429", user: "-", frontendName: "rate limit for frontend-Host-ratelimit", - backendName: "/", + backendURL: "/", }, } @@ -512,7 +512,7 @@ func (s *AccessLogSuite) TestAccessLogBackendNotFound(c *check.C) { code: "404", user: "-", frontendName: "backend not found", - backendName: "/", + backendURL: "/", }, } @@ -557,7 +557,7 @@ func (s *AccessLogSuite) TestAccessLogEntrypointWhitelist(c *check.C) { code: "403", user: "-", frontendName: "ipwhitelister for entrypoint httpWhitelistReject", - backendName: "-", + backendURL: "/", }, } @@ -604,7 +604,7 @@ func (s *AccessLogSuite) TestAccessLogFrontendWhitelist(c *check.C) { code: "403", user: "-", frontendName: "ipwhitelister for frontend-Host-frontend-whitelist", - backendName: "-", + backendURL: "/", }, } @@ -734,7 +734,7 @@ func checkAccessLogExactValues(c *check.C, line string, i int, v accessLogValue) c.Assert(results[accesslog.OriginStatus], checker.Equals, v.code) c.Assert(results[accesslog.RequestCount], checker.Equals, fmt.Sprintf("%d", i+1)) c.Assert(results[accesslog.FrontendName], checker.Matches, `^"?`+v.frontendName+`.*$`) - c.Assert(results[accesslog.BackendURL], checker.Matches, `^"?`+v.backendName+`.*$`) + c.Assert(results[accesslog.BackendURL], checker.Matches, `^"?`+v.backendURL+`.*$`) c.Assert(results[accesslog.Duration], checker.Matches, `^\d+ms$`) } diff --git a/integration/consul_catalog_test.go b/integration/consul_catalog_test.go index fdf35b41a..e92cee66a 100644 --- a/integration/consul_catalog_test.go +++ b/integration/consul_catalog_test.go @@ -1,6 +1,7 @@ package integration import ( + "bytes" "fmt" "net/http" "time" @@ -41,7 +42,7 @@ func (s *ConsulCatalogSuite) waitToElectConsulLeader() error { leader, err := s.consulClient.Status().Leader() if err != nil || len(leader) == 0 { - return fmt.Errorf("Leader not found. %v", err) + return fmt.Errorf("leader not found. %v", err) } return nil @@ -55,9 +56,6 @@ func (s *ConsulCatalogSuite) createConsulClient(config *api.Config, c *check.C) s.consulClient = consulClient return consulClient } -func (s *ConsulCatalogSuite) startConsulService(c *check.C) { - -} func (s *ConsulCatalogSuite) registerService(name string, address string, port int, tags []string) error { catalog := s.consulClient.Catalog() @@ -78,28 +76,45 @@ func (s *ConsulCatalogSuite) registerService(name string, address string, port i return err } -func (s *ConsulCatalogSuite) registerAgentService(name string, address string, port int, tags []string) error { +func (s *ConsulCatalogSuite) registerAgentService(name string, address string, port int, tags []string, withHealthCheck bool) error { agent := s.consulClient.Agent() - err := agent.ServiceRegister( + var healthCheck *api.AgentServiceCheck + if withHealthCheck { + healthCheck = &api.AgentServiceCheck{ + HTTP: "http://" + address, + Interval: "10s", + } + } else { + healthCheck = nil + } + return agent.ServiceRegister( &api.AgentServiceRegistration{ ID: address, Tags: tags, Name: name, Address: address, Port: port, - Check: &api.AgentServiceCheck{ - HTTP: "http://" + address, - Interval: "10s", - }, + Check: healthCheck, }, ) - return err +} + +func (s *ConsulCatalogSuite) registerCheck(name string, address string, port int) error { + agent := s.consulClient.Agent() + checkRegistration := &api.AgentCheckRegistration{ + ID: fmt.Sprintf("%s-%s", name, address), + Name: name, + ServiceID: address, + } + checkRegistration.HTTP = fmt.Sprintf("http://%s:%d/health", address, port) + checkRegistration.Interval = "2s" + checkRegistration.CheckID = address + return agent.CheckRegister(checkRegistration) } func (s *ConsulCatalogSuite) deregisterAgentService(address string) error { agent := s.consulClient.Agent() - err := agent.ServiceDeregister(address) - return err + return agent.ServiceDeregister(address) } func (s *ConsulCatalogSuite) deregisterService(name string, address string) error { @@ -115,6 +130,22 @@ func (s *ConsulCatalogSuite) deregisterService(name string, address string) erro return err } +func (s *ConsulCatalogSuite) consulEnableServiceMaintenance(name string) error { + return s.consulClient.Agent().EnableServiceMaintenance(name, fmt.Sprintf("Maintenance mode for service %s", name)) +} + +func (s *ConsulCatalogSuite) consulDisableServiceMaintenance(name string) error { + return s.consulClient.Agent().DisableServiceMaintenance(name) +} + +func (s *ConsulCatalogSuite) consulEnableNodeMaintenance() error { + return s.consulClient.Agent().EnableNodeMaintenance("Maintenance mode for node") +} + +func (s *ConsulCatalogSuite) consulDisableNodeMaintenance() error { + return s.consulClient.Agent().DisableNodeMaintenance() +} + func (s *ConsulCatalogSuite) TestSimpleConfiguration(c *check.C) { cmd, display := s.traefikCmd( withConfigFile("fixtures/consul_catalog/simple.toml"), @@ -273,7 +304,7 @@ func (s *ConsulCatalogSuite) TestRefreshConfigWithMultipleNodeWithoutHealthCheck c.Assert(err, checker.IsNil, check.Commentf("Error registering service")) defer s.deregisterService("test", whoami.NetworkSettings.IPAddress) - err = s.registerAgentService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1"}) + err = s.registerAgentService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1"}, true) c.Assert(err, checker.IsNil, check.Commentf("Error registering agent service")) defer s.deregisterAgentService(whoami.NetworkSettings.IPAddress) @@ -514,3 +545,132 @@ func (s *ConsulCatalogSuite) TestRetryWithConsulServer(c *check.C) { err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody()) c.Assert(err, checker.IsNil) } + +func (s *ConsulCatalogSuite) TestServiceWithMultipleHealthCheck(c *check.C) { + //Scale consul to 0 to be able to start traefik before and test retry + s.composeProject.Scale(c, "consul", 0) + + cmd, display := s.traefikCmd( + withConfigFile("fixtures/consul_catalog/simple.toml"), + "--consulCatalog", + "--consulCatalog.watch=false", + "--consulCatalog.exposedByDefault=true", + "--consulCatalog.endpoint="+s.consulIP+":8500", + "--consulCatalog.domain=consul.localhost") + defer display(c) + err := cmd.Start() + c.Assert(err, checker.IsNil) + defer cmd.Process.Kill() + + // Wait for Traefik to turn ready. + err = try.GetRequest("http://127.0.0.1:8000/", 2*time.Second, try.StatusCodeIs(http.StatusNotFound)) + c.Assert(err, checker.IsNil) + + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) + c.Assert(err, checker.IsNil) + req.Host = "test.consul.localhost" + + // Request should fail + err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusNotFound), try.HasBody()) + c.Assert(err, checker.IsNil) + + // Scale consul to 1 + s.composeProject.Scale(c, "consul", 1) + s.waitToElectConsulLeader() + + whoami := s.composeProject.Container(c, "whoami1") + // Register service + err = s.registerAgentService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1"}, true) + c.Assert(err, checker.IsNil, check.Commentf("Error registering agent service")) + defer s.deregisterAgentService(whoami.NetworkSettings.IPAddress) + + // Register one healthcheck + err = s.registerCheck("test", whoami.NetworkSettings.IPAddress, 80) + c.Assert(err, checker.IsNil, check.Commentf("Error registering check")) + + // Provider consul catalog should be present + err = try.GetRequest("http://127.0.0.1:8080/api/providers", 10*time.Second, try.BodyContains("consul_catalog")) + c.Assert(err, checker.IsNil) + + // Should be ok + err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody()) + c.Assert(err, checker.IsNil) + + // Change health value of service to critical + reqHealth, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s:80/health", whoami.NetworkSettings.IPAddress), bytes.NewBuffer([]byte("500"))) + c.Assert(err, checker.IsNil) + reqHealth.Host = "test.consul.localhost" + + err = try.Request(reqHealth, 10*time.Second, try.StatusCodeIs(http.StatusOK)) + c.Assert(err, checker.IsNil) + + // Should be a 404 + err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusNotFound), try.HasBody()) + c.Assert(err, checker.IsNil) + + // Change health value of service to passing + reqHealth, err = http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s:80/health", whoami.NetworkSettings.IPAddress), bytes.NewBuffer([]byte("200"))) + c.Assert(err, checker.IsNil) + err = try.Request(reqHealth, 10*time.Second, try.StatusCodeIs(http.StatusOK)) + c.Assert(err, checker.IsNil) + + // Should be a 200 + err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody()) + c.Assert(err, checker.IsNil) +} + +func (s *ConsulCatalogSuite) TestMaintenanceMode(c *check.C) { + cmd, display := s.traefikCmd( + withConfigFile("fixtures/consul_catalog/simple.toml"), + "--consulCatalog", + "--consulCatalog.endpoint="+s.consulIP+":8500", + "--consulCatalog.domain=consul.localhost") + defer display(c) + err := cmd.Start() + c.Assert(err, checker.IsNil) + defer cmd.Process.Kill() + + // Wait for Traefik to turn ready. + err = try.GetRequest("http://127.0.0.1:8000/", 2*time.Second, try.StatusCodeIs(http.StatusNotFound)) + c.Assert(err, checker.IsNil) + + whoami := s.composeProject.Container(c, "whoami1") + + err = s.registerAgentService("test", whoami.NetworkSettings.IPAddress, 80, []string{}, false) + c.Assert(err, checker.IsNil, check.Commentf("Error registering service")) + + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) + c.Assert(err, checker.IsNil) + req.Host = "test.consul.localhost" + + err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody()) + c.Assert(err, checker.IsNil) + + // Enable service maintenance mode + err = s.consulEnableServiceMaintenance(whoami.NetworkSettings.IPAddress) + c.Assert(err, checker.IsNil) + + err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusNotFound), try.HasBody()) + c.Assert(err, checker.IsNil) + + // Disable service maintenance mode + err = s.consulDisableServiceMaintenance(whoami.NetworkSettings.IPAddress) + c.Assert(err, checker.IsNil) + + err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody()) + c.Assert(err, checker.IsNil) + + // Enable node maintenance mode + err = s.consulEnableNodeMaintenance() + c.Assert(err, checker.IsNil) + + err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusNotFound), try.HasBody()) + c.Assert(err, checker.IsNil) + + // Disable node maintenance mode + err = s.consulDisableNodeMaintenance() + c.Assert(err, checker.IsNil) + + err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody()) + c.Assert(err, checker.IsNil) +} diff --git a/metrics/prometheus.go b/metrics/prometheus.go index c79eb57a7..5fce28890 100644 --- a/metrics/prometheus.go +++ b/metrics/prometheus.go @@ -36,25 +36,18 @@ const ( backendServerUpName = metricNamePrefix + "backend_server_up" ) -const ( - // generationAgeForever indicates that a metric never gets outdated. - generationAgeForever = 0 - // generationAgeDefault is the default age of three generations. - generationAgeDefault = 3 -) - // promState holds all metric state internally and acts as the only Collector we register for Prometheus. // // This enables control to remove metrics that belong to outdated configuration. // As an example why this is required, consider Traefik learns about a new service. // It populates the 'traefik_server_backend_up' metric for it with a value of 1 (alive). // When the backend is undeployed now the metric is still there in the client library -// and will be until Traefik would be restarted. +// and will be returned on the metrics endpoint until Traefik would be restarted. // -// To solve this problem promState keeps track of configuration generations. -// Every time a new configuration is loaded, the generation is increased by one. -// Metrics that "belong" to a dynamic configuration part of Traefik (e.g. backend, entrypoint) -// are removed, given they were tracked more than 3 generations ago. +// To solve this problem promState keeps track of Traefik's dynamic configuration. +// Metrics that "belong" to a dynamic configuration part like backends or entrypoints +// are removed after they were scraped at least once when the corresponding object +// doesn't exist anymore. var promState = newPrometheusState() // PrometheusHandler exposes Prometheus routes. @@ -163,40 +156,66 @@ func RegisterPrometheus(config *types.Prometheus) Registry { } } -// OnConfigurationUpdate increases the current generation of the prometheus state. -func OnConfigurationUpdate() { - promState.IncGeneration() +// OnConfigurationUpdate receives the current configuration from Traefik. +// It then converts the configuration to the optimized package internal format +// and sets it to the promState. +func OnConfigurationUpdate(configurations types.Configurations) { + dynamicConfig := newDynamicConfig() + + for _, config := range configurations { + for _, frontend := range config.Frontends { + for _, entrypointName := range frontend.EntryPoints { + dynamicConfig.entrypoints[entrypointName] = true + } + } + + for backendName, backend := range config.Backends { + dynamicConfig.backends[backendName] = make(map[string]bool) + for _, server := range backend.Servers { + dynamicConfig.backends[backendName][server.URL] = true + } + } + } + + promState.SetDynamicConfig(dynamicConfig) } func newPrometheusState() *prometheusState { - collectors := make(chan *collector) - state := make(map[string]*collector) - return &prometheusState{ - collectors: collectors, - state: state, + collectors: make(chan *collector), + dynamicConfig: newDynamicConfig(), + state: make(map[string]*collector), } } type prometheusState struct { - currentGeneration int - collectors chan *collector - describers []func(ch chan<- *stdprometheus.Desc) + collectors chan *collector + describers []func(ch chan<- *stdprometheus.Desc) - mtx sync.Mutex - state map[string]*collector + mtx sync.Mutex + dynamicConfig *dynamicConfig + state map[string]*collector } -func (ps *prometheusState) IncGeneration() { +// reset is a utility method for unit testing. It should be called after each +// test run that changes promState internally in order to avoid dependencies +// between unit tests. +func (ps *prometheusState) reset() { + ps.collectors = make(chan *collector) + ps.describers = []func(ch chan<- *stdprometheus.Desc){} + ps.dynamicConfig = newDynamicConfig() + ps.state = make(map[string]*collector) +} + +func (ps *prometheusState) SetDynamicConfig(dynamicConfig *dynamicConfig) { ps.mtx.Lock() defer ps.mtx.Unlock() - ps.currentGeneration++ + ps.dynamicConfig = dynamicConfig } func (ps *prometheusState) ListenValueUpdates() { for collector := range ps.collectors { ps.mtx.Lock() - collector.lastTrackedGeneration = ps.currentGeneration ps.state[collector.id] = collector ps.mtx.Unlock() } @@ -212,42 +231,89 @@ func (ps *prometheusState) Describe(ch chan<- *stdprometheus.Desc) { // Collect implements prometheus.Collector. It calls the Collect // method of all metrics it received on the collectors channel. -// It's also responsible to remove metrics that were tracked -// at least three generations ago. Those metrics are cleaned up -// after the Collect of them were called. +// It's also responsible to remove metrics that belong to an outdated configuration. +// The removal happens only after their Collect method was called to ensure that +// also those metrics will be exported on the current scrape. func (ps *prometheusState) Collect(ch chan<- stdprometheus.Metric) { ps.mtx.Lock() defer ps.mtx.Unlock() - outdatedKeys := []string{} + var outdatedKeys []string for key, cs := range ps.state { cs.collector.Collect(ch) - if cs.maxAge == generationAgeForever { - continue - } - if ps.currentGeneration-cs.lastTrackedGeneration >= cs.maxAge { + if ps.isOutdated(cs) { outdatedKeys = append(outdatedKeys, key) } } for _, key := range outdatedKeys { + ps.state[key].delete() delete(ps.state, key) } } -func newCollector(metricName string, lnvs labelNamesValues, c stdprometheus.Collector) *collector { - maxAge := generationAgeDefault +// isOutdated checks whether the passed collector has labels that mark +// it as belonging to an outdated configuration of Traefik. +func (ps *prometheusState) isOutdated(collector *collector) bool { + labels := collector.labels - // metrics without labels should never become outdated - if len(lnvs) == 0 { - maxAge = generationAgeForever + if entrypointName, ok := labels["entrypoint"]; ok && !ps.dynamicConfig.hasEntrypoint(entrypointName) { + return true } + if backendName, ok := labels["backend"]; ok { + if !ps.dynamicConfig.hasBackend(backendName) { + return true + } + if url, ok := labels["url"]; ok && !ps.dynamicConfig.hasServerURL(backendName, url) { + return true + } + } + + return false +} + +func newDynamicConfig() *dynamicConfig { + return &dynamicConfig{ + entrypoints: make(map[string]bool), + backends: make(map[string]map[string]bool), + } +} + +// dynamicConfig holds the current configuration for entrypoints, backends, +// and server URLs in an optimized way to check for existence. This provides +// a performant way to check whether the collected metrics belong to the +// current configuration or to an outdated one. +type dynamicConfig struct { + entrypoints map[string]bool + backends map[string]map[string]bool +} + +func (d *dynamicConfig) hasEntrypoint(entrypointName string) bool { + _, ok := d.entrypoints[entrypointName] + return ok +} + +func (d *dynamicConfig) hasBackend(backendName string) bool { + _, ok := d.backends[backendName] + return ok +} + +func (d *dynamicConfig) hasServerURL(backendName, serverURL string) bool { + if backend, hasBackend := d.backends[backendName]; hasBackend { + _, ok := backend[serverURL] + return ok + } + return false +} + +func newCollector(metricName string, labels stdprometheus.Labels, c stdprometheus.Collector, delete func()) *collector { return &collector{ - id: buildMetricID(metricName, lnvs), - maxAge: maxAge, + id: buildMetricID(metricName, labels), + labels: labels, collector: c, + delete: delete, } } @@ -255,16 +321,19 @@ func newCollector(metricName string, lnvs labelNamesValues, c stdprometheus.Coll // It adds information on how many generations this metric should be present // in the /metrics output, relatived to the time it was last tracked. type collector struct { - id string - collector stdprometheus.Collector - lastTrackedGeneration int - maxAge int + id string + labels stdprometheus.Labels + collector stdprometheus.Collector + delete func() } -func buildMetricID(metricName string, lnvs labelNamesValues) string { - newLnvs := append([]string{}, lnvs...) - sort.Strings(newLnvs) - return metricName + ":" + strings.Join(newLnvs, "|") +func buildMetricID(metricName string, labels stdprometheus.Labels) string { + var labelNamesValues []string + for name, value := range labels { + labelNamesValues = append(labelNamesValues, name, value) + } + sort.Strings(labelNamesValues) + return metricName + ":" + strings.Join(labelNamesValues, "|") } func newCounterFrom(collectors chan<- *collector, opts stdprometheus.CounterOpts, labelNames []string) *counter { @@ -297,9 +366,12 @@ func (c *counter) With(labelValues ...string) metrics.Counter { } func (c *counter) Add(delta float64) { - collector := c.cv.With(c.labelNamesValues.ToLabels()) + labels := c.labelNamesValues.ToLabels() + collector := c.cv.With(labels) collector.Add(delta) - c.collectors <- newCollector(c.name, c.labelNamesValues, collector) + c.collectors <- newCollector(c.name, labels, collector, func() { + c.cv.Delete(labels) + }) } func (c *counter) Describe(ch chan<- *stdprometheus.Desc) { @@ -336,15 +408,21 @@ func (g *gauge) With(labelValues ...string) metrics.Gauge { } func (g *gauge) Add(delta float64) { - collector := g.gv.With(g.labelNamesValues.ToLabels()) + labels := g.labelNamesValues.ToLabels() + collector := g.gv.With(labels) collector.Add(delta) - g.collectors <- newCollector(g.name, g.labelNamesValues, collector) + g.collectors <- newCollector(g.name, labels, collector, func() { + g.gv.Delete(labels) + }) } func (g *gauge) Set(value float64) { - collector := g.gv.With(g.labelNamesValues.ToLabels()) + labels := g.labelNamesValues.ToLabels() + collector := g.gv.With(labels) collector.Set(value) - g.collectors <- newCollector(g.name, g.labelNamesValues, collector) + g.collectors <- newCollector(g.name, labels, collector, func() { + g.gv.Delete(labels) + }) } func (g *gauge) Describe(ch chan<- *stdprometheus.Desc) { @@ -377,9 +455,12 @@ func (h *histogram) With(labelValues ...string) metrics.Histogram { } func (h *histogram) Observe(value float64) { - collector := h.hv.With(h.labelNamesValues.ToLabels()) + labels := h.labelNamesValues.ToLabels() + collector := h.hv.With(labels) collector.Observe(value) - h.collectors <- newCollector(h.name, h.labelNamesValues, collector) + h.collectors <- newCollector(h.name, labels, collector, func() { + h.hv.Delete(labels) + }) } func (h *histogram) Describe(ch chan<- *stdprometheus.Desc) { diff --git a/metrics/prometheus_test.go b/metrics/prometheus_test.go index 9c698e045..41d7ab308 100644 --- a/metrics/prometheus_test.go +++ b/metrics/prometheus_test.go @@ -7,12 +7,16 @@ import ( "testing" "time" + th "github.com/containous/traefik/testhelpers" "github.com/containous/traefik/types" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" ) func TestPrometheus(t *testing.T) { + // Reset state of global promState. + defer promState.reset() + prometheusRegistry := RegisterPrometheus(&types.Prometheus{}) defer prometheus.Unregister(promState) @@ -177,56 +181,94 @@ func TestPrometheus(t *testing.T) { } } -func TestPrometheusGenerationLogicForMetricWithLabel(t *testing.T) { +func TestPrometheusMetricRemoval(t *testing.T) { + // Reset state of global promState. + defer promState.reset() + prometheusRegistry := RegisterPrometheus(&types.Prometheus{}) defer prometheus.Unregister(promState) - // Metrics with labels belonging to a specific configuration in Traefik - // should be removed when the generationMaxAge is exceeded. As example - // we use the traefik_backend_requests_total metric. + configurations := make(types.Configurations) + configurations["providerName"] = th.BuildConfiguration( + th.WithFrontends( + th.WithFrontend("backend1", th.WithEntryPoints("entrypoint1")), + ), + th.WithBackends( + th.WithBackendNew("backend1", th.WithServersNew(th.WithServerNew("http://localhost:9000"))), + ), + ) + OnConfigurationUpdate(configurations) + + // Register some metrics manually that are not part of the active configuration. + // Those metrics should be part of the /metrics output on the first scrape but + // should be removed after that scrape. + prometheusRegistry. + EntrypointReqsCounter(). + With("entrypoint", "entrypoint2", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet, "protocol", "http"). + Add(1) prometheusRegistry. BackendReqsCounter(). - With("backend", "backend1", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet, "protocol", "http"). + With("backend", "backend2", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet, "protocol", "http"). + Add(1) + prometheusRegistry. + BackendServerUpGauge(). + With("backend", "backend1", "url", "http://localhost:9999"). + Set(1) + + delayForTrackingCompletion() + + assertMetricsExist(t, mustScrape(), entrypointReqsTotalName, backendReqsTotalName, backendServerUpName) + assertMetricsAbsent(t, mustScrape(), entrypointReqsTotalName, backendReqsTotalName, backendServerUpName) + + // To verify that metrics belonging to active configurations are not removed + // here the counter examples. + prometheusRegistry. + EntrypointReqsCounter(). + With("entrypoint", "entrypoint1", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet, "protocol", "http"). Add(1) delayForTrackingCompletion() - assertMetricExists(t, backendReqsTotalName, mustScrape()) - - // Increase the config generation one more than the max age of a metric. - for i := 0; i < generationAgeDefault+1; i++ { - OnConfigurationUpdate() - } - - // On the next scrape the metric still exists and will be removed - // after the scrape completed. - assertMetricExists(t, backendReqsTotalName, mustScrape()) - - // Now the metric should be absent. - assertMetricAbsent(t, backendReqsTotalName, mustScrape()) + assertMetricsExist(t, mustScrape(), entrypointReqsTotalName) + assertMetricsExist(t, mustScrape(), entrypointReqsTotalName) } -func TestPrometheusGenerationLogicForMetricWithoutLabel(t *testing.T) { +func TestPrometheusRemovedMetricsReset(t *testing.T) { + // Reset state of global promState. + defer promState.reset() + prometheusRegistry := RegisterPrometheus(&types.Prometheus{}) defer prometheus.Unregister(promState) - // Metrics without labels like traefik_config_reloads_total should live forever - // and never get removed. - prometheusRegistry.ConfigReloadsCounter().Add(1) + labelNamesValues := []string{ + "backend", "backend", + "code", strconv.Itoa(http.StatusOK), + "method", http.MethodGet, + "protocol", "http", + } + prometheusRegistry. + BackendReqsCounter(). + With(labelNamesValues...). + Add(3) delayForTrackingCompletion() - assertMetricExists(t, configReloadsTotalName, mustScrape()) + metricsFamilies := mustScrape() + assertCounterValue(t, 3, findMetricFamily(backendReqsTotalName, metricsFamilies), labelNamesValues...) - // Increase the config generation one more than the max age of a metric. - for i := 0; i < generationAgeDefault+100; i++ { - OnConfigurationUpdate() - } + // There is no dynamic configuration and so this metric will be deleted + // after the first scrape. + assertMetricsAbsent(t, mustScrape(), backendReqsTotalName) - // Scrape two times in order to verify, that it is not removed after the - // first scrape completed. - assertMetricExists(t, configReloadsTotalName, mustScrape()) - assertMetricExists(t, configReloadsTotalName, mustScrape()) + prometheusRegistry. + BackendReqsCounter(). + With(labelNamesValues...). + Add(1) + + delayForTrackingCompletion() + + metricsFamilies = mustScrape() + assertCounterValue(t, 1, findMetricFamily(backendReqsTotalName, metricsFamilies), labelNamesValues...) } // Tracking and gathering the metrics happens concurrently. @@ -247,17 +289,23 @@ func mustScrape() []*dto.MetricFamily { return families } -func assertMetricExists(t *testing.T, name string, families []*dto.MetricFamily) { +func assertMetricsExist(t *testing.T, families []*dto.MetricFamily, metricNames ...string) { t.Helper() - if findMetricFamily(name, families) == nil { - t.Errorf("gathered metrics do not contain %q", name) + + for _, metricName := range metricNames { + if findMetricFamily(metricName, families) == nil { + t.Errorf("gathered metrics should contain %q", metricName) + } } } -func assertMetricAbsent(t *testing.T, name string, families []*dto.MetricFamily) { +func assertMetricsAbsent(t *testing.T, families []*dto.MetricFamily, metricNames ...string) { t.Helper() - if findMetricFamily(name, families) != nil { - t.Errorf("gathered metrics contain %q, but should not", name) + + for _, metricName := range metricNames { + if findMetricFamily(metricName, families) != nil { + t.Errorf("gathered metrics should not contain %q", metricName) + } } } @@ -270,6 +318,58 @@ func findMetricFamily(name string, families []*dto.MetricFamily) *dto.MetricFami return nil } +func findMetricByLabelNamesValues(family *dto.MetricFamily, labelNamesValues ...string) *dto.Metric { + if family == nil { + return nil + } + + for _, metric := range family.Metric { + if hasMetricAllLabelPairs(metric, labelNamesValues...) { + return metric + } + } + + return nil +} + +func hasMetricAllLabelPairs(metric *dto.Metric, labelNamesValues ...string) bool { + for i := 0; i < len(labelNamesValues); i += 2 { + name, val := labelNamesValues[i], labelNamesValues[i+1] + if !hasMetricLabelPair(metric, name, val) { + return false + } + } + return true +} + +func hasMetricLabelPair(metric *dto.Metric, labelName, labelValue string) bool { + for _, labelPair := range metric.Label { + if labelPair.GetName() == labelName && labelPair.GetValue() == labelValue { + return true + } + } + return false +} + +func assertCounterValue(t *testing.T, want float64, family *dto.MetricFamily, labelNamesValues ...string) { + t.Helper() + + metric := findMetricByLabelNamesValues(family, labelNamesValues...) + + if metric == nil { + t.Error("metric must not be nil") + return + } + if metric.Counter == nil { + t.Errorf("metric %s must be a counter", family.GetName()) + return + } + + if cv := metric.Counter.GetValue(); cv != want { + t.Errorf("metric %s has value %v, want %v", family.GetName(), cv, want) + } +} + func buildCounterAssert(t *testing.T, metricName string, expectedValue int) func(family *dto.MetricFamily) { return func(family *dto.MetricFamily) { if cv := int(family.Metric[0].Counter.GetValue()); cv != expectedValue { diff --git a/middlewares/accesslog/save_backend.go b/middlewares/accesslog/save_backend.go index 674656a86..78b503aea 100644 --- a/middlewares/accesslog/save_backend.go +++ b/middlewares/accesslog/save_backend.go @@ -3,15 +3,14 @@ package accesslog import ( "fmt" "net/http" - "strings" "time" "github.com/urfave/negroni" "github.com/vulcand/oxy/utils" ) -// SaveBackend sends the backend name to the logger. These are always used with a corresponding -// SaveFrontend handler. +// SaveBackend sends the backend name to the logger. +// These are always used with a corresponding SaveFrontend handler. type SaveBackend struct { next http.Handler backendName string @@ -23,61 +22,9 @@ func NewSaveBackend(next http.Handler, backendName string) http.Handler { } func (sb *SaveBackend) ServeHTTP(rw http.ResponseWriter, r *http.Request) { - table := GetLogDataTable(r) - table.Core[BackendName] = sb.backendName - table.Core[BackendURL] = r.URL // note that this is *not* the original incoming URL - table.Core[BackendAddr] = r.URL.Host - - crw := &captureResponseWriter{rw: rw} - start := time.Now().UTC() - - sb.next.ServeHTTP(crw, r) - - // use UTC to handle switchover of daylight saving correctly - table.Core[OriginDuration] = time.Now().UTC().Sub(start) - table.Core[OriginStatus] = crw.Status() - table.Core[OriginStatusLine] = fmt.Sprintf("%03d %s", crw.Status(), http.StatusText(crw.Status())) - // make copy of headers so we can ensure there is no subsequent mutation during response processing - table.OriginResponse = make(http.Header) - utils.CopyHeaders(table.OriginResponse, crw.Header()) - table.Core[OriginContentSize] = crw.Size() -} - -// SaveFrontend sends the frontend name to the logger. These are sometimes used with a corresponding -// SaveBackend handler, but not always. For example, redirected requests don't reach a backend. -type SaveFrontend struct { - next http.Handler - frontendName string -} - -// NewSaveFrontend creates a SaveFrontend handler. -func NewSaveFrontend(next http.Handler, frontendName string) http.Handler { - return &SaveFrontend{next, frontendName} -} - -func (sb *SaveFrontend) ServeHTTP(rw http.ResponseWriter, r *http.Request) { - table := GetLogDataTable(r) - table.Core[FrontendName] = strings.TrimPrefix(sb.frontendName, "frontend-") - - sb.next.ServeHTTP(rw, r) -} - -// SaveNegroniFrontend sends the frontend name to the logger. -type SaveNegroniFrontend struct { - next negroni.Handler - frontendName string -} - -// NewSaveNegroniFrontend creates a SaveNegroniFrontend handler. -func NewSaveNegroniFrontend(next negroni.Handler, frontendName string) negroni.Handler { - return &SaveNegroniFrontend{next, frontendName} -} - -func (sb *SaveNegroniFrontend) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { - table := GetLogDataTable(r) - table.Core[FrontendName] = strings.TrimPrefix(sb.frontendName, "frontend-") - - sb.next.ServeHTTP(rw, r, next) + serveSaveBackend(rw, r, sb.backendName, func(crw *captureResponseWriter) { + sb.next.ServeHTTP(crw, r) + }) } // SaveNegroniBackend sends the backend name to the logger. @@ -92,13 +39,21 @@ func NewSaveNegroniBackend(next negroni.Handler, backendName string) negroni.Han } func (sb *SaveNegroniBackend) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { + serveSaveBackend(rw, r, sb.backendName, func(crw *captureResponseWriter) { + sb.next.ServeHTTP(crw, r, next) + }) +} + +func serveSaveBackend(rw http.ResponseWriter, r *http.Request, backendName string, apply func(*captureResponseWriter)) { table := GetLogDataTable(r) - table.Core[BackendName] = sb.backendName + table.Core[BackendName] = backendName + table.Core[BackendURL] = r.URL // note that this is *not* the original incoming URL + table.Core[BackendAddr] = r.URL.Host crw := &captureResponseWriter{rw: rw} start := time.Now().UTC() - sb.next.ServeHTTP(crw, r, next) + apply(crw) // use UTC to handle switchover of daylight saving correctly table.Core[OriginDuration] = time.Now().UTC().Sub(start) diff --git a/middlewares/accesslog/save_frontend.go b/middlewares/accesslog/save_frontend.go new file mode 100644 index 000000000..e256444a5 --- /dev/null +++ b/middlewares/accesslog/save_frontend.go @@ -0,0 +1,51 @@ +package accesslog + +import ( + "net/http" + "strings" + + "github.com/urfave/negroni" +) + +// SaveFrontend sends the frontend name to the logger. +// These are sometimes used with a corresponding SaveBackend handler, but not always. +// For example, redirected requests don't reach a backend. +type SaveFrontend struct { + next http.Handler + frontendName string +} + +// NewSaveFrontend creates a SaveFrontend handler. +func NewSaveFrontend(next http.Handler, frontendName string) http.Handler { + return &SaveFrontend{next, frontendName} +} + +func (sf *SaveFrontend) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + serveSaveFrontend(r, sf.frontendName, func() { + sf.next.ServeHTTP(rw, r) + }) +} + +// SaveNegroniFrontend sends the frontend name to the logger. +type SaveNegroniFrontend struct { + next negroni.Handler + frontendName string +} + +// NewSaveNegroniFrontend creates a SaveNegroniFrontend handler. +func NewSaveNegroniFrontend(next negroni.Handler, frontendName string) negroni.Handler { + return &SaveNegroniFrontend{next, frontendName} +} + +func (sf *SaveNegroniFrontend) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { + serveSaveFrontend(r, sf.frontendName, func() { + sf.next.ServeHTTP(rw, r, next) + }) +} + +func serveSaveFrontend(r *http.Request, frontendName string, apply func()) { + table := GetLogDataTable(r) + table.Core[FrontendName] = strings.TrimPrefix(frontendName, "frontend-") + + apply() +} diff --git a/middlewares/errorpages/error_pages.go b/middlewares/errorpages/error_pages.go index fb262081e..9fbe84706 100644 --- a/middlewares/errorpages/error_pages.go +++ b/middlewares/errorpages/error_pages.go @@ -101,12 +101,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request, next http. h.backendHandler.ServeHTTP(recorderErrorPage, pageReq.WithContext(req.Context())) - utils.CopyHeaders(w.Header(), recorder.Header()) - for key := range recorderErrorPage.Header() { - w.Header().Del(key) - } utils.CopyHeaders(w.Header(), recorderErrorPage.Header()) - w.WriteHeader(recorder.GetCode()) w.Write(recorderErrorPage.GetBody().Bytes()) return @@ -174,64 +169,65 @@ type responseRecorderWithCloseNotify struct { // CloseNotify returns a channel that receives at most a // single value (true) when the client connection has gone away. -func (rw *responseRecorderWithCloseNotify) CloseNotify() <-chan bool { - return rw.responseWriter.(http.CloseNotifier).CloseNotify() +func (r *responseRecorderWithCloseNotify) CloseNotify() <-chan bool { + return r.responseWriter.(http.CloseNotifier).CloseNotify() } // Header returns the response headers. -func (rw *responseRecorderWithoutCloseNotify) Header() http.Header { - if rw.HeaderMap == nil { - rw.HeaderMap = make(http.Header) +func (r *responseRecorderWithoutCloseNotify) Header() http.Header { + if r.HeaderMap == nil { + r.HeaderMap = make(http.Header) } - return rw.HeaderMap + + return r.HeaderMap } -func (rw *responseRecorderWithoutCloseNotify) GetCode() int { - return rw.Code +func (r *responseRecorderWithoutCloseNotify) GetCode() int { + return r.Code } -func (rw *responseRecorderWithoutCloseNotify) GetBody() *bytes.Buffer { - return rw.Body +func (r *responseRecorderWithoutCloseNotify) GetBody() *bytes.Buffer { + return r.Body } -func (rw *responseRecorderWithoutCloseNotify) IsStreamingResponseStarted() bool { - return rw.streamingResponseStarted +func (r *responseRecorderWithoutCloseNotify) IsStreamingResponseStarted() bool { + return r.streamingResponseStarted } // Write always succeeds and writes to rw.Body, if not nil. -func (rw *responseRecorderWithoutCloseNotify) Write(buf []byte) (int, error) { - if rw.err != nil { - return 0, rw.err +func (r *responseRecorderWithoutCloseNotify) Write(buf []byte) (int, error) { + if r.err != nil { + return 0, r.err } - return rw.Body.Write(buf) + return r.Body.Write(buf) } // WriteHeader sets rw.Code. -func (rw *responseRecorderWithoutCloseNotify) WriteHeader(code int) { - rw.Code = code +func (r *responseRecorderWithoutCloseNotify) WriteHeader(code int) { + r.Code = code } // Hijack hijacks the connection -func (rw *responseRecorderWithoutCloseNotify) Hijack() (net.Conn, *bufio.ReadWriter, error) { - return rw.responseWriter.(http.Hijacker).Hijack() +func (r *responseRecorderWithoutCloseNotify) Hijack() (net.Conn, *bufio.ReadWriter, error) { + return r.responseWriter.(http.Hijacker).Hijack() } // Flush sends any buffered data to the client. -func (rw *responseRecorderWithoutCloseNotify) Flush() { - if !rw.streamingResponseStarted { - utils.CopyHeaders(rw.responseWriter.Header(), rw.Header()) - rw.responseWriter.WriteHeader(rw.Code) - rw.streamingResponseStarted = true +func (r *responseRecorderWithoutCloseNotify) Flush() { + if !r.streamingResponseStarted { + utils.CopyHeaders(r.responseWriter.Header(), r.Header()) + r.responseWriter.WriteHeader(r.Code) + r.streamingResponseStarted = true } - _, err := rw.responseWriter.Write(rw.Body.Bytes()) + _, err := r.responseWriter.Write(r.Body.Bytes()) if err != nil { - log.Errorf("Error writing response in responseRecorder: %s", err) - rw.err = err + log.Errorf("Error writing response in responseRecorder: %v", err) + r.err = err } - rw.Body.Reset() + r.Body.Reset() - if flusher, ok := rw.responseWriter.(http.Flusher); ok { + if flusher, ok := r.responseWriter.(http.Flusher); ok { flusher.Flush() } } diff --git a/middlewares/errorpages/error_pages_test.go b/middlewares/errorpages/error_pages_test.go index 2264dc336..9cf19d87d 100644 --- a/middlewares/errorpages/error_pages_test.go +++ b/middlewares/errorpages/error_pages_test.go @@ -318,7 +318,6 @@ func TestHandlerOldWayIntegration(t *testing.T) { require.NoError(t, err) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("X-Foo", "bar") w.WriteHeader(test.backendCode) fmt.Fprintln(w, http.StatusText(test.backendCode)) }) @@ -331,7 +330,6 @@ func TestHandlerOldWayIntegration(t *testing.T) { n.ServeHTTP(recorder, req) test.validate(t, recorder) - assert.Equal(t, "bar", recorder.Header().Get("X-Foo"), "missing header") }) } } diff --git a/provider/acme/account.go b/provider/acme/account.go index f9bf2dc23..682a4ad8a 100644 --- a/provider/acme/account.go +++ b/provider/acme/account.go @@ -7,7 +7,7 @@ import ( "crypto/x509" "github.com/containous/traefik/log" - acme "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) // Account is used to store lets encrypt registration info diff --git a/provider/acme/challenge.go b/provider/acme/challenge.go index aec4a45ab..0ed638131 100644 --- a/provider/acme/challenge.go +++ b/provider/acme/challenge.go @@ -8,7 +8,7 @@ import ( "github.com/containous/flaeg" "github.com/containous/traefik/log" "github.com/containous/traefik/safe" - acme "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) func dnsOverrideDelay(delay flaeg.Duration) error { diff --git a/provider/acme/local_store.go b/provider/acme/local_store.go index bee69a3e8..66541bf3b 100644 --- a/provider/acme/local_store.go +++ b/provider/acme/local_store.go @@ -60,6 +60,7 @@ func (s *LocalStore) get() (*StoredData, error) { return nil, err } if isOldRegistration { + log.Debug("Reset ACME account.") s.storedData.Account = nil s.SaveDataChan <- s.storedData } diff --git a/provider/acme/provider.go b/provider/acme/provider.go index 734398308..d322628cc 100644 --- a/provider/acme/provider.go +++ b/provider/acme/provider.go @@ -22,8 +22,10 @@ import ( "github.com/containous/traefik/safe" traefiktls "github.com/containous/traefik/tls" "github.com/containous/traefik/types" + "github.com/containous/traefik/version" "github.com/pkg/errors" - acme "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" + legolog "github.com/xenolf/lego/log" "github.com/xenolf/lego/providers/dns" ) @@ -86,10 +88,11 @@ func (p *Provider) SetConfigListenerChan(configFromListenerChan chan types.Confi } func (p *Provider) init() error { + acme.UserAgent = fmt.Sprintf("containous-traefik/%s", version.Version) if p.ACMELogging { - acme.Logger = fmtlog.New(os.Stderr, "legolog: ", fmtlog.LstdFlags) + legolog.Logger = fmtlog.New(os.Stderr, "legolog: ", fmtlog.LstdFlags) } else { - acme.Logger = fmtlog.New(ioutil.Discard, "", 0) + legolog.Logger = fmtlog.New(ioutil.Discard, "", 0) } var err error @@ -103,6 +106,11 @@ func (p *Provider) init() error { return fmt.Errorf("unable to get ACME account : %v", err) } + // Reset Account if caServer changed, thus registration URI can be updated + if p.account != nil && p.account.Registration != nil && !strings.HasPrefix(p.account.Registration.URI, p.CAServer) { + p.account = nil + } + p.certificates, err = p.Store.GetCertificates() if err != nil { return fmt.Errorf("unable to get ACME certificates : %v", err) @@ -227,7 +235,7 @@ func (p *Provider) resolveCertificate(domain types.Domain, domainFromConfigurati } p.addCertificateForDomain(domain, certificate.Certificate, certificate.PrivateKey) - return &certificate, nil + return certificate, nil } func (p *Provider) getClient() (*acme.Client, error) { @@ -299,7 +307,6 @@ func (p *Provider) getClient() (*acme.Client, error) { } p.client = client } - return p.client, nil } diff --git a/provider/consulcatalog/consul_catalog.go b/provider/consulcatalog/consul_catalog.go index 65c44ac49..1c9010ccd 100644 --- a/provider/consulcatalog/consul_catalog.go +++ b/provider/consulcatalog/consul_catalog.go @@ -2,6 +2,7 @@ package consulcatalog import ( "errors" + "fmt" "strconv" "strings" "sync" @@ -255,7 +256,8 @@ func (p *Provider) watchHealthState(stopCh <-chan struct{}, watchCh chan<- map[s safe.Go(func() { // variable to hold previous state - var flashback []string + var flashback map[string][]string + var flashbackMaintenance []string options := &api.QueryOptions{WaitTime: DefaultWatchWaitTime} @@ -267,19 +269,31 @@ func (p *Provider) watchHealthState(stopCh <-chan struct{}, watchCh chan<- map[s } // Listening to changes that leads to `passing` state or degrades from it. - healthyState, meta, err := health.State("passing", options) + healthyState, meta, err := health.State("any", options) if err != nil { log.WithError(err).Error("Failed to retrieve health checks") notifyError(err) return } - var current []string + var current = make(map[string][]string) + var currentFailing = make(map[string]*api.HealthCheck) + var maintenance []string if healthyState != nil { for _, healthy := range healthyState { - current = append(current, healthy.ServiceID) + key := fmt.Sprintf("%s-%s", healthy.Node, healthy.ServiceID) + _, failing := currentFailing[key] + if healthy.Status == "passing" && !failing { + current[key] = append(current[key], healthy.Node) + } else if strings.HasPrefix(healthy.CheckID, "_service_maintenance") || strings.HasPrefix(healthy.CheckID, "_node_maintenance") { + maintenance = append(maintenance, healthy.CheckID) + } else { + currentFailing[key] = healthy + if _, ok := current[key]; ok { + delete(current, key) + } + } } - } // If LastIndex didn't change then it means `Get` returned @@ -302,18 +316,26 @@ func (p *Provider) watchHealthState(stopCh <-chan struct{}, watchCh chan<- map[s // A critical note is that the return of a blocking request is no guarantee of a change. // It is possible that there was an idempotent write that does not affect the result of the query. // Thus it is required to do extra check for changes... - addedKeys, removedKeys := getChangedStringKeys(current, flashback) + addedKeys, removedKeys, changedKeys := getChangedHealth(current, flashback) + + if len(addedKeys) > 0 || len(removedKeys) > 0 || len(changedKeys) > 0 { + log.WithField("DiscoveredServices", addedKeys). + WithField("MissingServices", removedKeys). + WithField("ChangedServices", changedKeys). + Debug("Health State change detected.") - if len(addedKeys) > 0 { - log.WithField("DiscoveredServices", addedKeys).Debug("Health State change detected.") watchCh <- data flashback = current - } + flashbackMaintenance = maintenance + } else { + addedKeysMaintenance, removedMaintenance := getChangedStringKeys(maintenance, flashbackMaintenance) - if len(removedKeys) > 0 { - log.WithField("MissingServices", removedKeys).Debug("Health State change detected.") - watchCh <- data - flashback = current + if len(addedKeysMaintenance) > 0 || len(removedMaintenance) > 0 { + log.WithField("MaintenanceMode", maintenance).Debug("Maintenance change detected.") + watchCh <- data + flashback = current + flashbackMaintenance = maintenance + } } } } @@ -394,6 +416,27 @@ func getChangedStringKeys(currState []string, prevState []string) ([]string, []s return fun.Keys(addedKeys).([]string), fun.Keys(removedKeys).([]string) } +func getChangedHealth(current map[string][]string, previous map[string][]string) ([]string, []string, []string) { + currKeySet := fun.Set(fun.Keys(current).([]string)).(map[string]bool) + prevKeySet := fun.Set(fun.Keys(previous).([]string)).(map[string]bool) + + addedKeys := fun.Difference(currKeySet, prevKeySet).(map[string]bool) + removedKeys := fun.Difference(prevKeySet, currKeySet).(map[string]bool) + + var changedKeys []string + + for key, value := range current { + if prevValue, ok := previous[key]; ok { + addedNodesKeys, removedNodesKeys := getChangedStringKeys(value, prevValue) + if len(addedNodesKeys) > 0 || len(removedNodesKeys) > 0 { + changedKeys = append(changedKeys, key) + } + } + } + + return fun.Keys(addedKeys).([]string), fun.Keys(removedKeys).([]string), changedKeys +} + func getChangedIntKeys(currState []int, prevState []int) ([]int, []int) { currKeySet := fun.Set(currState).(map[int]bool) prevKeySet := fun.Set(prevState).(map[int]bool) diff --git a/provider/kubernetes/kubernetes.go b/provider/kubernetes/kubernetes.go index 9896746e2..616ec4459 100644 --- a/provider/kubernetes/kubernetes.go +++ b/provider/kubernetes/kubernetes.go @@ -6,8 +6,10 @@ import ( "errors" "flag" "fmt" + "net" "os" "reflect" + "strconv" "strings" "text/template" "time" @@ -319,7 +321,7 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error) continue } for _, address := range subset.Addresses { - url := fmt.Sprintf("%s://%s:%d", protocol, address.IP, endpointPort) + url := protocol + "://" + net.JoinHostPort(address.IP, strconv.FormatInt(int64(endpointPort), 10)) name := url if address.TargetRef != nil && address.TargetRef.Name != "" { name = address.TargetRef.Name diff --git a/server/header_rewriter.go b/server/header_rewriter.go index 193220385..b4b46b2d7 100644 --- a/server/header_rewriter.go +++ b/server/header_rewriter.go @@ -44,7 +44,7 @@ func (h *headerRewriter) Rewrite(req *http.Request) { err := h.ips.IsAuthorized(req) if err != nil { - log.Error(err) + log.Debug(err) h.secureRewriter.Rewrite(req) return } diff --git a/server/server.go b/server/server.go index 9bf780d14..0dc255556 100644 --- a/server/server.go +++ b/server/server.go @@ -557,7 +557,10 @@ func (s *serverEntryPoint) getCertificate(clientHello *tls.ClientHelloInfo) (*tl } func (s *Server) postLoadConfiguration() { - metrics.OnConfigurationUpdate() + if s.metricsRegistry.IsEnabled() { + activeConfig := s.currentConfigurations.Get().(types.Configurations) + metrics.OnConfigurationUpdate(activeConfig) + } if s.globalConfiguration.ACME == nil || s.leadership == nil || !s.leadership.IsLeader() { return diff --git a/server/server_test.go b/server/server_test.go index ddd8070ce..78b145bd3 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -16,9 +16,8 @@ import ( "github.com/containous/traefik/healthcheck" "github.com/containous/traefik/metrics" "github.com/containous/traefik/middlewares" - "github.com/containous/traefik/provider/label" "github.com/containous/traefik/rules" - "github.com/containous/traefik/testhelpers" + th "github.com/containous/traefik/testhelpers" "github.com/containous/traefik/tls" "github.com/containous/traefik/types" "github.com/stretchr/testify/assert" @@ -202,9 +201,9 @@ func TestListenProvidersSkipsSameConfigurationForProvider(t *testing.T) { } }() - config := buildDynamicConfig( - withFrontend("frontend", buildFrontend()), - withBackend("backend", buildBackend()), + config := th.BuildConfiguration( + th.WithFrontends(th.WithFrontend("backend")), + th.WithBackends(th.WithBackendNew("backend")), ) // provide a configuration @@ -243,9 +242,9 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) { } }() - config := buildDynamicConfig( - withFrontend("frontend", buildFrontend()), - withBackend("backend", buildBackend()), + config := th.BuildConfiguration( + th.WithFrontends(th.WithFrontend("backend")), + th.WithBackends(th.WithBackendNew("backend")), ) server.configurationChan <- types.ConfigMessage{ProviderName: "kubernetes", Configuration: config} server.configurationChan <- types.ConfigMessage{ProviderName: "marathon", Configuration: config} @@ -398,7 +397,7 @@ func TestServerMultipleFrontendRules(t *testing.T) { t.Fatalf("Error while building route for %s: %+v", expression, err) } - request := testhelpers.MustNewRequest(http.MethodGet, test.requestURL, nil) + request := th.MustNewRequest(http.MethodGet, test.requestURL, nil) routeMatch := routeResult.Match(request, &mux.RouteMatch{Route: routeResult}) if !routeMatch { @@ -479,7 +478,7 @@ func TestServerLoadConfigHealthCheckOptions(t *testing.T) { if healthCheck != nil { expectedNumHealthCheckBackends = 1 } - assert.Len(t, healthcheck.GetHealthCheck(testhelpers.NewCollectingHealthCheckMetrics()).Backends, expectedNumHealthCheckBackends, "health check backends") + assert.Len(t, healthcheck.GetHealthCheck(th.NewCollectingHealthCheckMetrics()).Backends, expectedNumHealthCheckBackends, "health check backends") }) } } @@ -842,62 +841,88 @@ func TestServerResponseEmptyBackend(t *testing.T) { testCases := []struct { desc string - dynamicConfig func(testServerURL string) *types.Configuration + config func(testServerURL string) *types.Configuration expectedStatusCode int }{ { desc: "Ok", - dynamicConfig: func(testServerURL string) *types.Configuration { - return buildDynamicConfig( - withFrontend("frontend", buildFrontend(withRoute(requestPath, routeRule))), - withBackend("backend", buildBackend(withServer("testServer", testServerURL))), + config: func(testServerURL string) *types.Configuration { + return th.BuildConfiguration( + th.WithFrontends(th.WithFrontend("backend", + th.WithEntryPoints("http"), + th.WithRoutes(th.WithRoute(requestPath, routeRule))), + ), + th.WithBackends(th.WithBackendNew("backend", + th.WithLBMethod("wrr"), + th.WithServersNew(th.WithServerNew(testServerURL))), + ), ) }, expectedStatusCode: http.StatusOK, }, { desc: "No Frontend", - dynamicConfig: func(testServerURL string) *types.Configuration { - return buildDynamicConfig() + config: func(testServerURL string) *types.Configuration { + return th.BuildConfiguration() }, expectedStatusCode: http.StatusNotFound, }, { desc: "Empty Backend LB-Drr", - dynamicConfig: func(testServerURL string) *types.Configuration { - return buildDynamicConfig( - withFrontend("frontend", buildFrontend(withRoute(requestPath, routeRule))), - withBackend("backend", buildBackend(withLoadBalancer("Drr", false))), + config: func(testServerURL string) *types.Configuration { + return th.BuildConfiguration( + th.WithFrontends(th.WithFrontend("backend", + th.WithEntryPoints("http"), + th.WithRoutes(th.WithRoute(requestPath, routeRule))), + ), + th.WithBackends(th.WithBackendNew("backend", + th.WithLBMethod("drr")), + ), ) }, expectedStatusCode: http.StatusServiceUnavailable, }, { desc: "Empty Backend LB-Drr Sticky", - dynamicConfig: func(testServerURL string) *types.Configuration { - return buildDynamicConfig( - withFrontend("frontend", buildFrontend(withRoute(requestPath, routeRule))), - withBackend("backend", buildBackend(withLoadBalancer("Drr", true))), + config: func(testServerURL string) *types.Configuration { + return th.BuildConfiguration( + th.WithFrontends(th.WithFrontend("backend", + th.WithEntryPoints("http"), + th.WithRoutes(th.WithRoute(requestPath, routeRule))), + ), + th.WithBackends(th.WithBackendNew("backend", + th.WithLBMethod("drr"), th.WithLBSticky("test")), + ), ) }, expectedStatusCode: http.StatusServiceUnavailable, }, { desc: "Empty Backend LB-Wrr", - dynamicConfig: func(testServerURL string) *types.Configuration { - return buildDynamicConfig( - withFrontend("frontend", buildFrontend(withRoute(requestPath, routeRule))), - withBackend("backend", buildBackend(withLoadBalancer("Wrr", false))), + config: func(testServerURL string) *types.Configuration { + return th.BuildConfiguration( + th.WithFrontends(th.WithFrontend("backend", + th.WithEntryPoints("http"), + th.WithRoutes(th.WithRoute(requestPath, routeRule))), + ), + th.WithBackends(th.WithBackendNew("backend", + th.WithLBMethod("wrr")), + ), ) }, expectedStatusCode: http.StatusServiceUnavailable, }, { desc: "Empty Backend LB-Wrr Sticky", - dynamicConfig: func(testServerURL string) *types.Configuration { - return buildDynamicConfig( - withFrontend("frontend", buildFrontend(withRoute(requestPath, routeRule))), - withBackend("backend", buildBackend(withLoadBalancer("Wrr", true))), + config: func(testServerURL string) *types.Configuration { + return th.BuildConfiguration( + th.WithFrontends(th.WithFrontend("backend", + th.WithEntryPoints("http"), + th.WithRoutes(th.WithRoute(requestPath, routeRule))), + ), + th.WithBackends(th.WithBackendNew("backend", + th.WithLBMethod("wrr"), th.WithLBSticky("test")), + ), ) }, expectedStatusCode: http.StatusServiceUnavailable, @@ -919,7 +944,7 @@ func TestServerResponseEmptyBackend(t *testing.T) { entryPointsConfig := map[string]EntryPoint{ "http": {Configuration: &configuration.EntryPoint{ForwardedHeaders: &configuration.ForwardedHeaders{Insecure: true}}}, } - dynamicConfigs := types.Configurations{"config": test.dynamicConfig(testServer.URL)} + dynamicConfigs := types.Configurations{"config": test.config(testServer.URL)} srv := NewServer(globalConfig, nil, entryPointsConfig) entryPoints, err := srv.loadConfig(dynamicConfigs, globalConfig) @@ -1015,7 +1040,7 @@ func TestBuildRedirectHandler(t *testing.T) { rewrite, err := srv.buildRedirectHandler(test.srcEntryPointName, test.redirect) require.NoError(t, err) - req := testhelpers.MustNewRequest(http.MethodGet, test.url, nil) + req := th.MustNewRequest(http.MethodGet, test.url, nil) recorder := httptest.NewRecorder() rewrite.ServeHTTP(recorder, req, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -1145,71 +1170,3 @@ func TestNewServerWithResponseModifiers(t *testing.T) { }) } } - -func buildDynamicConfig(dynamicConfigBuilders ...func(*types.Configuration)) *types.Configuration { - config := &types.Configuration{ - Frontends: make(map[string]*types.Frontend), - Backends: make(map[string]*types.Backend), - } - for _, build := range dynamicConfigBuilders { - build(config) - } - return config -} - -func withFrontend(frontendName string, frontend *types.Frontend) func(*types.Configuration) { - return func(config *types.Configuration) { - config.Frontends[frontendName] = frontend - } -} - -func withBackend(backendName string, backend *types.Backend) func(*types.Configuration) { - return func(config *types.Configuration) { - config.Backends[backendName] = backend - } -} - -func buildFrontend(frontendBuilders ...func(*types.Frontend)) *types.Frontend { - fe := &types.Frontend{ - EntryPoints: []string{"http"}, - Backend: "backend", - Routes: make(map[string]types.Route), - } - for _, build := range frontendBuilders { - build(fe) - } - return fe -} - -func withRoute(routeName, rule string) func(*types.Frontend) { - return func(fe *types.Frontend) { - fe.Routes[routeName] = types.Route{Rule: rule} - } -} - -func buildBackend(backendBuilders ...func(*types.Backend)) *types.Backend { - be := &types.Backend{ - Servers: make(map[string]types.Server), - LoadBalancer: &types.LoadBalancer{Method: "Wrr"}, - } - for _, build := range backendBuilders { - build(be) - } - return be -} - -func withServer(name, url string) func(backend *types.Backend) { - return func(be *types.Backend) { - be.Servers[name] = types.Server{URL: url, Weight: label.DefaultWeight} - } -} - -func withLoadBalancer(method string, sticky bool) func(*types.Backend) { - return func(be *types.Backend) { - if sticky { - be.LoadBalancer = &types.LoadBalancer{Method: method, Stickiness: &types.Stickiness{CookieName: "test"}} - } else { - be.LoadBalancer = &types.LoadBalancer{Method: method} - } - } -} diff --git a/testhelpers/config.go b/testhelpers/config.go new file mode 100644 index 000000000..2217787dd --- /dev/null +++ b/testhelpers/config.go @@ -0,0 +1,134 @@ +package testhelpers + +import ( + "github.com/containous/traefik/provider" + "github.com/containous/traefik/types" +) + +// BuildConfiguration is a helper to create a configuration. +func BuildConfiguration(dynamicConfigBuilders ...func(*types.Configuration)) *types.Configuration { + config := &types.Configuration{} + for _, build := range dynamicConfigBuilders { + build(config) + } + return config +} + +// -- Backend + +// WithBackends is a helper to create a configuration +func WithBackends(opts ...func(*types.Backend) string) func(*types.Configuration) { + return func(c *types.Configuration) { + c.Backends = make(map[string]*types.Backend) + for _, opt := range opts { + b := &types.Backend{} + name := opt(b) + c.Backends[name] = b + } + } +} + +// WithBackendNew is a helper to create a configuration +func WithBackendNew(name string, opts ...func(*types.Backend)) func(*types.Backend) string { + return func(b *types.Backend) string { + for _, opt := range opts { + opt(b) + } + return name + } +} + +// WithServersNew is a helper to create a configuration +func WithServersNew(opts ...func(*types.Server) string) func(*types.Backend) { + return func(b *types.Backend) { + b.Servers = make(map[string]types.Server) + for _, opt := range opts { + s := &types.Server{Weight: 1} + name := opt(s) + b.Servers[name] = *s + } + } +} + +// WithServerNew is a helper to create a configuration +func WithServerNew(url string, opts ...func(*types.Server)) func(*types.Server) string { + return func(s *types.Server) string { + for _, opt := range opts { + opt(s) + } + s.URL = url + return provider.Normalize(url) + } +} + +// WithLBMethod is a helper to create a configuration +func WithLBMethod(method string) func(*types.Backend) { + return func(b *types.Backend) { + if b.LoadBalancer == nil { + b.LoadBalancer = &types.LoadBalancer{} + } + b.LoadBalancer.Method = method + } +} + +// -- Frontend + +// WithFrontends is a helper to create a configuration +func WithFrontends(opts ...func(*types.Frontend) string) func(*types.Configuration) { + return func(c *types.Configuration) { + c.Frontends = make(map[string]*types.Frontend) + for _, opt := range opts { + f := &types.Frontend{} + name := opt(f) + c.Frontends[name] = f + } + } +} + +// WithFrontend is a helper to create a configuration +func WithFrontend(backend string, opts ...func(*types.Frontend)) func(*types.Frontend) string { + return func(f *types.Frontend) string { + for _, opt := range opts { + opt(f) + } + f.Backend = backend + return backend + } +} + +// WithEntryPoints is a helper to create a configuration +func WithEntryPoints(eps ...string) func(*types.Frontend) { + return func(f *types.Frontend) { + f.EntryPoints = eps + } +} + +// WithRoutes is a helper to create a configuration +func WithRoutes(opts ...func(*types.Route) string) func(*types.Frontend) { + return func(f *types.Frontend) { + f.Routes = make(map[string]types.Route) + for _, opt := range opts { + s := &types.Route{} + name := opt(s) + f.Routes[name] = *s + } + } +} + +// WithRoute is a helper to create a configuration +func WithRoute(name string, rule string) func(*types.Route) string { + return func(r *types.Route) string { + r.Rule = rule + return name + } +} + +// WithLBSticky is a helper to create a configuration +func WithLBSticky(cookieName string) func(*types.Backend) { + return func(b *types.Backend) { + if b.LoadBalancer == nil { + b.LoadBalancer = &types.LoadBalancer{} + } + b.LoadBalancer.Stickiness = &types.Stickiness{CookieName: cookieName} + } +} diff --git a/testhelpers/helpers.go b/testhelpers/helpers.go index 17172692b..d0535d17f 100644 --- a/testhelpers/helpers.go +++ b/testhelpers/helpers.go @@ -7,16 +7,6 @@ import ( "net/url" ) -// Intp returns a pointer to the given integer value. -func Intp(i int) *int { - return &i -} - -// Stringp returns a pointer to the given string value. -func Stringp(s string) *string { - return &s -} - // MustNewRequest creates a new http get request or panics if it can't func MustNewRequest(method, urlStr string, body io.Reader) *http.Request { request, err := http.NewRequest(method, urlStr, body) diff --git a/testhelpers/metrics.go b/testhelpers/metrics.go index 2267ce98c..bc41c2c3f 100644 --- a/testhelpers/metrics.go +++ b/testhelpers/metrics.go @@ -46,12 +46,12 @@ type CollectingHealthCheckMetrics struct { Gauge *CollectingGauge } -// NewCollectingHealthCheckMetrics creates a new CollectingHealthCheckMetrics instance. -func NewCollectingHealthCheckMetrics() *CollectingHealthCheckMetrics { - return &CollectingHealthCheckMetrics{&CollectingGauge{}} -} - // BackendServerUpGauge is there to satisfy the healthcheck.metricsRegistry interface. func (m *CollectingHealthCheckMetrics) BackendServerUpGauge() metrics.Gauge { return m.Gauge } + +// NewCollectingHealthCheckMetrics creates a new CollectingHealthCheckMetrics instance. +func NewCollectingHealthCheckMetrics() *CollectingHealthCheckMetrics { + return &CollectingHealthCheckMetrics{&CollectingGauge{}} +} diff --git a/vendor/github.com/dnsimple/dnsimple-go/LICENSE.txt b/vendor/github.com/dnsimple/dnsimple-go/LICENSE.txt index 9d8e840fa..3c2f300e1 100644 --- a/vendor/github.com/dnsimple/dnsimple-go/LICENSE.txt +++ b/vendor/github.com/dnsimple/dnsimple-go/LICENSE.txt @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014-2017 Aetrion LLC dba DNSimple +Copyright (c) 2014-2018 Aetrion LLC dba DNSimple Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/accounts.go b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/accounts.go index aaadef0ff..2f86ed305 100644 --- a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/accounts.go +++ b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/accounts.go @@ -1,15 +1,12 @@ package dnsimple -import ( -) - type AccountsService struct { client *Client } // Account represents a DNSimple account. type Account struct { - ID int `json:"id,omitempty"` + ID int64 `json:"id,omitempty"` Email string `json:"email,omitempty"` PlanIdentifier string `json:"plan_identifier,omitempty"` CreatedAt string `json:"created_at,omitempty"` diff --git a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/certificates.go b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/certificates.go index 8e62d6c44..84ddba094 100644 --- a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/certificates.go +++ b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/certificates.go @@ -2,29 +2,31 @@ package dnsimple import ( "fmt" - "strconv" ) // CertificatesService handles communication with the certificate related // methods of the DNSimple API. // -// See https://developer.dnsimple.com/v2/domains/certificates +// See https://developer.dnsimple.com/v2/certificates type CertificatesService struct { client *Client } // Certificate represents a Certificate in DNSimple. type Certificate struct { - ID int `json:"id,omitempty"` - DomainID int `json:"domain_id,omitempty"` - CommonName string `json:"common_name,omitempty"` - Years int `json:"years,omitempty"` - State string `json:"state,omitempty"` - AuthorityIdentifier string `json:"authority_identifier,omitempty"` - CreatedAt string `json:"created_at,omitempty"` - UpdatedAt string `json:"updated_at,omitempty"` - ExpiresOn string `json:"expires_on,omitempty"` - CertificateRequest string `json:"csr,omitempty"` + ID int64 `json:"id,omitempty"` + DomainID int64 `json:"domain_id,omitempty"` + ContactID int64 `json:"contact_id,omitempty"` + CommonName string `json:"common_name,omitempty"` + AlternateNames []string `json:"alternate_names,omitempty"` + Years int `json:"years,omitempty"` + State string `json:"state,omitempty"` + AuthorityIdentifier string `json:"authority_identifier,omitempty"` + AutoRenew bool `json:"auto_renew"` + CreatedAt string `json:"created_at,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` + ExpiresOn string `json:"expires_on,omitempty"` + CertificateRequest string `json:"csr,omitempty"` } // CertificateBundle represents a container for all the PEM-encoded X509 certificate entities, @@ -37,9 +39,46 @@ type CertificateBundle struct { IntermediateCertificates []string `json:"chain,omitempty"` } -func certificatePath(accountID, domainIdentifier, certificateID string) (path string) { +// CertificatePurchase represents a Certificate Purchase in DNSimple. +type CertificatePurchase struct { + ID int64 `json:"id,omitempty"` + CertificateID int64 `json:"new_certificate_id,omitempty"` + State string `json:"state,omitempty"` + AutoRenew bool `json:"auto_renew,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` +} + +// CertificateRenewal represents a Certificate Renewal in DNSimple. +type CertificateRenewal struct { + ID int64 `json:"id,omitempty"` + OldCertificateID int64 `json:"old_certificate_id,omitempty"` + NewCertificateID int64 `json:"new_certificate_id,omitempty"` + State string `json:"state,omitempty"` + AutoRenew bool `json:"auto_renew,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` +} + +// LetsencryptCertificateAttributes is a set of attributes to purchase a Let's Encrypt certificate. +type LetsencryptCertificateAttributes struct { + ContactID int64 `json:"contact_id,omitempty"` + Name string `json:"name,omitempty"` + AutoRenew bool `json:"auto_renew,omitempty"` + AlternateNames []string `json:"alternate_names,omitempty"` +} + +func certificatePath(accountID, domainIdentifier string, certificateID int64) (path string) { path = fmt.Sprintf("%v/certificates", domainPath(accountID, domainIdentifier)) - if certificateID != "" { + if certificateID != 0 { + path += fmt.Sprintf("/%v", certificateID) + } + return +} + +func letsencryptCertificatePath(accountID, domainIdentifier string, certificateID int64) (path string) { + path = fmt.Sprintf("%v/certificates/letsencrypt", domainPath(accountID, domainIdentifier)) + if certificateID != 0 { path += fmt.Sprintf("/%v", certificateID) } return @@ -63,11 +102,23 @@ type certificatesResponse struct { Data []Certificate `json:"data"` } -// ListCertificates list the certificates for a domain. +// certificatePurchaseResponse represents a response from an API method that returns a CertificatePurchase struct. +type certificatePurchaseResponse struct { + Response + Data *CertificatePurchase `json:"data"` +} + +// certificateRenewalResponse represents a response from an API method that returns a CertificateRenewal struct. +type certificateRenewalResponse struct { + Response + Data *CertificateRenewal `json:"data"` +} + +// ListCertificates lists the certificates for a domain in the account. // -// See https://developer.dnsimple.com/v2/domains/certificates#list +// See https://developer.dnsimple.com/v2/certificates#listCertificates func (s *CertificatesService) ListCertificates(accountID, domainIdentifier string, options *ListOptions) (*certificatesResponse, error) { - path := versioned(certificatePath(accountID, domainIdentifier, "")) + path := versioned(certificatePath(accountID, domainIdentifier, 0)) certificatesResponse := &certificatesResponse{} path, err := addURLQueryOptions(path, options) @@ -84,11 +135,11 @@ func (s *CertificatesService) ListCertificates(accountID, domainIdentifier strin return certificatesResponse, nil } -// GetCertificate fetches the certificate. +// GetCertificate gets the details of a certificate. // -// See https://developer.dnsimple.com/v2/domains/certificates#get -func (s *CertificatesService) GetCertificate(accountID, domainIdentifier string, certificateID int) (*certificateResponse, error) { - path := versioned(certificatePath(accountID, domainIdentifier, strconv.Itoa(certificateID))) +// See https://developer.dnsimple.com/v2/certificates#getCertificate +func (s *CertificatesService) GetCertificate(accountID, domainIdentifier string, certificateID int64) (*certificateResponse, error) { + path := versioned(certificatePath(accountID, domainIdentifier, certificateID)) certificateResponse := &certificateResponse{} resp, err := s.client.get(path, certificateResponse) @@ -100,12 +151,12 @@ func (s *CertificatesService) GetCertificate(accountID, domainIdentifier string, return certificateResponse, nil } -// DownloadCertificate download the issued server certificate, -// as well the root certificate and the intermediate chain. +// DownloadCertificate gets the PEM-encoded certificate, +// along with the root certificate and intermediate chain. // -// See https://developer.dnsimple.com/v2/domains/certificates#download -func (s *CertificatesService) DownloadCertificate(accountID, domainIdentifier string, certificateID int) (*certificateBundleResponse, error) { - path := versioned(certificatePath(accountID, domainIdentifier, strconv.Itoa(certificateID)) + "/download") +// See https://developer.dnsimple.com/v2/certificates#downloadCertificate +func (s *CertificatesService) DownloadCertificate(accountID, domainIdentifier string, certificateID int64) (*certificateBundleResponse, error) { + path := versioned(certificatePath(accountID, domainIdentifier, certificateID) + "/download") certificateBundleResponse := &certificateBundleResponse{} resp, err := s.client.get(path, certificateBundleResponse) @@ -117,11 +168,11 @@ func (s *CertificatesService) DownloadCertificate(accountID, domainIdentifier st return certificateBundleResponse, nil } -// GetCertificatePrivateKey fetches the certificate private key. +// GetCertificatePrivateKey gets the PEM-encoded certificate private key. // -// See https://developer.dnsimple.com/v2/domains/certificates#get-private-key -func (s *CertificatesService) GetCertificatePrivateKey(accountID, domainIdentifier string, certificateID int) (*certificateBundleResponse, error) { - path := versioned(certificatePath(accountID, domainIdentifier, strconv.Itoa(certificateID)) + "/private_key") +// See https://developer.dnsimple.com/v2/certificates#getCertificatePrivateKey +func (s *CertificatesService) GetCertificatePrivateKey(accountID, domainIdentifier string, certificateID int64) (*certificateBundleResponse, error) { + path := versioned(certificatePath(accountID, domainIdentifier, certificateID) + "/private_key") certificateBundleResponse := &certificateBundleResponse{} resp, err := s.client.get(path, certificateBundleResponse) @@ -132,3 +183,67 @@ func (s *CertificatesService) GetCertificatePrivateKey(accountID, domainIdentifi certificateBundleResponse.HttpResponse = resp return certificateBundleResponse, nil } + +// PurchaseLetsencryptCertificate purchases a Let's Encrypt certificate. +// +// See https://developer.dnsimple.com/v2/certificates/#purchaseLetsencryptCertificate +func (s *CertificatesService) PurchaseLetsencryptCertificate(accountID, domainIdentifier string, certificateAttributes LetsencryptCertificateAttributes) (*certificatePurchaseResponse, error) { + path := versioned(letsencryptCertificatePath(accountID, domainIdentifier, 0)) + certificatePurchaseResponse := &certificatePurchaseResponse{} + + resp, err := s.client.post(path, certificateAttributes, certificatePurchaseResponse) + if err != nil { + return nil, err + } + + certificatePurchaseResponse.HttpResponse = resp + return certificatePurchaseResponse, nil +} + +// IssueLetsencryptCertificate issues a pending Let's Encrypt certificate purchase order. +// +// See https://developer.dnsimple.com/v2/certificates/#issueLetsencryptCertificate +func (s *CertificatesService) IssueLetsencryptCertificate(accountID, domainIdentifier string, certificateID int64) (*certificateResponse, error) { + path := versioned(letsencryptCertificatePath(accountID, domainIdentifier, certificateID) + "/issue") + certificateResponse := &certificateResponse{} + + resp, err := s.client.post(path, nil, certificateResponse) + if err != nil { + return nil, err + } + + certificateResponse.HttpResponse = resp + return certificateResponse, nil +} + +// PurchaseLetsencryptCertificateRenewal purchases a Let's Encrypt certificate renewal. +// +// See https://developer.dnsimple.com/v2/certificates/#purchaseRenewalLetsencryptCertificate +func (s *CertificatesService) PurchaseLetsencryptCertificateRenewal(accountID, domainIdentifier string, certificateID int64, certificateAttributes LetsencryptCertificateAttributes) (*certificateRenewalResponse, error) { + path := versioned(letsencryptCertificatePath(accountID, domainIdentifier, certificateID) + "/renewals") + certificateRenewalResponse := &certificateRenewalResponse{} + + resp, err := s.client.post(path, certificateAttributes, certificateRenewalResponse) + if err != nil { + return nil, err + } + + certificateRenewalResponse.HttpResponse = resp + return certificateRenewalResponse, nil +} + +// IssueLetsencryptCertificateRenewal issues a pending Let's Encrypt certificate renewal order. +// +// See https://developer.dnsimple.com/v2/certificates/#issueRenewalLetsencryptCertificate +func (s *CertificatesService) IssueLetsencryptCertificateRenewal(accountID, domainIdentifier string, certificateID, certificateRenewalID int64) (*certificateResponse, error) { + path := versioned(letsencryptCertificatePath(accountID, domainIdentifier, certificateID) + fmt.Sprintf("/renewals/%d/issue", certificateRenewalID)) + certificateResponse := &certificateResponse{} + + resp, err := s.client.post(path, nil, certificateResponse) + if err != nil { + return nil, err + } + + certificateResponse.HttpResponse = resp + return certificateResponse, nil +} diff --git a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/contacts.go b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/contacts.go index a09f38d87..d35cffef9 100644 --- a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/contacts.go +++ b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/contacts.go @@ -14,8 +14,8 @@ type ContactsService struct { // Contact represents a Contact in DNSimple. type Contact struct { - ID int `json:"id,omitempty"` - AccountID int `json:"account_id,omitempty"` + ID int64 `json:"id,omitempty"` + AccountID int64 `json:"account_id,omitempty"` Label string `json:"label,omitempty"` FirstName string `json:"first_name,omitempty"` LastName string `json:"last_name,omitempty"` @@ -34,7 +34,7 @@ type Contact struct { UpdatedAt string `json:"updated_at,omitempty"` } -func contactPath(accountID string, contactID int) (path string) { +func contactPath(accountID string, contactID int64) (path string) { path = fmt.Sprintf("/%v/contacts", accountID) if contactID != 0 { path += fmt.Sprintf("/%v", contactID) @@ -94,7 +94,7 @@ func (s *ContactsService) CreateContact(accountID string, contactAttributes Cont // GetContact fetches a contact. // // See https://developer.dnsimple.com/v2/contacts/#get -func (s *ContactsService) GetContact(accountID string, contactID int) (*contactResponse, error) { +func (s *ContactsService) GetContact(accountID string, contactID int64) (*contactResponse, error) { path := versioned(contactPath(accountID, contactID)) contactResponse := &contactResponse{} @@ -110,7 +110,7 @@ func (s *ContactsService) GetContact(accountID string, contactID int) (*contactR // UpdateContact updates a contact. // // See https://developer.dnsimple.com/v2/contacts/#update -func (s *ContactsService) UpdateContact(accountID string, contactID int, contactAttributes Contact) (*contactResponse, error) { +func (s *ContactsService) UpdateContact(accountID string, contactID int64, contactAttributes Contact) (*contactResponse, error) { path := versioned(contactPath(accountID, contactID)) contactResponse := &contactResponse{} @@ -126,7 +126,7 @@ func (s *ContactsService) UpdateContact(accountID string, contactID int, contact // DeleteContact PERMANENTLY deletes a contact from the account. // // See https://developer.dnsimple.com/v2/contacts/#delete -func (s *ContactsService) DeleteContact(accountID string, contactID int) (*contactResponse, error) { +func (s *ContactsService) DeleteContact(accountID string, contactID int64) (*contactResponse, error) { path := versioned(contactPath(accountID, contactID)) contactResponse := &contactResponse{} diff --git a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/dnsimple.go b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/dnsimple.go index 909480ff6..36887a67b 100644 --- a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/dnsimple.go +++ b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/dnsimple.go @@ -23,7 +23,7 @@ const ( // This is a pro-forma convention given that Go dependencies // tends to be fetched directly from the repo. // It is also used in the user-agent identify the client. - Version = "0.14.0" + Version = "0.16.0" // defaultBaseURL to the DNSimple production API. defaultBaseURL = "https://api.dnsimple.com" diff --git a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains.go b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains.go index 8b3bc25e0..6cd8fd7fb 100644 --- a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains.go +++ b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains.go @@ -14,9 +14,9 @@ type DomainsService struct { // Domain represents a domain in DNSimple. type Domain struct { - ID int `json:"id,omitempty"` - AccountID int `json:"account_id,omitempty"` - RegistrantID int `json:"registrant_id,omitempty"` + ID int64 `json:"id,omitempty"` + AccountID int64 `json:"account_id,omitempty"` + RegistrantID int64 `json:"registrant_id,omitempty"` Name string `json:"name,omitempty"` UnicodeName string `json:"unicode_name,omitempty"` Token string `json:"token,omitempty"` diff --git a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_collaborators.go b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_collaborators.go index a6a23f406..da0513996 100644 --- a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_collaborators.go +++ b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_collaborators.go @@ -6,10 +6,10 @@ import ( // Collaborator represents a Collaborator in DNSimple. type Collaborator struct { - ID int `json:"id,omitempty"` - DomainID int `json:"domain_id,omitempty"` + ID int64 `json:"id,omitempty"` + DomainID int64 `json:"domain_id,omitempty"` DomainName string `json:"domain_name,omitempty"` - UserID int `json:"user_id,omitempty"` + UserID int64 `json:"user_id,omitempty"` UserEmail string `json:"user_email,omitempty"` Invitation bool `json:"invitation,omitempty"` CreatedAt string `json:"created_at,omitempty"` @@ -17,9 +17,9 @@ type Collaborator struct { AcceptedAt string `json:"accepted_at,omitempty"` } -func collaboratorPath(accountID, domainIdentifier, collaboratorID string) (path string) { +func collaboratorPath(accountID, domainIdentifier string, collaboratorID int64) (path string) { path = fmt.Sprintf("%v/collaborators", domainPath(accountID, domainIdentifier)) - if collaboratorID != "" { + if collaboratorID != 0 { path += fmt.Sprintf("/%v", collaboratorID) } return @@ -46,7 +46,7 @@ type collaboratorsResponse struct { // // See https://developer.dnsimple.com/v2/domains/collaborators#list func (s *DomainsService) ListCollaborators(accountID, domainIdentifier string, options *ListOptions) (*collaboratorsResponse, error) { - path := versioned(collaboratorPath(accountID, domainIdentifier, "")) + path := versioned(collaboratorPath(accountID, domainIdentifier, 0)) collaboratorsResponse := &collaboratorsResponse{} path, err := addURLQueryOptions(path, options) @@ -67,7 +67,7 @@ func (s *DomainsService) ListCollaborators(accountID, domainIdentifier string, o // // See https://developer.dnsimple.com/v2/domains/collaborators#add func (s *DomainsService) AddCollaborator(accountID string, domainIdentifier string, attributes CollaboratorAttributes) (*collaboratorResponse, error) { - path := versioned(collaboratorPath(accountID, domainIdentifier, "")) + path := versioned(collaboratorPath(accountID, domainIdentifier, 0)) collaboratorResponse := &collaboratorResponse{} resp, err := s.client.post(path, attributes, collaboratorResponse) @@ -81,8 +81,8 @@ func (s *DomainsService) AddCollaborator(accountID string, domainIdentifier stri // RemoveCollaborator PERMANENTLY deletes a domain from the account. // -// See https://developer.dnsimple.com/v2/domains/collaborators#add -func (s *DomainsService) RemoveCollaborator(accountID string, domainIdentifier string, collaboratorID string) (*collaboratorResponse, error) { +// See https://developer.dnsimple.com/v2/domains/collaborators#remove +func (s *DomainsService) RemoveCollaborator(accountID string, domainIdentifier string, collaboratorID int64) (*collaboratorResponse, error) { path := versioned(collaboratorPath(accountID, domainIdentifier, collaboratorID)) collaboratorResponse := &collaboratorResponse{} diff --git a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_delegation_signer_records.go b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_delegation_signer_records.go index a4df814bf..f2f0092de 100644 --- a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_delegation_signer_records.go +++ b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_delegation_signer_records.go @@ -4,8 +4,8 @@ import "fmt" // DelegationSignerRecord represents a delegation signer record for a domain in DNSimple. type DelegationSignerRecord struct { - ID int `json:"id,omitempty"` - DomainID int `json:"domain_id,omitempty"` + ID int64 `json:"id,omitempty"` + DomainID int64 `json:"domain_id,omitempty"` Algorithm string `json:"algorithm"` Digest string `json:"digest"` DigestType string `json:"digest_type"` @@ -14,10 +14,10 @@ type DelegationSignerRecord struct { UpdatedAt string `json:"updated_at,omitempty"` } -func delegationSignerRecordPath(accountID string, domainIdentifier string, dsRecordID int) (path string) { +func delegationSignerRecordPath(accountID string, domainIdentifier string, dsRecordID int64) (path string) { path = fmt.Sprintf("%v/ds_records", domainPath(accountID, domainIdentifier)) if dsRecordID != 0 { - path += fmt.Sprintf("/%d", dsRecordID) + path += fmt.Sprintf("/%v", dsRecordID) } return } @@ -74,7 +74,7 @@ func (s *DomainsService) CreateDelegationSignerRecord(accountID string, domainId // GetDelegationSignerRecord fetches a delegation signer record. // // See https://developer.dnsimple.com/v2/domains/dnssec/#ds-record-get -func (s *DomainsService) GetDelegationSignerRecord(accountID string, domainIdentifier string, dsRecordID int) (*delegationSignerRecordResponse, error) { +func (s *DomainsService) GetDelegationSignerRecord(accountID string, domainIdentifier string, dsRecordID int64) (*delegationSignerRecordResponse, error) { path := versioned(delegationSignerRecordPath(accountID, domainIdentifier, dsRecordID)) dsRecordResponse := &delegationSignerRecordResponse{} @@ -91,7 +91,7 @@ func (s *DomainsService) GetDelegationSignerRecord(accountID string, domainIdent // from the domain. // // See https://developer.dnsimple.com/v2/domains/dnssec/#ds-record-delete -func (s *DomainsService) DeleteDelegationSignerRecord(accountID string, domainIdentifier string, dsRecordID int) (*delegationSignerRecordResponse, error) { +func (s *DomainsService) DeleteDelegationSignerRecord(accountID string, domainIdentifier string, dsRecordID int64) (*delegationSignerRecordResponse, error) { path := versioned(delegationSignerRecordPath(accountID, domainIdentifier, dsRecordID)) dsRecordResponse := &delegationSignerRecordResponse{} diff --git a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_dnssec.go b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_dnssec.go index d8d1cf06c..1ef054bcc 100644 --- a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_dnssec.go +++ b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_dnssec.go @@ -1,6 +1,8 @@ package dnsimple -import "fmt" +import ( + "fmt" +) // Dnssec represents the current DNSSEC settings for a domain in DNSimple. type Dnssec struct { diff --git a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_email_forwards.go b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_email_forwards.go index 599f86b08..3d4299ac5 100644 --- a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_email_forwards.go +++ b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_email_forwards.go @@ -6,18 +6,18 @@ import ( // EmailForward represents an email forward in DNSimple. type EmailForward struct { - ID int `json:"id,omitempty"` - DomainID int `json:"domain_id,omitempty"` + ID int64 `json:"id,omitempty"` + DomainID int64 `json:"domain_id,omitempty"` From string `json:"from,omitempty"` To string `json:"to,omitempty"` CreatedAt string `json:"created_at,omitempty"` UpdatedAt string `json:"updated_at,omitempty"` } -func emailForwardPath(accountID string, domainIdentifier string, forwardID int) (path string) { +func emailForwardPath(accountID string, domainIdentifier string, forwardID int64) (path string) { path = fmt.Sprintf("%v/email_forwards", domainPath(accountID, domainIdentifier)) if forwardID != 0 { - path += fmt.Sprintf("/%d", forwardID) + path += fmt.Sprintf("/%v", forwardID) } return } @@ -38,7 +38,7 @@ type emailForwardsResponse struct { // // See https://developer.dnsimple.com/v2/domains/email-forwards/#list func (s *DomainsService) ListEmailForwards(accountID string, domainIdentifier string, options *ListOptions) (*emailForwardsResponse, error) { - path := versioned(emailForwardPath(accountID, domainIdentifier , 0)) + path := versioned(emailForwardPath(accountID, domainIdentifier, 0)) forwardsResponse := &emailForwardsResponse{} path, err := addURLQueryOptions(path, options) @@ -74,7 +74,7 @@ func (s *DomainsService) CreateEmailForward(accountID string, domainIdentifier s // GetEmailForward fetches an email forward. // // See https://developer.dnsimple.com/v2/domains/email-forwards/#get -func (s *DomainsService) GetEmailForward(accountID string, domainIdentifier string, forwardID int) (*emailForwardResponse, error) { +func (s *DomainsService) GetEmailForward(accountID string, domainIdentifier string, forwardID int64) (*emailForwardResponse, error) { path := versioned(emailForwardPath(accountID, domainIdentifier, forwardID)) forwardResponse := &emailForwardResponse{} @@ -90,7 +90,7 @@ func (s *DomainsService) GetEmailForward(accountID string, domainIdentifier stri // DeleteEmailForward PERMANENTLY deletes an email forward from the domain. // // See https://developer.dnsimple.com/v2/domains/email-forwards/#delete -func (s *DomainsService) DeleteEmailForward(accountID string, domainIdentifier string, forwardID int) (*emailForwardResponse, error) { +func (s *DomainsService) DeleteEmailForward(accountID string, domainIdentifier string, forwardID int64) (*emailForwardResponse, error) { path := versioned(emailForwardPath(accountID, domainIdentifier, forwardID)) forwardResponse := &emailForwardResponse{} diff --git a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_pushes.go b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_pushes.go index 93c75be36..b965c7daf 100644 --- a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_pushes.go +++ b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_pushes.go @@ -6,19 +6,19 @@ import ( // DomainPush represents a domain push in DNSimple. type DomainPush struct { - ID int `json:"id,omitempty"` - DomainID int `json:"domain_id,omitempty"` - ContactID int `json:"contact_id,omitempty"` - AccountID int `json:"account_id,omitempty"` + ID int64 `json:"id,omitempty"` + DomainID int64 `json:"domain_id,omitempty"` + ContactID int64 `json:"contact_id,omitempty"` + AccountID int64 `json:"account_id,omitempty"` CreatedAt string `json:"created_at,omitempty"` UpdatedAt string `json:"updated_at,omitempty"` AcceptedAt string `json:"accepted_at,omitempty"` } -func domainPushPath(accountID string, pushID int) (path string) { +func domainPushPath(accountID string, pushID int64) (path string) { path = fmt.Sprintf("/%v/pushes", accountID) if pushID != 0 { - path += fmt.Sprintf("/%d", pushID) + path += fmt.Sprintf("/%v", pushID) } return } @@ -38,13 +38,13 @@ type domainPushesResponse struct { // DomainPushAttributes represent a domain push payload (see initiate). type DomainPushAttributes struct { NewAccountEmail string `json:"new_account_email,omitempty"` - ContactID string `json:"contact_id,omitempty"` + ContactID int64 `json:"contact_id,omitempty"` } // InitiatePush initiate a new domain push. // // See https://developer.dnsimple.com/v2/domains/pushes/#initiate -func (s *DomainsService) InitiatePush(accountID string, domainID string, pushAttributes DomainPushAttributes) (*domainPushResponse, error) { +func (s *DomainsService) InitiatePush(accountID, domainID string, pushAttributes DomainPushAttributes) (*domainPushResponse, error) { path := versioned(fmt.Sprintf("/%v/pushes", domainPath(accountID, domainID))) pushResponse := &domainPushResponse{} @@ -81,7 +81,7 @@ func (s *DomainsService) ListPushes(accountID string, options *ListOptions) (*do // AcceptPush accept a push for a domain. // // See https://developer.dnsimple.com/v2/domains/pushes/#accept -func (s *DomainsService) AcceptPush(accountID string, pushID int, pushAttributes DomainPushAttributes) (*domainPushResponse, error) { +func (s *DomainsService) AcceptPush(accountID string, pushID int64, pushAttributes DomainPushAttributes) (*domainPushResponse, error) { path := versioned(domainPushPath(accountID, pushID)) pushResponse := &domainPushResponse{} @@ -97,7 +97,7 @@ func (s *DomainsService) AcceptPush(accountID string, pushID int, pushAttributes // RejectPush reject a push for a domain. // // See https://developer.dnsimple.com/v2/domains/pushes/#reject -func (s *DomainsService) RejectPush(accountID string, pushID int) (*domainPushResponse, error) { +func (s *DomainsService) RejectPush(accountID string, pushID int64) (*domainPushResponse, error) { path := versioned(domainPushPath(accountID, pushID)) pushResponse := &domainPushResponse{} diff --git a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/registrar.go b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/registrar.go index e89b1a429..625bbb2dc 100644 --- a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/registrar.go +++ b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/registrar.go @@ -28,7 +28,7 @@ type domainCheckResponse struct { // CheckDomain checks a domain name. // // See https://developer.dnsimple.com/v2/registrar/#check -func (s *RegistrarService) CheckDomain(accountID, domainName string) (*domainCheckResponse, error) { +func (s *RegistrarService) CheckDomain(accountID string, domainName string) (*domainCheckResponse, error) { path := versioned(fmt.Sprintf("/%v/registrar/domains/%v/check", accountID, domainName)) checkResponse := &domainCheckResponse{} @@ -70,7 +70,7 @@ type DomainPremiumPriceOptions struct { // - renewal // // See https://developer.dnsimple.com/v2/registrar/#premium-price -func (s *RegistrarService) GetDomainPremiumPrice(accountID, domainName string, options *DomainPremiumPriceOptions) (*domainPremiumPriceResponse, error) { +func (s *RegistrarService) GetDomainPremiumPrice(accountID string, domainName string, options *DomainPremiumPriceOptions) (*domainPremiumPriceResponse, error) { var err error path := versioned(fmt.Sprintf("/%v/registrar/domains/%v/premium_price", accountID, domainName)) priceResponse := &domainPremiumPriceResponse{} @@ -100,7 +100,6 @@ type DomainRegistration struct { State string `json:"state"` AutoRenew bool `json:"auto_renew"` WhoisPrivacy bool `json:"whois_privacy"` - PremiumPrice string `json:"premium_price"` CreatedAt string `json:"created_at,omitempty"` UpdatedAt string `json:"updated_at,omitempty"` } @@ -122,6 +121,8 @@ type DomainRegisterRequest struct { // Set to true to enable the auto-renewal of the domain. // Default to true. EnableAutoRenewal bool `json:"auto_renew,omitempty"` + // Required as confirmation of the price, only if the domain is premium. + PremiumPrice string `json:"premium_price,omitempty"` } // RegisterDomain registers a domain name. @@ -150,7 +151,6 @@ type DomainTransfer struct { State string `json:"state"` AutoRenew bool `json:"auto_renew"` WhoisPrivacy bool `json:"whois_privacy"` - PremiumPrice string `json:"premium_price"` CreatedAt string `json:"created_at,omitempty"` UpdatedAt string `json:"updated_at,omitempty"` } @@ -175,6 +175,8 @@ type DomainTransferRequest struct { // Set to true to enable the auto-renewal of the domain. // Default to true. EnableAutoRenewal bool `json:"auto_renew,omitempty"` + // Required as confirmation of the price, only if the domain is premium. + PremiumPrice string `json:"premium_price,omitempty"` } // TransferDomain transfers a domain name. @@ -219,13 +221,12 @@ func (s *RegistrarService) TransferDomainOut(accountID string, domainName string // DomainRenewal represents the result of a domain renewal call. type DomainRenewal struct { - ID int `json:"id"` - DomainID int `json:"domain_id"` - Period int `json:"period"` - State string `json:"state"` - PremiumPrice string `json:"premium_price"` - CreatedAt string `json:"created_at,omitempty"` - UpdatedAt string `json:"updated_at,omitempty"` + ID int `json:"id"` + DomainID int `json:"domain_id"` + Period int `json:"period"` + State string `json:"state"` + CreatedAt string `json:"created_at,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` } // domainRenewalResponse represents a response from an API method that returns a domain renewal. @@ -239,6 +240,8 @@ type domainRenewalResponse struct { type DomainRenewRequest struct { // The number of years Period int `json:"period"` + // Required as confirmation of the price, only if the domain is premium. + PremiumPrice string `json:"premium_price,omitempty"` } // RenewDomain renews a domain name. diff --git a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/registrar_whois_privacy.go b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/registrar_whois_privacy.go index 9e0e96931..edf47b12a 100644 --- a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/registrar_whois_privacy.go +++ b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/registrar_whois_privacy.go @@ -6,8 +6,8 @@ import ( // WhoisPrivacy represents a whois privacy in DNSimple. type WhoisPrivacy struct { - ID int `json:"id,omitempty"` - DomainID int `json:"domain_id,omitempty"` + ID int64 `json:"id,omitempty"` + DomainID int64 `json:"domain_id,omitempty"` Enabled bool `json:"enabled,omitempty"` ExpiresOn string `json:"expires_on,omitempty"` CreatedAt string `json:"created_at,omitempty"` diff --git a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/services.go b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/services.go index 1d63d05c5..18afd1185 100644 --- a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/services.go +++ b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/services.go @@ -14,7 +14,7 @@ type ServicesService struct { // Service represents a Service in DNSimple. type Service struct { - ID int `json:"id,omitempty"` + ID int64 `json:"id,omitempty"` SID string `json:"sid,omitempty"` Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` @@ -36,10 +36,10 @@ type ServiceSetting struct { Password bool `json:"password,omitempty"` } -func servicePath(serviceID string) (path string) { +func servicePath(serviceIdentifier string) (path string) { path = "/services" - if serviceID != "" { - path += fmt.Sprintf("/%v", serviceID) + if serviceIdentifier != "" { + path += fmt.Sprintf("/%v", serviceIdentifier) } return } diff --git a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/services_domains.go b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/services_domains.go index f9889e5a3..0426dd620 100644 --- a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/services_domains.go +++ b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/services_domains.go @@ -4,11 +4,11 @@ import ( "fmt" ) -func domainServicesPath(accountID string, domainID string, serviceIdentifier string) string { +func domainServicesPath(accountID string, domainIdentifier string, serviceIdentifier string) string { if serviceIdentifier != "" { - return fmt.Sprintf("/%v/domains/%v/services/%v", accountID, domainID, serviceIdentifier) + return fmt.Sprintf("/%v/domains/%v/services/%v", accountID, domainIdentifier, serviceIdentifier) } - return fmt.Sprintf("/%v/domains/%v/services", accountID, domainID) + return fmt.Sprintf("/%v/domains/%v/services", accountID, domainIdentifier) } // DomainServiceSettings represents optional settings when applying a DNSimple one-click service to a domain. @@ -19,8 +19,8 @@ type DomainServiceSettings struct { // AppliedServices lists the applied one-click services for a domain. // // See https://developer.dnsimple.com/v2/services/domains/#applied -func (s *ServicesService) AppliedServices(accountID string, domainID string, options *ListOptions) (*servicesResponse, error) { - path := versioned(domainServicesPath(accountID, domainID, "")) +func (s *ServicesService) AppliedServices(accountID string, domainIdentifier string, options *ListOptions) (*servicesResponse, error) { + path := versioned(domainServicesPath(accountID, domainIdentifier, "")) servicesResponse := &servicesResponse{} path, err := addURLQueryOptions(path, options) @@ -40,8 +40,8 @@ func (s *ServicesService) AppliedServices(accountID string, domainID string, opt // ApplyService applies a one-click services to a domain. // // See https://developer.dnsimple.com/v2/services/domains/#apply -func (s *ServicesService) ApplyService(accountID string, serviceIdentifier string, domainID string, settings DomainServiceSettings) (*serviceResponse, error) { - path := versioned(domainServicesPath(accountID, domainID, serviceIdentifier)) +func (s *ServicesService) ApplyService(accountID string, serviceIdentifier string, domainIdentifier string, settings DomainServiceSettings) (*serviceResponse, error) { + path := versioned(domainServicesPath(accountID, domainIdentifier, serviceIdentifier)) serviceResponse := &serviceResponse{} resp, err := s.client.post(path, settings, nil) @@ -56,8 +56,8 @@ func (s *ServicesService) ApplyService(accountID string, serviceIdentifier strin // UnapplyService unapplies a one-click services from a domain. // // See https://developer.dnsimple.com/v2/services/domains/#unapply -func (s *ServicesService) UnapplyService(accountID string, serviceIdentifier string, domainID string) (*serviceResponse, error) { - path := versioned(domainServicesPath(accountID, domainID, serviceIdentifier)) +func (s *ServicesService) UnapplyService(accountID string, serviceIdentifier string, domainIdentifier string) (*serviceResponse, error) { + path := versioned(domainServicesPath(accountID, domainIdentifier, serviceIdentifier)) serviceResponse := &serviceResponse{} resp, err := s.client.delete(path, nil, nil) diff --git a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/templates.go b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/templates.go index 2b2503def..0cd2ec859 100644 --- a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/templates.go +++ b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/templates.go @@ -14,9 +14,9 @@ type TemplatesService struct { // Template represents a Template in DNSimple. type Template struct { - ID int `json:"id,omitempty"` + ID int64 `json:"id,omitempty"` SID string `json:"sid,omitempty"` - AccountID int `json:"account_id,omitempty"` + AccountID int64 `json:"account_id,omitempty"` Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` CreatedAt string `json:"created_at,omitempty"` diff --git a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/templates_domains.go b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/templates_domains.go index 85ca6678e..01901bb08 100644 --- a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/templates_domains.go +++ b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/templates_domains.go @@ -7,8 +7,8 @@ import ( // ApplyTemplate applies a template to the given domain. // // See https://developer.dnsimple.com/v2/templates/domains/#apply -func (s *TemplatesService) ApplyTemplate(accountID string, templateIdentifier string, domainID string) (*templateResponse, error) { - path := versioned(fmt.Sprintf("%v/templates/%v", domainPath(accountID, domainID), templateIdentifier)) +func (s *TemplatesService) ApplyTemplate(accountID string, templateIdentifier string, domainIdentifier string) (*templateResponse, error) { + path := versioned(fmt.Sprintf("%v/templates/%v", domainPath(accountID, domainIdentifier), templateIdentifier)) templateResponse := &templateResponse{} resp, err := s.client.post(path, nil, nil) diff --git a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/templates_records.go b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/templates_records.go index e9e717da7..74f6fd65a 100644 --- a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/templates_records.go +++ b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/templates_records.go @@ -6,8 +6,8 @@ import ( // TemplateRecord represents a DNS record for a template in DNSimple. type TemplateRecord struct { - ID int `json:"id,omitempty"` - TemplateID int `json:"template_id,omitempty"` + ID int64 `json:"id,omitempty"` + TemplateID int64 `json:"template_id,omitempty"` Name string `json:"name"` Content string `json:"content,omitempty"` TTL int `json:"ttl,omitempty"` @@ -17,8 +17,8 @@ type TemplateRecord struct { UpdatedAt string `json:"updated_at,omitempty"` } -func templateRecordPath(accountID string, templateIdentifier string, templateRecordID string) string { - if templateRecordID != "" { +func templateRecordPath(accountID string, templateIdentifier string, templateRecordID int64) string { + if templateRecordID != 0 { return fmt.Sprintf("%v/records/%v", templatePath(accountID, templateIdentifier), templateRecordID) } @@ -41,7 +41,7 @@ type templateRecordsResponse struct { // // See https://developer.dnsimple.com/v2/templates/records/#list func (s *TemplatesService) ListTemplateRecords(accountID string, templateIdentifier string, options *ListOptions) (*templateRecordsResponse, error) { - path := versioned(templateRecordPath(accountID, templateIdentifier, "")) + path := versioned(templateRecordPath(accountID, templateIdentifier, 0)) templateRecordsResponse := &templateRecordsResponse{} path, err := addURLQueryOptions(path, options) @@ -62,7 +62,7 @@ func (s *TemplatesService) ListTemplateRecords(accountID string, templateIdentif // // See https://developer.dnsimple.com/v2/templates/records/#create func (s *TemplatesService) CreateTemplateRecord(accountID string, templateIdentifier string, templateRecordAttributes TemplateRecord) (*templateRecordResponse, error) { - path := versioned(templateRecordPath(accountID, templateIdentifier, "")) + path := versioned(templateRecordPath(accountID, templateIdentifier, 0)) templateRecordResponse := &templateRecordResponse{} resp, err := s.client.post(path, templateRecordAttributes, templateRecordResponse) @@ -77,7 +77,7 @@ func (s *TemplatesService) CreateTemplateRecord(accountID string, templateIdenti // GetTemplateRecord fetches a template record. // // See https://developer.dnsimple.com/v2/templates/records/#get -func (s *TemplatesService) GetTemplateRecord(accountID string, templateIdentifier string, templateRecordID string) (*templateRecordResponse, error) { +func (s *TemplatesService) GetTemplateRecord(accountID string, templateIdentifier string, templateRecordID int64) (*templateRecordResponse, error) { path := versioned(templateRecordPath(accountID, templateIdentifier, templateRecordID)) templateRecordResponse := &templateRecordResponse{} @@ -93,7 +93,7 @@ func (s *TemplatesService) GetTemplateRecord(accountID string, templateIdentifie // DeleteTemplateRecord deletes a template record. // // See https://developer.dnsimple.com/v2/templates/records/#delete -func (s *TemplatesService) DeleteTemplateRecord(accountID string, templateIdentifier string, templateRecordID string) (*templateRecordResponse, error) { +func (s *TemplatesService) DeleteTemplateRecord(accountID string, templateIdentifier string, templateRecordID int64) (*templateRecordResponse, error) { path := versioned(templateRecordPath(accountID, templateIdentifier, templateRecordID)) templateRecordResponse := &templateRecordResponse{} diff --git a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/users.go b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/users.go index 90cd25b90..9c33f4083 100644 --- a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/users.go +++ b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/users.go @@ -2,6 +2,6 @@ package dnsimple // User represents a DNSimple user. type User struct { - ID int `json:"id,omitempty"` + ID int64 `json:"id,omitempty"` Email string `json:"email,omitempty"` } diff --git a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/vanity_name_server.go b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/vanity_name_server.go index f5d786cbb..cd1314a4c 100644 --- a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/vanity_name_server.go +++ b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/vanity_name_server.go @@ -14,7 +14,7 @@ type VanityNameServersService struct { // VanityNameServer represents data for a single vanity name server type VanityNameServer struct { - ID int `json:"id,omitempty"` + ID int64 `json:"id,omitempty"` Name string `json:"name,omitempty"` IPv4 string `json:"ipv4,omitempty"` IPv6 string `json:"ipv6,omitempty"` @@ -22,8 +22,8 @@ type VanityNameServer struct { UpdatedAt string `json:"updated_at,omitempty"` } -func vanityNameServerPath(accountID string, domainID string) string { - return fmt.Sprintf("/%v/vanity/%v", accountID, domainID) +func vanityNameServerPath(accountID string, domainIdentifier string) string { + return fmt.Sprintf("/%v/vanity/%v", accountID, domainIdentifier) } // vanityNameServerResponse represents a response for vanity name server enable and disable operations. @@ -35,8 +35,8 @@ type vanityNameServerResponse struct { // EnableVanityNameServers Vanity Name Servers for the given domain // // See https://developer.dnsimple.com/v2/vanity/#enable -func (s *VanityNameServersService) EnableVanityNameServers(accountID string, domainID string) (*vanityNameServerResponse, error) { - path := versioned(vanityNameServerPath(accountID, domainID)) +func (s *VanityNameServersService) EnableVanityNameServers(accountID string, domainIdentifier string) (*vanityNameServerResponse, error) { + path := versioned(vanityNameServerPath(accountID, domainIdentifier)) vanityNameServerResponse := &vanityNameServerResponse{} resp, err := s.client.put(path, nil, vanityNameServerResponse) @@ -51,8 +51,8 @@ func (s *VanityNameServersService) EnableVanityNameServers(accountID string, dom // DisableVanityNameServers Vanity Name Servers for the given domain // // See https://developer.dnsimple.com/v2/vanity/#disable -func (s *VanityNameServersService) DisableVanityNameServers(accountID string, domainID string) (*vanityNameServerResponse, error) { - path := versioned(vanityNameServerPath(accountID, domainID)) +func (s *VanityNameServersService) DisableVanityNameServers(accountID string, domainIdentifier string) (*vanityNameServerResponse, error) { + path := versioned(vanityNameServerPath(accountID, domainIdentifier)) vanityNameServerResponse := &vanityNameServerResponse{} resp, err := s.client.delete(path, nil, nil) diff --git a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/webhooks.go b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/webhooks.go index a5614944b..e6ac4194d 100644 --- a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/webhooks.go +++ b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/webhooks.go @@ -14,11 +14,11 @@ type WebhooksService struct { // Webhook represents a DNSimple webhook. type Webhook struct { - ID int `json:"id,omitempty"` + ID int64 `json:"id,omitempty"` URL string `json:"url,omitempty"` } -func webhookPath(accountID string, webhookID int) (path string) { +func webhookPath(accountID string, webhookID int64) (path string) { path = fmt.Sprintf("/%v/webhooks", accountID) if webhookID != 0 { path = fmt.Sprintf("%v/%v", path, webhookID) @@ -73,7 +73,7 @@ func (s *WebhooksService) CreateWebhook(accountID string, webhookAttributes Webh // GetWebhook fetches a webhook. // // See https://developer.dnsimple.com/v2/webhooks#get -func (s *WebhooksService) GetWebhook(accountID string, webhookID int) (*webhookResponse, error) { +func (s *WebhooksService) GetWebhook(accountID string, webhookID int64) (*webhookResponse, error) { path := versioned(webhookPath(accountID, webhookID)) webhookResponse := &webhookResponse{} @@ -89,7 +89,7 @@ func (s *WebhooksService) GetWebhook(accountID string, webhookID int) (*webhookR // DeleteWebhook PERMANENTLY deletes a webhook from the account. // // See https://developer.dnsimple.com/v2/webhooks#delete -func (s *WebhooksService) DeleteWebhook(accountID string, webhookID int) (*webhookResponse, error) { +func (s *WebhooksService) DeleteWebhook(accountID string, webhookID int64) (*webhookResponse, error) { path := versioned(webhookPath(accountID, webhookID)) webhookResponse := &webhookResponse{} diff --git a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/zones.go b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/zones.go index 80652cb99..a5f67d953 100644 --- a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/zones.go +++ b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/zones.go @@ -14,8 +14,8 @@ type ZonesService struct { // Zone represents a Zone in DNSimple. type Zone struct { - ID int `json:"id,omitempty"` - AccountID int `json:"account_id,omitempty"` + ID int64 `json:"id,omitempty"` + AccountID int64 `json:"account_id,omitempty"` Name string `json:"name,omitempty"` Reverse bool `json:"reverse,omitempty"` CreatedAt string `json:"created_at,omitempty"` diff --git a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/zones_records.go b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/zones_records.go index 99d099277..cd017e487 100644 --- a/vendor/github.com/dnsimple/dnsimple-go/dnsimple/zones_records.go +++ b/vendor/github.com/dnsimple/dnsimple-go/dnsimple/zones_records.go @@ -6,9 +6,9 @@ import ( // ZoneRecord represents a DNS record in DNSimple. type ZoneRecord struct { - ID int `json:"id,omitempty"` + ID int64 `json:"id,omitempty"` ZoneID string `json:"zone_id,omitempty"` - ParentID int `json:"parent_id,omitempty"` + ParentID int64 `json:"parent_id,omitempty"` Type string `json:"type,omitempty"` Name string `json:"name"` Content string `json:"content,omitempty"` @@ -20,10 +20,10 @@ type ZoneRecord struct { UpdatedAt string `json:"updated_at,omitempty"` } -func zoneRecordPath(accountID string, zoneID string, recordID int) (path string) { - path = fmt.Sprintf("/%v/zones/%v/records", accountID, zoneID) +func zoneRecordPath(accountID string, zoneName string, recordID int64) (path string) { + path = fmt.Sprintf("/%v/zones/%v/records", accountID, zoneName) if recordID != 0 { - path += fmt.Sprintf("/%d", recordID) + path += fmt.Sprintf("/%v", recordID) } return } @@ -51,16 +51,16 @@ type ZoneRecordListOptions struct { // Select records of given type. // Eg. TXT, A, NS. - Type string `url:"record_type,omitempty"` + Type string `url:"type,omitempty"` ListOptions } // ListRecords lists the zone records for a zone. // -// See https://developer.dnsimple.com/v2/zones/#list -func (s *ZonesService) ListRecords(accountID string, zoneID string, options *ZoneRecordListOptions) (*zoneRecordsResponse, error) { - path := versioned(zoneRecordPath(accountID, zoneID, 0)) +// See https://developer.dnsimple.com/v2/zones/records/#listZoneRecords +func (s *ZonesService) ListRecords(accountID string, zoneName string, options *ZoneRecordListOptions) (*zoneRecordsResponse, error) { + path := versioned(zoneRecordPath(accountID, zoneName, 0)) recordsResponse := &zoneRecordsResponse{} path, err := addURLQueryOptions(path, options) @@ -79,9 +79,9 @@ func (s *ZonesService) ListRecords(accountID string, zoneID string, options *Zon // CreateRecord creates a zone record. // -// See https://developer.dnsimple.com/v2/zones/#create -func (s *ZonesService) CreateRecord(accountID string, zoneID string, recordAttributes ZoneRecord) (*zoneRecordResponse, error) { - path := versioned(zoneRecordPath(accountID, zoneID, 0)) +// See https://developer.dnsimple.com/v2/zones/records/#createZoneRecord +func (s *ZonesService) CreateRecord(accountID string, zoneName string, recordAttributes ZoneRecord) (*zoneRecordResponse, error) { + path := versioned(zoneRecordPath(accountID, zoneName, 0)) recordResponse := &zoneRecordResponse{} resp, err := s.client.post(path, recordAttributes, recordResponse) @@ -95,9 +95,9 @@ func (s *ZonesService) CreateRecord(accountID string, zoneID string, recordAttri // GetRecord fetches a zone record. // -// See https://developer.dnsimple.com/v2/zones/#get -func (s *ZonesService) GetRecord(accountID string, zoneID string, recordID int) (*zoneRecordResponse, error) { - path := versioned(zoneRecordPath(accountID, zoneID, recordID)) +// See https://developer.dnsimple.com/v2/zones/records/#getZoneRecord +func (s *ZonesService) GetRecord(accountID string, zoneName string, recordID int64) (*zoneRecordResponse, error) { + path := versioned(zoneRecordPath(accountID, zoneName, recordID)) recordResponse := &zoneRecordResponse{} resp, err := s.client.get(path, recordResponse) @@ -111,9 +111,9 @@ func (s *ZonesService) GetRecord(accountID string, zoneID string, recordID int) // UpdateRecord updates a zone record. // -// See https://developer.dnsimple.com/v2/zones/#update -func (s *ZonesService) UpdateRecord(accountID string, zoneID string, recordID int, recordAttributes ZoneRecord) (*zoneRecordResponse, error) { - path := versioned(zoneRecordPath(accountID, zoneID, recordID)) +// See https://developer.dnsimple.com/v2/zones/records/#updateZoneRecord +func (s *ZonesService) UpdateRecord(accountID string, zoneName string, recordID int64, recordAttributes ZoneRecord) (*zoneRecordResponse, error) { + path := versioned(zoneRecordPath(accountID, zoneName, recordID)) recordResponse := &zoneRecordResponse{} resp, err := s.client.patch(path, recordAttributes, recordResponse) @@ -127,9 +127,9 @@ func (s *ZonesService) UpdateRecord(accountID string, zoneID string, recordID in // DeleteRecord PERMANENTLY deletes a zone record from the zone. // -// See https://developer.dnsimple.com/v2/zones/#delete -func (s *ZonesService) DeleteRecord(accountID string, zoneID string, recordID int) (*zoneRecordResponse, error) { - path := versioned(zoneRecordPath(accountID, zoneID, recordID)) +// See https://developer.dnsimple.com/v2/zones/records/#deleteZoneRecord +func (s *ZonesService) DeleteRecord(accountID string, zoneName string, recordID int64) (*zoneRecordResponse, error) { + path := versioned(zoneRecordPath(accountID, zoneName, recordID)) recordResponse := &zoneRecordResponse{} resp, err := s.client.delete(path, nil, nil) diff --git a/vendor/github.com/ovh/go-ovh/ovh/configuration.go b/vendor/github.com/ovh/go-ovh/ovh/configuration.go index e7dbb7c69..4cc624dd3 100644 --- a/vendor/github.com/ovh/go-ovh/ovh/configuration.go +++ b/vendor/github.com/ovh/go-ovh/ovh/configuration.go @@ -19,11 +19,18 @@ var ( // currentUserHome attempts to get current user's home directory func currentUserHome() (string, error) { + userHome := "" usr, err := user.Current() if err != nil { - return "", err + // Fallback by trying to read $HOME + userHome = os.Getenv("HOME") + if userHome != "" { + err = nil + } + } else { + userHome = usr.HomeDir } - return usr.HomeDir, nil + return userHome, nil } // appendConfigurationFile only if it exists. We need to do this because @@ -88,13 +95,13 @@ func (c *Client) loadConfig(endpointName string) error { // If we still have no valid endpoint, AppKey or AppSecret, return an error if c.endpoint == "" { - return fmt.Errorf("Unknown endpoint '%s'. Consider checking 'Endpoints' list of using an URL.", endpointName) + return fmt.Errorf("unknown endpoint '%s', consider checking 'Endpoints' list of using an URL", endpointName) } if c.AppKey == "" { - return fmt.Errorf("Missing application key. Please check your configuration or consult the documentation to create one.") + return fmt.Errorf("missing application key, please check your configuration or consult the documentation to create one") } if c.AppSecret == "" { - return fmt.Errorf("Missing application secret. Please check your configuration or consult the documentation to create one.") + return fmt.Errorf("missing application secret, please check your configuration or consult the documentation to create one") } return nil diff --git a/vendor/github.com/ovh/go-ovh/ovh/ovh.go b/vendor/github.com/ovh/go-ovh/ovh/ovh.go index c5f7f7265..a24e3689c 100644 --- a/vendor/github.com/ovh/go-ovh/ovh/ovh.go +++ b/vendor/github.com/ovh/go-ovh/ovh/ovh.go @@ -21,6 +21,7 @@ const DefaultTimeout = 180 * time.Second const ( OvhEU = "https://eu.api.ovh.com/1.0" OvhCA = "https://ca.api.ovh.com/1.0" + OvhUS = "https://api.ovh.us/1.0" KimsufiEU = "https://eu.api.kimsufi.com/1.0" KimsufiCA = "https://ca.api.kimsufi.com/1.0" SoyoustartEU = "https://eu.api.soyoustart.com/1.0" @@ -32,6 +33,7 @@ const ( var Endpoints = map[string]string{ "ovh-eu": OvhEU, "ovh-ca": OvhCA, + "ovh-us": OvhUS, "kimsufi-eu": KimsufiEU, "kimsufi-ca": KimsufiCA, "soyoustart-eu": SoyoustartEU, @@ -170,39 +172,6 @@ func (c *Client) DeleteUnAuth(url string, resType interface{}) error { return c.CallAPI("DELETE", url, nil, resType, false) } -// -// Low level API access -// - -// getResult check the response and unmarshals it into the response type if needed. -// Helper function, called from CallAPI. -func (c *Client) getResponse(response *http.Response, resType interface{}) error { - // Read all the response body - defer response.Body.Close() - body, err := ioutil.ReadAll(response.Body) - if err != nil { - return err - } - - // < 200 && >= 300 : API error - if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices { - apiError := &APIError{Code: response.StatusCode} - if err = json.Unmarshal(body, apiError); err != nil { - apiError.Message = string(body) - } - apiError.QueryID = response.Header.Get("X-Ovh-QueryID") - - return apiError - } - - // Nothing to unmarshal - if len(body) == 0 || resType == nil { - return nil - } - - return json.Unmarshal(body, &resType) -} - // timeDelta returns the time delta between the host and the remote API func (c *Client) getTimeDelta() (time.Duration, error) { @@ -210,6 +179,9 @@ func (c *Client) getTimeDelta() (time.Duration, error) { // Ensure only one thread is updating c.timeDeltaMutex.Lock() + // Ensure that the mutex will be released on return + defer c.timeDeltaMutex.Unlock() + // Did we wait ? Maybe no more needed if !c.timeDeltaDone { ovhTime, err := c.getTime() @@ -220,7 +192,6 @@ func (c *Client) getTimeDelta() (time.Duration, error) { c.timeDelta = time.Since(*ovhTime) c.timeDeltaDone = true } - c.timeDeltaMutex.Unlock() } return c.timeDelta, nil @@ -251,39 +222,22 @@ var getEndpointForSignature = func(c *Client) string { return c.endpoint } -// CallAPI is the lowest level call helper. If needAuth is true, -// inject authentication headers and sign the request. -// -// Request signature is a sha1 hash on following fields, joined by '+': -// - applicationSecret (from Client instance) -// - consumerKey (from Client instance) -// - capitalized method (from arguments) -// - full request url, including any query string argument -// - full serialized request body -// - server current time (takes time delta into account) -// -// Call will automatically assemble the target url from the endpoint -// configured in the client instance and the path argument. If the reqBody -// argument is not nil, it will also serialize it as json and inject -// the required Content-Type header. -// -// If everyrthing went fine, unmarshall response into resType and return nil -// otherwise, return the error -func (c *Client) CallAPI(method, path string, reqBody, resType interface{}, needAuth bool) error { +// NewRequest returns a new HTTP request +func (c *Client) NewRequest(method, path string, reqBody interface{}, needAuth bool) (*http.Request, error) { var body []byte var err error if reqBody != nil { body, err = json.Marshal(reqBody) if err != nil { - return err + return nil, err } } target := fmt.Sprintf("%s%s", c.endpoint, path) req, err := http.NewRequest(method, target, bytes.NewReader(body)) if err != nil { - return err + return nil, err } // Inject headers @@ -298,7 +252,7 @@ func (c *Client) CallAPI(method, path string, reqBody, resType interface{}, need if needAuth { timeDelta, err := c.TimeDelta() if err != nil { - return err + return nil, err } timestamp := getLocalTime().Add(-timeDelta).Unix() @@ -321,12 +275,71 @@ func (c *Client) CallAPI(method, path string, reqBody, resType interface{}, need // Send the request with requested timeout c.Client.Timeout = c.Timeout - response, err := c.Client.Do(req) + return req, nil +} + +// Do sends an HTTP request and returns an HTTP response +func (c *Client) Do(req *http.Request) (*http.Response, error) { + return c.Client.Do(req) +} + +// CallAPI is the lowest level call helper. If needAuth is true, +// inject authentication headers and sign the request. +// +// Request signature is a sha1 hash on following fields, joined by '+': +// - applicationSecret (from Client instance) +// - consumerKey (from Client instance) +// - capitalized method (from arguments) +// - full request url, including any query string argument +// - full serialized request body +// - server current time (takes time delta into account) +// +// Call will automatically assemble the target url from the endpoint +// configured in the client instance and the path argument. If the reqBody +// argument is not nil, it will also serialize it as json and inject +// the required Content-Type header. +// +// If everything went fine, unmarshall response into resType and return nil +// otherwise, return the error +func (c *Client) CallAPI(method, path string, reqBody, resType interface{}, needAuth bool) error { + req, err := c.NewRequest(method, path, reqBody, needAuth) + if err != nil { + return err + } + response, err := c.Do(req) + if err != nil { + return err + } + return c.UnmarshalResponse(response, resType) + +} + +// UnmarshalResponse checks the response and unmarshals it into the response +// type if needed Helper function, called from CallAPI +func (c *Client) UnmarshalResponse(response *http.Response, resType interface{}) error { + // Read all the response body + defer response.Body.Close() + body, err := ioutil.ReadAll(response.Body) if err != nil { return err } - // Unmarshal the result into the resType if possible - return c.getResponse(response, resType) + // < 200 && >= 300 : API error + if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices { + apiError := &APIError{Code: response.StatusCode} + if err = json.Unmarshal(body, apiError); err != nil { + apiError.Message = string(body) + } + apiError.QueryID = response.Header.Get("X-Ovh-QueryID") + + return apiError + } + + // Nothing to unmarshal + if len(body) == 0 || resType == nil { + return nil + } + + return json.Unmarshal(body, &resType) } diff --git a/vendor/github.com/vulcand/oxy/buffer/buffer.go b/vendor/github.com/vulcand/oxy/buffer/buffer.go index 6224fb8c7..e3ee40b33 100644 --- a/vendor/github.com/vulcand/oxy/buffer/buffer.go +++ b/vendor/github.com/vulcand/oxy/buffer/buffer.go @@ -199,7 +199,7 @@ func (b *Buffer) ServeHTTP(w http.ResponseWriter, req *http.Request) { if log.GetLevel() >= log.DebugLevel { logEntry := log.WithField("Request", utils.DumpHttpRequest(req)) logEntry.Debug("vulcand/oxy/buffer: begin ServeHttp on request") - defer logEntry.Debug("vulcand/oxy/buffer: competed ServeHttp on request") + defer logEntry.Debug("vulcand/oxy/buffer: completed ServeHttp on request") } if err := b.checkLimit(req); err != nil { diff --git a/vendor/github.com/vulcand/oxy/cbreaker/cbreaker.go b/vendor/github.com/vulcand/oxy/cbreaker/cbreaker.go index e97f69d56..e7f92f71f 100644 --- a/vendor/github.com/vulcand/oxy/cbreaker/cbreaker.go +++ b/vendor/github.com/vulcand/oxy/cbreaker/cbreaker.go @@ -103,7 +103,7 @@ func (c *CircuitBreaker) ServeHTTP(w http.ResponseWriter, req *http.Request) { if log.GetLevel() >= log.DebugLevel { logEntry := log.WithField("Request", utils.DumpHttpRequest(req)) logEntry.Debug("vulcand/oxy/circuitbreaker: begin ServeHttp on request") - defer logEntry.Debug("vulcand/oxy/circuitbreaker: competed ServeHttp on request") + defer logEntry.Debug("vulcand/oxy/circuitbreaker: completed ServeHttp on request") } if c.activateFallback(w, req) { c.fallback.ServeHTTP(w, req) diff --git a/vendor/github.com/vulcand/oxy/cbreaker/fallback.go b/vendor/github.com/vulcand/oxy/cbreaker/fallback.go index 2924b994f..a4fed70af 100644 --- a/vendor/github.com/vulcand/oxy/cbreaker/fallback.go +++ b/vendor/github.com/vulcand/oxy/cbreaker/fallback.go @@ -31,7 +31,7 @@ func (f *ResponseFallback) ServeHTTP(w http.ResponseWriter, req *http.Request) { if log.GetLevel() >= log.DebugLevel { logEntry := log.WithField("Request", utils.DumpHttpRequest(req)) logEntry.Debug("vulcand/oxy/fallback/response: begin ServeHttp on request") - defer logEntry.Debug("vulcand/oxy/fallback/response: competed ServeHttp on request") + defer logEntry.Debug("vulcand/oxy/fallback/response: completed ServeHttp on request") } if f.r.ContentType != "" { @@ -67,7 +67,7 @@ func (f *RedirectFallback) ServeHTTP(w http.ResponseWriter, req *http.Request) { if log.GetLevel() >= log.DebugLevel { logEntry := log.WithField("Request", utils.DumpHttpRequest(req)) logEntry.Debug("vulcand/oxy/fallback/redirect: begin ServeHttp on request") - defer logEntry.Debug("vulcand/oxy/fallback/redirect: competed ServeHttp on request") + defer logEntry.Debug("vulcand/oxy/fallback/redirect: completed ServeHttp on request") } location := f.u.String() diff --git a/vendor/github.com/vulcand/oxy/connlimit/connlimit.go b/vendor/github.com/vulcand/oxy/connlimit/connlimit.go index 8abf1c173..c7b392758 100644 --- a/vendor/github.com/vulcand/oxy/connlimit/connlimit.go +++ b/vendor/github.com/vulcand/oxy/connlimit/connlimit.go @@ -110,7 +110,7 @@ func (e *ConnErrHandler) ServeHTTP(w http.ResponseWriter, req *http.Request, err if log.GetLevel() >= log.DebugLevel { logEntry := log.WithField("Request", utils.DumpHttpRequest(req)) logEntry.Debug("vulcand/oxy/connlimit: begin ServeHttp on request") - defer logEntry.Debug("vulcand/oxy/connlimit: competed ServeHttp on request") + defer logEntry.Debug("vulcand/oxy/connlimit: completed ServeHttp on request") } if _, ok := err.(*MaxConnError); ok { diff --git a/vendor/github.com/vulcand/oxy/forward/fwd.go b/vendor/github.com/vulcand/oxy/forward/fwd.go index cf8f6eaef..ec3c4f8d4 100644 --- a/vendor/github.com/vulcand/oxy/forward/fwd.go +++ b/vendor/github.com/vulcand/oxy/forward/fwd.go @@ -302,7 +302,7 @@ func (f *httpForwarder) serveWebSocket(w http.ResponseWriter, req *http.Request, if f.log.GetLevel() >= log.DebugLevel { logEntry := f.log.WithField("Request", utils.DumpHttpRequest(req)) logEntry.Debug("vulcand/oxy/forward/websocket: begin ServeHttp on request") - defer logEntry.Debug("vulcand/oxy/forward/websocket: competed ServeHttp on request") + defer logEntry.Debug("vulcand/oxy/forward/websocket: completed ServeHttp on request") } outReq := f.copyWebSocketRequest(req) @@ -351,6 +351,7 @@ func (f *httpForwarder) serveWebSocket(w http.ResponseWriter, req *http.Request, }} utils.RemoveHeaders(resp.Header, WebsocketUpgradeHeaders...) + utils.CopyHeaders(resp.Header, w.Header()) underlyingConn, err := upgrader.Upgrade(w, req, resp.Header) if err != nil { @@ -370,11 +371,20 @@ func (f *httpForwarder) serveWebSocket(w http.ResponseWriter, req *http.Request, m := websocket.FormatCloseMessage(websocket.CloseNormalClosure, fmt.Sprintf("%v", err)) if e, ok := err.(*websocket.CloseError); ok { if e.Code != websocket.CloseNoStatusReceived { - m = websocket.FormatCloseMessage(e.Code, e.Text) + m = nil + // Following codes are not valid on the wire so just close the + // underlying TCP connection without sending a close frame. + if e.Code != websocket.CloseAbnormalClosure && + e.Code != websocket.CloseTLSHandshake { + + m = websocket.FormatCloseMessage(e.Code, e.Text) + } } } errc <- err - dst.WriteMessage(websocket.CloseMessage, m) + if m != nil { + dst.WriteMessage(websocket.CloseMessage, m) + } break } err = dst.WriteMessage(msgType, msg) diff --git a/vendor/github.com/vulcand/oxy/roundrobin/rebalancer.go b/vendor/github.com/vulcand/oxy/roundrobin/rebalancer.go index 9e574e8d8..81f916b74 100644 --- a/vendor/github.com/vulcand/oxy/roundrobin/rebalancer.go +++ b/vendor/github.com/vulcand/oxy/roundrobin/rebalancer.go @@ -145,7 +145,7 @@ func (rb *Rebalancer) ServeHTTP(w http.ResponseWriter, req *http.Request) { if log.GetLevel() >= log.DebugLevel { logEntry := log.WithField("Request", utils.DumpHttpRequest(req)) logEntry.Debug("vulcand/oxy/roundrobin/rebalancer: begin ServeHttp on request") - defer logEntry.Debug("vulcand/oxy/roundrobin/rebalancer: competed ServeHttp on request") + defer logEntry.Debug("vulcand/oxy/roundrobin/rebalancer: completed ServeHttp on request") } pw := utils.NewSimpleProxyWriter(w) diff --git a/vendor/github.com/vulcand/oxy/roundrobin/rr.go b/vendor/github.com/vulcand/oxy/roundrobin/rr.go index 137da6272..053773b7d 100644 --- a/vendor/github.com/vulcand/oxy/roundrobin/rr.go +++ b/vendor/github.com/vulcand/oxy/roundrobin/rr.go @@ -84,7 +84,7 @@ func (r *RoundRobin) ServeHTTP(w http.ResponseWriter, req *http.Request) { if log.GetLevel() >= log.DebugLevel { logEntry := log.WithField("Request", utils.DumpHttpRequest(req)) logEntry.Debug("vulcand/oxy/roundrobin/rr: begin ServeHttp on request") - defer logEntry.Debug("vulcand/oxy/roundrobin/rr: competed ServeHttp on request") + defer logEntry.Debug("vulcand/oxy/roundrobin/rr: completed ServeHttp on request") } // make shallow copy of request before chaning anything to avoid side effects diff --git a/vendor/github.com/xenolf/lego/acmev2/challenges.go b/vendor/github.com/xenolf/lego/acme/challenges.go similarity index 78% rename from vendor/github.com/xenolf/lego/acmev2/challenges.go rename to vendor/github.com/xenolf/lego/acme/challenges.go index 8fcc5bf87..cf7bd7f75 100644 --- a/vendor/github.com/xenolf/lego/acmev2/challenges.go +++ b/vendor/github.com/xenolf/lego/acme/challenges.go @@ -1,13 +1,13 @@ -package acmev2 +package acme // Challenge is a string that identifies a particular type and version of ACME challenge. type Challenge string const ( - // HTTP01 is the "http-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acmev2.md#http + // HTTP01 is the "http-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#http // Note: HTTP01ChallengePath returns the URL path to fulfill this challenge HTTP01 = Challenge("http-01") - // DNS01 is the "dns-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acmev2.md#dns + // DNS01 is the "dns-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#dns // Note: DNS01Record returns a DNS record which will fulfill this challenge DNS01 = Challenge("dns-01") ) diff --git a/vendor/github.com/xenolf/lego/acmev2/client.go b/vendor/github.com/xenolf/lego/acme/client.go similarity index 82% rename from vendor/github.com/xenolf/lego/acmev2/client.go rename to vendor/github.com/xenolf/lego/acme/client.go index ff3dc866b..a9e346089 100644 --- a/vendor/github.com/xenolf/lego/acmev2/client.go +++ b/vendor/github.com/xenolf/lego/acme/client.go @@ -1,5 +1,5 @@ -// package acmev2 implements the ACME protocol for Let's Encrypt and other conforming providers. -package acmev2 +// Package acme implements the ACME protocol for Let's Encrypt and other conforming providers. +package acme import ( "crypto" @@ -8,17 +8,13 @@ import ( "errors" "fmt" "io/ioutil" - "log" "net" "regexp" "strconv" "strings" "time" -) -var ( - // Logger is an optional custom logger. - Logger *log.Logger + "github.com/xenolf/lego/log" ) const ( @@ -31,16 +27,6 @@ const ( overallRequestLimit = 18 ) -// logf writes a log entry. It uses Logger if not -// nil, otherwise it uses the default log.Logger. -func logf(format string, args ...interface{}) { - if Logger != nil { - Logger.Printf(format, args...) - } else { - log.Printf(format, args...) - } -} - // User interface is to be implemented by users of this library. // It is used by the client type to get user specific information. type User interface { @@ -86,9 +72,6 @@ func NewClient(caDirURL string, user User, keyType KeyType) (*Client, error) { if dir.NewOrderURL == "" { return nil, errors.New("directory missing new order URL") } - /*if dir.RevokeCertURL == "" { - return nil, errors.New("directory missing revoke certificate URL") - }*/ jws := &jws{privKey: privKey, getNonceURL: dir.NewNonceURL} if reg := user.GetRegistration(); reg != nil { @@ -149,12 +132,17 @@ func (c *Client) GetToSURL() string { return c.directory.Meta.TermsOfService } +// GetExternalAccountRequired returns the External Account Binding requirement of the Directory +func (c *Client) GetExternalAccountRequired() bool { + return c.directory.Meta.ExternalAccountRequired +} + // Register the current account to the ACME server. func (c *Client) Register(tosAgreed bool) (*RegistrationResource, error) { if c == nil || c.user == nil { return nil, errors.New("acme: cannot register a nil client or user") } - logf("[INFO] acme: Registering account for %s", c.user.GetEmail()) + log.Printf("[INFO] acme: Registering account for %s", c.user.GetEmail()) accMsg := accountMessage{} if c.user.GetEmail() != "" { @@ -183,10 +171,58 @@ func (c *Client) Register(tosAgreed bool) (*RegistrationResource, error) { return reg, nil } +// RegisterWithExternalAccountBinding Register the current account to the ACME server. +func (c *Client) RegisterWithExternalAccountBinding(tosAgreed bool, kid string, hmacEncoded string) (*RegistrationResource, error) { + if c == nil || c.user == nil { + return nil, errors.New("acme: cannot register a nil client or user") + } + log.Printf("[INFO] acme: Registering account (EAB) for %s", c.user.GetEmail()) + + accMsg := accountMessage{} + if c.user.GetEmail() != "" { + accMsg.Contact = []string{"mailto:" + c.user.GetEmail()} + } else { + accMsg.Contact = []string{} + } + accMsg.TermsOfServiceAgreed = tosAgreed + + hmac, err := base64.RawURLEncoding.DecodeString(hmacEncoded) + if err != nil { + return nil, fmt.Errorf("acme: could not decode hmac key: %s", err.Error()) + } + + eabJWS, err := c.jws.signEABContent(c.directory.NewAccountURL, kid, hmac) + if err != nil { + return nil, fmt.Errorf("acme: error signing eab content: %s", err.Error()) + } + + eabPayload := eabJWS.FullSerialize() + + accMsg.ExternalAccountBinding = []byte(eabPayload) + + var serverReg accountMessage + hdr, err := postJSON(c.jws, c.directory.NewAccountURL, accMsg, &serverReg) + if err != nil { + remoteErr, ok := err.(RemoteError) + if ok && remoteErr.StatusCode == 409 { + } else { + return nil, err + } + } + + reg := &RegistrationResource{ + URI: hdr.Get("Location"), + Body: serverReg, + } + c.jws.kid = reg.URI + + return reg, nil +} + // ResolveAccountByKey will attempt to look up an account using the given account key // and return its registration resource. func (c *Client) ResolveAccountByKey() (*RegistrationResource, error) { - logf("[INFO] acme: Trying to resolve account by key") + log.Printf("[INFO] acme: Trying to resolve account by key") acc := accountMessage{OnlyReturnExisting: true} hdr, err := postJSON(c.jws, c.directory.NewAccountURL, acc, nil) @@ -201,7 +237,7 @@ func (c *Client) ResolveAccountByKey() (*RegistrationResource, error) { var retAccount accountMessage c.jws.kid = accountLink - hdr, err = postJSON(c.jws, accountLink, accountMessage{}, &retAccount) + _, err = postJSON(c.jws, accountLink, accountMessage{}, &retAccount) if err != nil { return nil, err } @@ -215,18 +251,14 @@ func (c *Client) DeleteRegistration() error { if c == nil || c.user == nil { return errors.New("acme: cannot unregister a nil client or user") } - logf("[INFO] acme: Deleting account for %s", c.user.GetEmail()) + log.Printf("[INFO] acme: Deleting account for %s", c.user.GetEmail()) accMsg := accountMessage{ Status: "deactivated", } _, err := postJSON(c.jws, c.user.GetRegistration().URI, accMsg, nil) - if err != nil { - return err - } - - return nil + return err } // QueryRegistration runs a POST request on the client's registration and @@ -239,7 +271,7 @@ func (c *Client) QueryRegistration() (*RegistrationResource, error) { return nil, errors.New("acme: cannot query the registration of a nil client or user") } // Log the URL here instead of the email as the email may not be set - logf("[INFO] acme: Querying account for %s", c.user.GetRegistration().URI) + log.Printf("[INFO] acme: Querying account for %s", c.user.GetRegistration().URI) accMsg := accountMessage{} @@ -265,7 +297,7 @@ func (c *Client) QueryRegistration() (*RegistrationResource, error) { // your issued certificate as a bundle. // This function will never return a partial certificate. If one domain in the list fails, // the whole certificate will fail. -func (c *Client) ObtainCertificateForCSR(csr x509.CertificateRequest, bundle bool) (CertificateResource, error) { +func (c *Client) ObtainCertificateForCSR(csr x509.CertificateRequest, bundle bool) (*CertificateResource, error) { // figure out what domains it concerns // start with the common name domains := []string{csr.Subject.CommonName} @@ -285,14 +317,14 @@ DNSNames: } if bundle { - logf("[INFO][%s] acme: Obtaining bundled SAN certificate given a CSR", strings.Join(domains, ", ")) + log.Printf("[INFO][%s] acme: Obtaining bundled SAN certificate given a CSR", strings.Join(domains, ", ")) } else { - logf("[INFO][%s] acme: Obtaining SAN certificate given a CSR", strings.Join(domains, ", ")) + log.Printf("[INFO][%s] acme: Obtaining SAN certificate given a CSR", strings.Join(domains, ", ")) } order, err := c.createOrderForIdentifiers(domains) if err != nil { - return CertificateResource{}, err + return nil, err } authz, err := c.getAuthzForOrder(order) if err != nil { @@ -300,16 +332,16 @@ DNSNames: /*for _, auth := range authz { c.disableAuthz(auth) }*/ - return CertificateResource{}, err + return nil, err } err = c.solveChallengeForAuthz(authz) if err != nil { // If any challenge fails, return. Do not generate partial SAN certificates. - return CertificateResource{}, err + return nil, err } - logf("[INFO][%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", ")) + log.Printf("[INFO][%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", ")) failures := make(ObtainError) cert, err := c.requestCertificateForCsr(order, bundle, csr.Raw, nil) @@ -339,20 +371,20 @@ DNSNames: // your issued certificate as a bundle. // This function will never return a partial certificate. If one domain in the list fails, // the whole certificate will fail. -func (c *Client) ObtainCertificate(domains []string, bundle bool, privKey crypto.PrivateKey, mustStaple bool) (CertificateResource, error) { +func (c *Client) ObtainCertificate(domains []string, bundle bool, privKey crypto.PrivateKey, mustStaple bool) (*CertificateResource, error) { if len(domains) == 0 { - return CertificateResource{}, errors.New("No domains to obtain a certificate for") + return nil, errors.New("No domains to obtain a certificate for") } if bundle { - logf("[INFO][%s] acme: Obtaining bundled SAN certificate", strings.Join(domains, ", ")) + log.Printf("[INFO][%s] acme: Obtaining bundled SAN certificate", strings.Join(domains, ", ")) } else { - logf("[INFO][%s] acme: Obtaining SAN certificate", strings.Join(domains, ", ")) + log.Printf("[INFO][%s] acme: Obtaining SAN certificate", strings.Join(domains, ", ")) } order, err := c.createOrderForIdentifiers(domains) if err != nil { - return CertificateResource{}, err + return nil, err } authz, err := c.getAuthzForOrder(order) if err != nil { @@ -360,16 +392,16 @@ func (c *Client) ObtainCertificate(domains []string, bundle bool, privKey crypto /*for _, auth := range authz { c.disableAuthz(auth) }*/ - return CertificateResource{}, err + return nil, err } err = c.solveChallengeForAuthz(authz) if err != nil { // If any challenge fails, return. Do not generate partial SAN certificates. - return CertificateResource{}, err + return nil, err } - logf("[INFO][%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", ")) + log.Printf("[INFO][%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", ")) failures := make(ObtainError) cert, err := c.requestCertificateForOrder(order, bundle, privKey, mustStaple) @@ -413,22 +445,22 @@ func (c *Client) RevokeCertificate(certificate []byte) error { // If bundle is true, the []byte contains both the issuer certificate and // your issued certificate as a bundle. // For private key reuse the PrivateKey property of the passed in CertificateResource should be non-nil. -func (c *Client) RenewCertificate(cert CertificateResource, bundle, mustStaple bool) (CertificateResource, error) { +func (c *Client) RenewCertificate(cert CertificateResource, bundle, mustStaple bool) (*CertificateResource, error) { // Input certificate is PEM encoded. Decode it here as we may need the decoded // cert later on in the renewal process. The input may be a bundle or a single certificate. certificates, err := parsePEMBundle(cert.Certificate) if err != nil { - return CertificateResource{}, err + return nil, err } x509Cert := certificates[0] if x509Cert.IsCA { - return CertificateResource{}, fmt.Errorf("[%s] Certificate bundle starts with a CA certificate", cert.Domain) + return nil, fmt.Errorf("[%s] Certificate bundle starts with a CA certificate", cert.Domain) } // This is just meant to be informal for the user. timeLeft := x509Cert.NotAfter.Sub(time.Now().UTC()) - logf("[INFO][%s] acme: Trying renewal with %d hours remaining", cert.Domain, int(timeLeft.Hours())) + log.Printf("[INFO][%s] acme: Trying renewal with %d hours remaining", cert.Domain, int(timeLeft.Hours())) // We always need to request a new certificate to renew. // Start by checking to see if the certificate was based off a CSR, and @@ -436,7 +468,7 @@ func (c *Client) RenewCertificate(cert CertificateResource, bundle, mustStaple b if len(cert.CSR) > 0 { csr, err := pemDecodeTox509CSR(cert.CSR) if err != nil { - return CertificateResource{}, err + return nil, err } newCert, failures := c.ObtainCertificateForCSR(*csr, bundle) return newCert, failures @@ -446,7 +478,7 @@ func (c *Client) RenewCertificate(cert CertificateResource, bundle, mustStaple b if cert.PrivateKey != nil { privKey, err = parsePEMPrivateKey(cert.PrivateKey) if err != nil { - return CertificateResource{}, err + return nil, err } } @@ -502,7 +534,7 @@ func (c *Client) solveChallengeForAuthz(authorizations []authorization) error { for _, authz := range authorizations { if authz.Status == "valid" { // Boulder might recycle recent validated authz (see issue #267) - logf("[INFO][%s] acme: Authorization already valid; skipping challenge", authz.Identifier.Value) + log.Printf("[INFO][%s] acme: Authorization already valid; skipping challenge", authz.Identifier.Value) continue } @@ -533,7 +565,7 @@ func (c *Client) chooseSolver(auth authorization, domain string) (int, solver) { if solver, ok := c.solvers[Challenge(challenge.Type)]; ok { return i, solver } - logf("[INFO][%s] acme: Could not find solver for: %s", domain, challenge.Type) + log.Printf("[INFO][%s] acme: Could not find solver for: %s", domain, challenge.Type) } return 0, nil } @@ -585,7 +617,7 @@ func (c *Client) getAuthzForOrder(order orderResource) ([]authorization, error) func logAuthz(order orderResource) { for i, auth := range order.Authorizations { - logf("[INFO][%s] AuthURL: %s", order.Identifiers[i].Value, auth) + log.Printf("[INFO][%s] AuthURL: %s", order.Identifiers[i].Value, auth) } } @@ -596,13 +628,13 @@ func (c *Client) disableAuthz(authURL string) error { return err } -func (c *Client) requestCertificateForOrder(order orderResource, bundle bool, privKey crypto.PrivateKey, mustStaple bool) (CertificateResource, error) { +func (c *Client) requestCertificateForOrder(order orderResource, bundle bool, privKey crypto.PrivateKey, mustStaple bool) (*CertificateResource, error) { var err error if privKey == nil { privKey, err = generatePrivateKey(c.keyType) if err != nil { - return CertificateResource{}, err + return nil, err } } @@ -616,24 +648,24 @@ func (c *Client) requestCertificateForOrder(order orderResource, bundle bool, pr // TODO: should the CSR be customizable? csr, err := generateCsr(privKey, commonName, san, mustStaple) if err != nil { - return CertificateResource{}, err + return nil, err } return c.requestCertificateForCsr(order, bundle, csr, pemEncode(privKey)) } -func (c *Client) requestCertificateForCsr(order orderResource, bundle bool, csr []byte, privateKeyPem []byte) (CertificateResource, error) { +func (c *Client) requestCertificateForCsr(order orderResource, bundle bool, csr []byte, privateKeyPem []byte) (*CertificateResource, error) { commonName := order.Domains[0] csrString := base64.RawURLEncoding.EncodeToString(csr) var retOrder orderMessage _, error := postJSON(c.jws, order.Finalize, csrMessage{Csr: csrString}, &retOrder) if error != nil { - return CertificateResource{}, error + return nil, error } if retOrder.Status == "invalid" { - return CertificateResource{}, error + return nil, error } certRes := CertificateResource{ @@ -646,11 +678,11 @@ func (c *Client) requestCertificateForCsr(order orderResource, bundle bool, csr // if the certificate is available right away, short cut! ok, err := c.checkCertResponse(retOrder, &certRes, bundle) if err != nil { - return CertificateResource{}, err + return nil, err } if ok { - return certRes, nil + return &certRes, nil } } @@ -658,21 +690,21 @@ func (c *Client) requestCertificateForCsr(order orderResource, bundle bool, csr for i := 0; i < maxChecks; i++ { _, err := getJSON(order.URL, &retOrder) if err != nil { - return CertificateResource{}, err + return nil, err } done, err := c.checkCertResponse(retOrder, &certRes, bundle) if err != nil { - return CertificateResource{}, err + return nil, err } if done { break } if i == maxChecks-1 { - return CertificateResource{}, fmt.Errorf("polled for certificate %d times; giving up", i) + return nil, fmt.Errorf("polled for certificate %d times; giving up", i) } } - return certRes, nil + return &certRes, nil } // checkCertResponse checks to see if the certificate is ready and a link is contained in the @@ -702,7 +734,7 @@ func (c *Client) checkCertResponse(order orderMessage, certRes *CertificateResou if err != nil { // If we fail to acquire the issuer cert, return the issued certificate - do not fail. - logf("[WARNING][%s] acme: Could not bundle issuer certificate: %v", certRes.Domain, err) + log.Printf("[WARNING][%s] acme: Could not bundle issuer certificate: %v", certRes.Domain, err) } else { issuerCert = pemEncode(derCertificateBytes(issuerCert)) @@ -719,7 +751,7 @@ func (c *Client) checkCertResponse(order orderMessage, certRes *CertificateResou certRes.Certificate = cert certRes.CertURL = order.Certificate certRes.CertStableURL = order.Certificate - logf("[INFO][%s] Server responded with a certificate.", certRes.Domain) + log.Printf("[INFO][%s] Server responded with a certificate.", certRes.Domain) return true, nil case "processing": @@ -733,7 +765,7 @@ func (c *Client) checkCertResponse(order orderMessage, certRes *CertificateResou // getIssuerCertificate requests the issuer certificate func (c *Client) getIssuerCertificate(url string) ([]byte, error) { - logf("[INFO] acme: Requesting issuer cert from %s", url) + log.Printf("[INFO] acme: Requesting issuer cert from %s", url) resp, err := httpGet(url) if err != nil { return nil, err @@ -787,14 +819,14 @@ func validate(j *jws, domain, uri string, c challenge) error { for { switch chlng.Status { case "valid": - logf("[INFO][%s] The server validated our request", domain) + log.Printf("[INFO][%s] The server validated our request", domain) return nil case "pending": - break + case "processing": case "invalid": return handleChallengeError(chlng) default: - return errors.New("The server returned an unexpected state") + return errors.New("the server returned an unexpected state") } ra, err := strconv.Atoi(hdr.Get("Retry-After")) diff --git a/vendor/github.com/xenolf/lego/acmev2/crypto.go b/vendor/github.com/xenolf/lego/acme/crypto.go similarity index 96% rename from vendor/github.com/xenolf/lego/acmev2/crypto.go rename to vendor/github.com/xenolf/lego/acme/crypto.go index 8f299a65b..7d4f44258 100644 --- a/vendor/github.com/xenolf/lego/acmev2/crypto.go +++ b/vendor/github.com/xenolf/lego/acme/crypto.go @@ -1,4 +1,4 @@ -package acmev2 +package acme import ( "bytes" @@ -9,6 +9,7 @@ import ( "crypto/rsa" "crypto/x509" "crypto/x509/pkix" + "encoding/asn1" "encoding/base64" "encoding/pem" "errors" @@ -19,8 +20,6 @@ import ( "net/http" "time" - "encoding/asn1" - "golang.org/x/crypto/ocsp" jose "gopkg.in/square/go-jose.v2" ) @@ -118,6 +117,10 @@ func GetOCSPForCert(bundle []byte) ([]byte, *ocsp.Response, error) { defer req.Body.Close() ocspResBytes, err := ioutil.ReadAll(limitReader(req.Body, 1024*1024)) + if err != nil { + return nil, nil, err + } + ocspRes, err := ocsp.ParseResponse(ocspResBytes, issuerCert) if err != nil { return nil, nil, err @@ -138,7 +141,7 @@ func getKeyAuthorization(token string, key interface{}) (string, error) { // Generate the Key Authorization for the challenge jwk := &jose.JSONWebKey{Key: publicKey} if jwk == nil { - return "", errors.New("Could not generate JWK from key") + return "", errors.New("could not generate JWK from key") } thumbBytes, err := jwk.Thumbprint(crypto.SHA256) if err != nil { @@ -173,7 +176,7 @@ func parsePEMBundle(bundle []byte) ([]*x509.Certificate, error) { } if len(certificates) == 0 { - return nil, errors.New("No certificates were found while parsing the bundle") + return nil, errors.New("no certificates were found while parsing the bundle") } return certificates, nil @@ -188,7 +191,7 @@ func parsePEMPrivateKey(key []byte) (crypto.PrivateKey, error) { case "EC PRIVATE KEY": return x509.ParseECPrivateKey(keyBlock.Bytes) default: - return nil, errors.New("Unknown PEM header value") + return nil, errors.New("unknown PEM header value") } } @@ -207,7 +210,7 @@ func generatePrivateKey(keyType KeyType) (crypto.PrivateKey, error) { return rsa.GenerateKey(rand.Reader, 8192) } - return nil, fmt.Errorf("Invalid KeyType: %s", keyType) + return nil, fmt.Errorf("invalid KeyType: %s", keyType) } func generateCsr(privateKey crypto.PrivateKey, domain string, san []string, mustStaple bool) ([]byte, error) { @@ -239,10 +242,8 @@ func pemEncode(data interface{}) []byte { pemBlock = &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes} case *rsa.PrivateKey: pemBlock = &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)} - break case *x509.CertificateRequest: pemBlock = &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: key.Raw} - break case derCertificateBytes: pemBlock = &pem.Block{Type: "CERTIFICATE", Bytes: []byte(data.(derCertificateBytes))} } diff --git a/vendor/github.com/xenolf/lego/acmev2/dns_challenge.go b/vendor/github.com/xenolf/lego/acme/dns_challenge.go similarity index 97% rename from vendor/github.com/xenolf/lego/acmev2/dns_challenge.go rename to vendor/github.com/xenolf/lego/acme/dns_challenge.go index d57a4bd63..d494501c8 100644 --- a/vendor/github.com/xenolf/lego/acmev2/dns_challenge.go +++ b/vendor/github.com/xenolf/lego/acme/dns_challenge.go @@ -1,16 +1,16 @@ -package acmev2 +package acme import ( "crypto/sha256" "encoding/base64" "errors" "fmt" - "log" "net" "strings" "time" "github.com/miekg/dns" + "github.com/xenolf/lego/log" ) type preCheckDNSFunc func(fqdn, value string) (bool, error) @@ -72,7 +72,7 @@ type dnsChallenge struct { } func (s *dnsChallenge) Solve(chlng challenge, domain string) error { - logf("[INFO][%s] acme: Trying to solve DNS-01", domain) + log.Printf("[INFO][%s] acme: Trying to solve DNS-01", domain) if s.provider == nil { return errors.New("No DNS Provider configured") @@ -97,7 +97,7 @@ func (s *dnsChallenge) Solve(chlng challenge, domain string) error { fqdn, value, _ := DNS01Record(domain, keyAuth) - logf("[INFO][%s] Checking DNS record propagation using %+v", domain, RecursiveNameservers) + log.Printf("[INFO][%s] Checking DNS record propagation using %+v", domain, RecursiveNameservers) var timeout, interval time.Duration switch provider := s.provider.(type) { diff --git a/vendor/github.com/xenolf/lego/acmev2/dns_challenge_manual.go b/vendor/github.com/xenolf/lego/acme/dns_challenge_manual.go similarity index 75% rename from vendor/github.com/xenolf/lego/acmev2/dns_challenge_manual.go rename to vendor/github.com/xenolf/lego/acme/dns_challenge_manual.go index 85dd9ca88..cd4c3c8a6 100644 --- a/vendor/github.com/xenolf/lego/acmev2/dns_challenge_manual.go +++ b/vendor/github.com/xenolf/lego/acme/dns_challenge_manual.go @@ -1,9 +1,11 @@ -package acmev2 +package acme import ( "bufio" "fmt" "os" + + "github.com/xenolf/lego/log" ) const ( @@ -28,9 +30,9 @@ func (*DNSProviderManual) Present(domain, token, keyAuth string) error { return err } - logf("[INFO] acme: Please create the following TXT record in your %s zone:", authZone) - logf("[INFO] acme: %s", dnsRecord) - logf("[INFO] acme: Press 'Enter' when you are done") + log.Printf("[INFO] acme: Please create the following TXT record in your %s zone:", authZone) + log.Printf("[INFO] acme: %s", dnsRecord) + log.Printf("[INFO] acme: Press 'Enter' when you are done") reader := bufio.NewReader(os.Stdin) _, _ = reader.ReadString('\n') @@ -47,7 +49,7 @@ func (*DNSProviderManual) CleanUp(domain, token, keyAuth string) error { return err } - logf("[INFO] acme: You can now remove this TXT record from your %s zone:", authZone) - logf("[INFO] acme: %s", dnsRecord) + log.Printf("[INFO] acme: You can now remove this TXT record from your %s zone:", authZone) + log.Printf("[INFO] acme: %s", dnsRecord) return nil } diff --git a/vendor/github.com/xenolf/lego/acmev2/error.go b/vendor/github.com/xenolf/lego/acme/error.go similarity index 99% rename from vendor/github.com/xenolf/lego/acmev2/error.go rename to vendor/github.com/xenolf/lego/acme/error.go index b92ce734c..78694debe 100644 --- a/vendor/github.com/xenolf/lego/acmev2/error.go +++ b/vendor/github.com/xenolf/lego/acme/error.go @@ -1,4 +1,4 @@ -package acmev2 +package acme import ( "bytes" @@ -14,18 +14,6 @@ const ( invalidNonceError = "urn:ietf:params:acme:error:badNonce" ) -// ObtainError is returned when there are specific errors available -// per domain. For example in ObtainCertificate -type ObtainError map[string]error - -func (e ObtainError) Error() string { - buffer := bytes.NewBufferString("acme: Error -> One or more domains had a problem:\n") - for dom, err := range e { - buffer.WriteString(fmt.Sprintf("[%s] %s\n", dom, err)) - } - return buffer.String() -} - // RemoteError is the base type for all errors specific to the ACME protocol. type RemoteError struct { StatusCode int `json:"status,omitempty"` @@ -55,6 +43,18 @@ type domainError struct { Error error } +// ObtainError is returned when there are specific errors available +// per domain. For example in ObtainCertificate +type ObtainError map[string]error + +func (e ObtainError) Error() string { + buffer := bytes.NewBufferString("acme: Error -> One or more domains had a problem:\n") + for dom, err := range e { + buffer.WriteString(fmt.Sprintf("[%s] %s\n", dom, err)) + } + return buffer.String() +} + func handleHTTPError(resp *http.Response) error { var errorDetail RemoteError diff --git a/vendor/github.com/xenolf/lego/acmev2/http.go b/vendor/github.com/xenolf/lego/acme/http.go similarity index 96% rename from vendor/github.com/xenolf/lego/acmev2/http.go rename to vendor/github.com/xenolf/lego/acme/http.go index bc51a36db..f45e7f871 100644 --- a/vendor/github.com/xenolf/lego/acmev2/http.go +++ b/vendor/github.com/xenolf/lego/acme/http.go @@ -1,4 +1,4 @@ -package acmev2 +package acme import ( "encoding/json" @@ -155,6 +155,6 @@ func postJSON(j *jws, uri string, reqBody, respBody interface{}) (http.Header, e // userAgent builds and returns the User-Agent string to use in requests. func userAgent() string { - ua := fmt.Sprintf("%s (%s; %s) %s %s", defaultGoUserAgent, runtime.GOOS, runtime.GOARCH, ourUserAgent, UserAgent) + ua := fmt.Sprintf("%s %s (%s; %s) %s", UserAgent, ourUserAgent, runtime.GOOS, runtime.GOARCH, defaultGoUserAgent) return strings.TrimSpace(ua) } diff --git a/vendor/github.com/xenolf/lego/acmev2/http_challenge.go b/vendor/github.com/xenolf/lego/acme/http_challenge.go similarity index 89% rename from vendor/github.com/xenolf/lego/acmev2/http_challenge.go rename to vendor/github.com/xenolf/lego/acme/http_challenge.go index 687297b94..7659bfc51 100644 --- a/vendor/github.com/xenolf/lego/acmev2/http_challenge.go +++ b/vendor/github.com/xenolf/lego/acme/http_challenge.go @@ -1,8 +1,9 @@ -package acmev2 +package acme import ( "fmt" - "log" + + "github.com/xenolf/lego/log" ) type httpChallenge struct { @@ -18,7 +19,7 @@ func HTTP01ChallengePath(token string) string { func (s *httpChallenge) Solve(chlng challenge, domain string) error { - logf("[INFO][%s] acme: Trying to solve HTTP-01", domain) + log.Printf("[INFO][%s] acme: Trying to solve HTTP-01", domain) // Generate the Key Authorization for the challenge keyAuth, err := getKeyAuthorization(chlng.Token, s.jws.privKey) diff --git a/vendor/github.com/xenolf/lego/acmev2/http_challenge_server.go b/vendor/github.com/xenolf/lego/acme/http_challenge_server.go similarity index 88% rename from vendor/github.com/xenolf/lego/acmev2/http_challenge_server.go rename to vendor/github.com/xenolf/lego/acme/http_challenge_server.go index 052d24050..214a278fc 100644 --- a/vendor/github.com/xenolf/lego/acmev2/http_challenge_server.go +++ b/vendor/github.com/xenolf/lego/acme/http_challenge_server.go @@ -1,10 +1,12 @@ -package acmev2 +package acme import ( "fmt" "net" "net/http" "strings" + + "github.com/xenolf/lego/log" ) // HTTPProviderServer implements ChallengeProvider for `http-01` challenge @@ -61,9 +63,9 @@ func (s *HTTPProviderServer) serve(domain, token, keyAuth string) { if strings.HasPrefix(r.Host, domain) && r.Method == "GET" { w.Header().Add("Content-Type", "text/plain") w.Write([]byte(keyAuth)) - logf("[INFO][%s] Served key authentication", domain) + log.Printf("[INFO][%s] Served key authentication", domain) } else { - logf("[WARN] Received request for domain %s with method %s but the domain did not match any challenge. Please ensure your are passing the HOST header properly.", r.Host, r.Method) + log.Printf("[WARN] Received request for domain %s with method %s but the domain did not match any challenge. Please ensure your are passing the HOST header properly.", r.Host, r.Method) w.Write([]byte("TEST")) } }) diff --git a/vendor/github.com/xenolf/lego/acmev2/jws.go b/vendor/github.com/xenolf/lego/acme/jws.go similarity index 67% rename from vendor/github.com/xenolf/lego/acmev2/jws.go rename to vendor/github.com/xenolf/lego/acme/jws.go index 7ab8edb76..bea762104 100644 --- a/vendor/github.com/xenolf/lego/acmev2/jws.go +++ b/vendor/github.com/xenolf/lego/acme/jws.go @@ -1,4 +1,4 @@ -package acmev2 +package acme import ( "bytes" @@ -26,13 +26,13 @@ type jws struct { func (j *jws) post(url string, content []byte) (*http.Response, error) { signedContent, err := j.signContent(url, content) if err != nil { - return nil, fmt.Errorf("Failed to sign content -> %s", err.Error()) + return nil, fmt.Errorf("failed to sign content -> %s", err.Error()) } data := bytes.NewBuffer([]byte(signedContent.FullSerialize())) resp, err := httpPost(url, "application/jose+json", data) if err != nil { - return nil, fmt.Errorf("Failed to HTTP POST to %s -> %s", url, err.Error()) + return nil, fmt.Errorf("failed to HTTP POST to %s -> %s", url, err.Error()) } nonce, nonceErr := getNonceFromResponse(resp) @@ -77,16 +77,45 @@ func (j *jws) signContent(url string, content []byte) (*jose.JSONWebSignature, e signer, err := jose.NewSigner(signKey, &options) if err != nil { - return nil, fmt.Errorf("Failed to create jose signer -> %s", err.Error()) + return nil, fmt.Errorf("failed to create jose signer -> %s", err.Error()) } signed, err := signer.Sign(content) if err != nil { - return nil, fmt.Errorf("Failed to sign content -> %s", err.Error()) + return nil, fmt.Errorf("failed to sign content -> %s", err.Error()) } return signed, nil } +func (j *jws) signEABContent(url, kid string, hmac []byte) (*jose.JSONWebSignature, error) { + jwk := jose.JSONWebKey{Key: j.privKey} + jwkJSON, err := jwk.Public().MarshalJSON() + if err != nil { + return nil, fmt.Errorf("acme: error encoding eab jwk key: %s", err.Error()) + } + + signer, err := jose.NewSigner( + jose.SigningKey{Algorithm: jose.HS256, Key: hmac}, + &jose.SignerOptions{ + EmbedJWK: false, + ExtraHeaders: map[jose.HeaderKey]interface{}{ + "kid": kid, + "url": url, + }, + }, + ) + if err != nil { + return nil, fmt.Errorf("failed to create External Account Binding jose signer -> %s", err.Error()) + } + + signed, err := signer.Sign(jwkJSON) + if err != nil { + return nil, fmt.Errorf("failed to External Account Binding sign content -> %s", err.Error()) + } + + return signed, nil +} + func (j *jws) Nonce() (string, error) { if nonce, ok := j.nonces.Pop(); ok { return nonce, nil @@ -122,7 +151,7 @@ func (n *nonceManager) Push(nonce string) { func getNonce(url string) (string, error) { resp, err := httpHead(url) if err != nil { - return "", fmt.Errorf("Failed to get nonce from HTTP HEAD -> %s", err.Error()) + return "", fmt.Errorf("failed to get nonce from HTTP HEAD -> %s", err.Error()) } return getNonceFromResponse(resp) @@ -131,7 +160,7 @@ func getNonce(url string) (string, error) { func getNonceFromResponse(resp *http.Response) (string, error) { nonce := resp.Header.Get("Replay-Nonce") if nonce == "" { - return "", fmt.Errorf("Server did not respond with a proper nonce header") + return "", fmt.Errorf("server did not respond with a proper nonce header") } return nonce, nil diff --git a/vendor/github.com/xenolf/lego/acmev2/messages.go b/vendor/github.com/xenolf/lego/acme/messages.go similarity index 86% rename from vendor/github.com/xenolf/lego/acmev2/messages.go rename to vendor/github.com/xenolf/lego/acme/messages.go index a3058dfb8..6946cc15a 100644 --- a/vendor/github.com/xenolf/lego/acmev2/messages.go +++ b/vendor/github.com/xenolf/lego/acme/messages.go @@ -1,6 +1,7 @@ -package acmev2 +package acme import ( + "encoding/json" "time" ) @@ -26,11 +27,12 @@ type directory struct { } type accountMessage struct { - Status string `json:"status,omitempty"` - Contact []string `json:"contact,omitempty"` - TermsOfServiceAgreed bool `json:"termsOfServiceAgreed,omitempty"` - Orders string `json:"orders,omitempty"` - OnlyReturnExisting bool `json:"onlyReturnExisting,omitempty"` + Status string `json:"status,omitempty"` + Contact []string `json:"contact,omitempty"` + TermsOfServiceAgreed bool `json:"termsOfServiceAgreed,omitempty"` + Orders string `json:"orders,omitempty"` + OnlyReturnExisting bool `json:"onlyReturnExisting,omitempty"` + ExternalAccountBinding json.RawMessage `json:"externalAccountBinding,omitempty"` } type orderResource struct { @@ -76,9 +78,6 @@ type csrMessage struct { Csr string `json:"csr"` } -type emptyObjectMessage struct { -} - type revokeCertMessage struct { Certificate string `json:"certificate"` } diff --git a/vendor/github.com/xenolf/lego/acmev2/provider.go b/vendor/github.com/xenolf/lego/acme/provider.go similarity index 98% rename from vendor/github.com/xenolf/lego/acmev2/provider.go rename to vendor/github.com/xenolf/lego/acme/provider.go index 70fb525ab..d177ff07a 100644 --- a/vendor/github.com/xenolf/lego/acmev2/provider.go +++ b/vendor/github.com/xenolf/lego/acme/provider.go @@ -1,4 +1,4 @@ -package acmev2 +package acme import "time" diff --git a/vendor/github.com/xenolf/lego/acmev2/utils.go b/vendor/github.com/xenolf/lego/acme/utils.go similarity index 96% rename from vendor/github.com/xenolf/lego/acmev2/utils.go rename to vendor/github.com/xenolf/lego/acme/utils.go index 86df09124..2fa0db304 100644 --- a/vendor/github.com/xenolf/lego/acmev2/utils.go +++ b/vendor/github.com/xenolf/lego/acme/utils.go @@ -1,4 +1,4 @@ -package acmev2 +package acme import ( "fmt" diff --git a/vendor/github.com/xenolf/lego/acmev2/pop_challenge.go b/vendor/github.com/xenolf/lego/acmev2/pop_challenge.go deleted file mode 100644 index f8f4e4e7c..000000000 --- a/vendor/github.com/xenolf/lego/acmev2/pop_challenge.go +++ /dev/null @@ -1 +0,0 @@ -package acmev2 diff --git a/vendor/github.com/xenolf/lego/log/logger.go b/vendor/github.com/xenolf/lego/log/logger.go new file mode 100644 index 000000000..291541c0a --- /dev/null +++ b/vendor/github.com/xenolf/lego/log/logger.go @@ -0,0 +1,59 @@ +package log + +import ( + "log" + "os" +) + +// Logger is an optional custom logger. +var Logger *log.Logger + +// Fatal writes a log entry. +// It uses Logger if not nil, otherwise it uses the default log.Logger. +func Fatal(args ...interface{}) { + if Logger == nil { + Logger = log.New(os.Stderr, "", log.LstdFlags) + } + + Logger.Fatal(args...) +} + +// Fatalf writes a log entry. +// It uses Logger if not nil, otherwise it uses the default log.Logger. +func Fatalf(format string, args ...interface{}) { + if Logger == nil { + Logger = log.New(os.Stderr, "", log.LstdFlags) + } + + Logger.Fatalf(format, args...) +} + +// Print writes a log entry. +// It uses Logger if not nil, otherwise it uses the default log.Logger. +func Print(args ...interface{}) { + if Logger == nil { + Logger = log.New(os.Stdout, "", log.LstdFlags) + } + + Logger.Print(args...) +} + +// Println writes a log entry. +// It uses Logger if not nil, otherwise it uses the default log.Logger. +func Println(args ...interface{}) { + if Logger == nil { + Logger = log.New(os.Stdout, "", log.LstdFlags) + } + + Logger.Println(args...) +} + +// Printf writes a log entry. +// It uses Logger if not nil, otherwise it uses the default log.Logger. +func Printf(format string, args ...interface{}) { + if Logger == nil { + Logger = log.New(os.Stdout, "", log.LstdFlags) + } + + Logger.Printf(format, args...) +} 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 4acf0b386..03d31754d 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/auroradns/auroradns.go +++ b/vendor/github.com/xenolf/lego/providers/dns/auroradns/auroradns.go @@ -8,7 +8,7 @@ import ( "github.com/edeckers/auroradnsclient" "github.com/edeckers/auroradnsclient/records" "github.com/edeckers/auroradnsclient/zones" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) // DNSProvider describes a provider for AuroraDNS @@ -60,14 +60,14 @@ func (provider *DNSProvider) getZoneInformationByName(name string) (zones.ZoneRe } } - return zones.ZoneRecord{}, fmt.Errorf("Could not find Zone record") + return zones.ZoneRecord{}, fmt.Errorf("could not find Zone record") } // Present creates a record with a secret func (provider *DNSProvider) Present(domain, token, keyAuth string) error { - fqdn, value, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, _ := acme.DNS01Record(domain, keyAuth) - authZone, err := acmev2.FindZoneByFqdn(acmev2.ToFqdn(domain), acmev2.RecursiveNameservers) + authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers) if err != nil { return fmt.Errorf("Could not determine zone for domain: '%s'. %s", domain, err) } @@ -81,9 +81,12 @@ func (provider *DNSProvider) Present(domain, token, keyAuth string) error { subdomain := fqdn[0 : len(fqdn)-len(authZone)-1] - authZone = acmev2.UnFqdn(authZone) + authZone = acme.UnFqdn(authZone) zoneRecord, err := provider.getZoneInformationByName(authZone) + if err != nil { + return fmt.Errorf("could not create record: %v", err) + } reqData := records.CreateRecordRequest{ @@ -95,7 +98,7 @@ func (provider *DNSProvider) Present(domain, token, keyAuth string) error { respData, err := provider.client.CreateRecord(zoneRecord.ID, reqData) if err != nil { - return fmt.Errorf("Could not create record: '%s'.", err) + return fmt.Errorf("could not create record: %v", err) } provider.recordIDsMu.Lock() @@ -107,22 +110,22 @@ func (provider *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes a given record that was generated by Present func (provider *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) provider.recordIDsMu.Lock() recordID, ok := provider.recordIDs[fqdn] provider.recordIDsMu.Unlock() if !ok { - return fmt.Errorf("Unknown recordID for '%s'", fqdn) + return fmt.Errorf("unknown recordID for %q", fqdn) } - authZone, err := acmev2.FindZoneByFqdn(acmev2.ToFqdn(domain), acmev2.RecursiveNameservers) + authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers) if err != nil { - return fmt.Errorf("Could not determine zone for domain: '%s'. %s", domain, err) + return fmt.Errorf("could not determine zone for domain: %q. %v", domain, err) } - authZone = acmev2.UnFqdn(authZone) + authZone = acme.UnFqdn(authZone) zoneRecord, err := provider.getZoneInformationByName(authZone) if err != nil { diff --git a/vendor/github.com/xenolf/lego/providers/dns/azure/azure.go b/vendor/github.com/xenolf/lego/providers/dns/azure/azure.go index c11ca8fc1..b26d8526c 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/azure/azure.go +++ b/vendor/github.com/xenolf/lego/providers/dns/azure/azure.go @@ -15,44 +15,49 @@ import ( "github.com/Azure/go-autorest/autorest/adal" "github.com/Azure/go-autorest/autorest/azure" "github.com/Azure/go-autorest/autorest/to" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) -// DNSProvider is an implementation of the acmev2.ChallengeProvider interface +// DNSProvider is an implementation of the acme.ChallengeProvider interface type DNSProvider struct { - clientId string + clientID string clientSecret string - subscriptionId string - tenantId string + subscriptionID string + tenantID string resourceGroup string - - context context.Context + context context.Context } // NewDNSProvider returns a DNSProvider instance configured for azure. // Credentials must be passed in the environment variables: AZURE_CLIENT_ID, // AZURE_CLIENT_SECRET, AZURE_SUBSCRIPTION_ID, AZURE_TENANT_ID, AZURE_RESOURCE_GROUP func NewDNSProvider() (*DNSProvider, error) { - clientId := os.Getenv("AZURE_CLIENT_ID") + clientID := os.Getenv("AZURE_CLIENT_ID") clientSecret := os.Getenv("AZURE_CLIENT_SECRET") - subscriptionId := os.Getenv("AZURE_SUBSCRIPTION_ID") - tenantId := os.Getenv("AZURE_TENANT_ID") + subscriptionID := os.Getenv("AZURE_SUBSCRIPTION_ID") + tenantID := os.Getenv("AZURE_TENANT_ID") resourceGroup := os.Getenv("AZURE_RESOURCE_GROUP") - return NewDNSProviderCredentials(clientId, clientSecret, subscriptionId, tenantId, resourceGroup) + return NewDNSProviderCredentials(clientID, clientSecret, subscriptionID, tenantID, resourceGroup) } // NewDNSProviderCredentials uses the supplied credentials to return a // DNSProvider instance configured for azure. -func NewDNSProviderCredentials(clientId, clientSecret, subscriptionId, tenantId, resourceGroup string) (*DNSProvider, error) { - if clientId == "" || clientSecret == "" || subscriptionId == "" || tenantId == "" || resourceGroup == "" { - return nil, fmt.Errorf("Azure configuration missing") +func NewDNSProviderCredentials(clientID, clientSecret, subscriptionID, tenantID, resourceGroup string) (*DNSProvider, error) { + if clientID == "" || clientSecret == "" || subscriptionID == "" || tenantID == "" || resourceGroup == "" { + var missingEnvVars []string + for _, envVar := range []string{"AZURE_CLIENT_ID", "AZURE_CLIENT_SECRET", "AZURE_SUBSCRIPTION_ID", "AZURE_TENANT_ID", "AZURE_RESOURCE_GROUP"} { + if os.Getenv(envVar) == "" { + missingEnvVars = append(missingEnvVars, envVar) + } + } + return nil, fmt.Errorf("Azure configuration missing: %s", strings.Join(missingEnvVars, ",")) } return &DNSProvider{ - clientId: clientId, + clientID: clientID, clientSecret: clientSecret, - subscriptionId: subscriptionId, - tenantId: tenantId, + subscriptionID: subscriptionID, + tenantID: tenantID, resourceGroup: resourceGroup, // TODO: A timeout can be added here for cancellation purposes. context: context.Background(), @@ -67,74 +72,77 @@ func (c *DNSProvider) Timeout() (timeout, interval time.Duration) { // Present creates a TXT record to fulfil the dns-01 challenge func (c *DNSProvider) Present(domain, token, keyAuth string) error { - fqdn, value, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, _ := acme.DNS01Record(domain, keyAuth) zone, err := c.getHostedZoneID(fqdn) if err != nil { return err } - rsc := dns.NewRecordSetsClient(c.subscriptionId) + rsc := dns.NewRecordSetsClient(c.subscriptionID) spt, err := c.newServicePrincipalTokenFromCredentials(azure.PublicCloud.ResourceManagerEndpoint) + if err != nil { + return err + } + rsc.Authorizer = autorest.NewBearerAuthorizer(spt) - relative := toRelativeRecord(fqdn, acmev2.ToFqdn(zone)) + relative := toRelativeRecord(fqdn, acme.ToFqdn(zone)) rec := dns.RecordSet{ Name: &relative, RecordSetProperties: &dns.RecordSetProperties{ TTL: to.Int64Ptr(60), - TxtRecords: &[]dns.TxtRecord{dns.TxtRecord{Value: &[]string{value}}}, + TxtRecords: &[]dns.TxtRecord{{Value: &[]string{value}}}, }, } + _, err = rsc.CreateOrUpdate(c.context, c.resourceGroup, zone, relative, dns.TXT, rec, "", "") - - if err != nil { - return err - } - - return nil + return err } // Returns the relative record to the domain func toRelativeRecord(domain, zone string) string { - return acmev2.UnFqdn(strings.TrimSuffix(domain, zone)) + return acme.UnFqdn(strings.TrimSuffix(domain, zone)) } // CleanUp removes the TXT record matching the specified parameters func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) zone, err := c.getHostedZoneID(fqdn) if err != nil { return err } - relative := toRelativeRecord(fqdn, acmev2.ToFqdn(zone)) - rsc := dns.NewRecordSetsClient(c.subscriptionId) + relative := toRelativeRecord(fqdn, acme.ToFqdn(zone)) + rsc := dns.NewRecordSetsClient(c.subscriptionID) spt, err := c.newServicePrincipalTokenFromCredentials(azure.PublicCloud.ResourceManagerEndpoint) - rsc.Authorizer = autorest.NewBearerAuthorizer(spt) - _, err = rsc.Delete(c.context, c.resourceGroup, zone, relative, dns.TXT, "") if err != nil { return err } - return nil + rsc.Authorizer = autorest.NewBearerAuthorizer(spt) + + _, err = rsc.Delete(c.context, c.resourceGroup, zone, relative, dns.TXT, "") + return err } // Checks that azure has a zone for this domain name. func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) { - authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) + authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return "", err } // Now we want to to Azure and get the zone. spt, err := c.newServicePrincipalTokenFromCredentials(azure.PublicCloud.ResourceManagerEndpoint) + if err != nil { + return "", err + } - dc := dns.NewZonesClient(c.subscriptionId) + dc := dns.NewZonesClient(c.subscriptionID) dc.Authorizer = autorest.NewBearerAuthorizer(spt) - zone, err := dc.Get(c.context, c.resourceGroup, acmev2.UnFqdn(authZone)) - + zone, err := dc.Get(c.context, c.resourceGroup, acme.UnFqdn(authZone)) if err != nil { return "", err } @@ -146,9 +154,9 @@ func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) { // NewServicePrincipalTokenFromCredentials creates a new ServicePrincipalToken using values of the // passed credentials map. func (c *DNSProvider) newServicePrincipalTokenFromCredentials(scope string) (*adal.ServicePrincipalToken, error) { - oauthConfig, err := adal.NewOAuthConfig(azure.PublicCloud.ActiveDirectoryEndpoint, c.tenantId) + oauthConfig, err := adal.NewOAuthConfig(azure.PublicCloud.ActiveDirectoryEndpoint, c.tenantID) if err != nil { - panic(err) + return nil, err } - return adal.NewServicePrincipalToken(*oauthConfig, c.clientId, c.clientSecret, scope) + return adal.NewServicePrincipalToken(*oauthConfig, c.clientID, c.clientSecret, scope) } diff --git a/vendor/github.com/xenolf/lego/providers/dns/bluecat/bluecat.go b/vendor/github.com/xenolf/lego/providers/dns/bluecat/bluecat.go index 2bb02230e..b0374b7e6 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/bluecat/bluecat.go +++ b/vendor/github.com/xenolf/lego/providers/dns/bluecat/bluecat.go @@ -15,26 +15,26 @@ import ( "io/ioutil" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) -const bluecatUrlTemplate = "%s/Services/REST/v1" +const bluecatURLTemplate = "%s/Services/REST/v1" const configType = "Configuration" const viewType = "View" const txtType = "TXTRecord" const zoneType = "Zone" type entityResponse struct { - Id uint `json:"id"` + ID uint `json:"id"` Name string `json:"name"` Type string `json:"type"` Properties string `json:"properties"` } -// DNSProvider is an implementation of the acmev2.ChallengeProvider interface that uses +// DNSProvider is an implementation of the acme.ChallengeProvider interface that uses // Bluecat's Address Manager REST API to manage TXT records for a domain. type DNSProvider struct { - baseUrl string + baseURL string userName string password string configName string @@ -56,7 +56,7 @@ func NewDNSProvider() (*DNSProvider, error) { password := os.Getenv("BLUECAT_PASSWORD") configName := os.Getenv("BLUECAT_CONFIG_NAME") dnsView := os.Getenv("BLUECAT_DNS_VIEW") - httpClient := http.Client{Timeout: time.Duration(30 * time.Second)} + httpClient := http.Client{Timeout: 30 * time.Second} return NewDNSProviderCredentials(server, userName, password, configName, dnsView, httpClient) } @@ -68,7 +68,7 @@ func NewDNSProviderCredentials(server, userName, password, configName, dnsView s } return &DNSProvider{ - baseUrl: fmt.Sprintf(bluecatUrlTemplate, server), + baseURL: fmt.Sprintf(bluecatURLTemplate, server), userName: userName, password: password, configName: configName, @@ -80,7 +80,7 @@ func NewDNSProviderCredentials(server, userName, password, configName, dnsView s // Send a REST request, using query parameters specified. The Authorization // header will be set if we have an active auth token func (d *DNSProvider) sendRequest(method, resource string, payload interface{}, queryArgs map[string]string) (*http.Response, error) { - url := fmt.Sprintf("%s/%s", d.baseUrl, resource) + url := fmt.Sprintf("%s/%s", d.baseURL, resource) body, err := json.Marshal(payload) if err != nil { @@ -160,14 +160,14 @@ func (d *DNSProvider) logout() error { if resp.StatusCode != 200 { return fmt.Errorf("Bluecat API request failed to delete session with HTTP status code %d", resp.StatusCode) - } else { - authBytes, _ := ioutil.ReadAll(resp.Body) - authResp := string(authBytes) + } - if !strings.Contains(authResp, "successfully") { - msg := strings.Trim(authResp, "\"") - return fmt.Errorf("Bluecat API request failed to delete session: %s", msg) - } + authBytes, _ := ioutil.ReadAll(resp.Body) + authResp := string(authBytes) + + if !strings.Contains(authResp, "successfully") { + msg := strings.Trim(authResp, "\"") + return fmt.Errorf("Bluecat API request failed to delete session: %s", msg) } d.token = "" @@ -176,7 +176,7 @@ func (d *DNSProvider) logout() error { } // Lookup the entity ID of the configuration named in our properties -func (d *DNSProvider) lookupConfId() (uint, error) { +func (d *DNSProvider) lookupConfID() (uint, error) { queryArgs := map[string]string{ "parentId": strconv.Itoa(0), "name": d.configName, @@ -194,18 +194,18 @@ func (d *DNSProvider) lookupConfId() (uint, error) { if err != nil { return 0, err } - return conf.Id, nil + return conf.ID, nil } // Find the DNS view with the given name within -func (d *DNSProvider) lookupViewId(viewName string) (uint, error) { - confId, err := d.lookupConfId() +func (d *DNSProvider) lookupViewID(viewName string) (uint, error) { + confID, err := d.lookupConfID() if err != nil { return 0, err } queryArgs := map[string]string{ - "parentId": strconv.FormatUint(uint64(confId), 10), + "parentId": strconv.FormatUint(uint64(confID), 10), "name": d.dnsView, "type": viewType, } @@ -222,13 +222,13 @@ func (d *DNSProvider) lookupViewId(viewName string) (uint, error) { return 0, err } - return view.Id, nil + return view.ID, nil } // Return the entityId of the parent zone by recursing from the root view // Also return the simple name of the host -func (d *DNSProvider) lookupParentZoneId(viewId uint, fqdn string) (uint, string, error) { - parentViewId := viewId +func (d *DNSProvider) lookupParentZoneID(viewID uint, fqdn string) (uint, string, error) { + parentViewID := viewID name := "" if fqdn != "" { @@ -237,25 +237,24 @@ func (d *DNSProvider) lookupParentZoneId(viewId uint, fqdn string) (uint, string name = zones[0] for i := last; i > -1; i-- { - zoneId, err := d.getZone(parentViewId, zones[i]) - if err != nil || zoneId == 0 { - return parentViewId, name, err + zoneID, err := d.getZone(parentViewID, zones[i]) + if err != nil || zoneID == 0 { + return parentViewID, name, err } if i > 0 { name = strings.Join(zones[0:i], ".") } - parentViewId = zoneId + parentViewID = zoneID } } - return parentViewId, name, nil + return parentViewID, name, nil } // Get the DNS zone with the specified name under the parentId -func (d *DNSProvider) getZone(parentId uint, name string) (uint, error) { - +func (d *DNSProvider) getZone(parentID uint, name string) (uint, error) { queryArgs := map[string]string{ - "parentId": strconv.FormatUint(uint64(parentId), 10), + "parentId": strconv.FormatUint(uint64(parentID), 10), "name": name, "type": zoneType, } @@ -276,29 +275,32 @@ func (d *DNSProvider) getZone(parentId uint, name string) (uint, error) { return 0, err } - return zone.Id, nil + return zone.ID, nil } // Present creates a TXT record using the specified parameters // This will *not* create a subzone to contain the TXT record, // so make sure the FQDN specified is within an extant zone. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) err := d.login() if err != nil { return err } - viewId, err := d.lookupViewId(d.dnsView) + viewID, err := d.lookupViewID(d.dnsView) if err != nil { return err } - parentZoneId, name, err := d.lookupParentZoneId(viewId, fqdn) + parentZoneID, name, err := d.lookupParentZoneID(viewID, fqdn) + if err != nil { + return err + } queryArgs := map[string]string{ - "parentId": strconv.FormatUint(uint64(parentZoneId), 10), + "parentId": strconv.FormatUint(uint64(parentZoneID), 10), } body := bluecatEntity{ @@ -322,23 +324,18 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("Bluecat API addEntity request failed: %s", addTxtResp) } - err = d.deploy(uint(parentZoneId)) + err = d.deploy(parentZoneID) if err != nil { return err } - err = d.logout() - if err != nil { - return err - } - - return nil + return d.logout() } // Deploy the DNS config for the specified entity to the authoritative servers -func (d *DNSProvider) deploy(entityId uint) error { +func (d *DNSProvider) deploy(entityID uint) error { queryArgs := map[string]string{ - "entityId": strconv.FormatUint(uint64(entityId), 10), + "entityId": strconv.FormatUint(uint64(entityID), 10), } resp, err := d.sendRequest("POST", "quickDeploy", nil, queryArgs) @@ -353,25 +350,25 @@ func (d *DNSProvider) deploy(entityId uint) error { // CleanUp removes the TXT record matching the specified parameters func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) err := d.login() if err != nil { return err } - viewId, err := d.lookupViewId(d.dnsView) + viewID, err := d.lookupViewID(d.dnsView) if err != nil { return err } - parentId, name, err := d.lookupParentZoneId(viewId, fqdn) + parentID, name, err := d.lookupParentZoneID(viewID, fqdn) if err != nil { return err } queryArgs := map[string]string{ - "parentId": strconv.FormatUint(uint64(parentId), 10), + "parentId": strconv.FormatUint(uint64(parentID), 10), "name": name, "type": txtType, } @@ -388,7 +385,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return err } queryArgs = map[string]string{ - "objectId": strconv.FormatUint(uint64(txtRec.Id), 10), + "objectId": strconv.FormatUint(uint64(txtRec.ID), 10), } resp, err = d.sendRequest("DELETE", "delete", nil, queryArgs) @@ -397,20 +394,15 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } defer resp.Body.Close() - err = d.deploy(parentId) + err = d.deploy(parentID) if err != nil { return err } - err = d.logout() - if err != nil { - return err - } - - return nil + return d.logout() } -//JSON body for Bluecat entity requests and responses +// JSON body for Bluecat entity requests and responses type bluecatEntity struct { ID string `json:"id,omitempty"` Name string `json:"name"` diff --git a/vendor/github.com/xenolf/lego/providers/dns/cloudflare/cloudflare.go b/vendor/github.com/xenolf/lego/providers/dns/cloudflare/cloudflare.go index ccea830ad..9b6556a64 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/cloudflare/cloudflare.go +++ b/vendor/github.com/xenolf/lego/providers/dns/cloudflare/cloudflare.go @@ -7,18 +7,20 @@ import ( "encoding/json" "fmt" "io" + "io/ioutil" "net/http" "os" + "strings" "time" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) // CloudFlareAPIURL represents the API endpoint to call. // TODO: Unexport? const CloudFlareAPIURL = "https://api.cloudflare.com/client/v4" -// DNSProvider is an implementation of the acmev2.ChallengeProvider interface +// DNSProvider is an implementation of the acme.ChallengeProvider interface type DNSProvider struct { authEmail string authKey string @@ -37,7 +39,14 @@ func NewDNSProvider() (*DNSProvider, error) { // DNSProvider instance configured for cloudflare. func NewDNSProviderCredentials(email, key string) (*DNSProvider, error) { if email == "" || key == "" { - return nil, fmt.Errorf("CloudFlare credentials missing") + missingEnvVars := []string{} + if email == "" { + missingEnvVars = append(missingEnvVars, "CLOUDFLARE_EMAIL") + } + if key == "" { + missingEnvVars = append(missingEnvVars, "CLOUDFLARE_API_KEY") + } + return nil, fmt.Errorf("CloudFlare credentials missing: %s", strings.Join(missingEnvVars, ",")) } return &DNSProvider{ @@ -54,7 +63,7 @@ func (c *DNSProvider) Timeout() (timeout, interval time.Duration) { // Present creates a TXT record to fulfil the dns-01 challenge func (c *DNSProvider) Present(domain, token, keyAuth string) error { - fqdn, value, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, _ := acme.DNS01Record(domain, keyAuth) zoneID, err := c.getHostedZoneID(fqdn) if err != nil { return err @@ -62,7 +71,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error { rec := cloudFlareRecord{ Type: "TXT", - Name: acmev2.UnFqdn(fqdn), + Name: acme.UnFqdn(fqdn), Content: value, TTL: 120, } @@ -73,16 +82,12 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error { } _, err = c.makeRequest("POST", fmt.Sprintf("/zones/%s/dns_records", zoneID), bytes.NewReader(body)) - if err != nil { - return err - } - - return nil + return err } // CleanUp removes the TXT record matching the specified parameters func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) record, err := c.findTxtRecord(fqdn) if err != nil { @@ -90,11 +95,7 @@ func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { } _, err = c.makeRequest("DELETE", fmt.Sprintf("/zones/%s/dns_records/%s", record.ZoneID, record.ID), nil) - if err != nil { - return err - } - - return nil + return err } func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) { @@ -104,12 +105,12 @@ func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) { Name string `json:"name"` } - authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) + authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return "", err } - result, err := c.makeRequest("GET", "/zones?name="+acmev2.UnFqdn(authZone), nil) + result, err := c.makeRequest("GET", "/zones?name="+acme.UnFqdn(authZone), nil) if err != nil { return "", err } @@ -135,7 +136,7 @@ func (c *DNSProvider) findTxtRecord(fqdn string) (*cloudFlareRecord, error) { result, err := c.makeRequest( "GET", - fmt.Sprintf("/zones/%s/dns_records?per_page=1000&type=TXT&name=%s", zoneID, acmev2.UnFqdn(fqdn)), + fmt.Sprintf("/zones/%s/dns_records?per_page=1000&type=TXT&name=%s", zoneID, acme.UnFqdn(fqdn)), nil, ) if err != nil { @@ -149,12 +150,12 @@ func (c *DNSProvider) findTxtRecord(fqdn string) (*cloudFlareRecord, error) { } for _, rec := range records { - if rec.Name == acmev2.UnFqdn(fqdn) { + if rec.Name == acme.UnFqdn(fqdn) { return &rec, nil } } - return nil, fmt.Errorf("No existing record found for %s", fqdn) + return nil, fmt.Errorf("no existing record found for %s", fqdn) } func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawMessage, error) { @@ -179,7 +180,6 @@ func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawM req.Header.Set("X-Auth-Email", c.authEmail) req.Header.Set("X-Auth-Key", c.authKey) - //req.Header.Set("User-Agent", userAgent()) client := http.Client{Timeout: 30 * time.Second} resp, err := client.Do(req) @@ -206,7 +206,11 @@ func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawM } return nil, fmt.Errorf("Cloudflare API Error \n%s", errStr) } - return nil, fmt.Errorf("Cloudflare API error") + strBody := "Unreadable body" + if body, err := ioutil.ReadAll(resp.Body); err == nil { + strBody = string(body) + } + return nil, fmt.Errorf("Cloudflare API error: the request %s sent a response with a body which is not in JSON format: %s", req.URL.String(), strBody) } return r.Result, nil diff --git a/vendor/github.com/xenolf/lego/providers/dns/cloudxns/cloudxns.go b/vendor/github.com/xenolf/lego/providers/dns/cloudxns/cloudxns.go index e69c60260..1204af149 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/cloudxns/cloudxns.go +++ b/vendor/github.com/xenolf/lego/providers/dns/cloudxns/cloudxns.go @@ -13,12 +13,12 @@ import ( "strconv" "time" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) const cloudXNSBaseURL = "https://www.cloudxns.net/api2/" -// DNSProvider is an implementation of the acmev2.ChallengeProvider interface +// DNSProvider is an implementation of the acme.ChallengeProvider interface type DNSProvider struct { apiKey string secretKey string @@ -48,7 +48,7 @@ func NewDNSProviderCredentials(apiKey, secretKey string) (*DNSProvider, error) { // Present creates a TXT record to fulfil the dns-01 challenge. func (c *DNSProvider) Present(domain, token, keyAuth string) error { - fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) zoneID, err := c.getHostedZoneID(fqdn) if err != nil { return err @@ -59,7 +59,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) zoneID, err := c.getHostedZoneID(fqdn) if err != nil { return err @@ -79,7 +79,7 @@ func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) { Domain string `json:"domain"` } - authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) + authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return "", err } @@ -101,7 +101,7 @@ func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) { } } - return "", fmt.Errorf("Zone %s not found in cloudxns for domain %s", authZone, fqdn) + return "", fmt.Errorf("zone %s not found in cloudxns for domain %s", authZone, fqdn) } func (c *DNSProvider) findTxtRecord(zoneID, fqdn string) (string, error) { @@ -117,12 +117,12 @@ func (c *DNSProvider) findTxtRecord(zoneID, fqdn string) (string, error) { } for _, record := range records { - if record.Host == acmev2.UnFqdn(fqdn) && record.Type == "TXT" { + if record.Host == acme.UnFqdn(fqdn) && record.Type == "TXT" { return record.RecordID, nil } } - return "", fmt.Errorf("No existing record found for %s", fqdn) + return "", fmt.Errorf("no existing record found for %s", fqdn) } func (c *DNSProvider) addTxtRecord(zoneID, fqdn, value string, ttl int) error { @@ -133,7 +133,7 @@ func (c *DNSProvider) addTxtRecord(zoneID, fqdn, value string, ttl int) error { payload := cloudXNSRecord{ ID: id, - Host: acmev2.UnFqdn(fqdn), + Host: acme.UnFqdn(fqdn), Value: value, Type: "TXT", LineID: 1, @@ -146,11 +146,7 @@ func (c *DNSProvider) addTxtRecord(zoneID, fqdn, value string, ttl int) error { } _, err = c.makeRequest("POST", "record", body) - if err != nil { - return err - } - - return nil + return err } func (c *DNSProvider) delTxtRecord(recordID, zoneID string) error { @@ -183,7 +179,7 @@ func (c *DNSProvider) makeRequest(method, uri string, body []byte) (json.RawMess req.Header.Set("API-HMAC", c.hmac(url, requestDate, string(body))) req.Header.Set("API-FORMAT", "json") - resp, err := acmev2.HTTPClient.Do(req) + resp, err := acme.HTTPClient.Do(req) if err != nil { return nil, err } diff --git a/vendor/github.com/xenolf/lego/providers/dns/digitalocean/digitalocean.go b/vendor/github.com/xenolf/lego/providers/dns/digitalocean/digitalocean.go index deb0548be..ef749e6fa 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/digitalocean/digitalocean.go +++ b/vendor/github.com/xenolf/lego/providers/dns/digitalocean/digitalocean.go @@ -11,10 +11,10 @@ import ( "sync" "time" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) -// DNSProvider is an implementation of the acmev2.ChallengeProvider interface +// DNSProvider is an implementation of the acme.ChallengeProvider interface // that uses DigitalOcean's REST API to manage TXT records for a domain. type DNSProvider struct { apiAuthToken string @@ -22,6 +22,12 @@ type DNSProvider struct { recordIDsMu sync.Mutex } +// 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 60 * time.Second, 5 * time.Second +} + // NewDNSProvider returns a DNSProvider instance configured for Digital // Ocean. Credentials must be passed in the environment variable: // DO_AUTH_TOKEN. @@ -44,32 +50,14 @@ func NewDNSProviderCredentials(apiAuthToken string) (*DNSProvider, error) { // Present creates a TXT record using the specified parameters func (d *DNSProvider) Present(domain, token, keyAuth string) error { - // txtRecordRequest represents the request body to DO's API to make a TXT record - type txtRecordRequest struct { - RecordType string `json:"type"` - Name string `json:"name"` - Data string `json:"data"` - TTL int `json:"ttl"` - } + fqdn, value, _ := acme.DNS01Record(domain, keyAuth) - // txtRecordResponse represents a response from DO's API after making a TXT record - type txtRecordResponse struct { - DomainRecord struct { - ID int `json:"id"` - Type string `json:"type"` - Name string `json:"name"` - Data string `json:"data"` - } `json:"domain_record"` - } - - fqdn, value, _ := acmev2.DNS01Record(domain, keyAuth) - - authZone, err := acmev2.FindZoneByFqdn(acmev2.ToFqdn(domain), acmev2.RecursiveNameservers) + authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers) if err != nil { - return fmt.Errorf("Could not determine zone for domain: '%s'. %s", domain, err) + return fmt.Errorf("could not determine zone for domain: '%s'. %s", domain, err) } - authZone = acmev2.UnFqdn(authZone) + authZone = acme.UnFqdn(authZone) reqURL := fmt.Sprintf("%s/v2/domains/%s/records", digitalOceanBaseURL, authZone) reqData := txtRecordRequest{RecordType: "TXT", Name: fqdn, Data: value, TTL: 30} @@ -113,7 +101,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) // get the record's unique ID from when we created it d.recordIDsMu.Lock() @@ -123,12 +111,12 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("unknown record ID for '%s'", fqdn) } - authZone, err := acmev2.FindZoneByFqdn(acmev2.ToFqdn(domain), acmev2.RecursiveNameservers) + authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers) if err != nil { - return fmt.Errorf("Could not determine zone for domain: '%s'. %s", domain, err) + return fmt.Errorf("could not determine zone for domain: '%s'. %s", domain, err) } - authZone = acmev2.UnFqdn(authZone) + authZone = acme.UnFqdn(authZone) reqURL := fmt.Sprintf("%s/v2/domains/%s/records/%d", digitalOceanBaseURL, authZone, recordID) req, err := http.NewRequest("DELETE", reqURL, nil) @@ -166,8 +154,20 @@ type digitalOceanAPIError struct { var digitalOceanBaseURL = "https://api.digitalocean.com" -// 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 60 * time.Second, 5 * time.Second +// txtRecordRequest represents the request body to DO's API to make a TXT record +type txtRecordRequest struct { + RecordType string `json:"type"` + Name string `json:"name"` + Data string `json:"data"` + TTL int `json:"ttl"` +} + +// txtRecordResponse represents a response from DO's API after making a TXT record +type txtRecordResponse struct { + DomainRecord struct { + ID int `json:"id"` + Type string `json:"type"` + Name string `json:"name"` + Data string `json:"data"` + } `json:"domain_record"` } 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 2f1344a69..d00028617 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/dns_providers.go +++ b/vendor/github.com/xenolf/lego/providers/dns/dns_providers.go @@ -1,10 +1,9 @@ -// Factory for DNS providers package dns import ( "fmt" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" "github.com/xenolf/lego/providers/dns/auroradns" "github.com/xenolf/lego/providers/dns/azure" "github.com/xenolf/lego/providers/dns/bluecat" @@ -21,9 +20,9 @@ import ( "github.com/xenolf/lego/providers/dns/fastdns" "github.com/xenolf/lego/providers/dns/gandi" "github.com/xenolf/lego/providers/dns/gandiv5" + "github.com/xenolf/lego/providers/dns/gcloud" "github.com/xenolf/lego/providers/dns/glesys" "github.com/xenolf/lego/providers/dns/godaddy" - "github.com/xenolf/lego/providers/dns/googlecloud" "github.com/xenolf/lego/providers/dns/lightsail" "github.com/xenolf/lego/providers/dns/linode" "github.com/xenolf/lego/providers/dns/namecheap" @@ -38,9 +37,10 @@ import ( "github.com/xenolf/lego/providers/dns/vultr" ) -func NewDNSChallengeProviderByName(name string) (acmev2.ChallengeProvider, error) { +// NewDNSChallengeProviderByName Factory for DNS providers +func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error) { var err error - var provider acmev2.ChallengeProvider + var provider acme.ChallengeProvider switch name { case "azure": provider, err = azure.NewDNSProvider() @@ -75,7 +75,7 @@ func NewDNSChallengeProviderByName(name string) (acmev2.ChallengeProvider, error case "glesys": provider, err = glesys.NewDNSProvider() case "gcloud": - provider, err = googlecloud.NewDNSProvider() + provider, err = gcloud.NewDNSProvider() case "godaddy": provider, err = godaddy.NewDNSProvider() case "lightsail": @@ -83,7 +83,7 @@ func NewDNSChallengeProviderByName(name string) (acmev2.ChallengeProvider, error case "linode": provider, err = linode.NewDNSProvider() case "manual": - provider, err = acmev2.NewDNSProviderManual() + provider, err = acme.NewDNSProviderManual() case "namecheap": provider, err = namecheap.NewDNSProvider() case "namedotcom": @@ -107,7 +107,7 @@ func NewDNSChallengeProviderByName(name string) (acmev2.ChallengeProvider, error case "exec": provider, err = exec.NewDNSProvider() default: - err = fmt.Errorf("Unrecognised DNS provider: %s", name) + err = fmt.Errorf("unrecognised DNS provider: %s", name) } return provider, err } diff --git a/vendor/github.com/xenolf/lego/providers/dns/dnsimple/dnsimple.go b/vendor/github.com/xenolf/lego/providers/dns/dnsimple/dnsimple.go index cebdff1ce..30c7fc2f6 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/dnsimple/dnsimple.go +++ b/vendor/github.com/xenolf/lego/providers/dns/dnsimple/dnsimple.go @@ -9,10 +9,10 @@ import ( "strings" "github.com/dnsimple/dnsimple-go/dnsimple" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) -// DNSProvider is an implementation of the acmev2.ChallengeProvider interface. +// DNSProvider is an implementation of the acme.ChallengeProvider interface. type DNSProvider struct { client *dnsimple.Client } @@ -23,14 +23,14 @@ type DNSProvider struct { // See: https://developer.dnsimple.com/v2/#authentication func NewDNSProvider() (*DNSProvider, error) { accessToken := os.Getenv("DNSIMPLE_OAUTH_TOKEN") - baseUrl := os.Getenv("DNSIMPLE_BASE_URL") + baseURL := os.Getenv("DNSIMPLE_BASE_URL") - return NewDNSProviderCredentials(accessToken, baseUrl) + return NewDNSProviderCredentials(accessToken, baseURL) } // NewDNSProviderCredentials uses the supplied credentials to return a // DNSProvider instance configured for dnsimple. -func NewDNSProviderCredentials(accessToken, baseUrl string) (*DNSProvider, error) { +func NewDNSProviderCredentials(accessToken, baseURL string) (*DNSProvider, error) { if accessToken == "" { return nil, fmt.Errorf("DNSimple OAuth token is missing") } @@ -38,8 +38,8 @@ func NewDNSProviderCredentials(accessToken, baseUrl string) (*DNSProvider, error client := dnsimple.NewClient(dnsimple.NewOauthTokenCredentials(accessToken)) client.UserAgent = "lego" - if baseUrl != "" { - client.BaseURL = baseUrl + if baseURL != "" { + client.BaseURL = baseURL } return &DNSProvider{client: client}, nil @@ -47,7 +47,7 @@ func NewDNSProviderCredentials(accessToken, baseUrl string) (*DNSProvider, error // Present creates a TXT record to fulfil the dns-01 challenge. func (c *DNSProvider) Present(domain, token, keyAuth string) error { - fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) zoneName, err := c.getHostedZone(domain) @@ -71,7 +71,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) records, err := c.findTxtRecords(domain, fqdn) if err != nil { @@ -94,7 +94,7 @@ func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { } func (c *DNSProvider) getHostedZone(domain string) (string, error) { - authZone, err := acmev2.FindZoneByFqdn(acmev2.ToFqdn(domain), acmev2.RecursiveNameservers) + authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers) if err != nil { return "", err } @@ -104,7 +104,7 @@ func (c *DNSProvider) getHostedZone(domain string) (string, error) { return "", err } - zoneName := acmev2.UnFqdn(authZone) + zoneName := acme.UnFqdn(authZone) zones, err := c.client.Zones.ListZones(accountID, &dnsimple.ZoneListOptions{NameLike: zoneName}) if err != nil { @@ -119,8 +119,7 @@ func (c *DNSProvider) getHostedZone(domain string) (string, error) { } if hostedZone.ID == 0 { - return "", fmt.Errorf("Zone %s not found in DNSimple for domain %s", authZone, domain) - + return "", fmt.Errorf("zone %s not found in DNSimple for domain %s", authZone, domain) } return hostedZone.Name, nil @@ -159,7 +158,7 @@ func (c *DNSProvider) newTxtRecord(zoneName, fqdn, value string, ttl int) *dnsim } func (c *DNSProvider) extractRecordName(fqdn, domain string) string { - name := acmev2.UnFqdn(fqdn) + name := acme.UnFqdn(fqdn) if idx := strings.Index(name, "."+domain); idx != -1 { return name[:idx] } @@ -173,8 +172,8 @@ func (c *DNSProvider) getAccountID() (string, error) { } if whoamiResponse.Data.Account == nil { - return "", fmt.Errorf("DNSimple user tokens are not supported, please use an account token.") + return "", fmt.Errorf("DNSimple user tokens are not supported, please use an account token") } - return strconv.Itoa(whoamiResponse.Data.Account.ID), nil + return strconv.FormatInt(whoamiResponse.Data.Account.ID, 10), nil } diff --git a/vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/dnsmadeeasy.go b/vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/dnsmadeeasy.go index 9ea7b51f7..11b1ac44d 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/dnsmadeeasy.go +++ b/vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/dnsmadeeasy.go @@ -14,10 +14,10 @@ import ( "strings" "time" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) -// DNSProvider is an implementation of the acmev2.ChallengeProvider interface that uses +// DNSProvider is an implementation of the acme.ChallengeProvider interface that uses // DNSMadeEasy's DNS API to manage TXT records for a domain. type DNSProvider struct { baseURL string @@ -77,9 +77,9 @@ func NewDNSProviderCredentials(baseURL, apiKey, apiSecret string) (*DNSProvider, // Present creates a TXT record using the specified parameters func (d *DNSProvider) Present(domainName, token, keyAuth string) error { - fqdn, value, ttl := acmev2.DNS01Record(domainName, keyAuth) + fqdn, value, ttl := acme.DNS01Record(domainName, keyAuth) - authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) + authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return err } @@ -95,18 +95,14 @@ func (d *DNSProvider) Present(domainName, token, keyAuth string) error { record := &Record{Type: "TXT", Name: name, Value: value, TTL: ttl} err = d.createRecord(domain, record) - if err != nil { - return err - } - - return nil + return err } // CleanUp removes the TXT records matching the specified parameters func (d *DNSProvider) CleanUp(domainName, token, keyAuth string) error { - fqdn, _, _ := acmev2.DNS01Record(domainName, keyAuth) + fqdn, _, _ := acme.DNS01Record(domainName, keyAuth) - authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) + authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return err } @@ -226,7 +222,7 @@ func (d *DNSProvider) sendRequest(method, resource string, payload interface{}) } client := &http.Client{ Transport: transport, - Timeout: time.Duration(10 * time.Second), + Timeout: 10 * time.Second, } resp, err := client.Do(req) if err != nil { diff --git a/vendor/github.com/xenolf/lego/providers/dns/dnspod/dnspod.go b/vendor/github.com/xenolf/lego/providers/dns/dnspod/dnspod.go index 4942eba75..0ce08a8bb 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/dnspod/dnspod.go +++ b/vendor/github.com/xenolf/lego/providers/dns/dnspod/dnspod.go @@ -8,10 +8,10 @@ import ( "strings" "github.com/decker502/dnspod-go" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) -// DNSProvider is an implementation of the acmev2.ChallengeProvider interface. +// DNSProvider is an implementation of the acme.ChallengeProvider interface. type DNSProvider struct { client *dnspod.Client } @@ -38,7 +38,7 @@ func NewDNSProviderCredentials(key string) (*DNSProvider, error) { // Present creates a TXT record to fulfil the dns-01 challenge. func (c *DNSProvider) Present(domain, token, keyAuth string) error { - fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) zoneID, zoneName, err := c.getHostedZone(domain) if err != nil { return err @@ -55,7 +55,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) records, err := c.findTxtRecords(domain, fqdn) if err != nil { @@ -82,14 +82,14 @@ func (c *DNSProvider) getHostedZone(domain string) (string, string, error) { return "", "", fmt.Errorf("dnspod API call failed: %v", err) } - authZone, err := acmev2.FindZoneByFqdn(acmev2.ToFqdn(domain), acmev2.RecursiveNameservers) + authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers) if err != nil { return "", "", err } var hostedZone dnspod.Domain for _, zone := range zones { - if zone.Name == acmev2.UnFqdn(authZone) { + if zone.Name == acme.UnFqdn(authZone) { hostedZone = zone } } @@ -138,7 +138,7 @@ func (c *DNSProvider) findTxtRecords(domain, fqdn string) ([]dnspod.Record, erro } func (c *DNSProvider) extractRecordName(fqdn, domain string) string { - name := acmev2.UnFqdn(fqdn) + name := acme.UnFqdn(fqdn) if idx := strings.Index(name, "."+domain); idx != -1 { return name[:idx] } diff --git a/vendor/github.com/xenolf/lego/providers/dns/duckdns/duckdns.go b/vendor/github.com/xenolf/lego/providers/dns/duckdns/duckdns.go index defafe35c..855838ae6 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/duckdns/duckdns.go +++ b/vendor/github.com/xenolf/lego/providers/dns/duckdns/duckdns.go @@ -1,5 +1,4 @@ -// Adds lego support for http://duckdns.org . -// +// Package duckdns Adds lego support for http://duckdns.org . // See http://www.duckdns.org/spec.jsp for more info on updating TXT records. package duckdns @@ -9,7 +8,7 @@ import ( "io/ioutil" "os" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) // DNSProvider adds and removes the record for the DNS challenge @@ -47,7 +46,7 @@ func makeDuckdnsURL(domain, token, txt string) string { } func issueDuckdnsRequest(url string) error { - response, err := acmev2.HTTPClient.Get(url) + response, err := acme.HTTPClient.Get(url) if err != nil { return err } @@ -70,7 +69,7 @@ func issueDuckdnsRequest(url string) error { // // To update the TXT record we just need to make one simple get request. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - _, txtRecord, _ := acmev2.DNS01Record(domain, keyAuth) + _, txtRecord, _ := acme.DNS01Record(domain, keyAuth) url := makeDuckdnsURL(domain, d.token, txtRecord) return issueDuckdnsRequest(url) } diff --git a/vendor/github.com/xenolf/lego/providers/dns/dyn/dyn.go b/vendor/github.com/xenolf/lego/providers/dns/dyn/dyn.go index b75febce8..ad65fab61 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/dyn/dyn.go +++ b/vendor/github.com/xenolf/lego/providers/dns/dyn/dyn.go @@ -11,7 +11,7 @@ import ( "strconv" "time" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) var dynBaseURL = "https://api.dynect.net/REST" @@ -30,7 +30,7 @@ type dynResponse struct { Messages json.RawMessage `json:"msgs"` } -// DNSProvider is an implementation of the acmev2.ChallengeProvider interface that uses +// DNSProvider is an implementation of the acme.ChallengeProvider interface that uses // Dyn's Managed DNS API to manage TXT records for a domain. type DNSProvider struct { customerName string @@ -80,7 +80,7 @@ func (d *DNSProvider) sendRequest(method, resource string, payload interface{}) req.Header.Set("Auth-Token", d.token) } - client := &http.Client{Timeout: time.Duration(10 * time.Second)} + client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Do(req) if err != nil { return nil, err @@ -158,7 +158,7 @@ func (d *DNSProvider) logout() error { req.Header.Set("Content-Type", "application/json") req.Header.Set("Auth-Token", d.token) - client := &http.Client{Timeout: time.Duration(10 * time.Second)} + client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Do(req) if err != nil { return err @@ -176,9 +176,9 @@ func (d *DNSProvider) logout() error { // Present creates a TXT record using the specified parameters func (d *DNSProvider) Present(domain, token, keyAuth string) error { - fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) - authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) + authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return err } @@ -206,12 +206,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return err } - err = d.logout() - if err != nil { - return err - } - - return nil + return d.logout() } func (d *DNSProvider) publish(zone, notes string) error { @@ -222,19 +217,16 @@ func (d *DNSProvider) publish(zone, notes string) error { pub := &publish{Publish: true, Notes: notes} resource := fmt.Sprintf("Zone/%s/", zone) - _, err := d.sendRequest("PUT", resource, pub) - if err != nil { - return err - } - return nil + _, err := d.sendRequest("PUT", resource, pub) + return err } // CleanUp removes the TXT record matching the specified parameters func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) - authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) + authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return err } @@ -253,7 +245,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { req.Header.Set("Content-Type", "application/json") req.Header.Set("Auth-Token", d.token) - client := &http.Client{Timeout: time.Duration(10 * time.Second)} + client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Do(req) if err != nil { return err @@ -269,10 +261,5 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return err } - err = d.logout() - if err != nil { - return err - } - - return nil + return d.logout() } diff --git a/vendor/github.com/xenolf/lego/providers/dns/exec/exec.go b/vendor/github.com/xenolf/lego/providers/dns/exec/exec.go index 86f1aa5ee..ee140ae76 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/exec/exec.go +++ b/vendor/github.com/xenolf/lego/providers/dns/exec/exec.go @@ -31,7 +31,7 @@ import ( "os/exec" "strconv" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) // DNSProvider adds and removes the record for the DNS challenge by calling a @@ -53,7 +53,7 @@ func NewDNSProvider() (*DNSProvider, error) { // Present creates a TXT record to fulfil the dns-01 challenge. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) cmd := exec.Command(d.program, "present", fqdn, value, strconv.Itoa(ttl)) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr @@ -63,7 +63,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) cmd := exec.Command(d.program, "cleanup", fqdn, value, strconv.Itoa(ttl)) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr diff --git a/vendor/github.com/xenolf/lego/providers/dns/exoscale/exoscale.go b/vendor/github.com/xenolf/lego/providers/dns/exoscale/exoscale.go index 5d97ac085..1467e8d75 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/exoscale/exoscale.go +++ b/vendor/github.com/xenolf/lego/providers/dns/exoscale/exoscale.go @@ -8,15 +8,15 @@ import ( "os" "github.com/exoscale/egoscale" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) -// DNSProvider is an implementation of the acmev2.ChallengeProvider interface. +// DNSProvider is an implementation of the acme.ChallengeProvider interface. type DNSProvider struct { client *egoscale.Client } -// Credentials must be passed in the environment variables: +// NewDNSProvider Credentials must be passed in the environment variables: // EXOSCALE_API_KEY, EXOSCALE_API_SECRET, EXOSCALE_ENDPOINT. func NewDNSProvider() (*DNSProvider, error) { key := os.Getenv("EXOSCALE_API_KEY") @@ -25,7 +25,7 @@ func NewDNSProvider() (*DNSProvider, error) { return NewDNSProviderClient(key, secret, endpoint) } -// Uses the supplied parameters to return a DNSProvider instance +// NewDNSProviderClient Uses the supplied parameters to return a DNSProvider instance // configured for Exoscale. func NewDNSProviderClient(key, secret, endpoint string) (*DNSProvider, error) { if key == "" || secret == "" { @@ -42,13 +42,13 @@ func NewDNSProviderClient(key, secret, endpoint string) (*DNSProvider, error) { // Present creates a TXT record to fulfil the dns-01 challenge. func (c *DNSProvider) Present(domain, token, keyAuth string) error { - fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) zone, recordName, err := c.FindZoneAndRecordName(fqdn, domain) if err != nil { return err } - recordID, err := c.FindExistingRecordId(zone, recordName) + recordID, err := c.FindExistingRecordID(zone, recordName) if err != nil { return err } @@ -78,13 +78,13 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the record matching the specified parameters. func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) zone, recordName, err := c.FindZoneAndRecordName(fqdn, domain) if err != nil { return err } - recordID, err := c.FindExistingRecordId(zone, recordName) + recordID, err := c.FindExistingRecordID(zone, recordName) if err != nil { return err } @@ -99,9 +99,9 @@ func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { return nil } -// Query Exoscale to find an existing record for this name. +// FindExistingRecordID Query Exoscale to find an existing record for this name. // Returns nil if no record could be found -func (c *DNSProvider) FindExistingRecordId(zone, recordName string) (int64, error) { +func (c *DNSProvider) FindExistingRecordID(zone, recordName string) (int64, error) { records, err := c.client.GetRecords(zone) if err != nil { return -1, errors.New("Error while retrievening DNS records: " + err.Error()) @@ -114,14 +114,14 @@ func (c *DNSProvider) FindExistingRecordId(zone, recordName string) (int64, erro return 0, nil } -// Extract DNS zone and DNS entry name +// FindZoneAndRecordName Extract DNS zone and DNS entry name func (c *DNSProvider) FindZoneAndRecordName(fqdn, domain string) (string, string, error) { - zone, err := acmev2.FindZoneByFqdn(acmev2.ToFqdn(domain), acmev2.RecursiveNameservers) + zone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers) if err != nil { return "", "", err } - zone = acmev2.UnFqdn(zone) - name := acmev2.UnFqdn(fqdn) + zone = acme.UnFqdn(zone) + name := acme.UnFqdn(fqdn) name = name[:len(name)-len("."+zone)] return zone, name, nil 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 349f12d49..dcbb93e5b 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/fastdns/fastdns.go +++ b/vendor/github.com/xenolf/lego/providers/dns/fastdns/fastdns.go @@ -7,10 +7,10 @@ import ( configdns "github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v1" "github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) -// DNSProvider is an implementation of the acmev2.ChallengeProvider interface. +// DNSProvider is an implementation of the acme.ChallengeProvider interface. type DNSProvider struct { config edgegrid.Config } @@ -47,7 +47,7 @@ func NewDNSProviderClient(host, clientToken, clientSecret, accessToken string) ( // Present creates a TXT record to fullfil the dns-01 challenge. func (c *DNSProvider) Present(domain, token, keyAuth string) error { - fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) zoneName, recordName, err := c.findZoneAndRecordName(fqdn, domain) if err != nil { return err @@ -81,7 +81,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the record matching the specified parameters. func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) zoneName, recordName, err := c.findZoneAndRecordName(fqdn, domain) if err != nil { return err @@ -108,12 +108,12 @@ func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { } func (c *DNSProvider) findZoneAndRecordName(fqdn, domain string) (string, string, error) { - zone, err := acmev2.FindZoneByFqdn(acmev2.ToFqdn(domain), acmev2.RecursiveNameservers) + zone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers) if err != nil { return "", "", err } - zone = acmev2.UnFqdn(zone) - name := acmev2.UnFqdn(fqdn) + zone = acme.UnFqdn(zone) + name := acme.UnFqdn(fqdn) name = name[:len(name)-len("."+zone)] return zone, name, nil diff --git a/vendor/github.com/xenolf/lego/providers/dns/gandi/gandi.go b/vendor/github.com/xenolf/lego/providers/dns/gandi/gandi.go index 689fff1d1..750bc8b45 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/gandi/gandi.go +++ b/vendor/github.com/xenolf/lego/providers/dns/gandi/gandi.go @@ -14,7 +14,7 @@ import ( "sync" "time" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) // Gandi API reference: http://doc.rpc.gandi.net/index.html @@ -26,7 +26,7 @@ var ( endpoint = "https://rpc.gandi.net/xmlrpc/" // findZoneByFqdn determines the DNS zone of an fqdn. It is overridden // during tests. - findZoneByFqdn = acmev2.FindZoneByFqdn + findZoneByFqdn = acme.FindZoneByFqdn ) // inProgressInfo contains information about an in-progress challenge @@ -37,7 +37,7 @@ type inProgressInfo struct { } // DNSProvider is an implementation of the -// acmev2.ChallengeProviderTimeout interface that uses Gandi's XML-RPC +// acme.ChallengeProviderTimeout interface that uses Gandi's XML-RPC // API to manage TXT records for a domain. type DNSProvider struct { apiKey string @@ -70,19 +70,22 @@ func NewDNSProviderCredentials(apiKey string) (*DNSProvider, error) { // does this by creating and activating a new temporary Gandi DNS // zone. This new zone contains the TXT record. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) if ttl < 300 { ttl = 300 // 300 is gandi minimum value for ttl } + // find authZone and Gandi zone_id for fqdn - authZone, err := findZoneByFqdn(fqdn, acmev2.RecursiveNameservers) + authZone, err := findZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return fmt.Errorf("Gandi DNS: findZoneByFqdn failure: %v", err) } + zoneID, err := d.getZoneID(authZone) if err != nil { return err } + // determine name of TXT record if !strings.HasSuffix( strings.ToLower(fqdn), strings.ToLower("."+authZone)) { @@ -90,40 +93,49 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { "Gandi DNS: unexpected authZone %s for fqdn %s", authZone, fqdn) } name := fqdn[:len(fqdn)-len("."+authZone)] + // acquire lock and check there is not a challenge already in // progress for this value of authZone d.inProgressMu.Lock() defer d.inProgressMu.Unlock() + if _, ok := d.inProgressAuthZones[authZone]; ok { return fmt.Errorf( "Gandi DNS: challenge already in progress for authZone %s", authZone) } + // perform API actions to create and activate new gandi zone // containing the required TXT record newZoneName := fmt.Sprintf( "%s [ACME Challenge %s]", - acmev2.UnFqdn(authZone), time.Now().Format(time.RFC822Z)) + acme.UnFqdn(authZone), time.Now().Format(time.RFC822Z)) + newZoneID, err := d.cloneZone(zoneID, newZoneName) if err != nil { return err } + newZoneVersion, err := d.newZoneVersion(newZoneID) if err != nil { return err } + err = d.addTXTRecord(newZoneID, newZoneVersion, name, value, ttl) if err != nil { return err } + err = d.setZoneVersion(newZoneID, newZoneVersion) if err != nil { return err } + err = d.setZone(authZone, newZoneID) if err != nil { return err } + // save data necessary for CleanUp d.inProgressFQDNs[fqdn] = inProgressInfo{ zoneID: zoneID, @@ -138,29 +150,29 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // parameters. It does this by restoring the old Gandi DNS zone and // removing the temporary one created by Present. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) // acquire lock and retrieve zoneID, newZoneID and authZone d.inProgressMu.Lock() defer d.inProgressMu.Unlock() + if _, ok := d.inProgressFQDNs[fqdn]; !ok { // if there is no cleanup information then just return return nil } + zoneID := d.inProgressFQDNs[fqdn].zoneID newZoneID := d.inProgressFQDNs[fqdn].newZoneID authZone := d.inProgressFQDNs[fqdn].authZone delete(d.inProgressFQDNs, fqdn) delete(d.inProgressAuthZones, authZone) + // perform API actions to restore old gandi zone for authZone err := d.setZone(authZone, zoneID) if err != nil { return err } - err = d.deleteZone(newZoneID) - if err != nil { - return err - } - return nil + + return d.deleteZone(newZoneID) } // Timeout returns the values (40*time.Minute, 60*time.Second) which @@ -259,15 +271,18 @@ func (e rpcError) Error() string { func httpPost(url string, bodyType string, body io.Reader) ([]byte, error) { client := http.Client{Timeout: 60 * time.Second} + resp, err := client.Post(url, bodyType, body) if err != nil { return nil, fmt.Errorf("Gandi DNS: HTTP Post Error: %v", err) } defer resp.Body.Close() + b, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("Gandi DNS: HTTP Post Error: %v", err) } + return b, nil } @@ -281,12 +296,14 @@ func rpcCall(call *methodCall, resp response) error { if err != nil { return fmt.Errorf("Gandi DNS: Marshal Error: %v", err) } + // post b = append([]byte(``+"\n"), b...) respBody, err := httpPost(endpoint, "text/xml", bytes.NewReader(b)) if err != nil { return err } + // unmarshal err = xml.Unmarshal(respBody, resp) if err != nil { @@ -313,12 +330,14 @@ func (d *DNSProvider) getZoneID(domain string) (int, error) { if err != nil { return 0, err } + var zoneID int for _, member := range resp.StructMembers { if member.Name == "zone_id" { zoneID = member.ValueInt } } + if zoneID == 0 { return 0, fmt.Errorf( "Gandi DNS: Could not determine zone_id for %s", domain) @@ -346,12 +365,14 @@ func (d *DNSProvider) cloneZone(zoneID int, name string) (int, error) { if err != nil { return 0, err } + var newZoneID int for _, member := range resp.StructMembers { if member.Name == "id" { newZoneID = member.ValueInt } } + if newZoneID == 0 { return 0, fmt.Errorf("Gandi DNS: Could not determine cloned zone_id") } @@ -370,6 +391,7 @@ func (d *DNSProvider) newZoneVersion(zoneID int) (int, error) { if err != nil { return 0, err } + if resp.Value == 0 { return 0, fmt.Errorf("Gandi DNS: Could not create new zone version") } @@ -402,10 +424,7 @@ func (d *DNSProvider) addTXTRecord(zoneID int, version int, name string, value s }, }, }, resp) - if err != nil { - return err - } - return nil + return err } func (d *DNSProvider) setZoneVersion(zoneID int, version int) error { @@ -421,6 +440,7 @@ func (d *DNSProvider) setZoneVersion(zoneID int, version int) error { if err != nil { return err } + if !resp.Value { return fmt.Errorf("Gandi DNS: could not set zone version") } @@ -440,12 +460,14 @@ func (d *DNSProvider) setZone(domain string, zoneID int) error { if err != nil { return err } + var respZoneID int for _, member := range resp.StructMembers { if member.Name == "zone_id" { respZoneID = member.ValueInt } } + if respZoneID != zoneID { return fmt.Errorf( "Gandi DNS: Could not set new zone_id for %s", domain) @@ -465,6 +487,7 @@ func (d *DNSProvider) deleteZone(zoneID int) error { if err != nil { return err } + if !resp.Value { return fmt.Errorf("Gandi DNS: could not delete zone_id") } diff --git a/vendor/github.com/xenolf/lego/providers/dns/gandiv5/gandiv5.go b/vendor/github.com/xenolf/lego/providers/dns/gandiv5/gandiv5.go index 6b96ef72a..4614723ae 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/gandiv5/gandiv5.go +++ b/vendor/github.com/xenolf/lego/providers/dns/gandiv5/gandiv5.go @@ -12,7 +12,7 @@ import ( "sync" "time" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) // Gandi API reference: http://doc.livedns.gandi.net/ @@ -21,9 +21,10 @@ var ( // endpoint is the Gandi API endpoint used by Present and // CleanUp. It is overridden during tests. endpoint = "https://dns.api.gandi.net/api/v5" + // findZoneByFqdn determines the DNS zone of an fqdn. It is overridden // during tests. - findZoneByFqdn = acmev2.FindZoneByFqdn + findZoneByFqdn = acme.FindZoneByFqdn ) // inProgressInfo contains information about an in-progress challenge @@ -33,7 +34,7 @@ type inProgressInfo struct { } // DNSProvider is an implementation of the -// acmev2.ChallengeProviderTimeout interface that uses Gandi's LiveDNS +// acme.ChallengeProviderTimeout interface that uses Gandi's LiveDNS // API to manage TXT records for a domain. type DNSProvider struct { apiKey string @@ -62,15 +63,17 @@ func NewDNSProviderCredentials(apiKey string) (*DNSProvider, error) { // Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) if ttl < 300 { ttl = 300 // 300 is gandi minimum value for ttl } + // find authZone - authZone, err := findZoneByFqdn(fqdn, acmev2.RecursiveNameservers) + authZone, err := findZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return fmt.Errorf("Gandi DNS: findZoneByFqdn failure: %v", err) } + // determine name of TXT record if !strings.HasSuffix( strings.ToLower(fqdn), strings.ToLower("."+authZone)) { @@ -78,15 +81,18 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { "Gandi DNS: unexpected authZone %s for fqdn %s", authZone, fqdn) } name := fqdn[:len(fqdn)-len("."+authZone)] + // acquire lock and check there is not a challenge already in // progress for this value of authZone d.inProgressMu.Lock() defer d.inProgressMu.Unlock() + // add TXT record into authZone - err = d.addTXTRecord(acmev2.UnFqdn(authZone), name, value, ttl) + err = d.addTXTRecord(acme.UnFqdn(authZone), name, value, ttl) if err != nil { return err } + // save data necessary for CleanUp d.inProgressFQDNs[fqdn] = inProgressInfo{ authZone: authZone, @@ -97,7 +103,8 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) + // acquire lock and retrieve authZone d.inProgressMu.Lock() defer d.inProgressMu.Unlock() @@ -105,15 +112,13 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { // if there is no cleanup information then just return return nil } + fieldName := d.inProgressFQDNs[fqdn].fieldName authZone := d.inProgressFQDNs[fqdn].authZone delete(d.inProgressFQDNs, fqdn) + // delete TXT record from authZone - err := d.deleteTXTRecord(acmev2.UnFqdn(authZone), fieldName) - if err != nil { - return err - } - return nil + return d.deleteTXTRecord(acme.UnFqdn(authZone), fieldName) } // Timeout returns the values (20*time.Minute, 20*time.Second) which @@ -149,16 +154,18 @@ func (d *DNSProvider) sendRequest(method string, resource string, payload interf if err != nil { return nil, err } + req, err := http.NewRequest(method, url, bytes.NewReader(body)) if err != nil { return nil, err } + req.Header.Set("Content-Type", "application/json") if len(d.apiKey) > 0 { req.Header.Set("X-Api-Key", d.apiKey) } - client := &http.Client{Timeout: time.Duration(10 * time.Second)} + client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Do(req) if err != nil { return nil, err diff --git a/vendor/github.com/xenolf/lego/providers/dns/googlecloud/googlecloud.go b/vendor/github.com/xenolf/lego/providers/dns/gcloud/googlecloud.go similarity index 85% rename from vendor/github.com/xenolf/lego/providers/dns/googlecloud/googlecloud.go rename to vendor/github.com/xenolf/lego/providers/dns/gcloud/googlecloud.go index 052c8dae5..4a9387093 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/googlecloud/googlecloud.go +++ b/vendor/github.com/xenolf/lego/providers/dns/gcloud/googlecloud.go @@ -1,17 +1,17 @@ -// Package googlecloud implements a DNS provider for solving the DNS-01 +// Package gcloud implements a DNS provider for solving the DNS-01 // challenge using Google Cloud DNS. -package googlecloud +package gcloud import ( + "encoding/json" "fmt" "io/ioutil" "os" "time" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" "golang.org/x/net/context" - "golang.org/x/oauth2" "golang.org/x/oauth2/google" "google.golang.org/api/dns/v1" @@ -28,10 +28,10 @@ type DNSProvider struct { // A Service Account file can be passed in the environment variable: // GCE_SERVICE_ACCOUNT_FILE func NewDNSProvider() (*DNSProvider, error) { - project := os.Getenv("GCE_PROJECT") if saFile, ok := os.LookupEnv("GCE_SERVICE_ACCOUNT_FILE"); ok { - return NewDNSProviderServiceAccount(project, saFile) + return NewDNSProviderServiceAccount(saFile) } + project := os.Getenv("GCE_PROJECT") return NewDNSProviderCredentials(project) } @@ -58,10 +58,7 @@ func NewDNSProviderCredentials(project string) (*DNSProvider, error) { // NewDNSProviderServiceAccount uses the supplied service account JSON file to // return a DNSProvider instance configured for Google Cloud DNS. -func NewDNSProviderServiceAccount(project string, saFile string) (*DNSProvider, error) { - if project == "" { - return nil, fmt.Errorf("Google Cloud project name missing") - } +func NewDNSProviderServiceAccount(saFile string) (*DNSProvider, error) { if saFile == "" { return nil, fmt.Errorf("Google Cloud Service Account file missing") } @@ -70,11 +67,22 @@ func NewDNSProviderServiceAccount(project string, saFile string) (*DNSProvider, if err != nil { return nil, fmt.Errorf("Unable to read Service Account file: %v", err) } + + // read project id from service account file + var datJSON struct { + ProjectID string `json:"project_id"` + } + err = json.Unmarshal(dat, &datJSON) + if err != nil || datJSON.ProjectID == "" { + return nil, fmt.Errorf("Project ID not found in Google Cloud Service Account file") + } + project := datJSON.ProjectID + conf, err := google.JWTConfigFromJSON(dat, dns.NdevClouddnsReadwriteScope) if err != nil { return nil, fmt.Errorf("Unable to acquire config: %v", err) } - client := conf.Client(oauth2.NoContext) + client := conf.Client(context.Background()) svc, err := dns.New(client) if err != nil { @@ -88,7 +96,7 @@ func NewDNSProviderServiceAccount(project string, saFile string) (*DNSProvider, // Present creates a TXT record to fulfil the dns-01 challenge. func (c *DNSProvider) Present(domain, token, keyAuth string) error { - fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) zone, err := c.getHostedZone(domain) if err != nil { @@ -135,7 +143,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) zone, err := c.getHostedZone(domain) if err != nil { @@ -167,7 +175,7 @@ func (c *DNSProvider) Timeout() (timeout, interval time.Duration) { // getHostedZone returns the managed-zone func (c *DNSProvider) getHostedZone(domain string) (string, error) { - authZone, err := acmev2.FindZoneByFqdn(acmev2.ToFqdn(domain), acmev2.RecursiveNameservers) + authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers) if err != nil { return "", err } diff --git a/vendor/github.com/xenolf/lego/providers/dns/glesys/glesys.go b/vendor/github.com/xenolf/lego/providers/dns/glesys/glesys.go index 7f8168b23..7623b564b 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/glesys/glesys.go +++ b/vendor/github.com/xenolf/lego/providers/dns/glesys/glesys.go @@ -6,14 +6,14 @@ import ( "bytes" "encoding/json" "fmt" - "log" "net/http" "os" "strings" "sync" "time" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/log" ) // GleSYS API reference: https://github.com/GleSYS/API/wiki/API-Documentation @@ -21,24 +21,8 @@ import ( // domainAPI is the GleSYS API endpoint used by Present and CleanUp. const domainAPI = "https://api.glesys.com/domain" -var ( - // Logger is used to log API communication results; - // if nil, the default log.Logger is used. - Logger *log.Logger -) - -// logf writes a log entry. It uses Logger if not -// nil, otherwise it uses the default log.Logger. -func logf(format string, args ...interface{}) { - if Logger != nil { - Logger.Printf(format, args...) - } else { - log.Printf(format, args...) - } -} - // DNSProvider is an implementation of the -// acmev2.ChallengeProviderTimeout interface that uses GleSYS +// acme.ChallengeProviderTimeout interface that uses GleSYS // API to manage TXT records for a domain. type DNSProvider struct { apiUser string @@ -71,15 +55,16 @@ func NewDNSProviderCredentials(apiUser string, apiKey string) (*DNSProvider, err // Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) if ttl < 60 { ttl = 60 // 60 is GleSYS minimum value for ttl } // find authZone - authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) + authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return fmt.Errorf("GleSYS DNS: findZoneByFqdn failure: %v", err) } + // determine name of TXT record if !strings.HasSuffix( strings.ToLower(fqdn), strings.ToLower("."+authZone)) { @@ -87,23 +72,27 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { "GleSYS DNS: unexpected authZone %s for fqdn %s", authZone, fqdn) } name := fqdn[:len(fqdn)-len("."+authZone)] + // acquire lock and check there is not a challenge already in // progress for this value of authZone d.inProgressMu.Lock() defer d.inProgressMu.Unlock() + // add TXT record into authZone - recordId, err := d.addTXTRecord(domain, acmev2.UnFqdn(authZone), name, value, ttl) + recordID, err := d.addTXTRecord(domain, acme.UnFqdn(authZone), name, value, ttl) if err != nil { return err } + // save data necessary for CleanUp - d.activeRecords[fqdn] = recordId + d.activeRecords[fqdn] = recordID return nil } // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) + // acquire lock and retrieve authZone d.inProgressMu.Lock() defer d.inProgressMu.Unlock() @@ -111,14 +100,12 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { // if there is no cleanup information then just return return nil } - recordId := d.activeRecords[fqdn] + + recordID := d.activeRecords[fqdn] delete(d.activeRecords, fqdn) + // delete TXT record from authZone - err := d.deleteTXTRecord(domain, recordId) - if err != nil { - return err - } - return nil + return d.deleteTXTRecord(domain, recordID) } // Timeout returns the values (20*time.Minute, 20*time.Second) which @@ -135,7 +122,7 @@ type addRecordRequest struct { Host string `json:"host"` Type string `json:"type"` Data string `json:"data"` - Ttl int `json:"ttl,omitempty"` + TTL int `json:"ttl,omitempty"` } type deleteRecordRequest struct { @@ -160,14 +147,16 @@ func (d *DNSProvider) sendRequest(method string, resource string, payload interf if err != nil { return nil, err } + req, err := http.NewRequest(method, url, bytes.NewReader(body)) if err != nil { return nil, err } + req.Header.Set("Content-Type", "application/json") req.SetBasicAuth(d.apiUser, d.apiKey) - client := &http.Client{Timeout: time.Duration(10 * time.Second)} + client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Do(req) if err != nil { return nil, err @@ -177,6 +166,7 @@ func (d *DNSProvider) sendRequest(method string, resource string, payload interf if resp.StatusCode >= 400 { return nil, fmt.Errorf("GleSYS DNS: request failed with HTTP status code %d", resp.StatusCode) } + var response responseStruct err = json.NewDecoder(resp.Body).Decode(&response) @@ -191,10 +181,10 @@ func (d *DNSProvider) addTXTRecord(fqdn string, domain string, name string, valu Host: name, Type: "TXT", Data: value, - Ttl: ttl, + TTL: ttl, }) if response != nil && response.Response.Status.Code == 200 { - logf("[INFO][%s] GleSYS DNS: Successfully created recordid %d", fqdn, response.Response.Record.Recordid) + log.Printf("[INFO][%s] GleSYS DNS: Successfully created recordid %d", fqdn, response.Response.Record.Recordid) return response.Response.Record.Recordid, nil } return 0, err @@ -205,7 +195,7 @@ func (d *DNSProvider) deleteTXTRecord(fqdn string, recordid int) error { Recordid: recordid, }) if response != nil && response.Response.Status.Code == 200 { - logf("[INFO][%s] GleSYS DNS: Successfully deleted recordid %d", fqdn, recordid) + log.Printf("[INFO][%s] GleSYS DNS: Successfully deleted recordid %d", fqdn, recordid) } return err } diff --git a/vendor/github.com/xenolf/lego/providers/dns/godaddy/godaddy.go b/vendor/github.com/xenolf/lego/providers/dns/godaddy/godaddy.go index de55a9f22..54fbad032 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/godaddy/godaddy.go +++ b/vendor/github.com/xenolf/lego/providers/dns/godaddy/godaddy.go @@ -13,13 +13,13 @@ import ( "io/ioutil" "strings" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) // GoDaddyAPIURL represents the API endpoint to call. const apiURL = "https://api.godaddy.com" -// DNSProvider is an implementation of the acmev2.ChallengeProvider interface +// DNSProvider is an implementation of the acme.ChallengeProvider interface type DNSProvider struct { apiKey string apiSecret string @@ -51,7 +51,7 @@ func (c *DNSProvider) Timeout() (timeout, interval time.Duration) { } func (c *DNSProvider) extractRecordName(fqdn, domain string) string { - name := acmev2.UnFqdn(fqdn) + name := acme.UnFqdn(fqdn) if idx := strings.Index(name, "."+domain); idx != -1 { return name[:idx] } @@ -60,7 +60,7 @@ func (c *DNSProvider) extractRecordName(fqdn, domain string) string { // Present creates a TXT record to fulfil the dns-01 challenge func (c *DNSProvider) Present(domain, token, keyAuth string) error { - fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) domainZone, err := c.getZone(fqdn) if err != nil { return err @@ -76,7 +76,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error { Type: "TXT", Name: recordName, Data: value, - Ttl: ttl, + TTL: ttl, }, } @@ -99,14 +99,14 @@ func (c *DNSProvider) updateRecords(records []DNSRecord, domainZone string, reco if resp.StatusCode != http.StatusOK { bodyBytes, _ := ioutil.ReadAll(resp.Body) - return fmt.Errorf("Could not create record %v; Status: %v; Body: %s\n", string(body), resp.StatusCode, string(bodyBytes)) + return fmt.Errorf("could not create record %v; Status: %v; Body: %s", string(body), resp.StatusCode, string(bodyBytes)) } return nil } // CleanUp sets null value in the TXT DNS record as GoDaddy has no proper DELETE record method func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) domainZone, err := c.getZone(fqdn) if err != nil { return err @@ -125,12 +125,12 @@ func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { } func (c *DNSProvider) getZone(fqdn string) (string, error) { - authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) + authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return "", err } - return acmev2.UnFqdn(authZone), nil + return acme.UnFqdn(authZone), nil } func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (*http.Response, error) { @@ -147,10 +147,11 @@ func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (*http.Res return client.Do(req) } +// DNSRecord a DNS record type DNSRecord struct { Type string `json:"type"` Name string `json:"name"` Data string `json:"data"` Priority int `json:"priority,omitempty"` - Ttl int `json:"ttl,omitempty"` + TTL int `json:"ttl,omitempty"` } diff --git a/vendor/github.com/xenolf/lego/providers/dns/lightsail/lightsail.go b/vendor/github.com/xenolf/lego/providers/dns/lightsail/lightsail.go index 4bd709d13..a4d2efafc 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/lightsail/lightsail.go +++ b/vendor/github.com/xenolf/lego/providers/dns/lightsail/lightsail.go @@ -11,14 +11,14 @@ import ( "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/lightsail" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) const ( maxRetries = 5 ) -// DNSProvider implements the acmev2.ChallengeProvider interface +// DNSProvider implements the acme.ChallengeProvider interface type DNSProvider struct { client *lightsail.Lightsail } @@ -71,7 +71,7 @@ func NewDNSProvider() (*DNSProvider, error) { // Present creates a TXT record using the specified parameters func (r *DNSProvider) Present(domain, token, keyAuth string) error { - fqdn, value, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, _ := acme.DNS01Record(domain, keyAuth) value = `"` + value + `"` err := r.newTxtRecord(domain, fqdn, value) return err @@ -79,7 +79,7 @@ func (r *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters func (r *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, value, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, _ := acme.DNS01Record(domain, keyAuth) value = `"` + value + `"` params := &lightsail.DeleteDomainEntryInput{ DomainName: aws.String(domain), diff --git a/vendor/github.com/xenolf/lego/providers/dns/linode/linode.go b/vendor/github.com/xenolf/lego/providers/dns/linode/linode.go index 4c6345ac7..1e8cbc503 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/linode/linode.go +++ b/vendor/github.com/xenolf/lego/providers/dns/linode/linode.go @@ -9,7 +9,7 @@ import ( "time" "github.com/timewasted/linode/dns" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) const ( @@ -19,11 +19,11 @@ const ( ) type hostedZoneInfo struct { - domainId int + domainID int resourceName string } -// DNSProvider implements the acmev2.ChallengeProvider interface. +// DNSProvider implements the acme.ChallengeProvider interface. type DNSProvider struct { linode *dns.DNS } @@ -66,13 +66,13 @@ func (p *DNSProvider) Timeout() (timeout, interval time.Duration) { // Present creates a TXT record using the specified parameters. func (p *DNSProvider) Present(domain, token, keyAuth string) error { - fqdn, value, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, _ := acme.DNS01Record(domain, keyAuth) zone, err := p.getHostedZoneInfo(fqdn) if err != nil { return err } - if _, err = p.linode.CreateDomainResourceTXT(zone.domainId, acmev2.UnFqdn(fqdn), value, 60); err != nil { + if _, err = p.linode.CreateDomainResourceTXT(zone.domainID, acme.UnFqdn(fqdn), value, 60); err != nil { return err } @@ -81,14 +81,14 @@ func (p *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (p *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, value, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, _ := acme.DNS01Record(domain, keyAuth) zone, err := p.getHostedZoneInfo(fqdn) if err != nil { return err } // Get all TXT records for the specified domain. - resources, err := p.linode.GetResourcesByType(zone.domainId, "TXT") + resources, err := p.linode.GetResourcesByType(zone.domainID, "TXT") if err != nil { return err } @@ -101,7 +101,7 @@ func (p *DNSProvider) CleanUp(domain, token, keyAuth string) error { return err } if resp.ResourceID != resource.ResourceID { - return errors.New("Error deleting resource: resource IDs do not match!") + return errors.New("error deleting resource: resource IDs do not match") } break } @@ -112,20 +112,20 @@ func (p *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (p *DNSProvider) getHostedZoneInfo(fqdn string) (*hostedZoneInfo, error) { // Lookup the zone that handles the specified FQDN. - authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) + authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return nil, err } resourceName := strings.TrimSuffix(fqdn, "."+authZone) // Query the authority zone. - domain, err := p.linode.GetDomain(acmev2.UnFqdn(authZone)) + domain, err := p.linode.GetDomain(acme.UnFqdn(authZone)) if err != nil { return nil, err } return &hostedZoneInfo{ - domainId: domain.DomainID, + domainID: domain.DomainID, resourceName: resourceName, }, nil } diff --git a/vendor/github.com/xenolf/lego/providers/dns/namecheap/namecheap.go b/vendor/github.com/xenolf/lego/providers/dns/namecheap/namecheap.go index f21bfeeb3..2991d8171 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/namecheap/namecheap.go +++ b/vendor/github.com/xenolf/lego/providers/dns/namecheap/namecheap.go @@ -12,7 +12,7 @@ import ( "strings" "time" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) // Notes about namecheap's tool API: @@ -132,7 +132,7 @@ type challenge struct { // newChallenge builds a challenge record from a domain name, a challenge // authentication key, and a map of available TLDs. func newChallenge(domain, keyAuth string, tlds map[string]string) (*challenge, error) { - domain = acmev2.UnFqdn(domain) + domain = acme.UnFqdn(domain) parts := strings.Split(domain, ".") // Find the longest matching TLD. @@ -144,7 +144,7 @@ func newChallenge(domain, keyAuth string, tlds map[string]string) (*challenge, e } } if longest < 1 { - return nil, fmt.Errorf("Invalid domain name '%s'", domain) + return nil, fmt.Errorf("invalid domain name %q", domain) } tld := strings.Join(parts[longest:], ".") @@ -155,7 +155,7 @@ func newChallenge(domain, keyAuth string, tlds map[string]string) (*challenge, e host = strings.Join(parts[:longest-1], ".") } - key, keyValue, _ := acmev2.DNS01Record(domain, keyAuth) + key, keyValue, _ := acme.DNS01Record(domain, keyAuth) return &challenge{ domain: domain, @@ -318,7 +318,7 @@ func (d *DNSProvider) setHosts(ch *challenge, hosts []host) error { shr.Errors[0].Description, shr.Errors[0].Number) } if shr.Result.IsSuccess != "true" { - return fmt.Errorf("Namecheap setHosts failed.") + return fmt.Errorf("Namecheap setHosts failed") } return nil diff --git a/vendor/github.com/xenolf/lego/providers/dns/namedotcom/namedotcom.go b/vendor/github.com/xenolf/lego/providers/dns/namedotcom/namedotcom.go index 0bd3a43ee..2df4a597a 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/namedotcom/namedotcom.go +++ b/vendor/github.com/xenolf/lego/providers/dns/namedotcom/namedotcom.go @@ -8,10 +8,10 @@ import ( "strings" "github.com/namedotcom/go/namecom" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) -// DNSProvider is an implementation of the acmev2.ChallengeProvider interface. +// DNSProvider is an implementation of the acme.ChallengeProvider interface. type DNSProvider struct { client *namecom.NameCom } @@ -47,7 +47,7 @@ func NewDNSProviderCredentials(username, apiToken, server string) (*DNSProvider, // Present creates a TXT record to fulfil the dns-01 challenge. func (c *DNSProvider) Present(domain, token, keyAuth string) error { - fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) request := &namecom.Record{ DomainName: domain, @@ -67,7 +67,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) records, err := c.getRecords(domain) if err != nil { @@ -116,7 +116,7 @@ func (c *DNSProvider) getRecords(domain string) ([]*namecom.Record, error) { } func (c *DNSProvider) extractRecordName(fqdn, domain string) string { - name := acmev2.UnFqdn(fqdn) + name := acme.UnFqdn(fqdn) if idx := strings.Index(name, "."+domain); idx != -1 { return name[:idx] } diff --git a/vendor/github.com/xenolf/lego/providers/dns/ns1/ns1.go b/vendor/github.com/xenolf/lego/providers/dns/ns1/ns1.go index d7afcfc89..105d73f89 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/ns1/ns1.go +++ b/vendor/github.com/xenolf/lego/providers/dns/ns1/ns1.go @@ -8,12 +8,12 @@ import ( "os" "time" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" "gopkg.in/ns1/ns1-go.v2/rest" "gopkg.in/ns1/ns1-go.v2/rest/model/dns" ) -// DNSProvider is an implementation of the acmev2.ChallengeProvider interface. +// DNSProvider is an implementation of the acme.ChallengeProvider interface. type DNSProvider struct { client *rest.Client } @@ -43,7 +43,7 @@ func NewDNSProviderCredentials(key string) (*DNSProvider, error) { // Present creates a TXT record to fulfil the dns-01 challenge. func (c *DNSProvider) Present(domain, token, keyAuth string) error { - fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) zone, err := c.getHostedZone(domain) if err != nil { @@ -61,14 +61,14 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) zone, err := c.getHostedZone(domain) if err != nil { return err } - name := acmev2.UnFqdn(fqdn) + name := acme.UnFqdn(fqdn) _, err = c.client.Records.Delete(zone.Zone, name, "TXT") return err } @@ -83,7 +83,7 @@ func (c *DNSProvider) getHostedZone(domain string) (*dns.Zone, error) { } func (c *DNSProvider) newTxtRecord(zone *dns.Zone, fqdn, value string, ttl int) *dns.Record { - name := acmev2.UnFqdn(fqdn) + name := acme.UnFqdn(fqdn) return &dns.Record{ Type: "TXT", diff --git a/vendor/github.com/xenolf/lego/providers/dns/otc/mock.go b/vendor/github.com/xenolf/lego/providers/dns/otc/mock.go index 0f2acb4b4..babc2b3ab 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/otc/mock.go +++ b/vendor/github.com/xenolf/lego/providers/dns/otc/mock.go @@ -2,11 +2,12 @@ package otc import ( "fmt" - "github.com/stretchr/testify/assert" "io/ioutil" "net/http" "net/http/httptest" "testing" + + "github.com/stretchr/testify/assert" ) var fakeOTCUserName = "test" @@ -15,12 +16,14 @@ var fakeOTCDomainName = "test" var fakeOTCProjectName = "test" var fakeOTCToken = "62244bc21da68d03ebac94e6636ff01f" +// DNSMock mock type DNSMock struct { t *testing.T Server *httptest.Server Mux *http.ServeMux } +// NewDNSMock create a new DNSMock func NewDNSMock(t *testing.T) *DNSMock { return &DNSMock{ t: t, @@ -38,6 +41,7 @@ func (m *DNSMock) ShutdownServer() { m.Server.Close() } +// HandleAuthSuccessfully Handle auth successfully func (m *DNSMock) HandleAuthSuccessfully() { m.Mux.HandleFunc("/v3/auth/token", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Subject-Token", fakeOTCToken) @@ -64,6 +68,7 @@ func (m *DNSMock) HandleAuthSuccessfully() { }) } +// HandleListZonesSuccessfully Handle list zones successfully func (m *DNSMock) HandleListZonesSuccessfully() { m.Mux.HandleFunc("/v2/zones", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, `{ @@ -79,6 +84,7 @@ func (m *DNSMock) HandleListZonesSuccessfully() { }) } +// HandleListZonesEmpty Handle list zones empty func (m *DNSMock) HandleListZonesEmpty() { m.Mux.HandleFunc("/v2/zones", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, `{ @@ -93,6 +99,7 @@ func (m *DNSMock) HandleListZonesEmpty() { }) } +// HandleDeleteRecordsetsSuccessfully Handle delete recordsets successfully func (m *DNSMock) HandleDeleteRecordsetsSuccessfully() { m.Mux.HandleFunc("/v2/zones/123123/recordsets/321321", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, `{ @@ -107,6 +114,7 @@ func (m *DNSMock) HandleDeleteRecordsetsSuccessfully() { }) } +// HandleListRecordsetsEmpty Handle list recordsets empty func (m *DNSMock) HandleListRecordsetsEmpty() { m.Mux.HandleFunc("/v2/zones/123123/recordsets", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, `{ @@ -118,6 +126,8 @@ func (m *DNSMock) HandleListRecordsetsEmpty() { assert.Equal(m.t, r.URL.RawQuery, "type=TXT&name=_acme-challenge.example.com.") }) } + +// HandleListRecordsetsSuccessfully Handle list recordsets successfully func (m *DNSMock) HandleListRecordsetsSuccessfully() { m.Mux.HandleFunc("/v2/zones/123123/recordsets", func(w http.ResponseWriter, r *http.Request) { if r.Method == "GET" { diff --git a/vendor/github.com/xenolf/lego/providers/dns/otc/otc.go b/vendor/github.com/xenolf/lego/providers/dns/otc/otc.go index 8b243d624..869187307 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/otc/otc.go +++ b/vendor/github.com/xenolf/lego/providers/dns/otc/otc.go @@ -12,10 +12,10 @@ import ( "os" "time" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) -// DNSProvider is an implementation of the acmev2.ChallengeProvider interface that uses +// DNSProvider is an implementation of the acme.ChallengeProvider interface that uses // OTC's Managed DNS API to manage TXT records for a domain. type DNSProvider struct { identityEndpoint string @@ -59,6 +59,7 @@ func NewDNSProviderCredentials(domainName, userName, password, projectName, iden }, nil } +// SendRequest send request func (d *DNSProvider) SendRequest(method, resource string, payload interface{}) (io.Reader, error) { url := fmt.Sprintf("%s/%s", d.otcBaseURL, resource) @@ -81,7 +82,7 @@ func (d *DNSProvider) SendRequest(method, resource string, payload interface{}) tr.DisableKeepAlives = true client := &http.Client{ - Timeout: time.Duration(10 * time.Second), + Timeout: 10 * time.Second, Transport: tr, } resp, err := client.Do(req) @@ -168,7 +169,7 @@ func (d *DNSProvider) loginRequest() error { } req.Header.Set("Content-Type", "application/json") - client := &http.Client{Timeout: time.Duration(10 * time.Second)} + client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Do(req) if err != nil { return err @@ -221,12 +222,7 @@ func (d *DNSProvider) loginRequest() error { // Starts a new OTC API Session. Authenticates using userName, password // and receives a token to be used in for subsequent requests. func (d *DNSProvider) login() error { - err := d.loginRequest() - if err != nil { - return err - } - - return nil + return d.loginRequest() } func (d *DNSProvider) getZoneID(zone string) (string, error) { @@ -305,21 +301,18 @@ func (d *DNSProvider) deleteRecordSet(zoneID, recordID string) error { resource := fmt.Sprintf("zones/%s/recordsets/%s", zoneID, recordID) _, err := d.SendRequest("DELETE", resource, nil) - if err != nil { - return err - } - return nil + return err } // Present creates a TXT record using the specified parameters func (d *DNSProvider) Present(domain, token, keyAuth string) error { - fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) if ttl < 300 { ttl = 300 // 300 is otc minimum value for ttl } - authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) + authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return err } @@ -340,7 +333,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { Name string `json:"name"` Description string `json:"description"` Type string `json:"type"` - Ttl int `json:"ttl"` + TTL int `json:"ttl"` Records []string `json:"records"` } @@ -348,23 +341,18 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { Name: fqdn, Description: "Added TXT record for ACME dns-01 challenge using lego client", Type: "TXT", - Ttl: 300, + TTL: ttl, Records: []string{fmt.Sprintf("\"%s\"", value)}, } _, err = d.SendRequest("POST", resource, r1) - - if err != nil { - return err - } - - return nil + return err } // CleanUp removes the TXT record matching the specified parameters func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) - authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) + authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return err } @@ -375,7 +363,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } zoneID, err := d.getZoneID(authZone) - if err != nil { return err } @@ -384,5 +371,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("unable go get record %s for zone %s: %s", fqdn, domain, err) } + return d.deleteRecordSet(zoneID, recordID) } diff --git a/vendor/github.com/xenolf/lego/providers/dns/ovh/ovh.go b/vendor/github.com/xenolf/lego/providers/dns/ovh/ovh.go index 417de2e2c..a49fc7742 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/ovh/ovh.go +++ b/vendor/github.com/xenolf/lego/providers/dns/ovh/ovh.go @@ -1,4 +1,4 @@ -// Package OVH implements a DNS provider for solving the DNS-01 +// Package ovh implements a DNS provider for solving the DNS-01 // challenge using OVH DNS. package ovh @@ -9,13 +9,13 @@ import ( "sync" "github.com/ovh/go-ovh/ovh" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) // OVH API reference: https://eu.api.ovh.com/ // Create a Token: https://eu.api.ovh.com/createToken/ -// DNSProvider is an implementation of the acmev2.ChallengeProvider interface +// DNSProvider is an implementation of the acme.ChallengeProvider interface // that uses OVH's REST API to manage TXT records for a domain. type DNSProvider struct { client *ovh.Client @@ -78,15 +78,15 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { Zone string `json:"zone"` } - fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) // Parse domain name - authZone, err := acmev2.FindZoneByFqdn(acmev2.ToFqdn(domain), acmev2.RecursiveNameservers) + authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers) if err != nil { return fmt.Errorf("Could not determine zone for domain: '%s'. %s", domain, err) } - authZone = acmev2.UnFqdn(authZone) + authZone = acme.UnFqdn(authZone) subDomain := d.extractRecordName(fqdn, authZone) reqURL := fmt.Sprintf("/domain/zone/%s/record", authZone) @@ -117,7 +117,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) // get the record's unique ID from when we created it d.recordIDsMu.Lock() @@ -127,12 +127,12 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("unknown record ID for '%s'", fqdn) } - authZone, err := acmev2.FindZoneByFqdn(acmev2.ToFqdn(domain), acmev2.RecursiveNameservers) + authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers) if err != nil { return fmt.Errorf("Could not determine zone for domain: '%s'. %s", domain, err) } - authZone = acmev2.UnFqdn(authZone) + authZone = acme.UnFqdn(authZone) reqURL := fmt.Sprintf("/domain/zone/%s/record/%d", authZone, recordID) @@ -151,7 +151,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } func (d *DNSProvider) extractRecordName(fqdn, domain string) string { - name := acmev2.UnFqdn(fqdn) + name := acme.UnFqdn(fqdn) if idx := strings.Index(name, "."+domain); idx != -1 { return name[:idx] } diff --git a/vendor/github.com/xenolf/lego/providers/dns/pdns/pdns.go b/vendor/github.com/xenolf/lego/providers/dns/pdns/pdns.go index 0ce84c6b9..9810245b9 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/pdns/pdns.go +++ b/vendor/github.com/xenolf/lego/providers/dns/pdns/pdns.go @@ -14,10 +14,10 @@ import ( "strings" "time" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) -// DNSProvider is an implementation of the acmev2.ChallengeProvider interface +// DNSProvider is an implementation of the acme.ChallengeProvider interface type DNSProvider struct { apiKey string host *url.URL @@ -29,12 +29,12 @@ type DNSProvider struct { // PDNS_API_URL and PDNS_API_KEY. func NewDNSProvider() (*DNSProvider, error) { key := os.Getenv("PDNS_API_KEY") - hostUrl, err := url.Parse(os.Getenv("PDNS_API_URL")) + hostURL, err := url.Parse(os.Getenv("PDNS_API_URL")) if err != nil { return nil, err } - return NewDNSProviderCredentials(hostUrl, key) + return NewDNSProviderCredentials(hostURL, key) } // NewDNSProviderCredentials uses the supplied credentials to return a @@ -65,7 +65,7 @@ func (c *DNSProvider) Timeout() (timeout, interval time.Duration) { // Present creates a TXT record to fulfil the dns-01 challenge func (c *DNSProvider) Present(domain, token, keyAuth string) error { - fqdn, value, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, _ := acme.DNS01Record(domain, keyAuth) zone, err := c.getHostedZone(fqdn) if err != nil { return err @@ -75,7 +75,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error { // pre-v1 API wants non-fqdn if c.apiVersion == 0 { - name = acmev2.UnFqdn(fqdn) + name = acme.UnFqdn(fqdn) } rec := pdnsRecord{ @@ -90,7 +90,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error { rrsets := rrSets{ RRSets: []rrSet{ - rrSet{ + { Name: name, ChangeType: "REPLACE", Type: "TXT", @@ -107,17 +107,12 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error { } _, err = c.makeRequest("PATCH", zone.URL, bytes.NewReader(body)) - if err != nil { - fmt.Println("here") - return err - } - - return nil + return err } // CleanUp removes the TXT record matching the specified parameters func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) zone, err := c.getHostedZone(fqdn) if err != nil { @@ -131,7 +126,7 @@ func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { rrsets := rrSets{ RRSets: []rrSet{ - rrSet{ + { Name: set.Name, Type: set.Type, ChangeType: "DELETE", @@ -144,16 +139,12 @@ func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { } _, err = c.makeRequest("PATCH", zone.URL, bytes.NewReader(body)) - if err != nil { - return err - } - - return nil + return err } func (c *DNSProvider) getHostedZone(fqdn string) (*hostedZone, error) { var zone hostedZone - authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) + authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return nil, err } @@ -172,7 +163,7 @@ func (c *DNSProvider) getHostedZone(fqdn string) (*hostedZone, error) { url = "" for _, zone := range zones { - if acmev2.UnFqdn(zone.Name) == acmev2.UnFqdn(authZone) { + if acme.UnFqdn(zone.Name) == acme.UnFqdn(authZone) { url = zone.URL } } @@ -215,12 +206,12 @@ func (c *DNSProvider) findTxtRecord(fqdn string) (*rrSet, error) { } for _, set := range zone.RRSets { - if (set.Name == acmev2.UnFqdn(fqdn) || set.Name == fqdn) && set.Type == "TXT" { + if (set.Name == acme.UnFqdn(fqdn) || set.Name == fqdn) && set.Type == "TXT" { return &set, nil } } - return nil, fmt.Errorf("No existing record found for %s", fqdn) + return nil, fmt.Errorf("no existing record found for %s", fqdn) } func (c *DNSProvider) getAPIVersion() { diff --git a/vendor/github.com/xenolf/lego/providers/dns/rackspace/rackspace.go b/vendor/github.com/xenolf/lego/providers/dns/rackspace/rackspace.go index 97a2bc9c9..d30e582b8 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/rackspace/rackspace.go +++ b/vendor/github.com/xenolf/lego/providers/dns/rackspace/rackspace.go @@ -11,13 +11,13 @@ import ( "os" "time" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) // rackspaceAPIURL represents the Identity API endpoint to call var rackspaceAPIURL = "https://identity.api.rackspacecloud.com/v2.0/tokens" -// DNSProvider is an implementation of the acmev2.ChallengeProvider interface +// DNSProvider is an implementation of the acme.ChallengeProvider interface // used to store the reusable token and DNS API endpoint type DNSProvider struct { token string @@ -92,7 +92,7 @@ func NewDNSProviderCredentials(user, key string) (*DNSProvider, error) { client := http.Client{Timeout: 30 * time.Second} resp, err := client.Do(req) if err != nil { - return nil, fmt.Errorf("Error querying Rackspace Identity API: %v", err) + return nil, fmt.Errorf("error querying Rackspace Identity API: %v", err) } defer resp.Body.Close() @@ -115,7 +115,7 @@ func NewDNSProviderCredentials(user, key string) (*DNSProvider, error) { } } if dnsEndpoint == "" { - return nil, fmt.Errorf("Failed to populate DNS endpoint, check Rackspace API for changes.") + return nil, fmt.Errorf("failed to populate DNS endpoint, check Rackspace API for changes") } return &DNSProvider{ @@ -126,15 +126,15 @@ func NewDNSProviderCredentials(user, key string) (*DNSProvider, error) { // Present creates a TXT record to fulfil the dns-01 challenge func (c *DNSProvider) Present(domain, token, keyAuth string) error { - fqdn, value, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, _ := acme.DNS01Record(domain, keyAuth) zoneID, err := c.getHostedZoneID(fqdn) if err != nil { return err } - rec := RackspaceRecords{ - RackspaceRecord: []RackspaceRecord{{ - Name: acmev2.UnFqdn(fqdn), + rec := Records{ + Record: []Record{{ + Name: acme.UnFqdn(fqdn), Type: "TXT", Data: value, TTL: 300, @@ -147,16 +147,12 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error { } _, err = c.makeRequest("POST", fmt.Sprintf("/domains/%d/records", zoneID), bytes.NewReader(body)) - if err != nil { - return err - } - - return nil + return err } // CleanUp removes the TXT record matching the specified parameters func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) zoneID, err := c.getHostedZoneID(fqdn) if err != nil { return err @@ -168,11 +164,7 @@ func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { } _, err = c.makeRequest("DELETE", fmt.Sprintf("/domains/%d/records?id=%s", zoneID, record.ID), nil) - if err != nil { - return err - } - - return nil + return err } // getHostedZoneID performs a lookup to get the DNS zone which needs @@ -187,12 +179,12 @@ func (c *DNSProvider) getHostedZoneID(fqdn string) (int, error) { } `json:"domains"` } - authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) + authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return 0, err } - result, err := c.makeRequest("GET", fmt.Sprintf("/domains?name=%s", acmev2.UnFqdn(authZone)), nil) + result, err := c.makeRequest("GET", fmt.Sprintf("/domains?name=%s", acme.UnFqdn(authZone)), nil) if err != nil { return 0, err } @@ -205,36 +197,35 @@ func (c *DNSProvider) getHostedZoneID(fqdn string) (int, error) { // If nothing was returned, or for whatever reason more than 1 was returned (the search uses exact match, so should not occur) if zoneSearchResponse.TotalEntries != 1 { - return 0, fmt.Errorf("Found %d zones for %s in Rackspace for domain %s", zoneSearchResponse.TotalEntries, authZone, fqdn) + return 0, fmt.Errorf("found %d zones for %s in Rackspace for domain %s", zoneSearchResponse.TotalEntries, authZone, fqdn) } return zoneSearchResponse.HostedZones[0].ID, nil } // findTxtRecord searches a DNS zone for a TXT record with a specific name -func (c *DNSProvider) findTxtRecord(fqdn string, zoneID int) (*RackspaceRecord, error) { - result, err := c.makeRequest("GET", fmt.Sprintf("/domains/%d/records?type=TXT&name=%s", zoneID, acmev2.UnFqdn(fqdn)), nil) +func (c *DNSProvider) findTxtRecord(fqdn string, zoneID int) (*Record, error) { + result, err := c.makeRequest("GET", fmt.Sprintf("/domains/%d/records?type=TXT&name=%s", zoneID, acme.UnFqdn(fqdn)), nil) if err != nil { return nil, err } - var records RackspaceRecords + var records Records err = json.Unmarshal(result, &records) if err != nil { return nil, err } - recordsLength := len(records.RackspaceRecord) + recordsLength := len(records.Record) switch recordsLength { case 1: - break case 0: - return nil, fmt.Errorf("No TXT record found for %s", fqdn) + return nil, fmt.Errorf("no TXT record found for %s", fqdn) default: - return nil, fmt.Errorf("More than 1 TXT record found for %s", fqdn) + return nil, fmt.Errorf("more than 1 TXT record found for %s", fqdn) } - return &records.RackspaceRecord[0], nil + return &records.Record[0], nil } // makeRequest is a wrapper function used for making DNS API requests @@ -251,13 +242,13 @@ func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawM client := http.Client{Timeout: 30 * time.Second} resp, err := client.Do(req) if err != nil { - return nil, fmt.Errorf("Error querying DNS API: %v", err) + return nil, fmt.Errorf("error querying DNS API: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted { - return nil, fmt.Errorf("Request failed for %s %s. Response code: %d", method, url, resp.StatusCode) + return nil, fmt.Errorf("request failed for %s %s. Response code: %d", method, url, resp.StatusCode) } var r json.RawMessage @@ -269,13 +260,13 @@ func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawM return r, nil } -// RackspaceRecords is the list of records sent/received from the DNS API -type RackspaceRecords struct { - RackspaceRecord []RackspaceRecord `json:"records"` +// Records is the list of records sent/received from the DNS API +type Records struct { + Record []Record `json:"records"` } -// RackspaceRecord represents a Rackspace DNS record -type RackspaceRecord struct { +// Record represents a Rackspace DNS record +type Record struct { Name string `json:"name"` Type string `json:"type"` Data string `json:"data"` diff --git a/vendor/github.com/xenolf/lego/providers/dns/rfc2136/rfc2136.go b/vendor/github.com/xenolf/lego/providers/dns/rfc2136/rfc2136.go index c185de162..8f4231df2 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/rfc2136/rfc2136.go +++ b/vendor/github.com/xenolf/lego/providers/dns/rfc2136/rfc2136.go @@ -10,10 +10,10 @@ import ( "time" "github.com/miekg/dns" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) -// DNSProvider is an implementation of the acmev2.ChallengeProvider interface that +// DNSProvider is an implementation of the acme.ChallengeProvider interface that // uses dynamic DNS updates (RFC 2136) to create TXT records on a nameserver. type DNSProvider struct { nameserver string @@ -77,7 +77,7 @@ func NewDNSProviderCredentials(nameserver, tsigAlgorithm, tsigKey, tsigSecret, t if err != nil { return nil, err } else if t < 0 { - return nil, fmt.Errorf("Invalid/negative RFC2136_TIMEOUT: %v", timeout) + return nil, fmt.Errorf("invalid/negative RFC2136_TIMEOUT: %v", timeout) } else { d.timeout = t } @@ -86,26 +86,26 @@ func NewDNSProviderCredentials(nameserver, tsigAlgorithm, tsigKey, tsigSecret, t return d, nil } -// Returns the timeout configured with RFC2136_TIMEOUT, or 60s. +// Timeout Returns the timeout configured with RFC2136_TIMEOUT, or 60s. func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return d.timeout, 2 * time.Second } // Present creates a TXT record using the specified parameters -func (r *DNSProvider) Present(domain, token, keyAuth string) error { - fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) - return r.changeRecord("INSERT", fqdn, value, ttl) +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) + return d.changeRecord("INSERT", fqdn, value, ttl) } // CleanUp removes the TXT record matching the specified parameters -func (r *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) - return r.changeRecord("REMOVE", fqdn, value, ttl) +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) + return d.changeRecord("REMOVE", fqdn, value, ttl) } -func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error { +func (d *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error { // Find the zone for the given fqdn - zone, err := acmev2.FindZoneByFqdn(fqdn, []string{r.nameserver}) + zone, err := acme.FindZoneByFqdn(fqdn, []string{d.nameserver}) if err != nil { return err } @@ -127,20 +127,20 @@ func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error { case "REMOVE": m.Remove(rrs) default: - return fmt.Errorf("Unexpected action: %s", action) + return fmt.Errorf("unexpected action: %s", action) } // Setup client c := new(dns.Client) c.SingleInflight = true // TSIG authentication / msg signing - if len(r.tsigKey) > 0 && len(r.tsigSecret) > 0 { - m.SetTsig(dns.Fqdn(r.tsigKey), r.tsigAlgorithm, 300, time.Now().Unix()) - c.TsigSecret = map[string]string{dns.Fqdn(r.tsigKey): r.tsigSecret} + if len(d.tsigKey) > 0 && len(d.tsigSecret) > 0 { + m.SetTsig(dns.Fqdn(d.tsigKey), d.tsigAlgorithm, 300, time.Now().Unix()) + c.TsigSecret = map[string]string{dns.Fqdn(d.tsigKey): d.tsigSecret} } // Send the query - reply, _, err := c.Exchange(m, r.nameserver) + reply, _, err := c.Exchange(m, d.nameserver) if err != nil { return fmt.Errorf("DNS update failed: %v", err) } diff --git a/vendor/github.com/xenolf/lego/providers/dns/route53/route53.go b/vendor/github.com/xenolf/lego/providers/dns/route53/route53.go index 82d13cef1..adc15401c 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/route53/route53.go +++ b/vendor/github.com/xenolf/lego/providers/dns/route53/route53.go @@ -14,7 +14,7 @@ import ( "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/route53" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) const ( @@ -22,7 +22,7 @@ const ( route53TTL = 10 ) -// DNSProvider implements the acmev2.ChallengeProvider interface +// DNSProvider implements the acme.ChallengeProvider interface type DNSProvider struct { client *route53.Route53 hostedZoneID string @@ -70,7 +70,11 @@ func NewDNSProvider() (*DNSProvider, error) { r := customRetryer{} r.NumMaxRetries = maxRetries config := request.WithRetryer(aws.NewConfig(), r) - client := route53.New(session.New(config)) + session, err := session.NewSessionWithOptions(session.Options{Config: *config}) + if err != nil { + return nil, err + } + client := route53.New(session) return &DNSProvider{ client: client, @@ -80,14 +84,14 @@ func NewDNSProvider() (*DNSProvider, error) { // Present creates a TXT record using the specified parameters func (r *DNSProvider) Present(domain, token, keyAuth string) error { - fqdn, value, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, _ := acme.DNS01Record(domain, keyAuth) value = `"` + value + `"` return r.changeRecord("UPSERT", fqdn, value, route53TTL) } // CleanUp removes the TXT record matching the specified parameters func (r *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, value, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, _ := acme.DNS01Record(domain, keyAuth) value = `"` + value + `"` return r.changeRecord("DELETE", fqdn, value, route53TTL) } @@ -95,7 +99,7 @@ func (r *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error { hostedZoneID, err := r.getHostedZoneID(fqdn) if err != nil { - return fmt.Errorf("Failed to determine Route 53 hosted zone ID: %v", err) + return fmt.Errorf("failed to determine Route 53 hosted zone ID: %v", err) } recordSet := newTXTRecordSet(fqdn, value, ttl) @@ -114,20 +118,20 @@ func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error { resp, err := r.client.ChangeResourceRecordSets(reqParams) if err != nil { - return fmt.Errorf("Failed to change Route 53 record set: %v", err) + return fmt.Errorf("failed to change Route 53 record set: %v", err) } statusID := resp.ChangeInfo.Id - return acmev2.WaitFor(120*time.Second, 4*time.Second, func() (bool, error) { + return acme.WaitFor(120*time.Second, 4*time.Second, func() (bool, error) { reqParams := &route53.GetChangeInput{ Id: statusID, } resp, err := r.client.GetChange(reqParams) if err != nil { - return false, fmt.Errorf("Failed to query Route 53 change status: %v", err) + return false, fmt.Errorf("failed to query Route 53 change status: %v", err) } - if *resp.ChangeInfo.Status == route53.ChangeStatusInsync { + if aws.StringValue(resp.ChangeInfo.Status) == route53.ChangeStatusInsync { return true, nil } return false, nil @@ -139,14 +143,14 @@ func (r *DNSProvider) getHostedZoneID(fqdn string) (string, error) { return r.hostedZoneID, nil } - authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) + authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return "", err } // .DNSName should not have a trailing dot reqParams := &route53.ListHostedZonesByNameInput{ - DNSName: aws.String(acmev2.UnFqdn(authZone)), + DNSName: aws.String(acme.UnFqdn(authZone)), } resp, err := r.client.ListHostedZonesByName(reqParams) if err != nil { @@ -156,14 +160,14 @@ func (r *DNSProvider) getHostedZoneID(fqdn string) (string, error) { var hostedZoneID string for _, hostedZone := range resp.HostedZones { // .Name has a trailing dot - if !*hostedZone.Config.PrivateZone && *hostedZone.Name == authZone { - hostedZoneID = *hostedZone.Id + if !aws.BoolValue(hostedZone.Config.PrivateZone) && aws.StringValue(hostedZone.Name) == authZone { + hostedZoneID = aws.StringValue(hostedZone.Id) break } } if len(hostedZoneID) == 0 { - return "", fmt.Errorf("Zone %s not found in Route 53 for domain %s", authZone, fqdn) + return "", fmt.Errorf("zone %s not found in Route 53 for domain %s", authZone, fqdn) } if strings.HasPrefix(hostedZoneID, "/hostedzone/") { diff --git a/vendor/github.com/xenolf/lego/providers/dns/vultr/vultr.go b/vendor/github.com/xenolf/lego/providers/dns/vultr/vultr.go index 6e0633264..bc2067579 100644 --- a/vendor/github.com/xenolf/lego/providers/dns/vultr/vultr.go +++ b/vendor/github.com/xenolf/lego/providers/dns/vultr/vultr.go @@ -9,10 +9,10 @@ import ( "strings" vultr "github.com/JamesClonk/vultr/lib" - "github.com/xenolf/lego/acmev2" + "github.com/xenolf/lego/acme" ) -// DNSProvider is an implementation of the acmev2.ChallengeProvider interface. +// DNSProvider is an implementation of the acme.ChallengeProvider interface. type DNSProvider struct { client *vultr.Client } @@ -40,7 +40,7 @@ func NewDNSProviderCredentials(apiKey string) (*DNSProvider, error) { // Present creates a TXT record to fulfil the DNS-01 challenge. func (c *DNSProvider) Present(domain, token, keyAuth string) error { - fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) + fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) zoneDomain, err := c.getHostedZone(domain) if err != nil { @@ -59,7 +59,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { - fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) + fqdn, _, _ := acme.DNS01Record(domain, keyAuth) zoneDomain, records, err := c.findTxtRecords(domain, fqdn) if err != nil { @@ -119,7 +119,7 @@ func (c *DNSProvider) findTxtRecords(domain, fqdn string) (string, []vultr.DNSRe } func (c *DNSProvider) extractRecordName(fqdn, domain string) string { - name := acmev2.UnFqdn(fqdn) + name := acme.UnFqdn(fqdn) if idx := strings.Index(name, "."+domain); idx != -1 { return name[:idx] } diff --git a/vendor/golang.org/x/oauth2/LICENSE b/vendor/golang.org/x/oauth2/LICENSE index d02f24fd5..6a66aea5e 100644 --- a/vendor/golang.org/x/oauth2/LICENSE +++ b/vendor/golang.org/x/oauth2/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2009 The oauth2 Authors. All rights reserved. +Copyright (c) 2009 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are diff --git a/vendor/golang.org/x/oauth2/client_appengine.go b/vendor/golang.org/x/oauth2/client_appengine.go deleted file mode 100644 index 8962c49d1..000000000 --- a/vendor/golang.org/x/oauth2/client_appengine.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2014 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build appengine - -// App Engine hooks. - -package oauth2 - -import ( - "net/http" - - "golang.org/x/net/context" - "golang.org/x/oauth2/internal" - "google.golang.org/appengine/urlfetch" -) - -func init() { - internal.RegisterContextClientFunc(contextClientAppEngine) -} - -func contextClientAppEngine(ctx context.Context) (*http.Client, error) { - return urlfetch.Client(ctx), nil -} diff --git a/vendor/golang.org/x/oauth2/google/default.go b/vendor/golang.org/x/oauth2/google/default.go index 004ed4eab..a31607437 100644 --- a/vendor/golang.org/x/oauth2/google/default.go +++ b/vendor/golang.org/x/oauth2/google/default.go @@ -18,14 +18,6 @@ import ( "golang.org/x/oauth2" ) -// DefaultCredentials holds "Application Default Credentials". -// For more details, see: -// https://developers.google.com/accounts/docs/application-default-credentials -type DefaultCredentials struct { - ProjectID string // may be empty - TokenSource oauth2.TokenSource -} - // DefaultClient returns an HTTP Client that uses the // DefaultTokenSource to obtain authentication credentials. func DefaultClient(ctx context.Context, scope ...string) (*http.Client, error) { @@ -47,25 +39,12 @@ func DefaultTokenSource(ctx context.Context, scope ...string) (oauth2.TokenSourc return creds.TokenSource, nil } -// FindDefaultCredentials searches for "Application Default Credentials". -// -// It looks for credentials in the following places, -// preferring the first location found: -// -// 1. A JSON file whose path is specified by the -// GOOGLE_APPLICATION_CREDENTIALS environment variable. -// 2. A JSON file in a location known to the gcloud command-line tool. -// On Windows, this is %APPDATA%/gcloud/application_default_credentials.json. -// On other systems, $HOME/.config/gcloud/application_default_credentials.json. -// 3. On Google App Engine it uses the appengine.AccessToken function. -// 4. On Google Compute Engine and Google App Engine Managed VMs, it fetches -// credentials from the metadata server. -// (In this final case any provided scopes are ignored.) -func FindDefaultCredentials(ctx context.Context, scope ...string) (*DefaultCredentials, error) { +// Common implementation for FindDefaultCredentials. +func findDefaultCredentials(ctx context.Context, scopes []string) (*DefaultCredentials, error) { // First, try the environment variable. const envVar = "GOOGLE_APPLICATION_CREDENTIALS" if filename := os.Getenv(envVar); filename != "" { - creds, err := readCredentialsFile(ctx, filename, scope) + creds, err := readCredentialsFile(ctx, filename, scopes) if err != nil { return nil, fmt.Errorf("google: error getting credentials using %v environment variable: %v", envVar, err) } @@ -74,7 +53,7 @@ func FindDefaultCredentials(ctx context.Context, scope ...string) (*DefaultCrede // Second, try a well-known file. filename := wellKnownFile() - if creds, err := readCredentialsFile(ctx, filename, scope); err == nil { + if creds, err := readCredentialsFile(ctx, filename, scopes); err == nil { return creds, nil } else if !os.IsNotExist(err) { return nil, fmt.Errorf("google: error getting credentials using well-known file (%v): %v", filename, err) @@ -84,7 +63,7 @@ func FindDefaultCredentials(ctx context.Context, scope ...string) (*DefaultCrede if appengineTokenFunc != nil && !appengineFlex { return &DefaultCredentials{ ProjectID: appengineAppIDFunc(ctx), - TokenSource: AppEngineTokenSource(ctx, scope...), + TokenSource: AppEngineTokenSource(ctx, scopes...), }, nil } @@ -102,6 +81,23 @@ func FindDefaultCredentials(ctx context.Context, scope ...string) (*DefaultCrede return nil, fmt.Errorf("google: could not find default credentials. See %v for more information.", url) } +// Common implementation for CredentialsFromJSON. +func credentialsFromJSON(ctx context.Context, jsonData []byte, scopes []string) (*DefaultCredentials, error) { + var f credentialsFile + if err := json.Unmarshal(jsonData, &f); err != nil { + return nil, err + } + ts, err := f.tokenSource(ctx, append([]string(nil), scopes...)) + if err != nil { + return nil, err + } + return &DefaultCredentials{ + ProjectID: f.ProjectID, + TokenSource: ts, + JSON: jsonData, + }, nil +} + func wellKnownFile() string { const f = "application_default_credentials.json" if runtime.GOOS == "windows" { @@ -115,16 +111,5 @@ func readCredentialsFile(ctx context.Context, filename string, scopes []string) if err != nil { return nil, err } - var f credentialsFile - if err := json.Unmarshal(b, &f); err != nil { - return nil, err - } - ts, err := f.tokenSource(ctx, append([]string(nil), scopes...)) - if err != nil { - return nil, err - } - return &DefaultCredentials{ - ProjectID: f.ProjectID, - TokenSource: ts, - }, nil + return CredentialsFromJSON(ctx, b, scopes...) } diff --git a/vendor/golang.org/x/oauth2/google/doc_go19.go b/vendor/golang.org/x/oauth2/google/doc_go19.go new file mode 100644 index 000000000..2a86325fe --- /dev/null +++ b/vendor/golang.org/x/oauth2/google/doc_go19.go @@ -0,0 +1,42 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.9 + +// Package google provides support for making OAuth2 authorized and authenticated +// HTTP requests to Google APIs. It supports the Web server flow, client-side +// credentials, service accounts, Google Compute Engine service accounts, and Google +// App Engine service accounts. +// +// A brief overview of the package follows. For more information, please read +// https://developers.google.com/accounts/docs/OAuth2 +// and +// https://developers.google.com/accounts/docs/application-default-credentials. +// +// OAuth2 Configs +// +// Two functions in this package return golang.org/x/oauth2.Config values from Google credential +// data. Google supports two JSON formats for OAuth2 credentials: one is handled by ConfigFromJSON, +// the other by JWTConfigFromJSON. The returned Config can be used to obtain a TokenSource or +// create an http.Client. +// +// +// Credentials +// +// The Credentials type represents Google credentials, including Application Default +// Credentials. +// +// Use FindDefaultCredentials to obtain Application Default Credentials. +// FindDefaultCredentials looks in some well-known places for a credentials file, and +// will call AppEngineTokenSource or ComputeTokenSource as needed. +// +// DefaultClient and DefaultTokenSource are convenience methods. They first call FindDefaultCredentials, +// then use the credentials to construct an http.Client or an oauth2.TokenSource. +// +// Use CredentialsFromJSON to obtain credentials from either of the two JSON formats +// described in OAuth2 Configs, above. The TokenSource in the returned value is the +// same as the one obtained from the oauth2.Config returned from ConfigFromJSON or +// JWTConfigFromJSON, but the Credentials may contain additional information +// that is useful is some circumstances. +package google // import "golang.org/x/oauth2/google" diff --git a/vendor/golang.org/x/oauth2/google/doc_not_go19.go b/vendor/golang.org/x/oauth2/google/doc_not_go19.go new file mode 100644 index 000000000..5c3c6e148 --- /dev/null +++ b/vendor/golang.org/x/oauth2/google/doc_not_go19.go @@ -0,0 +1,43 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !go1.9 + +// Package google provides support for making OAuth2 authorized and authenticated +// HTTP requests to Google APIs. It supports the Web server flow, client-side +// credentials, service accounts, Google Compute Engine service accounts, and Google +// App Engine service accounts. +// +// A brief overview of the package follows. For more information, please read +// https://developers.google.com/accounts/docs/OAuth2 +// and +// https://developers.google.com/accounts/docs/application-default-credentials. +// +// OAuth2 Configs +// +// Two functions in this package return golang.org/x/oauth2.Config values from Google credential +// data. Google supports two JSON formats for OAuth2 credentials: one is handled by ConfigFromJSON, +// the other by JWTConfigFromJSON. The returned Config can be used to obtain a TokenSource or +// create an http.Client. +// +// +// Credentials +// +// The DefaultCredentials type represents Google Application Default Credentials, as +// well as other forms of credential. +// +// Use FindDefaultCredentials to obtain Application Default Credentials. +// FindDefaultCredentials looks in some well-known places for a credentials file, and +// will call AppEngineTokenSource or ComputeTokenSource as needed. +// +// DefaultClient and DefaultTokenSource are convenience methods. They first call FindDefaultCredentials, +// then use the credentials to construct an http.Client or an oauth2.TokenSource. +// +// Use CredentialsFromJSON to obtain credentials from either of the two JSON +// formats described in OAuth2 Configs, above. (The DefaultCredentials returned may +// not be "Application Default Credentials".) The TokenSource in the returned value +// is the same as the one obtained from the oauth2.Config returned from +// ConfigFromJSON or JWTConfigFromJSON, but the DefaultCredentials may contain +// additional information that is useful is some circumstances. +package google // import "golang.org/x/oauth2/google" diff --git a/vendor/golang.org/x/oauth2/google/go19.go b/vendor/golang.org/x/oauth2/google/go19.go new file mode 100644 index 000000000..4d0318b1e --- /dev/null +++ b/vendor/golang.org/x/oauth2/google/go19.go @@ -0,0 +1,57 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.9 + +package google + +import ( + "golang.org/x/net/context" + "golang.org/x/oauth2" +) + +// Credentials holds Google credentials, including "Application Default Credentials". +// For more details, see: +// https://developers.google.com/accounts/docs/application-default-credentials +type Credentials struct { + ProjectID string // may be empty + TokenSource oauth2.TokenSource + + // JSON contains the raw bytes from a JSON credentials file. + // This field may be nil if authentication is provided by the + // environment and not with a credentials file, e.g. when code is + // running on Google Cloud Platform. + JSON []byte +} + +// DefaultCredentials is the old name of Credentials. +// +// Deprecated: use Credentials instead. +type DefaultCredentials = Credentials + +// FindDefaultCredentials searches for "Application Default Credentials". +// +// It looks for credentials in the following places, +// preferring the first location found: +// +// 1. A JSON file whose path is specified by the +// GOOGLE_APPLICATION_CREDENTIALS environment variable. +// 2. A JSON file in a location known to the gcloud command-line tool. +// On Windows, this is %APPDATA%/gcloud/application_default_credentials.json. +// On other systems, $HOME/.config/gcloud/application_default_credentials.json. +// 3. On Google App Engine it uses the appengine.AccessToken function. +// 4. On Google Compute Engine and Google App Engine Managed VMs, it fetches +// credentials from the metadata server. +// (In this final case any provided scopes are ignored.) +func FindDefaultCredentials(ctx context.Context, scopes ...string) (*Credentials, error) { + return findDefaultCredentials(ctx, scopes) +} + +// CredentialsFromJSON obtains Google credentials from a JSON value. The JSON can +// represent either a Google Developers Console client_credentials.json file (as in +// ConfigFromJSON) or a Google Developers service account key file (as in +// JWTConfigFromJSON). +func CredentialsFromJSON(ctx context.Context, jsonData []byte, scopes ...string) (*Credentials, error) { + return credentialsFromJSON(ctx, jsonData, scopes) +} diff --git a/vendor/golang.org/x/oauth2/google/google.go b/vendor/golang.org/x/oauth2/google/google.go index 66a8b0e18..f7481fbcc 100644 --- a/vendor/golang.org/x/oauth2/google/google.go +++ b/vendor/golang.org/x/oauth2/google/google.go @@ -2,17 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package google provides support for making OAuth2 authorized and -// authenticated HTTP requests to Google APIs. -// It supports the Web server flow, client-side credentials, service accounts, -// Google Compute Engine service accounts, and Google App Engine service -// accounts. -// -// For more information, please read -// https://developers.google.com/accounts/docs/OAuth2 -// and -// https://developers.google.com/accounts/docs/application-default-credentials. -package google // import "golang.org/x/oauth2/google" +package google import ( "encoding/json" diff --git a/vendor/golang.org/x/oauth2/google/not_go19.go b/vendor/golang.org/x/oauth2/google/not_go19.go new file mode 100644 index 000000000..544e40624 --- /dev/null +++ b/vendor/golang.org/x/oauth2/google/not_go19.go @@ -0,0 +1,54 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !go1.9 + +package google + +import ( + "golang.org/x/net/context" + "golang.org/x/oauth2" +) + +// DefaultCredentials holds Google credentials, including "Application Default Credentials". +// For more details, see: +// https://developers.google.com/accounts/docs/application-default-credentials +type DefaultCredentials struct { + ProjectID string // may be empty + TokenSource oauth2.TokenSource + + // JSON contains the raw bytes from a JSON credentials file. + // This field may be nil if authentication is provided by the + // environment and not with a credentials file, e.g. when code is + // running on Google Cloud Platform. + JSON []byte +} + +// FindDefaultCredentials searches for "Application Default Credentials". +// +// It looks for credentials in the following places, +// preferring the first location found: +// +// 1. A JSON file whose path is specified by the +// GOOGLE_APPLICATION_CREDENTIALS environment variable. +// 2. A JSON file in a location known to the gcloud command-line tool. +// On Windows, this is %APPDATA%/gcloud/application_default_credentials.json. +// On other systems, $HOME/.config/gcloud/application_default_credentials.json. +// 3. On Google App Engine it uses the appengine.AccessToken function. +// 4. On Google Compute Engine and Google App Engine Managed VMs, it fetches +// credentials from the metadata server. +// (In this final case any provided scopes are ignored.) +func FindDefaultCredentials(ctx context.Context, scopes ...string) (*DefaultCredentials, error) { + return findDefaultCredentials(ctx, scopes) +} + +// CredentialsFromJSON obtains Google credentials from a JSON value. The JSON can +// represent either a Google Developers Console client_credentials.json file (as in +// ConfigFromJSON) or a Google Developers service account key file (as in +// JWTConfigFromJSON). +// +// Note: despite the name, the returned credentials may not be Application Default Credentials. +func CredentialsFromJSON(ctx context.Context, jsonData []byte, scopes ...string) (*DefaultCredentials, error) { + return credentialsFromJSON(ctx, jsonData, scopes) +} diff --git a/vendor/golang.org/x/oauth2/google/sdk.go b/vendor/golang.org/x/oauth2/google/sdk.go index bdc18084b..b9660cadd 100644 --- a/vendor/golang.org/x/oauth2/google/sdk.go +++ b/vendor/golang.org/x/oauth2/google/sdk.go @@ -5,9 +5,11 @@ package google import ( + "bufio" "encoding/json" "errors" "fmt" + "io" "net/http" "os" "os/user" @@ -18,7 +20,6 @@ import ( "golang.org/x/net/context" "golang.org/x/oauth2" - "golang.org/x/oauth2/internal" ) type sdkCredentials struct { @@ -76,7 +77,7 @@ func NewSDKConfig(account string) (*SDKConfig, error) { return nil, fmt.Errorf("oauth2/google: failed to load SDK properties: %v", err) } defer f.Close() - ini, err := internal.ParseINI(f) + ini, err := parseINI(f) if err != nil { return nil, fmt.Errorf("oauth2/google: failed to parse SDK properties %q: %v", propertiesPath, err) } @@ -146,6 +147,34 @@ func (c *SDKConfig) Scopes() []string { return c.conf.Scopes } +func parseINI(ini io.Reader) (map[string]map[string]string, error) { + result := map[string]map[string]string{ + "": {}, // root section + } + scanner := bufio.NewScanner(ini) + currentSection := "" + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if strings.HasPrefix(line, ";") { + // comment. + continue + } + if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { + currentSection = strings.TrimSpace(line[1 : len(line)-1]) + result[currentSection] = map[string]string{} + continue + } + parts := strings.SplitN(line, "=", 2) + if len(parts) == 2 && parts[0] != "" { + result[currentSection][strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1]) + } + } + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("error scanning ini: %v", err) + } + return result, nil +} + // sdkConfigPath tries to guess where the gcloud config is located. // It can be overridden during tests. var sdkConfigPath = func() (string, error) { diff --git a/vendor/golang.org/x/oauth2/internal/client_appengine.go b/vendor/golang.org/x/oauth2/internal/client_appengine.go new file mode 100644 index 000000000..743487188 --- /dev/null +++ b/vendor/golang.org/x/oauth2/internal/client_appengine.go @@ -0,0 +1,13 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build appengine + +package internal + +import "google.golang.org/appengine/urlfetch" + +func init() { + appengineClientHook = urlfetch.Client +} diff --git a/vendor/golang.org/x/oauth2/internal/doc.go b/vendor/golang.org/x/oauth2/internal/doc.go new file mode 100644 index 000000000..03265e888 --- /dev/null +++ b/vendor/golang.org/x/oauth2/internal/doc.go @@ -0,0 +1,6 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package internal contains support packages for oauth2 package. +package internal diff --git a/vendor/golang.org/x/oauth2/internal/oauth2.go b/vendor/golang.org/x/oauth2/internal/oauth2.go index e31541b39..fc63fcab3 100644 --- a/vendor/golang.org/x/oauth2/internal/oauth2.go +++ b/vendor/golang.org/x/oauth2/internal/oauth2.go @@ -2,18 +2,14 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package internal contains support packages for oauth2 package. package internal import ( - "bufio" "crypto/rsa" "crypto/x509" "encoding/pem" "errors" "fmt" - "io" - "strings" ) // ParseKey converts the binary contents of a private key file @@ -39,38 +35,3 @@ func ParseKey(key []byte) (*rsa.PrivateKey, error) { } return parsed, nil } - -func ParseINI(ini io.Reader) (map[string]map[string]string, error) { - result := map[string]map[string]string{ - "": {}, // root section - } - scanner := bufio.NewScanner(ini) - currentSection := "" - for scanner.Scan() { - line := strings.TrimSpace(scanner.Text()) - if strings.HasPrefix(line, ";") { - // comment. - continue - } - if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { - currentSection = strings.TrimSpace(line[1 : len(line)-1]) - result[currentSection] = map[string]string{} - continue - } - parts := strings.SplitN(line, "=", 2) - if len(parts) == 2 && parts[0] != "" { - result[currentSection][strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1]) - } - } - if err := scanner.Err(); err != nil { - return nil, fmt.Errorf("error scanning ini: %v", err) - } - return result, nil -} - -func CondVal(v string) []string { - if v == "" { - return nil - } - return []string{v} -} diff --git a/vendor/golang.org/x/oauth2/internal/token.go b/vendor/golang.org/x/oauth2/internal/token.go index 018b58ad1..30fb315d1 100644 --- a/vendor/golang.org/x/oauth2/internal/token.go +++ b/vendor/golang.org/x/oauth2/internal/token.go @@ -2,11 +2,11 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package internal contains support packages for oauth2 package. package internal import ( "encoding/json" + "errors" "fmt" "io" "io/ioutil" @@ -18,9 +18,10 @@ import ( "time" "golang.org/x/net/context" + "golang.org/x/net/context/ctxhttp" ) -// Token represents the crendentials used to authorize +// Token represents the credentials used to authorize // the requests to access protected resources on the OAuth 2.0 // provider's backend. // @@ -102,9 +103,11 @@ var brokenAuthHeaderProviders = []string{ "https://api.twitch.tv/", "https://app.box.com/", "https://connect.stripe.com/", - "https://graph.facebook.com", // see https://github.com/golang/oauth2/issues/214 + "https://login.mailchimp.com/", "https://login.microsoftonline.com/", "https://login.salesforce.com/", + "https://login.windows.net", + "https://login.live.com/", "https://oauth.sandbox.trainingpeaks.com/", "https://oauth.trainingpeaks.com/", "https://oauth.vk.com/", @@ -120,11 +123,18 @@ var brokenAuthHeaderProviders = []string{ "https://www.wunderlist.com/oauth/", "https://api.patreon.com/", "https://sandbox.codeswholesale.com/oauth/token", + "https://api.sipgate.com/v1/authorization/oauth", + "https://api.medium.com/v1/tokens", + "https://log.finalsurge.com/oauth/token", + "https://multisport.todaysplan.com.au/rest/oauth/access_token", + "https://whats.todaysplan.com.au/rest/oauth/access_token", } // brokenAuthHeaderDomains lists broken providers that issue dynamic endpoints. var brokenAuthHeaderDomains = []string{ + ".auth0.com", ".force.com", + ".myshopify.com", ".okta.com", ".oktapreview.com", } @@ -165,10 +175,6 @@ func providerAuthHeaderWorks(tokenURL string) bool { } func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string, v url.Values) (*Token, error) { - hc, err := ContextClient(ctx) - if err != nil { - return nil, err - } bustedAuth := !providerAuthHeaderWorks(tokenURL) if bustedAuth { if clientID != "" { @@ -184,9 +190,9 @@ func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string, } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") if !bustedAuth { - req.SetBasicAuth(clientID, clientSecret) + req.SetBasicAuth(url.QueryEscape(clientID), url.QueryEscape(clientSecret)) } - r, err := hc.Do(req) + r, err := ctxhttp.Do(ctx, ContextClient(ctx), req) if err != nil { return nil, err } @@ -196,7 +202,10 @@ func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string, return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err) } if code := r.StatusCode; code < 200 || code > 299 { - return nil, fmt.Errorf("oauth2: cannot fetch token: %v\nResponse: %s", r.Status, body) + return nil, &RetrieveError{ + Response: r, + Body: body, + } } var token *Token @@ -243,5 +252,17 @@ func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string, if token.RefreshToken == "" { token.RefreshToken = v.Get("refresh_token") } + if token.AccessToken == "" { + return token, errors.New("oauth2: server response missing access_token") + } return token, nil } + +type RetrieveError struct { + Response *http.Response + Body []byte +} + +func (r *RetrieveError) Error() string { + return fmt.Sprintf("oauth2: cannot fetch token: %v\nResponse: %s", r.Response.Status, r.Body) +} diff --git a/vendor/golang.org/x/oauth2/internal/transport.go b/vendor/golang.org/x/oauth2/internal/transport.go index f1f173e34..d16f9ae1f 100644 --- a/vendor/golang.org/x/oauth2/internal/transport.go +++ b/vendor/golang.org/x/oauth2/internal/transport.go @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package internal contains support packages for oauth2 package. package internal import ( @@ -20,50 +19,16 @@ var HTTPClient ContextKey // because nobody else can create a ContextKey, being unexported. type ContextKey struct{} -// ContextClientFunc is a func which tries to return an *http.Client -// given a Context value. If it returns an error, the search stops -// with that error. If it returns (nil, nil), the search continues -// down the list of registered funcs. -type ContextClientFunc func(context.Context) (*http.Client, error) +var appengineClientHook func(context.Context) *http.Client -var contextClientFuncs []ContextClientFunc - -func RegisterContextClientFunc(fn ContextClientFunc) { - contextClientFuncs = append(contextClientFuncs, fn) -} - -func ContextClient(ctx context.Context) (*http.Client, error) { +func ContextClient(ctx context.Context) *http.Client { if ctx != nil { if hc, ok := ctx.Value(HTTPClient).(*http.Client); ok { - return hc, nil + return hc } } - for _, fn := range contextClientFuncs { - c, err := fn(ctx) - if err != nil { - return nil, err - } - if c != nil { - return c, nil - } + if appengineClientHook != nil { + return appengineClientHook(ctx) } - return http.DefaultClient, nil -} - -func ContextTransport(ctx context.Context) http.RoundTripper { - hc, err := ContextClient(ctx) - // This is a rare error case (somebody using nil on App Engine). - if err != nil { - return ErrorTransport{err} - } - return hc.Transport -} - -// ErrorTransport returns the specified error on RoundTrip. -// This RoundTripper should be used in rare error cases where -// error handling can be postponed to response handling time. -type ErrorTransport struct{ Err error } - -func (t ErrorTransport) RoundTrip(*http.Request) (*http.Response, error) { - return nil, t.Err + return http.DefaultClient } diff --git a/vendor/golang.org/x/oauth2/jwt/jwt.go b/vendor/golang.org/x/oauth2/jwt/jwt.go index e016db421..e08f31595 100644 --- a/vendor/golang.org/x/oauth2/jwt/jwt.go +++ b/vendor/golang.org/x/oauth2/jwt/jwt.go @@ -124,7 +124,10 @@ func (js jwtSource) Token() (*oauth2.Token, error) { return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err) } if c := resp.StatusCode; c < 200 || c > 299 { - return nil, fmt.Errorf("oauth2: cannot fetch token: %v\nResponse: %s", resp.Status, body) + return nil, &oauth2.RetrieveError{ + Response: resp, + Body: body, + } } // tokenRes is the JSON response body. var tokenRes struct { diff --git a/vendor/golang.org/x/oauth2/oauth2.go b/vendor/golang.org/x/oauth2/oauth2.go index 3e4835d7e..a047a5f98 100644 --- a/vendor/golang.org/x/oauth2/oauth2.go +++ b/vendor/golang.org/x/oauth2/oauth2.go @@ -117,7 +117,7 @@ func SetAuthURLParam(key, value string) AuthCodeOption { // that asks for permissions for the required scopes explicitly. // // State is a token to protect the user from CSRF attacks. You must -// always provide a non-zero string and validate that it matches the +// always provide a non-empty string and validate that it matches the // the state query parameter on your redirect callback. // See http://tools.ietf.org/html/rfc6749#section-10.12 for more info. // @@ -129,9 +129,16 @@ func (c *Config) AuthCodeURL(state string, opts ...AuthCodeOption) string { v := url.Values{ "response_type": {"code"}, "client_id": {c.ClientID}, - "redirect_uri": internal.CondVal(c.RedirectURL), - "scope": internal.CondVal(strings.Join(c.Scopes, " ")), - "state": internal.CondVal(state), + } + if c.RedirectURL != "" { + v.Set("redirect_uri", c.RedirectURL) + } + if len(c.Scopes) > 0 { + v.Set("scope", strings.Join(c.Scopes, " ")) + } + if state != "" { + // TODO(light): Docs say never to omit state; don't allow empty. + v.Set("state", state) } for _, opt := range opts { opt.setValue(v) @@ -157,12 +164,15 @@ func (c *Config) AuthCodeURL(state string, opts ...AuthCodeOption) string { // The HTTP client to use is derived from the context. // If nil, http.DefaultClient is used. func (c *Config) PasswordCredentialsToken(ctx context.Context, username, password string) (*Token, error) { - return retrieveToken(ctx, c, url.Values{ + v := url.Values{ "grant_type": {"password"}, "username": {username}, "password": {password}, - "scope": internal.CondVal(strings.Join(c.Scopes, " ")), - }) + } + if len(c.Scopes) > 0 { + v.Set("scope", strings.Join(c.Scopes, " ")) + } + return retrieveToken(ctx, c, v) } // Exchange converts an authorization code into a token. @@ -176,11 +186,14 @@ func (c *Config) PasswordCredentialsToken(ctx context.Context, username, passwor // The code will be in the *http.Request.FormValue("code"). Before // calling Exchange, be sure to validate FormValue("state"). func (c *Config) Exchange(ctx context.Context, code string) (*Token, error) { - return retrieveToken(ctx, c, url.Values{ - "grant_type": {"authorization_code"}, - "code": {code}, - "redirect_uri": internal.CondVal(c.RedirectURL), - }) + v := url.Values{ + "grant_type": {"authorization_code"}, + "code": {code}, + } + if c.RedirectURL != "" { + v.Set("redirect_uri", c.RedirectURL) + } + return retrieveToken(ctx, c, v) } // Client returns an HTTP client using the provided token. @@ -291,20 +304,20 @@ var HTTPClient internal.ContextKey // NewClient creates an *http.Client from a Context and TokenSource. // The returned client is not valid beyond the lifetime of the context. // +// Note that if a custom *http.Client is provided via the Context it +// is used only for token acquisition and is not used to configure the +// *http.Client returned from NewClient. +// // As a special case, if src is nil, a non-OAuth2 client is returned // using the provided context. This exists to support related OAuth2 // packages. func NewClient(ctx context.Context, src TokenSource) *http.Client { if src == nil { - c, err := internal.ContextClient(ctx) - if err != nil { - return &http.Client{Transport: internal.ErrorTransport{Err: err}} - } - return c + return internal.ContextClient(ctx) } return &http.Client{ Transport: &Transport{ - Base: internal.ContextTransport(ctx), + Base: internal.ContextClient(ctx).Transport, Source: ReuseTokenSource(nil, src), }, } diff --git a/vendor/golang.org/x/oauth2/token.go b/vendor/golang.org/x/oauth2/token.go index 7a3167f15..34db8cdc8 100644 --- a/vendor/golang.org/x/oauth2/token.go +++ b/vendor/golang.org/x/oauth2/token.go @@ -5,6 +5,7 @@ package oauth2 import ( + "fmt" "net/http" "net/url" "strconv" @@ -20,7 +21,7 @@ import ( // expirations due to client-server time mismatches. const expiryDelta = 10 * time.Second -// Token represents the crendentials used to authorize +// Token represents the credentials used to authorize // the requests to access protected resources on the OAuth 2.0 // provider's backend. // @@ -123,7 +124,7 @@ func (t *Token) expired() bool { if t.Expiry.IsZero() { return false } - return t.Expiry.Add(-expiryDelta).Before(time.Now()) + return t.Expiry.Round(0).Add(-expiryDelta).Before(time.Now()) } // Valid reports whether t is non-nil, has an AccessToken, and is not expired. @@ -152,7 +153,23 @@ func tokenFromInternal(t *internal.Token) *Token { func retrieveToken(ctx context.Context, c *Config, v url.Values) (*Token, error) { tk, err := internal.RetrieveToken(ctx, c.ClientID, c.ClientSecret, c.Endpoint.TokenURL, v) if err != nil { + if rErr, ok := err.(*internal.RetrieveError); ok { + return nil, (*RetrieveError)(rErr) + } return nil, err } return tokenFromInternal(tk), nil } + +// RetrieveError is the error returned when the token endpoint returns a +// non-2XX HTTP status code. +type RetrieveError struct { + Response *http.Response + // Body is the body that was consumed by reading Response.Body. + // It may be truncated. + Body []byte +} + +func (r *RetrieveError) Error() string { + return fmt.Sprintf("oauth2: cannot fetch token: %v\nResponse: %s", r.Response.Status, r.Body) +} diff --git a/vendor/golang.org/x/oauth2/transport.go b/vendor/golang.org/x/oauth2/transport.go index 92ac7e253..aa0d34f1e 100644 --- a/vendor/golang.org/x/oauth2/transport.go +++ b/vendor/golang.org/x/oauth2/transport.go @@ -31,9 +31,17 @@ type Transport struct { } // RoundTrip authorizes and authenticates the request with an -// access token. If no token exists or token is expired, -// tries to refresh/fetch a new token. +// access token from Transport's Source. func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { + reqBodyClosed := false + if req.Body != nil { + defer func() { + if !reqBodyClosed { + req.Body.Close() + } + }() + } + if t.Source == nil { return nil, errors.New("oauth2: Transport's Source is nil") } @@ -46,6 +54,10 @@ func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { token.SetAuthHeader(req2) t.setModReq(req, req2) res, err := t.base().RoundTrip(req2) + + // req.Body is assumed to have been closed by the base RoundTripper. + reqBodyClosed = true + if err != nil { t.setModReq(req, nil) return nil, err diff --git a/vendor/google.golang.org/api/dns/v1/dns-gen.go b/vendor/google.golang.org/api/dns/v1/dns-gen.go index fa66410d6..8ffc31664 100644 --- a/vendor/google.golang.org/api/dns/v1/dns-gen.go +++ b/vendor/google.golang.org/api/dns/v1/dns-gen.go @@ -66,6 +66,8 @@ func New(client *http.Client) (*Service, error) { } s := &Service{client: client, BasePath: basePath} s.Changes = NewChangesService(s) + s.DnsKeys = NewDnsKeysService(s) + s.ManagedZoneOperations = NewManagedZoneOperationsService(s) s.ManagedZones = NewManagedZonesService(s) s.Projects = NewProjectsService(s) s.ResourceRecordSets = NewResourceRecordSetsService(s) @@ -79,6 +81,10 @@ type Service struct { Changes *ChangesService + DnsKeys *DnsKeysService + + ManagedZoneOperations *ManagedZoneOperationsService + ManagedZones *ManagedZonesService Projects *ProjectsService @@ -102,6 +108,24 @@ type ChangesService struct { s *Service } +func NewDnsKeysService(s *Service) *DnsKeysService { + rs := &DnsKeysService{s: s} + return rs +} + +type DnsKeysService struct { + s *Service +} + +func NewManagedZoneOperationsService(s *Service) *ManagedZoneOperationsService { + rs := &ManagedZoneOperationsService{s: s} + return rs +} + +type ManagedZoneOperationsService struct { + s *Service +} + func NewManagedZonesService(s *Service) *ManagedZonesService { rs := &ManagedZonesService{s: s} return rs @@ -142,6 +166,9 @@ type Change struct { // only). Id string `json:"id,omitempty"` + // IsServing: If the DNS queries for the zone will be served. + IsServing bool `json:"isServing,omitempty"` + // Kind: Identifies what kind of resource this is. Value: the fixed // string "dns#change". Kind string `json:"kind,omitempty"` @@ -179,8 +206,8 @@ type Change struct { } func (s *Change) MarshalJSON() ([]byte, error) { - type noMethod Change - raw := noMethod(*s) + type NoMethod Change + raw := NoMethod(*s) return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) } @@ -190,6 +217,8 @@ type ChangesListResponse struct { // Changes: The requested changes. Changes []*Change `json:"changes,omitempty"` + Header *ResponseHeader `json:"header,omitempty"` + // Kind: Type of resource. Kind string `json:"kind,omitempty"` @@ -228,8 +257,246 @@ type ChangesListResponse struct { } func (s *ChangesListResponse) MarshalJSON() ([]byte, error) { - type noMethod ChangesListResponse - raw := noMethod(*s) + type NoMethod ChangesListResponse + raw := NoMethod(*s) + return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) +} + +// DnsKey: A DNSSEC key pair. +type DnsKey struct { + // Algorithm: String mnemonic specifying the DNSSEC algorithm of this + // key. Immutable after creation time. + // + // Possible values: + // "ecdsap256sha256" + // "ecdsap384sha384" + // "rsasha1" + // "rsasha256" + // "rsasha512" + Algorithm string `json:"algorithm,omitempty"` + + // CreationTime: The time that this resource was created in the control + // plane. This is in RFC3339 text format. Output only. + CreationTime string `json:"creationTime,omitempty"` + + // Description: A mutable string of at most 1024 characters associated + // with this resource for the user's convenience. Has no effect on the + // resource's function. + Description string `json:"description,omitempty"` + + // Digests: Cryptographic hashes of the DNSKEY resource record + // associated with this DnsKey. These digests are needed to construct a + // DS record that points at this DNS key. Output only. + Digests []*DnsKeyDigest `json:"digests,omitempty"` + + // Id: Unique identifier for the resource; defined by the server (output + // only). + Id string `json:"id,omitempty"` + + // IsActive: Active keys will be used to sign subsequent changes to the + // ManagedZone. Inactive keys will still be present as DNSKEY Resource + // Records for the use of resolvers validating existing signatures. + IsActive bool `json:"isActive,omitempty"` + + // KeyLength: Length of the key in bits. Specified at creation time then + // immutable. + KeyLength int64 `json:"keyLength,omitempty"` + + // KeyTag: The key tag is a non-cryptographic hash of the a DNSKEY + // resource record associated with this DnsKey. The key tag can be used + // to identify a DNSKEY more quickly (but it is not a unique + // identifier). In particular, the key tag is used in a parent zone's DS + // record to point at the DNSKEY in this child ManagedZone. The key tag + // is a number in the range [0, 65535] and the algorithm to calculate it + // is specified in RFC4034 Appendix B. Output only. + KeyTag int64 `json:"keyTag,omitempty"` + + // Kind: Identifies what kind of resource this is. Value: the fixed + // string "dns#dnsKey". + Kind string `json:"kind,omitempty"` + + // PublicKey: Base64 encoded public half of this key. Output only. + PublicKey string `json:"publicKey,omitempty"` + + // Type: One of "KEY_SIGNING" or "ZONE_SIGNING". Keys of type + // KEY_SIGNING have the Secure Entry Point flag set and, when active, + // will be used to sign only resource record sets of type DNSKEY. + // Otherwise, the Secure Entry Point flag will be cleared and this key + // will be used to sign only resource record sets of other types. + // Immutable after creation time. + // + // Possible values: + // "keySigning" + // "zoneSigning" + Type string `json:"type,omitempty"` + + // ServerResponse contains the HTTP response code and headers from the + // server. + googleapi.ServerResponse `json:"-"` + + // ForceSendFields is a list of field names (e.g. "Algorithm") to + // unconditionally include in API requests. By default, fields with + // empty values are omitted from API requests. However, any non-pointer, + // non-interface field appearing in ForceSendFields will be sent to the + // server regardless of whether the field is empty or not. This may be + // used to include empty fields in Patch requests. + ForceSendFields []string `json:"-"` + + // NullFields is a list of field names (e.g. "Algorithm") to include in + // API requests with the JSON null value. By default, fields with empty + // values are omitted from API requests. However, any field with an + // empty value appearing in NullFields will be sent to the server as + // null. It is an error if a field in this list has a non-empty value. + // This may be used to include null fields in Patch requests. + NullFields []string `json:"-"` +} + +func (s *DnsKey) MarshalJSON() ([]byte, error) { + type NoMethod DnsKey + raw := NoMethod(*s) + return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) +} + +type DnsKeyDigest struct { + // Digest: The base-16 encoded bytes of this digest. Suitable for use in + // a DS resource record. + Digest string `json:"digest,omitempty"` + + // Type: Specifies the algorithm used to calculate this digest. + // + // Possible values: + // "sha1" + // "sha256" + // "sha384" + Type string `json:"type,omitempty"` + + // ForceSendFields is a list of field names (e.g. "Digest") to + // unconditionally include in API requests. By default, fields with + // empty values are omitted from API requests. However, any non-pointer, + // non-interface field appearing in ForceSendFields will be sent to the + // server regardless of whether the field is empty or not. This may be + // used to include empty fields in Patch requests. + ForceSendFields []string `json:"-"` + + // NullFields is a list of field names (e.g. "Digest") to include in API + // requests with the JSON null value. By default, fields with empty + // values are omitted from API requests. However, any field with an + // empty value appearing in NullFields will be sent to the server as + // null. It is an error if a field in this list has a non-empty value. + // This may be used to include null fields in Patch requests. + NullFields []string `json:"-"` +} + +func (s *DnsKeyDigest) MarshalJSON() ([]byte, error) { + type NoMethod DnsKeyDigest + raw := NoMethod(*s) + return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) +} + +// DnsKeySpec: Parameters for DnsKey key generation. Used for generating +// initial keys for a new ManagedZone and as default when adding a new +// DnsKey. +type DnsKeySpec struct { + // Algorithm: String mnemonic specifying the DNSSEC algorithm of this + // key. + // + // Possible values: + // "ecdsap256sha256" + // "ecdsap384sha384" + // "rsasha1" + // "rsasha256" + // "rsasha512" + Algorithm string `json:"algorithm,omitempty"` + + // KeyLength: Length of the keys in bits. + KeyLength int64 `json:"keyLength,omitempty"` + + // KeyType: One of "KEY_SIGNING" or "ZONE_SIGNING". Keys of type + // KEY_SIGNING have the Secure Entry Point flag set and, when active, + // will be used to sign only resource record sets of type DNSKEY. + // Otherwise, the Secure Entry Point flag will be cleared and this key + // will be used to sign only resource record sets of other types. + // + // Possible values: + // "keySigning" + // "zoneSigning" + KeyType string `json:"keyType,omitempty"` + + // Kind: Identifies what kind of resource this is. Value: the fixed + // string "dns#dnsKeySpec". + Kind string `json:"kind,omitempty"` + + // ForceSendFields is a list of field names (e.g. "Algorithm") to + // unconditionally include in API requests. By default, fields with + // empty values are omitted from API requests. However, any non-pointer, + // non-interface field appearing in ForceSendFields will be sent to the + // server regardless of whether the field is empty or not. This may be + // used to include empty fields in Patch requests. + ForceSendFields []string `json:"-"` + + // NullFields is a list of field names (e.g. "Algorithm") to include in + // API requests with the JSON null value. By default, fields with empty + // values are omitted from API requests. However, any field with an + // empty value appearing in NullFields will be sent to the server as + // null. It is an error if a field in this list has a non-empty value. + // This may be used to include null fields in Patch requests. + NullFields []string `json:"-"` +} + +func (s *DnsKeySpec) MarshalJSON() ([]byte, error) { + type NoMethod DnsKeySpec + raw := NoMethod(*s) + return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) +} + +// DnsKeysListResponse: The response to a request to enumerate DnsKeys +// in a ManagedZone. +type DnsKeysListResponse struct { + // DnsKeys: The requested resources. + DnsKeys []*DnsKey `json:"dnsKeys,omitempty"` + + Header *ResponseHeader `json:"header,omitempty"` + + // Kind: Type of resource. + Kind string `json:"kind,omitempty"` + + // NextPageToken: The presence of this field indicates that there exist + // more results following your last page of results in pagination order. + // To fetch them, make another list request using this value as your + // pagination token. + // + // In this way you can retrieve the complete contents of even very large + // collections one page at a time. However, if the contents of the + // collection change between the first and last paginated list request, + // the set of all elements returned will be an inconsistent view of the + // collection. There is no way to retrieve a "snapshot" of collections + // larger than the maximum page size. + NextPageToken string `json:"nextPageToken,omitempty"` + + // ServerResponse contains the HTTP response code and headers from the + // server. + googleapi.ServerResponse `json:"-"` + + // ForceSendFields is a list of field names (e.g. "DnsKeys") to + // unconditionally include in API requests. By default, fields with + // empty values are omitted from API requests. However, any non-pointer, + // non-interface field appearing in ForceSendFields will be sent to the + // server regardless of whether the field is empty or not. This may be + // used to include empty fields in Patch requests. + ForceSendFields []string `json:"-"` + + // NullFields is a list of field names (e.g. "DnsKeys") to include in + // API requests with the JSON null value. By default, fields with empty + // values are omitted from API requests. However, any field with an + // empty value appearing in NullFields will be sent to the server as + // null. It is an error if a field in this list has a non-empty value. + // This may be used to include null fields in Patch requests. + NullFields []string `json:"-"` +} + +func (s *DnsKeysListResponse) MarshalJSON() ([]byte, error) { + type NoMethod DnsKeysListResponse + raw := NoMethod(*s) return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) } @@ -250,6 +517,9 @@ type ManagedZone struct { // "example.com.". DnsName string `json:"dnsName,omitempty"` + // DnssecConfig: DNSSEC configuration. + DnssecConfig *ManagedZoneDnsSecConfig `json:"dnssecConfig,omitempty"` + // Id: Unique identifier for the resource; defined by the server (output // only) Id uint64 `json:"id,omitempty,string"` @@ -258,6 +528,9 @@ type ManagedZone struct { // string "dns#managedZone". Kind string `json:"kind,omitempty"` + // Labels: User labels. + Labels map[string]string `json:"labels,omitempty"` + // Name: User assigned name for this resource. Must be unique within the // project. The name must be 1-63 characters long, must begin with a // letter, end with a letter or digit, and only contain lowercase @@ -295,12 +568,113 @@ type ManagedZone struct { } func (s *ManagedZone) MarshalJSON() ([]byte, error) { - type noMethod ManagedZone - raw := noMethod(*s) + type NoMethod ManagedZone + raw := NoMethod(*s) + return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) +} + +type ManagedZoneDnsSecConfig struct { + // DefaultKeySpecs: Specifies parameters that will be used for + // generating initial DnsKeys for this ManagedZone. Output only while + // state is not OFF. + DefaultKeySpecs []*DnsKeySpec `json:"defaultKeySpecs,omitempty"` + + // Kind: Identifies what kind of resource this is. Value: the fixed + // string "dns#managedZoneDnsSecConfig". + Kind string `json:"kind,omitempty"` + + // NonExistence: Specifies the mechanism used to provide authenticated + // denial-of-existence responses. Output only while state is not OFF. + // + // Possible values: + // "nsec" + // "nsec3" + NonExistence string `json:"nonExistence,omitempty"` + + // State: Specifies whether DNSSEC is enabled, and what mode it is in. + // + // Possible values: + // "off" + // "on" + // "transfer" + State string `json:"state,omitempty"` + + // ForceSendFields is a list of field names (e.g. "DefaultKeySpecs") to + // unconditionally include in API requests. By default, fields with + // empty values are omitted from API requests. However, any non-pointer, + // non-interface field appearing in ForceSendFields will be sent to the + // server regardless of whether the field is empty or not. This may be + // used to include empty fields in Patch requests. + ForceSendFields []string `json:"-"` + + // NullFields is a list of field names (e.g. "DefaultKeySpecs") to + // include in API requests with the JSON null value. By default, fields + // with empty values are omitted from API requests. However, any field + // with an empty value appearing in NullFields will be sent to the + // server as null. It is an error if a field in this list has a + // non-empty value. This may be used to include null fields in Patch + // requests. + NullFields []string `json:"-"` +} + +func (s *ManagedZoneDnsSecConfig) MarshalJSON() ([]byte, error) { + type NoMethod ManagedZoneDnsSecConfig + raw := NoMethod(*s) + return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) +} + +type ManagedZoneOperationsListResponse struct { + Header *ResponseHeader `json:"header,omitempty"` + + // Kind: Type of resource. + Kind string `json:"kind,omitempty"` + + // NextPageToken: The presence of this field indicates that there exist + // more results following your last page of results in pagination order. + // To fetch them, make another list request using this value as your + // page token. + // + // In this way you can retrieve the complete contents of even very large + // collections one page at a time. However, if the contents of the + // collection change between the first and last paginated list request, + // the set of all elements returned will be an inconsistent view of the + // collection. There is no way to retrieve a consistent snapshot of a + // collection larger than the maximum page size. + NextPageToken string `json:"nextPageToken,omitempty"` + + // Operations: The operation resources. + Operations []*Operation `json:"operations,omitempty"` + + // ServerResponse contains the HTTP response code and headers from the + // server. + googleapi.ServerResponse `json:"-"` + + // ForceSendFields is a list of field names (e.g. "Header") to + // unconditionally include in API requests. By default, fields with + // empty values are omitted from API requests. However, any non-pointer, + // non-interface field appearing in ForceSendFields will be sent to the + // server regardless of whether the field is empty or not. This may be + // used to include empty fields in Patch requests. + ForceSendFields []string `json:"-"` + + // NullFields is a list of field names (e.g. "Header") to include in API + // requests with the JSON null value. By default, fields with empty + // values are omitted from API requests. However, any field with an + // empty value appearing in NullFields will be sent to the server as + // null. It is an error if a field in this list has a non-empty value. + // This may be used to include null fields in Patch requests. + NullFields []string `json:"-"` +} + +func (s *ManagedZoneOperationsListResponse) MarshalJSON() ([]byte, error) { + type NoMethod ManagedZoneOperationsListResponse + raw := NoMethod(*s) return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) } type ManagedZonesListResponse struct { + Header *ResponseHeader `json:"header,omitempty"` + // Kind: Type of resource. Kind string `json:"kind,omitempty"` @@ -324,7 +698,7 @@ type ManagedZonesListResponse struct { // server. googleapi.ServerResponse `json:"-"` - // ForceSendFields is a list of field names (e.g. "Kind") to + // ForceSendFields is a list of field names (e.g. "Header") to // unconditionally include in API requests. By default, fields with // empty values are omitted from API requests. However, any non-pointer, // non-interface field appearing in ForceSendFields will be sent to the @@ -332,7 +706,7 @@ type ManagedZonesListResponse struct { // used to include empty fields in Patch requests. ForceSendFields []string `json:"-"` - // NullFields is a list of field names (e.g. "Kind") to include in API + // NullFields is a list of field names (e.g. "Header") to include in API // requests with the JSON null value. By default, fields with empty // values are omitted from API requests. However, any field with an // empty value appearing in NullFields will be sent to the server as @@ -342,8 +716,141 @@ type ManagedZonesListResponse struct { } func (s *ManagedZonesListResponse) MarshalJSON() ([]byte, error) { - type noMethod ManagedZonesListResponse - raw := noMethod(*s) + type NoMethod ManagedZonesListResponse + raw := NoMethod(*s) + return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) +} + +// Operation: An operation represents a successful mutation performed on +// a Cloud DNS resource. Operations provide: - An audit log of server +// resource mutations. - A way to recover/retry API calls in the case +// where the response is never received by the caller. Use the caller +// specified client_operation_id. +type Operation struct { + // DnsKeyContext: Only populated if the operation targeted a DnsKey + // (output only). + DnsKeyContext *OperationDnsKeyContext `json:"dnsKeyContext,omitempty"` + + // Id: Unique identifier for the resource. This is the + // client_operation_id if the client specified it when the mutation was + // initiated, otherwise, it is generated by the server. The name must be + // 1-63 characters long and match the regular expression [-a-z0-9]? + // (output only) + Id string `json:"id,omitempty"` + + // Kind: Identifies what kind of resource this is. Value: the fixed + // string "dns#operation". + Kind string `json:"kind,omitempty"` + + // StartTime: The time that this operation was started by the server. + // This is in RFC3339 text format (output only). + StartTime string `json:"startTime,omitempty"` + + // Status: Status of the operation. Can be one of the following: + // "PENDING" or "DONE" (output only). + // + // Possible values: + // "done" + // "pending" + Status string `json:"status,omitempty"` + + // Type: Type of the operation. Operations include insert, update, and + // delete (output only). + Type string `json:"type,omitempty"` + + // User: User who requested the operation, for example: + // user@example.com. cloud-dns-system for operations automatically done + // by the system. (output only) + User string `json:"user,omitempty"` + + // ZoneContext: Only populated if the operation targeted a ManagedZone + // (output only). + ZoneContext *OperationManagedZoneContext `json:"zoneContext,omitempty"` + + // ServerResponse contains the HTTP response code and headers from the + // server. + googleapi.ServerResponse `json:"-"` + + // ForceSendFields is a list of field names (e.g. "DnsKeyContext") to + // unconditionally include in API requests. By default, fields with + // empty values are omitted from API requests. However, any non-pointer, + // non-interface field appearing in ForceSendFields will be sent to the + // server regardless of whether the field is empty or not. This may be + // used to include empty fields in Patch requests. + ForceSendFields []string `json:"-"` + + // NullFields is a list of field names (e.g. "DnsKeyContext") to include + // in API requests with the JSON null value. By default, fields with + // empty values are omitted from API requests. However, any field with + // an empty value appearing in NullFields will be sent to the server as + // null. It is an error if a field in this list has a non-empty value. + // This may be used to include null fields in Patch requests. + NullFields []string `json:"-"` +} + +func (s *Operation) MarshalJSON() ([]byte, error) { + type NoMethod Operation + raw := NoMethod(*s) + return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) +} + +type OperationDnsKeyContext struct { + // NewValue: The post-operation DnsKey resource. + NewValue *DnsKey `json:"newValue,omitempty"` + + // OldValue: The pre-operation DnsKey resource. + OldValue *DnsKey `json:"oldValue,omitempty"` + + // ForceSendFields is a list of field names (e.g. "NewValue") to + // unconditionally include in API requests. By default, fields with + // empty values are omitted from API requests. However, any non-pointer, + // non-interface field appearing in ForceSendFields will be sent to the + // server regardless of whether the field is empty or not. This may be + // used to include empty fields in Patch requests. + ForceSendFields []string `json:"-"` + + // NullFields is a list of field names (e.g. "NewValue") to include in + // API requests with the JSON null value. By default, fields with empty + // values are omitted from API requests. However, any field with an + // empty value appearing in NullFields will be sent to the server as + // null. It is an error if a field in this list has a non-empty value. + // This may be used to include null fields in Patch requests. + NullFields []string `json:"-"` +} + +func (s *OperationDnsKeyContext) MarshalJSON() ([]byte, error) { + type NoMethod OperationDnsKeyContext + raw := NoMethod(*s) + return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) +} + +type OperationManagedZoneContext struct { + // NewValue: The post-operation ManagedZone resource. + NewValue *ManagedZone `json:"newValue,omitempty"` + + // OldValue: The pre-operation ManagedZone resource. + OldValue *ManagedZone `json:"oldValue,omitempty"` + + // ForceSendFields is a list of field names (e.g. "NewValue") to + // unconditionally include in API requests. By default, fields with + // empty values are omitted from API requests. However, any non-pointer, + // non-interface field appearing in ForceSendFields will be sent to the + // server regardless of whether the field is empty or not. This may be + // used to include empty fields in Patch requests. + ForceSendFields []string `json:"-"` + + // NullFields is a list of field names (e.g. "NewValue") to include in + // API requests with the JSON null value. By default, fields with empty + // values are omitted from API requests. However, any field with an + // empty value appearing in NullFields will be sent to the server as + // null. It is an error if a field in this list has a non-empty value. + // This may be used to include null fields in Patch requests. + NullFields []string `json:"-"` +} + +func (s *OperationManagedZoneContext) MarshalJSON() ([]byte, error) { + type NoMethod OperationManagedZoneContext + raw := NoMethod(*s) return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) } @@ -387,13 +894,21 @@ type Project struct { } func (s *Project) MarshalJSON() ([]byte, error) { - type noMethod Project - raw := noMethod(*s) + type NoMethod Project + raw := NoMethod(*s) return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) } // Quota: Limits associated with a Project. type Quota struct { + // BlackHoleHidesSystemZones: Whether a black hole zone should suppress + // system zones for this project. + BlackHoleHidesSystemZones bool `json:"blackHoleHidesSystemZones,omitempty"` + + // DnsKeysPerManagedZone: Maximum allowed number of DnsKeys per + // ManagedZone. + DnsKeysPerManagedZone int64 `json:"dnsKeysPerManagedZone,omitempty"` + // Kind: Identifies what kind of resource this is. Value: the fixed // string "dns#quota". Kind string `json:"kind,omitempty"` @@ -421,26 +936,32 @@ type Quota struct { // one ChangesCreateRequest in bytes. TotalRrdataSizePerChange int64 `json:"totalRrdataSizePerChange,omitempty"` - // ForceSendFields is a list of field names (e.g. "Kind") to - // unconditionally include in API requests. By default, fields with - // empty values are omitted from API requests. However, any non-pointer, - // non-interface field appearing in ForceSendFields will be sent to the - // server regardless of whether the field is empty or not. This may be - // used to include empty fields in Patch requests. + // WhitelistedKeySpecs: DNSSEC algorithm and key length types that can + // be used for DnsKeys. + WhitelistedKeySpecs []*DnsKeySpec `json:"whitelistedKeySpecs,omitempty"` + + // ForceSendFields is a list of field names (e.g. + // "BlackHoleHidesSystemZones") to unconditionally include in API + // requests. By default, fields with empty values are omitted from API + // requests. However, any non-pointer, non-interface field appearing in + // ForceSendFields will be sent to the server regardless of whether the + // field is empty or not. This may be used to include empty fields in + // Patch requests. ForceSendFields []string `json:"-"` - // NullFields is a list of field names (e.g. "Kind") to include in API - // requests with the JSON null value. By default, fields with empty - // values are omitted from API requests. However, any field with an - // empty value appearing in NullFields will be sent to the server as - // null. It is an error if a field in this list has a non-empty value. - // This may be used to include null fields in Patch requests. + // NullFields is a list of field names (e.g. + // "BlackHoleHidesSystemZones") to include in API requests with the JSON + // null value. By default, fields with empty values are omitted from API + // requests. However, any field with an empty value appearing in + // NullFields will be sent to the server as null. It is an error if a + // field in this list has a non-empty value. This may be used to include + // null fields in Patch requests. NullFields []string `json:"-"` } func (s *Quota) MarshalJSON() ([]byte, error) { - type noMethod Quota - raw := noMethod(*s) + type NoMethod Quota + raw := NoMethod(*s) return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) } @@ -458,6 +979,9 @@ type ResourceRecordSet struct { // 3.6.1). Rrdatas []string `json:"rrdatas,omitempty"` + // SignatureRrdatas: As defined in RFC 4034 (section 3.2). + SignatureRrdatas []string `json:"signatureRrdatas,omitempty"` + // Ttl: Number of seconds that this ResourceRecordSet can be cached by // resolvers. Ttl int64 `json:"ttl,omitempty"` @@ -484,12 +1008,14 @@ type ResourceRecordSet struct { } func (s *ResourceRecordSet) MarshalJSON() ([]byte, error) { - type noMethod ResourceRecordSet - raw := noMethod(*s) + type NoMethod ResourceRecordSet + raw := NoMethod(*s) return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) } type ResourceRecordSetsListResponse struct { + Header *ResponseHeader `json:"header,omitempty"` + // Kind: Type of resource. Kind string `json:"kind,omitempty"` @@ -513,7 +1039,7 @@ type ResourceRecordSetsListResponse struct { // server. googleapi.ServerResponse `json:"-"` - // ForceSendFields is a list of field names (e.g. "Kind") to + // ForceSendFields is a list of field names (e.g. "Header") to // unconditionally include in API requests. By default, fields with // empty values are omitted from API requests. However, any non-pointer, // non-interface field appearing in ForceSendFields will be sent to the @@ -521,7 +1047,7 @@ type ResourceRecordSetsListResponse struct { // used to include empty fields in Patch requests. ForceSendFields []string `json:"-"` - // NullFields is a list of field names (e.g. "Kind") to include in API + // NullFields is a list of field names (e.g. "Header") to include in API // requests with the JSON null value. By default, fields with empty // values are omitted from API requests. However, any field with an // empty value appearing in NullFields will be sent to the server as @@ -531,8 +1057,38 @@ type ResourceRecordSetsListResponse struct { } func (s *ResourceRecordSetsListResponse) MarshalJSON() ([]byte, error) { - type noMethod ResourceRecordSetsListResponse - raw := noMethod(*s) + type NoMethod ResourceRecordSetsListResponse + raw := NoMethod(*s) + return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) +} + +// ResponseHeader: Elements common to every response. +type ResponseHeader struct { + // OperationId: For mutating operation requests that completed + // successfully. This is the client_operation_id if the client specified + // it, otherwise it is generated by the server (output only). + OperationId string `json:"operationId,omitempty"` + + // ForceSendFields is a list of field names (e.g. "OperationId") to + // unconditionally include in API requests. By default, fields with + // empty values are omitted from API requests. However, any non-pointer, + // non-interface field appearing in ForceSendFields will be sent to the + // server regardless of whether the field is empty or not. This may be + // used to include empty fields in Patch requests. + ForceSendFields []string `json:"-"` + + // NullFields is a list of field names (e.g. "OperationId") to include + // in API requests with the JSON null value. By default, fields with + // empty values are omitted from API requests. However, any field with + // an empty value appearing in NullFields will be sent to the server as + // null. It is an error if a field in this list has a non-empty value. + // This may be used to include null fields in Patch requests. + NullFields []string `json:"-"` +} + +func (s *ResponseHeader) MarshalJSON() ([]byte, error) { + type NoMethod ResponseHeader + raw := NoMethod(*s) return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) } @@ -557,6 +1113,15 @@ func (r *ChangesService) Create(project string, managedZone string, change *Chan return c } +// ClientOperationId sets the optional parameter "clientOperationId": +// For mutating operation requests only. An optional identifier +// specified by the client. Must be unique for operation resources in +// the Operations collection. +func (c *ChangesCreateCall) ClientOperationId(clientOperationId string) *ChangesCreateCall { + c.urlParams_.Set("clientOperationId", clientOperationId) + return c +} + // Fields allows partial responses to be retrieved. See // https://developers.google.com/gdata/docs/2.0/basics#PartialResponse // for more information. @@ -639,7 +1204,7 @@ func (c *ChangesCreateCall) Do(opts ...googleapi.CallOption) (*Change, error) { }, } target := &ret - if err := json.NewDecoder(res.Body).Decode(target); err != nil { + if err := gensupport.DecodeResponse(target, res); err != nil { return nil, err } return ret, nil @@ -652,6 +1217,11 @@ func (c *ChangesCreateCall) Do(opts ...googleapi.CallOption) (*Change, error) { // "managedZone" // ], // "parameters": { + // "clientOperationId": { + // "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", + // "location": "query", + // "type": "string" + // }, // "managedZone": { // "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", // "location": "path", @@ -702,6 +1272,15 @@ func (r *ChangesService) Get(project string, managedZone string, changeId string return c } +// ClientOperationId sets the optional parameter "clientOperationId": +// For mutating operation requests only. An optional identifier +// specified by the client. Must be unique for operation resources in +// the Operations collection. +func (c *ChangesGetCall) ClientOperationId(clientOperationId string) *ChangesGetCall { + c.urlParams_.Set("clientOperationId", clientOperationId) + return c +} + // Fields allows partial responses to be retrieved. See // https://developers.google.com/gdata/docs/2.0/basics#PartialResponse // for more information. @@ -793,7 +1372,7 @@ func (c *ChangesGetCall) Do(opts ...googleapi.CallOption) (*Change, error) { }, } target := &ret - if err := json.NewDecoder(res.Body).Decode(target); err != nil { + if err := gensupport.DecodeResponse(target, res); err != nil { return nil, err } return ret, nil @@ -813,6 +1392,11 @@ func (c *ChangesGetCall) Do(opts ...googleapi.CallOption) (*Change, error) { // "required": true, // "type": "string" // }, + // "clientOperationId": { + // "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", + // "location": "query", + // "type": "string" + // }, // "managedZone": { // "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", // "location": "path", @@ -983,7 +1567,7 @@ func (c *ChangesListCall) Do(opts ...googleapi.CallOption) (*ChangesListResponse }, } target := &ret - if err := json.NewDecoder(res.Body).Decode(target); err != nil { + if err := gensupport.DecodeResponse(target, res); err != nil { return nil, err } return ret, nil @@ -1072,6 +1656,804 @@ func (c *ChangesListCall) Pages(ctx context.Context, f func(*ChangesListResponse } } +// method id "dns.dnsKeys.get": + +type DnsKeysGetCall struct { + s *Service + project string + managedZone string + dnsKeyId string + urlParams_ gensupport.URLParams + ifNoneMatch_ string + ctx_ context.Context + header_ http.Header +} + +// Get: Fetch the representation of an existing DnsKey. +func (r *DnsKeysService) Get(project string, managedZone string, dnsKeyId string) *DnsKeysGetCall { + c := &DnsKeysGetCall{s: r.s, urlParams_: make(gensupport.URLParams)} + c.project = project + c.managedZone = managedZone + c.dnsKeyId = dnsKeyId + return c +} + +// ClientOperationId sets the optional parameter "clientOperationId": +// For mutating operation requests only. An optional identifier +// specified by the client. Must be unique for operation resources in +// the Operations collection. +func (c *DnsKeysGetCall) ClientOperationId(clientOperationId string) *DnsKeysGetCall { + c.urlParams_.Set("clientOperationId", clientOperationId) + return c +} + +// DigestType sets the optional parameter "digestType": An optional +// comma-separated list of digest types to compute and display for key +// signing keys. If omitted, the recommended digest type will be +// computed and displayed. +func (c *DnsKeysGetCall) DigestType(digestType string) *DnsKeysGetCall { + c.urlParams_.Set("digestType", digestType) + return c +} + +// Fields allows partial responses to be retrieved. See +// https://developers.google.com/gdata/docs/2.0/basics#PartialResponse +// for more information. +func (c *DnsKeysGetCall) Fields(s ...googleapi.Field) *DnsKeysGetCall { + c.urlParams_.Set("fields", googleapi.CombineFields(s)) + return c +} + +// IfNoneMatch sets the optional parameter which makes the operation +// fail if the object's ETag matches the given value. This is useful for +// getting updates only after the object has changed since the last +// request. Use googleapi.IsNotModified to check whether the response +// error from Do is the result of In-None-Match. +func (c *DnsKeysGetCall) IfNoneMatch(entityTag string) *DnsKeysGetCall { + c.ifNoneMatch_ = entityTag + return c +} + +// Context sets the context to be used in this call's Do method. Any +// pending HTTP request will be aborted if the provided context is +// canceled. +func (c *DnsKeysGetCall) Context(ctx context.Context) *DnsKeysGetCall { + c.ctx_ = ctx + return c +} + +// Header returns an http.Header that can be modified by the caller to +// add HTTP headers to the request. +func (c *DnsKeysGetCall) Header() http.Header { + if c.header_ == nil { + c.header_ = make(http.Header) + } + return c.header_ +} + +func (c *DnsKeysGetCall) doRequest(alt string) (*http.Response, error) { + reqHeaders := make(http.Header) + for k, v := range c.header_ { + reqHeaders[k] = v + } + reqHeaders.Set("User-Agent", c.s.userAgent()) + if c.ifNoneMatch_ != "" { + reqHeaders.Set("If-None-Match", c.ifNoneMatch_) + } + var body io.Reader = nil + c.urlParams_.Set("alt", alt) + urls := googleapi.ResolveRelative(c.s.BasePath, "{project}/managedZones/{managedZone}/dnsKeys/{dnsKeyId}") + urls += "?" + c.urlParams_.Encode() + req, _ := http.NewRequest("GET", urls, body) + req.Header = reqHeaders + googleapi.Expand(req.URL, map[string]string{ + "project": c.project, + "managedZone": c.managedZone, + "dnsKeyId": c.dnsKeyId, + }) + return gensupport.SendRequest(c.ctx_, c.s.client, req) +} + +// Do executes the "dns.dnsKeys.get" call. +// Exactly one of *DnsKey or error will be non-nil. Any non-2xx status +// code is an error. Response headers are in either +// *DnsKey.ServerResponse.Header or (if a response was returned at all) +// in error.(*googleapi.Error).Header. Use googleapi.IsNotModified to +// check whether the returned error was because http.StatusNotModified +// was returned. +func (c *DnsKeysGetCall) Do(opts ...googleapi.CallOption) (*DnsKey, error) { + gensupport.SetOptions(c.urlParams_, opts...) + res, err := c.doRequest("json") + if res != nil && res.StatusCode == http.StatusNotModified { + if res.Body != nil { + res.Body.Close() + } + return nil, &googleapi.Error{ + Code: res.StatusCode, + Header: res.Header, + } + } + if err != nil { + return nil, err + } + defer googleapi.CloseBody(res) + if err := googleapi.CheckResponse(res); err != nil { + return nil, err + } + ret := &DnsKey{ + ServerResponse: googleapi.ServerResponse{ + Header: res.Header, + HTTPStatusCode: res.StatusCode, + }, + } + target := &ret + if err := gensupport.DecodeResponse(target, res); err != nil { + return nil, err + } + return ret, nil + // { + // "description": "Fetch the representation of an existing DnsKey.", + // "httpMethod": "GET", + // "id": "dns.dnsKeys.get", + // "parameterOrder": [ + // "project", + // "managedZone", + // "dnsKeyId" + // ], + // "parameters": { + // "clientOperationId": { + // "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", + // "location": "query", + // "type": "string" + // }, + // "digestType": { + // "description": "An optional comma-separated list of digest types to compute and display for key signing keys. If omitted, the recommended digest type will be computed and displayed.", + // "location": "query", + // "type": "string" + // }, + // "dnsKeyId": { + // "description": "The identifier of the requested DnsKey.", + // "location": "path", + // "required": true, + // "type": "string" + // }, + // "managedZone": { + // "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", + // "location": "path", + // "required": true, + // "type": "string" + // }, + // "project": { + // "description": "Identifies the project addressed by this request.", + // "location": "path", + // "required": true, + // "type": "string" + // } + // }, + // "path": "{project}/managedZones/{managedZone}/dnsKeys/{dnsKeyId}", + // "response": { + // "$ref": "DnsKey" + // }, + // "scopes": [ + // "https://www.googleapis.com/auth/cloud-platform", + // "https://www.googleapis.com/auth/cloud-platform.read-only", + // "https://www.googleapis.com/auth/ndev.clouddns.readonly", + // "https://www.googleapis.com/auth/ndev.clouddns.readwrite" + // ] + // } + +} + +// method id "dns.dnsKeys.list": + +type DnsKeysListCall struct { + s *Service + project string + managedZone string + urlParams_ gensupport.URLParams + ifNoneMatch_ string + ctx_ context.Context + header_ http.Header +} + +// List: Enumerate DnsKeys to a ResourceRecordSet collection. +func (r *DnsKeysService) List(project string, managedZone string) *DnsKeysListCall { + c := &DnsKeysListCall{s: r.s, urlParams_: make(gensupport.URLParams)} + c.project = project + c.managedZone = managedZone + return c +} + +// DigestType sets the optional parameter "digestType": An optional +// comma-separated list of digest types to compute and display for key +// signing keys. If omitted, the recommended digest type will be +// computed and displayed. +func (c *DnsKeysListCall) DigestType(digestType string) *DnsKeysListCall { + c.urlParams_.Set("digestType", digestType) + return c +} + +// MaxResults sets the optional parameter "maxResults": Maximum number +// of results to be returned. If unspecified, the server will decide how +// many results to return. +func (c *DnsKeysListCall) MaxResults(maxResults int64) *DnsKeysListCall { + c.urlParams_.Set("maxResults", fmt.Sprint(maxResults)) + return c +} + +// PageToken sets the optional parameter "pageToken": A tag returned by +// a previous list request that was truncated. Use this parameter to +// continue a previous list request. +func (c *DnsKeysListCall) PageToken(pageToken string) *DnsKeysListCall { + c.urlParams_.Set("pageToken", pageToken) + return c +} + +// Fields allows partial responses to be retrieved. See +// https://developers.google.com/gdata/docs/2.0/basics#PartialResponse +// for more information. +func (c *DnsKeysListCall) Fields(s ...googleapi.Field) *DnsKeysListCall { + c.urlParams_.Set("fields", googleapi.CombineFields(s)) + return c +} + +// IfNoneMatch sets the optional parameter which makes the operation +// fail if the object's ETag matches the given value. This is useful for +// getting updates only after the object has changed since the last +// request. Use googleapi.IsNotModified to check whether the response +// error from Do is the result of In-None-Match. +func (c *DnsKeysListCall) IfNoneMatch(entityTag string) *DnsKeysListCall { + c.ifNoneMatch_ = entityTag + return c +} + +// Context sets the context to be used in this call's Do method. Any +// pending HTTP request will be aborted if the provided context is +// canceled. +func (c *DnsKeysListCall) Context(ctx context.Context) *DnsKeysListCall { + c.ctx_ = ctx + return c +} + +// Header returns an http.Header that can be modified by the caller to +// add HTTP headers to the request. +func (c *DnsKeysListCall) Header() http.Header { + if c.header_ == nil { + c.header_ = make(http.Header) + } + return c.header_ +} + +func (c *DnsKeysListCall) doRequest(alt string) (*http.Response, error) { + reqHeaders := make(http.Header) + for k, v := range c.header_ { + reqHeaders[k] = v + } + reqHeaders.Set("User-Agent", c.s.userAgent()) + if c.ifNoneMatch_ != "" { + reqHeaders.Set("If-None-Match", c.ifNoneMatch_) + } + var body io.Reader = nil + c.urlParams_.Set("alt", alt) + urls := googleapi.ResolveRelative(c.s.BasePath, "{project}/managedZones/{managedZone}/dnsKeys") + urls += "?" + c.urlParams_.Encode() + req, _ := http.NewRequest("GET", urls, body) + req.Header = reqHeaders + googleapi.Expand(req.URL, map[string]string{ + "project": c.project, + "managedZone": c.managedZone, + }) + return gensupport.SendRequest(c.ctx_, c.s.client, req) +} + +// Do executes the "dns.dnsKeys.list" call. +// Exactly one of *DnsKeysListResponse or error will be non-nil. Any +// non-2xx status code is an error. Response headers are in either +// *DnsKeysListResponse.ServerResponse.Header or (if a response was +// returned at all) in error.(*googleapi.Error).Header. Use +// googleapi.IsNotModified to check whether the returned error was +// because http.StatusNotModified was returned. +func (c *DnsKeysListCall) Do(opts ...googleapi.CallOption) (*DnsKeysListResponse, error) { + gensupport.SetOptions(c.urlParams_, opts...) + res, err := c.doRequest("json") + if res != nil && res.StatusCode == http.StatusNotModified { + if res.Body != nil { + res.Body.Close() + } + return nil, &googleapi.Error{ + Code: res.StatusCode, + Header: res.Header, + } + } + if err != nil { + return nil, err + } + defer googleapi.CloseBody(res) + if err := googleapi.CheckResponse(res); err != nil { + return nil, err + } + ret := &DnsKeysListResponse{ + ServerResponse: googleapi.ServerResponse{ + Header: res.Header, + HTTPStatusCode: res.StatusCode, + }, + } + target := &ret + if err := gensupport.DecodeResponse(target, res); err != nil { + return nil, err + } + return ret, nil + // { + // "description": "Enumerate DnsKeys to a ResourceRecordSet collection.", + // "httpMethod": "GET", + // "id": "dns.dnsKeys.list", + // "parameterOrder": [ + // "project", + // "managedZone" + // ], + // "parameters": { + // "digestType": { + // "description": "An optional comma-separated list of digest types to compute and display for key signing keys. If omitted, the recommended digest type will be computed and displayed.", + // "location": "query", + // "type": "string" + // }, + // "managedZone": { + // "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", + // "location": "path", + // "required": true, + // "type": "string" + // }, + // "maxResults": { + // "description": "Optional. Maximum number of results to be returned. If unspecified, the server will decide how many results to return.", + // "format": "int32", + // "location": "query", + // "type": "integer" + // }, + // "pageToken": { + // "description": "Optional. A tag returned by a previous list request that was truncated. Use this parameter to continue a previous list request.", + // "location": "query", + // "type": "string" + // }, + // "project": { + // "description": "Identifies the project addressed by this request.", + // "location": "path", + // "required": true, + // "type": "string" + // } + // }, + // "path": "{project}/managedZones/{managedZone}/dnsKeys", + // "response": { + // "$ref": "DnsKeysListResponse" + // }, + // "scopes": [ + // "https://www.googleapis.com/auth/cloud-platform", + // "https://www.googleapis.com/auth/cloud-platform.read-only", + // "https://www.googleapis.com/auth/ndev.clouddns.readonly", + // "https://www.googleapis.com/auth/ndev.clouddns.readwrite" + // ] + // } + +} + +// Pages invokes f for each page of results. +// A non-nil error returned from f will halt the iteration. +// The provided context supersedes any context provided to the Context method. +func (c *DnsKeysListCall) Pages(ctx context.Context, f func(*DnsKeysListResponse) error) error { + c.ctx_ = ctx + defer c.PageToken(c.urlParams_.Get("pageToken")) // reset paging to original point + for { + x, err := c.Do() + if err != nil { + return err + } + if err := f(x); err != nil { + return err + } + if x.NextPageToken == "" { + return nil + } + c.PageToken(x.NextPageToken) + } +} + +// method id "dns.managedZoneOperations.get": + +type ManagedZoneOperationsGetCall struct { + s *Service + project string + managedZone string + operation string + urlParams_ gensupport.URLParams + ifNoneMatch_ string + ctx_ context.Context + header_ http.Header +} + +// Get: Fetch the representation of an existing Operation. +func (r *ManagedZoneOperationsService) Get(project string, managedZone string, operation string) *ManagedZoneOperationsGetCall { + c := &ManagedZoneOperationsGetCall{s: r.s, urlParams_: make(gensupport.URLParams)} + c.project = project + c.managedZone = managedZone + c.operation = operation + return c +} + +// ClientOperationId sets the optional parameter "clientOperationId": +// For mutating operation requests only. An optional identifier +// specified by the client. Must be unique for operation resources in +// the Operations collection. +func (c *ManagedZoneOperationsGetCall) ClientOperationId(clientOperationId string) *ManagedZoneOperationsGetCall { + c.urlParams_.Set("clientOperationId", clientOperationId) + return c +} + +// Fields allows partial responses to be retrieved. See +// https://developers.google.com/gdata/docs/2.0/basics#PartialResponse +// for more information. +func (c *ManagedZoneOperationsGetCall) Fields(s ...googleapi.Field) *ManagedZoneOperationsGetCall { + c.urlParams_.Set("fields", googleapi.CombineFields(s)) + return c +} + +// IfNoneMatch sets the optional parameter which makes the operation +// fail if the object's ETag matches the given value. This is useful for +// getting updates only after the object has changed since the last +// request. Use googleapi.IsNotModified to check whether the response +// error from Do is the result of In-None-Match. +func (c *ManagedZoneOperationsGetCall) IfNoneMatch(entityTag string) *ManagedZoneOperationsGetCall { + c.ifNoneMatch_ = entityTag + return c +} + +// Context sets the context to be used in this call's Do method. Any +// pending HTTP request will be aborted if the provided context is +// canceled. +func (c *ManagedZoneOperationsGetCall) Context(ctx context.Context) *ManagedZoneOperationsGetCall { + c.ctx_ = ctx + return c +} + +// Header returns an http.Header that can be modified by the caller to +// add HTTP headers to the request. +func (c *ManagedZoneOperationsGetCall) Header() http.Header { + if c.header_ == nil { + c.header_ = make(http.Header) + } + return c.header_ +} + +func (c *ManagedZoneOperationsGetCall) doRequest(alt string) (*http.Response, error) { + reqHeaders := make(http.Header) + for k, v := range c.header_ { + reqHeaders[k] = v + } + reqHeaders.Set("User-Agent", c.s.userAgent()) + if c.ifNoneMatch_ != "" { + reqHeaders.Set("If-None-Match", c.ifNoneMatch_) + } + var body io.Reader = nil + c.urlParams_.Set("alt", alt) + urls := googleapi.ResolveRelative(c.s.BasePath, "{project}/managedZones/{managedZone}/operations/{operation}") + urls += "?" + c.urlParams_.Encode() + req, _ := http.NewRequest("GET", urls, body) + req.Header = reqHeaders + googleapi.Expand(req.URL, map[string]string{ + "project": c.project, + "managedZone": c.managedZone, + "operation": c.operation, + }) + return gensupport.SendRequest(c.ctx_, c.s.client, req) +} + +// Do executes the "dns.managedZoneOperations.get" call. +// Exactly one of *Operation or error will be non-nil. Any non-2xx +// status code is an error. Response headers are in either +// *Operation.ServerResponse.Header or (if a response was returned at +// all) in error.(*googleapi.Error).Header. Use googleapi.IsNotModified +// to check whether the returned error was because +// http.StatusNotModified was returned. +func (c *ManagedZoneOperationsGetCall) Do(opts ...googleapi.CallOption) (*Operation, error) { + gensupport.SetOptions(c.urlParams_, opts...) + res, err := c.doRequest("json") + if res != nil && res.StatusCode == http.StatusNotModified { + if res.Body != nil { + res.Body.Close() + } + return nil, &googleapi.Error{ + Code: res.StatusCode, + Header: res.Header, + } + } + if err != nil { + return nil, err + } + defer googleapi.CloseBody(res) + if err := googleapi.CheckResponse(res); err != nil { + return nil, err + } + ret := &Operation{ + ServerResponse: googleapi.ServerResponse{ + Header: res.Header, + HTTPStatusCode: res.StatusCode, + }, + } + target := &ret + if err := gensupport.DecodeResponse(target, res); err != nil { + return nil, err + } + return ret, nil + // { + // "description": "Fetch the representation of an existing Operation.", + // "httpMethod": "GET", + // "id": "dns.managedZoneOperations.get", + // "parameterOrder": [ + // "project", + // "managedZone", + // "operation" + // ], + // "parameters": { + // "clientOperationId": { + // "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", + // "location": "query", + // "type": "string" + // }, + // "managedZone": { + // "description": "Identifies the managed zone addressed by this request.", + // "location": "path", + // "required": true, + // "type": "string" + // }, + // "operation": { + // "description": "Identifies the operation addressed by this request.", + // "location": "path", + // "required": true, + // "type": "string" + // }, + // "project": { + // "description": "Identifies the project addressed by this request.", + // "location": "path", + // "required": true, + // "type": "string" + // } + // }, + // "path": "{project}/managedZones/{managedZone}/operations/{operation}", + // "response": { + // "$ref": "Operation" + // }, + // "scopes": [ + // "https://www.googleapis.com/auth/cloud-platform", + // "https://www.googleapis.com/auth/cloud-platform.read-only", + // "https://www.googleapis.com/auth/ndev.clouddns.readonly", + // "https://www.googleapis.com/auth/ndev.clouddns.readwrite" + // ] + // } + +} + +// method id "dns.managedZoneOperations.list": + +type ManagedZoneOperationsListCall struct { + s *Service + project string + managedZone string + urlParams_ gensupport.URLParams + ifNoneMatch_ string + ctx_ context.Context + header_ http.Header +} + +// List: Enumerate Operations for the given ManagedZone. +func (r *ManagedZoneOperationsService) List(project string, managedZone string) *ManagedZoneOperationsListCall { + c := &ManagedZoneOperationsListCall{s: r.s, urlParams_: make(gensupport.URLParams)} + c.project = project + c.managedZone = managedZone + return c +} + +// MaxResults sets the optional parameter "maxResults": Maximum number +// of results to be returned. If unspecified, the server will decide how +// many results to return. +func (c *ManagedZoneOperationsListCall) MaxResults(maxResults int64) *ManagedZoneOperationsListCall { + c.urlParams_.Set("maxResults", fmt.Sprint(maxResults)) + return c +} + +// PageToken sets the optional parameter "pageToken": A tag returned by +// a previous list request that was truncated. Use this parameter to +// continue a previous list request. +func (c *ManagedZoneOperationsListCall) PageToken(pageToken string) *ManagedZoneOperationsListCall { + c.urlParams_.Set("pageToken", pageToken) + return c +} + +// SortBy sets the optional parameter "sortBy": Sorting criterion. The +// only supported values are START_TIME and ID. +// +// Possible values: +// "id" +// "startTime" (default) +func (c *ManagedZoneOperationsListCall) SortBy(sortBy string) *ManagedZoneOperationsListCall { + c.urlParams_.Set("sortBy", sortBy) + return c +} + +// Fields allows partial responses to be retrieved. See +// https://developers.google.com/gdata/docs/2.0/basics#PartialResponse +// for more information. +func (c *ManagedZoneOperationsListCall) Fields(s ...googleapi.Field) *ManagedZoneOperationsListCall { + c.urlParams_.Set("fields", googleapi.CombineFields(s)) + return c +} + +// IfNoneMatch sets the optional parameter which makes the operation +// fail if the object's ETag matches the given value. This is useful for +// getting updates only after the object has changed since the last +// request. Use googleapi.IsNotModified to check whether the response +// error from Do is the result of In-None-Match. +func (c *ManagedZoneOperationsListCall) IfNoneMatch(entityTag string) *ManagedZoneOperationsListCall { + c.ifNoneMatch_ = entityTag + return c +} + +// Context sets the context to be used in this call's Do method. Any +// pending HTTP request will be aborted if the provided context is +// canceled. +func (c *ManagedZoneOperationsListCall) Context(ctx context.Context) *ManagedZoneOperationsListCall { + c.ctx_ = ctx + return c +} + +// Header returns an http.Header that can be modified by the caller to +// add HTTP headers to the request. +func (c *ManagedZoneOperationsListCall) Header() http.Header { + if c.header_ == nil { + c.header_ = make(http.Header) + } + return c.header_ +} + +func (c *ManagedZoneOperationsListCall) doRequest(alt string) (*http.Response, error) { + reqHeaders := make(http.Header) + for k, v := range c.header_ { + reqHeaders[k] = v + } + reqHeaders.Set("User-Agent", c.s.userAgent()) + if c.ifNoneMatch_ != "" { + reqHeaders.Set("If-None-Match", c.ifNoneMatch_) + } + var body io.Reader = nil + c.urlParams_.Set("alt", alt) + urls := googleapi.ResolveRelative(c.s.BasePath, "{project}/managedZones/{managedZone}/operations") + urls += "?" + c.urlParams_.Encode() + req, _ := http.NewRequest("GET", urls, body) + req.Header = reqHeaders + googleapi.Expand(req.URL, map[string]string{ + "project": c.project, + "managedZone": c.managedZone, + }) + return gensupport.SendRequest(c.ctx_, c.s.client, req) +} + +// Do executes the "dns.managedZoneOperations.list" call. +// Exactly one of *ManagedZoneOperationsListResponse or error will be +// non-nil. Any non-2xx status code is an error. Response headers are in +// either *ManagedZoneOperationsListResponse.ServerResponse.Header or +// (if a response was returned at all) in +// error.(*googleapi.Error).Header. Use googleapi.IsNotModified to check +// whether the returned error was because http.StatusNotModified was +// returned. +func (c *ManagedZoneOperationsListCall) Do(opts ...googleapi.CallOption) (*ManagedZoneOperationsListResponse, error) { + gensupport.SetOptions(c.urlParams_, opts...) + res, err := c.doRequest("json") + if res != nil && res.StatusCode == http.StatusNotModified { + if res.Body != nil { + res.Body.Close() + } + return nil, &googleapi.Error{ + Code: res.StatusCode, + Header: res.Header, + } + } + if err != nil { + return nil, err + } + defer googleapi.CloseBody(res) + if err := googleapi.CheckResponse(res); err != nil { + return nil, err + } + ret := &ManagedZoneOperationsListResponse{ + ServerResponse: googleapi.ServerResponse{ + Header: res.Header, + HTTPStatusCode: res.StatusCode, + }, + } + target := &ret + if err := gensupport.DecodeResponse(target, res); err != nil { + return nil, err + } + return ret, nil + // { + // "description": "Enumerate Operations for the given ManagedZone.", + // "httpMethod": "GET", + // "id": "dns.managedZoneOperations.list", + // "parameterOrder": [ + // "project", + // "managedZone" + // ], + // "parameters": { + // "managedZone": { + // "description": "Identifies the managed zone addressed by this request.", + // "location": "path", + // "required": true, + // "type": "string" + // }, + // "maxResults": { + // "description": "Optional. Maximum number of results to be returned. If unspecified, the server will decide how many results to return.", + // "format": "int32", + // "location": "query", + // "type": "integer" + // }, + // "pageToken": { + // "description": "Optional. A tag returned by a previous list request that was truncated. Use this parameter to continue a previous list request.", + // "location": "query", + // "type": "string" + // }, + // "project": { + // "description": "Identifies the project addressed by this request.", + // "location": "path", + // "required": true, + // "type": "string" + // }, + // "sortBy": { + // "default": "startTime", + // "description": "Sorting criterion. The only supported values are START_TIME and ID.", + // "enum": [ + // "id", + // "startTime" + // ], + // "enumDescriptions": [ + // "", + // "" + // ], + // "location": "query", + // "type": "string" + // } + // }, + // "path": "{project}/managedZones/{managedZone}/operations", + // "response": { + // "$ref": "ManagedZoneOperationsListResponse" + // }, + // "scopes": [ + // "https://www.googleapis.com/auth/cloud-platform", + // "https://www.googleapis.com/auth/cloud-platform.read-only", + // "https://www.googleapis.com/auth/ndev.clouddns.readonly", + // "https://www.googleapis.com/auth/ndev.clouddns.readwrite" + // ] + // } + +} + +// Pages invokes f for each page of results. +// A non-nil error returned from f will halt the iteration. +// The provided context supersedes any context provided to the Context method. +func (c *ManagedZoneOperationsListCall) Pages(ctx context.Context, f func(*ManagedZoneOperationsListResponse) error) error { + c.ctx_ = ctx + defer c.PageToken(c.urlParams_.Get("pageToken")) // reset paging to original point + for { + x, err := c.Do() + if err != nil { + return err + } + if err := f(x); err != nil { + return err + } + if x.NextPageToken == "" { + return nil + } + c.PageToken(x.NextPageToken) + } +} + // method id "dns.managedZones.create": type ManagedZonesCreateCall struct { @@ -1091,6 +2473,15 @@ func (r *ManagedZonesService) Create(project string, managedzone *ManagedZone) * return c } +// ClientOperationId sets the optional parameter "clientOperationId": +// For mutating operation requests only. An optional identifier +// specified by the client. Must be unique for operation resources in +// the Operations collection. +func (c *ManagedZonesCreateCall) ClientOperationId(clientOperationId string) *ManagedZonesCreateCall { + c.urlParams_.Set("clientOperationId", clientOperationId) + return c +} + // Fields allows partial responses to be retrieved. See // https://developers.google.com/gdata/docs/2.0/basics#PartialResponse // for more information. @@ -1172,7 +2563,7 @@ func (c *ManagedZonesCreateCall) Do(opts ...googleapi.CallOption) (*ManagedZone, }, } target := &ret - if err := json.NewDecoder(res.Body).Decode(target); err != nil { + if err := gensupport.DecodeResponse(target, res); err != nil { return nil, err } return ret, nil @@ -1184,6 +2575,11 @@ func (c *ManagedZonesCreateCall) Do(opts ...googleapi.CallOption) (*ManagedZone, // "project" // ], // "parameters": { + // "clientOperationId": { + // "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", + // "location": "query", + // "type": "string" + // }, // "project": { // "description": "Identifies the project addressed by this request.", // "location": "path", @@ -1225,6 +2621,15 @@ func (r *ManagedZonesService) Delete(project string, managedZone string) *Manage return c } +// ClientOperationId sets the optional parameter "clientOperationId": +// For mutating operation requests only. An optional identifier +// specified by the client. Must be unique for operation resources in +// the Operations collection. +func (c *ManagedZonesDeleteCall) ClientOperationId(clientOperationId string) *ManagedZonesDeleteCall { + c.urlParams_.Set("clientOperationId", clientOperationId) + return c +} + // Fields allows partial responses to be retrieved. See // https://developers.google.com/gdata/docs/2.0/basics#PartialResponse // for more information. @@ -1290,6 +2695,11 @@ func (c *ManagedZonesDeleteCall) Do(opts ...googleapi.CallOption) error { // "managedZone" // ], // "parameters": { + // "clientOperationId": { + // "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", + // "location": "query", + // "type": "string" + // }, // "managedZone": { // "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", // "location": "path", @@ -1332,6 +2742,15 @@ func (r *ManagedZonesService) Get(project string, managedZone string) *ManagedZo return c } +// ClientOperationId sets the optional parameter "clientOperationId": +// For mutating operation requests only. An optional identifier +// specified by the client. Must be unique for operation resources in +// the Operations collection. +func (c *ManagedZonesGetCall) ClientOperationId(clientOperationId string) *ManagedZonesGetCall { + c.urlParams_.Set("clientOperationId", clientOperationId) + return c +} + // Fields allows partial responses to be retrieved. See // https://developers.google.com/gdata/docs/2.0/basics#PartialResponse // for more information. @@ -1422,7 +2841,7 @@ func (c *ManagedZonesGetCall) Do(opts ...googleapi.CallOption) (*ManagedZone, er }, } target := &ret - if err := json.NewDecoder(res.Body).Decode(target); err != nil { + if err := gensupport.DecodeResponse(target, res); err != nil { return nil, err } return ret, nil @@ -1435,6 +2854,11 @@ func (c *ManagedZonesGetCall) Do(opts ...googleapi.CallOption) (*ManagedZone, er // "managedZone" // ], // "parameters": { + // "clientOperationId": { + // "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", + // "location": "query", + // "type": "string" + // }, // "managedZone": { // "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", // "location": "path", @@ -1593,7 +3017,7 @@ func (c *ManagedZonesListCall) Do(opts ...googleapi.CallOption) (*ManagedZonesLi }, } target := &ret - if err := json.NewDecoder(res.Body).Decode(target); err != nil { + if err := gensupport.DecodeResponse(target, res); err != nil { return nil, err } return ret, nil @@ -1663,6 +3087,322 @@ func (c *ManagedZonesListCall) Pages(ctx context.Context, f func(*ManagedZonesLi } } +// method id "dns.managedZones.patch": + +type ManagedZonesPatchCall struct { + s *Service + project string + managedZone string + managedzone *ManagedZone + urlParams_ gensupport.URLParams + ctx_ context.Context + header_ http.Header +} + +// Patch: Apply a partial update to an existing ManagedZone. +func (r *ManagedZonesService) Patch(project string, managedZone string, managedzone *ManagedZone) *ManagedZonesPatchCall { + c := &ManagedZonesPatchCall{s: r.s, urlParams_: make(gensupport.URLParams)} + c.project = project + c.managedZone = managedZone + c.managedzone = managedzone + return c +} + +// ClientOperationId sets the optional parameter "clientOperationId": +// For mutating operation requests only. An optional identifier +// specified by the client. Must be unique for operation resources in +// the Operations collection. +func (c *ManagedZonesPatchCall) ClientOperationId(clientOperationId string) *ManagedZonesPatchCall { + c.urlParams_.Set("clientOperationId", clientOperationId) + return c +} + +// Fields allows partial responses to be retrieved. See +// https://developers.google.com/gdata/docs/2.0/basics#PartialResponse +// for more information. +func (c *ManagedZonesPatchCall) Fields(s ...googleapi.Field) *ManagedZonesPatchCall { + c.urlParams_.Set("fields", googleapi.CombineFields(s)) + return c +} + +// Context sets the context to be used in this call's Do method. Any +// pending HTTP request will be aborted if the provided context is +// canceled. +func (c *ManagedZonesPatchCall) Context(ctx context.Context) *ManagedZonesPatchCall { + c.ctx_ = ctx + return c +} + +// Header returns an http.Header that can be modified by the caller to +// add HTTP headers to the request. +func (c *ManagedZonesPatchCall) Header() http.Header { + if c.header_ == nil { + c.header_ = make(http.Header) + } + return c.header_ +} + +func (c *ManagedZonesPatchCall) doRequest(alt string) (*http.Response, error) { + reqHeaders := make(http.Header) + for k, v := range c.header_ { + reqHeaders[k] = v + } + reqHeaders.Set("User-Agent", c.s.userAgent()) + var body io.Reader = nil + body, err := googleapi.WithoutDataWrapper.JSONReader(c.managedzone) + if err != nil { + return nil, err + } + reqHeaders.Set("Content-Type", "application/json") + c.urlParams_.Set("alt", alt) + urls := googleapi.ResolveRelative(c.s.BasePath, "{project}/managedZones/{managedZone}") + urls += "?" + c.urlParams_.Encode() + req, _ := http.NewRequest("PATCH", urls, body) + req.Header = reqHeaders + googleapi.Expand(req.URL, map[string]string{ + "project": c.project, + "managedZone": c.managedZone, + }) + return gensupport.SendRequest(c.ctx_, c.s.client, req) +} + +// Do executes the "dns.managedZones.patch" call. +// Exactly one of *Operation or error will be non-nil. Any non-2xx +// status code is an error. Response headers are in either +// *Operation.ServerResponse.Header or (if a response was returned at +// all) in error.(*googleapi.Error).Header. Use googleapi.IsNotModified +// to check whether the returned error was because +// http.StatusNotModified was returned. +func (c *ManagedZonesPatchCall) Do(opts ...googleapi.CallOption) (*Operation, error) { + gensupport.SetOptions(c.urlParams_, opts...) + res, err := c.doRequest("json") + if res != nil && res.StatusCode == http.StatusNotModified { + if res.Body != nil { + res.Body.Close() + } + return nil, &googleapi.Error{ + Code: res.StatusCode, + Header: res.Header, + } + } + if err != nil { + return nil, err + } + defer googleapi.CloseBody(res) + if err := googleapi.CheckResponse(res); err != nil { + return nil, err + } + ret := &Operation{ + ServerResponse: googleapi.ServerResponse{ + Header: res.Header, + HTTPStatusCode: res.StatusCode, + }, + } + target := &ret + if err := gensupport.DecodeResponse(target, res); err != nil { + return nil, err + } + return ret, nil + // { + // "description": "Apply a partial update to an existing ManagedZone.", + // "httpMethod": "PATCH", + // "id": "dns.managedZones.patch", + // "parameterOrder": [ + // "project", + // "managedZone" + // ], + // "parameters": { + // "clientOperationId": { + // "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", + // "location": "query", + // "type": "string" + // }, + // "managedZone": { + // "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", + // "location": "path", + // "required": true, + // "type": "string" + // }, + // "project": { + // "description": "Identifies the project addressed by this request.", + // "location": "path", + // "required": true, + // "type": "string" + // } + // }, + // "path": "{project}/managedZones/{managedZone}", + // "request": { + // "$ref": "ManagedZone" + // }, + // "response": { + // "$ref": "Operation" + // }, + // "scopes": [ + // "https://www.googleapis.com/auth/cloud-platform", + // "https://www.googleapis.com/auth/ndev.clouddns.readwrite" + // ] + // } + +} + +// method id "dns.managedZones.update": + +type ManagedZonesUpdateCall struct { + s *Service + project string + managedZone string + managedzone *ManagedZone + urlParams_ gensupport.URLParams + ctx_ context.Context + header_ http.Header +} + +// Update: Update an existing ManagedZone. +func (r *ManagedZonesService) Update(project string, managedZone string, managedzone *ManagedZone) *ManagedZonesUpdateCall { + c := &ManagedZonesUpdateCall{s: r.s, urlParams_: make(gensupport.URLParams)} + c.project = project + c.managedZone = managedZone + c.managedzone = managedzone + return c +} + +// ClientOperationId sets the optional parameter "clientOperationId": +// For mutating operation requests only. An optional identifier +// specified by the client. Must be unique for operation resources in +// the Operations collection. +func (c *ManagedZonesUpdateCall) ClientOperationId(clientOperationId string) *ManagedZonesUpdateCall { + c.urlParams_.Set("clientOperationId", clientOperationId) + return c +} + +// Fields allows partial responses to be retrieved. See +// https://developers.google.com/gdata/docs/2.0/basics#PartialResponse +// for more information. +func (c *ManagedZonesUpdateCall) Fields(s ...googleapi.Field) *ManagedZonesUpdateCall { + c.urlParams_.Set("fields", googleapi.CombineFields(s)) + return c +} + +// Context sets the context to be used in this call's Do method. Any +// pending HTTP request will be aborted if the provided context is +// canceled. +func (c *ManagedZonesUpdateCall) Context(ctx context.Context) *ManagedZonesUpdateCall { + c.ctx_ = ctx + return c +} + +// Header returns an http.Header that can be modified by the caller to +// add HTTP headers to the request. +func (c *ManagedZonesUpdateCall) Header() http.Header { + if c.header_ == nil { + c.header_ = make(http.Header) + } + return c.header_ +} + +func (c *ManagedZonesUpdateCall) doRequest(alt string) (*http.Response, error) { + reqHeaders := make(http.Header) + for k, v := range c.header_ { + reqHeaders[k] = v + } + reqHeaders.Set("User-Agent", c.s.userAgent()) + var body io.Reader = nil + body, err := googleapi.WithoutDataWrapper.JSONReader(c.managedzone) + if err != nil { + return nil, err + } + reqHeaders.Set("Content-Type", "application/json") + c.urlParams_.Set("alt", alt) + urls := googleapi.ResolveRelative(c.s.BasePath, "{project}/managedZones/{managedZone}") + urls += "?" + c.urlParams_.Encode() + req, _ := http.NewRequest("PUT", urls, body) + req.Header = reqHeaders + googleapi.Expand(req.URL, map[string]string{ + "project": c.project, + "managedZone": c.managedZone, + }) + return gensupport.SendRequest(c.ctx_, c.s.client, req) +} + +// Do executes the "dns.managedZones.update" call. +// Exactly one of *Operation or error will be non-nil. Any non-2xx +// status code is an error. Response headers are in either +// *Operation.ServerResponse.Header or (if a response was returned at +// all) in error.(*googleapi.Error).Header. Use googleapi.IsNotModified +// to check whether the returned error was because +// http.StatusNotModified was returned. +func (c *ManagedZonesUpdateCall) Do(opts ...googleapi.CallOption) (*Operation, error) { + gensupport.SetOptions(c.urlParams_, opts...) + res, err := c.doRequest("json") + if res != nil && res.StatusCode == http.StatusNotModified { + if res.Body != nil { + res.Body.Close() + } + return nil, &googleapi.Error{ + Code: res.StatusCode, + Header: res.Header, + } + } + if err != nil { + return nil, err + } + defer googleapi.CloseBody(res) + if err := googleapi.CheckResponse(res); err != nil { + return nil, err + } + ret := &Operation{ + ServerResponse: googleapi.ServerResponse{ + Header: res.Header, + HTTPStatusCode: res.StatusCode, + }, + } + target := &ret + if err := gensupport.DecodeResponse(target, res); err != nil { + return nil, err + } + return ret, nil + // { + // "description": "Update an existing ManagedZone.", + // "httpMethod": "PUT", + // "id": "dns.managedZones.update", + // "parameterOrder": [ + // "project", + // "managedZone" + // ], + // "parameters": { + // "clientOperationId": { + // "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", + // "location": "query", + // "type": "string" + // }, + // "managedZone": { + // "description": "Identifies the managed zone addressed by this request. Can be the managed zone name or id.", + // "location": "path", + // "required": true, + // "type": "string" + // }, + // "project": { + // "description": "Identifies the project addressed by this request.", + // "location": "path", + // "required": true, + // "type": "string" + // } + // }, + // "path": "{project}/managedZones/{managedZone}", + // "request": { + // "$ref": "ManagedZone" + // }, + // "response": { + // "$ref": "Operation" + // }, + // "scopes": [ + // "https://www.googleapis.com/auth/cloud-platform", + // "https://www.googleapis.com/auth/ndev.clouddns.readwrite" + // ] + // } + +} + // method id "dns.projects.get": type ProjectsGetCall struct { @@ -1681,6 +3421,15 @@ func (r *ProjectsService) Get(project string) *ProjectsGetCall { return c } +// ClientOperationId sets the optional parameter "clientOperationId": +// For mutating operation requests only. An optional identifier +// specified by the client. Must be unique for operation resources in +// the Operations collection. +func (c *ProjectsGetCall) ClientOperationId(clientOperationId string) *ProjectsGetCall { + c.urlParams_.Set("clientOperationId", clientOperationId) + return c +} + // Fields allows partial responses to be retrieved. See // https://developers.google.com/gdata/docs/2.0/basics#PartialResponse // for more information. @@ -1770,7 +3519,7 @@ func (c *ProjectsGetCall) Do(opts ...googleapi.CallOption) (*Project, error) { }, } target := &ret - if err := json.NewDecoder(res.Body).Decode(target); err != nil { + if err := gensupport.DecodeResponse(target, res); err != nil { return nil, err } return ret, nil @@ -1782,6 +3531,11 @@ func (c *ProjectsGetCall) Do(opts ...googleapi.CallOption) (*Project, error) { // "project" // ], // "parameters": { + // "clientOperationId": { + // "description": "For mutating operation requests only. An optional identifier specified by the client. Must be unique for operation resources in the Operations collection.", + // "location": "query", + // "type": "string" + // }, // "project": { // "description": "Identifies the project addressed by this request.", // "location": "path", @@ -1945,7 +3699,7 @@ func (c *ResourceRecordSetsListCall) Do(opts ...googleapi.CallOption) (*Resource }, } target := &ret - if err := json.NewDecoder(res.Body).Decode(target); err != nil { + if err := gensupport.DecodeResponse(target, res); err != nil { return nil, err } return ret, nil diff --git a/vendor/google.golang.org/api/gensupport/go18.go b/vendor/google.golang.org/api/gensupport/go18.go new file mode 100644 index 000000000..c76cb8f20 --- /dev/null +++ b/vendor/google.golang.org/api/gensupport/go18.go @@ -0,0 +1,17 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.8 + +package gensupport + +import ( + "io" + "net/http" +) + +// SetGetBody sets the GetBody field of req to f. +func SetGetBody(req *http.Request, f func() (io.ReadCloser, error)) { + req.GetBody = f +} diff --git a/vendor/google.golang.org/api/gensupport/media.go b/vendor/google.golang.org/api/gensupport/media.go index f3e77fc52..5895fef88 100644 --- a/vendor/google.golang.org/api/gensupport/media.go +++ b/vendor/google.golang.org/api/gensupport/media.go @@ -5,12 +5,15 @@ package gensupport import ( + "bytes" "fmt" "io" "io/ioutil" "mime/multipart" "net/http" "net/textproto" + "strings" + "sync" "google.golang.org/api/googleapi" ) @@ -103,12 +106,13 @@ type typeReader struct { typ string } -// multipartReader combines the contents of multiple readers to creat a multipart/related HTTP body. +// multipartReader combines the contents of multiple readers to create a multipart/related HTTP body. // Close must be called if reads from the multipartReader are abandoned before reaching EOF. type multipartReader struct { pr *io.PipeReader - pipeOpen bool ctype string + mu sync.Mutex + pipeOpen bool } func newMultipartReader(parts []typeReader) *multipartReader { @@ -144,10 +148,13 @@ func (mp *multipartReader) Read(data []byte) (n int, err error) { } func (mp *multipartReader) Close() error { + mp.mu.Lock() if !mp.pipeOpen { + mp.mu.Unlock() return nil } mp.pipeOpen = false + mp.mu.Unlock() return mp.pr.Close() } @@ -251,11 +258,11 @@ func (mi *MediaInfo) UploadType() string { } // UploadRequest sets up an HTTP request for media upload. It adds headers -// as necessary, and returns a replacement for the body. -func (mi *MediaInfo) UploadRequest(reqHeaders http.Header, body io.Reader) (newBody io.Reader, cleanup func()) { +// as necessary, and returns a replacement for the body and a function for http.Request.GetBody. +func (mi *MediaInfo) UploadRequest(reqHeaders http.Header, body io.Reader) (newBody io.Reader, getBody func() (io.ReadCloser, error), cleanup func()) { cleanup = func() {} if mi == nil { - return body, cleanup + return body, nil, cleanup } var media io.Reader if mi.media != nil { @@ -269,7 +276,17 @@ func (mi *MediaInfo) UploadRequest(reqHeaders http.Header, body io.Reader) (newB media, _, _, _ = mi.buffer.Chunk() } if media != nil { + fb := readerFunc(body) + fm := readerFunc(media) combined, ctype := CombineBodyMedia(body, "application/json", media, mi.mType) + if fb != nil && fm != nil { + getBody = func() (io.ReadCloser, error) { + rb := ioutil.NopCloser(fb()) + rm := ioutil.NopCloser(fm()) + r, _ := CombineBodyMedia(rb, "application/json", rm, mi.mType) + return r, nil + } + } cleanup = func() { combined.Close() } reqHeaders.Set("Content-Type", ctype) body = combined @@ -277,7 +294,27 @@ func (mi *MediaInfo) UploadRequest(reqHeaders http.Header, body io.Reader) (newB if mi.buffer != nil && mi.mType != "" && !mi.singleChunk { reqHeaders.Set("X-Upload-Content-Type", mi.mType) } - return body, cleanup + return body, getBody, cleanup +} + +// readerFunc returns a function that always returns an io.Reader that has the same +// contents as r, provided that can be done without consuming r. Otherwise, it +// returns nil. +// See http.NewRequest (in net/http/request.go). +func readerFunc(r io.Reader) func() io.Reader { + switch r := r.(type) { + case *bytes.Buffer: + buf := r.Bytes() + return func() io.Reader { return bytes.NewReader(buf) } + case *bytes.Reader: + snapshot := *r + return func() io.Reader { r := snapshot; return &r } + case *strings.Reader: + snapshot := *r + return func() io.Reader { r := snapshot; return &r } + default: + return nil + } } // ResumableUpload returns an appropriately configured ResumableUpload value if the diff --git a/vendor/google.golang.org/api/gensupport/not_go18.go b/vendor/google.golang.org/api/gensupport/not_go18.go new file mode 100644 index 000000000..2536501ce --- /dev/null +++ b/vendor/google.golang.org/api/gensupport/not_go18.go @@ -0,0 +1,14 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !go1.8 + +package gensupport + +import ( + "io" + "net/http" +) + +func SetGetBody(req *http.Request, f func() (io.ReadCloser, error)) {} diff --git a/vendor/google.golang.org/api/gensupport/send.go b/vendor/google.golang.org/api/gensupport/send.go index 092044f44..0f75aa867 100644 --- a/vendor/google.golang.org/api/gensupport/send.go +++ b/vendor/google.golang.org/api/gensupport/send.go @@ -5,6 +5,7 @@ package gensupport import ( + "encoding/json" "errors" "net/http" @@ -59,3 +60,12 @@ func SendRequest(ctx context.Context, client *http.Client, req *http.Request) (* } return resp, err } + +// DecodeResponse decodes the body of res into target. If there is no body, +// target is unchanged. +func DecodeResponse(target interface{}, res *http.Response) error { + if res.StatusCode == http.StatusNoContent { + return nil + } + return json.NewDecoder(res.Body).Decode(target) +} diff --git a/vendor/gopkg.in/ns1/ns1-go.v2/rest/model/data/meta.go b/vendor/gopkg.in/ns1/ns1-go.v2/rest/model/data/meta.go index 8638c1c09..6b10c12ee 100644 --- a/vendor/gopkg.in/ns1/ns1-go.v2/rest/model/data/meta.go +++ b/vendor/gopkg.in/ns1/ns1-go.v2/rest/model/data/meta.go @@ -1,12 +1,23 @@ package data +import ( + "bytes" + "encoding/json" + "fmt" + "net" + "reflect" + "sort" + "strconv" + "strings" +) + // FeedPtr represents the dynamic metadata value in which a feed is providing the value. type FeedPtr struct { FeedID string `json:"feed,omitempty"` } -// Meta contains information on an entities metadata table. Metadata key/value -// pairs are used by a records' filter pipeline during a dns query. +// Meta contains information on an entity's metadata table. Metadata key/value +// pairs are used by a record's filter pipeline during a dns query. // All values can be a feed id as well, indicating real-time updates of these values. // Structure/Precendence of metadata tables: // - Record @@ -125,3 +136,357 @@ type Meta struct { // int or FeedPtr. HighWatermark interface{} `json:"high_watermark,omitempty"` } + +// StringMap returns a map[string]interface{} representation of metadata (for use with terraform in nested structures) +func (meta *Meta) StringMap() map[string]interface{} { + m := make(map[string]interface{}) + v := reflect.Indirect(reflect.ValueOf(meta)) + t := v.Type() + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + fv := v.Field(i) + if fv.IsNil() { + continue + } + tag := f.Tag.Get("json") + + tag = strings.Split(tag, ",")[0] + + m[tag] = FormatInterface(fv.Interface()) + } + return m +} + +// FormatInterface takes an interface of types: string, bool, int, float64, []string, and FeedPtr, and returns a string representation of said interface +func FormatInterface(i interface{}) string { + switch v := i.(type) { + case string: + return v + case bool: + if v { + return "1" + } + return "0" + case int: + return strconv.FormatInt(int64(v), 10) + case float64: + return strconv.FormatFloat(v, 'f', -1, 64) + case []string: + return strings.Join(v, ",") + case []interface{}: + slc := make([]string, 0) + for _, s := range v { + slc = append(slc, s.(string)) + } + return strings.Join(slc, ",") + case FeedPtr: + data, _ := json.Marshal(v) + return string(data) + default: + panic(fmt.Sprintf("expected v to be convertible to a string, got: %+v, %T", v, v)) + } +} + +// ParseType returns an interface containing a string, bool, int, float64, []string, or FeedPtr +// float64 values with no decimal may be returned as integers, but that should be ok because the api won't know the difference +// when it's json encoded +func ParseType(s string) interface{} { + slc := strings.Split(s, ",") + if len(slc) > 1 { + sort.Strings(slc) + return slc + } + + feedptr := FeedPtr{} + err := json.Unmarshal([]byte(s), &feedptr) + if err == nil { + return feedptr + } + + f, err := strconv.ParseFloat(s, 64) + if err == nil { + if !isIntegral(f) { + return f + } + return int(f) + } + + return s +} + +func isIntegral(f float64) bool { + return f == float64(int(f)) +} + +// MetaFromMap creates a *Meta and uses reflection to set fields from a map. This will panic if a value for a key is not a string. +// This it to ensure compatibility with terraform +func MetaFromMap(m map[string]interface{}) *Meta { + meta := &Meta{} + mv := reflect.Indirect(reflect.ValueOf(meta)) + mt := mv.Type() + for k, v := range m { + name := ToCamel(k) + if _, ok := mt.FieldByName(name); ok { + fv := mv.FieldByName(name) + if name == "Up" { + if v.(string) == "1" { + fv.Set(reflect.ValueOf(true)) + } else { + fv.Set(reflect.ValueOf(false)) + } + } else { + fv.Set(reflect.ValueOf(ParseType(v.(string)))) + } + } + } + return meta +} + +// metaValidation is a validation struct for a metadata field. +// It contains the kinds of types that the field can be, and a list of check functions that will run on the field +type metaValidation struct { + kinds []reflect.Kind + checkFuncs []func(v reflect.Value) error +} + +// validateLatLong makes sure that the given lat/long is within the range 180.0 to -180.0 +func validateLatLong(v reflect.Value) error { + if v.Kind() == reflect.Float64 { + f := v.Interface().(float64) + if f < -180.0 || f > 180.0 { + return fmt.Errorf("latitude/longitude values must be between -180.0 and 180.0, got %f", f) + } + } + return nil +} + +// validateCidr makes sure that the given string is a valid cidr +func validateCidr(v reflect.Value) error { + if v.Kind() == reflect.String { + s := v.Interface().(string) + _, _, err := net.ParseCIDR(s) + if err != nil { + return err + } + } + var last error + if v.Kind() == reflect.Slice { + slc := v.Interface().([]interface{}) + for _, s := range slc { + _, _, err := net.ParseCIDR(s.(string)) + last = err + } + } + return last +} + +// validatePositiveNumber makes sure that the given number (float or int) is positive +func validatePositiveNumber(fieldName string, v reflect.Value) error { + i := 0 + if v.Kind() == reflect.Int { + i = v.Interface().(int) + + } + + if v.Kind() == reflect.Float64 { + i = int(v.Interface().(float64)) + } + + if i < 0 { + return fmt.Errorf("%s must be a positive number, was %+v", fieldName, v.Interface()) + } + + return nil +} + +// geoMap is a map of all of the georegions +var geoMap = map[string]struct{}{ + "US-EAST": {}, "US-CENTRAL": {}, "US-WEST": {}, + "EUROPE": {}, "ASIAPAC": {}, "SOUTH-AMERICA": {}, "AFRICA": {}, +} + +// geoKeyString returns a string representation of all of the georegions +func geoKeyString() string { + length := 0 + slc := make([]string, 0) + for k := range geoMap { + slc = append(slc, k) + length += len(k) + 1 + } + sort.Strings(slc) + + b := bytes.NewBuffer(make([]byte, 0, length-1)) + + for _, k := range slc { + b.WriteString(k + ",") + } + + return strings.TrimRight(b.String(), ",") +} + +// validateGeoregion makes sure that the given georegion is correct +func validateGeoregion(v reflect.Value) error { + if v.Kind() == reflect.String { + s := v.String() + if _, ok := geoMap[s]; !ok { + return fmt.Errorf("georegion must be one or more of %s, found %s", geoKeyString(), s) + } + } + + if v.Kind() == reflect.Slice { + if slc, ok := v.Interface().([]string); ok { + for _, s := range slc { + if _, ok := geoMap[s]; !ok { + return fmt.Errorf("georegion must be one or more of %s, found %s", geoKeyString(), s) + } + } + return nil + } + slc := v.Interface().([]interface{}) + for _, s := range slc { + if _, ok := geoMap[s.(string)]; !ok { + return fmt.Errorf("georegion must be one or more of %s, found %s", geoKeyString(), s) + } + } + } + return nil +} + +// validateCountryStateProvince makes sure that the given field only has two characters +func validateCountryStateProvince(v reflect.Value) error { + if v.Kind() == reflect.String { + s := v.String() + if len(s) != 2 { + return fmt.Errorf("country/state/province codes must be 2 digits as specified in ISO3166/ISO3166-2, got: %s", s) + } + } + + if v.Kind() == reflect.Slice { + if slc, ok := v.Interface().([]string); ok { + for _, s := range slc { + if len(s) != 2 { + return fmt.Errorf("country/state/province codes must be 2 digits as specified in ISO3166/ISO3166-2, got: %s", s) + } + } + return nil + } + slc := v.Interface().([]interface{}) + for _, s := range slc { + if len(s.(string)) != 2 { + return fmt.Errorf("country/state/province codes must be 2 digits as specified in ISO3166/ISO3166-2, got: %s", s) + } + } + } + return nil +} + +// validateNoteLength validates that a note's length is less than 256 characters +func validateNoteLength(v reflect.Value) error { + if v.Kind() == reflect.String { + s := v.String() + if len(s) > 256 { + return fmt.Errorf("note length must be less than 256 characters, was %d", len(s)) + } + } + return nil +} + +// checkFuncs is shorthand for returning a slice of functions that take a reflect.Value and return an error +func checkFuncs(f ...func(v reflect.Value) error) []func(v reflect.Value) error { + return f +} + +// kinds is shorthand for returning a slice of reflect.Kind +func kinds(k ...reflect.Kind) []reflect.Kind { + return k +} + +// validationMap is a map of meta fields to validation types and functions +var validationMap = map[string]metaValidation{ + "Up": {kinds(reflect.Bool), nil}, + "Connections": {kinds(reflect.Int), checkFuncs( + func(v reflect.Value) error { + return validatePositiveNumber("Connections", v) + })}, + "Requests": {kinds(reflect.Int), checkFuncs( + func(v reflect.Value) error { + return validatePositiveNumber("Requests", v) + })}, + "LoadAvg": {kinds(reflect.Float64, reflect.Int), checkFuncs( + func(v reflect.Value) error { + return validatePositiveNumber("LoadAvg", v) + })}, + "Pulsar": {kinds(reflect.String), nil}, + "Latitude": {kinds(reflect.Float64, reflect.Int), checkFuncs(validateLatLong)}, + "Longitude": {kinds(reflect.Float64, reflect.Int), checkFuncs(validateLatLong)}, + "Georegion": {kinds(reflect.String, reflect.Slice), checkFuncs(validateGeoregion)}, + "Country": {kinds(reflect.String, reflect.Slice), checkFuncs(validateCountryStateProvince)}, + "USState": {kinds(reflect.String, reflect.Slice), checkFuncs(validateCountryStateProvince)}, + "CAProvince": {kinds(reflect.String, reflect.Slice), checkFuncs(validateCountryStateProvince)}, + "Note": {kinds(reflect.String), checkFuncs(validateNoteLength)}, + "IPPrefixes": {kinds(reflect.String, reflect.Slice), checkFuncs(validateCidr)}, + "ASN": {kinds(reflect.String, reflect.Slice), nil}, + "Priority": {kinds(reflect.Int), checkFuncs( + func(v reflect.Value) error { + return validatePositiveNumber("Priority", v) + })}, + "Weight": {kinds(reflect.Float64, reflect.Int), checkFuncs( + func(v reflect.Value) error { + return validatePositiveNumber("Weight", v) + })}, + "LowWatermark": {kinds(reflect.Int), nil}, + "HighWatermark": {kinds(reflect.Int), nil}, +} + +// validate takes a field name, a reflect value, and metaValidation and validates the given field +func validate(name string, v reflect.Value, m metaValidation) (errs []error) { + + check := true + // if this is a FeedPtr or a *FeedPtr then we're ok, skip checking the rest of the types + if v.Kind() == reflect.Struct || v.Kind() == reflect.Invalid { + check = false + } + + if check { + match := false + for _, k := range m.kinds { + if k == v.Kind() { + match = true + } + } + + if !match { + errs = append(errs, fmt.Errorf("found type mismatch for meta field '%s'. expected %+v, got: %+v", name, m.kinds, v.Kind())) + } + + for _, f := range m.checkFuncs { + err := f(v) + if err != nil { + errs = append(errs, err) + } + } + } + + if v.Kind() == reflect.Struct { + if _, ok := v.Interface().(FeedPtr); !ok { + errs = append(errs, fmt.Errorf("if a meta field is a struct, it must be a FeedPtr, got: %s", v.Type())) + } + } + + return +} + +// Validate validates metadata fields and returns a list of errors if any are found +func (meta *Meta) Validate() (errs []error) { + mv := reflect.Indirect(reflect.ValueOf(meta)) + mt := mv.Type() + for i := 0; i < mt.NumField(); i++ { + fv := mt.Field(i) + err := validate(fv.Name, mv.Field(i).Elem(), validationMap[fv.Name]) + if err != nil { + errs = append(errs, err...) + } + } + + return errs +} diff --git a/vendor/gopkg.in/ns1/ns1-go.v2/rest/model/data/string.go b/vendor/gopkg.in/ns1/ns1-go.v2/rest/model/data/string.go new file mode 100644 index 000000000..e9731c418 --- /dev/null +++ b/vendor/gopkg.in/ns1/ns1-go.v2/rest/model/data/string.go @@ -0,0 +1,48 @@ +package data + +// The following code is a modified copy of functions found in strcase, found here: https://github.com/iancoleman/strcase + +import ( + "bytes" + "regexp" + "strings" + "unicode" +) + +// ToCamel converts a string to CamelCase +func ToCamel(s string) string { + s = addWordBoundariesToNumbers(s) + s = strings.TrimSpace(s) + b := bytes.NewBuffer(make([]byte, 0)) + c := true + for _, v := range s { + if v >= 'A' && v <= 'Z' { + b.WriteString(string(v)) + } + if v >= '0' && v <= '9' { + b.WriteString(string(v)) + } + if v >= 'a' && v <= 'z' { + if c { + b.WriteString(string(unicode.ToUpper(v))) + } else { + b.WriteString(string(v)) + } + } + if v == '_' || v == ' ' || v == '-' { + c = true + } else { + c = false + } + } + return strings.TrimSpace(b.String()) +} + +var numberSequence = regexp.MustCompile(`([a-zA-Z])(\d+)([a-zA-Z]?)`) +var numberReplacement = []byte(`$1 $2 $3`) + +func addWordBoundariesToNumbers(s string) string { + b := []byte(s) + b = numberSequence.ReplaceAll(b, numberReplacement) + return string(b) +} diff --git a/vendor/gopkg.in/ns1/ns1-go.v2/rest/model/dns/zone.go b/vendor/gopkg.in/ns1/ns1-go.v2/rest/model/dns/zone.go index 18abe0d50..ac5514803 100644 --- a/vendor/gopkg.in/ns1/ns1-go.v2/rest/model/dns/zone.go +++ b/vendor/gopkg.in/ns1/ns1-go.v2/rest/model/dns/zone.go @@ -1,5 +1,6 @@ package dns +import "encoding/json" import "gopkg.in/ns1/ns1-go.v2/rest/model/data" // Zone wraps an NS1 /zone resource @@ -44,13 +45,13 @@ func (z Zone) String() string { // ZoneRecord wraps Zone's "records" attribute type ZoneRecord struct { - Domain string `json:"Domain,omitempty"` - ID string `json:"id,omitempty"` - Link string `json:"link,omitempty"` - ShortAns []string `json:"short_answers,omitempty"` - Tier int `json:"tier,omitempty"` - TTL int `json:"ttl,omitempty"` - Type string `json:"type,omitempty"` + Domain string `json:"Domain,omitempty"` + ID string `json:"id,omitempty"` + Link string `json:"link,omitempty"` + ShortAns []string `json:"short_answers,omitempty"` + Tier json.Number `json:"tier,omitempty"` + TTL int `json:"ttl,omitempty"` + Type string `json:"type,omitempty"` } // ZonePrimary wraps a Zone's "primary" attribute diff --git a/webui/src/app/components/providers/providers.component.html b/webui/src/app/components/providers/providers.component.html index bd9b3cc28..2039bc571 100644 --- a/webui/src/app/components/providers/providers.component.html +++ b/webui/src/app/components/providers/providers.component.html @@ -211,7 +211,7 @@
-
+
{{ wlRange }} diff --git a/whitelist/ip.go b/whitelist/ip.go index 33a988eba..bfb74e007 100644 --- a/whitelist/ip.go +++ b/whitelist/ip.go @@ -61,16 +61,19 @@ func (ip *IP) IsAuthorized(req *http.Request) error { xFFs := req.Header[XForwardedFor] if len(xFFs) > 0 { for _, xFF := range xFFs { - ok, err := ip.contains(parseHost(xFF)) - if err != nil { - return err - } + xffs := strings.Split(xFF, ",") + for _, xff := range xffs { + ok, err := ip.contains(parseHost(xff)) + if err != nil { + return err + } - if ok { - return nil - } + if ok { + return nil + } - invalidMatches = append(invalidMatches, xFF) + invalidMatches = append(invalidMatches, xff) + } } } } diff --git a/whitelist/ip_test.go b/whitelist/ip_test.go index f29c09335..0b0efefc0 100644 --- a/whitelist/ip_test.go +++ b/whitelist/ip_test.go @@ -27,6 +27,14 @@ func TestIsAuthorized(t *testing.T) { xForwardedForValues: []string{"1.2.3.1", "10.2.3.1"}, authorized: true, }, + { + desc: "allow UseXForwardedFor, remoteAddr not in range, UseXForwardedFor in range (compact XFF)", + whiteList: []string{"1.2.3.4/24"}, + allowXForwardedFor: true, + remoteAddr: "10.2.3.1:123", + xForwardedForValues: []string{"1.2.3.1, 10.2.3.1"}, + authorized: true, + }, { desc: "allow UseXForwardedFor, remoteAddr in range, UseXForwardedFor in range", whiteList: []string{"1.2.3.4/24"},