Skip to content

Commit c0d9262

Browse files
refactor(source): standardize FQDN template accross sources (#6093)
* chore(fqdn): standardize FQDN template Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * refactore(fqdn): standardize FQDN template Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> --------- Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>
1 parent 63296f5 commit c0d9262

File tree

13 files changed

+422
-157
lines changed

13 files changed

+422
-157
lines changed

source/contour_httpproxy.go

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -157,17 +157,14 @@ func (sc *httpProxySource) Endpoints(_ context.Context) ([]*endpoint.Endpoint, e
157157
}
158158

159159
// apply template if fqdn is missing on HTTPProxy
160-
if (sc.combineFQDNAnnotation || len(hpEndpoints) == 0) && sc.fqdnTemplate != nil {
161-
tmplEndpoints, err := sc.endpointsFromTemplate(hp)
162-
if err != nil {
163-
return nil, fmt.Errorf("failed to get endpoints from template: %w", err)
164-
}
165-
166-
if sc.combineFQDNAnnotation {
167-
hpEndpoints = append(hpEndpoints, tmplEndpoints...)
168-
} else {
169-
hpEndpoints = tmplEndpoints
170-
}
160+
hpEndpoints, err = fqdn.CombineWithTemplatedEndpoints(
161+
hpEndpoints,
162+
sc.fqdnTemplate,
163+
sc.combineFQDNAnnotation,
164+
func() ([]*endpoint.Endpoint, error) { return sc.endpointsFromTemplate(hp) },
165+
)
166+
if err != nil {
167+
return nil, err
171168
}
172169

173170
if len(hpEndpoints) == 0 {

source/fqdn/fqdn.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import (
2525

2626
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2727
"k8s.io/apimachinery/pkg/runtime"
28+
29+
"sigs.k8s.io/external-dns/endpoint"
2830
)
2931

3032
func ParseTemplate(input string) (*template.Template, error) {
@@ -94,3 +96,36 @@ func isIPv4String(target string) bool {
9496
}
9597
return netIP.Is4()
9698
}
99+
100+
// CombineWithTemplatedEndpoints merges annotation-based endpoints with template-based endpoints
101+
// according to the FQDN template configuration.
102+
//
103+
// Logic:
104+
// - If fqdnTemplate is nil, returns original endpoints unchanged
105+
// - If combineFQDNAnnotation is true, appends templated endpoints to existing
106+
// - If combineFQDNAnnotation is false and endpoints is empty, uses templated endpoints
107+
// - If combineFQDNAnnotation is false and endpoints exist, returns original unchanged
108+
func CombineWithTemplatedEndpoints(
109+
endpoints []*endpoint.Endpoint,
110+
fqdnTemplate *template.Template,
111+
combineFQDNAnnotation bool,
112+
templateFunc func() ([]*endpoint.Endpoint, error),
113+
) ([]*endpoint.Endpoint, error) {
114+
if fqdnTemplate == nil {
115+
return endpoints, nil
116+
}
117+
118+
if !combineFQDNAnnotation && len(endpoints) > 0 {
119+
return endpoints, nil
120+
}
121+
122+
templatedEndpoints, err := templateFunc()
123+
if err != nil {
124+
return nil, fmt.Errorf("failed to get endpoints from template: %w", err)
125+
}
126+
127+
if combineFQDNAnnotation {
128+
return append(endpoints, templatedEndpoints...), nil
129+
}
130+
return templatedEndpoints, nil
131+
}

source/fqdn/fqdn_test.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,16 @@ limitations under the License.
1717
package fqdn
1818

1919
import (
20+
"errors"
2021
"testing"
22+
"text/template"
2123

2224
"github.com/stretchr/testify/assert"
2325
"github.com/stretchr/testify/require"
2426
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2527
"k8s.io/apimachinery/pkg/runtime"
28+
29+
"sigs.k8s.io/external-dns/endpoint"
2630
)
2731

2832
func TestParseTemplate(t *testing.T) {
@@ -431,3 +435,103 @@ func TestExecTemplateExecutionError(t *testing.T) {
431435
require.Error(t, err)
432436
assert.Contains(t, err.Error(), "failed to apply template on TestKind default/test-name")
433437
}
438+
439+
func TestCombineWithTemplatedEndpoints(t *testing.T) {
440+
// Create a dummy template for tests that need one
441+
dummyTemplate := template.Must(template.New("test").Parse("{{.Name}}"))
442+
443+
annotationEndpoints := []*endpoint.Endpoint{
444+
endpoint.NewEndpoint("annotation.example.com", endpoint.RecordTypeA, "1.2.3.4"),
445+
}
446+
templatedEndpoints := []*endpoint.Endpoint{
447+
endpoint.NewEndpoint("template.example.com", endpoint.RecordTypeA, "5.6.7.8"),
448+
}
449+
450+
successTemplateFunc := func() ([]*endpoint.Endpoint, error) {
451+
return templatedEndpoints, nil
452+
}
453+
errorTemplateFunc := func() ([]*endpoint.Endpoint, error) {
454+
return nil, errors.New("template error")
455+
}
456+
457+
tests := []struct {
458+
name string
459+
endpoints []*endpoint.Endpoint
460+
fqdnTemplate *template.Template
461+
combineFQDNAnnotation bool
462+
templateFunc func() ([]*endpoint.Endpoint, error)
463+
want []*endpoint.Endpoint
464+
wantErr bool
465+
}{
466+
{
467+
name: "nil template returns original endpoints",
468+
endpoints: annotationEndpoints,
469+
fqdnTemplate: nil,
470+
templateFunc: successTemplateFunc,
471+
want: annotationEndpoints,
472+
},
473+
{
474+
name: "combine=false with existing endpoints returns original",
475+
endpoints: annotationEndpoints,
476+
fqdnTemplate: dummyTemplate,
477+
templateFunc: successTemplateFunc,
478+
want: annotationEndpoints,
479+
},
480+
{
481+
name: "combine=false with empty endpoints returns templated",
482+
endpoints: []*endpoint.Endpoint{},
483+
fqdnTemplate: dummyTemplate,
484+
templateFunc: successTemplateFunc,
485+
want: templatedEndpoints,
486+
},
487+
{
488+
name: "combine=true appends templated to existing",
489+
endpoints: annotationEndpoints,
490+
fqdnTemplate: dummyTemplate,
491+
combineFQDNAnnotation: true,
492+
templateFunc: successTemplateFunc,
493+
want: append(annotationEndpoints, templatedEndpoints...),
494+
},
495+
{
496+
name: "combine=true with empty endpoints returns templated",
497+
endpoints: []*endpoint.Endpoint{},
498+
fqdnTemplate: dummyTemplate,
499+
combineFQDNAnnotation: true,
500+
templateFunc: successTemplateFunc,
501+
want: templatedEndpoints,
502+
},
503+
{
504+
name: "template error is propagated",
505+
endpoints: []*endpoint.Endpoint{},
506+
fqdnTemplate: dummyTemplate,
507+
templateFunc: errorTemplateFunc,
508+
want: nil,
509+
wantErr: true,
510+
},
511+
{
512+
name: "nil endpoints with combine=false returns templated",
513+
endpoints: nil,
514+
fqdnTemplate: dummyTemplate,
515+
templateFunc: successTemplateFunc,
516+
want: templatedEndpoints,
517+
},
518+
}
519+
520+
for _, tt := range tests {
521+
t.Run(tt.name, func(t *testing.T) {
522+
got, err := CombineWithTemplatedEndpoints(
523+
tt.endpoints,
524+
tt.fqdnTemplate,
525+
tt.combineFQDNAnnotation,
526+
tt.templateFunc,
527+
)
528+
if tt.wantErr {
529+
require.Error(t, err)
530+
require.ErrorContains(t, err, "failed to get endpoints from template")
531+
return
532+
}
533+
require.NoError(t, err)
534+
assert.Equal(t, tt.want, got)
535+
})
536+
}
537+
}

source/ingress.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -162,13 +162,14 @@ func (sc *ingressSource) Endpoints(_ context.Context) ([]*endpoint.Endpoint, err
162162
ingEndpoints := endpointsFromIngress(ing, sc.ignoreHostnameAnnotation, sc.ignoreIngressTLSSpec, sc.ignoreIngressRulesSpec)
163163

164164
// apply template if host is missing on ingress
165-
if (sc.combineFQDNAnnotation || len(ingEndpoints) == 0) && sc.fqdnTemplate != nil {
166-
iEndpoints, err := sc.endpointsFromTemplate(ing)
167-
if err != nil {
168-
return nil, err
169-
}
170-
171-
ingEndpoints = append(ingEndpoints, iEndpoints...)
165+
ingEndpoints, err = fqdn.CombineWithTemplatedEndpoints(
166+
ingEndpoints,
167+
sc.fqdnTemplate,
168+
sc.combineFQDNAnnotation,
169+
func() ([]*endpoint.Endpoint, error) { return sc.endpointsFromTemplate(ing) },
170+
)
171+
if err != nil {
172+
return nil, err
172173
}
173174

174175
if len(ingEndpoints) == 0 {

source/istio_gateway.go

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -166,28 +166,26 @@ func (sc *gatewaySource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, e
166166
return nil, err
167167
}
168168

169-
// apply template if host is missing on gateway
170-
if (sc.combineFQDNAnnotation || len(gwHostnames) == 0) && sc.fqdnTemplate != nil {
171-
iHostnames, err := fqdn.ExecTemplate(sc.fqdnTemplate, gateway)
172-
if err != nil {
173-
return nil, err
174-
}
175-
176-
if sc.combineFQDNAnnotation {
177-
gwHostnames = append(gwHostnames, iHostnames...)
178-
} else {
179-
gwHostnames = iHostnames
180-
}
181-
}
182-
183169
log.Debugf("Processing gateway '%s/%s.%s' and hosts %q", gateway.Namespace, gateway.APIVersion, gateway.Name, strings.Join(gwHostnames, ","))
184170

185-
if len(gwHostnames) == 0 {
186-
log.Debugf("No hostnames could be generated from gateway %s/%s", gateway.Namespace, gateway.Name)
187-
continue
171+
gwEndpoints, err := sc.endpointsFromGateway(ctx, gwHostnames, gateway)
172+
if err != nil {
173+
return nil, err
188174
}
189175

190-
gwEndpoints, err := sc.endpointsFromGateway(ctx, gwHostnames, gateway)
176+
// apply template if host is missing on gateway
177+
gwEndpoints, err = fqdn.CombineWithTemplatedEndpoints(
178+
gwEndpoints,
179+
sc.fqdnTemplate,
180+
sc.combineFQDNAnnotation,
181+
func() ([]*endpoint.Endpoint, error) {
182+
hostnames, err := fqdn.ExecTemplate(sc.fqdnTemplate, gateway)
183+
if err != nil {
184+
return nil, err
185+
}
186+
return sc.endpointsFromGateway(ctx, hostnames, gateway)
187+
},
188+
)
191189
if err != nil {
192190
return nil, err
193191
}

source/istio_virtualservice.go

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -171,17 +171,14 @@ func (sc *virtualServiceSource) Endpoints(ctx context.Context) ([]*endpoint.Endp
171171
}
172172

173173
// apply template if host is missing on VirtualService
174-
if (sc.combineFQDNAnnotation || len(gwEndpoints) == 0) && sc.fqdnTemplate != nil {
175-
iEndpoints, err := sc.endpointsFromTemplate(ctx, vService)
176-
if err != nil {
177-
return nil, err
178-
}
179-
180-
if sc.combineFQDNAnnotation {
181-
gwEndpoints = append(gwEndpoints, iEndpoints...)
182-
} else {
183-
gwEndpoints = iEndpoints
184-
}
174+
gwEndpoints, err = fqdn.CombineWithTemplatedEndpoints(
175+
gwEndpoints,
176+
sc.fqdnTemplate,
177+
sc.combineFQDNAnnotation,
178+
func() ([]*endpoint.Endpoint, error) { return sc.endpointsFromTemplate(ctx, vService) },
179+
)
180+
if err != nil {
181+
return nil, err
185182
}
186183

187184
if len(gwEndpoints) == 0 {

0 commit comments

Comments
 (0)