summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/xenolf/lego/providers
diff options
context:
space:
mode:
authorChristopher Speller <crspeller@gmail.com>2016-11-16 19:28:52 -0500
committerGitHub <noreply@github.com>2016-11-16 19:28:52 -0500
commit0135904f7d3e1c0e763adaefe267c736616e3d26 (patch)
treec27be7588f98eaea62e0bd0c0087f2b348da9738 /vendor/github.com/xenolf/lego/providers
parent0b296dd8c2aefefe89787be5cc627d44cf431150 (diff)
downloadchat-0135904f7d3e1c0e763adaefe267c736616e3d26.tar.gz
chat-0135904f7d3e1c0e763adaefe267c736616e3d26.tar.bz2
chat-0135904f7d3e1c0e763adaefe267c736616e3d26.zip
Upgrading server dependancies (#4566)
Diffstat (limited to 'vendor/github.com/xenolf/lego/providers')
-rw-r--r--vendor/github.com/xenolf/lego/providers/dns/auroradns/auroradns.go141
-rw-r--r--vendor/github.com/xenolf/lego/providers/dns/auroradns/auroradns_test.go148
-rw-r--r--vendor/github.com/xenolf/lego/providers/dns/azure/azure.go142
-rw-r--r--vendor/github.com/xenolf/lego/providers/dns/azure/azure_test.go89
-rw-r--r--vendor/github.com/xenolf/lego/providers/dns/dnspod/dnspod.go146
-rw-r--r--vendor/github.com/xenolf/lego/providers/dns/dnspod/dnspod_test.go72
-rw-r--r--vendor/github.com/xenolf/lego/providers/dns/exoscale/exoscale.go132
-rw-r--r--vendor/github.com/xenolf/lego/providers/dns/exoscale/exoscale_test.go103
-rw-r--r--vendor/github.com/xenolf/lego/providers/dns/gandi/gandi_test.go4
-rw-r--r--vendor/github.com/xenolf/lego/providers/dns/googlecloud/googlecloud.go10
-rw-r--r--vendor/github.com/xenolf/lego/providers/dns/googlecloud/googlecloud_test.go14
-rw-r--r--vendor/github.com/xenolf/lego/providers/dns/ns1/ns1.go97
-rw-r--r--vendor/github.com/xenolf/lego/providers/dns/ns1/ns1_test.go67
-rw-r--r--vendor/github.com/xenolf/lego/providers/dns/rackspace/rackspace.go284
-rw-r--r--vendor/github.com/xenolf/lego/providers/dns/rackspace/rackspace_test.go220
-rw-r--r--vendor/github.com/xenolf/lego/providers/http/memcached/README.md15
-rw-r--r--vendor/github.com/xenolf/lego/providers/http/memcached/memcached.go59
-rw-r--r--vendor/github.com/xenolf/lego/providers/http/memcached/memcached_test.go111
18 files changed, 1852 insertions, 2 deletions
diff --git a/vendor/github.com/xenolf/lego/providers/dns/auroradns/auroradns.go b/vendor/github.com/xenolf/lego/providers/dns/auroradns/auroradns.go
new file mode 100644
index 000000000..55b48f9b4
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/providers/dns/auroradns/auroradns.go
@@ -0,0 +1,141 @@
+package auroradns
+
+import (
+ "fmt"
+ "github.com/edeckers/auroradnsclient"
+ "github.com/edeckers/auroradnsclient/records"
+ "github.com/edeckers/auroradnsclient/zones"
+ "github.com/xenolf/lego/acme"
+ "os"
+ "sync"
+)
+
+// DNSProvider describes a provider for AuroraDNS
+type DNSProvider struct {
+ recordIDs map[string]string
+ recordIDsMu sync.Mutex
+ client *auroradnsclient.AuroraDNSClient
+}
+
+// NewDNSProvider returns a DNSProvider instance configured for AuroraDNS.
+// Credentials must be passed in the environment variables: AURORA_USER_ID
+// and AURORA_KEY.
+func NewDNSProvider() (*DNSProvider, error) {
+ userID := os.Getenv("AURORA_USER_ID")
+ key := os.Getenv("AURORA_KEY")
+
+ endpoint := os.Getenv("AURORA_ENDPOINT")
+ if endpoint == "" {
+ endpoint = "https://api.auroradns.eu"
+ }
+
+ return NewDNSProviderCredentials(endpoint, userID, key)
+}
+
+// NewDNSProviderCredentials uses the supplied credentials to return a
+// DNSProvider instance configured for AuroraDNS.
+func NewDNSProviderCredentials(baseURL string, userID string, key string) (*DNSProvider, error) {
+ client, err := auroradnsclient.NewAuroraDNSClient(baseURL, userID, key)
+ if err != nil {
+ return nil, err
+ }
+
+ return &DNSProvider{
+ client: client,
+ recordIDs: make(map[string]string),
+ }, nil
+}
+
+func (provider *DNSProvider) getZoneInformationByName(name string) (zones.ZoneRecord, error) {
+ zs, err := provider.client.GetZones()
+
+ if err != nil {
+ return zones.ZoneRecord{}, err
+ }
+
+ for _, element := range zs {
+ if element.Name == name {
+ return element, nil
+ }
+ }
+
+ 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, _ := acme.DNS01Record(domain, keyAuth)
+
+ 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)
+ }
+
+ // 1. Aurora will happily create the TXT record when it is provided a fqdn,
+ // but it will only appear in the control panel and will not be
+ // propagated to DNS servers. Extract and use subdomain instead.
+ // 2. A trailing dot in the fqdn will cause Aurora to add a trailing dot to
+ // the subdomain, resulting in _acme-challenge..<domain> rather
+ // than _acme-challenge.<domain>
+
+ subdomain := fqdn[0 : len(fqdn)-len(authZone)-1]
+
+ authZone = acme.UnFqdn(authZone)
+
+ zoneRecord, err := provider.getZoneInformationByName(authZone)
+
+ reqData :=
+ records.CreateRecordRequest{
+ RecordType: "TXT",
+ Name: subdomain,
+ Content: value,
+ TTL: 300,
+ }
+
+ respData, err := provider.client.CreateRecord(zoneRecord.ID, reqData)
+ if err != nil {
+ return fmt.Errorf("Could not create record: '%s'.", err)
+ }
+
+ provider.recordIDsMu.Lock()
+ provider.recordIDs[fqdn] = respData.ID
+ provider.recordIDsMu.Unlock()
+
+ return nil
+}
+
+// CleanUp removes a given record that was generated by Present
+func (provider *DNSProvider) CleanUp(domain, token, keyAuth string) error {
+ 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)
+ }
+
+ 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 = acme.UnFqdn(authZone)
+
+ zoneRecord, err := provider.getZoneInformationByName(authZone)
+ if err != nil {
+ return err
+ }
+
+ _, err = provider.client.RemoveRecord(zoneRecord.ID, recordID)
+ if err != nil {
+ return err
+ }
+
+ provider.recordIDsMu.Lock()
+ delete(provider.recordIDs, fqdn)
+ provider.recordIDsMu.Unlock()
+
+ return nil
+}
diff --git a/vendor/github.com/xenolf/lego/providers/dns/auroradns/auroradns_test.go b/vendor/github.com/xenolf/lego/providers/dns/auroradns/auroradns_test.go
new file mode 100644
index 000000000..f4df7fa61
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/providers/dns/auroradns/auroradns_test.go
@@ -0,0 +1,148 @@
+package auroradns
+
+import (
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+)
+
+var fakeAuroraDNSUserId = "asdf1234"
+var fakeAuroraDNSKey = "key"
+
+func TestAuroraDNSPresent(t *testing.T) {
+ var requestReceived bool
+
+ mock := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == "GET" && r.URL.Path == "/zones" {
+ w.WriteHeader(http.StatusCreated)
+ fmt.Fprintf(w, `[{
+ "id": "c56a4180-65aa-42ec-a945-5fd21dec0538",
+ "name": "example.com"
+ }]`)
+ return
+ }
+
+ requestReceived = true
+
+ if got, want := r.Method, "POST"; got != want {
+ t.Errorf("Expected method to be '%s' but got '%s'", want, got)
+ }
+
+ if got, want := r.URL.Path, "/zones/c56a4180-65aa-42ec-a945-5fd21dec0538/records"; got != want {
+ t.Errorf("Expected path to be '%s' but got '%s'", want, got)
+ }
+
+ if got, want := r.Header.Get("Content-Type"), "application/json"; got != want {
+ t.Errorf("Expected Content-Type to be '%s' but got '%s'", want, got)
+ }
+
+ reqBody, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ t.Fatalf("Error reading request body: %v", err)
+ }
+
+ if got, want := string(reqBody),
+ `{"type":"TXT","name":"_acme-challenge","content":"w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI","ttl":300}`; got != want {
+
+ t.Errorf("Expected body data to be: `%s` but got `%s`", want, got)
+ }
+
+ w.WriteHeader(http.StatusCreated)
+ fmt.Fprintf(w, `{
+ "id": "c56a4180-65aa-42ec-a945-5fd21dec0538",
+ "type": "TXT",
+ "name": "_acme-challenge",
+ "ttl": 300
+ }`)
+ }))
+
+ defer mock.Close()
+
+ auroraProvider, err := NewDNSProviderCredentials(mock.URL, fakeAuroraDNSUserId, fakeAuroraDNSKey)
+ if auroraProvider == nil {
+ t.Fatal("Expected non-nil AuroraDNS provider, but was nil")
+ }
+
+ if err != nil {
+ t.Fatalf("Expected no error creating provider, but got: %v", err)
+ }
+
+ err = auroraProvider.Present("example.com", "", "foobar")
+ if err != nil {
+ t.Fatalf("Expected no error creating TXT record, but got: %v", err)
+ }
+
+ if !requestReceived {
+ t.Error("Expected request to be received by mock backend, but it wasn't")
+ }
+}
+
+func TestAuroraDNSCleanUp(t *testing.T) {
+ var requestReceived bool
+
+ mock := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == "GET" && r.URL.Path == "/zones" {
+ w.WriteHeader(http.StatusCreated)
+ fmt.Fprintf(w, `[{
+ "id": "c56a4180-65aa-42ec-a945-5fd21dec0538",
+ "name": "example.com"
+ }]`)
+ return
+ }
+
+ if r.Method == "POST" && r.URL.Path == "/zones/c56a4180-65aa-42ec-a945-5fd21dec0538/records" {
+ w.WriteHeader(http.StatusCreated)
+ fmt.Fprintf(w, `{
+ "id": "ec56a4180-65aa-42ec-a945-5fd21dec0538",
+ "type": "TXT",
+ "name": "_acme-challenge",
+ "ttl": 300
+ }`)
+ return
+ }
+
+ requestReceived = true
+
+ if got, want := r.Method, "DELETE"; got != want {
+ t.Errorf("Expected method to be '%s' but got '%s'", want, got)
+ }
+
+ if got, want := r.URL.Path,
+ "/zones/c56a4180-65aa-42ec-a945-5fd21dec0538/records/ec56a4180-65aa-42ec-a945-5fd21dec0538"; got != want {
+ t.Errorf("Expected path to be '%s' but got '%s'", want, got)
+ }
+
+ if got, want := r.Header.Get("Content-Type"), "application/json"; got != want {
+ t.Errorf("Expected Content-Type to be '%s' but got '%s'", want, got)
+ }
+
+ w.WriteHeader(http.StatusCreated)
+ fmt.Fprintf(w, `{}`)
+ }))
+ defer mock.Close()
+
+ auroraProvider, err := NewDNSProviderCredentials(mock.URL, fakeAuroraDNSUserId, fakeAuroraDNSKey)
+ if auroraProvider == nil {
+ t.Fatal("Expected non-nil AuroraDNS provider, but was nil")
+ }
+
+ if err != nil {
+ t.Fatalf("Expected no error creating provider, but got: %v", err)
+ }
+
+ err = auroraProvider.Present("example.com", "", "foobar")
+ if err != nil {
+ t.Fatalf("Expected no error creating TXT record, but got: %v", err)
+ }
+
+ err = auroraProvider.CleanUp("example.com", "", "foobar")
+ if err != nil {
+ t.Fatalf("Expected no error removing TXT record, but got: %v", err)
+ }
+
+ if !requestReceived {
+ t.Error("Expected request to be received by mock backend, but it wasn't")
+ }
+}
diff --git a/vendor/github.com/xenolf/lego/providers/dns/azure/azure.go b/vendor/github.com/xenolf/lego/providers/dns/azure/azure.go
new file mode 100644
index 000000000..6742e4f56
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/providers/dns/azure/azure.go
@@ -0,0 +1,142 @@
+// Package azure implements a DNS provider for solving the DNS-01
+// challenge using azure DNS.
+// Azure doesn't like trailing dots on domain names, most of the acme code does.
+package azure
+
+import (
+ "fmt"
+ "os"
+ "time"
+
+ "github.com/Azure/azure-sdk-for-go/arm/dns"
+
+ "github.com/Azure/go-autorest/autorest/azure"
+ "github.com/Azure/go-autorest/autorest/to"
+ "github.com/xenolf/lego/acme"
+ "strings"
+)
+
+// DNSProvider is an implementation of the acme.ChallengeProvider interface
+type DNSProvider struct {
+ clientId string
+ clientSecret string
+ subscriptionId string
+ tenantId string
+ resourceGroup string
+}
+
+// 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
+func NewDNSProvider() (*DNSProvider, error) {
+ clientId := os.Getenv("AZURE_CLIENT_ID")
+ clientSecret := os.Getenv("AZURE_CLIENT_SECRET")
+ 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)
+}
+
+// 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")
+ }
+
+ return &DNSProvider{
+ clientId: clientId,
+ clientSecret: clientSecret,
+ subscriptionId: subscriptionId,
+ tenantId: tenantId,
+ resourceGroup: resourceGroup,
+ }, nil
+}
+
+// Timeout returns the timeout and interval to use when checking for DNS
+// propagation. Adjusting here to cope with spikes in propagation times.
+func (c *DNSProvider) Timeout() (timeout, interval time.Duration) {
+ return 120 * time.Second, 2 * time.Second
+}
+
+// Present creates a TXT record to fulfil the dns-01 challenge
+func (c *DNSProvider) Present(domain, token, keyAuth string) error {
+ fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
+ zone, err := c.getHostedZoneID(fqdn)
+ if err != nil {
+ return err
+ }
+
+ rsc := dns.NewRecordSetsClient(c.subscriptionId)
+ rsc.Authorizer, err = c.newServicePrincipalTokenFromCredentials(azure.PublicCloud.ResourceManagerEndpoint)
+ relative := toRelativeRecord(fqdn, acme.ToFqdn(zone))
+ rec := dns.RecordSet{
+ Name: &relative,
+ Properties: &dns.RecordSetProperties{
+ TTL: to.Int64Ptr(60),
+ TXTRecords: &[]dns.TxtRecord{dns.TxtRecord{Value: &[]string{value}}},
+ },
+ }
+ _, err = rsc.CreateOrUpdate(c.resourceGroup, zone, relative, dns.TXT, rec, "", "")
+
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// Returns the relative record to the domain
+func toRelativeRecord(domain, zone string) string {
+ 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, _, _ := acme.DNS01Record(domain, keyAuth)
+
+ zone, err := c.getHostedZoneID(fqdn)
+ if err != nil {
+ return err
+ }
+
+ relative := toRelativeRecord(fqdn, acme.ToFqdn(zone))
+ rsc := dns.NewRecordSetsClient(c.subscriptionId)
+ rsc.Authorizer, err = c.newServicePrincipalTokenFromCredentials(azure.PublicCloud.ResourceManagerEndpoint)
+ _, err = rsc.Delete(c.resourceGroup, zone, relative, dns.TXT, "", "")
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// Checks that azure has a zone for this domain name.
+func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) {
+ authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
+ if err != nil {
+ return "", err
+ }
+
+ // Now we want to to Azure and get the zone.
+ dc := dns.NewZonesClient(c.subscriptionId)
+ dc.Authorizer, err = c.newServicePrincipalTokenFromCredentials(azure.PublicCloud.ResourceManagerEndpoint)
+ zone, err := dc.Get(c.resourceGroup, acme.UnFqdn(authZone))
+
+ if err != nil {
+ return "", err
+ }
+
+ // zone.Name shouldn't have a trailing dot(.)
+ return to.String(zone.Name), nil
+}
+
+// NewServicePrincipalTokenFromCredentials creates a new ServicePrincipalToken using values of the
+// passed credentials map.
+func (c *DNSProvider) newServicePrincipalTokenFromCredentials(scope string) (*azure.ServicePrincipalToken, error) {
+ oauthConfig, err := azure.PublicCloud.OAuthConfigForTenant(c.tenantId)
+ if err != nil {
+ panic(err)
+ }
+ return azure.NewServicePrincipalToken(*oauthConfig, c.clientId, c.clientSecret, scope)
+}
diff --git a/vendor/github.com/xenolf/lego/providers/dns/azure/azure_test.go b/vendor/github.com/xenolf/lego/providers/dns/azure/azure_test.go
new file mode 100644
index 000000000..db55f578a
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/providers/dns/azure/azure_test.go
@@ -0,0 +1,89 @@
+package azure
+
+import (
+ "os"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+var (
+ azureLiveTest bool
+ azureClientID string
+ azureClientSecret string
+ azureSubscriptionID string
+ azureTenantID string
+ azureResourceGroup string
+ azureDomain string
+)
+
+func init() {
+ azureClientID = os.Getenv("AZURE_CLIENT_ID")
+ azureClientSecret = os.Getenv("AZURE_CLIENT_SECRET")
+ azureSubscriptionID = os.Getenv("AZURE_SUBSCRIPTION_ID")
+ azureTenantID = os.Getenv("AZURE_TENANT_ID")
+ azureResourceGroup = os.Getenv("AZURE_RESOURCE_GROUP")
+ azureDomain = os.Getenv("AZURE_DOMAIN")
+ if len(azureClientID) > 0 && len(azureClientSecret) > 0 {
+ azureLiveTest = true
+ }
+}
+
+func restoreAzureEnv() {
+ os.Setenv("AZURE_CLIENT_ID", azureClientID)
+ os.Setenv("AZURE_SUBSCRIPTION_ID", azureSubscriptionID)
+}
+
+func TestNewDNSProviderValid(t *testing.T) {
+ if !azureLiveTest {
+ t.Skip("skipping live test (requires credentials)")
+ }
+ os.Setenv("AZURE_CLIENT_ID", "")
+ _, err := NewDNSProviderCredentials(azureClientID, azureClientSecret, azureSubscriptionID, azureTenantID, azureResourceGroup)
+ assert.NoError(t, err)
+ restoreAzureEnv()
+}
+
+func TestNewDNSProviderValidEnv(t *testing.T) {
+ if !azureLiveTest {
+ t.Skip("skipping live test (requires credentials)")
+ }
+ os.Setenv("AZURE_CLIENT_ID", "other")
+ _, err := NewDNSProvider()
+ assert.NoError(t, err)
+ restoreAzureEnv()
+}
+
+func TestNewDNSProviderMissingCredErr(t *testing.T) {
+ os.Setenv("AZURE_SUBSCRIPTION_ID", "")
+ _, err := NewDNSProvider()
+ assert.EqualError(t, err, "Azure configuration missing")
+ restoreAzureEnv()
+}
+
+func TestLiveAzurePresent(t *testing.T) {
+ if !azureLiveTest {
+ t.Skip("skipping live test")
+ }
+
+ provider, err := NewDNSProviderCredentials(azureClientID, azureClientSecret, azureSubscriptionID, azureTenantID, azureResourceGroup)
+ assert.NoError(t, err)
+
+ err = provider.Present(azureDomain, "", "123d==")
+ assert.NoError(t, err)
+}
+
+func TestLiveAzureCleanUp(t *testing.T) {
+ if !azureLiveTest {
+ t.Skip("skipping live test")
+ }
+
+ provider, err := NewDNSProviderCredentials(azureClientID, azureClientSecret, azureSubscriptionID, azureTenantID, azureResourceGroup)
+ time.Sleep(time.Second * 1)
+
+ assert.NoError(t, err)
+
+ err = provider.CleanUp(azureDomain, "", "123d==")
+ assert.NoError(t, err)
+}
diff --git a/vendor/github.com/xenolf/lego/providers/dns/dnspod/dnspod.go b/vendor/github.com/xenolf/lego/providers/dns/dnspod/dnspod.go
new file mode 100644
index 000000000..0ce08a8bb
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/providers/dns/dnspod/dnspod.go
@@ -0,0 +1,146 @@
+// Package dnspod implements a DNS provider for solving the DNS-01 challenge
+// using dnspod DNS.
+package dnspod
+
+import (
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/decker502/dnspod-go"
+ "github.com/xenolf/lego/acme"
+)
+
+// DNSProvider is an implementation of the acme.ChallengeProvider interface.
+type DNSProvider struct {
+ client *dnspod.Client
+}
+
+// NewDNSProvider returns a DNSProvider instance configured for dnspod.
+// Credentials must be passed in the environment variables: DNSPOD_API_KEY.
+func NewDNSProvider() (*DNSProvider, error) {
+ key := os.Getenv("DNSPOD_API_KEY")
+ return NewDNSProviderCredentials(key)
+}
+
+// NewDNSProviderCredentials uses the supplied credentials to return a
+// DNSProvider instance configured for dnspod.
+func NewDNSProviderCredentials(key string) (*DNSProvider, error) {
+ if key == "" {
+ return nil, fmt.Errorf("dnspod credentials missing")
+ }
+
+ params := dnspod.CommonParams{LoginToken: key, Format: "json"}
+ return &DNSProvider{
+ client: dnspod.NewClient(params),
+ }, nil
+}
+
+// Present creates a TXT record to fulfil the dns-01 challenge.
+func (c *DNSProvider) Present(domain, token, keyAuth string) error {
+ fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
+ zoneID, zoneName, err := c.getHostedZone(domain)
+ if err != nil {
+ return err
+ }
+
+ recordAttributes := c.newTxtRecord(zoneName, fqdn, value, ttl)
+ _, _, err = c.client.Domains.CreateRecord(zoneID, *recordAttributes)
+ if err != nil {
+ return fmt.Errorf("dnspod API call failed: %v", err)
+ }
+
+ return nil
+}
+
+// CleanUp removes the TXT record matching the specified parameters.
+func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
+ fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
+
+ records, err := c.findTxtRecords(domain, fqdn)
+ if err != nil {
+ return err
+ }
+
+ zoneID, _, err := c.getHostedZone(domain)
+ if err != nil {
+ return err
+ }
+
+ for _, rec := range records {
+ _, err := c.client.Domains.DeleteRecord(zoneID, rec.ID)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (c *DNSProvider) getHostedZone(domain string) (string, string, error) {
+ zones, _, err := c.client.Domains.List()
+ if err != nil {
+ return "", "", fmt.Errorf("dnspod API call failed: %v", err)
+ }
+
+ 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 == acme.UnFqdn(authZone) {
+ hostedZone = zone
+ }
+ }
+
+ if hostedZone.ID == 0 {
+ return "", "", fmt.Errorf("Zone %s not found in dnspod for domain %s", authZone, domain)
+
+ }
+
+ return fmt.Sprintf("%v", hostedZone.ID), hostedZone.Name, nil
+}
+
+func (c *DNSProvider) newTxtRecord(zone, fqdn, value string, ttl int) *dnspod.Record {
+ name := c.extractRecordName(fqdn, zone)
+
+ return &dnspod.Record{
+ Type: "TXT",
+ Name: name,
+ Value: value,
+ Line: "默认",
+ TTL: "600",
+ }
+}
+
+func (c *DNSProvider) findTxtRecords(domain, fqdn string) ([]dnspod.Record, error) {
+ zoneID, zoneName, err := c.getHostedZone(domain)
+ if err != nil {
+ return nil, err
+ }
+
+ var records []dnspod.Record
+ result, _, err := c.client.Domains.ListRecords(zoneID, "")
+ if err != nil {
+ return records, fmt.Errorf("dnspod API call has failed: %v", err)
+ }
+
+ recordName := c.extractRecordName(fqdn, zoneName)
+
+ for _, record := range result {
+ if record.Name == recordName {
+ records = append(records, record)
+ }
+ }
+
+ return records, nil
+}
+
+func (c *DNSProvider) extractRecordName(fqdn, domain string) string {
+ name := acme.UnFqdn(fqdn)
+ if idx := strings.Index(name, "."+domain); idx != -1 {
+ return name[:idx]
+ }
+ return name
+}
diff --git a/vendor/github.com/xenolf/lego/providers/dns/dnspod/dnspod_test.go b/vendor/github.com/xenolf/lego/providers/dns/dnspod/dnspod_test.go
new file mode 100644
index 000000000..3311eb0a6
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/providers/dns/dnspod/dnspod_test.go
@@ -0,0 +1,72 @@
+package dnspod
+
+import (
+ "github.com/stretchr/testify/assert"
+ "os"
+ "testing"
+ "time"
+)
+
+var (
+ dnspodLiveTest bool
+ dnspodAPIKey string
+ dnspodDomain string
+)
+
+func init() {
+ dnspodAPIKey = os.Getenv("DNSPOD_API_KEY")
+ dnspodDomain = os.Getenv("DNSPOD_DOMAIN")
+ if len(dnspodAPIKey) > 0 && len(dnspodDomain) > 0 {
+ dnspodLiveTest = true
+ }
+}
+
+func restorednspodEnv() {
+ os.Setenv("DNSPOD_API_KEY", dnspodAPIKey)
+}
+
+func TestNewDNSProviderValid(t *testing.T) {
+ os.Setenv("DNSPOD_API_KEY", "")
+ _, err := NewDNSProviderCredentials("123")
+ assert.NoError(t, err)
+ restorednspodEnv()
+}
+func TestNewDNSProviderValidEnv(t *testing.T) {
+ os.Setenv("DNSPOD_API_KEY", "123")
+ _, err := NewDNSProvider()
+ assert.NoError(t, err)
+ restorednspodEnv()
+}
+
+func TestNewDNSProviderMissingCredErr(t *testing.T) {
+ os.Setenv("DNSPOD_API_KEY", "")
+ _, err := NewDNSProvider()
+ assert.EqualError(t, err, "dnspod credentials missing")
+ restorednspodEnv()
+}
+
+func TestLivednspodPresent(t *testing.T) {
+ if !dnspodLiveTest {
+ t.Skip("skipping live test")
+ }
+
+ provider, err := NewDNSProviderCredentials(dnspodAPIKey)
+ assert.NoError(t, err)
+
+ err = provider.Present(dnspodDomain, "", "123d==")
+ assert.NoError(t, err)
+}
+
+func TestLivednspodCleanUp(t *testing.T) {
+ if !dnspodLiveTest {
+ t.Skip("skipping live test")
+ }
+
+ time.Sleep(time.Second * 1)
+
+ provider, err := NewDNSProviderCredentials(dnspodAPIKey)
+ assert.NoError(t, err)
+
+ err = provider.CleanUp(dnspodDomain, "", "123d==")
+ assert.NoError(t, err)
+}
diff --git a/vendor/github.com/xenolf/lego/providers/dns/exoscale/exoscale.go b/vendor/github.com/xenolf/lego/providers/dns/exoscale/exoscale.go
new file mode 100644
index 000000000..3b6b58d08
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/providers/dns/exoscale/exoscale.go
@@ -0,0 +1,132 @@
+// Package exoscale implements a DNS provider for solving the DNS-01 challenge
+// using exoscale DNS.
+package exoscale
+
+import (
+ "errors"
+ "fmt"
+ "os"
+
+ "github.com/pyr/egoscale/src/egoscale"
+ "github.com/xenolf/lego/acme"
+)
+
+// DNSProvider is an implementation of the acme.ChallengeProvider interface.
+type DNSProvider struct {
+ client *egoscale.Client
+}
+
+// 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")
+ secret := os.Getenv("EXOSCALE_API_SECRET")
+ endpoint := os.Getenv("EXOSCALE_ENDPOINT")
+ return NewDNSProviderClient(key, secret, endpoint)
+}
+
+// Uses the supplied parameters to return a DNSProvider instance
+// configured for Exoscale.
+func NewDNSProviderClient(key, secret, endpoint string) (*DNSProvider, error) {
+ if key == "" || secret == "" {
+ return nil, fmt.Errorf("Exoscale credentials missing")
+ }
+ if endpoint == "" {
+ endpoint = "https://api.exoscale.ch/dns"
+ }
+
+ return &DNSProvider{
+ client: egoscale.NewClient(endpoint, key, secret),
+ }, nil
+}
+
+// Present creates a TXT record to fulfil the dns-01 challenge.
+func (c *DNSProvider) Present(domain, token, keyAuth string) error {
+ 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)
+ if err != nil {
+ return err
+ }
+
+ record := egoscale.DNSRecord{
+ Name: recordName,
+ Ttl: ttl,
+ Content: value,
+ RecordType: "TXT",
+ }
+
+ if recordId == 0 {
+ _, err := c.client.CreateRecord(zone, record)
+ if err != nil {
+ return errors.New("Error while creating DNS record: " + err.Error())
+ }
+ } else {
+ record.Id = recordId
+ _, err := c.client.UpdateRecord(zone, record)
+ if err != nil {
+ return errors.New("Error while updating DNS record: " + err.Error())
+ }
+ }
+
+ return nil
+}
+
+// CleanUp removes the record matching the specified parameters.
+func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
+ fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
+ zone, recordName, err := c.FindZoneAndRecordName(fqdn, domain)
+ if err != nil {
+ return err
+ }
+
+ recordId, err := c.FindExistingRecordId(zone, recordName)
+ if err != nil {
+ return err
+ }
+
+ if recordId != 0 {
+ record := egoscale.DNSRecord{
+ Id: recordId,
+ }
+
+ err = c.client.DeleteRecord(zone, record)
+ if err != nil {
+ return errors.New("Error while deleting DNS record: " + err.Error())
+ }
+ }
+
+ return nil
+}
+
+// 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) {
+ responses, err := c.client.GetRecords(zone)
+ if err != nil {
+ return -1, errors.New("Error while retrievening DNS records: " + err.Error())
+ }
+ for _, response := range responses {
+ if response.Record.Name == recordName {
+ return response.Record.Id, nil
+ }
+ }
+ return 0, nil
+}
+
+// Extract DNS zone and DNS entry name
+func (c *DNSProvider) FindZoneAndRecordName(fqdn, domain string) (string, string, error) {
+ zone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
+ if err != nil {
+ return "", "", err
+ }
+ 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/exoscale/exoscale_test.go b/vendor/github.com/xenolf/lego/providers/dns/exoscale/exoscale_test.go
new file mode 100644
index 000000000..343dd56f8
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/providers/dns/exoscale/exoscale_test.go
@@ -0,0 +1,103 @@
+package exoscale
+
+import (
+ "os"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+var (
+ exoscaleLiveTest bool
+ exoscaleAPIKey string
+ exoscaleAPISecret string
+ exoscaleDomain string
+)
+
+func init() {
+ exoscaleAPISecret = os.Getenv("EXOSCALE_API_SECRET")
+ exoscaleAPIKey = os.Getenv("EXOSCALE_API_KEY")
+ exoscaleDomain = os.Getenv("EXOSCALE_DOMAIN")
+ if len(exoscaleAPIKey) > 0 && len(exoscaleAPISecret) > 0 && len(exoscaleDomain) > 0 {
+ exoscaleLiveTest = true
+ }
+}
+
+func restoreExoscaleEnv() {
+ os.Setenv("EXOSCALE_API_KEY", exoscaleAPIKey)
+ os.Setenv("EXOSCALE_API_SECRET", exoscaleAPISecret)
+}
+
+func TestNewDNSProviderValid(t *testing.T) {
+ os.Setenv("EXOSCALE_API_KEY", "")
+ os.Setenv("EXOSCALE_API_SECRET", "")
+ _, err := NewDNSProviderClient("example@example.com", "123", "")
+ assert.NoError(t, err)
+ restoreExoscaleEnv()
+}
+func TestNewDNSProviderValidEnv(t *testing.T) {
+ os.Setenv("EXOSCALE_API_KEY", "example@example.com")
+ os.Setenv("EXOSCALE_API_SECRET", "123")
+ _, err := NewDNSProvider()
+ assert.NoError(t, err)
+ restoreExoscaleEnv()
+}
+
+func TestNewDNSProviderMissingCredErr(t *testing.T) {
+ os.Setenv("EXOSCALE_API_KEY", "")
+ os.Setenv("EXOSCALE_API_SECRET", "")
+ _, err := NewDNSProvider()
+ assert.EqualError(t, err, "Exoscale credentials missing")
+ restoreExoscaleEnv()
+}
+
+func TestExtractRootRecordName(t *testing.T) {
+ provider, err := NewDNSProviderClient("example@example.com", "123", "")
+ assert.NoError(t, err)
+
+ zone, recordName, err := provider.FindZoneAndRecordName("_acme-challenge.bar.com.", "bar.com")
+ assert.NoError(t, err)
+ assert.Equal(t, "bar.com", zone)
+ assert.Equal(t, "_acme-challenge", recordName)
+}
+
+func TestExtractSubRecordName(t *testing.T) {
+ provider, err := NewDNSProviderClient("example@example.com", "123", "")
+ assert.NoError(t, err)
+
+ zone, recordName, err := provider.FindZoneAndRecordName("_acme-challenge.foo.bar.com.", "foo.bar.com")
+ assert.NoError(t, err)
+ assert.Equal(t, "bar.com", zone)
+ assert.Equal(t, "_acme-challenge.foo", recordName)
+}
+
+func TestLiveExoscalePresent(t *testing.T) {
+ if !exoscaleLiveTest {
+ t.Skip("skipping live test")
+ }
+
+ provider, err := NewDNSProviderClient(exoscaleAPIKey, exoscaleAPISecret, "")
+ assert.NoError(t, err)
+
+ err = provider.Present(exoscaleDomain, "", "123d==")
+ assert.NoError(t, err)
+
+ // Present Twice to handle create / update
+ err = provider.Present(exoscaleDomain, "", "123d==")
+ assert.NoError(t, err)
+}
+
+func TestLiveExoscaleCleanUp(t *testing.T) {
+ if !exoscaleLiveTest {
+ t.Skip("skipping live test")
+ }
+
+ time.Sleep(time.Second * 1)
+
+ provider, err := NewDNSProviderClient(exoscaleAPIKey, exoscaleAPISecret, "")
+ assert.NoError(t, err)
+
+ err = provider.CleanUp(exoscaleDomain, "", "123d==")
+ assert.NoError(t, err)
+}
diff --git a/vendor/github.com/xenolf/lego/providers/dns/gandi/gandi_test.go b/vendor/github.com/xenolf/lego/providers/dns/gandi/gandi_test.go
index 15919e2eb..451333ca1 100644
--- a/vendor/github.com/xenolf/lego/providers/dns/gandi/gandi_test.go
+++ b/vendor/github.com/xenolf/lego/providers/dns/gandi/gandi_test.go
@@ -141,7 +141,7 @@ func TestDNSProviderLive(t *testing.T) {
}
// complete the challenge
bundle := false
- _, failures := client.ObtainCertificate([]string{domain}, bundle, nil)
+ _, failures := client.ObtainCertificate([]string{domain}, bundle, nil, false)
if len(failures) > 0 {
t.Fatal(failures)
}
@@ -496,7 +496,7 @@ var serverResponses = map[string]string{
</member>
<member>
<name>id</name>
-<value><int>3333333333</int></value>
+<value><int>333333333</int></value>
</member>
<member>
<name>value</name>
diff --git a/vendor/github.com/xenolf/lego/providers/dns/googlecloud/googlecloud.go b/vendor/github.com/xenolf/lego/providers/dns/googlecloud/googlecloud.go
index b8d9951c9..ea6c0875c 100644
--- a/vendor/github.com/xenolf/lego/providers/dns/googlecloud/googlecloud.go
+++ b/vendor/github.com/xenolf/lego/providers/dns/googlecloud/googlecloud.go
@@ -68,6 +68,16 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error {
Additions: []*dns.ResourceRecordSet{rec},
}
+ // Look for existing records.
+ list, err := c.client.ResourceRecordSets.List(c.project, zone).Name(fqdn).Type("TXT").Do()
+ if err != nil {
+ return err
+ }
+ if len(list.Rrsets) > 0 {
+ // Attempt to delete the existing records when adding our new one.
+ change.Deletions = list.Rrsets
+ }
+
chg, err := c.client.Changes.Create(c.project, zone, change).Do()
if err != nil {
return err
diff --git a/vendor/github.com/xenolf/lego/providers/dns/googlecloud/googlecloud_test.go b/vendor/github.com/xenolf/lego/providers/dns/googlecloud/googlecloud_test.go
index d73788163..75a10d9a4 100644
--- a/vendor/github.com/xenolf/lego/providers/dns/googlecloud/googlecloud_test.go
+++ b/vendor/github.com/xenolf/lego/providers/dns/googlecloud/googlecloud_test.go
@@ -70,6 +70,20 @@ func TestLiveGoogleCloudPresent(t *testing.T) {
assert.NoError(t, err)
}
+func TestLiveGoogleCloudPresentMultiple(t *testing.T) {
+ if !gcloudLiveTest {
+ t.Skip("skipping live test")
+ }
+
+ provider, err := NewDNSProviderCredentials(gcloudProject)
+ assert.NoError(t, err)
+
+ // Check that we're able to create multiple entries
+ err = provider.Present(gcloudDomain, "1", "123d==")
+ err = provider.Present(gcloudDomain, "2", "123d==")
+ assert.NoError(t, err)
+}
+
func TestLiveGoogleCloudCleanUp(t *testing.T) {
if !gcloudLiveTest {
t.Skip("skipping live test")
diff --git a/vendor/github.com/xenolf/lego/providers/dns/ns1/ns1.go b/vendor/github.com/xenolf/lego/providers/dns/ns1/ns1.go
new file mode 100644
index 000000000..105d73f89
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/providers/dns/ns1/ns1.go
@@ -0,0 +1,97 @@
+// Package ns1 implements a DNS provider for solving the DNS-01 challenge
+// using NS1 DNS.
+package ns1
+
+import (
+ "fmt"
+ "net/http"
+ "os"
+ "time"
+
+ "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 acme.ChallengeProvider interface.
+type DNSProvider struct {
+ client *rest.Client
+}
+
+// NewDNSProvider returns a DNSProvider instance configured for NS1.
+// Credentials must be passed in the environment variables: NS1_API_KEY.
+func NewDNSProvider() (*DNSProvider, error) {
+ key := os.Getenv("NS1_API_KEY")
+ if key == "" {
+ return nil, fmt.Errorf("NS1 credentials missing")
+ }
+ return NewDNSProviderCredentials(key)
+}
+
+// NewDNSProviderCredentials uses the supplied credentials to return a
+// DNSProvider instance configured for NS1.
+func NewDNSProviderCredentials(key string) (*DNSProvider, error) {
+ if key == "" {
+ return nil, fmt.Errorf("NS1 credentials missing")
+ }
+
+ httpClient := &http.Client{Timeout: time.Second * 10}
+ client := rest.NewClient(httpClient, rest.SetAPIKey(key))
+
+ return &DNSProvider{client}, nil
+}
+
+// Present creates a TXT record to fulfil the dns-01 challenge.
+func (c *DNSProvider) Present(domain, token, keyAuth string) error {
+ fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
+
+ zone, err := c.getHostedZone(domain)
+ if err != nil {
+ return err
+ }
+
+ record := c.newTxtRecord(zone, fqdn, value, ttl)
+ _, err = c.client.Records.Create(record)
+ if err != nil && err != rest.ErrRecordExists {
+ return err
+ }
+
+ return nil
+}
+
+// CleanUp removes the TXT record matching the specified parameters.
+func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
+ fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
+
+ zone, err := c.getHostedZone(domain)
+ if err != nil {
+ return err
+ }
+
+ name := acme.UnFqdn(fqdn)
+ _, err = c.client.Records.Delete(zone.Zone, name, "TXT")
+ return err
+}
+
+func (c *DNSProvider) getHostedZone(domain string) (*dns.Zone, error) {
+ zone, _, err := c.client.Zones.Get(domain)
+ if err != nil {
+ return nil, err
+ }
+
+ return zone, nil
+}
+
+func (c *DNSProvider) newTxtRecord(zone *dns.Zone, fqdn, value string, ttl int) *dns.Record {
+ name := acme.UnFqdn(fqdn)
+
+ return &dns.Record{
+ Type: "TXT",
+ Zone: zone.Zone,
+ Domain: name,
+ TTL: ttl,
+ Answers: []*dns.Answer{
+ {Rdata: []string{value}},
+ },
+ }
+}
diff --git a/vendor/github.com/xenolf/lego/providers/dns/ns1/ns1_test.go b/vendor/github.com/xenolf/lego/providers/dns/ns1/ns1_test.go
new file mode 100644
index 000000000..eb9150dde
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/providers/dns/ns1/ns1_test.go
@@ -0,0 +1,67 @@
+package ns1
+
+import (
+ "os"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+var (
+ liveTest bool
+ apiKey string
+ domain string
+)
+
+func init() {
+ apiKey = os.Getenv("NS1_API_KEY")
+ domain = os.Getenv("NS1_DOMAIN")
+ if len(apiKey) > 0 && len(domain) > 0 {
+ liveTest = true
+ }
+}
+
+func restoreNS1Env() {
+ os.Setenv("NS1_API_KEY", apiKey)
+}
+
+func TestNewDNSProviderValid(t *testing.T) {
+ os.Setenv("NS1_API_KEY", "")
+ _, err := NewDNSProviderCredentials("123")
+ assert.NoError(t, err)
+ restoreNS1Env()
+}
+
+func TestNewDNSProviderMissingCredErr(t *testing.T) {
+ os.Setenv("NS1_API_KEY", "")
+ _, err := NewDNSProvider()
+ assert.EqualError(t, err, "NS1 credentials missing")
+ restoreNS1Env()
+}
+
+func TestLivePresent(t *testing.T) {
+ if !liveTest {
+ t.Skip("skipping live test")
+ }
+
+ provider, err := NewDNSProviderCredentials(apiKey)
+ assert.NoError(t, err)
+
+ err = provider.Present(domain, "", "123d==")
+ assert.NoError(t, err)
+}
+
+func TestLiveCleanUp(t *testing.T) {
+ if !liveTest {
+ t.Skip("skipping live test")
+ }
+
+ time.Sleep(time.Second * 1)
+
+ provider, err := NewDNSProviderCredentials(apiKey)
+ assert.NoError(t, err)
+
+ err = provider.CleanUp(domain, "", "123d==")
+ assert.NoError(t, err)
+}
diff --git a/vendor/github.com/xenolf/lego/providers/dns/rackspace/rackspace.go b/vendor/github.com/xenolf/lego/providers/dns/rackspace/rackspace.go
new file mode 100644
index 000000000..2b106a27e
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/providers/dns/rackspace/rackspace.go
@@ -0,0 +1,284 @@
+// Package rackspace implements a DNS provider for solving the DNS-01
+// challenge using rackspace DNS.
+package rackspace
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "time"
+
+ "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 acme.ChallengeProvider interface
+// used to store the reusable token and DNS API endpoint
+type DNSProvider struct {
+ token string
+ cloudDNSEndpoint string
+}
+
+// NewDNSProvider returns a DNSProvider instance configured for Rackspace.
+// Credentials must be passed in the environment variables: RACKSPACE_USER
+// and RACKSPACE_API_KEY.
+func NewDNSProvider() (*DNSProvider, error) {
+ user := os.Getenv("RACKSPACE_USER")
+ key := os.Getenv("RACKSPACE_API_KEY")
+ return NewDNSProviderCredentials(user, key)
+}
+
+// NewDNSProviderCredentials uses the supplied credentials to return a
+// DNSProvider instance configured for Rackspace. It authenticates against
+// the API, also grabbing the DNS Endpoint.
+func NewDNSProviderCredentials(user, key string) (*DNSProvider, error) {
+ if user == "" || key == "" {
+ return nil, fmt.Errorf("Rackspace credentials missing")
+ }
+
+ type APIKeyCredentials struct {
+ Username string `json:"username"`
+ APIKey string `json:"apiKey"`
+ }
+
+ type Auth struct {
+ APIKeyCredentials `json:"RAX-KSKEY:apiKeyCredentials"`
+ }
+
+ type RackspaceAuthData struct {
+ Auth `json:"auth"`
+ }
+
+ type RackspaceIdentity struct {
+ Access struct {
+ ServiceCatalog []struct {
+ Endpoints []struct {
+ PublicURL string `json:"publicURL"`
+ TenantID string `json:"tenantId"`
+ } `json:"endpoints"`
+ Name string `json:"name"`
+ } `json:"serviceCatalog"`
+ Token struct {
+ ID string `json:"id"`
+ } `json:"token"`
+ } `json:"access"`
+ }
+
+ authData := RackspaceAuthData{
+ Auth: Auth{
+ APIKeyCredentials: APIKeyCredentials{
+ Username: user,
+ APIKey: key,
+ },
+ },
+ }
+
+ body, err := json.Marshal(authData)
+ if err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequest("POST", rackspaceAPIURL, bytes.NewReader(body))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Content-Type", "application/json")
+
+ 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)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("Rackspace Authentication failed. Response code: %d", resp.StatusCode)
+ }
+
+ var rackspaceIdentity RackspaceIdentity
+ err = json.NewDecoder(resp.Body).Decode(&rackspaceIdentity)
+ if err != nil {
+ return nil, err
+ }
+
+ // Iterate through the Service Catalog to get the DNS Endpoint
+ var dnsEndpoint string
+ for _, service := range rackspaceIdentity.Access.ServiceCatalog {
+ if service.Name == "cloudDNS" {
+ dnsEndpoint = service.Endpoints[0].PublicURL
+ break
+ }
+ }
+ if dnsEndpoint == "" {
+ return nil, fmt.Errorf("Failed to populate DNS endpoint, check Rackspace API for changes.")
+ }
+
+ return &DNSProvider{
+ token: rackspaceIdentity.Access.Token.ID,
+ cloudDNSEndpoint: dnsEndpoint,
+ }, nil
+}
+
+// Present creates a TXT record to fulfil the dns-01 challenge
+func (c *DNSProvider) Present(domain, token, keyAuth string) error {
+ fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
+ zoneID, err := c.getHostedZoneID(fqdn)
+ if err != nil {
+ return err
+ }
+
+ rec := RackspaceRecords{
+ RackspaceRecord: []RackspaceRecord{{
+ Name: acme.UnFqdn(fqdn),
+ Type: "TXT",
+ Data: value,
+ TTL: 300,
+ }},
+ }
+
+ body, err := json.Marshal(rec)
+ if err != nil {
+ return err
+ }
+
+ _, err = c.makeRequest("POST", fmt.Sprintf("/domains/%d/records", zoneID), bytes.NewReader(body))
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// CleanUp removes the TXT record matching the specified parameters
+func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
+ fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
+ zoneID, err := c.getHostedZoneID(fqdn)
+ if err != nil {
+ return err
+ }
+
+ record, err := c.findTxtRecord(fqdn, zoneID)
+ if err != nil {
+ return err
+ }
+
+ _, err = c.makeRequest("DELETE", fmt.Sprintf("/domains/%d/records?id=%s", zoneID, record.ID), nil)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// getHostedZoneID performs a lookup to get the DNS zone which needs
+// modifying for a given FQDN
+func (c *DNSProvider) getHostedZoneID(fqdn string) (int, error) {
+ // HostedZones represents the response when querying Rackspace DNS zones
+ type ZoneSearchResponse struct {
+ TotalEntries int `json:"totalEntries"`
+ HostedZones []struct {
+ ID int `json:"id"`
+ Name string `json:"name"`
+ } `json:"domains"`
+ }
+
+ authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
+ if err != nil {
+ return 0, err
+ }
+
+ result, err := c.makeRequest("GET", fmt.Sprintf("/domains?name=%s", acme.UnFqdn(authZone)), nil)
+ if err != nil {
+ return 0, err
+ }
+
+ var zoneSearchResponse ZoneSearchResponse
+ err = json.Unmarshal(result, &zoneSearchResponse)
+ if err != nil {
+ return 0, err
+ }
+
+ // 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 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, acme.UnFqdn(fqdn)), nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var records RackspaceRecords
+ err = json.Unmarshal(result, &records)
+ if err != nil {
+ return nil, err
+ }
+
+ recordsLength := len(records.RackspaceRecord)
+ switch recordsLength {
+ case 1:
+ break
+ case 0:
+ 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 &records.RackspaceRecord[0], nil
+}
+
+// makeRequest is a wrapper function used for making DNS API requests
+func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawMessage, error) {
+ url := c.cloudDNSEndpoint + uri
+ req, err := http.NewRequest(method, url, body)
+ if err != nil {
+ return nil, err
+ }
+
+ req.Header.Set("X-Auth-Token", c.token)
+ req.Header.Set("Content-Type", "application/json")
+
+ 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)
+ }
+
+ 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)
+ }
+
+ var r json.RawMessage
+ err = json.NewDecoder(resp.Body).Decode(&r)
+ if err != nil {
+ return nil, fmt.Errorf("JSON decode failed for %s %s. Response code: %d", method, url, resp.StatusCode)
+ }
+
+ return r, nil
+}
+
+// RackspaceRecords is the list of records sent/recieved from the DNS API
+type RackspaceRecords struct {
+ RackspaceRecord []RackspaceRecord `json:"records"`
+}
+
+// RackspaceRecord represents a Rackspace DNS record
+type RackspaceRecord struct {
+ Name string `json:"name"`
+ Type string `json:"type"`
+ Data string `json:"data"`
+ TTL int `json:"ttl,omitempty"`
+ ID string `json:"id,omitempty"`
+}
diff --git a/vendor/github.com/xenolf/lego/providers/dns/rackspace/rackspace_test.go b/vendor/github.com/xenolf/lego/providers/dns/rackspace/rackspace_test.go
new file mode 100644
index 000000000..22c979cad
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/providers/dns/rackspace/rackspace_test.go
@@ -0,0 +1,220 @@
+package rackspace
+
+import (
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+var (
+ rackspaceLiveTest bool
+ rackspaceUser string
+ rackspaceAPIKey string
+ rackspaceDomain string
+ testAPIURL string
+)
+
+func init() {
+ rackspaceUser = os.Getenv("RACKSPACE_USER")
+ rackspaceAPIKey = os.Getenv("RACKSPACE_API_KEY")
+ rackspaceDomain = os.Getenv("RACKSPACE_DOMAIN")
+ if len(rackspaceUser) > 0 && len(rackspaceAPIKey) > 0 && len(rackspaceDomain) > 0 {
+ rackspaceLiveTest = true
+ }
+}
+
+func testRackspaceEnv() {
+ rackspaceAPIURL = testAPIURL
+ os.Setenv("RACKSPACE_USER", "testUser")
+ os.Setenv("RACKSPACE_API_KEY", "testKey")
+}
+
+func liveRackspaceEnv() {
+ rackspaceAPIURL = "https://identity.api.rackspacecloud.com/v2.0/tokens"
+ os.Setenv("RACKSPACE_USER", rackspaceUser)
+ os.Setenv("RACKSPACE_API_KEY", rackspaceAPIKey)
+}
+
+func startTestServers() (identityAPI, dnsAPI *httptest.Server) {
+ dnsAPI = httptest.NewServer(dnsMux())
+ dnsEndpoint := dnsAPI.URL + "/123456"
+
+ identityAPI = httptest.NewServer(identityHandler(dnsEndpoint))
+ testAPIURL = identityAPI.URL + "/"
+ return
+}
+
+func closeTestServers(identityAPI, dnsAPI *httptest.Server) {
+ identityAPI.Close()
+ dnsAPI.Close()
+}
+
+func identityHandler(dnsEndpoint string) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ reqBody, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+ resp, found := jsonMap[string(reqBody)]
+ if !found {
+ w.WriteHeader(http.StatusBadRequest)
+ return
+ }
+ resp = strings.Replace(resp, "https://dns.api.rackspacecloud.com/v1.0/123456", dnsEndpoint, 1)
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprintf(w, resp)
+ })
+}
+
+func dnsMux() *http.ServeMux {
+ mux := http.NewServeMux()
+
+ // Used by `getHostedZoneID()` finding `zoneID` "?name=example.com"
+ mux.HandleFunc("/123456/domains", func(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Query().Get("name") == "example.com" {
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprintf(w, jsonMap["zoneDetails"])
+ return
+ }
+ w.WriteHeader(http.StatusBadRequest)
+ return
+ })
+
+ mux.HandleFunc("/123456/domains/112233/records", func(w http.ResponseWriter, r *http.Request) {
+ switch r.Method {
+ // Used by `Present()` creating the TXT record
+ case http.MethodPost:
+ reqBody, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+ resp, found := jsonMap[string(reqBody)]
+ if !found {
+ w.WriteHeader(http.StatusBadRequest)
+ return
+ }
+ w.WriteHeader(http.StatusAccepted)
+ fmt.Fprintf(w, resp)
+ // Used by `findTxtRecord()` finding `record.ID` "?type=TXT&name=_acme-challenge.example.com"
+ case http.MethodGet:
+ if r.URL.Query().Get("type") == "TXT" && r.URL.Query().Get("name") == "_acme-challenge.example.com" {
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprintf(w, jsonMap["recordDetails"])
+ return
+ }
+ w.WriteHeader(http.StatusBadRequest)
+ return
+ // Used by `CleanUp()` deleting the TXT record "?id=445566"
+ case http.MethodDelete:
+ if r.URL.Query().Get("id") == "TXT-654321" {
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprintf(w, jsonMap["recordDelete"])
+ return
+ }
+ w.WriteHeader(http.StatusBadRequest)
+ }
+ })
+
+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusNotFound)
+ fmt.Printf("Not Found for Request: (%+v)\n\n", r)
+ })
+
+ return mux
+}
+
+func TestNewDNSProviderMissingCredErr(t *testing.T) {
+ testRackspaceEnv()
+ _, err := NewDNSProviderCredentials("", "")
+ assert.EqualError(t, err, "Rackspace credentials missing")
+}
+
+func TestOfflineRackspaceValid(t *testing.T) {
+ testRackspaceEnv()
+ provider, err := NewDNSProviderCredentials(os.Getenv("RACKSPACE_USER"), os.Getenv("RACKSPACE_API_KEY"))
+
+ assert.NoError(t, err)
+ assert.Equal(t, provider.token, "testToken", "The token should match")
+}
+
+func TestOfflineRackspacePresent(t *testing.T) {
+ testRackspaceEnv()
+ provider, err := NewDNSProvider()
+
+ if assert.NoError(t, err) {
+ err = provider.Present("example.com", "token", "keyAuth")
+ assert.NoError(t, err)
+ }
+}
+
+func TestOfflineRackspaceCleanUp(t *testing.T) {
+ testRackspaceEnv()
+ provider, err := NewDNSProvider()
+
+ if assert.NoError(t, err) {
+ err = provider.CleanUp("example.com", "token", "keyAuth")
+ assert.NoError(t, err)
+ }
+}
+
+func TestNewDNSProviderValidEnv(t *testing.T) {
+ if !rackspaceLiveTest {
+ t.Skip("skipping live test")
+ }
+
+ liveRackspaceEnv()
+ provider, err := NewDNSProvider()
+ assert.NoError(t, err)
+ assert.Contains(t, provider.cloudDNSEndpoint, "https://dns.api.rackspacecloud.com/v1.0/", "The endpoint URL should contain the base")
+}
+
+func TestRackspacePresent(t *testing.T) {
+ if !rackspaceLiveTest {
+ t.Skip("skipping live test")
+ }
+
+ liveRackspaceEnv()
+ provider, err := NewDNSProvider()
+ assert.NoError(t, err)
+
+ err = provider.Present(rackspaceDomain, "", "112233445566==")
+ assert.NoError(t, err)
+}
+
+func TestRackspaceCleanUp(t *testing.T) {
+ if !rackspaceLiveTest {
+ t.Skip("skipping live test")
+ }
+
+ time.Sleep(time.Second * 15)
+
+ liveRackspaceEnv()
+ provider, err := NewDNSProvider()
+ assert.NoError(t, err)
+
+ err = provider.CleanUp(rackspaceDomain, "", "112233445566==")
+ assert.NoError(t, err)
+}
+
+func TestMain(m *testing.M) {
+ identityAPI, dnsAPI := startTestServers()
+ defer closeTestServers(identityAPI, dnsAPI)
+ os.Exit(m.Run())
+}
+
+var jsonMap = map[string]string{
+ `{"auth":{"RAX-KSKEY:apiKeyCredentials":{"username":"testUser","apiKey":"testKey"}}}`: `{"access":{"token":{"id":"testToken","expires":"1970-01-01T00:00:00.000Z","tenant":{"id":"123456","name":"123456"},"RAX-AUTH:authenticatedBy":["APIKEY"]},"serviceCatalog":[{"type":"rax:dns","endpoints":[{"publicURL":"https://dns.api.rackspacecloud.com/v1.0/123456","tenantId":"123456"}],"name":"cloudDNS"}],"user":{"id":"fakeUseID","name":"testUser"}}}`,
+ "zoneDetails": `{"domains":[{"name":"example.com","id":112233,"emailAddress":"hostmaster@example.com","updated":"1970-01-01T00:00:00.000+0000","created":"1970-01-01T00:00:00.000+0000"}],"totalEntries":1}`,
+ `{"records":[{"name":"_acme-challenge.example.com","type":"TXT","data":"pW9ZKG0xz_PCriK-nCMOjADy9eJcgGWIzkkj2fN4uZM","ttl":300}]}`: `{"request":"{\"records\":[{\"name\":\"_acme-challenge.example.com\",\"type\":\"TXT\",\"data\":\"pW9ZKG0xz_PCriK-nCMOjADy9eJcgGWIzkkj2fN4uZM\",\"ttl\":300}]}","status":"RUNNING","verb":"POST","jobId":"00000000-0000-0000-0000-0000000000","callbackUrl":"https://dns.api.rackspacecloud.com/v1.0/123456/status/00000000-0000-0000-0000-0000000000","requestUrl":"https://dns.api.rackspacecloud.com/v1.0/123456/domains/112233/records"}`,
+ "recordDetails": `{"records":[{"name":"_acme-challenge.example.com","id":"TXT-654321","type":"TXT","data":"pW9ZKG0xz_PCriK-nCMOjADy9eJcgGWIzkkj2fN4uZM","ttl":300,"updated":"1970-01-01T00:00:00.000+0000","created":"1970-01-01T00:00:00.000+0000"}]}`,
+ "recordDelete": `{"status":"RUNNING","verb":"DELETE","jobId":"00000000-0000-0000-0000-0000000000","callbackUrl":"https://dns.api.rackspacecloud.com/v1.0/123456/status/00000000-0000-0000-0000-0000000000","requestUrl":"https://dns.api.rackspacecloud.com/v1.0/123456/domains/112233/recordsid=TXT-654321"}`,
+}
diff --git a/vendor/github.com/xenolf/lego/providers/http/memcached/README.md b/vendor/github.com/xenolf/lego/providers/http/memcached/README.md
new file mode 100644
index 000000000..f14d216df
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/providers/http/memcached/README.md
@@ -0,0 +1,15 @@
+# Memcached http provider
+
+Publishes challenges into memcached where they can be retrieved by nginx. Allows
+specifying multiple memcached servers and the responses will be published to all
+of them, making it easier to verify when your domain is hosted on a cluster of
+servers.
+
+Example nginx config:
+
+```
+ location /.well-known/acme-challenge/ {
+ set $memcached_key "$uri";
+ memcached_pass 127.0.0.1:11211;
+ }
+```
diff --git a/vendor/github.com/xenolf/lego/providers/http/memcached/memcached.go b/vendor/github.com/xenolf/lego/providers/http/memcached/memcached.go
new file mode 100644
index 000000000..9c5f6c0b4
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/providers/http/memcached/memcached.go
@@ -0,0 +1,59 @@
+// Package webroot implements a HTTP provider for solving the HTTP-01 challenge using web server's root path.
+package memcached
+
+import (
+ "fmt"
+ "path"
+
+ "github.com/rainycape/memcache"
+ "github.com/xenolf/lego/acme"
+)
+
+// HTTPProvider implements ChallengeProvider for `http-01` challenge
+type MemcachedProvider struct {
+ hosts []string
+}
+
+// NewHTTPProvider returns a HTTPProvider instance with a configured webroot path
+func NewMemcachedProvider(hosts []string) (*MemcachedProvider, error) {
+ if len(hosts) == 0 {
+ return nil, fmt.Errorf("No memcached hosts provided")
+ }
+
+ c := &MemcachedProvider{
+ hosts: hosts,
+ }
+
+ return c, nil
+}
+
+// Present makes the token available at `HTTP01ChallengePath(token)` by creating a file in the given webroot path
+func (w *MemcachedProvider) Present(domain, token, keyAuth string) error {
+ var errs []error
+
+ challengePath := path.Join("/", acme.HTTP01ChallengePath(token))
+ for _, host := range w.hosts {
+ mc, err := memcache.New(host)
+ if err != nil {
+ errs = append(errs, err)
+ continue
+ }
+ mc.Add(&memcache.Item{
+ Key: challengePath,
+ Value: []byte(keyAuth),
+ Expiration: 60,
+ })
+ }
+
+ if len(errs) == len(w.hosts) {
+ return fmt.Errorf("Unable to store key in any of the memcache hosts -> %v", errs)
+ }
+
+ return nil
+}
+
+// CleanUp removes the file created for the challenge
+func (w *MemcachedProvider) CleanUp(domain, token, keyAuth string) error {
+ // Memcached will clean up itself, that's what expiration is for.
+ return nil
+}
diff --git a/vendor/github.com/xenolf/lego/providers/http/memcached/memcached_test.go b/vendor/github.com/xenolf/lego/providers/http/memcached/memcached_test.go
new file mode 100644
index 000000000..287a33304
--- /dev/null
+++ b/vendor/github.com/xenolf/lego/providers/http/memcached/memcached_test.go
@@ -0,0 +1,111 @@
+package memcached
+
+import (
+ "os"
+ "path"
+ "strings"
+ "testing"
+
+ "github.com/rainycape/memcache"
+ "github.com/stretchr/testify/assert"
+ "github.com/xenolf/lego/acme"
+)
+
+var (
+ memcachedHosts []string
+)
+
+const (
+ domain = "lego.test"
+ token = "foo"
+ keyAuth = "bar"
+)
+
+func init() {
+ memcachedHostsStr := os.Getenv("MEMCACHED_HOSTS")
+ if len(memcachedHostsStr) > 0 {
+ memcachedHosts = strings.Split(memcachedHostsStr, ",")
+ }
+}
+
+func TestNewMemcachedProviderEmpty(t *testing.T) {
+ emptyHosts := make([]string, 0)
+ _, err := NewMemcachedProvider(emptyHosts)
+ assert.EqualError(t, err, "No memcached hosts provided")
+}
+
+func TestNewMemcachedProviderValid(t *testing.T) {
+ if len(memcachedHosts) == 0 {
+ t.Skip("Skipping memcached tests")
+ }
+ _, err := NewMemcachedProvider(memcachedHosts)
+ assert.NoError(t, err)
+}
+
+func TestMemcachedPresentSingleHost(t *testing.T) {
+ if len(memcachedHosts) == 0 {
+ t.Skip("Skipping memcached tests")
+ }
+ p, err := NewMemcachedProvider(memcachedHosts[0:1])
+ assert.NoError(t, err)
+
+ challengePath := path.Join("/", acme.HTTP01ChallengePath(token))
+
+ err = p.Present(domain, token, keyAuth)
+ assert.NoError(t, err)
+ mc, err := memcache.New(memcachedHosts[0])
+ assert.NoError(t, err)
+ i, err := mc.Get(challengePath)
+ assert.NoError(t, err)
+ assert.Equal(t, i.Value, []byte(keyAuth))
+}
+
+func TestMemcachedPresentMultiHost(t *testing.T) {
+ if len(memcachedHosts) <= 1 {
+ t.Skip("Skipping memcached multi-host tests")
+ }
+ p, err := NewMemcachedProvider(memcachedHosts)
+ assert.NoError(t, err)
+
+ challengePath := path.Join("/", acme.HTTP01ChallengePath(token))
+
+ err = p.Present(domain, token, keyAuth)
+ assert.NoError(t, err)
+ for _, host := range memcachedHosts {
+ mc, err := memcache.New(host)
+ assert.NoError(t, err)
+ i, err := mc.Get(challengePath)
+ assert.NoError(t, err)
+ assert.Equal(t, i.Value, []byte(keyAuth))
+ }
+}
+
+func TestMemcachedPresentPartialFailureMultiHost(t *testing.T) {
+ if len(memcachedHosts) == 0 {
+ t.Skip("Skipping memcached tests")
+ }
+ hosts := append(memcachedHosts, "5.5.5.5:11211")
+ p, err := NewMemcachedProvider(hosts)
+ assert.NoError(t, err)
+
+ challengePath := path.Join("/", acme.HTTP01ChallengePath(token))
+
+ err = p.Present(domain, token, keyAuth)
+ assert.NoError(t, err)
+ for _, host := range memcachedHosts {
+ mc, err := memcache.New(host)
+ assert.NoError(t, err)
+ i, err := mc.Get(challengePath)
+ assert.NoError(t, err)
+ assert.Equal(t, i.Value, []byte(keyAuth))
+ }
+}
+
+func TestMemcachedCleanup(t *testing.T) {
+ if len(memcachedHosts) == 0 {
+ t.Skip("Skipping memcached tests")
+ }
+ p, err := NewMemcachedProvider(memcachedHosts)
+ assert.NoError(t, err)
+ assert.NoError(t, p.CleanUp(domain, token, keyAuth))
+}