Skip to content

Commit f56b462

Browse files
committed
fix(civo): normalize @ apex record names
The Civo DNS API may return "@" as the record name for apex (root) domain records. The provider only handled the empty string case, causing apex records to be unrecognized during reconciliation. This led to repeated "database_dns_record_already_exist" errors on every sync loop as the provider attempted to re-create records it couldn't find. Normalize "@" to "" in both Records() and getRecordID(). Related: #6183 Signed-off-by: Cody J. Hanson <cody@codyjhanson.com>
1 parent d38daef commit f56b462

File tree

2 files changed

+75
-2
lines changed

2 files changed

+75
-2
lines changed

provider/civo/civo.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,8 @@ func (p *CivoProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error
132132

133133
// root name is identified by the empty string and should be
134134
// translated to zone name for the endpoint entry.
135-
if r.Name == "" {
135+
// The Civo API may return "@" for apex records instead of "".
136+
if r.Name == "" || r.Name == "@" {
136137
name = zone.Name
137138
}
138139

@@ -539,7 +540,12 @@ func getRecordID(records []civogo.DNSRecord, zone civogo.DNSDomain, ep endpoint.
539540
for _, record := range records {
540541
stripedName := getStrippedRecordName(zone, ep)
541542
toUpper := strings.ToUpper(string(record.Type))
542-
if record.Name == stripedName && toUpper == ep.RecordType {
543+
// The Civo API may return "@" for apex record names instead of "".
544+
recordName := record.Name
545+
if recordName == "@" {
546+
recordName = ""
547+
}
548+
if recordName == stripedName && toUpper == ep.RecordType {
543549
matchedRecords = append(matchedRecords, record)
544550
}
545551
}

provider/civo/civo_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,49 @@ func TestCivoProviderRecords(t *testing.T) {
130130
assert.Equal(t, int(records[1].RecordTTL), expected[1].TTL)
131131
}
132132

133+
func TestCivoProviderRecordsApexAt(t *testing.T) {
134+
client, server, _ := civogo.NewAdvancedClientForTesting([]civogo.ConfigAdvanceClientForTesting{
135+
{
136+
Method: "GET",
137+
Value: []civogo.ValueAdvanceClientForTesting{
138+
{
139+
RequestBody: ``,
140+
URL: "/v2/dns/12345/records",
141+
ResponseBody: `[
142+
{"id": "1", "domain_id":"12345", "account_id": "1", "name": "@", "type": "A", "value": "10.0.0.0", "ttl": 600},
143+
{"id": "2", "account_id": "1", "domain_id":"12345", "name": "www", "type": "A", "value": "10.0.0.1", "ttl": 600}
144+
]`,
145+
},
146+
{
147+
RequestBody: ``,
148+
URL: "/v2/dns",
149+
ResponseBody: `[
150+
{"id": "12345", "account_id": "1", "name": "example.com"}
151+
]`,
152+
},
153+
},
154+
},
155+
})
156+
157+
defer server.Close()
158+
provider := &CivoProvider{
159+
Client: *client,
160+
domainFilter: endpoint.NewDomainFilter([]string{"example.com"}),
161+
}
162+
163+
records, err := provider.Records(context.Background())
164+
assert.NoError(t, err)
165+
assert.Len(t, records, 2)
166+
167+
// The "@" apex record should be translated to the zone name
168+
assert.Equal(t, "example.com", records[0].DNSName)
169+
assert.Equal(t, "A", records[0].RecordType)
170+
assert.Equal(t, "10.0.0.0", records[0].Targets[0])
171+
172+
// Regular subdomain record should work as before
173+
assert.Equal(t, "www.example.com", records[1].DNSName)
174+
}
175+
133176
func TestCivoProviderRecordsWithError(t *testing.T) {
134177
client, server, _ := civogo.NewAdvancedClientForTesting([]civogo.ConfigAdvanceClientForTesting{
135178
{
@@ -866,6 +909,30 @@ func TestCivoProviderGetRecordID(t *testing.T) {
866909
assert.Equal(t, id[0].ID, record[0].ID)
867910
}
868911

912+
func TestCivoProviderGetRecordIDApexAt(t *testing.T) {
913+
zone := civogo.DNSDomain{
914+
ID: "12345",
915+
Name: "test.com",
916+
}
917+
918+
// Civo API may return "@" for apex record names
919+
records := []civogo.DNSRecord{{
920+
ID: "1",
921+
Type: "A",
922+
Name: "@",
923+
Value: "10.0.0.0",
924+
DNSDomainID: "12345",
925+
TTL: 600,
926+
}}
927+
928+
// Apex endpoint: DNSName == zone name, so getStrippedRecordName returns ""
929+
ep := endpoint.Endpoint{DNSName: "test.com", Targets: endpoint.Targets{"10.0.0.0"}, RecordType: "A"}
930+
matched := getRecordID(records, zone, ep)
931+
932+
assert.Len(t, matched, 1)
933+
assert.Equal(t, "1", matched[0].ID)
934+
}
935+
869936
func TestCivo_submitChangesCreate(t *testing.T) {
870937
log.SetOutput(io.Discard)
871938
client, server, _ := civogo.NewAdvancedClientForTesting([]civogo.ConfigAdvanceClientForTesting{

0 commit comments

Comments
 (0)