feat: fix hetzner api provide example-config.yaml instead of config.yaml which is used for testing
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
config.yaml
|
||||||
17
example-config.yaml
Normal file
17
example-config.yaml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
server:
|
||||||
|
bind_address: ":9090"
|
||||||
|
tls:
|
||||||
|
enabled: false
|
||||||
|
cert_file: "cert.pem"
|
||||||
|
key_file: "key.pem"
|
||||||
|
upstream:
|
||||||
|
provider: hetzner
|
||||||
|
hetzner:
|
||||||
|
api_token: "YOUR_HETZNER_API_TOKEN"
|
||||||
|
clients:
|
||||||
|
client1:
|
||||||
|
secret: "s3cr3t123"
|
||||||
|
exact:
|
||||||
|
- "home.example.com"
|
||||||
|
wildcard:
|
||||||
|
- "example.net"
|
||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
pvd "git.cloonar.com/cloonar/updns/internal/provider"
|
pvd "git.cloonar.com/cloonar/updns/internal/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultAPIBase = "https://dns.hetzner.com"
|
const defaultAPIBase = "https://dns.hetzner.com/api/v1"
|
||||||
|
|
||||||
type provider struct {
|
type provider struct {
|
||||||
token string
|
token string
|
||||||
@@ -29,10 +29,12 @@ type zonesResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type record struct {
|
type record struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
ZoneID string `json:"zone_id"`
|
||||||
|
TTL int `json:"ttl"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type recordsResponse struct {
|
type recordsResponse struct {
|
||||||
@@ -62,10 +64,11 @@ func (p *provider) UpdateRecord(ctx context.Context, domain, ip string) error {
|
|||||||
} else {
|
} else {
|
||||||
zoneName = strings.Join(parts[len(parts)-2:], ".")
|
zoneName = strings.Join(parts[len(parts)-2:], ".")
|
||||||
}
|
}
|
||||||
|
subdomain := strings.Join(parts[:len(parts)-2], ".")
|
||||||
// Fetch zone ID
|
// Fetch zone ID
|
||||||
zonesURL := fmt.Sprintf("%s/zones?name=%s", p.apiBaseURL, zoneName)
|
zonesURL := fmt.Sprintf("%s/zones?name=%s", p.apiBaseURL, zoneName)
|
||||||
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, zonesURL, nil)
|
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, zonesURL, nil)
|
||||||
req.Header.Set("Authorization", "Bearer "+p.token)
|
req.Header.Set("Auth-API-Token", p.token)
|
||||||
resp, err := p.client.Do(req)
|
resp, err := p.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("fetch zones: %w", err)
|
return fmt.Errorf("fetch zones: %w", err)
|
||||||
@@ -86,7 +89,7 @@ func (p *provider) UpdateRecord(ctx context.Context, domain, ip string) error {
|
|||||||
// Fetch records in zone
|
// Fetch records in zone
|
||||||
recsURL := fmt.Sprintf("%s/records?zone_id=%s", p.apiBaseURL, zoneID)
|
recsURL := fmt.Sprintf("%s/records?zone_id=%s", p.apiBaseURL, zoneID)
|
||||||
req, _ = http.NewRequestWithContext(ctx, http.MethodGet, recsURL, nil)
|
req, _ = http.NewRequestWithContext(ctx, http.MethodGet, recsURL, nil)
|
||||||
req.Header.Set("Authorization", "Bearer "+p.token)
|
req.Header.Set("Auth-API-Token", p.token)
|
||||||
resp, err = p.client.Do(req)
|
resp, err = p.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("fetch records: %w", err)
|
return fmt.Errorf("fetch records: %w", err)
|
||||||
@@ -101,32 +104,64 @@ func (p *provider) UpdateRecord(ctx context.Context, domain, ip string) error {
|
|||||||
}
|
}
|
||||||
var recID string
|
var recID string
|
||||||
for _, rec := range rr.Records {
|
for _, rec := range rr.Records {
|
||||||
if rec.Name == domain {
|
if rec.Name == subdomain {
|
||||||
recID = rec.ID
|
recID = rec.ID
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if recID == "" {
|
if recID == "" {
|
||||||
return fmt.Errorf("record %s not found", domain)
|
// 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)
|
||||||
|
}
|
||||||
|
} 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 := map[string]string{"value": ip}
|
|
||||||
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("Authorization", "Bearer "+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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user