feat: add posibility to use token file in hetzner config
This commit is contained in:
@@ -6,8 +6,10 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"git.cloonar.com/cloonar/updns/internal/config"
|
||||
pvd "git.cloonar.com/cloonar/updns/internal/provider"
|
||||
)
|
||||
|
||||
@@ -33,8 +35,8 @@ type record struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
Type string `json:"type"`
|
||||
ZoneID string `json:"zone_id"`
|
||||
TTL int `json:"ttl"`
|
||||
ZoneID string `json:"zone_id"`
|
||||
TTL int `json:"ttl"`
|
||||
}
|
||||
|
||||
type recordsResponse struct {
|
||||
@@ -42,12 +44,47 @@ type recordsResponse struct {
|
||||
}
|
||||
|
||||
// NewProvider creates a Hetzner DNS provider using the official API.
|
||||
func NewProvider(token string) pvd.Provider {
|
||||
return &provider{token: token, client: http.DefaultClient, apiBaseURL: defaultAPIBase}
|
||||
func NewProvider(cfg config.HetznerConfig) (pvd.Provider, error) {
|
||||
var token string
|
||||
|
||||
hasToken := cfg.APIToken != ""
|
||||
hasTokenFile := cfg.APITokenFile != ""
|
||||
|
||||
if hasToken && hasTokenFile {
|
||||
return nil, fmt.Errorf("hetzner config: provide api_token or api_token_file, not both")
|
||||
}
|
||||
if !hasToken && !hasTokenFile {
|
||||
return nil, fmt.Errorf("hetzner config: api_token or api_token_file must be provided")
|
||||
}
|
||||
|
||||
if hasTokenFile {
|
||||
tokenBytes, err := os.ReadFile(cfg.APITokenFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading hetzner token file %q: %w", cfg.APITokenFile, err)
|
||||
}
|
||||
token = strings.TrimSpace(string(tokenBytes))
|
||||
} else {
|
||||
token = cfg.APIToken
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
// This case might happen if the file exists but is empty
|
||||
return nil, fmt.Errorf("hetzner api token is empty")
|
||||
}
|
||||
|
||||
return &provider{token: token, client: http.DefaultClient, apiBaseURL: defaultAPIBase}, nil
|
||||
}
|
||||
|
||||
// NewProviderWithURL creates a Hetzner provider with a custom API base URL (for testing).
|
||||
// Note: This testing helper still requires a direct token string.
|
||||
func NewProviderWithURL(token, apiBaseURL string) pvd.Provider {
|
||||
// Basic validation for the test helper
|
||||
if token == "" {
|
||||
panic("NewProviderWithURL requires a non-empty token for testing")
|
||||
}
|
||||
if apiBaseURL == "" {
|
||||
panic("NewProviderWithURL requires a non-empty apiBaseURL for testing")
|
||||
}
|
||||
return &provider{token: token, client: http.DefaultClient, apiBaseURL: apiBaseURL}
|
||||
}
|
||||
|
||||
@@ -64,7 +101,7 @@ func (p *provider) UpdateRecord(ctx context.Context, domain, ip string) error {
|
||||
} else {
|
||||
zoneName = strings.Join(parts[len(parts)-2:], ".")
|
||||
}
|
||||
subdomain := strings.Join(parts[:len(parts)-2], ".")
|
||||
subdomain := strings.Join(parts[:len(parts)-2], ".")
|
||||
// Fetch zone ID
|
||||
zonesURL := fmt.Sprintf("%s/zones?name=%s", p.apiBaseURL, zoneName)
|
||||
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, zonesURL, nil)
|
||||
@@ -111,57 +148,57 @@ func (p *provider) UpdateRecord(ctx context.Context, domain, ip string) error {
|
||||
}
|
||||
if recID == "" {
|
||||
// return fmt.Errorf("record %s not found", domain)
|
||||
// Create new record
|
||||
// Cut the last 2 parts of the domain name
|
||||
createURL := fmt.Sprintf("%s/records", p.apiBaseURL)
|
||||
body := record{
|
||||
Name: subdomain,
|
||||
Type: "A",
|
||||
Value: ip,
|
||||
TTL: 60,
|
||||
ZoneID: zoneID,
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
if err := json.NewEncoder(buf).Encode(body); err != nil {
|
||||
return fmt.Errorf("encode create body: %w", err)
|
||||
}
|
||||
req, _ = http.NewRequestWithContext(ctx, http.MethodPost, createURL, buf)
|
||||
req.Header.Set("Auth-API-Token", p.token)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err = p.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create record: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("create API status: %s", resp.Status)
|
||||
}
|
||||
// Create new record
|
||||
// Cut the last 2 parts of the domain name
|
||||
createURL := fmt.Sprintf("%s/records", p.apiBaseURL)
|
||||
body := record{
|
||||
Name: subdomain,
|
||||
Type: "A",
|
||||
Value: ip,
|
||||
TTL: 60,
|
||||
ZoneID: zoneID,
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
if err := json.NewEncoder(buf).Encode(body); err != nil {
|
||||
return fmt.Errorf("encode create body: %w", err)
|
||||
}
|
||||
req, _ = http.NewRequestWithContext(ctx, http.MethodPost, createURL, buf)
|
||||
req.Header.Set("Auth-API-Token", p.token)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err = p.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create record: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("create API status: %s", resp.Status)
|
||||
}
|
||||
} else {
|
||||
// Update record value
|
||||
updateURL := fmt.Sprintf("%s/records/%s", p.apiBaseURL, recID)
|
||||
body := record{
|
||||
Name: subdomain,
|
||||
Type: "A",
|
||||
Value: ip,
|
||||
TTL: 60,
|
||||
ZoneID: zoneID,
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
if err := json.NewEncoder(buf).Encode(body); err != nil {
|
||||
return fmt.Errorf("encode update body: %w", err)
|
||||
}
|
||||
req, _ = http.NewRequestWithContext(ctx, http.MethodPut, updateURL, buf)
|
||||
req.Header.Set("Auth-API-Token", p.token)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err = p.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("update record: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("update API status: %s", resp.Status)
|
||||
}
|
||||
}
|
||||
// Update record value
|
||||
updateURL := fmt.Sprintf("%s/records/%s", p.apiBaseURL, recID)
|
||||
body := record{
|
||||
Name: subdomain,
|
||||
Type: "A",
|
||||
Value: ip,
|
||||
TTL: 60,
|
||||
ZoneID: zoneID,
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
if err := json.NewEncoder(buf).Encode(body); err != nil {
|
||||
return fmt.Errorf("encode update body: %w", err)
|
||||
}
|
||||
req, _ = http.NewRequestWithContext(ctx, http.MethodPut, updateURL, buf)
|
||||
req.Header.Set("Auth-API-Token", p.token)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err = p.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("update record: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("update API status: %s", resp.Status)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user