chore: bump app version to v0.5.1 and enhance logging in API client and provider for better debugging
All checks were successful
Release / build-image (push) Successful in 37s

This commit is contained in:
Tommy 2025-03-15 19:10:14 +01:00
parent 5f99618449
commit 67882bc3b6
Signed by: tommy
SSH key fingerprint: SHA256:1LWgQT3QPHIT29plS8jjXc3S1FcE/4oGvsx3Efxs6Uc
4 changed files with 152 additions and 35 deletions

View file

@ -1 +1 @@
appVersion: v0.5.0
appVersion: v0.5.1

View file

@ -5,8 +5,10 @@ import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"strconv"
"os"
"strings"
)
const apiURL string = "https://api.domeneshop.no/v0"
@ -64,28 +66,74 @@ func NewClient(apiToken, apiSecret string) *Client {
// Request makes a request against the API with an optional body, and makes sure
// that the required Authorization header is set using `setBasicAuth`
func (c *Client) Request(method string, endpoint string, reqBody []byte, v interface{}) error {
logLevel := strings.ToLower(os.Getenv("LOG_LEVEL"))
var buf = bytes.NewBuffer(reqBody)
req, err := http.NewRequest(method, fmt.Sprintf("%s/%s", apiURL, endpoint), buf)
fullURL := fmt.Sprintf("%s/%s", apiURL, endpoint)
if logLevel == "debug" {
log.Printf("Making %s request to %s", method, fullURL)
}
req, err := http.NewRequest(method, fullURL, buf)
if err != nil {
return err
if logLevel == "debug" {
log.Printf("Error creating request: %v", err)
}
return fmt.Errorf("failed to create request: %w", err)
}
// Check if credentials are provided
if c.APIToken == "" || c.APISecret == "" {
if logLevel == "debug" {
log.Println("API credentials are missing or empty")
}
return fmt.Errorf("API token or secret is empty")
}
req.SetBasicAuth(c.APIToken, c.APISecret)
versionInfo := version
req.Header.Set("User-Agent", fmt.Sprintf("external-dns-domeneshop-webhook/v"+versionInfo))
req.Header.Set("Accept", "application/json")
if len(reqBody) > 0 {
req.Header.Set("Content-Type", "application/json")
}
if logLevel == "debug" {
log.Printf("Making request with headers: %v", req.Header)
}
resp, err := c.http.Do(req)
if err != nil {
return err
if logLevel == "debug" {
log.Printf("Request failed: %v", err)
}
return fmt.Errorf("API request failed: %w", err)
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return err
if logLevel == "debug" {
log.Printf("Failed to read response body: %v", err)
}
return fmt.Errorf("failed to read response body: %w", err)
}
if logLevel == "debug" {
log.Printf("API response status: %s", resp.Status)
if len(respBody) > 0 {
shortBody := respBody
if len(shortBody) > 500 {
shortBody = shortBody[:500]
}
log.Printf("API response body (truncated): %s", string(shortBody))
}
}
if resp.StatusCode == 401 || resp.StatusCode == 403 {
return fmt.Errorf("authentication failed: %s (check API token and secret)", resp.Status)
}
if resp.StatusCode > 399 {
@ -93,7 +141,12 @@ func (c *Client) Request(method string, endpoint string, reqBody []byte, v inter
}
if v != nil {
return json.Unmarshal(respBody, &v)
if err := json.Unmarshal(respBody, &v); err != nil {
if logLevel == "debug" {
log.Printf("Failed to parse JSON response: %v", err)
}
return fmt.Errorf("failed to parse API response: %w", err)
}
}
return nil
}
@ -143,43 +196,66 @@ func (c *Client) GetDNSRecordByHostData(domain Domain, host string, data string)
// GetDomains fetches the domain list and returns the Domain object
// for the matching domain.
func (c *Client) GetDomains() ([]Domain, error) {
logLevel := strings.ToLower(os.Getenv("LOG_LEVEL"))
var domains []Domain
domains_list := make([]Domain, 0)
if logLevel == "debug" {
log.Println("Fetching domains list from Domeneshop API")
}
err := c.Request("GET", "domains", nil, &domains)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to fetch domains: %w", err)
}
if logLevel == "debug" {
log.Printf("Retrieved %d domains from API", len(domains))
}
// Filter domains to only include those with DNS service enabled
var filteredDomains []Domain
for _, d := range domains {
if !d.Services.DNS {
// Domains without DNS service cannot have DNS record added
continue
if d.Services.DNS {
filteredDomains = append(filteredDomains, d)
} else if logLevel == "debug" {
log.Printf("Skipping domain '%s' because DNS service is not enabled", d.Name)
}
domains_list = append(domains_list, d)
}
if len(domains_list) > 0 {
return domains_list, nil
}
return nil, fmt.Errorf("failed to find domains")
if len(filteredDomains) == 0 {
if logLevel == "debug" {
log.Println("No domains with DNS service enabled were found")
}
return nil, fmt.Errorf("no domains with DNS service enabled found")
}
if logLevel == "debug" {
log.Printf("Returning %d domains with DNS service enabled", len(filteredDomains))
}
return filteredDomains, nil
}
// GetRecords fetches the records for the specified domain
func (c *Client) GetRecords(domainId int) ([]DNSRecord, error) {
// GetRecords fetches all DNS records for a domain
func (c *Client) GetRecords(domainID int) ([]DNSRecord, error) {
logLevel := strings.ToLower(os.Getenv("LOG_LEVEL"))
var records []DNSRecord
endpoint := "domains/" + strconv.Itoa(domainId) + "/dns"
if logLevel == "debug" {
log.Printf("Fetching DNS records for domain ID %d", domainID)
}
endpoint := fmt.Sprintf("domains/%d/dns", domainID)
err := c.Request("GET", endpoint, nil, &records)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to fetch DNS records for domain ID %d: %w", domainID, err)
}
if len(records) > 0 {
return records, nil
if logLevel == "debug" {
log.Printf("Retrieved %d DNS records for domain ID %d", len(records), domainID)
}
return nil, fmt.Errorf("failed to find records for specified domain")
return records, nil
}
func (c *Client) CreateRecord(domainZone string, record DNSRecord) bool {

View file

@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
"log"
"os"
"strings"
@ -81,31 +82,65 @@ func (p *Provider) ApplyChanges(body io.ReadCloser) (error string) {
}
func (p *Provider) Records() []*endpoint.Endpoint {
logLevel := strings.ToLower(os.Getenv("LOG_LEVEL"))
endpoints := make([]*endpoint.Endpoint, 0)
if logLevel == "debug" {
log.Println("Attempting to fetch domains from Domeneshop API")
}
// Get all domains
domains, err := p.domeneshopClient.GetDomains()
if err != nil {
fmt.Println(err)
os.Exit(1)
if logLevel == "debug" {
log.Printf("Error fetching domains from Domeneshop API: %v", err)
}
fmt.Println("failed to find domains from Domeneshop API")
// Return empty endpoints instead of exiting
return endpoints
}
if logLevel == "debug" {
log.Printf("Successfully fetched %d domains from Domeneshop API", len(domains))
}
// Get all records for each domain
for _, domain := range domains {
if logLevel == "debug" {
log.Printf("Fetching records for domain '%s' (ID: %d)", domain.Name, domain.ID)
}
records, err := p.domeneshopClient.GetRecords(domain.ID)
if err != nil {
fmt.Println(err)
os.Exit(1)
if logLevel == "debug" {
log.Printf("Error fetching records for domain '%s': %v", domain.Name, err)
}
fmt.Printf("failed to find records for domain %s: %v\n", domain.Name, err)
// Continue with next domain instead of exiting
continue
}
if logLevel == "debug" {
log.Printf("Found %d records for domain '%s'", len(records), domain.Name)
}
for _, record := range records {
fqdn := record.Host + "." + domain.Name
if logLevel == "debug" {
log.Printf("Adding record: %s %s %s TTL:%d", fqdn, record.Type, record.Data, record.TTL)
}
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(fqdn, record.Type, endpoint.TTL(record.TTL), record.Data))
}
}
// TODO: use SupportedRecordType in provider-package for external-dns to filter records
if len(endpoints) == 0 {
fmt.Println("failed to find records for specified domain")
if logLevel == "debug" {
log.Println("No records found for any domains")
}
} else if logLevel == "debug" {
log.Printf("Returning %d total endpoints", len(endpoints))
}
return endpoints
}

View file

@ -11,6 +11,7 @@ import (
"sort"
domeneshopProvider "code.252.no/pub/external-dns-domeneshop-webhook/internal/provider"
"sigs.k8s.io/external-dns/endpoint"
)
const (
@ -84,14 +85,19 @@ func (p *Webhook) Records(w http.ResponseWriter, r *http.Request) {
}
records := p.provider.Records()
if records == nil {
// Even if there are no records, return an empty array
records = []*endpoint.Endpoint{}
}
w.Header().Set(contentTypeHeader, string(mediaTypeFormat+"version="+"1"))
w.Header().Set(varyHeader, contentTypeHeader)
err := json.NewEncoder(w).Encode(records)
if err != nil {
fmt.Printf("Error encoding JSON response: %v\n", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
}
// This handler returns DomainFilter and is used on the route for "/""