Implement EnsureCNAME function and tests for wildcard CNAME creation in Cloudflare
This commit is contained in:
@@ -388,3 +388,63 @@ func (c *cfClient) deleteRecord(zoneID, recordID string) error {
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnsureCNAME creates a CNAME record (name → target) in Cloudflare if one
|
||||
// doesn't already exist with the correct target. Used by the reconciler to
|
||||
// set up wildcard CNAMEs (e.g., *.cloud.payne.io → cloud.payne.io) for
|
||||
// public domains with subdomains enabled.
|
||||
func EnsureCNAME(apiToken, name, target string) error {
|
||||
cf := &cfClient{apiToken: apiToken}
|
||||
|
||||
parts := strings.Split(name, ".")
|
||||
// Strip leading wildcard for zone extraction: *.cloud.payne.io → payne.io
|
||||
nonWild := parts
|
||||
if parts[0] == "*" {
|
||||
nonWild = parts[1:]
|
||||
}
|
||||
if len(nonWild) < 2 {
|
||||
return fmt.Errorf("invalid CNAME name: %s", name)
|
||||
}
|
||||
zoneName := strings.Join(nonWild[len(nonWild)-2:], ".")
|
||||
|
||||
zoneID, err := cf.getZoneID(zoneName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting zone ID for %s: %w", zoneName, err)
|
||||
}
|
||||
|
||||
// Check for existing CNAME — skip if already correct
|
||||
if recordID, content, err := cf.getRecordID(zoneID, name, "CNAME"); err == nil {
|
||||
if content == target {
|
||||
return nil // already up to date
|
||||
}
|
||||
// Wrong target — update it
|
||||
return cf.patchCNAME(zoneID, recordID, name, target)
|
||||
}
|
||||
|
||||
slog.Info("creating wildcard CNAME", "component", "ddns", "name", name, "target", target)
|
||||
return cf.createCNAME(zoneID, name, target)
|
||||
}
|
||||
|
||||
func (c *cfClient) createCNAME(zoneID, name, target string) error {
|
||||
body, _ := json.Marshal(map[string]string{"type": "CNAME", "name": name, "content": target})
|
||||
resp, err := c.do(http.MethodPost,
|
||||
fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records", zoneID),
|
||||
bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating CNAME: %w", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *cfClient) patchCNAME(zoneID, recordID, name, target string) error {
|
||||
body, _ := json.Marshal(map[string]string{"type": "CNAME", "name": name, "content": target})
|
||||
resp, err := c.do(http.MethodPatch,
|
||||
fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records/%s", zoneID, recordID),
|
||||
bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return fmt.Errorf("updating CNAME: %w", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user