From 56cb356ef2d95275cc0bc5c7fa599396a32d23bb Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 25 Nov 2025 19:29:47 +0100 Subject: [PATCH] edgeone: add zones mapping (#2728) --- cmd/zz_gen_cmd_dnshelp.go | 1 + docs/content/dns/zz_gen_edgeone.md | 1 + providers/dns/dnspod/dnspod.go | 2 +- providers/dns/edgeone/edgeone.go | 19 +++++++++++---- providers/dns/edgeone/edgeone.toml | 1 + providers/dns/edgeone/edgeone_test.go | 24 +++++++++++++++++-- providers/dns/edgeone/wrapper.go | 33 ++++++++++++++++----------- 7 files changed, 61 insertions(+), 20 deletions(-) diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index deb93e6eb..2cba1b73a 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -1342,6 +1342,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(` - "EDGEONE_REGION": Region`) ew.writeln(` - "EDGEONE_SESSION_TOKEN": Access Key token`) ew.writeln(` - "EDGEONE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) + ew.writeln(` - "EDGEONE_ZONES_MAPPING": Mapping between DNS zones and site IDs. (ex: 'example.org:id1,example.com:id2')`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/edgeone`) diff --git a/docs/content/dns/zz_gen_edgeone.md b/docs/content/dns/zz_gen_edgeone.md index b7b5b1eec..227127d65 100644 --- a/docs/content/dns/zz_gen_edgeone.md +++ b/docs/content/dns/zz_gen_edgeone.md @@ -55,6 +55,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | `EDGEONE_REGION` | Region | | `EDGEONE_SESSION_TOKEN` | Access Key token | | `EDGEONE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | +| `EDGEONE_ZONES_MAPPING` | Mapping between DNS zones and site IDs. (ex: 'example.org:id1,example.com:id2') | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. More information [here]({{% ref "dns#configuration-and-credentials" %}}). diff --git a/providers/dns/dnspod/dnspod.go b/providers/dns/dnspod/dnspod.go index c9376b956..52a873c7b 100644 --- a/providers/dns/dnspod/dnspod.go +++ b/providers/dns/dnspod/dnspod.go @@ -165,7 +165,7 @@ func (d *DNSProvider) getHostedZone(domain string) (string, string, error) { } if hostedZone.ID == "" || hostedZone.ID == "0" { - return "", "", fmt.Errorf("zone %s not found in dnspod for domain %s", authZone, domain) + return "", "", fmt.Errorf("zone %s not found for domain %s", authZone, domain) } return hostedZone.ID.String(), hostedZone.Name, nil diff --git a/providers/dns/edgeone/edgeone.go b/providers/dns/edgeone/edgeone.go index 3402122bb..509a75c77 100644 --- a/providers/dns/edgeone/edgeone.go +++ b/providers/dns/edgeone/edgeone.go @@ -26,6 +26,7 @@ const ( EnvSecretKey = envNamespace + "SECRET_KEY" EnvRegion = envNamespace + "REGION" EnvSessionToken = envNamespace + "SESSION_TOKEN" + EnvZonesMapping = envNamespace + "ZONES_MAPPING" EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" @@ -40,6 +41,8 @@ type Config struct { Region string SessionToken string + ZonesMapping map[string]string + PropagationTimeout time.Duration PollingInterval time.Duration TTL int @@ -78,6 +81,14 @@ func NewDNSProvider() (*DNSProvider, error) { config.Region = env.GetOrDefaultString(EnvRegion, "") config.SessionToken = env.GetOrDefaultString(EnvSessionToken, "") + mapping := env.GetOrDefaultString(EnvZonesMapping, "") + if mapping != "" { + config.ZonesMapping, err = env.ParsePairs(mapping) + if err != nil { + return nil, fmt.Errorf("edgeone: zones mapping: %w", err) + } + } + return NewDNSProviderConfig(config) } @@ -121,7 +132,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { ctx := context.Background() - zone, err := d.getHostedZone(ctx, info.EffectiveFQDN) + zoneID, err := d.getHostedZoneID(ctx, info.EffectiveFQDN) if err != nil { return fmt.Errorf("edgeone: failed to get hosted zone: %w", err) } @@ -133,7 +144,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { request := teo.NewCreateDnsRecordRequest() request.Name = ptr.Pointer(punnyCoded) - request.ZoneId = zone.ZoneId + request.ZoneId = zoneID request.Type = ptr.Pointer("TXT") request.Content = ptr.Pointer(info.Value) request.TTL = ptr.Pointer(int64(d.config.TTL)) @@ -156,7 +167,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { ctx := context.Background() - zone, err := d.getHostedZone(ctx, info.EffectiveFQDN) + zoneID, err := d.getHostedZoneID(ctx, info.EffectiveFQDN) if err != nil { return fmt.Errorf("edgeone: failed to get hosted zone: %w", err) } @@ -171,7 +182,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } request := teo.NewDeleteDnsRecordsRequest() - request.ZoneId = zone.ZoneId + request.ZoneId = zoneID request.RecordIds = []*string{recordID} _, err = teo.DeleteDnsRecordsWithContext(ctx, d.client, request) diff --git a/providers/dns/edgeone/edgeone.toml b/providers/dns/edgeone/edgeone.toml index 120756da6..a33af75b2 100644 --- a/providers/dns/edgeone/edgeone.toml +++ b/providers/dns/edgeone/edgeone.toml @@ -17,6 +17,7 @@ lego --email you@example.com --dns edgeone -d '*.example.com' -d example.com run [Configuration.Additional] EDGEONE_SESSION_TOKEN = "Access Key token" EDGEONE_REGION = "Region" + EDGEONE_ZONES_MAPPING = "Mapping between DNS zones and site IDs. (ex: 'example.org:id1,example.com:id2')" EDGEONE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 30)" EDGEONE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 1200)" EDGEONE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" diff --git a/providers/dns/edgeone/edgeone_test.go b/providers/dns/edgeone/edgeone_test.go index 1c92118dc..7bd4f6f6d 100644 --- a/providers/dns/edgeone/edgeone_test.go +++ b/providers/dns/edgeone/edgeone_test.go @@ -9,8 +9,11 @@ import ( const envDomain = envNamespace + "DOMAIN" -var envTest = tester.NewEnvTest(EnvSecretID, EnvSecretKey). - WithDomain(envDomain) +var envTest = tester.NewEnvTest( + EnvSecretID, + EnvSecretKey, + EnvZonesMapping, +).WithDomain(envDomain) func TestNewDNSProvider(t *testing.T) { testCases := []struct { @@ -25,6 +28,14 @@ func TestNewDNSProvider(t *testing.T) { EnvSecretKey: "456", }, }, + { + desc: "success with zones mapping", + envVars: map[string]string{ + EnvSecretID: "123", + EnvSecretKey: "456", + EnvZonesMapping: "example.org:id1,example.com:id2", + }, + }, { desc: "missing credentials", envVars: map[string]string{ @@ -49,6 +60,15 @@ func TestNewDNSProvider(t *testing.T) { }, expected: "edgeone: some credentials information are missing: EDGEONE_SECRET_KEY", }, + { + desc: "invalid mapping", + envVars: map[string]string{ + EnvSecretID: "123", + EnvSecretKey: "456", + EnvZonesMapping: "example.org:id1,example.com", + }, + expected: "edgeone: zones mapping: incorrect pair: example.com", + }, } for _, test := range testCases { diff --git a/providers/dns/edgeone/wrapper.go b/providers/dns/edgeone/wrapper.go index c3e9d965b..53fae9427 100644 --- a/providers/dns/edgeone/wrapper.go +++ b/providers/dns/edgeone/wrapper.go @@ -9,10 +9,22 @@ import ( teo "github.com/go-acme/tencentedgdeone/v20220901" ) -func (d *DNSProvider) getHostedZone(ctx context.Context, domain string) (*teo.Zone, error) { +func (d *DNSProvider) getHostedZoneID(ctx context.Context, domain string) (*string, error) { + authZone, err := dns01.FindZoneByFqdn(domain) + if err != nil { + return nil, fmt.Errorf("could not find zone: %w", err) + } + + if d.config.ZonesMapping != nil { + zoneID, ok := d.config.ZonesMapping[authZone] + if ok { + return ptr.Pointer(zoneID), nil + } + } + request := teo.NewDescribeZonesRequest() - var domains []*teo.Zone + var zones []*teo.Zone for { response, err := teo.DescribeZonesWithContext(ctx, d.client, request) @@ -20,23 +32,18 @@ func (d *DNSProvider) getHostedZone(ctx context.Context, domain string) (*teo.Zo return nil, fmt.Errorf("API call failed: %w", err) } - domains = append(domains, response.Response.Zones...) + zones = append(zones, response.Response.Zones...) - if int64(len(domains)) >= ptr.Deref(response.Response.TotalCount) { + if int64(len(zones)) >= ptr.Deref(response.Response.TotalCount) { break } - request.Offset = ptr.Pointer(int64(len(domains))) - } - - authZone, err := dns01.FindZoneByFqdn(domain) - if err != nil { - return nil, fmt.Errorf("could not find zone: %w", err) + request.Offset = ptr.Pointer(int64(len(zones))) } var hostedZone *teo.Zone - for _, zone := range domains { + for _, zone := range zones { unfqdn := dns01.UnFqdn(authZone) if ptr.Deref(zone.ZoneName) == unfqdn { hostedZone = zone @@ -44,8 +51,8 @@ func (d *DNSProvider) getHostedZone(ctx context.Context, domain string) (*teo.Zo } if hostedZone == nil { - return nil, fmt.Errorf("zone %s not found in dnspod for domain %s", authZone, domain) + return nil, fmt.Errorf("zone %s not found for domain %s", authZone, domain) } - return hostedZone, nil + return hostedZone.ZoneId, nil }