From 7d459b59c5882aac5cd8545cbda77e18852f5cd3 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sun, 22 Feb 2026 14:14:18 +0100 Subject: [PATCH] liara: add support for team ID (#2867) --- cmd/zz_gen_cmd_dnshelp.go | 1 + docs/content/dns/zz_gen_liara.md | 1 + providers/dns/liara/internal/client.go | 27 +++++++--- providers/dns/liara/internal/client_test.go | 55 ++++++++++++++++++--- providers/dns/liara/liara.go | 7 ++- providers/dns/liara/liara.toml | 1 + 6 files changed, 76 insertions(+), 16 deletions(-) diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 161729c79..8e3b4ebce 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -2394,6 +2394,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(` - "LIARA_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) ew.writeln(` - "LIARA_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) ew.writeln(` - "LIARA_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "LIARA_TEAM_ID": The team ID to access services in a team`) ew.writeln(` - "LIARA_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)`) ew.writeln() diff --git a/docs/content/dns/zz_gen_liara.md b/docs/content/dns/zz_gen_liara.md index 8a6ddbd99..658ce8077 100644 --- a/docs/content/dns/zz_gen_liara.md +++ b/docs/content/dns/zz_gen_liara.md @@ -50,6 +50,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | `LIARA_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | | `LIARA_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | | `LIARA_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `LIARA_TEAM_ID` | The team ID to access services in a team | | `LIARA_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600) | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. diff --git a/providers/dns/liara/internal/client.go b/providers/dns/liara/internal/client.go index 93cdcf7c8..95c39695b 100644 --- a/providers/dns/liara/internal/client.go +++ b/providers/dns/liara/internal/client.go @@ -20,17 +20,23 @@ const defaultBaseURL = "https://dns-service.iran.liara.ir" type Client struct { baseURL *url.URL httpClient *http.Client + + teamID string } // NewClient creates a new Client. -func NewClient(hc *http.Client) *Client { +func NewClient(hc *http.Client, teamID string) *Client { baseURL, _ := url.Parse(defaultBaseURL) if hc == nil { hc = &http.Client{Timeout: 10 * time.Second} } - return &Client{httpClient: hc, baseURL: baseURL} + return &Client{ + httpClient: hc, + baseURL: baseURL, + teamID: teamID, + } } // GetRecords gets the records of a domain. @@ -38,7 +44,7 @@ func NewClient(hc *http.Client) *Client { func (c *Client) GetRecords(ctx context.Context, domainName string) ([]Record, error) { endpoint := c.baseURL.JoinPath("api", "v1", "zones", domainName, "dns-records") - req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + req, err := c.newJSONRequest(ctx, http.MethodGet, endpoint, nil) if err != nil { return nil, fmt.Errorf("create request: %w", err) } @@ -73,7 +79,7 @@ func (c *Client) GetRecords(ctx context.Context, domainName string) ([]Record, e func (c *Client) CreateRecord(ctx context.Context, domainName string, record Record) (*Record, error) { endpoint := c.baseURL.JoinPath("api", "v1", "zones", domainName, "dns-records") - req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) + req, err := c.newJSONRequest(ctx, http.MethodPost, endpoint, record) if err != nil { return nil, fmt.Errorf("create request: %w", err) } @@ -108,7 +114,7 @@ func (c *Client) CreateRecord(ctx context.Context, domainName string, record Rec func (c *Client) GetRecord(ctx context.Context, domainName, recordID string) (*Record, error) { endpoint := c.baseURL.JoinPath("api", "v1", "zones", domainName, "dns-records", recordID) - req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + req, err := c.newJSONRequest(ctx, http.MethodGet, endpoint, nil) if err != nil { return nil, fmt.Errorf("create request: %w", err) } @@ -143,7 +149,7 @@ func (c *Client) GetRecord(ctx context.Context, domainName, recordID string) (*R func (c *Client) DeleteRecord(ctx context.Context, domainName, recordID string) error { endpoint := c.baseURL.JoinPath("api", "v1", "zones", domainName, "dns-records", recordID) - req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) + req, err := c.newJSONRequest(ctx, http.MethodDelete, endpoint, nil) if err != nil { return fmt.Errorf("create request: %w", err) } @@ -162,7 +168,14 @@ func (c *Client) DeleteRecord(ctx context.Context, domainName, recordID string) return nil } -func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { +func (c *Client) newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + if c.teamID != "" { + query := endpoint.Query() + query.Set("teamID", c.teamID) + + endpoint.RawQuery = query.Encode() + } + buf := new(bytes.Buffer) if payload != nil { diff --git a/providers/dns/liara/internal/client_test.go b/providers/dns/liara/internal/client_test.go index 57ac7e8b3..b6d007046 100644 --- a/providers/dns/liara/internal/client_test.go +++ b/providers/dns/liara/internal/client_test.go @@ -13,10 +13,10 @@ import ( const apiKey = "key" -func mockBuilder() *servermock.Builder[*Client] { +func mockBuilder(teamID string) *servermock.Builder[*Client] { return servermock.NewBuilder[*Client]( func(server *httptest.Server) (*Client, error) { - client := NewClient(OAuthStaticAccessToken(server.Client(), apiKey)) + client := NewClient(OAuthStaticAccessToken(server.Client(), apiKey), teamID) client.baseURL, _ = url.Parse(server.URL) return client, nil @@ -26,7 +26,7 @@ func mockBuilder() *servermock.Builder[*Client] { } func TestClient_GetRecords(t *testing.T) { - client := mockBuilder(). + client := mockBuilder(""). Route("GET /api/v1/zones/example.com/dns-records", servermock.ResponseFromFixture("RecordsResponse.json")). Build(t) @@ -50,7 +50,7 @@ func TestClient_GetRecords(t *testing.T) { } func TestClient_GetRecord(t *testing.T) { - client := mockBuilder(). + client := mockBuilder(""). Route("GET /api/v1/zones/example.com/dns-records/123", servermock.ResponseFromFixture("RecordResponse.json")). Build(t) @@ -72,7 +72,7 @@ func TestClient_GetRecord(t *testing.T) { } func TestClient_CreateRecord(t *testing.T) { - client := mockBuilder(). + client := mockBuilder(""). Route("POST /api/v1/zones/example.com/dns-records", servermock.ResponseFromFixture("RecordResponse.json"). WithStatusCode(http.StatusCreated), @@ -108,8 +108,47 @@ func TestClient_CreateRecord(t *testing.T) { assert.Equal(t, expected, record) } +func TestClient_CreateRecord_withTeamID(t *testing.T) { + client := mockBuilder("123"). + Route("POST /api/v1/zones/example.com/dns-records", + servermock.ResponseFromFixture("RecordResponse.json"). + WithStatusCode(http.StatusCreated), + servermock.CheckRequestJSONBody(`{"name":"string","type":"string","ttl":3600,"contents":[{"text":"string"}]}`), + servermock.CheckQueryParameter().Strict().With("teamID", "123"), + ). + Build(t) + + data := Record{ + Type: "string", + Name: "string", + Contents: []Content{ + { + Text: "string", + }, + }, + TTL: 3600, + } + + record, err := client.CreateRecord(t.Context(), "example.com", data) + require.NoError(t, err) + + expected := &Record{ + ID: "string", + Type: "string", + Name: "string", + Contents: []Content{ + { + Text: "string", + }, + }, + TTL: 3600, + } + + assert.Equal(t, expected, record) +} + func TestClient_DeleteRecord(t *testing.T) { - client := mockBuilder(). + client := mockBuilder(""). Route("DELETE /api/v1/zones/example.com/dns-records/123", servermock.Noop(). WithStatusCode(http.StatusNoContent)). @@ -120,7 +159,7 @@ func TestClient_DeleteRecord(t *testing.T) { } func TestClient_DeleteRecord_NotFound_Response(t *testing.T) { - client := mockBuilder(). + client := mockBuilder(""). Route("DELETE /api/v1/zones/example.com/dns-records/123", servermock.Noop(). WithStatusCode(http.StatusNotFound)). @@ -131,7 +170,7 @@ func TestClient_DeleteRecord_NotFound_Response(t *testing.T) { } func TestClient_DeleteRecord_error(t *testing.T) { - client := mockBuilder(). + client := mockBuilder(""). Route("DELETE /api/v1/zones/example.com/dns-records/123", servermock.ResponseFromFixture("error.json"). WithStatusCode(http.StatusUnauthorized)). diff --git a/providers/dns/liara/liara.go b/providers/dns/liara/liara.go index b91b004cc..c7e403eed 100644 --- a/providers/dns/liara/liara.go +++ b/providers/dns/liara/liara.go @@ -23,6 +23,7 @@ const ( envNamespace = "LIARA_" EnvAPIKey = envNamespace + "API_KEY" + EnvTeamID = envNamespace + "TEAM_ID" EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" @@ -39,7 +40,9 @@ var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. type Config struct { - APIKey string + APIKey string + TeamID string + TTL int PropagationTimeout time.Duration PollingInterval time.Duration @@ -77,6 +80,7 @@ func NewDNSProvider() (*DNSProvider, error) { config := NewDefaultConfig() config.APIKey = values[EnvAPIKey] + config.TeamID = env.GetOrFile(EnvTeamID) return NewDNSProviderConfig(config) } @@ -112,6 +116,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { clientdebug.Wrap( internal.OAuthStaticAccessToken(retryClient.StandardClient(), config.APIKey), ), + config.TeamID, ) return &DNSProvider{ diff --git a/providers/dns/liara/liara.toml b/providers/dns/liara/liara.toml index 4ed53ec75..f471de04e 100644 --- a/providers/dns/liara/liara.toml +++ b/providers/dns/liara/liara.toml @@ -13,6 +13,7 @@ lego --dns liara -d '*.example.com' -d example.com run [Configuration.Credentials] LIARA_API_KEY = "The API key" [Configuration.Additional] + LIARA_TEAM_ID = "The team ID to access services in a team" LIARA_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" LIARA_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" LIARA_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)"