otc: adds option to use private zone (#2649)

Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
This commit is contained in:
Nikita Shashkov 2025-09-17 15:39:50 +03:00 committed by GitHub
commit bf0e89cdd9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 85 additions and 21 deletions

View file

@ -2572,7 +2572,6 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln(`Credentials:`)
ew.writeln(` - "OTC_DOMAIN_NAME": Domain name`)
ew.writeln(` - "OTC_IDENTITY_ENDPOINT": Identity endpoint URL`)
ew.writeln(` - "OTC_PASSWORD": Password`)
ew.writeln(` - "OTC_PROJECT_NAME": Project name`)
ew.writeln(` - "OTC_USER_NAME": User name`)
@ -2580,7 +2579,9 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "OTC_HTTP_TIMEOUT": API request timeout in seconds (Default: 10)`)
ew.writeln(` - "OTC_IDENTITY_ENDPOINT": Identity endpoint URL`)
ew.writeln(` - "OTC_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`)
ew.writeln(` - "OTC_PRIVATE_ZONE": Set to true to use private zones only (default: use public zones only)`)
ew.writeln(` - "OTC_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`)
ew.writeln(` - "OTC_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 60)`)
ew.writeln(` - "OTC_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`)

View file

@ -35,7 +35,6 @@ _Please contribute by adding a CLI example._
| Environment Variable Name | Description |
|-----------------------|-------------|
| `OTC_DOMAIN_NAME` | Domain name |
| `OTC_IDENTITY_ENDPOINT` | Identity endpoint URL |
| `OTC_PASSWORD` | Password |
| `OTC_PROJECT_NAME` | Project name |
| `OTC_USER_NAME` | User name |
@ -49,7 +48,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}).
| Environment Variable Name | Description |
|--------------------------------|-------------|
| `OTC_HTTP_TIMEOUT` | API request timeout in seconds (Default: 10) |
| `OTC_IDENTITY_ENDPOINT` | Identity endpoint URL |
| `OTC_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) |
| `OTC_PRIVATE_ZONE` | Set to true to use private zones only (default: use public zones only) |
| `OTC_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) |
| `OTC_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 60) |
| `OTC_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) |

View file

@ -42,8 +42,8 @@ func NewClient(username, password, domainName, projectName string) *Client {
}
}
func (c *Client) GetZoneID(ctx context.Context, zone string) (string, error) {
zonesResp, err := c.getZones(ctx, zone)
func (c *Client) GetZoneID(ctx context.Context, zone string, privateZone bool) (string, error) {
zonesResp, err := c.getZones(ctx, zone, privateZone)
if err != nil {
return "", err
}
@ -62,13 +62,16 @@ func (c *Client) GetZoneID(ctx context.Context, zone string) (string, error) {
}
// https://docs.otc.t-systems.com/domain-name-service/api-ref/apis/public_zone_management/querying_public_zones.html
func (c *Client) getZones(ctx context.Context, zone string) (*ZonesResponse, error) {
func (c *Client) getZones(ctx context.Context, zone string, privateZone bool) (*ZonesResponse, error) {
c.muBaseURL.Lock()
endpoint := c.baseURL.JoinPath("zones")
c.muBaseURL.Unlock()
query := endpoint.Query()
query.Set("name", zone)
if privateZone {
query.Set("type", "private")
}
endpoint.RawQuery = query.Encode()
req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil)

View file

@ -33,7 +33,22 @@ func TestClient_GetZoneID(t *testing.T) {
With("name", "example.com.")).
Build(t)
zoneID, err := client.GetZoneID(context.Background(), "example.com.")
zoneID, err := client.GetZoneID(context.Background(), "example.com.", false)
require.NoError(t, err)
assert.Equal(t, "123123", zoneID)
}
func TestClient_GetZoneID_private(t *testing.T) {
client := mockBuilder().
Route("GET /zones",
servermock.ResponseFromFixture("zones_GET.json"),
servermock.CheckQueryParameter().Strict().
With("name", "example.com.").
With("type", "private")).
Build(t)
zoneID, err := client.GetZoneID(context.Background(), "example.com.", true)
require.NoError(t, err)
assert.Equal(t, "123123", zoneID)
@ -47,7 +62,7 @@ func TestClient_GetZoneID_error(t *testing.T) {
With("name", "example.com.")).
Build(t)
_, err := client.GetZoneID(context.Background(), "example.com.")
_, err := client.GetZoneID(context.Background(), "example.com.", false)
require.EqualError(t, err, "zone example.com. not found")
}

View file

@ -23,6 +23,7 @@ const (
EnvPassword = envNamespace + "PASSWORD"
EnvProjectName = envNamespace + "PROJECT_NAME"
EnvIdentityEndpoint = envNamespace + "IDENTITY_ENDPOINT"
EnvPrivateZone = envNamespace + "PRIVATE_ZONE"
EnvTTL = envNamespace + "TTL"
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
@ -40,11 +41,13 @@ var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
// Config is used to configure the creation of the DNSProvider.
type Config struct {
IdentityEndpoint string
DomainName string
ProjectName string
UserName string
Password string
DomainName string
ProjectName string
UserName string
Password string
IdentityEndpoint string
PrivateZone bool
PropagationTimeout time.Duration
PollingInterval time.Duration
SequenceInterval time.Duration
@ -65,10 +68,12 @@ func NewDefaultConfig() *Config {
tr.DisableKeepAlives = true
return &Config{
PrivateZone: env.GetOrDefaultBool(EnvPrivateZone, false),
IdentityEndpoint: env.GetOrDefaultString(EnvIdentityEndpoint, defaultIdentityEndpoint),
TTL: env.GetOrDefaultInt(EnvTTL, minTTL),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout),
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
IdentityEndpoint: env.GetOrDefaultString(EnvIdentityEndpoint, defaultIdentityEndpoint),
SequenceInterval: env.GetOrDefaultSecond(EnvSequenceInterval, dns01.DefaultPropagationTimeout),
HTTPClient: &http.Client{
Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 10*time.Second),
@ -144,7 +149,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
return fmt.Errorf("otc: %w", err)
}
zoneID, err := d.client.GetZoneID(ctx, authZone)
zoneID, err := d.client.GetZoneID(ctx, authZone, d.config.PrivateZone)
if err != nil {
return fmt.Errorf("otc: unable to get zone: %w", err)
}
@ -181,7 +186,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
return fmt.Errorf("otc: %w", err)
}
zoneID, err := d.client.GetZoneID(ctx, authZone)
zoneID, err := d.client.GetZoneID(ctx, authZone, d.config.PrivateZone)
if err != nil {
return fmt.Errorf("otc: %w", err)
}

View file

@ -12,8 +12,9 @@ Example = ''''''
OTC_PASSWORD = "Password"
OTC_PROJECT_NAME = "Project name"
OTC_DOMAIN_NAME = "Domain name"
OTC_IDENTITY_ENDPOINT = "Identity endpoint URL"
[Configuration.Additional]
OTC_IDENTITY_ENDPOINT = "Identity endpoint URL"
OTC_PRIVATE_ZONE = "Set to true to use private zones only (default: use public zones only)"
OTC_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)"
OTC_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)"
OTC_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 60)"

View file

@ -18,6 +18,7 @@ var envTest = tester.NewEnvTest(
EnvDomainName,
EnvUserName,
EnvPassword,
EnvPrivateZone,
EnvProjectName,
EnvIdentityEndpoint).
WithDomain(envDomain)
@ -213,7 +214,7 @@ func TestLiveCleanUp(t *testing.T) {
}
func TestDNSProvider_Present(t *testing.T) {
provider := mockBuilder().
provider := mockBuilder(false).
Route("GET /v2/zones",
servermock.ResponseFromInternal("zones_GET.json"),
servermock.CheckQueryParameter().Strict().
@ -227,8 +228,24 @@ func TestDNSProvider_Present(t *testing.T) {
require.NoError(t, err)
}
func TestDNSProvider_Present_private(t *testing.T) {
provider := mockBuilder(true).
Route("GET /v2/zones",
servermock.ResponseFromInternal("zones_GET.json"),
servermock.CheckQueryParameter().Strict().
With("name", "example.com.").
With("type", "private")).
Route("POST /v2/zones/123123/recordsets",
servermock.Noop(),
servermock.CheckRequestJSONBodyFromInternal("zones-recordsets_POST-request.json")).
Build(t)
err := provider.Present("example.com", "", "123d==")
require.NoError(t, err)
}
func TestDNSProvider_Present_emptyZone(t *testing.T) {
provider := mockBuilder().
provider := mockBuilder(false).
Route("GET /v2/zones",
servermock.ResponseFromInternal("zones_GET_empty.json"),
servermock.CheckQueryParameter().Strict().
@ -240,7 +257,7 @@ func TestDNSProvider_Present_emptyZone(t *testing.T) {
}
func TestDNSProvider_Cleanup(t *testing.T) {
provider := mockBuilder().
provider := mockBuilder(false).
Route("GET /v2/zones",
servermock.ResponseFromInternal("zones_GET.json"),
servermock.CheckQueryParameter().Strict().
@ -258,8 +275,28 @@ func TestDNSProvider_Cleanup(t *testing.T) {
require.NoError(t, err)
}
func TestDNSProvider_Cleanup_private(t *testing.T) {
provider := mockBuilder(true).
Route("GET /v2/zones",
servermock.ResponseFromInternal("zones_GET.json"),
servermock.CheckQueryParameter().Strict().
With("name", "example.com.").
With("type", "private")).
Route("GET /v2/zones/123123/recordsets",
servermock.ResponseFromInternal("zones-recordsets_GET.json"),
servermock.CheckQueryParameter().Strict().
With("name", "_acme-challenge.example.com.").
With("type", "TXT")).
Route("DELETE /v2/zones/123123/recordsets/321321",
servermock.ResponseFromInternal("zones-recordsets_DELETE.json")).
Build(t)
err := provider.CleanUp("example.com", "", "123d==")
require.NoError(t, err)
}
func TestDNSProvider_Cleanup_emptyRecordset(t *testing.T) {
provider := mockBuilder().
provider := mockBuilder(false).
Route("GET /v2/zones",
servermock.ResponseFromInternal("zones_GET.json"),
servermock.CheckQueryParameter().Strict().
@ -275,7 +312,7 @@ func TestDNSProvider_Cleanup_emptyRecordset(t *testing.T) {
require.EqualError(t, err, "otc: unable to get record _acme-challenge.example.com. for zone example.com: record not found")
}
func mockBuilder() *servermock.Builder[*DNSProvider] {
func mockBuilder(private bool) *servermock.Builder[*DNSProvider] {
return servermock.NewBuilder(
func(server *httptest.Server) (*DNSProvider, error) {
config := NewDefaultConfig()
@ -285,6 +322,7 @@ func mockBuilder() *servermock.Builder[*DNSProvider] {
config.DomainName = "example.com"
config.ProjectName = "test"
config.IdentityEndpoint = fmt.Sprintf("%s/v3/auth/token", server.URL)
config.PrivateZone = private
return NewDNSProviderConfig(config)
},