Repository: xenolf/lego
Pull request #565 (2018-06-03 15:37:03)

Review DNS providers.
- manage more finely the missing environment variables
- use Testify
- Fix bluecat `httpClient` argument
- use `defer` in tests to keep clean the environment.



Patch:
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")
+	values, err := env.Get("AURORA_USER_ID", "AURORA_KEY")
+	if err != nil {
+		return nil, fmt.Errorf("AuroraDNS: %v", err)
+	}
 
 	endpoint := os.Getenv("AURORA_ENDPOINT")
-	if endpoint == "" {
-		endpoint = "https://api.auroradns.eu"
-	}
 
-	return NewDNSProviderCredentials(endpoint, userID, key)
+	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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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)
+	}
+
+	hostURL, err := url.Parse(values["PDNS_API_URL"])
+	if err != nil {
+		return nil, fmt.Errorf("PDNS: %v", err)
 	}
 
-	return NewDNSProviderCredentials(hostURL, key)
+	return NewDNSProviderCredentials(hostURL, values["PDNS_API_KEY"])
 }
 
 // NewDNSProviderCredentials uses the supplied credentials to return a
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/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


Test patch:
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_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/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_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_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/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_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/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_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_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_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_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/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_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_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_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_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_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_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_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/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_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) {