From c4bbb4b819c087d02066fe745d7ca5b60a480d36 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 11 Jun 2018 17:32:50 +0200 Subject: [PATCH] Review DNS providers. (#565) * refactor: review DNS providers. --- platform/config/env/env.go | 27 ++++++ providers/dns/auroradns/auroradns.go | 20 ++-- providers/dns/auroradns/auroradns_test.go | 81 ++++------------ providers/dns/azure/azure.go | 29 +++--- providers/dns/azure/azure_test.go | 15 ++- providers/dns/bluecat/bluecat.go | 36 ++++--- providers/dns/cloudflare/cloudflare.go | 30 +++--- providers/dns/cloudflare/cloudflare_test.go | 25 +++-- providers/dns/cloudxns/cloudxns.go | 11 ++- providers/dns/cloudxns/cloudxns_test.go | 13 ++- providers/dns/digitalocean/digitalocean.go | 10 +- .../dns/digitalocean/digitalocean_test.go | 77 +++++---------- providers/dns/dns_providers_test.go | 12 +-- providers/dns/dnsimple/dnsimple_test.go | 17 ++-- providers/dns/dnsmadeeasy/dnsmadeeasy.go | 14 +-- providers/dns/dnspod/dnspod.go | 12 ++- providers/dns/dnspod/dnspod_test.go | 16 +-- providers/dns/duckdns/duckdns.go | 15 +-- providers/dns/duckdns/duckdns_test.go | 12 ++- providers/dns/dyn/dyn.go | 12 ++- providers/dns/exoscale/exoscale.go | 11 ++- providers/dns/exoscale/exoscale_test.go | 14 ++- providers/dns/fastdns/fastdns.go | 19 ++-- providers/dns/fastdns/fastdns_test.go | 13 ++- providers/dns/gandi/gandi.go | 10 +- providers/dns/gandi/gandi_test.go | 48 +++++---- providers/dns/gandiv5/gandiv5.go | 10 +- providers/dns/gandiv5/gandiv5_test.go | 49 +++++----- providers/dns/gcloud/googlecloud.go | 17 ++-- providers/dns/gcloud/googlecloud_test.go | 13 ++- providers/dns/glesys/glesys.go | 11 ++- providers/dns/godaddy/godaddy.go | 20 ++-- .../lightsail/lightsail_integration_test.go | 19 ++-- providers/dns/lightsail/lightsail_test.go | 5 +- providers/dns/linode/linode.go | 11 ++- providers/dns/linode/linode_test.go | 66 +++++++------ providers/dns/namecheap/namecheap.go | 13 ++- providers/dns/namecheap/namecheap_test.go | 35 ++++--- providers/dns/namedotcom/namedotcom.go | 13 ++- providers/dns/namedotcom/namedotcom_test.go | 4 +- providers/dns/ns1/ns1.go | 11 ++- providers/dns/ns1/ns1_test.go | 10 +- providers/dns/otc/otc.go | 19 ++-- providers/dns/otc/otc_test.go | 15 +-- providers/dns/ovh/ovh.go | 18 ++-- providers/dns/ovh/ovh_test.go | 97 +++++++++++++------ providers/dns/pdns/pdns.go | 14 ++- providers/dns/pdns/pdns_test.go | 18 ++-- providers/dns/rackspace/rackspace.go | 11 ++- providers/dns/rfc2136/rfc2136.go | 8 +- providers/dns/rfc2136/rfc2136_test.go | 86 +++++++--------- .../dns/route53/route53_integration_test.go | 9 +- providers/dns/route53/route53_test.go | 12 +-- providers/dns/vultr/vultr.go | 10 +- providers/dns/vultr/vultr_test.go | 8 +- 55 files changed, 671 insertions(+), 560 deletions(-) create mode 100644 platform/config/env/env.go diff --git a/platform/config/env/env.go b/platform/config/env/env.go new file mode 100644 index 00000000..1267def7 --- /dev/null +++ b/platform/config/env/env.go @@ -0,0 +1,27 @@ +package env + +import ( + "fmt" + "os" + "strings" +) + +// Get environment variables +func Get(names ...string) (map[string]string, error) { + values := map[string]string{} + + var missingEnvVars []string + for _, envVar := range names { + value := os.Getenv(envVar) + if value == "" { + missingEnvVars = append(missingEnvVars, envVar) + } + values[envVar] = value + } + + if len(missingEnvVars) > 0 { + return nil, fmt.Errorf("some credentials information are missing: %s", strings.Join(missingEnvVars, ",")) + } + + return values, nil +} diff --git a/providers/dns/auroradns/auroradns.go b/providers/dns/auroradns/auroradns.go index 03d31754..d7c09603 100644 --- a/providers/dns/auroradns/auroradns.go +++ b/providers/dns/auroradns/auroradns.go @@ -9,6 +9,7 @@ import ( "github.com/edeckers/auroradnsclient/records" "github.com/edeckers/auroradnsclient/zones" "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" ) // DNSProvider describes a provider for AuroraDNS @@ -22,20 +23,23 @@ type DNSProvider struct { // 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" + values, err := env.Get("AURORA_USER_ID", "AURORA_KEY") + if err != nil { + return nil, fmt.Errorf("AuroraDNS: %v", err) } - return NewDNSProviderCredentials(endpoint, userID, key) + endpoint := os.Getenv("AURORA_ENDPOINT") + + return NewDNSProviderCredentials(endpoint, values["AURORA_USER_ID"], values["AURORA_KEY"]) } // NewDNSProviderCredentials uses the supplied credentials to return a // DNSProvider instance configured for AuroraDNS. func NewDNSProviderCredentials(baseURL string, userID string, key string) (*DNSProvider, error) { + if baseURL == "" { + baseURL = "https://api.auroradns.eu" + } + client, err := auroradnsclient.NewAuroraDNSClient(baseURL, userID, key) if err != nil { return nil, err @@ -69,7 +73,7 @@ func (provider *DNSProvider) Present(domain, token, keyAuth string) error { 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) } // 1. Aurora will happily create the TXT record when it is provided a fqdn, diff --git a/providers/dns/auroradns/auroradns_test.go b/providers/dns/auroradns/auroradns_test.go index f93bc95d..d8ffd82b 100644 --- a/providers/dns/auroradns/auroradns_test.go +++ b/providers/dns/auroradns/auroradns_test.go @@ -6,6 +6,9 @@ import ( "net/http" "net/http/httptest" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var fakeAuroraDNSUserID = "asdf1234" @@ -26,28 +29,13 @@ func TestAuroraDNSPresent(t *testing.T) { 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) - } + assert.Equal(t, http.MethodPost, r.Method, "method") + assert.Equal(t, "/zones/c56a4180-65aa-42ec-a945-5fd21dec0538/records", r.URL.Path, "Path") + assert.Equal(t, "application/json", r.Header.Get("Content-Type"), "Content-Type") 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) - } + require.NoError(t, err, "reading request body") + assert.Equal(t, `{"type":"TXT","name":"_acme-challenge","content":"w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI","ttl":300}`, string(reqBody)) w.WriteHeader(http.StatusCreated) fmt.Fprintf(w, `{ @@ -61,22 +49,13 @@ func TestAuroraDNSPresent(t *testing.T) { 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) - } + require.NoError(t, err) + require.NotNil(t, auroraProvider) err = auroraProvider.Present("example.com", "", "foobar") - if err != nil { - t.Fatalf("Expected no error creating TXT record, but got: %v", err) - } + require.NoError(t, err, "fail to create TXT record") - if !requestReceived { - t.Error("Expected request to be received by mock backend, but it wasn't") - } + assert.True(t, requestReceived, "Expected request to be received by mock backend, but it wasn't") } func TestAuroraDNSCleanUp(t *testing.T) { @@ -105,18 +84,9 @@ func TestAuroraDNSCleanUp(t *testing.T) { 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) - } + assert.Equal(t, http.MethodDelete, r.Method, "method") + assert.Equal(t, "/zones/c56a4180-65aa-42ec-a945-5fd21dec0538/records/ec56a4180-65aa-42ec-a945-5fd21dec0538", r.URL.Path, "Path") + assert.Equal(t, "application/json", r.Header.Get("Content-Type"), "Content-Type") w.WriteHeader(http.StatusCreated) fmt.Fprintf(w, `{}`) @@ -124,25 +94,14 @@ func TestAuroraDNSCleanUp(t *testing.T) { 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) - } + require.NoError(t, err) + require.NotNil(t, auroraProvider) err = auroraProvider.Present("example.com", "", "foobar") - if err != nil { - t.Fatalf("Expected no error creating TXT record, but got: %v", err) - } + require.NoError(t, err, "fail to create TXT record") err = auroraProvider.CleanUp("example.com", "", "foobar") - if err != nil { - t.Fatalf("Expected no error removing TXT record, but got: %v", err) - } + require.NoError(t, err, "fail to remove TXT record") - if !requestReceived { - t.Error("Expected request to be received by mock backend, but it wasn't") - } + assert.True(t, requestReceived, "Expected request to be received by mock backend, but it wasn't") } diff --git a/providers/dns/azure/azure.go b/providers/dns/azure/azure.go index b26d8526..abd342eb 100644 --- a/providers/dns/azure/azure.go +++ b/providers/dns/azure/azure.go @@ -5,8 +5,8 @@ package azure import ( "context" + "errors" "fmt" - "os" "strings" "time" @@ -16,6 +16,7 @@ import ( "github.com/Azure/go-autorest/autorest/azure" "github.com/Azure/go-autorest/autorest/to" "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" ) // DNSProvider is an implementation of the acme.ChallengeProvider interface @@ -32,25 +33,25 @@ type DNSProvider struct { // 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") - 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) + values, err := env.Get("AZURE_CLIENT_ID", "AZURE_CLIENT_SECRET", "AZURE_SUBSCRIPTION_ID", "AZURE_TENANT_ID", "AZURE_RESOURCE_GROUP") + if err != nil { + return nil, fmt.Errorf("Azure: %v", err) + } + + return NewDNSProviderCredentials( + values["AZURE_CLIENT_ID"], + values["AZURE_CLIENT_SECRET"], + values["AZURE_SUBSCRIPTION_ID"], + values["AZURE_TENANT_ID"], + values["AZURE_RESOURCE_GROUP"], + ) } // 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 == "" { - 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 nil, errors.New("Azure: some credentials information are missing") } return &DNSProvider{ diff --git a/providers/dns/azure/azure_test.go b/providers/dns/azure/azure_test.go index 3eeb10fc..212d8566 100644 --- a/providers/dns/azure/azure_test.go +++ b/providers/dns/azure/azure_test.go @@ -30,7 +30,7 @@ func init() { } } -func restoreAzureEnv() { +func restoreEnv() { os.Setenv("AZURE_CLIENT_ID", azureClientID) os.Setenv("AZURE_SUBSCRIPTION_ID", azureSubscriptionID) } @@ -39,27 +39,32 @@ func TestNewDNSProviderValid(t *testing.T) { if !azureLiveTest { t.Skip("skipping live test (requires credentials)") } + + defer restoreEnv() 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)") } + + defer restoreEnv() os.Setenv("AZURE_CLIENT_ID", "other") + _, err := NewDNSProvider() assert.NoError(t, err) - restoreAzureEnv() } func TestNewDNSProviderMissingCredErr(t *testing.T) { + defer restoreEnv() os.Setenv("AZURE_SUBSCRIPTION_ID", "") + _, err := NewDNSProvider() - assert.EqualError(t, err, "Azure configuration missing: AZURE_CLIENT_ID,AZURE_CLIENT_SECRET,AZURE_SUBSCRIPTION_ID,AZURE_TENANT_ID,AZURE_RESOURCE_GROUP") - restoreAzureEnv() + assert.EqualError(t, err, "Azure: some credentials information are missing: AZURE_CLIENT_ID,AZURE_CLIENT_SECRET,AZURE_SUBSCRIPTION_ID,AZURE_TENANT_ID,AZURE_RESOURCE_GROUP") } func TestLiveAzurePresent(t *testing.T) { diff --git a/providers/dns/bluecat/bluecat.go b/providers/dns/bluecat/bluecat.go index b0374b7e..83a502ca 100644 --- a/providers/dns/bluecat/bluecat.go +++ b/providers/dns/bluecat/bluecat.go @@ -6,16 +6,15 @@ import ( "bytes" "encoding/json" "fmt" + "io/ioutil" "net/http" - "os" "regexp" "strconv" "strings" "time" - "io/ioutil" - "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" ) const bluecatURLTemplate = "%s/Services/REST/v1" @@ -51,29 +50,42 @@ type DNSProvider struct { // and external DNS View Name must be passed in BLUECAT_CONFIG_NAME and // BLUECAT_DNS_VIEW func NewDNSProvider() (*DNSProvider, error) { - server := os.Getenv("BLUECAT_SERVER_URL") - userName := os.Getenv("BLUECAT_USER_NAME") - password := os.Getenv("BLUECAT_PASSWORD") - configName := os.Getenv("BLUECAT_CONFIG_NAME") - dnsView := os.Getenv("BLUECAT_DNS_VIEW") - httpClient := http.Client{Timeout: 30 * time.Second} - return NewDNSProviderCredentials(server, userName, password, configName, dnsView, httpClient) + values, err := env.Get("BLUECAT_SERVER_URL", "BLUECAT_USER_NAME", "BLUECAT_CONFIG_NAME", "BLUECAT_CONFIG_NAME", "BLUECAT_DNS_VIEW") + if err != nil { + return nil, fmt.Errorf("BlueCat: %v", err) + } + + httpClient := &http.Client{Timeout: 30 * time.Second} + + return NewDNSProviderCredentials( + values["BLUECAT_SERVER_URL"], + values["BLUECAT_USER_NAME"], + values["BLUECAT_PASSWORD"], + values["BLUECAT_CONFIG_NAME"], + values["BLUECAT_DNS_VIEW"], + httpClient, + ) } // NewDNSProviderCredentials uses the supplied credentials to return a // DNSProvider instance configured for Bluecat DNS. -func NewDNSProviderCredentials(server, userName, password, configName, dnsView string, httpClient http.Client) (*DNSProvider, error) { +func NewDNSProviderCredentials(server, userName, password, configName, dnsView string, httpClient *http.Client) (*DNSProvider, error) { if server == "" || userName == "" || password == "" || configName == "" || dnsView == "" { return nil, fmt.Errorf("Bluecat credentials missing") } + client := http.DefaultClient + if httpClient != nil { + client = httpClient + } + return &DNSProvider{ baseURL: fmt.Sprintf(bluecatURLTemplate, server), userName: userName, password: password, configName: configName, dnsView: dnsView, - httpClient: http.DefaultClient, + httpClient: client, }, nil } diff --git a/providers/dns/cloudflare/cloudflare.go b/providers/dns/cloudflare/cloudflare.go index 9b6556a6..85bbe498 100644 --- a/providers/dns/cloudflare/cloudflare.go +++ b/providers/dns/cloudflare/cloudflare.go @@ -5,15 +5,15 @@ package cloudflare import ( "bytes" "encoding/json" + "errors" "fmt" "io" "io/ioutil" "net/http" - "os" - "strings" "time" "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" ) // CloudFlareAPIURL represents the API endpoint to call. @@ -30,23 +30,19 @@ type DNSProvider struct { // Credentials must be passed in the environment variables: CLOUDFLARE_EMAIL // and CLOUDFLARE_API_KEY. func NewDNSProvider() (*DNSProvider, error) { - email := os.Getenv("CLOUDFLARE_EMAIL") - key := os.Getenv("CLOUDFLARE_API_KEY") - return NewDNSProviderCredentials(email, key) + values, err := env.Get("CLOUDFLARE_EMAIL", "CLOUDFLARE_API_KEY") + if err != nil { + return nil, fmt.Errorf("CloudFlare: %v", err) + } + + return NewDNSProviderCredentials(values["CLOUDFLARE_EMAIL"], values["CLOUDFLARE_API_KEY"]) } // NewDNSProviderCredentials uses the supplied credentials to return a // DNSProvider instance configured for cloudflare. func NewDNSProviderCredentials(email, key string) (*DNSProvider, error) { if email == "" || key == "" { - 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 nil, errors.New("CloudFlare: some credentials information are missing") } return &DNSProvider{ @@ -63,7 +59,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, _ := acme.DNS01Record(domain, keyAuth) + fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) zoneID, err := c.getHostedZoneID(fqdn) if err != nil { return err @@ -73,7 +69,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error { Type: "TXT", Name: acme.UnFqdn(fqdn), Content: value, - TTL: 120, + TTL: ttl, } body, err := json.Marshal(rec) @@ -122,7 +118,7 @@ func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) { } if len(hostedZone) != 1 { - return "", fmt.Errorf("Zone %s not found in CloudFlare for domain %s", authZone, fqdn) + return "", fmt.Errorf("zone %s not found in CloudFlare for domain %s", authZone, fqdn) } return hostedZone[0].ID, nil @@ -184,7 +180,7 @@ 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 Cloudflare API -> %v", err) + return nil, fmt.Errorf("error querying Cloudflare API -> %v", err) } defer resp.Body.Close() diff --git a/providers/dns/cloudflare/cloudflare_test.go b/providers/dns/cloudflare/cloudflare_test.go index 9fab1622..18affcd0 100644 --- a/providers/dns/cloudflare/cloudflare_test.go +++ b/providers/dns/cloudflare/cloudflare_test.go @@ -24,7 +24,7 @@ func init() { } } -func restoreCloudFlareEnv() { +func restoreEnv() { os.Setenv("CLOUDFLARE_EMAIL", cflareEmail) os.Setenv("CLOUDFLARE_API_KEY", cflareAPIKey) } @@ -32,32 +32,37 @@ func restoreCloudFlareEnv() { func TestNewDNSProviderValid(t *testing.T) { os.Setenv("CLOUDFLARE_EMAIL", "") os.Setenv("CLOUDFLARE_API_KEY", "") + defer restoreEnv() + _, err := NewDNSProviderCredentials("123", "123") + assert.NoError(t, err) - restoreCloudFlareEnv() } func TestNewDNSProviderValidEnv(t *testing.T) { + defer restoreEnv() os.Setenv("CLOUDFLARE_EMAIL", "test@example.com") os.Setenv("CLOUDFLARE_API_KEY", "123") + _, err := NewDNSProvider() assert.NoError(t, err) - restoreCloudFlareEnv() } func TestNewDNSProviderMissingCredErr(t *testing.T) { + defer restoreEnv() os.Setenv("CLOUDFLARE_EMAIL", "") os.Setenv("CLOUDFLARE_API_KEY", "") + _, err := NewDNSProvider() - assert.EqualError(t, err, "CloudFlare credentials missing: CLOUDFLARE_EMAIL,CLOUDFLARE_API_KEY") - restoreCloudFlareEnv() + assert.EqualError(t, err, "CloudFlare: some credentials information are missing: CLOUDFLARE_EMAIL,CLOUDFLARE_API_KEY") } -func TestNewDNSProviderMissingCredErrSingle(t *testing.T){ - os.Setenv("CLOUDFLARE_EMAIL", "awesome@possum.com") - _, err:= NewDNSProvider() - assert.EqualError(t, err, "CloudFlare credentials missing: CLOUDFLARE_API_KEY") - restoreCloudFlareEnv() +func TestNewDNSProviderMissingCredErrSingle(t *testing.T) { + defer restoreEnv() + os.Setenv("CLOUDFLARE_EMAIL", "awesome@possum.com") + + _, err := NewDNSProvider() + assert.EqualError(t, err, "CloudFlare: some credentials information are missing: CLOUDFLARE_API_KEY") } func TestCloudFlarePresent(t *testing.T) { diff --git a/providers/dns/cloudxns/cloudxns.go b/providers/dns/cloudxns/cloudxns.go index 1204af14..26655552 100644 --- a/providers/dns/cloudxns/cloudxns.go +++ b/providers/dns/cloudxns/cloudxns.go @@ -9,11 +9,11 @@ import ( "encoding/json" "fmt" "net/http" - "os" "strconv" "time" "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" ) const cloudXNSBaseURL = "https://www.cloudxns.net/api2/" @@ -28,9 +28,12 @@ type DNSProvider struct { // Credentials must be passed in the environment variables: CLOUDXNS_API_KEY // and CLOUDXNS_SECRET_KEY. func NewDNSProvider() (*DNSProvider, error) { - apiKey := os.Getenv("CLOUDXNS_API_KEY") - secretKey := os.Getenv("CLOUDXNS_SECRET_KEY") - return NewDNSProviderCredentials(apiKey, secretKey) + values, err := env.Get("CLOUDXNS_API_KEY", "CLOUDXNS_SECRET_KEY") + if err != nil { + return nil, fmt.Errorf("CloudXNS: %v", err) + } + + return NewDNSProviderCredentials(values["CLOUDXNS_API_KEY"], values["CLOUDXNS_SECRET_KEY"]) } // NewDNSProviderCredentials uses the supplied credentials to return a diff --git a/providers/dns/cloudxns/cloudxns_test.go b/providers/dns/cloudxns/cloudxns_test.go index ee08df2b..aa1ddf27 100644 --- a/providers/dns/cloudxns/cloudxns_test.go +++ b/providers/dns/cloudxns/cloudxns_test.go @@ -24,33 +24,36 @@ func init() { } } -func restoreCloudXNSEnv() { +func restoreEnv() { os.Setenv("CLOUDXNS_API_KEY", cxAPIKey) os.Setenv("CLOUDXNS_SECRET_KEY", cxSecretKey) } func TestNewDNSProviderValid(t *testing.T) { + defer restoreEnv() os.Setenv("CLOUDXNS_API_KEY", "") os.Setenv("CLOUDXNS_SECRET_KEY", "") + _, err := NewDNSProviderCredentials("123", "123") assert.NoError(t, err) - restoreCloudXNSEnv() } func TestNewDNSProviderValidEnv(t *testing.T) { + defer restoreEnv() os.Setenv("CLOUDXNS_API_KEY", "123") os.Setenv("CLOUDXNS_SECRET_KEY", "123") + _, err := NewDNSProvider() assert.NoError(t, err) - restoreCloudXNSEnv() } func TestNewDNSProviderMissingCredErr(t *testing.T) { + defer restoreEnv() os.Setenv("CLOUDXNS_API_KEY", "") os.Setenv("CLOUDXNS_SECRET_KEY", "") + _, err := NewDNSProvider() - assert.EqualError(t, err, "CloudXNS credentials missing") - restoreCloudXNSEnv() + assert.EqualError(t, err, "CloudXNS: some credentials information are missing: CLOUDXNS_API_KEY,CLOUDXNS_SECRET_KEY") } func TestCloudXNSPresent(t *testing.T) { diff --git a/providers/dns/digitalocean/digitalocean.go b/providers/dns/digitalocean/digitalocean.go index ef749e6f..9abb97d8 100644 --- a/providers/dns/digitalocean/digitalocean.go +++ b/providers/dns/digitalocean/digitalocean.go @@ -7,11 +7,11 @@ import ( "encoding/json" "fmt" "net/http" - "os" "sync" "time" "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" ) // DNSProvider is an implementation of the acme.ChallengeProvider interface @@ -32,8 +32,12 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { // Ocean. Credentials must be passed in the environment variable: // DO_AUTH_TOKEN. func NewDNSProvider() (*DNSProvider, error) { - apiAuthToken := os.Getenv("DO_AUTH_TOKEN") - return NewDNSProviderCredentials(apiAuthToken) + values, err := env.Get("DO_AUTH_TOKEN") + if err != nil { + return nil, fmt.Errorf("DigitalOcean: %v", err) + } + + return NewDNSProviderCredentials(values["DO_AUTH_TOKEN"]) } // NewDNSProviderCredentials uses the supplied credentials to return a diff --git a/providers/dns/digitalocean/digitalocean_test.go b/providers/dns/digitalocean/digitalocean_test.go index c2bef08b..7a77f894 100644 --- a/providers/dns/digitalocean/digitalocean_test.go +++ b/providers/dns/digitalocean/digitalocean_test.go @@ -6,6 +6,9 @@ import ( "net/http" "net/http/httptest" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var fakeDigitalOceanAuth = "asdf1234" @@ -16,26 +19,14 @@ func TestDigitalOceanPresent(t *testing.T) { mock := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 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, "/v2/domains/example.com/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) - } - if got, want := r.Header.Get("Authorization"), "Bearer asdf1234"; got != want { - t.Errorf("Expected Authorization to be '%s' but got '%s'", want, got) - } + assert.Equal(t, http.MethodPost, r.Method, "method") + assert.Equal(t, "/v2/domains/example.com/records", r.URL.Path, "Path") + assert.Equal(t, "application/json", r.Header.Get("Content-Type"), "Content-Type") + assert.Equal(t, "Bearer asdf1234", r.Header.Get("Authorization"), "Authorization") 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.example.com.","data":"w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI","ttl":30}`; got != want { - t.Errorf("Expected body data to be: `%s` but got `%s`", want, got) - } + require.NoError(t, err, "reading request body") + assert.Equal(t, `{"type":"TXT","name":"_acme-challenge.example.com.","data":"w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI","ttl":30}`, string(reqBody)) w.WriteHeader(http.StatusCreated) fmt.Fprintf(w, `{ @@ -54,20 +45,13 @@ func TestDigitalOceanPresent(t *testing.T) { digitalOceanBaseURL = mock.URL doprov, err := NewDNSProviderCredentials(fakeDigitalOceanAuth) - if doprov == nil { - t.Fatal("Expected non-nil DigitalOcean provider, but was nil") - } - if err != nil { - t.Fatalf("Expected no error creating provider, but got: %v", err) - } + require.NoError(t, err) + require.NotNil(t, doprov) err = doprov.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") - } + require.NoError(t, err, "fail to create TXT record") + + assert.True(t, requestReceived, "Expected request to be received by mock backend, but it wasn't") } func TestDigitalOceanCleanUp(t *testing.T) { @@ -76,19 +60,11 @@ func TestDigitalOceanCleanUp(t *testing.T) { mock := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 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, "/v2/domains/example.com/records/1234567"; got != want { - t.Errorf("Expected path to be '%s' but got '%s'", want, got) - } + assert.Equal(t, http.MethodDelete, r.Method, "method") + assert.Equal(t, "/v2/domains/example.com/records/1234567", r.URL.Path, "Path") // NOTE: Even though the body is empty, DigitalOcean API docs still show setting this Content-Type... - 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) - } - if got, want := r.Header.Get("Authorization"), "Bearer asdf1234"; got != want { - t.Errorf("Expected Authorization to be '%s' but got '%s'", want, got) - } + assert.Equal(t, "application/json", r.Header.Get("Content-Type"), "Content-Type") + assert.Equal(t, "Bearer asdf1234", r.Header.Get("Authorization"), "Authorization") w.WriteHeader(http.StatusNoContent) })) @@ -96,22 +72,15 @@ func TestDigitalOceanCleanUp(t *testing.T) { digitalOceanBaseURL = mock.URL doprov, err := NewDNSProviderCredentials(fakeDigitalOceanAuth) - if doprov == nil { - t.Fatal("Expected non-nil DigitalOcean provider, but was nil") - } - if err != nil { - t.Fatalf("Expected no error creating provider, but got: %v", err) - } + require.NoError(t, err) + require.NotNil(t, doprov) doprov.recordIDsMu.Lock() doprov.recordIDs["_acme-challenge.example.com."] = 1234567 doprov.recordIDsMu.Unlock() err = doprov.CleanUp("example.com", "", "") - 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") - } + require.NoError(t, err, "fail to remove TXT record") + + assert.True(t, requestReceived, "Expected request to be received by mock backend, but it wasn't") } diff --git a/providers/dns/dns_providers_test.go b/providers/dns/dns_providers_test.go index 3f87ffd3..29c366c8 100644 --- a/providers/dns/dns_providers_test.go +++ b/providers/dns/dns_providers_test.go @@ -2,7 +2,6 @@ package dns import ( "os" - "reflect" "testing" "github.com/stretchr/testify/assert" @@ -25,23 +24,24 @@ func restoreExoscaleEnv() { } func TestKnownDNSProviderSuccess(t *testing.T) { + defer restoreExoscaleEnv() os.Setenv("EXOSCALE_API_KEY", "abc") os.Setenv("EXOSCALE_API_SECRET", "123") + provider, err := NewDNSChallengeProviderByName("exoscale") assert.NoError(t, err) assert.NotNil(t, provider) - if reflect.TypeOf(provider) != reflect.TypeOf(&exoscale.DNSProvider{}) { - t.Errorf("Not loaded correct DNS proviver: %v is not *exoscale.DNSProvider", reflect.TypeOf(provider)) - } - restoreExoscaleEnv() + + assert.IsType(t, &exoscale.DNSProvider{}, provider, "Not loaded correct DNS provider") } func TestKnownDNSProviderError(t *testing.T) { + defer restoreExoscaleEnv() os.Setenv("EXOSCALE_API_KEY", "") os.Setenv("EXOSCALE_API_SECRET", "") + _, err := NewDNSChallengeProviderByName("exoscale") assert.Error(t, err) - restoreExoscaleEnv() } func TestUnknownDNSProvider(t *testing.T) { diff --git a/providers/dns/dnsimple/dnsimple_test.go b/providers/dns/dnsimple/dnsimple_test.go index 0d44410f..048fe217 100644 --- a/providers/dns/dnsimple/dnsimple_test.go +++ b/providers/dns/dnsimple/dnsimple_test.go @@ -31,7 +31,7 @@ func init() { } } -func restoreDNSimpleEnv() { +func restoreEnv() { os.Setenv("DNSIMPLE_OAUTH_TOKEN", dnsimpleOauthToken) os.Setenv("DNSIMPLE_BASE_URL", dnsimpleBaseURL) } @@ -41,9 +41,9 @@ func restoreDNSimpleEnv() { // func TestNewDNSProviderValid(t *testing.T) { - defer restoreDNSimpleEnv() - + defer restoreEnv() os.Setenv("DNSIMPLE_OAUTH_TOKEN", "123") + provider, err := NewDNSProvider() assert.NotNil(t, provider) @@ -52,10 +52,10 @@ func TestNewDNSProviderValid(t *testing.T) { } func TestNewDNSProviderValidWithBaseUrl(t *testing.T) { - defer restoreDNSimpleEnv() - + defer restoreEnv() os.Setenv("DNSIMPLE_OAUTH_TOKEN", "123") os.Setenv("DNSIMPLE_BASE_URL", "https://api.dnsimple.test") + provider, err := NewDNSProvider() assert.NotNil(t, provider) @@ -65,11 +65,8 @@ func TestNewDNSProviderValidWithBaseUrl(t *testing.T) { } func TestNewDNSProviderInvalidWithMissingOauthToken(t *testing.T) { - if dnsimpleLiveTest { - t.Skip("skipping test in live mode") - } - - defer restoreDNSimpleEnv() + defer restoreEnv() + os.Setenv("DNSIMPLE_OAUTH_TOKEN", "") provider, err := NewDNSProvider() diff --git a/providers/dns/dnsmadeeasy/dnsmadeeasy.go b/providers/dns/dnsmadeeasy/dnsmadeeasy.go index 11b1ac44..2083cdc5 100644 --- a/providers/dns/dnsmadeeasy/dnsmadeeasy.go +++ b/providers/dns/dnsmadeeasy/dnsmadeeasy.go @@ -15,6 +15,7 @@ import ( "time" "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" ) // DNSProvider is an implementation of the acme.ChallengeProvider interface that uses @@ -45,20 +46,19 @@ type Record struct { // Credentials must be passed in the environment variables: DNSMADEEASY_API_KEY // and DNSMADEEASY_API_SECRET. func NewDNSProvider() (*DNSProvider, error) { - dnsmadeeasyAPIKey := os.Getenv("DNSMADEEASY_API_KEY") - dnsmadeeasyAPISecret := os.Getenv("DNSMADEEASY_API_SECRET") - dnsmadeeasySandbox := os.Getenv("DNSMADEEASY_SANDBOX") + values, err := env.Get("DNSMADEEASY_API_KEY", "DNSMADEEASY_API_SECRET") + if err != nil { + return nil, fmt.Errorf("DNSMadeEasy: %v", err) + } var baseURL string - - sandbox, _ := strconv.ParseBool(dnsmadeeasySandbox) - if sandbox { + if sandbox, _ := strconv.ParseBool(os.Getenv("DNSMADEEASY_SANDBOX")); sandbox { baseURL = "https://api.sandbox.dnsmadeeasy.com/V2.0" } else { baseURL = "https://api.dnsmadeeasy.com/V2.0" } - return NewDNSProviderCredentials(baseURL, dnsmadeeasyAPIKey, dnsmadeeasyAPISecret) + return NewDNSProviderCredentials(baseURL, values["DNSMADEEASY_API_KEY"], values["DNSMADEEASY_API_SECRET"]) } // NewDNSProviderCredentials uses the supplied credentials to return a diff --git a/providers/dns/dnspod/dnspod.go b/providers/dns/dnspod/dnspod.go index 0ce08a8b..bfbeee62 100644 --- a/providers/dns/dnspod/dnspod.go +++ b/providers/dns/dnspod/dnspod.go @@ -4,11 +4,11 @@ package dnspod import ( "fmt" - "os" "strings" "github.com/decker502/dnspod-go" "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" ) // DNSProvider is an implementation of the acme.ChallengeProvider interface. @@ -19,8 +19,12 @@ type DNSProvider struct { // 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) + values, err := env.Get("DNSPOD_API_KEY") + if err != nil { + return nil, fmt.Errorf("DNSPod: %v", err) + } + + return NewDNSProviderCredentials(values["DNSPOD_API_KEY"]) } // NewDNSProviderCredentials uses the supplied credentials to return a @@ -95,7 +99,7 @@ func (c *DNSProvider) getHostedZone(domain string) (string, string, error) { } if hostedZone.ID == 0 { - return "", "", fmt.Errorf("Zone %s not found in dnspod for domain %s", authZone, domain) + return "", "", fmt.Errorf("zone %s not found in dnspod for domain %s", authZone, domain) } diff --git a/providers/dns/dnspod/dnspod_test.go b/providers/dns/dnspod/dnspod_test.go index 3311eb0a..06ecf3c7 100644 --- a/providers/dns/dnspod/dnspod_test.go +++ b/providers/dns/dnspod/dnspod_test.go @@ -1,10 +1,11 @@ package dnspod import ( - "github.com/stretchr/testify/assert" "os" "testing" "time" + + "github.com/stretchr/testify/assert" ) var ( @@ -21,28 +22,31 @@ func init() { } } -func restorednspodEnv() { +func restoreEnv() { os.Setenv("DNSPOD_API_KEY", dnspodAPIKey) } func TestNewDNSProviderValid(t *testing.T) { + defer restoreEnv() os.Setenv("DNSPOD_API_KEY", "") + _, err := NewDNSProviderCredentials("123") assert.NoError(t, err) - restorednspodEnv() } func TestNewDNSProviderValidEnv(t *testing.T) { + defer restoreEnv() os.Setenv("DNSPOD_API_KEY", "123") + _, err := NewDNSProvider() assert.NoError(t, err) - restorednspodEnv() } func TestNewDNSProviderMissingCredErr(t *testing.T) { + defer restoreEnv() os.Setenv("DNSPOD_API_KEY", "") + _, err := NewDNSProvider() - assert.EqualError(t, err, "dnspod credentials missing") - restorednspodEnv() + assert.EqualError(t, err, "DNSPod: some credentials information are missing: DNSPOD_API_KEY") } func TestLivednspodPresent(t *testing.T) { diff --git a/providers/dns/duckdns/duckdns.go b/providers/dns/duckdns/duckdns.go index 855838ae..e00f7137 100644 --- a/providers/dns/duckdns/duckdns.go +++ b/providers/dns/duckdns/duckdns.go @@ -1,4 +1,4 @@ -// Package duckdns 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 @@ -6,30 +6,33 @@ import ( "errors" "fmt" "io/ioutil" - "os" "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" ) // DNSProvider adds and removes the record for the DNS challenge type DNSProvider struct { - // The duckdns api token + // The api token token string } // NewDNSProvider returns a new DNS provider using // environment variable DUCKDNS_TOKEN for adding and removing the DNS record. func NewDNSProvider() (*DNSProvider, error) { - duckdnsToken := os.Getenv("DUCKDNS_TOKEN") + values, err := env.Get("DUCKDNS_TOKEN") + if err != nil { + return nil, fmt.Errorf("DuckDNS: %v", err) + } - return NewDNSProviderCredentials(duckdnsToken) + return NewDNSProviderCredentials(values["DUCKDNS_TOKEN"]) } // NewDNSProviderCredentials uses the supplied credentials to return a // DNSProvider instance configured for http://duckdns.org . func NewDNSProviderCredentials(duckdnsToken string) (*DNSProvider, error) { if duckdnsToken == "" { - return nil, errors.New("environment variable DUCKDNS_TOKEN not set") + return nil, errors.New("DuckDNS: credentials missing") } return &DNSProvider{token: duckdnsToken}, nil diff --git a/providers/dns/duckdns/duckdns_test.go b/providers/dns/duckdns/duckdns_test.go index aefc0943..5c184702 100644 --- a/providers/dns/duckdns/duckdns_test.go +++ b/providers/dns/duckdns/duckdns_test.go @@ -22,22 +22,26 @@ func init() { } } -func restoreDuckdnsEnv() { +func restoreEnv() { os.Setenv("DUCKDNS_TOKEN", duckdnsToken) } func TestNewDNSProviderValidEnv(t *testing.T) { + defer restoreEnv() os.Setenv("DUCKDNS_TOKEN", "123") + _, err := NewDNSProvider() assert.NoError(t, err) - restoreDuckdnsEnv() } + func TestNewDNSProviderMissingCredErr(t *testing.T) { + defer restoreEnv() os.Setenv("DUCKDNS_TOKEN", "") + _, err := NewDNSProvider() - assert.EqualError(t, err, "environment variable DUCKDNS_TOKEN not set") - restoreDuckdnsEnv() + assert.EqualError(t, err, "DuckDNS: some credentials information are missing: DUCKDNS_TOKEN") } + func TestLiveDuckdnsPresent(t *testing.T) { if !duckdnsLiveTest { t.Skip("skipping live test") diff --git a/providers/dns/dyn/dyn.go b/providers/dns/dyn/dyn.go index ad65fab6..27e6f7ed 100644 --- a/providers/dns/dyn/dyn.go +++ b/providers/dns/dyn/dyn.go @@ -7,11 +7,11 @@ import ( "encoding/json" "fmt" "net/http" - "os" "strconv" "time" "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" ) var dynBaseURL = "https://api.dynect.net/REST" @@ -43,10 +43,12 @@ type DNSProvider struct { // Credentials must be passed in the environment variables: DYN_CUSTOMER_NAME, // DYN_USER_NAME and DYN_PASSWORD. func NewDNSProvider() (*DNSProvider, error) { - customerName := os.Getenv("DYN_CUSTOMER_NAME") - userName := os.Getenv("DYN_USER_NAME") - password := os.Getenv("DYN_PASSWORD") - return NewDNSProviderCredentials(customerName, userName, password) + values, err := env.Get("DYN_CUSTOMER_NAME", "DYN_USER_NAME", "DYN_PASSWORD") + if err != nil { + return nil, fmt.Errorf("DynDNS: %v", err) + } + + return NewDNSProviderCredentials(values["DYN_CUSTOMER_NAME"], values["DYN_USER_NAME"], values["DYN_PASSWORD"]) } // NewDNSProviderCredentials uses the supplied credentials to return a diff --git a/providers/dns/exoscale/exoscale.go b/providers/dns/exoscale/exoscale.go index 1467e8d7..421a3b40 100644 --- a/providers/dns/exoscale/exoscale.go +++ b/providers/dns/exoscale/exoscale.go @@ -9,6 +9,7 @@ import ( "github.com/exoscale/egoscale" "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" ) // DNSProvider is an implementation of the acme.ChallengeProvider interface. @@ -19,10 +20,13 @@ type DNSProvider struct { // 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") - secret := os.Getenv("EXOSCALE_API_SECRET") + values, err := env.Get("EXOSCALE_API_KEY", "EXOSCALE_API_SECRET") + if err != nil { + return nil, fmt.Errorf("Exoscale: %v", err) + } + endpoint := os.Getenv("EXOSCALE_ENDPOINT") - return NewDNSProviderClient(key, secret, endpoint) + return NewDNSProviderClient(values["EXOSCALE_API_KEY"], values["EXOSCALE_API_SECRET"], endpoint) } // NewDNSProviderClient Uses the supplied parameters to return a DNSProvider instance @@ -31,6 +35,7 @@ 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" } diff --git a/providers/dns/exoscale/exoscale_test.go b/providers/dns/exoscale/exoscale_test.go index 343dd56f..10eed34e 100644 --- a/providers/dns/exoscale/exoscale_test.go +++ b/providers/dns/exoscale/exoscale_test.go @@ -24,32 +24,36 @@ func init() { } } -func restoreExoscaleEnv() { +func restoreEnv() { os.Setenv("EXOSCALE_API_KEY", exoscaleAPIKey) os.Setenv("EXOSCALE_API_SECRET", exoscaleAPISecret) } func TestNewDNSProviderValid(t *testing.T) { + defer restoreEnv() 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) { + defer restoreEnv() 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", "") + defer restoreEnv() + _, err := NewDNSProvider() - assert.EqualError(t, err, "Exoscale credentials missing") - restoreExoscaleEnv() + assert.EqualError(t, err, "Exoscale: some credentials information are missing: EXOSCALE_API_KEY,EXOSCALE_API_SECRET") } func TestExtractRootRecordName(t *testing.T) { diff --git a/providers/dns/fastdns/fastdns.go b/providers/dns/fastdns/fastdns.go index dcbb93e5..78249127 100644 --- a/providers/dns/fastdns/fastdns.go +++ b/providers/dns/fastdns/fastdns.go @@ -2,12 +2,12 @@ package fastdns import ( "fmt" - "os" "reflect" configdns "github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v1" "github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid" "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" ) // DNSProvider is an implementation of the acme.ChallengeProvider interface. @@ -18,19 +18,24 @@ type DNSProvider struct { // NewDNSProvider uses the supplied environment variables to return a DNSProvider instance: // AKAMAI_HOST, AKAMAI_CLIENT_TOKEN, AKAMAI_CLIENT_SECRET, AKAMAI_ACCESS_TOKEN func NewDNSProvider() (*DNSProvider, error) { - host := os.Getenv("AKAMAI_HOST") - clientToken := os.Getenv("AKAMAI_CLIENT_TOKEN") - clientSecret := os.Getenv("AKAMAI_CLIENT_SECRET") - accessToken := os.Getenv("AKAMAI_ACCESS_TOKEN") + values, err := env.Get("AKAMAI_HOST", "AKAMAI_CLIENT_TOKEN", "AKAMAI_CLIENT_SECRET", "AKAMAI_ACCESS_TOKEN") + if err != nil { + return nil, fmt.Errorf("FastDNS: %v", err) + } - return NewDNSProviderClient(host, clientToken, clientSecret, accessToken) + return NewDNSProviderClient( + values["AKAMAI_HOST"], + values["AKAMAI_CLIENT_TOKEN"], + values["AKAMAI_CLIENT_SECRET"], + values["AKAMAI_ACCESS_TOKEN"], + ) } // NewDNSProviderClient uses the supplied parameters to return a DNSProvider instance // configured for FastDNS. func NewDNSProviderClient(host, clientToken, clientSecret, accessToken string) (*DNSProvider, error) { if clientToken == "" || clientSecret == "" || accessToken == "" || host == "" { - return nil, fmt.Errorf("Akamai FastDNS credentials missing") + return nil, fmt.Errorf("FastDNS credentials are missing") } config := edgegrid.Config{ Host: host, diff --git a/providers/dns/fastdns/fastdns_test.go b/providers/dns/fastdns/fastdns_test.go index 2c36f614..ed2b9d7c 100644 --- a/providers/dns/fastdns/fastdns_test.go +++ b/providers/dns/fastdns/fastdns_test.go @@ -29,7 +29,7 @@ func init() { } } -func restoreFastdnsEnv() { +func restoreEnv() { os.Setenv("AKAMAI_HOST", host) os.Setenv("AKAMAI_CLIENT_TOKEN", clientToken) os.Setenv("AKAMAI_CLIENT_SECRET", clientSecret) @@ -37,33 +37,36 @@ func restoreFastdnsEnv() { } func TestNewDNSProviderValid(t *testing.T) { + defer restoreEnv() os.Setenv("AKAMAI_HOST", "") os.Setenv("AKAMAI_CLIENT_TOKEN", "") os.Setenv("AKAMAI_CLIENT_SECRET", "") os.Setenv("AKAMAI_ACCESS_TOKEN", "") + _, err := NewDNSProviderClient("somehost", "someclienttoken", "someclientsecret", "someaccesstoken") assert.NoError(t, err) - restoreFastdnsEnv() } + func TestNewDNSProviderValidEnv(t *testing.T) { + defer restoreEnv() os.Setenv("AKAMAI_HOST", "somehost") os.Setenv("AKAMAI_CLIENT_TOKEN", "someclienttoken") os.Setenv("AKAMAI_CLIENT_SECRET", "someclientsecret") os.Setenv("AKAMAI_ACCESS_TOKEN", "someaccesstoken") + _, err := NewDNSProvider() assert.NoError(t, err) - restoreFastdnsEnv() } func TestNewDNSProviderMissingCredErr(t *testing.T) { + defer restoreEnv() os.Setenv("AKAMAI_HOST", "") os.Setenv("AKAMAI_CLIENT_TOKEN", "") os.Setenv("AKAMAI_CLIENT_SECRET", "") os.Setenv("AKAMAI_ACCESS_TOKEN", "") _, err := NewDNSProvider() - assert.EqualError(t, err, "Akamai FastDNS credentials missing") - restoreFastdnsEnv() + assert.EqualError(t, err, "FastDNS: some credentials information are missing: AKAMAI_HOST,AKAMAI_CLIENT_TOKEN,AKAMAI_CLIENT_SECRET,AKAMAI_ACCESS_TOKEN") } func TestLiveFastdnsPresent(t *testing.T) { diff --git a/providers/dns/gandi/gandi.go b/providers/dns/gandi/gandi.go index 750bc8b4..fa43f581 100644 --- a/providers/dns/gandi/gandi.go +++ b/providers/dns/gandi/gandi.go @@ -9,12 +9,12 @@ import ( "io" "io/ioutil" "net/http" - "os" "strings" "sync" "time" "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" ) // Gandi API reference: http://doc.rpc.gandi.net/index.html @@ -49,8 +49,12 @@ type DNSProvider struct { // NewDNSProvider returns a DNSProvider instance configured for Gandi. // Credentials must be passed in the environment variable: GANDI_API_KEY. func NewDNSProvider() (*DNSProvider, error) { - apiKey := os.Getenv("GANDI_API_KEY") - return NewDNSProviderCredentials(apiKey) + values, err := env.Get("GANDI_API_KEY") + if err != nil { + return nil, fmt.Errorf("GandiDNS: %v", err) + } + + return NewDNSProviderCredentials(values["GANDI_API_KEY"]) } // NewDNSProviderCredentials uses the supplied credentials to return a diff --git a/providers/dns/gandi/gandi_test.go b/providers/dns/gandi/gandi_test.go index 520c7e37..792949b5 100644 --- a/providers/dns/gandi/gandi_test.go +++ b/providers/dns/gandi/gandi_test.go @@ -8,6 +8,8 @@ import ( "regexp" "strings" "testing" + + "github.com/stretchr/testify/require" ) // TestDNSProvider runs Present and CleanUp against a fake Gandi RPC @@ -15,55 +17,49 @@ import ( func TestDNSProvider(t *testing.T) { fakeAPIKey := "123412341234123412341234" fakeKeyAuth := "XXXX" + provider, err := NewDNSProviderCredentials(fakeAPIKey) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + regexpDate, err := regexp.Compile(`\[ACME Challenge [^\]:]*:[^\]]*\]`) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + // start fake RPC server fakeServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Header.Get("Content-Type") != "text/xml" { - t.Fatalf("Content-Type: text/xml header not found") - } + require.Equal(t, "text/xml", r.Header.Get("Content-Type"), "invalid content type") + req, err := ioutil.ReadAll(r.Body) - if err != nil { - t.Fatal(err) - } - req = regexpDate.ReplaceAllLiteral( - req, []byte(`[ACME Challenge 01 Jan 16 00:00 +0000]`)) + require.NoError(t, err) + + req = regexpDate.ReplaceAllLiteral(req, []byte(`[ACME Challenge 01 Jan 16 00:00 +0000]`)) resp, ok := serverResponses[string(req)] - if !ok { - t.Fatalf("Server response for request not found") - } + require.True(t, ok, "Server response for request not found") + _, err = io.Copy(w, strings.NewReader(resp)) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) })) defer fakeServer.Close() + // define function to override findZoneByFqdn with fakeFindZoneByFqdn := func(fqdn string, nameserver []string) (string, error) { return "example.com.", nil } + // override gandi endpoint and findZoneByFqdn function savedEndpoint, savedFindZoneByFqdn := endpoint, findZoneByFqdn defer func() { endpoint, findZoneByFqdn = savedEndpoint, savedFindZoneByFqdn }() + endpoint, findZoneByFqdn = fakeServer.URL+"/", fakeFindZoneByFqdn + // run Present err = provider.Present("abc.def.example.com", "", fakeKeyAuth) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + // run CleanUp err = provider.CleanUp("abc.def.example.com", "", fakeKeyAuth) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } // serverResponses is the XML-RPC Request->Response map used by the diff --git a/providers/dns/gandiv5/gandiv5.go b/providers/dns/gandiv5/gandiv5.go index 4614723a..c0dff4d1 100644 --- a/providers/dns/gandiv5/gandiv5.go +++ b/providers/dns/gandiv5/gandiv5.go @@ -7,12 +7,12 @@ import ( "encoding/json" "fmt" "net/http" - "os" "strings" "sync" "time" "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" ) // Gandi API reference: http://doc.livedns.gandi.net/ @@ -45,8 +45,12 @@ type DNSProvider struct { // NewDNSProvider returns a DNSProvider instance configured for Gandi. // Credentials must be passed in the environment variable: GANDIV5_API_KEY. func NewDNSProvider() (*DNSProvider, error) { - apiKey := os.Getenv("GANDIV5_API_KEY") - return NewDNSProviderCredentials(apiKey) + values, err := env.Get("GANDIV5_API_KEY") + if err != nil { + return nil, fmt.Errorf("GandiDNS: %v", err) + } + + return NewDNSProviderCredentials(values["GANDIV5_API_KEY"]) } // NewDNSProviderCredentials uses the supplied credentials to return a diff --git a/providers/dns/gandiv5/gandiv5_test.go b/providers/dns/gandiv5/gandiv5_test.go index f6fa779f..4ccb0bc7 100644 --- a/providers/dns/gandiv5/gandiv5_test.go +++ b/providers/dns/gandiv5/gandiv5_test.go @@ -8,6 +8,8 @@ import ( "regexp" "strings" "testing" + + "github.com/stretchr/testify/require" ) // TestDNSProvider runs Present and CleanUp against a fake Gandi RPC @@ -15,55 +17,50 @@ import ( func TestDNSProvider(t *testing.T) { fakeAPIKey := "123412341234123412341234" fakeKeyAuth := "XXXX" + provider, err := NewDNSProviderCredentials(fakeAPIKey) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + regexpToken, err := regexp.Compile(`"rrset_values":\[".+"\]`) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + // start fake RPC server fakeServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Header.Get("Content-Type") != "application/json" { - t.Fatalf("Content-Type: application/json header not found") - } + require.Equal(t, "application/json", r.Header.Get("Content-Type"), "invalid content type") + req, err := ioutil.ReadAll(r.Body) - if err != nil { - t.Fatal(err) - } - req = regexpToken.ReplaceAllLiteral( - req, []byte(`"rrset_values":["TOKEN"]`)) + require.NoError(t, err) + + req = regexpToken.ReplaceAllLiteral(req, []byte(`"rrset_values":["TOKEN"]`)) + resp, ok := serverResponses[string(req)] - if !ok { - t.Fatalf("Server response for request not found") - } + require.True(t, ok, "Server response for request not found") + _, err = io.Copy(w, strings.NewReader(resp)) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) })) defer fakeServer.Close() + // define function to override findZoneByFqdn with fakeFindZoneByFqdn := func(fqdn string, nameserver []string) (string, error) { return "example.com.", nil } + // override gandi endpoint and findZoneByFqdn function savedEndpoint, savedFindZoneByFqdn := endpoint, findZoneByFqdn defer func() { endpoint, findZoneByFqdn = savedEndpoint, savedFindZoneByFqdn }() + endpoint, findZoneByFqdn = fakeServer.URL, fakeFindZoneByFqdn + // run Present err = provider.Present("abc.def.example.com", "", fakeKeyAuth) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + // run CleanUp err = provider.CleanUp("abc.def.example.com", "", fakeKeyAuth) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } // serverResponses is the JSON Request->Response map used by the diff --git a/providers/dns/gcloud/googlecloud.go b/providers/dns/gcloud/googlecloud.go index 4a938709..c50c0ed7 100644 --- a/providers/dns/gcloud/googlecloud.go +++ b/providers/dns/gcloud/googlecloud.go @@ -31,6 +31,7 @@ func NewDNSProvider() (*DNSProvider, error) { if saFile, ok := os.LookupEnv("GCE_SERVICE_ACCOUNT_FILE"); ok { return NewDNSProviderServiceAccount(saFile) } + project := os.Getenv("GCE_PROJECT") return NewDNSProviderCredentials(project) } @@ -44,11 +45,11 @@ func NewDNSProviderCredentials(project string) (*DNSProvider, error) { client, err := google.DefaultClient(context.Background(), dns.NdevClouddnsReadwriteScope) if err != nil { - return nil, fmt.Errorf("Unable to get Google Cloud client: %v", err) + return nil, fmt.Errorf("unable to get Google Cloud client: %v", err) } svc, err := dns.New(client) if err != nil { - return nil, fmt.Errorf("Unable to create Google Cloud DNS service: %v", err) + return nil, fmt.Errorf("unable to create Google Cloud DNS service: %v", err) } return &DNSProvider{ project: project, @@ -65,7 +66,7 @@ func NewDNSProviderServiceAccount(saFile string) (*DNSProvider, error) { dat, err := ioutil.ReadFile(saFile) if err != nil { - return nil, fmt.Errorf("Unable to read Service Account file: %v", err) + return nil, fmt.Errorf("unable to read Service Account file: %v", err) } // read project id from service account file @@ -74,19 +75,19 @@ func NewDNSProviderServiceAccount(saFile string) (*DNSProvider, error) { } err = json.Unmarshal(dat, &datJSON) if err != nil || datJSON.ProjectID == "" { - return nil, fmt.Errorf("Project ID not found in Google Cloud Service Account file") + 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) + return nil, fmt.Errorf("unable to acquire config: %v", err) } client := conf.Client(context.Background()) svc, err := dns.New(client) if err != nil { - return nil, fmt.Errorf("Unable to create Google Cloud DNS service: %v", err) + return nil, fmt.Errorf("unable to create Google Cloud DNS service: %v", err) } return &DNSProvider{ project: project, @@ -189,7 +190,7 @@ func (c *DNSProvider) getHostedZone(domain string) (string, error) { } if len(zones.ManagedZones) == 0 { - return "", fmt.Errorf("No matching GoogleCloud domain found for domain %s", authZone) + return "", fmt.Errorf("no matching GoogleCloud domain found for domain %s", authZone) } return zones.ManagedZones[0].Name, nil @@ -202,7 +203,7 @@ func (c *DNSProvider) findTxtRecords(zone, fqdn string) ([]*dns.ResourceRecordSe return nil, err } - found := []*dns.ResourceRecordSet{} + var found []*dns.ResourceRecordSet for _, r := range recs.Rrsets { if r.Type == "TXT" && r.Name == fqdn { found = append(found, r) diff --git a/providers/dns/gcloud/googlecloud_test.go b/providers/dns/gcloud/googlecloud_test.go index ea171e2a..3be678cf 100644 --- a/providers/dns/gcloud/googlecloud_test.go +++ b/providers/dns/gcloud/googlecloud_test.go @@ -27,7 +27,7 @@ func init() { } } -func restoreGCloudEnv() { +func restoreEnv() { os.Setenv("GCE_PROJECT", gcloudProject) } @@ -35,27 +35,32 @@ func TestNewDNSProviderValid(t *testing.T) { if !gcloudLiveTest { t.Skip("skipping live test (requires credentials)") } + + defer restoreEnv() os.Setenv("GCE_PROJECT", "") + _, err := NewDNSProviderCredentials("my-project") assert.NoError(t, err) - restoreGCloudEnv() } func TestNewDNSProviderValidEnv(t *testing.T) { if !gcloudLiveTest { t.Skip("skipping live test (requires credentials)") } + + defer restoreEnv() os.Setenv("GCE_PROJECT", "my-project") + _, err := NewDNSProvider() assert.NoError(t, err) - restoreGCloudEnv() } func TestNewDNSProviderMissingCredErr(t *testing.T) { + defer restoreEnv() os.Setenv("GCE_PROJECT", "") + _, err := NewDNSProvider() assert.EqualError(t, err, "Google Cloud project name missing") - restoreGCloudEnv() } func TestLiveGoogleCloudPresent(t *testing.T) { diff --git a/providers/dns/glesys/glesys.go b/providers/dns/glesys/glesys.go index 7623b564..71ef461f 100644 --- a/providers/dns/glesys/glesys.go +++ b/providers/dns/glesys/glesys.go @@ -7,13 +7,13 @@ import ( "encoding/json" "fmt" "net/http" - "os" "strings" "sync" "time" "github.com/xenolf/lego/acme" "github.com/xenolf/lego/log" + "github.com/xenolf/lego/platform/config/env" ) // GleSYS API reference: https://github.com/GleSYS/API/wiki/API-Documentation @@ -35,9 +35,12 @@ type DNSProvider struct { // Credentials must be passed in the environment variables: GLESYS_API_USER // and GLESYS_API_KEY. func NewDNSProvider() (*DNSProvider, error) { - apiUser := os.Getenv("GLESYS_API_USER") - apiKey := os.Getenv("GLESYS_API_KEY") - return NewDNSProviderCredentials(apiUser, apiKey) + values, err := env.Get("GLESYS_API_USER", "GLESYS_API_KEY") + if err != nil { + return nil, fmt.Errorf("GleSYS DNS: %v", err) + } + + return NewDNSProviderCredentials(values["GLESYS_API_USER"], values["GLESYS_API_KEY"]) } // NewDNSProviderCredentials uses the supplied credentials to return a diff --git a/providers/dns/godaddy/godaddy.go b/providers/dns/godaddy/godaddy.go index 54fbad03..75716b8a 100644 --- a/providers/dns/godaddy/godaddy.go +++ b/providers/dns/godaddy/godaddy.go @@ -2,18 +2,17 @@ package godaddy import ( - "fmt" - "io" - "net/http" - "os" - "time" - "bytes" "encoding/json" + "fmt" + "io" "io/ioutil" + "net/http" "strings" + "time" "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" ) // GoDaddyAPIURL represents the API endpoint to call. @@ -29,9 +28,12 @@ type DNSProvider struct { // Credentials must be passed in the environment variables: GODADDY_API_KEY // and GODADDY_API_SECRET. func NewDNSProvider() (*DNSProvider, error) { - apikey := os.Getenv("GODADDY_API_KEY") - secret := os.Getenv("GODADDY_API_SECRET") - return NewDNSProviderCredentials(apikey, secret) + values, err := env.Get("GODADDY_API_KEY", "GODADDY_API_SECRET") + if err != nil { + return nil, fmt.Errorf("GoDaddy: %v", err) + } + + return NewDNSProviderCredentials(values["GODADDY_API_KEY"], values["GODADDY_API_SECRET"]) } // NewDNSProviderCredentials uses the supplied credentials to return a diff --git a/providers/dns/lightsail/lightsail_integration_test.go b/providers/dns/lightsail/lightsail_integration_test.go index ee6216ea..579780d4 100644 --- a/providers/dns/lightsail/lightsail_integration_test.go +++ b/providers/dns/lightsail/lightsail_integration_test.go @@ -8,40 +8,40 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/lightsail" + "github.com/stretchr/testify/require" ) func TestLightsailTTL(t *testing.T) { - m, err := testGetAndPreCheck() if err != nil { t.Skip(err.Error()) } provider, err := NewDNSProvider() - if err != nil { - t.Fatalf("Fatal: %s", err.Error()) - } + require.NoError(t, err) err = provider.Present(m["lightsailDomain"], "foo", "bar") - if err != nil { - t.Fatalf("Fatal: %s", err.Error()) - } + require.NoError(t, err) + // we need a separate Lightshail client here as the one in the DNS provider is // unexported. fqdn := "_acme-challenge." + m["lightsailDomain"] svc := lightsail.New(session.New()) if err != nil { provider.CleanUp(m["lightsailDomain"], "foo", "bar") - t.Fatalf("Fatal: %s", err.Error()) + t.Fatal(err) } + params := &lightsail.GetDomainInput{ DomainName: aws.String(m["lightsailDomain"]), } + resp, err := svc.GetDomain(params) if err != nil { provider.CleanUp(m["lightsailDomain"], "foo", "bar") - t.Fatalf("Fatal: %s", err.Error()) + t.Fatal(err) } + entries := resp.Domain.DomainEntries for _, entry := range entries { if *entry.Type == "TXT" && *entry.Name == fqdn { @@ -49,6 +49,7 @@ func TestLightsailTTL(t *testing.T) { return } } + provider.CleanUp(m["lightsailDomain"], "foo", "bar") t.Fatalf("Could not find a TXT record for _acme-challenge.%s", m["lightsailDomain"]) } diff --git a/providers/dns/lightsail/lightsail_test.go b/providers/dns/lightsail/lightsail_test.go index d443da54..ee41073a 100644 --- a/providers/dns/lightsail/lightsail_test.go +++ b/providers/dns/lightsail/lightsail_test.go @@ -23,7 +23,7 @@ func init() { lightsailSecret = os.Getenv("AWS_SECRET_ACCESS_KEY") } -func restoreLightsailEnv() { +func restoreEnv() { os.Setenv("AWS_ACCESS_KEY_ID", lightsailKey) os.Setenv("AWS_SECRET_ACCESS_KEY", lightsailSecret) os.Setenv("AWS_REGION", "us-east-1") @@ -43,6 +43,7 @@ func makeLightsailProvider(ts *httptest.Server) *DNSProvider { } func TestCredentialsFromEnv(t *testing.T) { + defer restoreEnv() os.Setenv("AWS_ACCESS_KEY_ID", "123") os.Setenv("AWS_SECRET_ACCESS_KEY", "123") os.Setenv("AWS_REGION", "us-east-1") @@ -54,8 +55,6 @@ func TestCredentialsFromEnv(t *testing.T) { sess := session.New(config) _, err := sess.Config.Credentials.Get() assert.NoError(t, err, "Expected credentials to be set from environment") - - restoreLightsailEnv() } func TestLightsailPresent(t *testing.T) { diff --git a/providers/dns/linode/linode.go b/providers/dns/linode/linode.go index 1e8cbc50..22117b66 100644 --- a/providers/dns/linode/linode.go +++ b/providers/dns/linode/linode.go @@ -4,12 +4,13 @@ package linode import ( "errors" - "os" + "fmt" "strings" "time" "github.com/timewasted/linode/dns" "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" ) const ( @@ -31,8 +32,12 @@ type DNSProvider struct { // NewDNSProvider returns a DNSProvider instance configured for Linode. // Credentials must be passed in the environment variable: LINODE_API_KEY. func NewDNSProvider() (*DNSProvider, error) { - apiKey := os.Getenv("LINODE_API_KEY") - return NewDNSProviderCredentials(apiKey) + values, err := env.Get("LINODE_API_KEY") + if err != nil { + return nil, fmt.Errorf("Linode: %v", err) + } + + return NewDNSProviderCredentials(values["LINODE_API_KEY"]) } // NewDNSProviderCredentials uses the supplied credentials to return a diff --git a/providers/dns/linode/linode_test.go b/providers/dns/linode/linode_test.go index d9713a27..515cdc25 100644 --- a/providers/dns/linode/linode_test.go +++ b/providers/dns/linode/linode_test.go @@ -48,8 +48,7 @@ func newMockServer(t *testing.T, responses MockResponseMap) *httptest.Server { action := r.URL.Query().Get("api_action") resp, ok := responses[action] if !ok { - msg := fmt.Sprintf("Unsupported mock action: %s", action) - require.FailNow(t, msg) + require.FailNowf(t, "Unsupported mock action: %s", action) } // Build the response that the server will return. @@ -75,17 +74,19 @@ func newMockServer(t *testing.T, responses MockResponseMap) *httptest.Server { } func TestNewDNSProviderWithEnv(t *testing.T) { - os.Setenv("LINODE_API_KEY", "testing") defer restoreEnv() + os.Setenv("LINODE_API_KEY", "testing") + _, err := NewDNSProvider() assert.NoError(t, err) } func TestNewDNSProviderWithoutEnv(t *testing.T) { - os.Setenv("LINODE_API_KEY", "") defer restoreEnv() + os.Setenv("LINODE_API_KEY", "") + _, err := NewDNSProvider() - assert.EqualError(t, err, "Linode credentials missing") + assert.EqualError(t, err, "Linode: some credentials information are missing: LINODE_API_KEY") } func TestNewDNSProviderCredentialsWithKey(t *testing.T) { @@ -99,8 +100,9 @@ func TestNewDNSProviderCredentialsWithoutKey(t *testing.T) { } func TestDNSProvider_Present(t *testing.T) { - os.Setenv("LINODE_API_KEY", "testing") defer restoreEnv() + os.Setenv("LINODE_API_KEY", "testing") + p, err := NewDNSProvider() assert.NoError(t, err) @@ -109,7 +111,7 @@ func TestDNSProvider_Present(t *testing.T) { mockResponses := MockResponseMap{ "domain.list": MockResponse{ Response: []dns.Domain{ - dns.Domain{ + { Domain: domain, DomainID: 1234, }, @@ -121,8 +123,10 @@ func TestDNSProvider_Present(t *testing.T) { }, }, } + mockSrv := newMockServer(t, mockResponses) defer mockSrv.Close() + p.linode.ToLinode().SetEndpoint(mockSrv.URL) err = p.Present(domain, "", keyAuth) @@ -130,8 +134,9 @@ func TestDNSProvider_Present(t *testing.T) { } func TestDNSProvider_PresentNoDomain(t *testing.T) { - os.Setenv("LINODE_API_KEY", "testing") defer restoreEnv() + os.Setenv("LINODE_API_KEY", "testing") + p, err := NewDNSProvider() assert.NoError(t, err) @@ -140,15 +145,17 @@ func TestDNSProvider_PresentNoDomain(t *testing.T) { mockResponses := MockResponseMap{ "domain.list": MockResponse{ Response: []dns.Domain{ - dns.Domain{ + { Domain: "foobar.com", DomainID: 1234, }, }, }, } + mockSrv := newMockServer(t, mockResponses) defer mockSrv.Close() + p.linode.ToLinode().SetEndpoint(mockSrv.URL) err = p.Present(domain, "", keyAuth) @@ -156,8 +163,9 @@ func TestDNSProvider_PresentNoDomain(t *testing.T) { } func TestDNSProvider_PresentCreateFailed(t *testing.T) { - os.Setenv("LINODE_API_KEY", "testing") defer restoreEnv() + os.Setenv("LINODE_API_KEY", "testing") + p, err := NewDNSProvider() assert.NoError(t, err) @@ -166,7 +174,7 @@ func TestDNSProvider_PresentCreateFailed(t *testing.T) { mockResponses := MockResponseMap{ "domain.list": MockResponse{ Response: []dns.Domain{ - dns.Domain{ + { Domain: domain, DomainID: 1234, }, @@ -175,7 +183,7 @@ func TestDNSProvider_PresentCreateFailed(t *testing.T) { "domain.resource.create": MockResponse{ Response: nil, Errors: []linode.ResponseError{ - linode.ResponseError{ + { Code: 1234, Message: "Failed to create domain resource", }, @@ -184,6 +192,7 @@ func TestDNSProvider_PresentCreateFailed(t *testing.T) { } mockSrv := newMockServer(t, mockResponses) defer mockSrv.Close() + p.linode.ToLinode().SetEndpoint(mockSrv.URL) err = p.Present(domain, "", keyAuth) @@ -197,8 +206,9 @@ func TestDNSProvider_PresentLive(t *testing.T) { } func TestDNSProvider_CleanUp(t *testing.T) { - os.Setenv("LINODE_API_KEY", "testing") defer restoreEnv() + os.Setenv("LINODE_API_KEY", "testing") + p, err := NewDNSProvider() assert.NoError(t, err) @@ -207,7 +217,7 @@ func TestDNSProvider_CleanUp(t *testing.T) { mockResponses := MockResponseMap{ "domain.list": MockResponse{ Response: []dns.Domain{ - dns.Domain{ + { Domain: domain, DomainID: 1234, }, @@ -215,7 +225,7 @@ func TestDNSProvider_CleanUp(t *testing.T) { }, "domain.resource.list": MockResponse{ Response: []dns.Resource{ - dns.Resource{ + { DomainID: 1234, Name: "_acme-challenge", ResourceID: 1234, @@ -230,8 +240,10 @@ func TestDNSProvider_CleanUp(t *testing.T) { }, }, } + mockSrv := newMockServer(t, mockResponses) defer mockSrv.Close() + p.linode.ToLinode().SetEndpoint(mockSrv.URL) err = p.CleanUp(domain, "", keyAuth) @@ -239,8 +251,9 @@ func TestDNSProvider_CleanUp(t *testing.T) { } func TestDNSProvider_CleanUpNoDomain(t *testing.T) { - os.Setenv("LINODE_API_KEY", "testing") defer restoreEnv() + os.Setenv("LINODE_API_KEY", "testing") + p, err := NewDNSProvider() assert.NoError(t, err) @@ -249,15 +262,17 @@ func TestDNSProvider_CleanUpNoDomain(t *testing.T) { mockResponses := MockResponseMap{ "domain.list": MockResponse{ Response: []dns.Domain{ - dns.Domain{ + { Domain: "foobar.com", DomainID: 1234, }, }, }, } + mockSrv := newMockServer(t, mockResponses) defer mockSrv.Close() + p.linode.ToLinode().SetEndpoint(mockSrv.URL) err = p.CleanUp(domain, "", keyAuth) @@ -265,8 +280,9 @@ func TestDNSProvider_CleanUpNoDomain(t *testing.T) { } func TestDNSProvider_CleanUpDeleteFailed(t *testing.T) { - os.Setenv("LINODE_API_KEY", "testing") defer restoreEnv() + os.Setenv("LINODE_API_KEY", "testing") + p, err := NewDNSProvider() assert.NoError(t, err) @@ -275,7 +291,7 @@ func TestDNSProvider_CleanUpDeleteFailed(t *testing.T) { mockResponses := MockResponseMap{ "domain.list": MockResponse{ Response: []dns.Domain{ - dns.Domain{ + { Domain: domain, DomainID: 1234, }, @@ -283,7 +299,7 @@ func TestDNSProvider_CleanUpDeleteFailed(t *testing.T) { }, "domain.resource.list": MockResponse{ Response: []dns.Resource{ - dns.Resource{ + { DomainID: 1234, Name: "_acme-challenge", ResourceID: 1234, @@ -295,23 +311,19 @@ func TestDNSProvider_CleanUpDeleteFailed(t *testing.T) { "domain.resource.delete": MockResponse{ Response: nil, Errors: []linode.ResponseError{ - linode.ResponseError{ + { Code: 1234, Message: "Failed to delete domain resource", }, }, }, } + mockSrv := newMockServer(t, mockResponses) defer mockSrv.Close() + p.linode.ToLinode().SetEndpoint(mockSrv.URL) err = p.CleanUp(domain, "", keyAuth) assert.EqualError(t, err, "Failed to delete domain resource") } - -func TestDNSProvider_CleanUpLive(t *testing.T) { - if !isTestLive { - t.Skip("Skipping live test") - } -} diff --git a/providers/dns/namecheap/namecheap.go b/providers/dns/namecheap/namecheap.go index 2991d817..35be8b4c 100644 --- a/providers/dns/namecheap/namecheap.go +++ b/providers/dns/namecheap/namecheap.go @@ -8,11 +8,11 @@ import ( "io/ioutil" "net/http" "net/url" - "os" "strings" "time" "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" ) // Notes about namecheap's tool API: @@ -48,9 +48,12 @@ type DNSProvider struct { // Credentials must be passed in the environment variables: NAMECHEAP_API_USER // and NAMECHEAP_API_KEY. func NewDNSProvider() (*DNSProvider, error) { - apiUser := os.Getenv("NAMECHEAP_API_USER") - apiKey := os.Getenv("NAMECHEAP_API_KEY") - return NewDNSProviderCredentials(apiUser, apiKey) + values, err := env.Get("NAMECHEAP_API_USER", "NAMECHEAP_API_KEY") + if err != nil { + return nil, fmt.Errorf("NameCheap: %v", err) + } + + return NewDNSProviderCredentials(values["NAMECHEAP_API_USER"], values["NAMECHEAP_API_KEY"]) } // NewDNSProviderCredentials uses the supplied credentials to return a @@ -117,7 +120,7 @@ func getClientIP() (addr string, err error) { return string(clientIP), nil } -// A challenge repesents all the data needed to specify a dns-01 challenge +// A challenge represents all the data needed to specify a dns-01 challenge // to lets-encrypt. type challenge struct { domain string diff --git a/providers/dns/namecheap/namecheap_test.go b/providers/dns/namecheap/namecheap_test.go index 0631d4a3..dfa07524 100644 --- a/providers/dns/namecheap/namecheap_test.go +++ b/providers/dns/namecheap/namecheap_test.go @@ -241,24 +241,27 @@ func TestNamecheapDomainSplit(t *testing.T) { } for _, test := range tests { - valid := true - ch, err := newChallenge(test.domain, "", tlds) - if err != nil { - valid = false - } + test := test + t.Run(test.domain, func(t *testing.T) { + valid := true + ch, err := newChallenge(test.domain, "", tlds) + if err != nil { + valid = false + } - if test.valid && !valid { - t.Errorf("Expected '%s' to split", test.domain) - } else if !test.valid && valid { - t.Errorf("Expected '%s' to produce error", test.domain) - } + if test.valid && !valid { + t.Errorf("Expected '%s' to split", test.domain) + } else if !test.valid && valid { + t.Errorf("Expected '%s' to produce error", test.domain) + } - if test.valid && valid { - assertEq(t, "domain", ch.domain, test.domain) - assertEq(t, "tld", ch.tld, test.tld) - assertEq(t, "sld", ch.sld, test.sld) - assertEq(t, "host", ch.host, test.host) - } + if test.valid && valid { + assertEq(t, "domain", ch.domain, test.domain) + assertEq(t, "tld", ch.tld, test.tld) + assertEq(t, "sld", ch.sld, test.sld) + assertEq(t, "host", ch.host, test.host) + } + }) } } diff --git a/providers/dns/namedotcom/namedotcom.go b/providers/dns/namedotcom/namedotcom.go index 2df4a597..39c0cca9 100644 --- a/providers/dns/namedotcom/namedotcom.go +++ b/providers/dns/namedotcom/namedotcom.go @@ -9,6 +9,7 @@ import ( "github.com/namedotcom/go/namecom" "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" ) // DNSProvider is an implementation of the acme.ChallengeProvider interface. @@ -19,11 +20,13 @@ type DNSProvider struct { // NewDNSProvider returns a DNSProvider instance configured for namedotcom. // Credentials must be passed in the environment variables: NAMECOM_USERNAME and NAMECOM_API_TOKEN func NewDNSProvider() (*DNSProvider, error) { - username := os.Getenv("NAMECOM_USERNAME") - apiToken := os.Getenv("NAMECOM_API_TOKEN") - server := os.Getenv("NAMECOM_SERVER") + values, err := env.Get("NAMECOM_USERNAME", "NAMECOM_API_TOKEN") + if err != nil { + return nil, fmt.Errorf("Name.com: %v", err) + } - return NewDNSProviderCredentials(username, apiToken, server) + server := os.Getenv("NAMECOM_SERVER") + return NewDNSProviderCredentials(values["NAMECOM_USERNAME"], values["NAMECOM_API_TOKEN"], server) } // NewDNSProviderCredentials uses the supplied credentials to return a @@ -59,7 +62,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error { _, err := c.client.CreateRecord(request) if err != nil { - return fmt.Errorf("namedotcom API call failed: %v", err) + return fmt.Errorf("Name.com API call failed: %v", err) } return nil diff --git a/providers/dns/namedotcom/namedotcom_test.go b/providers/dns/namedotcom/namedotcom_test.go index 6d00a464..5153d6b5 100644 --- a/providers/dns/namedotcom/namedotcom_test.go +++ b/providers/dns/namedotcom/namedotcom_test.go @@ -27,7 +27,7 @@ func init() { } } -func TestLivenamedotcomPresent(t *testing.T) { +func TestLiveNamedotcomPresent(t *testing.T) { if !namedotcomLiveTest { t.Skip("skipping live test") } @@ -43,7 +43,7 @@ func TestLivenamedotcomPresent(t *testing.T) { // Cleanup // -func TestLivenamedotcomCleanUp(t *testing.T) { +func TestLiveNamedotcomCleanUp(t *testing.T) { if !namedotcomLiveTest { t.Skip("skipping live test") } diff --git a/providers/dns/ns1/ns1.go b/providers/dns/ns1/ns1.go index 105d73f8..e41af869 100644 --- a/providers/dns/ns1/ns1.go +++ b/providers/dns/ns1/ns1.go @@ -5,10 +5,10 @@ package ns1 import ( "fmt" "net/http" - "os" "time" "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" "gopkg.in/ns1/ns1-go.v2/rest" "gopkg.in/ns1/ns1-go.v2/rest/model/dns" ) @@ -21,11 +21,12 @@ type DNSProvider struct { // 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") + values, err := env.Get("NS1_API_KEY") + if err != nil { + return nil, fmt.Errorf("NS1: %v", err) } - return NewDNSProviderCredentials(key) + + return NewDNSProviderCredentials(values["NS1_API_KEY"]) } // NewDNSProviderCredentials uses the supplied credentials to return a diff --git a/providers/dns/ns1/ns1_test.go b/providers/dns/ns1/ns1_test.go index eb9150dd..b9866fb7 100644 --- a/providers/dns/ns1/ns1_test.go +++ b/providers/dns/ns1/ns1_test.go @@ -22,22 +22,24 @@ func init() { } } -func restoreNS1Env() { +func restoreEnv() { os.Setenv("NS1_API_KEY", apiKey) } func TestNewDNSProviderValid(t *testing.T) { + defer restoreEnv() os.Setenv("NS1_API_KEY", "") + _, err := NewDNSProviderCredentials("123") assert.NoError(t, err) - restoreNS1Env() } func TestNewDNSProviderMissingCredErr(t *testing.T) { + defer restoreEnv() os.Setenv("NS1_API_KEY", "") + _, err := NewDNSProvider() - assert.EqualError(t, err, "NS1 credentials missing") - restoreNS1Env() + assert.EqualError(t, err, "NS1: some credentials information are missing: NS1_API_KEY") } func TestLivePresent(t *testing.T) { diff --git a/providers/dns/otc/otc.go b/providers/dns/otc/otc.go index 86918730..76941c8d 100644 --- a/providers/dns/otc/otc.go +++ b/providers/dns/otc/otc.go @@ -13,6 +13,7 @@ import ( "time" "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" ) // DNSProvider is an implementation of the acme.ChallengeProvider interface that uses @@ -31,12 +32,18 @@ type DNSProvider struct { // Credentials must be passed in the environment variables: OTC_USER_NAME, // OTC_DOMAIN_NAME, OTC_PASSWORD OTC_PROJECT_NAME and OTC_IDENTITY_ENDPOINT. func NewDNSProvider() (*DNSProvider, error) { - domainName := os.Getenv("OTC_DOMAIN_NAME") - userName := os.Getenv("OTC_USER_NAME") - password := os.Getenv("OTC_PASSWORD") - projectName := os.Getenv("OTC_PROJECT_NAME") - identityEndpoint := os.Getenv("OTC_IDENTITY_ENDPOINT") - return NewDNSProviderCredentials(domainName, userName, password, projectName, identityEndpoint) + values, err := env.Get("OTC_DOMAIN_NAME", "OTC_USER_NAME", "OTC_PASSWORD", "OTC_PROJECT_NAME") + if err != nil { + return nil, fmt.Errorf("OTC: %v", err) + } + + return NewDNSProviderCredentials( + values["OTC_DOMAIN_NAME"], + values["OTC_USER_NAME"], + values["OTC_PASSWORD"], + values["OTC_PROJECT_NAME"], + os.Getenv("OTC_IDENTITY_ENDPOINT"), + ) } // NewDNSProviderCredentials uses the supplied credentials to return a diff --git a/providers/dns/otc/otc_test.go b/providers/dns/otc/otc_test.go index 0c05334a..dfee06a4 100644 --- a/providers/dns/otc/otc_test.go +++ b/providers/dns/otc/otc_test.go @@ -2,10 +2,11 @@ package otc import ( "fmt" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/suite" "os" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" ) type OTCDNSTestSuite struct { @@ -34,6 +35,8 @@ func (s *OTCDNSTestSuite) createDNSProvider() (*DNSProvider, error) { } func (s *OTCDNSTestSuite) TestOTCDNSLoginEnv() { + defer os.Clearenv() + os.Setenv("OTC_DOMAIN_NAME", "unittest1") os.Setenv("OTC_USER_NAME", "unittest2") os.Setenv("OTC_PASSWORD", "unittest3") @@ -53,15 +56,13 @@ func (s *OTCDNSTestSuite) TestOTCDNSLoginEnv() { provider, err = NewDNSProvider() assert.Nil(s.T(), err) assert.Equal(s.T(), provider.identityEndpoint, "https://iam.eu-de.otc.t-systems.com:443/v3/auth/tokens") - - os.Clearenv() } func (s *OTCDNSTestSuite) TestOTCDNSLoginEnvEmpty() { - _, err := NewDNSProvider() - assert.Equal(s.T(), "OTC credentials missing", err.Error()) + defer os.Clearenv() - os.Clearenv() + _, err := NewDNSProvider() + assert.EqualError(s.T(), err, "OTC: some credentials information are missing: OTC_DOMAIN_NAME,OTC_USER_NAME,OTC_PASSWORD,OTC_PROJECT_NAME") } func (s *OTCDNSTestSuite) TestOTCDNSLogin() { diff --git a/providers/dns/ovh/ovh.go b/providers/dns/ovh/ovh.go index a49fc774..98752306 100644 --- a/providers/dns/ovh/ovh.go +++ b/providers/dns/ovh/ovh.go @@ -4,12 +4,12 @@ package ovh import ( "fmt" - "os" "strings" "sync" "github.com/ovh/go-ovh/ovh" "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" ) // OVH API reference: https://eu.api.ovh.com/ @@ -30,11 +30,17 @@ type DNSProvider struct { // OVH_APPLICATION_SECRET // OVH_CONSUMER_KEY func NewDNSProvider() (*DNSProvider, error) { - apiEndpoint := os.Getenv("OVH_ENDPOINT") - applicationKey := os.Getenv("OVH_APPLICATION_KEY") - applicationSecret := os.Getenv("OVH_APPLICATION_SECRET") - consumerKey := os.Getenv("OVH_CONSUMER_KEY") - return NewDNSProviderCredentials(apiEndpoint, applicationKey, applicationSecret, consumerKey) + values, err := env.Get("OVH_ENDPOINT", "OVH_APPLICATION_KEY", "OVH_APPLICATION_SECRET", "OVH_CONSUMER_KEY") + if err != nil { + return nil, fmt.Errorf("OVH: %v", err) + } + + return NewDNSProviderCredentials( + values["OVH_ENDPOINT"], + values["OVH_APPLICATION_KEY"], + values["OVH_APPLICATION_SECRET"], + values["OVH_CONSUMER_KEY"], + ) } // NewDNSProviderCredentials uses the supplied credentials to return a diff --git a/providers/dns/ovh/ovh_test.go b/providers/dns/ovh/ovh_test.go index 47da60e5..04d34a0f 100644 --- a/providers/dns/ovh/ovh_test.go +++ b/providers/dns/ovh/ovh_test.go @@ -33,47 +33,88 @@ func restoreEnv() { } func TestNewDNSProviderValidEnv(t *testing.T) { + defer restoreEnv() os.Setenv("OVH_ENDPOINT", "ovh-eu") os.Setenv("OVH_APPLICATION_KEY", "1234") os.Setenv("OVH_APPLICATION_SECRET", "5678") os.Setenv("OVH_CONSUMER_KEY", "abcde") - defer restoreEnv() + _, err := NewDNSProvider() assert.NoError(t, err) } func TestNewDNSProviderMissingCredErr(t *testing.T) { - os.Setenv("OVH_ENDPOINT", "") - os.Setenv("OVH_APPLICATION_KEY", "1234") - os.Setenv("OVH_APPLICATION_SECRET", "5678") - os.Setenv("OVH_CONSUMER_KEY", "abcde") defer restoreEnv() - _, err := NewDNSProvider() - assert.EqualError(t, err, "OVH credentials missing") - os.Setenv("OVH_ENDPOINT", "ovh-eu") - os.Setenv("OVH_APPLICATION_KEY", "") - os.Setenv("OVH_APPLICATION_SECRET", "5678") - os.Setenv("OVH_CONSUMER_KEY", "abcde") - defer restoreEnv() - _, err = NewDNSProvider() - assert.EqualError(t, err, "OVH credentials missing") + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "missing OVH_ENDPOINT", + envVars: map[string]string{ + "OVH_ENDPOINT": "", + "OVH_APPLICATION_KEY": "1234", + "OVH_APPLICATION_SECRET": "5678", + "OVH_CONSUMER_KEY": "abcde", + }, + expected: "OVH: some credentials information are missing: OVH_ENDPOINT", + }, + { + desc: "missing OVH_APPLICATION_KEY", + envVars: map[string]string{ + "OVH_ENDPOINT": "ovh-eu", + "OVH_APPLICATION_KEY": "", + "OVH_APPLICATION_SECRET": "5678", + "OVH_CONSUMER_KEY": "abcde", + }, + expected: "OVH: some credentials information are missing: OVH_APPLICATION_KEY", + }, + { + desc: "missing OVH_APPLICATION_SECRET", + envVars: map[string]string{ + "OVH_ENDPOINT": "ovh-eu", + "OVH_APPLICATION_KEY": "1234", + "OVH_APPLICATION_SECRET": "", + "OVH_CONSUMER_KEY": "abcde", + }, + expected: "OVH: some credentials information are missing: OVH_APPLICATION_SECRET", + }, + { + desc: "missing OVH_CONSUMER_KEY", + envVars: map[string]string{ + "OVH_ENDPOINT": "ovh-eu", + "OVH_APPLICATION_KEY": "1234", + "OVH_APPLICATION_SECRET": "5678", + "OVH_CONSUMER_KEY": "", + }, + expected: "OVH: some credentials information are missing: OVH_CONSUMER_KEY", + }, + { + desc: "all missing", + envVars: map[string]string{ + "OVH_ENDPOINT": "", + "OVH_APPLICATION_KEY": "", + "OVH_APPLICATION_SECRET": "", + "OVH_CONSUMER_KEY": "", + }, + expected: "OVH: some credentials information are missing: OVH_ENDPOINT,OVH_APPLICATION_KEY,OVH_APPLICATION_SECRET,OVH_CONSUMER_KEY", + }, + } - os.Setenv("OVH_ENDPOINT", "ovh-eu") - os.Setenv("OVH_APPLICATION_KEY", "1234") - os.Setenv("OVH_APPLICATION_SECRET", "") - os.Setenv("OVH_CONSUMER_KEY", "abcde") - defer restoreEnv() - _, err = NewDNSProvider() - assert.EqualError(t, err, "OVH credentials missing") + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { - os.Setenv("OVH_ENDPOINT", "ovh-eu") - os.Setenv("OVH_APPLICATION_KEY", "1234") - os.Setenv("OVH_APPLICATION_SECRET", "5678") - os.Setenv("OVH_CONSUMER_KEY", "") - defer restoreEnv() - _, err = NewDNSProvider() - assert.EqualError(t, err, "OVH credentials missing") + for key, value := range test.envVars { + os.Setenv(key, value) + } + + _, err := NewDNSProvider() + assert.EqualError(t, err, test.expected) + }) + } } func TestLivePresent(t *testing.T) { diff --git a/providers/dns/pdns/pdns.go b/providers/dns/pdns/pdns.go index 9810245b..e97de9a7 100644 --- a/providers/dns/pdns/pdns.go +++ b/providers/dns/pdns/pdns.go @@ -9,12 +9,12 @@ import ( "io" "net/http" "net/url" - "os" "strconv" "strings" "time" "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" ) // DNSProvider is an implementation of the acme.ChallengeProvider interface @@ -28,13 +28,17 @@ type DNSProvider struct { // Credentials must be passed in the environment variable: // 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")) + values, err := env.Get("PDNS_API_KEY", "PDNS_API_URL") if err != nil { - return nil, err + return nil, fmt.Errorf("PDNS: %v", err) } - return NewDNSProviderCredentials(hostURL, key) + hostURL, err := url.Parse(values["PDNS_API_URL"]) + if err != nil { + return nil, fmt.Errorf("PDNS: %v", err) + } + + return NewDNSProviderCredentials(hostURL, values["PDNS_API_KEY"]) } // NewDNSProviderCredentials uses the supplied credentials to return a diff --git a/providers/dns/pdns/pdns_test.go b/providers/dns/pdns/pdns_test.go index 70e7670e..c436d4fb 100644 --- a/providers/dns/pdns/pdns_test.go +++ b/providers/dns/pdns/pdns_test.go @@ -26,42 +26,46 @@ func init() { } } -func restorePdnsEnv() { +func restoreEnv() { os.Setenv("PDNS_API_URL", pdnsURLStr) os.Setenv("PDNS_API_KEY", pdnsAPIKey) } func TestNewDNSProviderValid(t *testing.T) { + defer restoreEnv() os.Setenv("PDNS_API_URL", "") os.Setenv("PDNS_API_KEY", "") + tmpURL, _ := url.Parse("http://localhost:8081") _, err := NewDNSProviderCredentials(tmpURL, "123") assert.NoError(t, err) - restorePdnsEnv() } func TestNewDNSProviderValidEnv(t *testing.T) { + defer restoreEnv() os.Setenv("PDNS_API_URL", "http://localhost:8081") os.Setenv("PDNS_API_KEY", "123") + _, err := NewDNSProvider() assert.NoError(t, err) - restorePdnsEnv() } func TestNewDNSProviderMissingHostErr(t *testing.T) { + defer restoreEnv() os.Setenv("PDNS_API_URL", "") os.Setenv("PDNS_API_KEY", "123") + _, err := NewDNSProvider() - assert.EqualError(t, err, "PDNS API URL missing") - restorePdnsEnv() + assert.EqualError(t, err, "PDNS: some credentials information are missing: PDNS_API_URL") } func TestNewDNSProviderMissingKeyErr(t *testing.T) { + defer restoreEnv() os.Setenv("PDNS_API_URL", pdnsURLStr) os.Setenv("PDNS_API_KEY", "") + _, err := NewDNSProvider() - assert.EqualError(t, err, "PDNS API key missing") - restorePdnsEnv() + assert.EqualError(t, err, "PDNS: some credentials information are missing: PDNS_API_KEY,PDNS_API_URL") } func TestPdnsPresentAndCleanup(t *testing.T) { diff --git a/providers/dns/rackspace/rackspace.go b/providers/dns/rackspace/rackspace.go index d30e582b..1ec576a9 100644 --- a/providers/dns/rackspace/rackspace.go +++ b/providers/dns/rackspace/rackspace.go @@ -8,10 +8,10 @@ import ( "fmt" "io" "net/http" - "os" "time" "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" ) // rackspaceAPIURL represents the Identity API endpoint to call @@ -28,9 +28,12 @@ type DNSProvider struct { // 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) + values, err := env.Get("RACKSPACE_USER", "RACKSPACE_API_KEY") + if err != nil { + return nil, fmt.Errorf("Rackspace: %v", err) + } + + return NewDNSProviderCredentials(values["RACKSPACE_USER"], values["RACKSPACE_API_KEY"]) } // NewDNSProviderCredentials uses the supplied credentials to return a diff --git a/providers/dns/rfc2136/rfc2136.go b/providers/dns/rfc2136/rfc2136.go index 8f4231df..ee0b7c14 100644 --- a/providers/dns/rfc2136/rfc2136.go +++ b/providers/dns/rfc2136/rfc2136.go @@ -38,6 +38,7 @@ func NewDNSProvider() (*DNSProvider, error) { tsigKey := os.Getenv("RFC2136_TSIG_KEY") tsigSecret := os.Getenv("RFC2136_TSIG_SECRET") timeout := os.Getenv("RFC2136_TIMEOUT") + return NewDNSProviderCredentials(nameserver, tsigAlgorithm, tsigKey, tsigSecret, timeout) } @@ -58,13 +59,14 @@ func NewDNSProviderCredentials(nameserver, tsigAlgorithm, tsigKey, tsigSecret, t return nil, err } } - d := &DNSProvider{ - nameserver: nameserver, - } + + d := &DNSProvider{nameserver: nameserver} + if tsigAlgorithm == "" { tsigAlgorithm = dns.HmacMD5 } d.tsigAlgorithm = tsigAlgorithm + if len(tsigKey) > 0 && len(tsigSecret) > 0 { d.tsigKey = tsigKey d.tsigSecret = tsigSecret diff --git a/providers/dns/rfc2136/rfc2136_test.go b/providers/dns/rfc2136/rfc2136_test.go index f3ca65b3..a92589f0 100644 --- a/providers/dns/rfc2136/rfc2136_test.go +++ b/providers/dns/rfc2136/rfc2136_test.go @@ -10,6 +10,8 @@ import ( "time" "github.com/miekg/dns" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/xenolf/lego/acme" ) @@ -32,22 +34,20 @@ func TestRFC2136CanaryLocalTestServer(t *testing.T) { defer dns.HandleRemove("example.com.") server, addrstr, err := runLocalDNSTestServer("127.0.0.1:0", false) - if err != nil { - t.Fatalf("Failed to start test server: %v", err) - } + require.NoError(t, err, "Failed to start test server") defer server.Shutdown() c := new(dns.Client) m := new(dns.Msg) + m.SetQuestion("example.com.", dns.TypeTXT) + r, _, err := c.Exchange(m, addrstr) - if err != nil || len(r.Extra) == 0 { - t.Fatalf("Failed to communicate with test server: %v", err) - } + require.NoError(t, err, "Failed to communicate with test server") + assert.Len(t, r.Extra, 1, "Failed to communicate with test server") + txt := r.Extra[0].(*dns.TXT).Txt[0] - if txt != "Hello world" { - t.Error("Expected test server to return 'Hello world' but got: ", txt) - } + assert.Equal(t, "Hello world", txt) } func TestRFC2136ServerSuccess(t *testing.T) { @@ -56,18 +56,14 @@ func TestRFC2136ServerSuccess(t *testing.T) { defer dns.HandleRemove(rfc2136TestZone) server, addrstr, err := runLocalDNSTestServer("127.0.0.1:0", false) - if err != nil { - t.Fatalf("Failed to start test server: %v", err) - } + require.NoError(t, err, "Failed to start test server") defer server.Shutdown() provider, err := NewDNSProviderCredentials(addrstr, "", "", "", "") - if err != nil { - t.Fatalf("Expected NewDNSProviderCredentials() to return no error but the error was -> %v", err) - } - if err := provider.Present(rfc2136TestDomain, "", rfc2136TestKeyAuth); err != nil { - t.Errorf("Expected Present() to return no error but the error was -> %v", err) - } + require.NoError(t, err) + + err = provider.Present(rfc2136TestDomain, "", rfc2136TestKeyAuth) + require.NoError(t, err) } func TestRFC2136ServerError(t *testing.T) { @@ -76,19 +72,16 @@ func TestRFC2136ServerError(t *testing.T) { defer dns.HandleRemove(rfc2136TestZone) server, addrstr, err := runLocalDNSTestServer("127.0.0.1:0", false) - if err != nil { - t.Fatalf("Failed to start test server: %v", err) - } + require.NoError(t, err, "Failed to start test server") defer server.Shutdown() provider, err := NewDNSProviderCredentials(addrstr, "", "", "", "") - if err != nil { - t.Fatalf("Expected NewDNSProviderCredentials() to return no error but the error was -> %v", err) - } - if err := provider.Present(rfc2136TestDomain, "", rfc2136TestKeyAuth); err == nil { - t.Errorf("Expected Present() to return an error but it did not.") - } else if !strings.Contains(err.Error(), "NOTZONE") { - t.Errorf("Expected Present() to return an error with the 'NOTZONE' rcode string but it did not.") + require.NoError(t, err) + + err = provider.Present(rfc2136TestDomain, "", rfc2136TestKeyAuth) + require.Error(t, err) + if !strings.Contains(err.Error(), "NOTZONE") { + t.Errorf("Expected Present() to return an error with the 'NOTZONE' rcode string but it did not: %v", err) } } @@ -98,18 +91,14 @@ func TestRFC2136TsigClient(t *testing.T) { defer dns.HandleRemove(rfc2136TestZone) server, addrstr, err := runLocalDNSTestServer("127.0.0.1:0", true) - if err != nil { - t.Fatalf("Failed to start test server: %v", err) - } + require.NoError(t, err, "Failed to start test server") defer server.Shutdown() provider, err := NewDNSProviderCredentials(addrstr, "", rfc2136TestTsigKey, rfc2136TestTsigSecret, "") - if err != nil { - t.Fatalf("Expected NewDNSProviderCredentials() to return no error but the error was -> %v", err) - } - if err := provider.Present(rfc2136TestDomain, "", rfc2136TestKeyAuth); err != nil { - t.Errorf("Expected Present() to return no error but the error was -> %v", err) - } + require.NoError(t, err) + + err = provider.Present(rfc2136TestDomain, "", rfc2136TestKeyAuth) + require.NoError(t, err) } func TestRFC2136ValidUpdatePacket(t *testing.T) { @@ -118,9 +107,7 @@ func TestRFC2136ValidUpdatePacket(t *testing.T) { defer dns.HandleRemove(rfc2136TestZone) server, addrstr, err := runLocalDNSTestServer("127.0.0.1:0", false) - if err != nil { - t.Fatalf("Failed to start test server: %v", err) - } + require.NoError(t, err, "Failed to start test server") defer server.Shutdown() txtRR, _ := dns.NewRR(fmt.Sprintf("%s %d IN TXT %s", rfc2136TestFqdn, rfc2136TestTTL, rfc2136TestValue)) @@ -130,26 +117,21 @@ func TestRFC2136ValidUpdatePacket(t *testing.T) { m.RemoveRRset(rrs) m.Insert(rrs) expectstr := m.String() + expect, err := m.Pack() - if err != nil { - t.Fatalf("Error packing expect msg: %v", err) - } + require.NoError(t, err, "error packing") provider, err := NewDNSProviderCredentials(addrstr, "", "", "", "") - if err != nil { - t.Fatalf("Expected NewDNSProviderCredentials() to return no error but the error was -> %v", err) - } + require.NoError(t, err) - if err := provider.Present(rfc2136TestDomain, "", "1234d=="); err != nil { - t.Errorf("Expected Present() to return no error but the error was -> %v", err) - } + err = provider.Present(rfc2136TestDomain, "", "1234d==") + require.NoError(t, err) rcvMsg := <-reqChan rcvMsg.Id = m.Id + actual, err := rcvMsg.Pack() - if err != nil { - t.Fatalf("Error packing actual msg: %v", err) - } + require.NoError(t, err, "error packing") if !bytes.Equal(actual, expect) { tmp := new(dns.Msg) diff --git a/providers/dns/route53/route53_integration_test.go b/providers/dns/route53/route53_integration_test.go index 6ece1888..b641a90e 100644 --- a/providers/dns/route53/route53_integration_test.go +++ b/providers/dns/route53/route53_integration_test.go @@ -8,6 +8,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/route53" + "github.com/stretchr/testify/require" ) func TestRoute53TTL(t *testing.T) { @@ -17,14 +18,10 @@ func TestRoute53TTL(t *testing.T) { } provider, err := NewDNSProvider() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) err = provider.Present(m["route53Domain"], "foo", "bar") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // we need a separate R53 client here as the one in the DNS provider is // unexported. diff --git a/providers/dns/route53/route53_test.go b/providers/dns/route53/route53_test.go index e1505470..00bbe02e 100644 --- a/providers/dns/route53/route53_test.go +++ b/providers/dns/route53/route53_test.go @@ -26,7 +26,7 @@ func init() { route53Zone = os.Getenv("AWS_HOSTED_ZONE_ID") } -func restoreRoute53Env() { +func restoreEnv() { os.Setenv("AWS_ACCESS_KEY_ID", route53Key) os.Setenv("AWS_SECRET_ACCESS_KEY", route53Secret) os.Setenv("AWS_REGION", route53Region) @@ -46,6 +46,7 @@ func makeRoute53Provider(ts *httptest.Server) *DNSProvider { } func TestCredentialsFromEnv(t *testing.T) { + defer restoreEnv() os.Setenv("AWS_ACCESS_KEY_ID", "123") os.Setenv("AWS_SECRET_ACCESS_KEY", "123") os.Setenv("AWS_REGION", "us-east-1") @@ -57,23 +58,20 @@ func TestCredentialsFromEnv(t *testing.T) { sess := session.New(config) _, err := sess.Config.Credentials.Get() assert.NoError(t, err, "Expected credentials to be set from environment") - - restoreRoute53Env() } func TestRegionFromEnv(t *testing.T) { + defer restoreEnv() os.Setenv("AWS_REGION", "us-east-1") sess := session.New(aws.NewConfig()) assert.Equal(t, "us-east-1", aws.StringValue(sess.Config.Region), "Expected Region to be set from environment") - - restoreRoute53Env() } func TestHostedZoneIDFromEnv(t *testing.T) { - const testZoneID = "testzoneid" + defer restoreEnv() - defer restoreRoute53Env() + const testZoneID = "testzoneid" os.Setenv("AWS_HOSTED_ZONE_ID", testZoneID) provider, err := NewDNSProvider() diff --git a/providers/dns/vultr/vultr.go b/providers/dns/vultr/vultr.go index bc206757..5694e292 100644 --- a/providers/dns/vultr/vultr.go +++ b/providers/dns/vultr/vultr.go @@ -5,11 +5,11 @@ package vultr import ( "fmt" - "os" "strings" vultr "github.com/JamesClonk/vultr/lib" "github.com/xenolf/lego/acme" + "github.com/xenolf/lego/platform/config/env" ) // DNSProvider is an implementation of the acme.ChallengeProvider interface. @@ -20,8 +20,12 @@ type DNSProvider struct { // NewDNSProvider returns a DNSProvider instance with a configured Vultr client. // Authentication uses the VULTR_API_KEY environment variable. func NewDNSProvider() (*DNSProvider, error) { - apiKey := os.Getenv("VULTR_API_KEY") - return NewDNSProviderCredentials(apiKey) + values, err := env.Get("VULTR_API_KEY") + if err != nil { + return nil, fmt.Errorf("Vultr: %v", err) + } + + return NewDNSProviderCredentials(values["VULTR_API_KEY"]) } // NewDNSProviderCredentials uses the supplied credentials to return a DNSProvider diff --git a/providers/dns/vultr/vultr_test.go b/providers/dns/vultr/vultr_test.go index 7c8cdaf1..e058065d 100644 --- a/providers/dns/vultr/vultr_test.go +++ b/providers/dns/vultr/vultr_test.go @@ -25,17 +25,19 @@ func restoreEnv() { } func TestNewDNSProviderValidEnv(t *testing.T) { - os.Setenv("VULTR_API_KEY", "123") defer restoreEnv() + os.Setenv("VULTR_API_KEY", "123") + _, err := NewDNSProvider() assert.NoError(t, err) } func TestNewDNSProviderMissingCredErr(t *testing.T) { - os.Setenv("VULTR_API_KEY", "") defer restoreEnv() + os.Setenv("VULTR_API_KEY", "") + _, err := NewDNSProvider() - assert.EqualError(t, err, "Vultr credentials missing") + assert.EqualError(t, err, "Vultr: some credentials information are missing: VULTR_API_KEY") } func TestLivePresent(t *testing.T) {