Repository: StackExchange/dnscontrol
Pull request #1174 (2021-06-11 17:17:50)

NEW PROVIDER: AkamaiEdgeDNS
This PR introduces a provider for Akamai EdgeDNS.

This PR also includes a change to downcase TLSA data.  TLSA data is a hexadecimal string and is case insensitive.  See pull request 1171.

svernick: I forgot to run 'go generate' before the previous commit.  My apologies.
svernick: Hi.  Is there anything that I need to do before this PR can be merged?  Thanks!
svernick: I _think_ that I made all of the required corrections.  If not, I'm happy to do so.
Thanks very much!

Patch:
diff --git a/OWNERS b/OWNERS
index d045330d..f7ca55cb 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,4 +1,5 @@
 # providers/activedir
+providers/akamaiedgedns @svernick
 providers/axfrddns @hnrgrgr
 providers/azuredns @vatsalyagoel
 providers/bind @tlimoncelli
diff --git a/README.md b/README.md
index 5adeb02d..2748917f 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,7 @@ Currently supported DNS providers:
  - AWS Route 53
  - AXFR+DDNS
  - Active Directory (Deprecated, see Microsoft DNS)
+ - Akamai Edge DNS
  - Azure DNS
  - BIND
  - ClouDNS
diff --git a/build/generate/featureMatrix.go b/build/generate/featureMatrix.go
index 1bfd4580..f25b554a 100644
--- a/build/generate/featureMatrix.go
+++ b/build/generate/featureMatrix.go
@@ -42,6 +42,7 @@ func generateFeatureMatrix() error {
 			{"R53_ALIAS", "Provider supports Route 53 limited ALIAS"},
 			{"AZURE_ALIAS", "Provider supports Azure DNS limited ALIAS"},
 			{"DS", "Provider supports adding DS records"},
+			{ "AKAMAICDN", "Provider supports adding AKAMAICDN records"},
 
 			{"dual host", "This provider is recommended for use in 'dual hosting' scenarios. Usually this means the provider allows full control over the apex NS records"},
 			{"create-domains", "This means the provider can automatically create domains that do not currently exist on your account. The 'dnscontrol create-domains' command will initialize any missing domains"},
@@ -88,6 +89,7 @@ func generateFeatureMatrix() error {
 		setCap("SRV", providers.CanUseSRV)
 		setCap("SSHFP", providers.CanUseSSHFP)
 		setCap("TLSA", providers.CanUseTLSA)
+		setCap("AKAMAICDN", providers.CanUseAKAMAICDN)
 		setCap("get-zones", providers.CanGetZones)
 		setCap("DS", providers.CanUseDS)
 		setDoc("dual host", providers.DocDualHost, false)
diff --git a/docs/_functions/domain/AKAMAICDN.md b/docs/_functions/domain/AKAMAICDN.md
new file mode 100644
index 00000000..176355d4
--- /dev/null
+++ b/docs/_functions/domain/AKAMAICDN.md
@@ -0,0 +1,10 @@
+---
+name: AKAMAICDN
+parameters:
+- name
+- target
+- modifiers...
+---
+
+AKAMAICDN is a proprietary record type that is used to configure [Zone Apex Mapping](https://blogs.akamai.com/2019/08/fast-dns-zone-apex-mapping-dnssec.html).
+The AKAMAICDN target must be preconfigured in the Akamai network.
diff --git a/docs/_includes/matrix.html b/docs/_includes/matrix.html
index 79036fe5..6f6e885d 100644
--- a/docs/_includes/matrix.html
+++ b/docs/_includes/matrix.html
@@ -7,6 +7,7 @@
 	<tr>
 	<th></th>
 	<th class="rotate"><div><span>ACTIVEDIRECTORY_PS</span></div></th>
+	<th class="rotate"><div><span>AKAMAIEDGEDNS</span></div></th>
 	<th class="rotate"><div><span>AXFRDDNS</span></div></th>
 	<th class="rotate"><div><span>AZURE_DNS</span></div></th>
 	<th class="rotate"><div><span>BIND</span></div></th>
@@ -52,6 +53,9 @@
 		<td class="danger">
 			<i class="fa fa-times text-danger" aria-hidden="true"></i>
 		</td>
+		<td class="danger">
+			<i class="fa fa-times text-danger" aria-hidden="true"></i>
+		</td>
 		<td class="success">
 			<i class="fa fa-check text-success" aria-hidden="true"></i>
 		</td>
@@ -172,6 +176,9 @@
 		<td class="success">
 			<i class="fa fa-check text-success" aria-hidden="true"></i>
 		</td>
+		<td class="success">
+			<i class="fa fa-check text-success" aria-hidden="true"></i>
+		</td>
 		<td class="danger">
 			<i class="fa fa-times text-danger" aria-hidden="true"></i>
 		</td>
@@ -280,6 +287,9 @@
 		<td class="danger">
 			<i class="fa fa-times text-danger" aria-hidden="true"></i>
 		</td>
+		<td class="danger">
+			<i class="fa fa-times text-danger" aria-hidden="true"></i>
+		</td>
 		<td class="success">
 			<i class="fa fa-check text-success" aria-hidden="true"></i>
 		</td>
@@ -373,6 +383,9 @@
 		<td class="danger">
 			<i class="fa fa-times text-danger" aria-hidden="true"></i>
 		</td>
+		<td class="danger">
+			<i class="fa fa-times text-danger" aria-hidden="true"></i>
+		</td>
 		<td><i class="fa fa-minus dim"></i></td>
 		<td class="danger" data-toggle="tooltip" data-container="body" data-placement="top" title="Azure DNS does not provide a generic ALIAS functionality. Use AZURE_ALIAS instead.">
 			<i class="fa has-tooltip fa-times text-danger" aria-hidden="true"></i>
@@ -455,6 +468,9 @@
 	<tr>
 		<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider can automatically manage DNSSEC">AUTODNSSEC</th>
 		<td><i class="fa fa-minus dim"></i></td>
+		<td class="success">
+			<i class="fa fa-check text-success" aria-hidden="true"></i>
+		</td>
 		<td class="success" data-toggle="tooltip" data-container="body" data-placement="top" title="Just warn when DNSSEC is requested but no RRSIG is found in the AXFR or warn when DNSSEC is not requested but RRSIG are found in the AXFR.">
 			<i class="fa has-tooltip fa-check text-success" aria-hidden="true"></i>
 		</td>
@@ -526,6 +542,9 @@
 		<td class="success">
 			<i class="fa fa-check text-success" aria-hidden="true"></i>
 		</td>
+		<td class="success">
+			<i class="fa fa-check text-success" aria-hidden="true"></i>
+		</td>
 		<td><i class="fa fa-minus dim"></i></td>
 		<td class="success">
 			<i class="fa fa-check text-success" aria-hidden="true"></i>
@@ -612,6 +631,9 @@
 		<td class="success">
 			<i class="fa fa-check text-success" aria-hidden="true"></i>
 		</td>
+		<td class="success">
+			<i class="fa fa-check text-success" aria-hidden="true"></i>
+		</td>
 		<td class="danger">
 			<i class="fa fa-times text-danger" aria-hidden="true"></i>
 		</td>
@@ -698,6 +720,9 @@
 		<td class="success">
 			<i class="fa fa-check text-success" aria-hidden="true"></i>
 		</td>
+		<td class="success">
+			<i class="fa fa-check text-success" aria-hidden="true"></i>
+		</td>
 		<td class="danger">
 			<i class="fa fa-times text-danger" aria-hidden="true"></i>
 		</td>
@@ -753,6 +778,9 @@
 	<tr>
 		<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider can manage SOA records">SOA</th>
 		<td><i class="fa fa-minus dim"></i></td>
+		<td class="danger">
+			<i class="fa fa-times text-danger" aria-hidden="true"></i>
+		</td>
 		<td><i class="fa fa-minus dim"></i></td>
 		<td><i class="fa fa-minus dim"></i></td>
 		<td class="success">
@@ -810,6 +838,9 @@
 		<td class="success">
 			<i class="fa fa-check text-success" aria-hidden="true"></i>
 		</td>
+		<td class="success">
+			<i class="fa fa-check text-success" aria-hidden="true"></i>
+		</td>
 		<td><i class="fa fa-minus dim"></i></td>
 		<td class="success">
 			<i class="fa fa-check text-success" aria-hidden="true"></i>
@@ -894,6 +925,9 @@
 		<td class="success">
 			<i class="fa fa-check text-success" aria-hidden="true"></i>
 		</td>
+		<td class="success">
+			<i class="fa fa-check text-success" aria-hidden="true"></i>
+		</td>
 		<td class="danger">
 			<i class="fa fa-times text-danger" aria-hidden="true"></i>
 		</td>
@@ -968,6 +1002,9 @@
 		<td class="success">
 			<i class="fa fa-check text-success" aria-hidden="true"></i>
 		</td>
+		<td class="success">
+			<i class="fa fa-check text-success" aria-hidden="true"></i>
+		</td>
 		<td class="danger">
 			<i class="fa fa-times text-danger" aria-hidden="true"></i>
 		</td>
@@ -1081,6 +1118,7 @@
 		<td><i class="fa fa-minus dim"></i></td>
 		<td><i class="fa fa-minus dim"></i></td>
 		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
 		</tr>
 	<tr>
 		<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider supports Route 53 limited ALIAS">R53_ALIAS</th>
@@ -1101,6 +1139,7 @@
 		<td><i class="fa fa-minus dim"></i></td>
 		<td><i class="fa fa-minus dim"></i></td>
 		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
 		<td class="danger" data-toggle="tooltip" data-container="body" data-placement="top" title="Using ALIAS is possible through our extended DNS (X-DNS) service. Feel free to get in touch with us.">
 			<i class="fa has-tooltip fa-times text-danger" aria-hidden="true"></i>
 		</td>
@@ -1130,6 +1169,7 @@
 		<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider supports Azure DNS limited ALIAS">AZURE_ALIAS</th>
 		<td><i class="fa fa-minus dim"></i></td>
 		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
 		<td class="success">
 			<i class="fa fa-check text-success" aria-hidden="true"></i>
 		</td>
@@ -1173,6 +1213,9 @@
 	<tr>
 		<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider supports adding DS records">DS</th>
 		<td><i class="fa fa-minus dim"></i></td>
+		<td class="danger">
+			<i class="fa fa-times text-danger" aria-hidden="true"></i>
+		</td>
 		<td><i class="fa fa-minus dim"></i></td>
 		<td><i class="fa fa-minus dim"></i></td>
 		<td class="success">
@@ -1230,11 +1273,55 @@
 		<td><i class="fa fa-minus dim"></i></td>
 		<td><i class="fa fa-minus dim"></i></td>
 		</tr>
+	<tr>
+		<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider supports adding AKAMAICDN records">AKAMAICDN</th>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td class="success">
+			<i class="fa fa-check text-success" aria-hidden="true"></i>
+		</td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
+		</tr>
 	<tr>
 		<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="This provider is recommended for use in &#39;dual hosting&#39; scenarios. Usually this means the provider allows full control over the apex NS records">dual host</th>
 		<td class="danger" data-toggle="tooltip" data-container="body" data-placement="top" title="This driver does not manage NS records, so should not be used for dual-host scenarios">
 			<i class="fa has-tooltip fa-times text-danger" aria-hidden="true"></i>
 		</td>
+		<td class="success">
+			<i class="fa fa-check text-success" aria-hidden="true"></i>
+		</td>
 		<td class="danger">
 			<i class="fa fa-times text-danger" aria-hidden="true"></i>
 		</td>
@@ -1327,6 +1414,9 @@
 		<td class="danger" data-toggle="tooltip" data-container="body" data-placement="top" title="AD depends on the zone already existing on the dns server">
 			<i class="fa has-tooltip fa-times text-danger" aria-hidden="true"></i>
 		</td>
+		<td class="success">
+			<i class="fa fa-check text-success" aria-hidden="true"></i>
+		</td>
 		<td class="danger">
 			<i class="fa fa-times text-danger" aria-hidden="true"></i>
 		</td>
@@ -1438,6 +1528,9 @@
 		<td class="danger">
 			<i class="fa fa-times text-danger" aria-hidden="true"></i>
 		</td>
+		<td class="danger">
+			<i class="fa fa-times text-danger" aria-hidden="true"></i>
+		</td>
 		<td class="success">
 			<i class="fa fa-check text-success" aria-hidden="true"></i>
 		</td>
@@ -1543,6 +1636,9 @@
 		<td class="info">
 			<i class="fa fa-circle-o text-info" aria-hidden="true"></i>
 		</td>
+		<td class="success">
+			<i class="fa fa-check text-success" aria-hidden="true"></i>
+		</td>
 		<td class="danger">
 			<i class="fa fa-times text-danger" aria-hidden="true"></i>
 		</td>
diff --git a/docs/_providers/akamaiedgedns.md b/docs/_providers/akamaiedgedns.md
new file mode 100644
index 00000000..fd2b22e3
--- /dev/null
+++ b/docs/_providers/akamaiedgedns.md
@@ -0,0 +1,76 @@
+---
+name: AkamaiEdgeDns
+title: Akamai Edge DNS Provider
+layout: default
+jsId: AKAMAIEDGEDNS
+---
+
+# Akamai Edge DNS Provider
+
+"Akamai Edge DNS Provider" configures Akamai's
+[Edge DNS](https://www.akamai.com/us/en/products/security/edge-dns.jsp) service.
+
+This provider interacts with Edge DNS via the
+[Edge DNS Zone Management API](https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html).
+
+Before you can use this provider, you need to create an "API Client" with authorization to use the
+[Edge DNS Zone Management API](https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html).
+
+See the "Get Started" section of [Edge DNS Zone Management API](https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html),
+which says, "To enable this API, choose the API service named DNS—Zone Record Management, and set the access level to READ-WRITE."
+
+Follow directions at [Authenticate With EdgeGrid](https://developer.akamai.com/getting-started/edgegrid) to generate
+the required credentials.
+
+## Configuration
+
+In the credentials file (creds.json), you must provide the following:
+{% highlight json %}  
+"akamaiedgedns": {  
+    "client_secret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",  
+    "host": "akaa-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxx.akamaiapis.net",  
+    "access_token": "akaa-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",  
+    "client_token": "akaa-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",  
+    "contract_id": "X-XXXX",  
+    "group_id": "NNNNNN"  
+}  
+{% endhighlight %}  
+
+## Usage
+
+A new zone created by DNSControl:
+
+```
+dnscontrol create-domains
+```
+
+automatically creates SOA and authoritative NS records.
+
+Akamai assigns a unique set of authoritative nameservers for each contract.  These authorities should be
+used as the NS records on all zones belonging to this contract.
+
+The NS records for these authorities have a TTL of 86400.
+
+Add:
+
+```
+NAMESERVER_TTL(86400)
+```
+
+modifier to the dnscontrol.js D() function so that DNSControl does not change the TTL of the authoritative NS records.
+
+Example 'dnsconfig.js':  
+{% highlight js %}  
+var REG_NONE = NewRegistrar('none', 'NONE');  
+var DNS_AKAMAIEDGEDNS = NewDnsProvider('akamaiedgedns', 'AKAMAIEDGEDNS');  
+
+D('example.com', REG_NONE, DnsProvider(DNS_AKAMAIEDGEDNS),  
+  NAMESERVER_TTL(86400),  
+  AUTODNSSEC_ON,  
+  AKAMAICDN("@", "www.preconfigured.edgesuite.net", TTL(20)),  
+  A('foo','1.2.3.4')  
+);  
+{% endhighlight %}  
+
+AKAMAICDN is a proprietary record type that is used to configure [Zone Apex Mapping](https://blogs.akamai.com/2019/08/fast-dns-zone-apex-mapping-dnssec.html).
+The AKAMAICDN target must be preconfigured in the Akamai network.
diff --git a/docs/provider-list.md b/docs/provider-list.md
index ca4279b4..d188c36c 100644
--- a/docs/provider-list.md
+++ b/docs/provider-list.md
@@ -72,6 +72,7 @@ provided to help community members support their code independently.
 Maintainers of contributed providers:
 
 * `AXFRDDNS` @hnrgrgr
+* `AKAMAIEDGEDNS` @svernick
 * `CLOUDNS` @pragmaton
 * `CSCGLOBAL` @Air-New-Zealand
 * `DESEC` @D3luxee
diff --git a/go.mod b/go.mod
index b3c10bbb..401a9ec8 100644
--- a/go.mod
+++ b/go.mod
@@ -3,36 +3,35 @@ module github.com/StackExchange/dnscontrol/v3
 go 1.16
 
 require (
-	github.com/Azure/azure-sdk-for-go v55.1.0+incompatible
+	github.com/Azure/azure-sdk-for-go v55.2.0+incompatible
 	github.com/Azure/go-autorest/autorest v0.11.18 // indirect
 	github.com/Azure/go-autorest/autorest/azure/auth v0.5.7
 	github.com/Azure/go-autorest/autorest/to v0.4.0
 	github.com/DisposaBoy/JsonConfigReader v0.0.0-20201129172854-99cf318d67e7
 	github.com/PuerkitoBio/goquery v1.6.1
 	github.com/TomOnTime/utfutil v0.0.0-20200626160131-0b0178852c8f
+	github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1
 	github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883
 	github.com/andybalholm/cascadia v1.2.0 // indirect
-	github.com/aws/aws-sdk-go v1.38.53
+	github.com/aws/aws-sdk-go v1.38.58
 	github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6
 	github.com/bhendo/go-powershell v0.0.0-20190719160123-219e7fb4e41e
-	github.com/billputer/go-namecheap v0.0.0-20170915210158-0c7adb0710f8
+	github.com/billputer/go-namecheap v0.0.0-20210108011502-994a912fb7f9
 	github.com/boombuler/barcode v1.0.1 // indirect
 	github.com/cenkalti/backoff v2.2.1+incompatible // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
 	github.com/daaku/go.zipexe v1.0.1 // indirect
-	github.com/digitalocean/godo v1.61.0
+	github.com/digitalocean/godo v1.62.0
 	github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c
-	github.com/dnsimple/dnsimple-go v0.62.0
-	github.com/exoscale/egoscale v0.23.0
+	github.com/dnsimple/dnsimple-go v0.70.1
+	github.com/exoscale/egoscale v1.19.0
 	github.com/fatih/color v1.10.0 // indirect
 	github.com/frankban/quicktest v1.11.3 // indirect
 	github.com/go-acme/lego v2.7.2+incompatible
 	github.com/go-gandi/go-gandi v0.0.0-20200921091836-0d8a64b9cc09
 	github.com/gobwas/glob v0.2.4-0.20181002190808-e7a84e9525fe
 	github.com/gofrs/uuid v4.0.0+incompatible // indirect
-	github.com/golang/snappy v0.0.3 // indirect
-	github.com/google/go-github/v35 v35.2.0
-	github.com/google/go-querystring v1.0.1-0.20190318165438-c8c88dbee036 // indirect
+	github.com/google/go-github/v35 v35.3.0
 	github.com/gopherjs/jquery v0.0.0-20191017083323-73f4c7416038
 	github.com/hashicorp/errwrap v1.1.0 // indirect
 	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
@@ -49,32 +48,30 @@ require (
 	github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04
 	github.com/nrdcg/goinwx v0.8.1
 	github.com/oracle/oci-go-sdk/v32 v32.0.0
-	github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014
+	github.com/ovh/go-ovh v1.1.0
 	github.com/philhug/opensrs-go v0.0.0-20171126225031-9dfa7433020d
 	github.com/pierrec/lz4 v2.6.0+incompatible // indirect
 	github.com/pkg/errors v0.9.1
 	github.com/pquerna/otp v1.3.0
 	github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494
-	github.com/renier/xmlrpc v0.0.0-20191022213033-ce560eccbd00 // indirect
 	github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	github.com/sergi/go-diff v1.1.0 // indirect
 	github.com/smartystreets/assertions v1.0.0 // indirect
-	github.com/smartystreets/goconvey v1.6.4 // indirect
-	github.com/softlayer/softlayer-go v0.0.0-20170804160555-5e1c8cccc730
+	github.com/softlayer/softlayer-go v1.0.3
 	github.com/stretchr/objx v0.3.0 // indirect
 	github.com/stretchr/testify v1.7.0
 	github.com/tdewolff/minify/v2 v2.9.17
 	github.com/urfave/cli/v2 v2.3.0
-	github.com/vultr/govultr v1.0.0
+	github.com/vultr/govultr v1.1.1
 	github.com/xddxdd/ottoext v0.0.0-20210101073831-439879ee6281
 	golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect
-	golang.org/x/net v0.0.0-20210525063256-abc453219eb5
+	golang.org/x/net v0.0.0-20210610132358-84b48f89b13b
 	golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c
 	golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
-	google.golang.org/api v0.47.0
+	google.golang.org/api v0.48.0
 	gopkg.in/ini.v1 v1.62.0 // indirect
-	gopkg.in/ns1/ns1-go.v2 v2.4.4
+	gopkg.in/ns1/ns1-go.v2 v2.5.1
 	gopkg.in/yaml.v2 v2.4.0
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
 )
diff --git a/go.sum b/go.sum
index d1f3c4a1..df6df3fd 100644
--- a/go.sum
+++ b/go.sum
@@ -17,8 +17,9 @@ cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKP
 cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
 cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
 cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
-cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8=
 cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
+cloud.google.com/go v0.83.0 h1:bAMqZidYkmIsUqe6PtkEPT7Q+vfizScn+jfNA6jwK9c=
+cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
 cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@@ -37,8 +38,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-github.com/Azure/azure-sdk-for-go v55.1.0+incompatible h1:B+zsJuu6v/LyVPO59e6/7T/JoU6+eLLE574wLJ1NT6A=
-github.com/Azure/azure-sdk-for-go v55.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/Azure/azure-sdk-for-go v55.2.0+incompatible h1:TL2/vJWJEPOrmv97nHcbvjXES0Ntlb9P95hqGA1J2dU=
+github.com/Azure/azure-sdk-for-go v55.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
 github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
 github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
 github.com/Azure/go-autorest/autorest v0.11.17/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
@@ -75,6 +76,8 @@ github.com/PuerkitoBio/goquery v1.6.1 h1:FgjbQZKl5HTmcn4sKBgvx8vv63nhyhIpv7lJpFG
 github.com/PuerkitoBio/goquery v1.6.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
 github.com/TomOnTime/utfutil v0.0.0-20200626160131-0b0178852c8f h1:MXp+2PP1RxWWoE3qmOecVblerzKCryXkFXq9er+EDr8=
 github.com/TomOnTime/utfutil v0.0.0-20200626160131-0b0178852c8f/go.mod h1:FiuynIwe98RFhWI8nZ0dnsldPVsBy9rHH1hn2WYwme4=
+github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1 h1:bLzehmpyCwQiqCE1Qe9Ny6fbFqs7hPlmo9vKv2orUxs=
+github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1/go.mod h1:kX6YddBkXqqywAe8c9LyvgTCyFuZCTMF4cRPQhc3Fy8=
 github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
 github.com/alecthomas/kong v0.2.2/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
 github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
@@ -85,16 +88,16 @@ github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxB
 github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
-github.com/aws/aws-sdk-go v1.38.53 h1:Qj5OvKPrDGTiCnWj+kwQXAlBO6OaFBH/WaRzJPZPg3w=
-github.com/aws/aws-sdk-go v1.38.53/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
+github.com/aws/aws-sdk-go v1.38.58 h1:s4BKYcepKuX73xRTSRI3dQCfAM3zwmKgTLvjG/wtBEM=
+github.com/aws/aws-sdk-go v1.38.58/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 h1:4NNbNM2Iq/k57qEu7WfL67UrbPq1uFWxW4qODCohi+0=
 github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6/go.mod h1:J29hk+f9lJrblVIfiJOtTFk+OblBawmib4uz/VdKzlg=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 github.com/bhendo/go-powershell v0.0.0-20190719160123-219e7fb4e41e h1:KCjb01YiNoRaJ5c+SbnPLWjVzU9vqRYHg3e5JcN50nM=
 github.com/bhendo/go-powershell v0.0.0-20190719160123-219e7fb4e41e/go.mod h1:f7vw6ObmmNcyFQLhZX9eUGBJGpnwTJFDvVjqZxIxHWY=
-github.com/billputer/go-namecheap v0.0.0-20170915210158-0c7adb0710f8 h1:sIv3xbwhhAG94a62Q/rrSBtrWcXiYgldNOeqifyKSgo=
-github.com/billputer/go-namecheap v0.0.0-20170915210158-0c7adb0710f8/go.mod h1:bqqNsI2akL+lLWyApkYY0cxquWPKwEBU0Wd3chi3TEg=
+github.com/billputer/go-namecheap v0.0.0-20210108011502-994a912fb7f9 h1:2vQTbEJvFsyd1VefzZ34GUkUD6TkJleYYJh9/25WBE4=
+github.com/billputer/go-namecheap v0.0.0-20210108011502-994a912fb7f9/go.mod h1:bqqNsI2akL+lLWyApkYY0cxquWPKwEBU0Wd3chi3TEg=
 github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
 github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
 github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
@@ -121,15 +124,15 @@ github.com/daaku/go.zipexe v1.0.1/go.mod h1:5xWogtqlYnfBXkSB1o9xysukNP9GTvaNkqzU
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/digitalocean/godo v1.61.0 h1:PChvUn5+Ajwvl7caOJoFbcM9r+y91ssQbM0W0k3pnZU=
-github.com/digitalocean/godo v1.61.0/go.mod h1:p7dOjjtSBqCTUksqtA5Fd3uaKs9kyTq2xcz76ulEJRU=
+github.com/digitalocean/godo v1.62.0 h1:7Gw2KFsWkxl36qJa0s50tgXaE0Cgm51JdRP+MFQvNnM=
+github.com/digitalocean/godo v1.62.0/go.mod h1:p7dOjjtSBqCTUksqtA5Fd3uaKs9kyTq2xcz76ulEJRU=
 github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
 github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
 github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
 github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c h1:+Zo5Ca9GH0RoeVZQKzFJcTLoAixx5s5Gq3pTIS+n354=
 github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c/go.mod h1:HJGU9ULdREjOcVGZVPB5s6zYmHi1RxzT71l2wQyLmnE=
-github.com/dnsimple/dnsimple-go v0.62.0 h1:Lr4U8F08FBL90rMEtQMR547RebifgwTPGCRC/+6ZW4g=
-github.com/dnsimple/dnsimple-go v0.62.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg=
+github.com/dnsimple/dnsimple-go v0.70.1 h1:cSZndVjttLpgplDuesY4LFIvfKf/zRA1J7mCATBbzSM=
+github.com/dnsimple/dnsimple-go v0.70.1/go.mod h1:F9WHww9cC76hrnwGFfAfrqdW99j3MOYasQcIwTS/aUk=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -138,8 +141,8 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y
 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/exoscale/egoscale v0.23.0 h1:hoUDzrO8yNoobNdnrRvlRFjfg3Ng0vQTrv6bXRJu6z0=
-github.com/exoscale/egoscale v0.23.0/go.mod h1:hRo78jkjkCDKpivQdRBEpNYF5+cVpCJCPDg2/r45KaY=
+github.com/exoscale/egoscale v1.19.0 h1:DbSyyzaPyxDBgGOuduCU26rQ4PJb/GP+8srXRabRf5U=
+github.com/exoscale/egoscale v1.19.0/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
 github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
@@ -213,16 +216,18 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-github/v35 v35.2.0 h1:s/soW8jauhjUC3rh8JI0FePuocj0DEI9DNBg/bVplE8=
-github.com/google/go-github/v35 v35.2.0/go.mod h1:s0515YVTI+IMrDoy9Y4pHt9ShGpzHvHO8rZ7L7acgvs=
+github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-github/v35 v35.3.0 h1:fU+WBzuukn0VssbayTT+Zo3/ESKX9JYWjbZTLOTEyho=
+github.com/google/go-github/v35 v35.3.0/go.mod h1:yWB7uCcVWaUbUP74Aq3whuMySRMatyRmq5U9FTNlbio=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
-github.com/google/go-querystring v1.0.1-0.20190318165438-c8c88dbee036 h1:Avad62mreCc9la5buHvHZXbvsY+GPYUVjd8xsi48FYY=
-github.com/google/go-querystring v1.0.1-0.20190318165438-c8c88dbee036/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
+github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
+github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
@@ -234,7 +239,10 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe
 github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
@@ -287,6 +295,7 @@ github.com/hexonet/go-sdk/v3 v3.5.2 h1:zNY5NjlKdamBHy9NKmX4WSAL9wvKJ+KoWLrrQAbmC
 github.com/hexonet/go-sdk/v3 v3.5.2/go.mod h1:X/TQ5RQ7MMNsTajP4/lr3/eBkOoz8qUiha2lydNBGZE=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
 github.com/jarcoal/httpmock v1.0.8 h1:8kI16SoO6LQKgPE7PvQuV+YuD/inwHd7fOOe2zMbo4k=
 github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
 github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
@@ -333,6 +342,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
 github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ=
 github.com/kolo/xmlrpc v0.0.0-20201022064351-38db28db192b h1:iNjcivnc6lhbvJA3LD622NPrUponluJrBWPIwGG/3Bg=
 github.com/kolo/xmlrpc v0.0.0-20201022064351-38db28db192b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
@@ -385,9 +396,11 @@ github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH
 github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
 github.com/oracle/oci-go-sdk/v32 v32.0.0 h1:SSbzrQO3WRcPJEZ8+b3SFPYsPtkFM96clqrp03lrwbU=
 github.com/oracle/oci-go-sdk/v32 v32.0.0/go.mod h1:aZc4jC59IuNP3cr5y1nj555QvwojMX2nMJaBiozuuEs=
-github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014 h1:37VE5TYj2m/FLA9SNr4z0+A0JefvTmR60Zwf8XSEV7c=
-github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ=
+github.com/ovh/go-ovh v1.1.0 h1:bHXZmw8nTgZin4Nv7JuaLs0KG5x54EQR7migYTd1zrk=
+github.com/ovh/go-ovh v1.1.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA=
 github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
+github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
 github.com/philhug/opensrs-go v0.0.0-20171126225031-9dfa7433020d h1:nf4+lHs8TQeIGFYZMcNg4iQOnZndLfYxnQaKEdqHVA4=
 github.com/philhug/opensrs-go v0.0.0-20171126225031-9dfa7433020d/go.mod h1:VDIGNBy0tHXMDAu4gxHFQJDq3NuwqUxw2Kok7wi+6ck=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
@@ -408,8 +421,6 @@ github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7q
 github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494 h1:wSmWgpuccqS2IOfmYrbRiUgv+g37W5suLLLxwwniTSc=
 github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494/go.mod h1:yipyliwI08eQ6XwDm1fEwKPdF/xdbkiHtrU+1Hg+vc4=
-github.com/renier/xmlrpc v0.0.0-20191022213033-ce560eccbd00 h1:mKU5SDtoxNCBexJlsba8dNKN8SoDNMibWxVEowW1fig=
-github.com/renier/xmlrpc v0.0.0-20191022213033-ce560eccbd00/go.mod h1:gRAiPF5C5Nd0eyyRdqIu9qTiFSoZzpTq727b5B8fkkU=
 github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac h1:kYPjbEN6YPYWWHI6ky1J813KzIq/8+Wg4TO4xU7A/KU=
 github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@@ -422,15 +433,20 @@ github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIH
 github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
 github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8=
 github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
 github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/softlayer/softlayer-go v0.0.0-20170804160555-5e1c8cccc730 h1:NFtcXPhyIM81jGPrxh5OvdRM1esrM2lIYlO0SFm3KOg=
-github.com/softlayer/softlayer-go v0.0.0-20170804160555-5e1c8cccc730/go.mod h1:Cw4GTlQccdRGSEf6KiMju767x0NEHE0YIVPJSaXjlsw=
+github.com/softlayer/softlayer-go v1.0.3 h1:9FONm5xzQ9belQtbdryR6gBg4EF6hX6lrjNKi0IvZkU=
+github.com/softlayer/softlayer-go v1.0.3/go.mod h1:6HepcfAXROz0Rf63krk5hPZyHT6qyx2MNvYyHof7ik4=
+github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e h1:3OgWYFw7jxCZPcvAg+4R8A50GZ+CCkARF10lxu2qDsQ=
+github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e/go.mod h1:fKZCUVdirrxrBpwd9wb+lSoVixvpwAu8eHzbQB2tums=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=
 github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@@ -449,10 +465,13 @@ github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
 github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
 github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
-github.com/vultr/govultr v1.0.0 h1:yeJrYp9wyA4xXaQZ7eOL2u1wKn2JU79HjRevwvpxbJ4=
-github.com/vultr/govultr v1.0.0/go.mod h1:wZZXZbYbqyY1n3AldoeYNZK4Wnmmoq6dNFkvd5TV3ss=
+github.com/vultr/govultr v1.1.1 h1:ntltxMYyJstjKz1v2CLNxFZiqjlt/8HqD1GZeJ/fAMc=
+github.com/vultr/govultr v1.1.1/go.mod h1:QXCNTRg0nwu95ayiMC3feYvrAFTLnj94s2FiibIpoC4=
 github.com/xddxdd/ottoext v0.0.0-20210101073831-439879ee6281 h1:QruJ/b9zaFRxtNYvekIXlqWh/BoJJrSJg+FdmMNp8cw=
 github.com/xddxdd/ottoext v0.0.0-20210101073831-439879ee6281/go.mod h1:8Hr+gV9GtucFQOptKfzuYxqg++CyIjg4E7QCR7F3Dxw=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
+github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -553,8 +572,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20210525063256-abc453219eb5 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo=
-golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210610132358-84b48f89b13b h1:k+E048sYJHyVnsr1GDrRZWQ32D2C7lWs9JRc0bel53A=
+golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -588,6 +607,7 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -627,8 +647,9 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E=
 golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603125802-9665404d3644 h1:CA1DEQ4NdKphKeL70tvsWNdT5oFh1lOjihRcEDROi0I=
+golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -680,6 +701,7 @@ golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapK
 golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
 golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
 golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
+golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
@@ -694,6 +716,7 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f
 golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -720,8 +743,9 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q
 google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
 google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
 google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
-google.golang.org/api v0.47.0 h1:sQLWZQvP6jPGIP4JGPkJu4zHswrv81iobiyszr3b/0I=
 google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
+google.golang.org/api v0.48.0 h1:RDAPWfNFY06dffEXfn7hZF5Fr1ZbnChzfQZAPyBd1+I=
+google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -769,8 +793,10 @@ google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6D
 google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
-google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384 h1:z+j74wi4yV+P7EtK9gPLGukOk7mFOy9wMQaC0wNb7eY=
 google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08 h1:pc16UedxnxXXtGxHCSUhafAoVHQZ0yXl8ZelMH4EETc=
+google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
 google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
@@ -791,8 +817,11 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5
 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/grpc v1.37.1 h1:ARnQJNWxGyYJpdf/JXscNlQr/uv607ZPU9Z7ogHi+iI=
+google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
 google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
+google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -814,15 +843,18 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/errgo.v1 v1.0.0-20161222125816-442357a80af5/go.mod h1:u0ALmqvLRxLI95fkdCEWrE6mhWYZW1aMOJHp5YXLHTg=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
-gopkg.in/h2non/gock.v1 v1.0.14 h1:fTeu9fcUvSnLNacYvYI54h+1/XEteDyHvrVCZEEEYNM=
 gopkg.in/h2non/gock.v1 v1.0.14/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
+gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0=
+gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
 gopkg.in/httprequest.v1 v1.1.1/go.mod h1:/CkavNL+g3qLOrpFHVrEx4NKepeqR4XTZWNj4sGGjz0=
+gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
 gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/mgo.v2 v2.0.0-20160818015218-f2b6f6c918c4/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
 gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
-gopkg.in/ns1/ns1-go.v2 v2.4.4 h1:xV/rJE2m8p3IncnwMEFrv36K4NDnRXu4CoIkgq+AZek=
-gopkg.in/ns1/ns1-go.v2 v2.4.4/go.mod h1:GMnKY+ZuoJ+lVLL+78uSTjwTz2jMazq6AfGKQOYhsPk=
+gopkg.in/ns1/ns1-go.v2 v2.5.1 h1:yDCffh6bVBQuOUHuikJ2CC3cnMmsu8biEEwtLkpxVSI=
+gopkg.in/ns1/ns1-go.v2 v2.5.1/go.mod h1:GMnKY+ZuoJ+lVLL+78uSTjwTz2jMazq6AfGKQOYhsPk=
 gopkg.in/readline.v1 v1.0.0-20160726135117-62c6fe619375/go.mod h1:lNEQeAhU009zbRxng+XOj5ITVgY24WcbNnQopyfKoYQ=
 gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
 gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
diff --git a/models/domain.go b/models/domain.go
index f3fa4c51..5d342b94 100644
--- a/models/domain.go
+++ b/models/domain.go
@@ -77,7 +77,7 @@ func (dc *DomainConfig) Punycode() error {
 
 		// Set the target:
 		switch rec.Type { // #rtype_variations
-		case "ALIAS", "MX", "NS", "CNAME", "PTR", "SRV", "URL", "URL301", "FRAME", "R53_ALIAS", "NS1_URLFWD":
+		case "ALIAS", "MX", "NS", "CNAME", "PTR", "SRV", "URL", "URL301", "FRAME", "R53_ALIAS", "NS1_URLFWD", "AKAMAICDN":
 			// These rtypes are hostnames, therefore need to be converted (unlike, for example, an AAAA record)
 			t, err := idna.ToASCII(rec.GetTargetField())
 			if err != nil {
diff --git a/models/record.go b/models/record.go
index 1137afd1..a29d693f 100644
--- a/models/record.go
+++ b/models/record.go
@@ -499,10 +499,10 @@ func downcase(recs []*RecordConfig) {
 		r.Name = strings.ToLower(r.Name)
 		r.NameFQDN = strings.ToLower(r.NameFQDN)
 		switch r.Type { // #rtype_variations
-		case "ANAME", "CNAME", "DS", "MX", "NS", "PTR", "NAPTR", "SRV":
+		case "ANAME", "CNAME", "DS", "MX", "NS", "PTR", "NAPTR", "SRV", "TLSA", "AKAMAICDN":
 			// These record types have a target that is case insensitive, so we downcase it.
 			r.target = strings.ToLower(r.target)
-		case "A", "AAAA", "ALIAS", "CAA", "IMPORT_TRANSFORM", "TLSA", "TXT", "SSHFP", "CF_REDIRECT", "CF_TEMP_REDIRECT":
+		case "A", "AAAA", "ALIAS", "CAA", "IMPORT_TRANSFORM", "TXT", "SSHFP", "CF_REDIRECT", "CF_TEMP_REDIRECT":
 			// These record types have a target that is case sensitive, or is an IP address. We leave them alone.
 			// Do nothing.
 		case "SOA":
diff --git a/models/t_parse.go b/models/t_parse.go
index 4c47b077..00aba081 100644
--- a/models/t_parse.go
+++ b/models/t_parse.go
@@ -29,7 +29,7 @@ func (r *RecordConfig) PopulateFromString(rtype, contents, origin string) error
 			return fmt.Errorf("invalid IP in AAAA record: %s", contents)
 		}
 		return r.SetTargetIP(ip) // Reformat to canonical form.
-	case "ALIAS", "ANAME", "CNAME", "NS", "PTR":
+	case "AKAMAICDN", "ALIAS", "ANAME", "CNAME", "NS", "PTR":
 		return r.SetTarget(contents)
 	case "CAA":
 		return r.SetTargetCAAString(contents)
diff --git a/models/target.go b/models/target.go
index 2e035844..ffe8401b 100644
--- a/models/target.go
+++ b/models/target.go
@@ -91,7 +91,7 @@ func (rc *RecordConfig) GetTargetSortable() string {
 func (rc *RecordConfig) GetTargetDebug() string {
 	content := fmt.Sprintf("%s %s %s %d", rc.Type, rc.NameFQDN, rc.target, rc.TTL)
 	switch rc.Type { // #rtype_variations
-	case "A", "AAAA", "CNAME", "NS", "PTR", "TXT":
+	case "A", "AAAA", "CNAME", "NS", "PTR", "TXT", "AKAMAICDN":
 		// Nothing special.
 	case "DS":
 		content += fmt.Sprintf(" ds_algorithm=%d ds_keytag=%d ds_digesttype=%d ds_digest=%s", rc.DsAlgorithm, rc.DsKeyTag, rc.DsDigestType, rc.DsDigest)
diff --git a/pkg/js/helpers.js b/pkg/js/helpers.js
index ec3c8763..3689c669 100644
--- a/pkg/js/helpers.js
+++ b/pkg/js/helpers.js
@@ -215,6 +215,9 @@ var A = recordBuilder('A');
 // AAAA(name,ip, recordModifiers...)
 var AAAA = recordBuilder('AAAA');
 
+// AKAMAICDN(name, target, recordModifiers...)
+var AKAMAICDN = recordBuilder('AKAMAICDN');
+
 // ALIAS(name,target, recordModifiers...)
 var ALIAS = recordBuilder('ALIAS');
 
diff --git a/pkg/normalize/validate.go b/pkg/normalize/validate.go
index 201ca204..3852188b 100644
--- a/pkg/normalize/validate.go
+++ b/pkg/normalize/validate.go
@@ -248,7 +248,7 @@ func importTransform(srcDomain, dstDomain *models.DomainConfig, transforms []tra
 			r := newRec()
 			r.SetTarget(transformCNAME(r.GetTargetField(), srcDomain.Name, dstDomain.Name))
 			dstDomain.Records = append(dstDomain.Records, r)
-		case "MX", "NAPTR", "NS", "SOA", "SRV", "TXT", "CAA", "TLSA":
+		case "AKAMAICDN", "MX", "NAPTR", "NS", "SOA", "SRV", "TXT", "CAA", "TLSA":
 			// Not imported.
 			continue
 		default:
@@ -555,6 +555,7 @@ var providerCapabilityChecks = []pairTypeCapability{
 	capabilityCheck("SRV", providers.CanUseSRV),
 	capabilityCheck("TLSA", providers.CanUseTLSA),
 	capabilityCheck("AZURE_ALIAS", providers.CanUseAzureAlias),
+	capabilityCheck("AKAMAICDN", providers.CanUseAKAMAICDN),
 
 	// DS needs special record-level checks
 	{
diff --git a/pkg/printer/printer.go b/pkg/printer/printer.go
index 65ae8826..ab0df97b 100644
--- a/pkg/printer/printer.go
+++ b/pkg/printer/printer.go
@@ -105,7 +105,7 @@ func (c ConsolePrinter) StartDNSProvider(provider string, skip bool) {
 	if skip {
 		lbl = " (skipping)\n"
 	}
-	fmt.Fprintf(c.Writer, "----- DNS Provider: %s...%s", provider, lbl)
+	fmt.Fprintf(c.Writer, "----- DNS Provider: %s...%s\n", provider, lbl)
 }
 
 // StartRegistrar is called at the start of each new registrar.
@@ -114,7 +114,7 @@ func (c ConsolePrinter) StartRegistrar(provider string, skip bool) {
 	if skip {
 		lbl = " (skipping)\n"
 	}
-	fmt.Fprintf(c.Writer, "----- Registrar: %s...%s", provider, lbl)
+	fmt.Fprintf(c.Writer, "----- Registrar: %s...%s\n", provider, lbl)
 }
 
 // EndProvider is called at the end of each provider.
diff --git a/providers/_all/all.go b/providers/_all/all.go
index 2f1bbbb0..3b268c08 100644
--- a/providers/_all/all.go
+++ b/providers/_all/all.go
@@ -4,6 +4,7 @@ package all
 import (
 	// Define all known providers here. They should each register themselves with the providers package via init function.
 	_ "github.com/StackExchange/dnscontrol/v3/providers/activedir"
+	_ "github.com/StackExchange/dnscontrol/v3/providers/akamaiedgedns"
 	_ "github.com/StackExchange/dnscontrol/v3/providers/axfrddns"
 	_ "github.com/StackExchange/dnscontrol/v3/providers/azuredns"
 	_ "github.com/StackExchange/dnscontrol/v3/providers/bind"
diff --git a/providers/akamaiedgedns/akamaiEdgeDnsProvider.go b/providers/akamaiedgedns/akamaiEdgeDnsProvider.go
new file mode 100644
index 00000000..91c5306a
--- /dev/null
+++ b/providers/akamaiedgedns/akamaiEdgeDnsProvider.go
@@ -0,0 +1,247 @@
+package akamaiedgedns
+
+/*
+Akamai Edge DNS provider
+
+For information about Akamai Edge DNS, see:
+https://www.akamai.com/us/en/products/security/edge-dns.jsp
+https://learn.akamai.com/en-us/products/cloud_security/edge_dns.html
+https://www.akamai.com/us/en/multimedia/documents/product-brief/edge-dns-product-brief.pdf
+*/
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/StackExchange/dnscontrol/v3/models"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/printer"
+	"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
+	"github.com/StackExchange/dnscontrol/v3/providers"
+	"strings"
+)
+
+var features = providers.DocumentationNotes{
+	// The default for unlisted capabilities is 'Cannot'.
+	// See providers/capabilities.go for the entire list of capabilties.
+	providers.CanUseAlias:            providers.Cannot(),
+	providers.CanUseCAA:              providers.Can(),
+	providers.CanUseDS:               providers.Cannot(),
+	providers.CanUseDSForChildren:    providers.Can(),
+	providers.CanUsePTR:              providers.Can(),
+	providers.CanUseNAPTR:            providers.Can(),
+	providers.CanUseSRV:              providers.Can(),
+	providers.CanUseSSHFP:            providers.Can(),
+	providers.CanUseTLSA:             providers.Can(),
+	providers.CanAutoDNSSEC:          providers.Can(),
+	providers.CantUseNOPURGE:         providers.Cannot(),
+	providers.DocOfficiallySupported: providers.Cannot(),
+	providers.DocDualHost:            providers.Can(),
+	providers.CanUseSOA:              providers.Cannot(),
+	providers.DocCreateDomains:       providers.Can(),
+	providers.CanGetZones:            providers.Can(),
+	providers.CanUseAKAMAICDN:        providers.Can(),
+}
+
+type edgeDNSProvider struct {
+	contractID string
+	groupID    string
+}
+
+func init() {
+	fns := providers.DspFuncs{
+		Initializer:   newEdgeDNSDSP,
+		RecordAuditor: AuditRecords,
+	}
+	providers.RegisterDomainServiceProviderType("AKAMAIEDGEDNS", fns, features)
+	providers.RegisterCustomRecordType("AKAMAICDN", "AKAMAIEDGEDNS", "")
+}
+
+// DnsServiceProvider
+func newEdgeDNSDSP(config map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
+	clientSecret := config["client_secret"]
+	host := config["host"]
+	accessToken := config["access_token"]
+	clientToken := config["client_token"]
+	contractID := config["contract_id"]
+	groupID := config["group_id"]
+
+	if clientSecret == "" {
+		return nil, fmt.Errorf("creds.json: client_secret must not be empty")
+	}
+	if host == "" {
+		return nil, fmt.Errorf("creds.json: host must not be empty")
+	}
+	if accessToken == "" {
+		return nil, fmt.Errorf("creds.json: accessToken must not be empty")
+	}
+	if clientToken == "" {
+		return nil, fmt.Errorf("creds.json: clientToken must not be empty")
+	}
+	if contractID == "" {
+		return nil, fmt.Errorf("creds.json: contractID must not be empty")
+	}
+	if groupID == "" {
+		return nil, fmt.Errorf("creds.json: groupID must not be empty")
+	}
+
+	initialize(clientSecret, host, accessToken, clientToken)
+
+	api := &edgeDNSProvider{
+		contractID: contractID,
+		groupID:    groupID,
+	}
+	return api, nil
+}
+
+// EnsureDomainExists configures a new zone if the zone does not already exist.
+func (a *edgeDNSProvider) EnsureDomainExists(domain string) error {
+	if zoneDoesExist(domain) {
+		printer.Debugf("Zone %s already exists\n", domain)
+		return nil
+	}
+	return createZone(domain, a.contractID, a.groupID)
+}
+
+// GetDomainCorrections return a list of corrections. Each correction is a text string describing the change
+// and a function that, if called, will make the change.
+// “dnscontrol preview” simply prints the text strings.
+// "dnscontrol push" prints the strings and calls the functions.
+func (a *edgeDNSProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
+	err := dc.Punycode()
+	if err != nil {
+		return nil, err
+	}
+
+	existingRecords, err := getRecords(dc.Name)
+	if err != nil {
+		return nil, err
+	}
+
+	models.PostProcessRecords(existingRecords)
+	txtutil.SplitSingleLongTxt(dc.Records)
+
+	keysToUpdate, err := (diff.New(dc)).ChangedGroups(existingRecords)
+	if err != nil {
+		return nil, err
+	}
+
+	existingRecordsMap := make(map[models.RecordKey][]*models.RecordConfig)
+	for _, r := range existingRecords {
+		key := models.RecordKey{NameFQDN: r.NameFQDN, Type: r.Type}
+		existingRecordsMap[key] = append(existingRecordsMap[key], r)
+	}
+
+	desiredRecordsMap := dc.Records.GroupedByKey()
+
+	// Deletes must occur first. For example, if replacing a existing CNAME with an A of the same name:
+	//    DELETE CNAME foo.example.net
+	// must occur before
+	//    CREATE A foo.example.net
+	// because both an A and a CNAME for the same name is not allowed.
+
+	corrections := []*models.Correction{}     // deletes first
+	lastCorrections := []*models.Correction{} // creates and replaces last
+
+	for key, msg := range keysToUpdate {
+		existing, okExisting := existingRecordsMap[key]
+		desired, okDesired := desiredRecordsMap[key]
+
+		if okExisting && !okDesired {
+			// In the existing map but not in the desired map: Delete
+			corrections = append(corrections, &models.Correction{
+				Msg: strings.Join(msg, "\n   "),
+				F: func() error {
+					return deleteRecordset(existing, dc.Name)
+				},
+			})
+			printer.Debugf("deleteRecordset: %s %s\n", key.NameFQDN, key.Type)
+			for _, rdata := range existing {
+				printer.Debugf("  Rdata: %s\n", rdata.GetTargetCombined())
+			}
+		} else if !okExisting && okDesired {
+			// Not in the existing map but in the desired map: Create
+			lastCorrections = append(lastCorrections, &models.Correction{
+				Msg: strings.Join(msg, "\n   "),
+				F: func() error {
+					return createRecordset(desired, dc.Name)
+				},
+			})
+			printer.Debugf("createRecordset: %s %s\n", key.NameFQDN, key.Type)
+			for _, rdata := range desired {
+				printer.Debugf("  Rdata: %s\n", rdata.GetTargetCombined())
+			}
+		} else if okExisting && okDesired {
+			// In the existing map and in the desired map: Replace
+			lastCorrections = append(lastCorrections, &models.Correction{
+				Msg: strings.Join(msg, "\n   "),
+				F: func() error {
+					return replaceRecordset(desired, dc.Name)
+				},
+			})
+			printer.Debugf("replaceRecordset: %s %s\n", key.NameFQDN, key.Type)
+			for _, rdata := range desired {
+				printer.Debugf("  Rdata: %s\n", rdata.GetTargetCombined())
+			}
+		}
+	}
+
+	// Deletes first, then creates and replaces
+	corrections = append(corrections, lastCorrections...)
+
+	// AutoDnsSec correction
+	existingAutoDNSSecEnabled, err := isAutoDNSSecEnabled(dc.Name)
+	if err != nil {
+		return nil, err
+	}
+
+	desiredAutoDNSSecEnabled := dc.AutoDNSSEC == "on"
+
+	if !existingAutoDNSSecEnabled && desiredAutoDNSSecEnabled {
+		// Existing false (disabled), Desired true (enabled)
+		corrections = append(corrections, &models.Correction{
+			Msg: "Enable AutoDnsSec\n",
+			F: func() error {
+				return autoDNSSecEnable(true, dc.Name)
+			},
+		})
+		printer.Debugf("autoDNSSecEnable: Enable AutoDnsSec for zone %s\n", dc.Name)
+	} else if existingAutoDNSSecEnabled && !desiredAutoDNSSecEnabled {
+		// Existing true (enabled), Desired false (disabled)
+		corrections = append(corrections, &models.Correction{
+			Msg: "Disable AutoDnsSec\n",
+			F: func() error {
+				return autoDNSSecEnable(false, dc.Name)
+			},
+		})
+		printer.Debugf("autoDNSSecEnable: Disable AutoDnsSec for zone %s\n", dc.Name)
+	}
+
+	return corrections, nil
+}
+
+// GetNameservers returns the nameservers for a domain.
+func (a *edgeDNSProvider) GetNameservers(domain string) ([]*models.Nameserver, error) {
+	authorities, err := getAuthorities(a.contractID)
+	if err != nil {
+		return nil, err
+	}
+	return models.ToNameserversStripTD(authorities)
+}
+
+// GetZoneRecords returns an array of RecordConfig structs for a zone.
+func (a *edgeDNSProvider) GetZoneRecords(domain string) (models.Records, error) {
+	records, err := getRecords(domain)
+	if err != nil {
+		return nil, err
+	}
+	return records, nil
+}
+
+// ListZones returns all DNS zones managed by this provider.
+func (a *edgeDNSProvider) ListZones() ([]string, error) {
+	zones, err := listZones(a.contractID)
+	if err != nil {
+		return nil, err
+	}
+	return zones, nil
+}
diff --git a/providers/akamaiedgedns/akamaiEdgeDnsService.go b/providers/akamaiedgedns/akamaiEdgeDnsService.go
new file mode 100644
index 00000000..76a3df8c
--- /dev/null
+++ b/providers/akamaiedgedns/akamaiEdgeDnsService.go
@@ -0,0 +1,305 @@
+package akamaiedgedns
+
+/*
+For information about Akamai's "Edge DNS Zone Management API", see:
+https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html
+
+For information about "AkamaiOPEN-edgegrid-golang" library, see:
+https://github.com/akamai/AkamaiOPEN-edgegrid-golang
+*/
+
+import (
+	"fmt"
+	"github.com/StackExchange/dnscontrol/v3/models"
+	"github.com/StackExchange/dnscontrol/v3/pkg/printer"
+	dnsv2 "github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v2"
+	"github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid"
+)
+
+// initialize initializes the "Akamai OPEN EdgeGrid" library
+func initialize(clientSecret string, host string, accessToken string, clientToken string) {
+
+	eg := edgegrid.Config{
+		ClientSecret: clientSecret,
+		Host:         host,
+		AccessToken:  accessToken,
+		ClientToken:  clientToken,
+		MaxBody:      131072,
+		Debug:        false,
+	}
+	dnsv2.Init(eg)
+}
+
+// zoneDoesExist returns true if the zone exists, false otherwise.
+func zoneDoesExist(zonename string) bool {
+	_, err := dnsv2.GetZone(zonename)
+	return err == nil
+}
+
+// createZone create a new zone and creates SOA and NS records for the zone.
+// Akamai assigns a unique set of authoritative nameservers for each contract. These authorities should be
+// used as the NS records on all zones belonging to this contract.
+func createZone(zonename string, contractID string, groupID string) error {
+	zone := &dnsv2.ZoneCreate{
+		Zone:                  zonename,
+		Type:                  "PRIMARY",
+		Comment:               "This zone created by DNSControl (http://dnscontrol.org)",
+		SignAndServe:          false,
+		SignAndServeAlgorithm: "RSA_SHA512",
+		ContractId:            contractID,
+	}
+
+	queryArgs := &dnsv2.ZoneQueryString{
+		Contract: contractID,
+		Group:    groupID,
+	}
+
+	err := dnsv2.ValidateZone(zone)
+	if err != nil {
+		return fmt.Errorf("Invalid value provided for zone. Error: %s", err.Error())
+	}
+
+	err = zone.Save(*queryArgs)
+	if err != nil {
+		return fmt.Errorf("Zone create failed. Error: %s", err.Error())
+	}
+
+	// Indirectly create NS and SOA records
+	err = zone.SaveChangelist()
+	if err != nil {
+		return fmt.Errorf("Zone initialization failed. SOA and NS records need to be created")
+	}
+	err = zone.SubmitChangelist()
+	if err != nil {
+		return fmt.Errorf("Zone create failed. Error: %s", err.Error())
+	}
+
+	printer.Printf("Created zone: %s\n", zone.Zone)
+	printer.Printf("  Type: %s\n", zone.Type)
+	printer.Printf("  Comment: %s\n", zone.Comment)
+	printer.Printf("  SignAndServe: %v\n", zone.SignAndServe)
+	printer.Printf("  SignAndServeAlgorithm: %s\n", zone.SignAndServeAlgorithm)
+	printer.Printf("  ContractId: %s\n", zone.ContractId)
+	printer.Printf("  GroupId: %s\n", queryArgs.Group)
+
+	return nil
+}
+
+// listZones lists all zones associated with this contract.
+func listZones(contractID string) ([]string, error) {
+	queryArgs := dnsv2.ZoneListQueryArgs{
+		ContractIds: contractID,
+		ShowAll:     true,
+	}
+
+	zoneListResp, err := dnsv2.ListZones(queryArgs)
+	if err != nil {
+		return nil, fmt.Errorf("Zone List retrieval failed. Error: %s", err.Error())
+	}
+
+	edgeDNSZones := zoneListResp.Zones // what we have
+	var zones []string                 // what we return
+
+	for _, edgeDNSZone := range edgeDNSZones {
+		zones = append(zones, edgeDNSZone.Zone)
+	}
+
+	return zones, nil
+}
+
+// isAutoDNSSecEnabled returns true if AutoDNSSEC (SignAndServe) is enabled, false otherwise.
+func isAutoDNSSecEnabled(zonename string) (bool, error) {
+	zone, err := dnsv2.GetZone(zonename)
+	if err != nil {
+		if dnsv2.IsConfigDNSError(err) && err.(dnsv2.ConfigDNSError).NotFound() {
+			return false, fmt.Errorf("Zone %s does not exist. Error: %s",
+				zonename, err.Error())
+		}
+		return false, fmt.Errorf("Error retrieving information for zone %s. Error: %s",
+			zonename, err.Error())
+	}
+	return zone.SignAndServe, nil
+}
+
+// autoDNSSecEnable enables or disables AutoDNSSEC (SignAndServe) for the zone.
+func autoDNSSecEnable(enable bool, zonename string) error {
+	zone, err := dnsv2.GetZone(zonename)
+	if err != nil {
+		if dnsv2.IsConfigDNSError(err) && err.(dnsv2.ConfigDNSError).NotFound() {
+			return fmt.Errorf("Zone %s does not exist. Error: %s",
+				zonename, err.Error())
+		}
+		return fmt.Errorf("Error retrieving information for zone %s. Error: %s",
+			zonename, err.Error())
+	}
+
+	algorithm := "RSA_SHA512"
+	if zone.SignAndServeAlgorithm != "" {
+		algorithm = zone.SignAndServeAlgorithm
+	}
+
+	modifiedzone := &dnsv2.ZoneCreate{
+		Zone:                  zone.Zone,
+		Type:                  zone.Type,
+		Masters:               zone.Masters,
+		Comment:               zone.Comment,
+		SignAndServe:          enable, // AutoDNSSEC
+		SignAndServeAlgorithm: algorithm,
+		TsigKey:               zone.TsigKey,
+		EndCustomerId:         zone.EndCustomerId,
+		ContractId:            zone.ContractId,
+	}
+
+	queryArgs := dnsv2.ZoneQueryString{}
+
+	err = modifiedzone.Update(queryArgs)
+	if err != nil {
+		return fmt.Errorf("Error updating zone %s. Error: %s",
+			zonename, err.Error())
+	}
+
+	return nil
+}
+
+// getAuthorities returns the list of authoritative nameservers for the contract.
+// Akamai assigns a unique set of authoritative nameservers for each contract. These authorities should be
+// used as the NS records on all zones belonging to this contract.
+func getAuthorities(contractID string) ([]string, error) {
+	authorityResponse, err := dnsv2.GetAuthorities(contractID)
+	if err != nil {
+		return nil, fmt.Errorf("getAuthorities - ContractID %s: Authorities retrieval failed. Error: %s",
+			contractID, err.Error())
+	}
+	contracts := authorityResponse.Contracts
+	if len(contracts) != 1 {
+		return nil, fmt.Errorf("getAuthorities - ContractID %s: Expected 1 element in array but got %d",
+			contractID, len(contracts))
+	}
+	cid := contracts[0].ContractID
+	if cid != contractID {
+		return nil, fmt.Errorf("getAuthorities - ContractID %s: Got authorities for wrong contractID (%s)",
+			contractID, cid)
+	}
+	authorities := contracts[0].Authorities
+	return authorities, nil
+}
+
+// rcToRs converts DNSControl RecordConfig records to an AkamaiEdgeDNS recordset.
+func rcToRs(records []*models.RecordConfig, zonename string) (*dnsv2.RecordBody, error) {
+	if len(records) == 0 {
+		return nil, fmt.Errorf("No records to replace")
+	}
+
+	akaRecord := &dnsv2.RecordBody{
+		Name:       records[0].NameFQDN,
+		RecordType: records[0].Type,
+		TTL:        int(records[0].TTL),
+	}
+
+	for _, r := range records {
+		akaRecord.Target = append(akaRecord.Target, r.GetTargetCombined())
+	}
+
+	return akaRecord, nil
+}
+
+// createRecordset creates a new AkamaiEdgeDNS recordset in the zone.
+func createRecordset(records []*models.RecordConfig, zonename string) error {
+	akaRecord, err := rcToRs(records, zonename)
+	if err != nil {
+		return err
+	}
+
+	err = akaRecord.Save(zonename, true)
+	if err != nil {
+		return fmt.Errorf("Recordset creation failed. Error: %s", err.Error())
+	}
+	return nil
+}
+
+// replaceRecordset replaces an existing AkamaiEdgeDNS recordset in the zone.
+func replaceRecordset(records []*models.RecordConfig, zonename string) error {
+	akaRecord, err := rcToRs(records, zonename)
+	if err != nil {
+		return err
+	}
+
+	err = akaRecord.Update(zonename, true)
+	if err != nil {
+		return fmt.Errorf("Recordset update failed. Error: %s", err.Error())
+	}
+	return nil
+}
+
+// deleteRecordset deletes an existing AkamaiEdgeDNS recordset in the zone.
+func deleteRecordset(records []*models.RecordConfig, zonename string) error {
+	akaRecord, err := rcToRs(records, zonename)
+	if err != nil {
+		return err
+	}
+
+	err = akaRecord.Delete(zonename, true)
+	if err != nil {
+		if dnsv2.IsConfigDNSError(err) && err.(dnsv2.ConfigDNSError).NotFound() {
+			return fmt.Errorf("Recordset not found")
+		}
+		return fmt.Errorf("Failed to delete recordset. Error: %s", err.Error())
+	}
+	return nil
+}
+
+/*
+  Example AkamaiEdgeDNS Recordset (as JSON):
+        {
+            "name": "test.com",
+            "rdata": [
+                "a7.akafp.net.",
+                "a4.akafp.net.",
+                "a0.akafp.net."
+            ],
+            "ttl": 10000,
+            "type": "NS"
+        }
+*/
+
+// getRecords returns all RecordConfig records in the zone.
+func getRecords(zonename string) ([]*models.RecordConfig, error) {
+	queryArgs := dnsv2.RecordsetQueryArgs{ShowAll: true}
+
+	rsetResp, err := dnsv2.GetRecordsets(zonename, queryArgs)
+	if err != nil {
+		return nil, fmt.Errorf("Recordset list retrieval failed. Error: %s", err.Error())
+	}
+
+	akaRecordsets := rsetResp.Recordsets     // what we have
+	var recordConfigs []*models.RecordConfig // what we return
+
+	// For each AkamaiEdgeDNS recordset...
+	for _, akarecset := range akaRecordsets {
+		akaname := akarecset.Name
+		akatype := akarecset.Type
+		akattl := akarecset.TTL
+
+		// Don't report the existence of an SOA record (because DnsControl will try to delete the SOA record).
+		if akatype == "SOA" {
+			continue
+		}
+
+		// ... convert the recordset into 1 or more RecordConfig structs
+		for _, r := range akarecset.Rdata {
+			rc := &models.RecordConfig{
+				Type: akatype,
+				TTL:  uint32(akattl),
+			}
+			rc.SetLabelFromFQDN(akaname, zonename)
+			err = rc.PopulateFromString(akatype, r, zonename)
+			if err != nil {
+				return nil, err
+			}
+
+			recordConfigs = append(recordConfigs, rc)
+		}
+	}
+
+	return recordConfigs, nil
+}
diff --git a/providers/akamaiedgedns/auditrecords.go b/providers/akamaiedgedns/auditrecords.go
new file mode 100644
index 00000000..4729c6d8
--- /dev/null
+++ b/providers/akamaiedgedns/auditrecords.go
@@ -0,0 +1,8 @@
+package akamaiedgedns
+
+import "github.com/StackExchange/dnscontrol/v3/models"
+
+// AuditRecords returns an error if any records are not supportable by this provider.
+func AuditRecords(records []*models.RecordConfig) error {
+	return nil
+}
diff --git a/providers/capabilities.go b/providers/capabilities.go
index 58a239fe..0a00be13 100644
--- a/providers/capabilities.go
+++ b/providers/capabilities.go
@@ -69,6 +69,9 @@ const (
 
 	// CanUseSOA indicates the provider supports full management of a zone's SOA record
 	CanUseSOA
+
+	// CanUseAKAMAICDN indicates the provider support the specific AKAMAICDN records that only the Akamai EdgeDns provider supports
+	CanUseAKAMAICDN
 )
 
 var providerCapabilities = map[string]map[Capability]bool{}


Test patch:
diff --git a/integrationTest/integration_test.go b/integrationTest/integration_test.go
index 8ca87b77..5c4ccfb6 100644
--- a/integrationTest/integration_test.go
+++ b/integrationTest/integration_test.go
@@ -986,8 +986,9 @@ func makeTests(t *testing.T) []*TestGroup {
 
 		testgroup("pager1201",
 			only(
-				//"MSDNS",     //  No paging done. No need to test.
-				//"AZURE_DNS", // Currently failing. See https://github.com/StackExchange/dnscontrol/issues/770
+				//"MSDNS",         //  No paging done. No need to test.
+				//"AKAMAIEDGEDNS", //  No paging done. No need to test.
+				//"AZURE_DNS",     // Currently failing. See https://github.com/StackExchange/dnscontrol/issues/770
 				"HEXONET",
 				"HOSTINGDE",
 				//"ROUTE53", // Currently failing. See https://github.com/StackExchange/dnscontrol/issues/908
diff --git a/integrationTest/providers.json b/integrationTest/providers.json
index 68f08f9f..a5f82708 100644
--- a/integrationTest/providers.json
+++ b/integrationTest/providers.json
@@ -58,6 +58,15 @@
     "api_key": "$DNSMADEEASY_API_KEY",
     "secret_key": "$DNSMADEEASY_SECRET_KEY"
   },
+  "AKAMAIEDGEDNS": {
+    "client_secret": "$AED_CLIENT_SECRET",
+    "host": "$AED_HOST",
+    "access_token": "$AED_ACCESS_TOKEN",
+    "client_token": "$AED_CLIENT_TOKEN",
+    "contract_id": "$AED_CONTRACT_ID",
+    "group_id": "$AED_GROUP_ID",
+    "domain": "$AED_DOMAIN"
+  },
   "EXOSCALE": {
     "apikey": "$EXOSCALE_API_KEY",
     "dns-endpoint": "https://api.exoscale.ch/dns",
diff --git a/pkg/js/parse_tests/015-tlsa/foo.com.zone b/pkg/js/parse_tests/015-tlsa/foo.com.zone
index db5cbadc..9e872198 100644
--- a/pkg/js/parse_tests/015-tlsa/foo.com.zone
+++ b/pkg/js/parse_tests/015-tlsa/foo.com.zone
@@ -1,2 +1,2 @@
 $TTL 300
-_443._tcp        IN TLSA  3 1 1 MDFiYTQ3MTljODBiNmZlOTExYjA5MWE3YzA1MTI0YjY0ZWVlY2U5NjRlMDljMDU4ZWY4Zjk4MDVkYWNhNTQ2YiAgLQo=
+_443._tcp        IN TLSA  3 1 1 mdfiytq3mtljodbinmzlotexyja5mwe3yza1mti0yjy0zwvly2u5njrlmdljmdu4zwy4zjk4mdvkywnhntq2yiaglqo=