Merge branch 'main' into branch_allrecs

This commit is contained in:
Thomas Limoncelli 2025-11-30 20:10:43 -05:00
commit 8c140c0b3b
No known key found for this signature in database
16 changed files with 409 additions and 37 deletions

View file

@ -52,7 +52,7 @@ jobs:
Write-Host "Integration test providers: $Providers" Write-Host "Integration test providers: $Providers"
echo "integration_test_providers=$(ConvertTo-Json -InputObject $Providers -Compress)" >> $env:GITHUB_OUTPUT echo "integration_test_providers=$(ConvertTo-Json -InputObject $Providers -Compress)" >> $env:GITHUB_OUTPUT
env: env:
PROVIDERS: "['AXFRDDNS', 'AXFRDDNS_DNSSEC', 'AZURE_DNS','BIND','BUNNY_DNS','CLOUDFLAREAPI','CLOUDNS','CNR','DIGITALOCEAN','FORTIGATE','GANDI_V5','GCLOUD','HEDNS','HEXONET','HUAWEICLOUD','INWX','JOKER','MYTHICBEASTS', 'NAMEDOTCOM','NS1','POWERDNS','ROUTE53','SAKURACLOUD','TRANSIP']" PROVIDERS: "['AXFRDDNS', 'AXFRDDNS_DNSSEC', 'AZURE_DNS','BIND','BUNNY_DNS','CLOUDFLAREAPI','CLOUDNS','CNR','DIGITALOCEAN','FORTIGATE','GANDI_V5','GCLOUD','HEDNS','HETZNER_V2','HEXONET','HUAWEICLOUD','INWX','JOKER','MYTHICBEASTS', 'NAMEDOTCOM','NS1','POWERDNS','ROUTE53','SAKURACLOUD','TRANSIP']"
ENV_CONTEXT: ${{ toJson(env) }} ENV_CONTEXT: ${{ toJson(env) }}
VARS_CONTEXT: ${{ toJson(vars) }} VARS_CONTEXT: ${{ toJson(vars) }}
SECRETS_CONTEXT: ${{ toJson(secrets) }} SECRETS_CONTEXT: ${{ toJson(secrets) }}
@ -87,6 +87,7 @@ jobs:
GANDI_V5_DOMAIN: ${{ vars.GANDI_V5_DOMAIN }} GANDI_V5_DOMAIN: ${{ vars.GANDI_V5_DOMAIN }}
GCLOUD_DOMAIN: ${{ vars.GCLOUD_DOMAIN }} GCLOUD_DOMAIN: ${{ vars.GCLOUD_DOMAIN }}
HEDNS_DOMAIN: ${{ vars.HEDNS_DOMAIN }} HEDNS_DOMAIN: ${{ vars.HEDNS_DOMAIN }}
HETZNER_V2_DOMAIN: ${{ vars.HETZNER_V2_DOMAIN }}
HEXONET_DOMAIN: ${{ vars.HEXONET_DOMAIN }} HEXONET_DOMAIN: ${{ vars.HEXONET_DOMAIN }}
HUAWEICLOUD_DOMAIN: ${{ vars.HUAWEICLOUD_DOMAIN }} HUAWEICLOUD_DOMAIN: ${{ vars.HUAWEICLOUD_DOMAIN }}
JOKER_DOMAIN: ${{ vars.JOKER_DOMAIN }} JOKER_DOMAIN: ${{ vars.JOKER_DOMAIN }}
@ -154,6 +155,8 @@ jobs:
HEDNS_TOTP_SECRET: ${{ secrets.HEDNS_TOTP_SECRET }} HEDNS_TOTP_SECRET: ${{ secrets.HEDNS_TOTP_SECRET }}
HEDNS_USERNAME: ${{ secrets.HEDNS_USERNAME }} HEDNS_USERNAME: ${{ secrets.HEDNS_USERNAME }}
# #
HETZNER_V2_API_TOKEN: ${{ secrets.HETZNER_V2_API_TOKEN }}
#
HEXONET_ENTITY: ${{ secrets.HEXONET_ENTITY }} HEXONET_ENTITY: ${{ secrets.HEXONET_ENTITY }}
HEXONET_PW: ${{ secrets.HEXONET_PW }} HEXONET_PW: ${{ secrets.HEXONET_PW }}
HEXONET_UID: ${{ secrets.HEXONET_UID }} HEXONET_UID: ${{ secrets.HEXONET_UID }}

View file

@ -39,7 +39,7 @@ changelog:
regexp: "(?i)^.*(major|new provider|feature)[(\\w)]*:+.*$" regexp: "(?i)^.*(major|new provider|feature)[(\\w)]*:+.*$"
order: 1 order: 1
- title: 'Provider-specific changes:' - title: 'Provider-specific changes:'
regexp: "(?i)((adguardhome|akamaiedge|autodns|axfrd|azure|azure_private_dns|bind|bunnydns|cloudflare|cloudflareapi_old|cloudns|cnr|cscglobal|desec|digitalocean|dnsimple|dnsmadeeasy|doh|domainnameshop|dynadot|easyname|exoscale|fortigate|gandi|gcloud|gcore|hedns|hetzner|hexonet|hostingde|huaweicloud|inwx|joker|linode|loopia|luadns|mythicbeasts|namecheap|namedotcom|netcup|netlify|ns1|opensrs|oracle|ovh|packetframe|porkbun|powerdns|realtimeregister|route53|rwth|sakuracloud|softlayer|transip|vultr).*:)+.*" regexp: "(?i)((adguardhome|akamaiedge|autodns|axfrd|azure|azure_private_dns|bind|bunnydns|cloudflare|cloudflareapi_old|cloudns|cnr|cscglobal|desec|digitalocean|dnsimple|dnsmadeeasy|doh|domainnameshop|dynadot|easyname|exoscale|fortigate|gandi|gcloud|gcore|hedns|hetzner|hetznerv2|hexonet|hostingde|huaweicloud|inwx|joker|linode|loopia|luadns|mythicbeasts|namecheap|namedotcom|netcup|netlify|ns1|opensrs|oracle|ovh|packetframe|porkbun|powerdns|realtimeregister|route53|rwth|sakuracloud|softlayer|transip|vultr).*:)+.*"
order: 2 order: 2
- title: 'Documentation:' - title: 'Documentation:'
regexp: "(?i)^.*(docs)[(\\w)]*:+.*$" regexp: "(?i)^.*(docs)[(\\w)]*:+.*$"

1
OWNERS
View file

@ -25,6 +25,7 @@ providers/gcloud @riyadhalnur
providers/gcore @xddxdd providers/gcore @xddxdd
providers/hedns @rblenkinsopp providers/hedns @rblenkinsopp
providers/hetzner @das7pad providers/hetzner @das7pad
providers/hetznerv2 @das7pad
providers/hexonet @KaiSchwarz-cnic providers/hexonet @KaiSchwarz-cnic
providers/hostingde @juliusrickert providers/hostingde @juliusrickert
providers/huaweicloud @huihuimoe providers/huaweicloud @huihuimoe

View file

@ -137,7 +137,8 @@
* [Gandi_v5](provider/gandi_v5.md) * [Gandi_v5](provider/gandi_v5.md)
* [Gcore](provider/gcore.md) * [Gcore](provider/gcore.md)
* [Google Cloud DNS](provider/gcloud.md) * [Google Cloud DNS](provider/gcloud.md)
* [Hetzner DNS Console](provider/hetzner.md) * [Hetzner DNS API](provider/hetzner_v2.md)
* [Hetzner DNS Console (legacy)](provider/hetzner.md)
* [HEXONET](provider/hexonet.md) * [HEXONET](provider/hexonet.md)
* [hosting.de](provider/hostingde.md) * [hosting.de](provider/hostingde.md)
* [Huawei Cloud DNS](provider/huaweicloud.md) * [Huawei Cloud DNS](provider/huaweicloud.md)

View file

@ -0,0 +1,54 @@
## Configuration
To use this provider, add an entry to `creds.json` with `TYPE` set to `HETZNER_V2`
along with a [Hetzner API Token](https://docs.hetzner.cloud/reference/cloud#getting-started).
Example:
{% code title="creds.json" %}
```json
{
"hetzner_v2": {
"TYPE": "HETZNER_V2",
"api_token": "your-api-token"
}
}
```
{% endcode %}
## Metadata
This provider does not recognize any special metadata fields unique to Hetzner DNS API.
## Usage
An example configuration:
{% code title="dnsconfig.js" %}
```javascript
var REG_NONE = NewRegistrar("none");
var DSP_HETZNER = NewDnsProvider("hetzner_v2");
D("example.com", REG_NONE, DnsProvider(DSP_HETZNER),
A("test", "1.2.3.4"),
);
```
{% endcode %}
## Activation
Create a new API Key in the
[Hetzner Console](https://docs.hetzner.cloud/reference/cloud#getting-started).
## Caveats
### NS
Removing the Hetzner provided NS records at the root is not possible.
### SOA
Hetzner DNS API does not allow changing the SOA record via their API.
There is an alternative method using an import of a full BIND file, but this
approach does not play nice with incremental changes or ignored records.
At this time you cannot update SOA records via DNSControl.

View file

@ -52,6 +52,7 @@ Jump to a table:
| [`GCORE`](gcore.md) | ❌ | ✅ | ❌ | | [`GCORE`](gcore.md) | ❌ | ✅ | ❌ |
| [`HEDNS`](hedns.md) | ❌ | ✅ | ❌ | | [`HEDNS`](hedns.md) | ❌ | ✅ | ❌ |
| [`HETZNER`](hetzner.md) | ❌ | ✅ | ❌ | | [`HETZNER`](hetzner.md) | ❌ | ✅ | ❌ |
| [`HETZNER_V2`](hetzner_v2.md) | ❌ | ✅ | ❌ |
| [`HEXONET`](hexonet.md) | ❌ | ✅ | ✅ | | [`HEXONET`](hexonet.md) | ❌ | ✅ | ✅ |
| [`HOSTINGDE`](hostingde.md) | ❌ | ✅ | ✅ | | [`HOSTINGDE`](hostingde.md) | ❌ | ✅ | ✅ |
| [`HUAWEICLOUD`](huaweicloud.md) | ❌ | ✅ | ❌ | | [`HUAWEICLOUD`](huaweicloud.md) | ❌ | ✅ | ❌ |
@ -112,6 +113,7 @@ Jump to a table:
| [`GCORE`](gcore.md) | ✅ | ✅ | ✅ | ✅ | | [`GCORE`](gcore.md) | ✅ | ✅ | ✅ | ✅ |
| [`HEDNS`](hedns.md) | ❔ | ✅ | ✅ | ✅ | | [`HEDNS`](hedns.md) | ❔ | ✅ | ✅ | ✅ |
| [`HETZNER`](hetzner.md) | ✅ | ✅ | ✅ | ✅ | | [`HETZNER`](hetzner.md) | ✅ | ✅ | ✅ | ✅ |
| [`HETZNER_V2`](hetzner_v2.md) | ✅ | ✅ | ✅ | ✅ |
| [`HEXONET`](hexonet.md) | ❔ | ✅ | ✅ | ❔ | | [`HEXONET`](hexonet.md) | ❔ | ✅ | ✅ | ❔ |
| [`HOSTINGDE`](hostingde.md) | ❔ | ✅ | ✅ | ✅ | | [`HOSTINGDE`](hostingde.md) | ❔ | ✅ | ✅ | ✅ |
| [`HUAWEICLOUD`](huaweicloud.md) | ❔ | ✅ | ✅ | ✅ | | [`HUAWEICLOUD`](huaweicloud.md) | ❔ | ✅ | ✅ | ✅ |
@ -169,6 +171,7 @@ Jump to a table:
| [`GCORE`](gcore.md) | ✅ | ❔ | ❌ | ✅ | ❔ | | [`GCORE`](gcore.md) | ✅ | ❔ | ❌ | ✅ | ❔ |
| [`HEDNS`](hedns.md) | ✅ | ❔ | ✅ | ✅ | ❌ | | [`HEDNS`](hedns.md) | ✅ | ❔ | ✅ | ✅ | ❌ |
| [`HETZNER`](hetzner.md) | ❌ | ❔ | ❌ | ❌ | ❌ | | [`HETZNER`](hetzner.md) | ❌ | ❔ | ❌ | ❌ | ❌ |
| [`HETZNER_V2`](hetzner_v2.md) | ❌ | ❔ | ❌ | ✅ | ❌ |
| [`HEXONET`](hexonet.md) | ❌ | ❔ | ❔ | ✅ | ❔ | | [`HEXONET`](hexonet.md) | ❌ | ❔ | ❔ | ✅ | ❔ |
| [`HOSTINGDE`](hostingde.md) | ✅ | ❔ | ❌ | ✅ | ✅ | | [`HOSTINGDE`](hostingde.md) | ✅ | ❔ | ❌ | ✅ | ✅ |
| [`HUAWEICLOUD`](huaweicloud.md) | ❌ | ❔ | ❌ | ❌ | ❌ | | [`HUAWEICLOUD`](huaweicloud.md) | ❌ | ❔ | ❌ | ❌ | ❌ |
@ -223,6 +226,7 @@ Jump to a table:
| [`GCORE`](gcore.md) | ❔ | ❌ | ✅ | ✅ | | [`GCORE`](gcore.md) | ❔ | ❌ | ✅ | ✅ |
| [`HEDNS`](hedns.md) | ❔ | ✅ | ✅ | ✅ | | [`HEDNS`](hedns.md) | ❔ | ✅ | ✅ | ✅ |
| [`HETZNER`](hetzner.md) | ❔ | ❌ | ✅ | ❔ | | [`HETZNER`](hetzner.md) | ❔ | ❌ | ✅ | ❔ |
| [`HETZNER_V2`](hetzner_v2.md) | ❔ | ❌ | ✅ | ✅ |
| [`HEXONET`](hexonet.md) | ❔ | ❔ | ✅ | ❔ | | [`HEXONET`](hexonet.md) | ❔ | ❔ | ✅ | ❔ |
| [`HOSTINGDE`](hostingde.md) | ❔ | ❌ | ✅ | ❔ | | [`HOSTINGDE`](hostingde.md) | ❔ | ❌ | ✅ | ❔ |
| [`HUAWEICLOUD`](huaweicloud.md) | ❔ | ❌ | ✅ | ❌ | | [`HUAWEICLOUD`](huaweicloud.md) | ❔ | ❌ | ✅ | ❌ |
@ -276,6 +280,7 @@ Jump to a table:
| [`GCORE`](gcore.md) | ✅ | ✅ | ❔ | ❌ | ❌ | | [`GCORE`](gcore.md) | ✅ | ✅ | ❔ | ❌ | ❌ |
| [`HEDNS`](hedns.md) | ✅ | ✅ | ❔ | ✅ | ❌ | | [`HEDNS`](hedns.md) | ✅ | ✅ | ❔ | ✅ | ❌ |
| [`HETZNER`](hetzner.md) | ✅ | ❔ | ❔ | ❌ | ✅ | | [`HETZNER`](hetzner.md) | ✅ | ❔ | ❔ | ❌ | ✅ |
| [`HETZNER_V2`](hetzner_v2.md) | ✅ | ✅ | ❔ | ❌ | ✅ |
| [`HEXONET`](hexonet.md) | ✅ | ❔ | ❔ | ❔ | ✅ | | [`HEXONET`](hexonet.md) | ✅ | ❔ | ❔ | ❔ | ✅ |
| [`HOSTINGDE`](hostingde.md) | ✅ | ❔ | ❔ | ✅ | ✅ | | [`HOSTINGDE`](hostingde.md) | ✅ | ❔ | ❔ | ✅ | ✅ |
| [`HUAWEICLOUD`](huaweicloud.md) | ✅ | ❌ | ❔ | ❌ | ❌ | | [`HUAWEICLOUD`](huaweicloud.md) | ✅ | ❌ | ❔ | ❌ | ❌ |
@ -320,6 +325,7 @@ Jump to a table:
| [`GCORE`](gcore.md) | ✅ | ❔ | ❌ | | [`GCORE`](gcore.md) | ✅ | ❔ | ❌ |
| [`HEDNS`](hedns.md) | ❌ | ❔ | ❌ | | [`HEDNS`](hedns.md) | ❌ | ❔ | ❌ |
| [`HETZNER`](hetzner.md) | ❌ | ❔ | ✅ | | [`HETZNER`](hetzner.md) | ❌ | ❔ | ✅ |
| [`HETZNER_V2`](hetzner_v2.md) | ❌ | ❔ | ✅ |
| [`HOSTINGDE`](hostingde.md) | ✅ | ❔ | ✅ | | [`HOSTINGDE`](hostingde.md) | ✅ | ❔ | ✅ |
| [`HUAWEICLOUD`](huaweicloud.md) | ❔ | ❔ | ❌ | | [`HUAWEICLOUD`](huaweicloud.md) | ❔ | ❔ | ❌ |
| [`INWX`](inwx.md) | ✅ | ❔ | ❔ | | [`INWX`](inwx.md) | ✅ | ❔ | ❔ |

11
go.mod
View file

@ -38,7 +38,7 @@ require (
github.com/miekg/dns v1.1.68 github.com/miekg/dns v1.1.68
github.com/mittwald/go-powerdns v0.6.7 github.com/mittwald/go-powerdns v0.6.7
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04
github.com/nrdcg/goinwx v0.11.0 github.com/nrdcg/goinwx v0.12.0
github.com/ovh/go-ovh v1.9.0 github.com/ovh/go-ovh v1.9.0
github.com/philhug/opensrs-go v0.0.0-20171126225031-9dfa7433020d github.com/philhug/opensrs-go v0.0.0-20171126225031-9dfa7433020d
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
@ -66,6 +66,7 @@ require (
github.com/fbiville/markdown-table-formatter v0.3.0 github.com/fbiville/markdown-table-formatter v0.3.0
github.com/google/go-cmp v0.7.0 github.com/google/go-cmp v0.7.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/hetznercloud/hcloud-go/v2 v2.30.0
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.174 github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.174
github.com/kylelemons/godebug v1.1.0 github.com/kylelemons/godebug v1.1.0
github.com/luadns/luadns-go v0.3.0 github.com/luadns/luadns-go v0.3.0
@ -97,8 +98,10 @@ require (
github.com/aws/aws-sdk-go-v2/service/sso v1.30.1 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.30.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5 // indirect
github.com/aws/smithy-go v1.23.2 // indirect github.com/aws/smithy-go v1.23.2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.24.0 // indirect github.com/bits-and-blooms/bitset v1.24.0 // indirect
github.com/boombuler/barcode v1.0.1 // indirect github.com/boombuler/barcode v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/deepmap/oapi-codegen v1.9.1 // indirect github.com/deepmap/oapi-codegen v1.9.1 // indirect
@ -126,10 +129,15 @@ require (
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/peterhellberg/link v1.2.0 // indirect github.com/peterhellberg/link v1.2.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect github.com/sergi/go-diff v1.2.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect
@ -147,6 +155,7 @@ require (
go.opentelemetry.io/otel v1.37.0 // indirect go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/mod v0.29.0 // indirect golang.org/x/mod v0.29.0 // indirect
golang.org/x/sync v0.18.0 // indirect golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect golang.org/x/sys v0.38.0 // indirect

26
go.sum
View file

@ -78,6 +78,8 @@ github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM=
github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
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 h1:4NNbNM2Iq/k57qEu7WfL67UrbPq1uFWxW4qODCohi+0=
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6/go.mod h1:J29hk+f9lJrblVIfiJOtTFk+OblBawmib4uz/VdKzlg= github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6/go.mod h1:J29hk+f9lJrblVIfiJOtTFk+OblBawmib4uz/VdKzlg=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/billputer/go-namecheap v0.0.0-20210108011502-994a912fb7f9 h1:2vQTbEJvFsyd1VefzZ34GUkUD6TkJleYYJh9/25WBE4= 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/billputer/go-namecheap v0.0.0-20210108011502-994a912fb7f9/go.mod h1:bqqNsI2akL+lLWyApkYY0cxquWPKwEBU0Wd3chi3TEg=
github.com/bits-and-blooms/bitset v1.24.0 h1:H4x4TuulnokZKvHLfzVRTHJfFfnHEeSYJizujEZvmAM= github.com/bits-and-blooms/bitset v1.24.0 h1:H4x4TuulnokZKvHLfzVRTHJfFfnHEeSYJizujEZvmAM=
@ -90,6 +92,8 @@ github.com/centralnicgroup-opensource/rtldev-middleware-go-sdk/v4 v4.0.7 h1:Jk7u
github.com/centralnicgroup-opensource/rtldev-middleware-go-sdk/v4 v4.0.7/go.mod h1:FnQtD0+Q/1NZxi0eEWN+3ZRyMsE9vzSB3YjyunkbKD0= github.com/centralnicgroup-opensource/rtldev-middleware-go-sdk/v4 v4.0.7/go.mod h1:FnQtD0+Q/1NZxi0eEWN+3ZRyMsE9vzSB3YjyunkbKD0=
github.com/centralnicgroup-opensource/rtldev-middleware-go-sdk/v5 v5.0.18 h1:RvyTDU0VmnUBd3Qm2i6irEXtCR2KRIxnRlD8l+5z/DY= github.com/centralnicgroup-opensource/rtldev-middleware-go-sdk/v5 v5.0.18 h1:RvyTDU0VmnUBd3Qm2i6irEXtCR2KRIxnRlD8l+5z/DY=
github.com/centralnicgroup-opensource/rtldev-middleware-go-sdk/v5 v5.0.18/go.mod h1:a6n4wXFHbMW0iJFxHIJR4PkgG5krP52nOVCBU0m+Obw= github.com/centralnicgroup-opensource/rtldev-middleware-go-sdk/v5 v5.0.18/go.mod h1:a6n4wXFHbMW0iJFxHIJR4PkgG5krP52nOVCBU0m+Obw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
@ -229,6 +233,8 @@ github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB1
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/hetznercloud/hcloud-go/v2 v2.30.0 h1:fgAUtCCw4PbJNSs9XPLHVu0//dTNMbPq8P/48ovmdG8=
github.com/hetznercloud/hcloud-go/v2 v2.30.0/go.mod h1:zv7x2kM7xyJ5mW/+y4HbfxQYhk8TE57ypTa1hofsYdw=
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.174 h1:FBlx7E5rl8doUTbizt+DXR0zU05Mu2oEYvc/2GMB7pc= github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.174 h1:FBlx7E5rl8doUTbizt+DXR0zU05Mu2oEYvc/2GMB7pc=
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.174/go.mod h1:M+yna96Fx9o5GbIUnF3OvVvQGjgfVSyeJbV9Yb1z/wI= github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.174/go.mod h1:M+yna96Fx9o5GbIUnF3OvVvQGjgfVSyeJbV9Yb1z/wI=
github.com/influxdata/tdigest v0.0.1 h1:XpFptwYmnEKUqmkcDjrzffswZ3nvNeevbUSLPP/ZzIY= github.com/influxdata/tdigest v0.0.1 h1:XpFptwYmnEKUqmkcDjrzffswZ3nvNeevbUSLPP/ZzIY=
@ -246,6 +252,8 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00=
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@ -300,6 +308,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAAxrQxRHEu7FkapwTuI2WmL1rw4g= github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAAxrQxRHEu7FkapwTuI2WmL1rw4g=
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8= github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
@ -307,8 +317,8 @@ github.com/nicholas-fedor/shoutrrr v0.12.0 h1:8mwJdfU+uBEybSymwQJMGl/grG7lvVUKbV
github.com/nicholas-fedor/shoutrrr v0.12.0/go.mod h1:WYiRalR4C43Qmd2zhPWGIFIxu633NB1hDM6Ap/DQcsA= github.com/nicholas-fedor/shoutrrr v0.12.0/go.mod h1:WYiRalR4C43Qmd2zhPWGIFIxu633NB1hDM6Ap/DQcsA=
github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 h1:Up6+btDp321ZG5/zdSLo48H9Iaq0UQGthrhWC6pCxzE= github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 h1:Up6+btDp321ZG5/zdSLo48H9Iaq0UQGthrhWC6pCxzE=
github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481/go.mod h1:yKZQO8QE2bHlgozqWDiRVqTFlLQSj30K/6SAK8EeYFw= github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481/go.mod h1:yKZQO8QE2bHlgozqWDiRVqTFlLQSj30K/6SAK8EeYFw=
github.com/nrdcg/goinwx v0.11.0 h1:GER0SE3POub7rxARt3Y3jRy1OON1hwF1LRxHz5xsFBw= github.com/nrdcg/goinwx v0.12.0 h1:ujdUqDBnaRSFwzVnImvPHYw3w3m9XgmGImNUw1GyMb4=
github.com/nrdcg/goinwx v0.11.0/go.mod h1:0BXSC0FxVtU4aTjX0Zw3x0DK32tjugLzeNIAGtwXvPQ= github.com/nrdcg/goinwx v0.12.0/go.mod h1:IrVKd3ZDbFiMjdPgML4CSxZAY9wOoqLvH44zv3NodJ0=
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns= github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
@ -333,7 +343,15 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs= github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494 h1:wSmWgpuccqS2IOfmYrbRiUgv+g37W5suLLLxwwniTSc= 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/qdm12/reprint v0.0.0-20200326205758-722754a53494/go.mod h1:yipyliwI08eQ6XwDm1fEwKPdF/xdbkiHtrU+1Hg+vc4=
github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY= github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
@ -426,6 +444,10 @@ go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFh
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=

View file

@ -176,6 +176,11 @@
"api_key": "$HETZNER_API_KEY", "api_key": "$HETZNER_API_KEY",
"domain": "$HETZNER_DOMAIN" "domain": "$HETZNER_DOMAIN"
}, },
"HETZNER_V2": {
"TYPE": "HETZNER_V2",
"api_token": "$HETZNER_V2_API_TOKEN",
"domain": "$HETZNER_V2_DOMAIN"
},
"HEXONET": { "HEXONET": {
"TYPE": "HEXONET", "TYPE": "HEXONET",
"apientity": "$HEXONET_ENTITY", "apientity": "$HEXONET_ENTITY",

View file

@ -19,7 +19,7 @@ func CorrectZoneRecords(driver models.DNSProvider, dc *models.DomainConfig) ([]*
models.CanonicalizeTargets(existingRecords, dc.Name) models.CanonicalizeTargets(existingRecords, dc.Name)
models.CanonicalizeTargets(dc.Records, dc.Name) models.CanonicalizeTargets(dc.Records, dc.Name)
// Copy dc so that any corrections code that wants to // Copy dc so that any correction code that wants to
// modify the records may. For example, if the provider only // modify the records may. For example, if the provider only
// supports certain TTL values, it will adjust the ones in // supports certain TTL values, it will adjust the ones in
// dc.Records. // dc.Records.

View file

@ -30,6 +30,7 @@ import (
_ "github.com/StackExchange/dnscontrol/v4/providers/gcore" _ "github.com/StackExchange/dnscontrol/v4/providers/gcore"
_ "github.com/StackExchange/dnscontrol/v4/providers/hedns" _ "github.com/StackExchange/dnscontrol/v4/providers/hedns"
_ "github.com/StackExchange/dnscontrol/v4/providers/hetzner" _ "github.com/StackExchange/dnscontrol/v4/providers/hetzner"
_ "github.com/StackExchange/dnscontrol/v4/providers/hetznerv2"
_ "github.com/StackExchange/dnscontrol/v4/providers/hexonet" _ "github.com/StackExchange/dnscontrol/v4/providers/hexonet"
_ "github.com/StackExchange/dnscontrol/v4/providers/hostingde" _ "github.com/StackExchange/dnscontrol/v4/providers/hostingde"
_ "github.com/StackExchange/dnscontrol/v4/providers/huaweicloud" _ "github.com/StackExchange/dnscontrol/v4/providers/huaweicloud"

View file

@ -0,0 +1,12 @@
package hetznerv2
import (
"github.com/StackExchange/dnscontrol/v4/models"
)
// AuditRecords returns a list of errors corresponding to the records
// that aren't supported by this provider. If all records are
// supported, an empty list is returned.
func AuditRecords(_ []*models.RecordConfig) []error {
return nil
}

View file

@ -0,0 +1,264 @@
package hetznerv2
import (
"context"
"encoding/json"
"errors"
"github.com/hetznercloud/hcloud-go/v2/hcloud"
"golang.org/x/net/idna"
"github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/pkg/diff2"
"github.com/StackExchange/dnscontrol/v4/pkg/txtutil"
"github.com/StackExchange/dnscontrol/v4/pkg/version"
"github.com/StackExchange/dnscontrol/v4/pkg/zonecache"
"github.com/StackExchange/dnscontrol/v4/providers"
)
var features = providers.DocumentationNotes{
// The default for unlisted capabilities is 'Cannot'.
// See providers/capabilities.go for the entire list of capabilities.
providers.CanAutoDNSSEC: providers.Cannot(),
providers.CanConcur: providers.Can(),
providers.CanGetZones: providers.Can(),
providers.CanOnlyDiff1Features: providers.Can(),
providers.CanUseAlias: providers.Cannot(),
providers.CanUseCAA: providers.Can(),
providers.CanUseDS: providers.Can(),
providers.CanUseDSForChildren: providers.Cannot(),
providers.CanUseLOC: providers.Cannot(),
providers.CanUseNAPTR: providers.Cannot(),
providers.CanUsePTR: providers.Can(),
providers.CanUseSOA: providers.Cannot(),
providers.CanUseSRV: providers.Can(),
providers.CanUseSVCB: providers.Can(),
providers.CanUseHTTPS: providers.Can(),
providers.CanUseSSHFP: providers.Cannot(),
providers.CanUseTLSA: providers.Can(),
providers.DocCreateDomains: providers.Can(),
providers.DocOfficiallySupported: providers.Cannot(),
providers.DocDualHost: providers.Can(),
}
func init() {
const providerName = "HETZNER_V2"
const providerMaintainer = "@das7pad"
fns := providers.DspFuncs{
Initializer: New,
RecordAuditor: AuditRecords,
}
providers.RegisterDomainServiceProviderType(providerName, fns, features)
providers.RegisterMaintainer(providerName, providerMaintainer)
}
// New creates a new API handle.
func New(settings map[string]string, _ json.RawMessage) (providers.DNSServiceProvider, error) {
apiToken := settings["api_token"]
if apiToken == "" {
return nil, errors.New("missing HETZNER_V2 api_token")
}
h := &hetznerv2Provider{
client: hcloud.NewClient(
hcloud.WithToken(apiToken),
hcloud.WithApplication("dnscontrol", version.Version()),
),
}
h.zoneCache = zonecache.New(h.fetchAllZones)
return h, nil
}
type hetznerv2Provider struct {
zoneCache zonecache.ZoneCache[*hcloud.Zone]
client *hcloud.Client
}
// fetchAllZones is used by the zonecache.ZoneCache.
func (h *hetznerv2Provider) fetchAllZones() (map[string]*hcloud.Zone, error) {
flat, err := h.client.Zone.All(context.Background())
if err != nil {
return nil, err
}
zones := make(map[string]*hcloud.Zone, len(flat))
for _, z := range flat {
zones[z.Name] = z
}
return zones, nil
}
// EnsureZoneExists creates a zone if it does not exist
func (h *hetznerv2Provider) EnsureZoneExists(domain string, _ map[string]string) error {
encoded, err := idna.ToASCII(domain)
if err != nil {
return err
}
if ok, err2 := h.zoneCache.HasZone(encoded); err2 != nil || ok {
return err2
}
result, _, err := h.client.Zone.Create(context.Background(), hcloud.ZoneCreateOpts{
Name: encoded,
Mode: hcloud.ZoneModePrimary,
})
if err != nil {
return err
}
err = h.client.Action.WaitFor(context.Background(), result.Action)
if err != nil {
return err
}
z, _, err := h.client.Zone.GetByID(context.Background(), result.Zone.ID)
if err != nil {
return err
}
h.zoneCache.SetZone(encoded, z)
return nil
}
// GetZoneRecordsCorrections returns a list of corrections that will turn existing records into dc.Records.
func (h *hetznerv2Provider) GetZoneRecordsCorrections(dc *models.DomainConfig, existingRecords models.Records) ([]*models.Correction, int, error) {
encoded, err := idna.ToASCII(dc.Name)
if err != nil {
return nil, 0, err
}
z, err := h.zoneCache.GetZone(encoded)
if err != nil {
return nil, 0, err
}
// Hetzner Cloud has a "ByRecordSet" API for DNS.
// At each label:rtype pair, we either delete all records or UPSERT the desired records.
instructions, actualChangeCount, err := diff2.ByRecordSet(existingRecords, dc, nil)
if err != nil {
return nil, 0, err
}
var reports []*models.Correction
for _, instruction := range instructions {
switch instruction.Type {
case diff2.REPORT:
reports = append(reports, &models.Correction{
Msg: instruction.MsgsJoined,
})
continue
case diff2.CREATE:
first := instruction.New[0]
ttl := int(first.TTL)
opts := hcloud.ZoneRRSetCreateOpts{
Name: first.Name,
Type: hcloud.ZoneRRSetType(first.Type),
TTL: &ttl,
}
for _, r := range instruction.New {
opts.Records = append(opts.Records, hcloud.ZoneRRSetRecord{
Value: r.GetTargetCombinedFunc(txtutil.EncodeQuoted),
})
}
reports = append(reports, &models.Correction{
F: func() error {
_, _, err2 := h.client.Zone.CreateRRSet(context.Background(), z, opts)
return err2
},
Msg: instruction.MsgsJoined,
})
case diff2.CHANGE:
rrSet := instruction.Old[0].Original.(*hcloud.ZoneRRSet)
reports = append(reports, &models.Correction{
F: func() error {
if instruction.New[0].TTL != instruction.Old[0].TTL {
ttl := int(instruction.New[0].TTL)
opts := hcloud.ZoneRRSetChangeTTLOpts{TTL: &ttl}
_, _, err2 := h.client.Zone.ChangeRRSetTTL(context.Background(), rrSet, opts)
if err2 != nil {
return err2
}
}
opts := hcloud.ZoneRRSetSetRecordsOpts{}
for _, r := range instruction.New {
opts.Records = append(opts.Records, hcloud.ZoneRRSetRecord{
Value: r.GetTargetCombinedFunc(txtutil.EncodeQuoted),
})
}
_, _, err2 := h.client.Zone.SetRRSetRecords(context.Background(), rrSet, opts)
return err2
},
Msg: instruction.MsgsJoined,
})
case diff2.DELETE:
reports = append(reports, &models.Correction{
F: func() error {
rc := instruction.Old[0].Original.(*hcloud.ZoneRRSet)
_, _, err2 := h.client.Zone.DeleteRRSet(context.Background(), rc)
return err2
},
Msg: instruction.MsgsJoined,
})
}
}
return reports, actualChangeCount, nil
}
// GetNameservers returns the nameservers for a domain.
func (h *hetznerv2Provider) GetNameservers(domain string) ([]*models.Nameserver, error) {
encoded, err := idna.ToASCII(domain)
if err != nil {
return nil, err
}
z, err := h.zoneCache.GetZone(encoded)
if err != nil {
return nil, err
}
return models.ToNameserversStripTD(z.AuthoritativeNameservers.Assigned)
}
// GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
func (h *hetznerv2Provider) GetZoneRecords(domain string, _ map[string]string) (models.Records, error) {
encoded, err := idna.ToASCII(domain)
if err != nil {
return nil, err
}
z, err := h.zoneCache.GetZone(encoded)
if err != nil {
return nil, err
}
opts := hcloud.ZoneRRSetListOpts{}
opts.PerPage = 100
records, err := h.client.Zone.AllRRSetsWithOpts(context.Background(), z, opts)
if err != nil {
return nil, err
}
existingRecords := make([]*models.RecordConfig, 0, len(records))
for _, rrSet := range records {
if rrSet.Type == hcloud.ZoneRRSetTypeSOA {
// SOA records are not available for editing, hide them.
continue
}
base := models.RecordConfig{
Type: string(rrSet.Type),
Original: rrSet,
}
base.SetLabel(rrSet.Name, z.Name)
if rrSet.TTL != nil {
base.TTL = uint32(*rrSet.TTL)
} else {
base.TTL = uint32(z.TTL)
}
for _, r := range rrSet.Records {
rc := base
if err = rc.PopulateFromStringFunc(rc.Type, r.Value, z.Name, txtutil.ParseQuoted); err != nil {
return nil, err
}
existingRecords = append(existingRecords, &rc)
}
}
return existingRecords, nil
}
// ListZones lists the zones on this account.
func (h *hetznerv2Provider) ListZones() ([]string, error) {
return h.zoneCache.GetZoneNames()
}

View file

@ -10,12 +10,8 @@ import (
// supported, an empty list is returned. // supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error { func AuditRecords(records []*models.RecordConfig) []error {
a := rejectif.Auditor{} a := rejectif.Auditor{}
a.Add("TXT", rejectif.TxtHasBackticks) // Last verified 2021-03-01
a.Add("TXT", rejectif.TxtHasBackticks) // Last verified 2021-03-01
a.Add("TXT", rejectif.TxtHasTrailingSpace) // Last verified 2021-03-01 a.Add("TXT", rejectif.TxtHasTrailingSpace) // Last verified 2021-03-01
a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2021-03-01
a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2021-03-01
return a.Audit(records) return a.Audit(records)
} }

View file

@ -10,25 +10,22 @@ const (
// testing shows 'AUTO' is what to expect if the domain has automatic // testing shows 'AUTO' is what to expect if the domain has automatic
// DNSSEC enabled. // DNSSEC enabled.
// AutoDNSSEC is the status for DNSSEC enabled with automatic management // AutoDNSSECStatus is the status for DNSSEC enabled with automatic management
AutoDNSSECStatus = "AUTO" AutoDNSSECStatus = "AUTO"
// ManualDNSSEC is the status for DNSSEC enabled with manual management // ManualDNSSECStatus is the status for DNSSEC enabled with manual management
ManualDNSSECStatus = "MANUAL" ManualDNSSECStatus = "MANUAL"
) )
// DNSSecStatus returns domain dnssec status // DNSSecStatus returns domain dnssec status
func (api *inwxAPI) DNSSecStatus(domain string) (string, error) { func (api *inwxAPI) DNSSecStatus(domain string) (string, error) {
resp, err := api.client.Dnssec.Info([]string{domain}) resp, err := api.client.Dnssec.Info([]string{domain})
if err != nil { if err != nil {
return "", err return "", err
} }
// domain has no DNSSEC configuration // domain has no DNSSEC configuration
if len(resp.Data) == 0 { if len(resp.Data) == 0 {
return "", nil return "", nil
} }
return resp.Data[0].DNSSecStatus, nil return resp.Data[0].DNSSecStatus, nil
} }
@ -40,16 +37,12 @@ func (api *inwxAPI) enableAutoDNSSEC(domain string) error {
if err != nil { if err != nil {
return err return err
} }
err = api.client.Dnssec.Enable(domain) err = api.client.Dnssec.Enable(domain)
return err return err
} }
// disableAutoDNSSEC disables automatic management of DNSSEC // disableAutoDNSSEC disables automatic management of DNSSEC
func (api *inwxAPI) disableAutoDNSSEC(domain string) error { func (api *inwxAPI) disableAutoDNSSEC(domain string) error {
err := api.client.Dnssec.Disable(domain) err := api.client.Dnssec.Disable(domain)
return err return err
} }

View file

@ -22,6 +22,9 @@ import (
/* /*
INWX Registrar and DNS provider INWX Registrar and DNS provider
Based on this great INWX API implementation:
https://github.com/nrdcg/goinwx
Info required in `creds.json`: Info required in `creds.json`:
- username - username
- password - password
@ -34,7 +37,6 @@ Either of the following settings is required when two factor authentication is e
Additional settings available in `creds.json`: Additional settings available in `creds.json`:
- sandbox (set to 1 to use the sandbox API from INWX) - sandbox (set to 1 to use the sandbox API from INWX)
*/ */
// InwxProductionDefaultNs contains the default INWX nameservers. // InwxProductionDefaultNs contains the default INWX nameservers.
@ -182,10 +184,10 @@ func makeNameserverRecordRequest(domain string, rec *models.RecordConfig) *goinw
switch rType := rec.Type; rType { switch rType := rec.Type; rType {
/* /*
INWX is a little bit special for CNAME,NS,MX and SRV records: INWX is a little bit special for CNAME, NS, MX and SRV records:
The API will not accept any target with a final dot but will The API will not accept any target with a final dot but will
instead always add this final dot internally. instead always add this final dot internally.
Records with empty targets (i.e. records with target ".") Records with empty targets (i.e., records with target ".")
are allowed. are allowed.
*/ */
case "CNAME", "NS", "ALIAS": case "CNAME", "NS", "ALIAS":
@ -219,14 +221,14 @@ func (api *inwxAPI) createRecord(domain string, rec *models.RecordConfig) error
} }
// updateRecord is used by GetDomainCorrections to update an existing record. // updateRecord is used by GetDomainCorrections to update an existing record.
func (api *inwxAPI) updateRecord(RecordID int, rec *models.RecordConfig) error { func (api *inwxAPI) updateRecord(RecordID string, rec *models.RecordConfig) error {
req := makeNameserverRecordRequest("", rec) req := makeNameserverRecordRequest("", rec)
err := api.client.Nameservers.UpdateRecord(RecordID, req) err := api.client.Nameservers.UpdateRecord(RecordID, req)
return err return err
} }
// deleteRecord is used by GetDomainCorrections to delete a record. // deleteRecord is used by GetDomainCorrections to delete a record.
func (api *inwxAPI) deleteRecord(RecordID int) error { func (api *inwxAPI) deleteRecord(RecordID string) error {
return api.client.Nameservers.DeleteRecord(RecordID) return api.client.Nameservers.DeleteRecord(RecordID)
} }
@ -244,7 +246,8 @@ func (api *inwxAPI) AutoDnssecToggle(dc *models.DomainConfig, corrections []*mod
} }
if dnssecStatus == ManualDNSSECStatus && dc.AutoDNSSEC != "" { if dnssecStatus == ManualDNSSECStatus && dc.AutoDNSSEC != "" {
return corrections, fmt.Errorf("INWX: Domain %s has manual DNSSEC enabled. Disable it before using AUTODNSSEC_ON/AUTODNSSEC_OFF", dc.Name) return corrections, fmt.Errorf("INWX: Domain %s has manual DNSSEC enabled. Disable it before using "+
"AUTODNSSEC_ON/AUTODNSSEC_OFF", dc.Name)
} }
if dnssecStatus != AutoDNSSECStatus && dc.AutoDNSSEC == "on" { if dnssecStatus != AutoDNSSECStatus && dc.AutoDNSSEC == "on" {
@ -289,23 +292,25 @@ func (api *inwxAPI) GetZoneRecordsCorrections(dc *models.DomainConfig, foundReco
return nil, 0, err return nil, 0, err
} }
for _, change := range changes { for _, change := range changes {
changeMsgs := change.MsgsJoined changeMessage := change.MsgsJoined
switch change.Type { switch change.Type {
case diff2.REPORT: case diff2.REPORT:
corrections = append(corrections, &models.Correction{Msg: changeMsgs}) corrections = append(corrections, &models.Correction{Msg: changeMessage})
case diff2.CHANGE: case diff2.CHANGE:
oldRec := change.Old[0] oldRec := change.Old[0]
newRec := change.New[0] newRec := change.New[0]
if isNullMX(newRec) || isNullMX(oldRec) { if isNullMX(newRec) || isNullMX(oldRec) {
// changing to or from a Null MX has to be delete then create // changing to or from a Null MX has to be deleted then create
deletes = append(deletes, &models.Correction{ deletes = append(deletes, &models.Correction{
Msg: color.RedString("- DELETE %s %s %s ttl=%d", oldRec.GetLabelFQDN(), oldRec.Type, oldRec.ToComparableNoTTL(), oldRec.TTL), Msg: color.RedString("- DELETE %s %s %s ttl=%d", oldRec.GetLabelFQDN(), oldRec.Type,
oldRec.ToComparableNoTTL(), oldRec.TTL),
F: func() error { F: func() error {
return api.deleteRecord(oldRec.Original.(goinwx.NameserverRecord).ID) return api.deleteRecord(oldRec.Original.(goinwx.NameserverRecord).ID)
}, },
}) })
deferred = append(deferred, &models.Correction{ deferred = append(deferred, &models.Correction{
Msg: color.GreenString("+ CREATE %s %s %s ttl=%d", newRec.GetLabelFQDN(), newRec.Type, newRec.ToComparableNoTTL(), newRec.TTL), Msg: color.GreenString("+ CREATE %s %s %s ttl=%d", newRec.GetLabelFQDN(), newRec.Type,
newRec.ToComparableNoTTL(), newRec.TTL),
F: func() error { F: func() error {
return api.createRecord(dc.Name, newRec) return api.createRecord(dc.Name, newRec)
}, },
@ -313,7 +318,7 @@ func (api *inwxAPI) GetZoneRecordsCorrections(dc *models.DomainConfig, foundReco
} else { } else {
recID := oldRec.Original.(goinwx.NameserverRecord).ID recID := oldRec.Original.(goinwx.NameserverRecord).ID
corrections = append(corrections, &models.Correction{ corrections = append(corrections, &models.Correction{
Msg: changeMsgs, Msg: changeMessage,
F: func() error { F: func() error {
return api.updateRecord(recID, newRec) return api.updateRecord(recID, newRec)
}, },
@ -322,7 +327,7 @@ func (api *inwxAPI) GetZoneRecordsCorrections(dc *models.DomainConfig, foundReco
} }
case diff2.CREATE: case diff2.CREATE:
creates = append(creates, &models.Correction{ creates = append(creates, &models.Correction{
Msg: changeMsgs, Msg: changeMessage,
F: func() error { F: func() error {
return api.createRecord(dc.Name, change.New[0]) return api.createRecord(dc.Name, change.New[0])
}, },
@ -330,7 +335,7 @@ func (api *inwxAPI) GetZoneRecordsCorrections(dc *models.DomainConfig, foundReco
case diff2.DELETE: case diff2.DELETE:
recID := change.Old[0].Original.(goinwx.NameserverRecord).ID recID := change.Old[0].Original.(goinwx.NameserverRecord).ID
deletes = append(deletes, &models.Correction{ deletes = append(deletes, &models.Correction{
Msg: changeMsgs, Msg: changeMessage,
F: func() error { return api.deleteRecord(recID) }, F: func() error { return api.deleteRecord(recID) },
}) })
default: default:
@ -343,7 +348,7 @@ func (api *inwxAPI) GetZoneRecordsCorrections(dc *models.DomainConfig, foundReco
return corrections, actualChangeCount, nil return corrections, actualChangeCount, nil
} }
// getDefaultNameservers returns string map with default nameservers based on e.g. sandbox mode. // getDefaultNameservers returns a string map with default nameservers based on e.g. sandbox mode.
func (api *inwxAPI) getDefaultNameservers() []string { func (api *inwxAPI) getDefaultNameservers() []string {
if api.sandbox { if api.sandbox {
return InwxSandboxDefaultNs return InwxSandboxDefaultNs