Skip to content

Commit d31f0e7

Browse files
committed
fix: allow create to ignore existing matching records
ensure both DNS record and tunnel ingress are configured
1 parent 67de8fe commit d31f0e7

File tree

7 files changed

+102
-78
lines changed

7 files changed

+102
-78
lines changed

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
FROM golang:1 as build
1+
FROM golang:1 AS build
22
WORKDIR /go/src/app
33
COPY . .
44
ARG BUILD_COMMIT=unknown
55
ARG BUILD_TIME=unknown
6-
RUN --mount=type=cache,target=/root/.cache/go-build \
6+
RUN --mount=type=cache,target=/go/pkg/mod \
77
make build-binary BUILD_COMMIT=${BUILD_COMMIT} BUILD_TIME=${BUILD_TIME} OUTPUT=/go/bin/app
88

99
FROM gcr.io/distroless/static-debian12:nonroot

pkg/cf/client.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ func (p clientImpl) GetTunnelConfiguration(ctx context.Context, accountID, tunne
6868
return nil, fmt.Errorf("failed to get tunnel configuration: %w", err)
6969
}
7070

71+
log.Debug().Any("tunnel_configuration", tunnel).Send()
7172
return &tunnel, nil
7273
}
7374

@@ -79,20 +80,22 @@ func (p clientImpl) UpdateTunnelIngress(ctx context.Context, accountID, tunnelID
7980
return fmt.Errorf("failed to get tunnel configuration: %w", err)
8081
}
8182

82-
log.Debug().Any("tunnel_before", tunnel).Send()
83+
log.Trace().Any("tunnel_before", tunnel).Send()
8384

8485
tunnel.Config.Ingress = ingress
8586
params := cloudflare.TunnelConfigurationParams{
8687
TunnelID: tunnelID,
8788
Config: tunnel.Config,
8889
}
8990

90-
log.Debug().Any("tunnel_after", tunnel).Send()
91+
log.Trace().Any("tunnel_after", tunnel).Send()
9192

92-
if _, err := p.api.UpdateTunnelConfiguration(ctx, rc, params); err != nil {
93+
tunnel, err = p.api.UpdateTunnelConfiguration(ctx, rc, params)
94+
if err != nil {
9395
return fmt.Errorf("failed to update tunnel configuration: %w", err)
9496
}
9597

98+
log.Debug().Any("updated_tunnel_configuration", tunnel).Send()
9699
return nil
97100
}
98101

@@ -102,6 +105,7 @@ func (p clientImpl) ListZones(ctx context.Context) ([]cloudflare.Zone, error) {
102105
return nil, fmt.Errorf("failed to list zones: %w", err)
103106
}
104107

108+
log.Debug().Any("zones", zones).Send()
105109
return zones, nil
106110
}
107111

@@ -131,6 +135,7 @@ func (p clientImpl) ListZoneRecords(ctx context.Context, zoneID string) ([]cloud
131135
return nil, fmt.Errorf("failed to list dns records for zone %s: %w", zoneID, err)
132136
}
133137

138+
log.Debug().Any("records", records).Send()
134139
return records, nil
135140
}
136141

@@ -145,10 +150,12 @@ func (p clientImpl) CreateDNSRecord(ctx context.Context, record cloudflare.DNSRe
145150
Content: record.Content,
146151
}
147152

148-
if _, err := p.api.CreateDNSRecord(ctx, rc, params); err != nil {
153+
record, err := p.api.CreateDNSRecord(ctx, rc, params)
154+
if err != nil {
149155
return fmt.Errorf("failed to create dns record %s: %w", record.Name, err)
150156
}
151157

158+
log.Debug().Any("created_record", record).Send()
152159
return nil
153160
}
154161

@@ -158,6 +165,7 @@ func (p clientImpl) DeleteDNSRecord(ctx context.Context, zoneID, recordID string
158165
return fmt.Errorf("failed to delete dns record %s: %w", recordID, err)
159166
}
160167

168+
log.Debug().Str("deleted_record_id", recordID).Send()
161169
return nil
162170
}
163171

@@ -173,9 +181,11 @@ func (p clientImpl) UpdateDNSRecord(ctx context.Context, record cloudflare.DNSRe
173181
Content: record.Content,
174182
}
175183

176-
if _, err := p.api.UpdateDNSRecord(ctx, rc, params); err != nil {
184+
record, err := p.api.UpdateDNSRecord(ctx, rc, params)
185+
if err != nil {
177186
return fmt.Errorf("failed to update dns record %s: %w", record.Name, err)
178187
}
179188

189+
log.Debug().Any("updated_record", record).Send()
180190
return nil
181191
}

pkg/provider/provider.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,23 @@ func (p CloudflareTunnelProvider) Records(ctx context.Context) ([]*endpoint.Endp
3030
return nil, fmt.Errorf("failed to get tunnel configuration: %w", err)
3131
}
3232

33+
records, err := p.Cloudflare.ListAllZoneRecords(ctx)
34+
if err != nil {
35+
return nil, fmt.Errorf("failed to list all zone records: %w", err)
36+
}
37+
38+
recordMap := cf.RecordMapByName(records)
3339
endpoints := []*endpoint.Endpoint{}
40+
3441
for _, ingress := range tunnel.Config.Ingress {
3542
if ingress.Hostname == "" {
3643
continue
3744
}
3845

46+
if r, ok := recordMap[ingress.Hostname]; !ok || r == nil {
47+
continue
48+
}
49+
3950
endpoints = append(endpoints, &endpoint.Endpoint{
4051
DNSName: ingress.Hostname,
4152
RecordType: endpoint.RecordTypeCNAME,
@@ -53,7 +64,8 @@ func (p CloudflareTunnelProvider) Records(ctx context.Context) ([]*endpoint.Endp
5364
func (CloudflareTunnelProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) ([]*endpoint.Endpoint, error) {
5465
adjusted := []*endpoint.Endpoint{}
5566
for _, e := range endpoints {
56-
if e.RecordType != endpoint.RecordTypeCNAME {
67+
if e.RecordType != endpoint.RecordTypeCNAME &&
68+
e.RecordType != endpoint.RecordTypeTXT {
5769
continue
5870
}
5971

pkg/provider/rules.go

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,21 @@ import (
44
"fmt"
55

66
"github.com/cloudflare/cloudflare-go"
7+
"github.com/rs/zerolog/log"
78
"sigs.k8s.io/external-dns/plan"
89
)
910

1011
type Rules []cloudflare.UnvalidatedIngressRule
1112

1213
func (r *Rules) CreateRule(hostname, service string) error {
1314
for _, rule := range *r {
14-
if rule.Hostname == hostname {
15-
return fmt.Errorf("rule for hostname %s already exists", hostname)
15+
if rule.Hostname == hostname && rule.Service == service {
16+
log.Debug().Str("hostname", hostname).Str("service", service).Msg("rule already exists, skipping")
17+
return nil
18+
}
19+
20+
if rule.Hostname == hostname && rule.Service != service {
21+
return fmt.Errorf("rule for hostname %s already exists: %s", hostname, service)
1622
}
1723
}
1824

@@ -50,30 +56,18 @@ func (r *Rules) DeleteRule(hostname string) error {
5056

5157
func (r *Rules) ApplyChanges(changes *plan.Changes) error {
5258
for _, change := range changes.Create {
53-
if change.RecordType != "CNAME" {
54-
continue
55-
}
56-
5759
if err := r.CreateRule(change.DNSName, change.Targets[0]); err != nil {
5860
return err
5961
}
6062
}
6163

6264
for _, change := range changes.UpdateNew {
63-
if change.RecordType != "CNAME" {
64-
continue
65-
}
66-
6765
if err := r.UpdateRule(change.DNSName, change.Targets[0]); err != nil {
6866
return err
6967
}
7068
}
7169

7270
for _, change := range changes.Delete {
73-
if change.RecordType != "CNAME" {
74-
continue
75-
}
76-
7771
if err := r.DeleteRule(change.DNSName); err != nil {
7872
return err
7973
}

pkg/provider/rules_test.go

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@ func TestRules_CreateRule(t *testing.T) {
1818
},
1919
}
2020

21-
err := rules.CreateRule("example2.com", "service2")
21+
// same
22+
err := rules.CreateRule("example.com", "service1")
2223
assert.NoError(t, err)
24+
assert.Len(t, rules, 1)
2325

24-
err = rules.CreateRule("example.com", "service3")
25-
assert.Error(t, err)
26-
assert.EqualError(t, err, "rule for hostname example.com already exists")
26+
// new
27+
err = rules.CreateRule("example2.com", "service2")
28+
assert.NoError(t, err)
29+
assert.Len(t, rules, 2)
2730
}
2831

2932
func TestRules_UpdateRule(t *testing.T) {
@@ -36,10 +39,11 @@ func TestRules_UpdateRule(t *testing.T) {
3639

3740
err := rules.UpdateRule("example.com", "service2")
3841
assert.NoError(t, err)
42+
assert.Len(t, rules, 1)
3943

4044
err = rules.UpdateRule("example2.com", "service3")
41-
assert.Error(t, err)
4245
assert.EqualError(t, err, "rule for hostname example2.com does not exist")
46+
assert.Len(t, rules, 1)
4347
}
4448

4549
func TestRules_DeleteRule(t *testing.T) {
@@ -52,19 +56,18 @@ func TestRules_DeleteRule(t *testing.T) {
5256

5357
err := rules.DeleteRule("example.com")
5458
assert.NoError(t, err)
59+
assert.Len(t, rules, 0)
5560

5661
err = rules.DeleteRule("example2.com")
57-
assert.Error(t, err)
5862
assert.EqualError(t, err, "rule for hostname example2.com does not exist")
63+
assert.Len(t, rules, 0)
5964
}
6065

6166
func TestRules_ApplyChanges(t *testing.T) {
62-
rules := provider.Rules{
63-
cloudflare.UnvalidatedIngressRule{
64-
Hostname: "example.com",
65-
Service: "service1",
66-
},
67-
}
67+
rules := provider.Rules{{
68+
Hostname: "example.com",
69+
Service: "service1",
70+
}}
6871

6972
changes := &plan.Changes{
7073
Create: []*endpoint.Endpoint{
@@ -83,13 +86,17 @@ func TestRules_ApplyChanges(t *testing.T) {
8386
},
8487
Delete: []*endpoint.Endpoint{
8588
{
86-
DNSName: "example.com",
87-
Targets: []string{"service1"},
89+
DNSName: "example2.com",
90+
Targets: []string{"service2"},
8891
RecordType: "CNAME",
8992
},
9093
},
9194
}
9295

9396
err := rules.ApplyChanges(changes)
9497
assert.NoError(t, err)
98+
assert.Equal(t, provider.Rules{{
99+
Hostname: "example.com",
100+
Service: "service3",
101+
}}, rules)
95102
}

pkg/server/server.go

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,22 @@ func NewServer(port int64, p provider.Provider, readTimeout, writeTimeout time.D
3939
}
4040

4141
func handleNegotiation(p provider.Provider) http.HandlerFunc {
42+
log := log.With().Str("action", "handleNegotiation").Logger()
43+
4244
return func(w http.ResponseWriter, r *http.Request) {
4345
raw, err := json.Marshal(p.GetDomainFilter())
4446
if err != nil {
45-
write(w, http.StatusInternalServerError, nil)
47+
err = fmt.Errorf("failed to marshal response: %w", err)
48+
log.Error().Err(err).Send()
49+
w.WriteHeader(http.StatusInternalServerError)
50+
_, _ = w.Write([]byte(http.StatusText(http.StatusInternalServerError)))
4651
return
4752
}
4853

49-
log.Debug().Str("action", "handleNegotiation").RawJSON("domain_filter", raw).Send()
54+
log.Debug().RawJSON("domain_filter", raw).Send()
5055
w.Header().Set(contentTypeHeader, externalDNSMediaType)
51-
write(w, http.StatusOK, raw)
56+
w.WriteHeader(http.StatusOK)
57+
_, _ = w.Write(raw)
5258
}
5359
}
5460

@@ -58,21 +64,26 @@ func handleGetRecords(p provider.Provider) http.HandlerFunc {
5864
return func(w http.ResponseWriter, r *http.Request) {
5965
records, err := p.Records(r.Context())
6066
if err != nil {
61-
log.Error().Err(err).Msg("failed to get records")
62-
write(w, http.StatusInternalServerError, nil)
67+
err = fmt.Errorf("failed to get records: %w", err)
68+
log.Error().Err(err).Send()
69+
w.WriteHeader(http.StatusInternalServerError)
70+
_, _ = w.Write([]byte(http.StatusText(http.StatusInternalServerError)))
6371
return
6472
}
6573

6674
raw, err := json.Marshal(records)
6775
if err != nil {
68-
log.Error().Err(err).Msg("failed to marshal records to json")
69-
write(w, http.StatusInternalServerError, nil)
76+
err = fmt.Errorf("failed to marshal records to json: %w", err)
77+
log.Error().Err(err).Send()
78+
w.WriteHeader(http.StatusInternalServerError)
79+
_, _ = w.Write([]byte(http.StatusText(http.StatusInternalServerError)))
7080
return
7181
}
7282

7383
log.Debug().RawJSON("records", raw).Send()
7484
w.Header().Set(contentTypeHeader, externalDNSMediaType)
75-
write(w, http.StatusOK, raw)
85+
w.WriteHeader(http.StatusOK)
86+
_, _ = w.Write(raw)
7687
}
7788
}
7889

@@ -82,20 +93,24 @@ func handleApplyChanges(p provider.Provider) http.HandlerFunc {
8293
return func(w http.ResponseWriter, r *http.Request) {
8394
var changes plan.Changes
8495
if err := json.NewDecoder(r.Body).Decode(&changes); err != nil {
85-
log.Error().Err(err).Msg("failed to decode changes")
96+
err = fmt.Errorf("failed to decode changes: %w", err)
97+
log.Error().Err(err).Send()
98+
w.WriteHeader(http.StatusBadRequest)
8699
w.WriteHeader(http.StatusBadRequest)
87-
write(w, http.StatusBadRequest, nil)
100+
_, _ = w.Write([]byte(http.StatusText(http.StatusBadRequest)))
88101
return
89102
}
90103

91104
if err := p.ApplyChanges(r.Context(), &changes); err != nil {
92-
log.Error().Err(err).Any("changes", changes).Msg("failed to apply changes")
93-
write(w, http.StatusInternalServerError, nil)
105+
err = fmt.Errorf("failed to apply changes: %w", err)
106+
log.Error().Err(err).Any("changes", changes).Send()
107+
w.WriteHeader(http.StatusInternalServerError)
108+
_, _ = w.Write([]byte(http.StatusText(http.StatusInternalServerError)))
94109
return
95110
}
96111

97112
log.Debug().Any("changes", changes).Send()
98-
write(w, http.StatusNoContent, nil)
113+
w.WriteHeader(http.StatusNoContent)
99114
}
100115
}
101116

@@ -105,27 +120,34 @@ func handleAdjustEndpoints(p provider.Provider) http.HandlerFunc {
105120
return func(w http.ResponseWriter, r *http.Request) {
106121
var endpoints []*endpoint.Endpoint
107122
if err := json.NewDecoder(r.Body).Decode(&endpoints); err != nil {
108-
log.Error().Err(err).Msg("failed to decode endpoints")
109-
write(w, http.StatusBadRequest, nil)
123+
err = fmt.Errorf("failed to decode endpoints: %w", err)
124+
log.Error().Err(err).Send()
125+
w.WriteHeader(http.StatusBadRequest)
126+
_, _ = w.Write([]byte(http.StatusText(http.StatusBadRequest)))
110127
return
111128
}
112129

113130
endpoints, err := p.AdjustEndpoints(endpoints)
114131
if err != nil {
115-
log.Error().Err(err).Any("endpoints", endpoints).Msg("failed to adjust endpoints")
116-
write(w, http.StatusInternalServerError, nil)
132+
err = fmt.Errorf("failed to adjust endpoints: %w", err)
133+
log.Error().Err(err).Any("endpoints", endpoints).Send()
134+
w.WriteHeader(http.StatusInternalServerError)
135+
_, _ = w.Write([]byte(http.StatusText(http.StatusInternalServerError)))
117136
return
118137
}
119138

120139
raw, err := json.Marshal(endpoints)
121140
if err != nil {
122-
log.Error().Err(err).Any("endpoints", endpoints).Msg("failed to marshal endpoints to json")
123-
write(w, http.StatusInternalServerError, nil)
141+
err = fmt.Errorf("failed to marshal endpoints to json: %w", err)
142+
log.Error().Err(err).Any("endpoints", endpoints).Send()
143+
w.WriteHeader(http.StatusInternalServerError)
144+
_, _ = w.Write([]byte(http.StatusText(http.StatusInternalServerError)))
124145
return
125146
}
126147

127-
log.Debug().Any("endpoints", endpoints).Send()
148+
log.Debug().RawJSON("endpoints", raw).Send()
128149
w.Header().Set(contentTypeHeader, externalDNSMediaType)
129-
write(w, http.StatusOK, raw)
150+
w.WriteHeader(http.StatusOK)
151+
_, _ = w.Write(raw)
130152
}
131153
}

0 commit comments

Comments
 (0)