external-dns/source/unstructured_fqdn_test.go
Ivan Ka eb40149b99
test: improve code coverage (#6321)
* test: improve code coverage

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* test: improve code coverage

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

---------

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>
2026-03-29 23:46:12 +05:30

1069 lines
33 KiB
Go

/*
Copyright 2026 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package source
import (
"testing"
"sigs.k8s.io/external-dns/internal/testutils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/source/annotations"
templatetest "sigs.k8s.io/external-dns/source/template/testutil"
)
func TestUnstructuredFqdnTemplatingExamples(t *testing.T) {
type cfg struct {
resources []string
fqdnTemplate string
targetTemplate string
fqdnTargetTemplate string
labelFilter string
combine bool
}
for _, tt := range []struct {
title string
cfg cfg
objects []*unstructured.Unstructured
expected []*endpoint.Endpoint
}{
{
title: "ConfigMap with comma-separated hostnames",
cfg: cfg{
resources: []string{"configmaps.v1"},
fqdnTemplate: `{{index .Object.data "hostnames"}}`,
targetTemplate: `{{index .Object.data "target"}}`,
},
objects: []*unstructured.Unstructured{
{
Object: map[string]any{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]any{
"name": "multi-dns",
"namespace": "default",
"annotations": map[string]any{
annotations.ControllerKey: annotations.ControllerValue,
},
},
"data": map[string]any{
"hostnames": "entry1.internal.tld,entry2.example.tld",
"target": "10.10.10.10",
},
},
},
},
expected: []*endpoint.Endpoint{
endpoint.NewEndpoint("entry1.internal.tld", endpoint.RecordTypeA, "10.10.10.10").
WithLabel(endpoint.ResourceLabelKey, "configmap/default/multi-dns"),
endpoint.NewEndpoint("entry2.example.tld", endpoint.RecordTypeA, "10.10.10.10").
WithLabel(endpoint.ResourceLabelKey, "configmap/default/multi-dns"),
},
},
{
title: "with IP address",
cfg: cfg{
resources: []string{"virtualmachineinstances.v1.kubevirt.io"},
fqdnTemplate: `{{.Name}}.{{index .Status.interfaces 0 "name"}}.vmi.com`,
targetTemplate: `{{index .Status.interfaces 0 "ipAddress"}}`,
},
objects: []*unstructured.Unstructured{
{
Object: map[string]any{
"apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachineInstance",
"metadata": map[string]any{
"name": "my-vm",
"namespace": "default",
},
"status": map[string]any{
"interfaces": []any{
map[string]any{
"ipAddress": "10.244.1.50",
"name": "main",
},
},
},
},
},
},
expected: []*endpoint.Endpoint{
endpoint.NewEndpoint("my-vm.main.vmi.com", endpoint.RecordTypeA, "10.244.1.50").
WithLabel(endpoint.ResourceLabelKey, "virtualmachineinstance/default/my-vm"),
},
},
{
title: "Crossplane RDSInstance with endpoint",
cfg: cfg{
resources: []string{"rdsinstances.v1alpha1.rds.aws.crossplane.io"},
fqdnTemplate: "{{.Name}}.db.example.com",
targetTemplate: "{{.Status.atProvider.endpoint.address}}",
},
objects: []*unstructured.Unstructured{
{
Object: map[string]any{
"apiVersion": "rds.aws.crossplane.io/v1alpha1",
"kind": "RDSInstance",
"metadata": map[string]any{
"name": "prod-postgres",
"namespace": "default",
},
"status": map[string]any{
"atProvider": map[string]any{
"endpoint": map[string]any{
"address": "prod-postgres.abc123.us-east-1.rds",
},
},
},
},
},
},
expected: []*endpoint.Endpoint{
endpoint.NewEndpoint("prod-postgres.db.example.com", endpoint.RecordTypeCNAME, "prod-postgres.abc123.us-east-1.rds.").
WithLabel(endpoint.ResourceLabelKey, "rdsinstance/default/prod-postgres"),
},
},
{
title: "multiple VirtualMachineInstances",
cfg: cfg{
resources: []string{"virtualmachineinstances.v1.kubevirt.io"},
fqdnTemplate: "{{.Name}}.vmi.example.com",
targetTemplate: `{{index .Status.interfaces 0 "ipAddress"}}`,
},
objects: []*unstructured.Unstructured{
{
Object: map[string]any{
"apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachineInstance",
"metadata": map[string]any{
"name": "vm-one",
"namespace": "default",
},
"status": map[string]any{
"interfaces": []any{
map[string]any{"ipAddress": "10.244.1.10"},
},
},
},
},
{
Object: map[string]any{
"apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachineInstance",
"metadata": map[string]any{
"name": "vm-two",
"namespace": "default",
},
"status": map[string]any{
"interfaces": []any{
map[string]any{"ipAddress": "10.244.1.20"},
},
},
},
},
},
expected: []*endpoint.Endpoint{
endpoint.NewEndpoint("vm-one.vmi.example.com", endpoint.RecordTypeA, "10.244.1.10").
WithLabel(endpoint.ResourceLabelKey, "virtualmachineinstance/default/vm-one"),
endpoint.NewEndpoint("vm-two.vmi.example.com", endpoint.RecordTypeA, "10.244.1.20").
WithLabel(endpoint.ResourceLabelKey, "virtualmachineinstance/default/vm-two"),
},
},
{
title: "multiple hosts from template",
cfg: cfg{
resources: []string{"proxyservices.v1beta1.proxyconfigs.acme.corp"},
fqdnTemplate: "{{.Name}}.mesh.com,{{.Name}}.internal.com",
targetTemplate: "{{index .Spec.hosts 0}}",
},
objects: []*unstructured.Unstructured{
{
Object: map[string]any{
"apiVersion": "proxyconfigs.acme.corp/v1beta1",
"kind": "ProxyService",
"metadata": map[string]any{
"name": "reviews",
"namespace": "default",
},
"spec": map[string]any{
"hosts": []any{
"promo.svc.local",
},
},
},
},
},
expected: []*endpoint.Endpoint{
endpoint.NewEndpoint("reviews.internal.com", endpoint.RecordTypeCNAME, "promo.svc.local").
WithLabel(endpoint.ResourceLabelKey, "proxyservice/default/reviews"),
endpoint.NewEndpoint("reviews.mesh.com", endpoint.RecordTypeCNAME, "promo.svc.local").
WithLabel(endpoint.ResourceLabelKey, "proxyservice/default/reviews"),
},
},
{
title: "with labels",
cfg: cfg{
resources: []string{"applications.v1alpha1.argoproj.io"},
fqdnTemplate: `{{index .Labels "app.kubernetes.io/instance"}}.apps.com`,
targetTemplate: "{{.Status.loadBalancer}}",
},
objects: []*unstructured.Unstructured{
{
Object: map[string]any{
"apiVersion": "argoproj.io/v1alpha1",
"kind": "Application",
"metadata": map[string]any{
"name": "guestbook",
"namespace": "default",
"labels": map[string]any{
"app.kubernetes.io/instance": "guestbook-prod",
},
},
"status": map[string]any{
"loadBalancer": "lb.example.com",
},
},
},
},
expected: []*endpoint.Endpoint{
endpoint.NewEndpoint("guestbook-prod.apps.com", endpoint.RecordTypeCNAME, "lb.example.com").
WithLabel(endpoint.ResourceLabelKey, "application/default/guestbook"),
},
},
{
title: "with ttl annotation set",
cfg: cfg{
resources: []string{"applications.v1alpha1.argoproj.io"},
fqdnTemplate: `{{index .Labels "app.kubernetes.io/instance"}}.apps.com`,
targetTemplate: "{{.Status.loadBalancer}}",
},
objects: []*unstructured.Unstructured{
{
Object: map[string]any{
"apiVersion": "argoproj.io/v1alpha1",
"kind": "Application",
"metadata": map[string]any{
"name": "guestbook",
"namespace": "ns",
"labels": map[string]any{
"app.kubernetes.io/instance": "guestbook-prod",
},
"annotations": map[string]any{
annotations.TtlKey: "300",
},
},
"status": map[string]any{
"loadBalancer": "lb.example.com",
},
},
},
},
expected: []*endpoint.Endpoint{
endpoint.NewEndpointWithTTL("guestbook-prod.apps.com", endpoint.RecordTypeCNAME, 300, "lb.example.com").
WithLabel(endpoint.ResourceLabelKey, "application/ns/guestbook"),
},
},
{
title: "two different resource types - VirtualMachineInstance and RDSInstance",
cfg: cfg{
resources: []string{
"virtualmachineinstances.v1.kubevirt.io",
"rdsinstances.v1alpha1.rds.aws.crossplane.io",
},
fqdnTemplate: "{{.Name}}.{{.Namespace}}.com",
targetTemplate: `
{{if .Status.interfaces}}{{index .Status.interfaces 0 "ipAddress"}}{{else}}{{.Status.atProvider.endpoint.address}}{{end}}`,
},
objects: []*unstructured.Unstructured{
{
Object: map[string]any{
"apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachineInstance",
"metadata": map[string]any{
"name": "my-vm",
"namespace": "vms",
},
"status": map[string]any{
"interfaces": []any{
map[string]any{
"ipAddress": "10.244.1.100",
},
},
},
},
},
{
Object: map[string]any{
"apiVersion": "rds.aws.crossplane.io/v1alpha1",
"kind": "RDSInstance",
"metadata": map[string]any{
"name": "my-db",
"namespace": "databases",
},
"status": map[string]any{
"atProvider": map[string]any{
"endpoint": map[string]any{
"address": "my-db.abc123.us-west-2.rds",
},
},
},
},
},
},
expected: []*endpoint.Endpoint{
endpoint.NewEndpoint("my-db.databases.com", endpoint.RecordTypeCNAME, "my-db.abc123.us-west-2.rds").
WithLabel(endpoint.ResourceLabelKey, "rdsinstance/databases/my-db"),
endpoint.NewEndpoint("my-vm.vms.com", endpoint.RecordTypeA, "10.244.1.100").
WithLabel(endpoint.ResourceLabelKey, "virtualmachineinstance/vms/my-vm"),
},
},
{
title: "two different resource types with same template",
cfg: cfg{
resources: []string{
"virtualmachineinstances.v1.kubevirt.io",
"targetgroupbindings.v1beta1.elbv2.k8s.aws",
},
fqdnTemplate: "{{.Name}}.{{.Kind}}.example.com",
targetTemplate: "{{.Status.target}}",
},
objects: []*unstructured.Unstructured{
{
Object: map[string]any{
"apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachineInstance",
"metadata": map[string]any{
"name": "web-server",
"namespace": "default",
},
"status": map[string]any{
"target": "192.168.1.10",
},
},
},
{
Object: map[string]any{
"apiVersion": "elbv2.k8s.aws/v1beta1",
"kind": "TargetGroupBinding",
"metadata": map[string]any{
"name": "api-tgb",
"namespace": "default",
},
"status": map[string]any{
"target": "lb.api.example.com",
},
},
},
},
expected: []*endpoint.Endpoint{
endpoint.NewEndpoint("api-tgb.TargetGroupBinding.example.com", endpoint.RecordTypeCNAME, "lb.api.example.com").
WithLabel(endpoint.ResourceLabelKey, "targetgroupbinding/default/api-tgb"),
endpoint.NewEndpoint("web-server.VirtualMachineInstance.example.com", endpoint.RecordTypeA, "192.168.1.10").
WithLabel(endpoint.ResourceLabelKey, "virtualmachineinstance/default/web-server"),
},
},
{
title: "combined annotations and template",
cfg: cfg{
resources: []string{"virtualmachineinstances.v1.kubevirt.io"},
fqdnTemplate: "{{.Name}}.template.example.com",
targetTemplate: `{{index .Status.interfaces 0 "ipAddress"}}`,
combine: true,
},
objects: []*unstructured.Unstructured{
{
Object: map[string]any{
"apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachineInstance",
"metadata": map[string]any{
"name": "my-vm",
"namespace": "default",
"annotations": map[string]any{
annotations.HostnameKey: "my-vm.annotation.example.com",
annotations.TargetKey: "192.168.1.100",
},
},
"status": map[string]any{
"interfaces": []any{
map[string]any{
"ipAddress": "10.244.1.50",
},
},
},
},
},
},
expected: []*endpoint.Endpoint{
endpoint.NewEndpoint("my-vm.annotation.example.com", endpoint.RecordTypeA, "192.168.1.100").
WithLabel(endpoint.ResourceLabelKey, "virtualmachineinstance/default/my-vm"),
endpoint.NewEndpoint("my-vm.template.example.com", endpoint.RecordTypeA, "10.244.1.50").
WithLabel(endpoint.ResourceLabelKey, "virtualmachineinstance/default/my-vm"),
},
},
{
title: "three different resource types",
cfg: cfg{
resources: []string{
"virtualmachineinstances.v1.kubevirt.io",
"targetgroupbinding.v1beta1.elbv2.k8s.aws",
"apisixroute.v2.apisix.apache.org",
},
fqdnTemplate: `
{{if eq .Kind "VirtualMachineInstance"}}{{.Name}}.vm.com{{end}},
{{if eq .Kind "TargetGroupBinding"}}{{.Name}}.tgb.com{{end}},
{{if eq .Kind "ApisixRoute"}}{{.Name}}.route.com{{end}}`,
targetTemplate: `
{{if eq .Kind "VirtualMachineInstance"}}{{index .Status.interfaces 0 "ipAddress"}}{{end}},
{{if eq .Kind "TargetGroupBinding"}}{{.Status.loadBalancerHostname}}{{end}},
{{if eq .Kind "ApisixRoute"}}{{.Status.apisix.gateway}}{{end}}`,
},
objects: []*unstructured.Unstructured{
{
Object: map[string]any{
"apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachineInstance",
"metadata": map[string]any{
"name": "my-vm",
"namespace": "vms",
},
"status": map[string]any{
"interfaces": []any{
map[string]any{
"ipAddress": "10.0.0.1",
"name": "default",
},
},
},
},
},
{
Object: map[string]any{
"apiVersion": "elbv2.k8s.aws/v1beta1",
"kind": "TargetGroupBinding",
"metadata": map[string]any{
"name": "my-tgb",
"namespace": "apps",
},
"status": map[string]any{
"loadBalancerHostname": "my-alb.us-east-1.elb.amazonaws.com",
},
},
},
{
Object: map[string]any{
"apiVersion": "apisix.apache.org/v2",
"kind": "ApisixRoute",
"metadata": map[string]any{
"name": "httpbin",
"namespace": "ingress-apisix",
},
"spec": map[string]any{
"ingressClassName": "apisix",
"http": []any{
map[string]any{
"name": "httpbin",
"match": map[string]any{
"paths": []any{"/ip"},
},
"backends": []any{
map[string]any{
"serviceName": "httpbin",
"servicePort": int64(80),
},
},
},
},
},
"status": map[string]any{
"apisix": map[string]any{
"gateway": "apisix-gateway.ingress-apisix.svc.cluster.local",
},
},
},
},
},
expected: []*endpoint.Endpoint{
endpoint.NewEndpoint("httpbin.route.com", endpoint.RecordTypeCNAME, "apisix-gateway.ingress-apisix.svc.cluster.local").
WithLabel(endpoint.ResourceLabelKey, "apisixroute/ingress-apisix/httpbin"),
endpoint.NewEndpoint("my-tgb.tgb.com", endpoint.RecordTypeCNAME, "my-alb.us-east-1.elb.amazonaws.com").
WithLabel(endpoint.ResourceLabelKey, "targetgroupbinding/apps/my-tgb"),
endpoint.NewEndpoint("my-vm.vm.com", endpoint.RecordTypeA, "10.0.0.1").
WithLabel(endpoint.ResourceLabelKey, "virtualmachineinstance/vms/my-vm"),
},
},
{
title: "ACK S3 Bucket with FieldExport to ConfigMap",
cfg: cfg{
resources: []string{
"buckets.v1alpha1.s3.services.k8s.aws",
"fieldexports.v1alpha1.services.k8s.aws",
"configmap.v1"},
fqdnTemplate: `{{if eq .Kind "ConfigMap"}}{{.Name}}.s3.example.com{{end}}`,
targetTemplate: `
{{if eq .Kind "ConfigMap"}}{{$url := index .Object.data "default.export-user-data-bucket"}}{{trimSuffix (trimPrefix $url "https://") "/"}}{{end}}`,
labelFilter: "app.kubernetes.io/name=example-app",
},
objects: []*unstructured.Unstructured{
{
Object: map[string]any{
"apiVersion": "s3.services.k8s.aws/v1alpha1",
"kind": "Bucket",
"metadata": map[string]any{
"name": "application-user-data",
"namespace": "default",
"labels": map[string]any{
"app.kubernetes.io/name": "example-app",
"app.kubernetes.io/part-of": "exported-config",
},
"annotations": map[string]any{
annotations.ControllerKey: annotations.ControllerValue,
},
},
"spec": map[string]any{
"name": "doc-example-bucket",
},
},
},
{
Object: map[string]any{
"apiVersion": "services.k8s.aws/v1alpha1",
"kind": "FieldExport",
"metadata": map[string]any{
"name": "export-user-data-bucket",
"namespace": "default",
"labels": map[string]any{
"app.kubernetes.io/name": "example-app",
"app.kubernetes.io/part-of": "exported-config",
},
"annotations": map[string]any{
annotations.ControllerKey: annotations.ControllerValue,
},
},
"spec": map[string]any{
"to": map[string]any{
"name": "application-user-data-cm",
"namespace": "default",
"kind": "configmap",
},
"from": map[string]any{
"path": ".status.location",
"resource": map[string]any{
"group": "s3.services.k8s.aws",
"kind": "Bucket",
"name": "application-user-data",
"namespace": "default",
},
},
},
},
},
{
Object: map[string]any{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]any{
"name": "application-user-data-cm",
"namespace": "default",
"labels": map[string]any{
"app.kubernetes.io/name": "example-app",
"app.kubernetes.io/part-of": "exported-config",
},
"annotations": map[string]any{
annotations.ControllerKey: annotations.ControllerValue,
},
},
"data": map[string]any{
"default.export-user-data-bucket": "https://doc-example-bucket.s3.amazonaws.com/",
},
},
},
},
expected: []*endpoint.Endpoint{
endpoint.NewEndpoint("application-user-data-cm.s3.example.com", endpoint.RecordTypeCNAME, "doc-example-bucket.s3.amazonaws.com").
WithLabel(endpoint.ResourceLabelKey, "configmap/default/application-user-data-cm"),
},
},
{
title: "EndpointSlice for headless service with per-pod DNS",
cfg: cfg{
resources: []string{"endpointslices.v1.discovery.k8s.io"},
fqdnTargetTemplate: `
{{if and (eq .Kind "EndpointSlice") (hasKey .Labels "service.kubernetes.io/headless")}}
{{range $ep := .Object.endpoints}}{{if $ep.conditions.ready}}{{range $ep.addresses}}{{$ep.targetRef.name}}.pod.com:{{.}},{{end}}{{end}}{{end}}{{end}}`,
},
objects: []*unstructured.Unstructured{
{
Object: map[string]any{
"apiVersion": "discovery.k8s.io/v1",
"kind": "EndpointSlice",
"metadata": map[string]any{
"name": "test-headless-abc12",
"namespace": "default",
"labels": map[string]any{
"endpointslice.kubernetes.io/managed-by": "endpointslice-controller.k8s.io",
"kubernetes.io/service-name": "test-headless",
"service.kubernetes.io/headless": "",
},
},
"addressType": "IPv4",
"endpoints": []any{
map[string]any{
"addresses": []any{"10.244.1.2", "2001:db8::1"},
"conditions": map[string]any{
"ready": true,
},
"nodeName": "worker1",
"targetRef": map[string]any{
"kind": "Pod",
"name": "app-abc12",
"namespace": "default",
},
},
map[string]any{
"addresses": []any{"10.244.2.3", "10.244.2.4"},
"conditions": map[string]any{
"ready": true,
},
"nodeName": "worker2",
"targetRef": map[string]any{
"kind": "Pod",
"name": "app-def34",
"namespace": "default",
},
},
},
"ports": []any{
map[string]any{
"name": "http",
"port": int64(80),
"protocol": "TCP",
},
},
},
},
},
expected: []*endpoint.Endpoint{
endpoint.NewEndpoint("app-abc12.pod.com", endpoint.RecordTypeA, "10.244.1.2").
WithLabel(endpoint.ResourceLabelKey, "endpointslice/default/test-headless-abc12"),
endpoint.NewEndpoint("app-abc12.pod.com", endpoint.RecordTypeAAAA, "2001:db8::1").
WithLabel(endpoint.ResourceLabelKey, "endpointslice/default/test-headless-abc12"),
endpoint.NewEndpoint("app-def34.pod.com", endpoint.RecordTypeA, "10.244.2.3", "10.244.2.4").
WithLabel(endpoint.ResourceLabelKey, "endpointslice/default/test-headless-abc12"),
},
},
{
title: "EndpointSlice for headless service with single FQDN per EndpointSlice",
cfg: cfg{
resources: []string{"endpointslices.v1.discovery.k8s.io"},
fqdnTargetTemplate: `
{{if and (eq .Kind "EndpointSlice") (hasKey .Labels "service.kubernetes.io/headless")}}
{{$svcName := index .Labels "kubernetes.io/service-name"}}{{range $ep := .Object.endpoints}}
{{if $ep.conditions.ready}}{{range $ep.addresses}}{{$svcName}}.example.com:{{.}},{{end}}{{end}}{{end}}{{end}}`,
},
objects: []*unstructured.Unstructured{
{
Object: map[string]any{
"apiVersion": "discovery.k8s.io/v1",
"kind": "EndpointSlice",
"metadata": map[string]any{
"name": "test-headless-abc12",
"namespace": "default",
"labels": map[string]any{
"endpointslice.kubernetes.io/managed-by": "endpointslice-controller.k8s.io",
"kubernetes.io/service-name": "my-headless",
"service.kubernetes.io/headless": "",
},
},
"addressType": "IPv4",
"endpoints": []any{
map[string]any{
"addresses": []any{"10.244.1.2"},
"conditions": map[string]any{
"ready": true,
},
"nodeName": "worker1",
"targetRef": map[string]any{
"kind": "Pod",
"name": "app-abc12",
"namespace": "default",
},
},
map[string]any{
"addresses": []any{"10.244.2.3", "10.244.2.4"},
"conditions": map[string]any{
"ready": true,
},
"nodeName": "worker2",
"targetRef": map[string]any{
"kind": "Pod",
"name": "app-def34",
"namespace": "default",
},
},
},
"ports": []any{
map[string]any{
"name": "http",
"port": int64(80),
"protocol": "TCP",
},
},
},
},
},
expected: []*endpoint.Endpoint{
endpoint.NewEndpoint("my-headless.example.com", endpoint.RecordTypeA, "10.244.1.2", "10.244.2.3", "10.244.2.4").
WithLabel(endpoint.ResourceLabelKey, "endpointslice/default/test-headless-abc12"),
},
},
{
title: "fqdnTargetTemplate returns no values when condition not met",
cfg: cfg{
resources: []string{"endpointslices.v1.discovery.k8s.io"},
fqdnTargetTemplate: `
{{if and (eq .Kind "EndpointSlice") (hasKey .Labels "service.kubernetes.io/headless")}}
{{range $ep := .Object.endpoints}}{{if $ep.conditions.ready}}{{range $ep.addresses}}{{$ep.targetRef.name}}.pod.com:{{.}},{{end}}{{end}}{{end}}{{end}}`,
},
objects: []*unstructured.Unstructured{
{
Object: map[string]any{
"apiVersion": "discovery.k8s.io/v1",
"kind": "EndpointSlice",
"metadata": map[string]any{
"name": "regular-service-abc12",
"namespace": "default",
"labels": map[string]any{
"endpointslice.kubernetes.io/managed-by": "endpointslice-controller.k8s.io",
"kubernetes.io/service-name": "regular-service",
// Note: missing service.kubernetes.io/headless label
},
},
"addressType": "IPv4",
"endpoints": []any{
map[string]any{
"addresses": []any{"10.244.1.2"},
"conditions": map[string]any{
"ready": true,
},
"targetRef": map[string]any{
"kind": "Pod",
"name": "app-abc12",
"namespace": "default",
},
},
},
},
},
},
expected: nil,
},
{
title: "both fqdnTargetTemplate and fqdnTemplate set - endpoints from both are combined",
cfg: cfg{
resources: []string{"virtualmachineinstances.v1.kubevirt.io"},
fqdnTargetTemplate: `{{range $iface := .Status.interfaces}}{{$.Name}}-{{index $iface "name"}}.ifaces.example.com:{{index $iface "ipAddress"}},{{end}}`,
fqdnTemplate: "{{.Name}}.vmi.example.com",
targetTemplate: `{{index .Status.interfaces 0 "ipAddress"}}`,
combine: true,
},
objects: []*unstructured.Unstructured{
{
Object: map[string]any{
"apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachineInstance",
"metadata": map[string]any{
"name": "my-vm",
"namespace": "default",
},
"status": map[string]any{
"interfaces": []any{
map[string]any{
"name": "eth0",
"ipAddress": "10.244.1.50",
},
map[string]any{
"name": "eth1",
"ipAddress": "192.168.1.50",
},
},
},
},
},
},
expected: []*endpoint.Endpoint{
// from fqdnTargetTemplate: per-interface 1:1 host:IP pairs
endpoint.NewEndpoint("my-vm-eth0.ifaces.example.com", endpoint.RecordTypeA, "10.244.1.50").
WithLabel(endpoint.ResourceLabelKey, "virtualmachineinstance/default/my-vm"),
endpoint.NewEndpoint("my-vm-eth1.ifaces.example.com", endpoint.RecordTypeA, "192.168.1.50").
WithLabel(endpoint.ResourceLabelKey, "virtualmachineinstance/default/my-vm"),
// from fqdnTemplate + targetTemplate: service-level record for the primary interface
endpoint.NewEndpoint("my-vm.vmi.example.com", endpoint.RecordTypeA, "10.244.1.50").
WithLabel(endpoint.ResourceLabelKey, "virtualmachineinstance/default/my-vm"),
},
},
{
title: "fqdnTargetTemplate pair without colon separator is skipped",
cfg: cfg{
resources: []string{"virtualmachineinstances.v1.kubevirt.io"},
fqdnTargetTemplate: "nocolonseparator",
},
objects: []*unstructured.Unstructured{
{
Object: map[string]any{
"apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachineInstance",
"metadata": map[string]any{
"name": "my-vm",
"namespace": "default",
},
},
},
},
expected: nil,
},
{
title: "fqdnTargetTemplate pair with empty host is skipped",
cfg: cfg{
resources: []string{"virtualmachineinstances.v1.kubevirt.io"},
fqdnTargetTemplate: ":10.0.0.1",
},
objects: []*unstructured.Unstructured{
{
Object: map[string]any{
"apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachineInstance",
"metadata": map[string]any{
"name": "my-vm",
"namespace": "default",
},
},
},
},
expected: nil,
},
{
title: "fqdnTargetTemplate pair with empty target is skipped",
cfg: cfg{
resources: []string{"virtualmachineinstances.v1.kubevirt.io"},
fqdnTargetTemplate: "host.example.com:",
},
objects: []*unstructured.Unstructured{
{
Object: map[string]any{
"apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachineInstance",
"metadata": map[string]any{
"name": "my-vm",
"namespace": "default",
},
},
},
},
expected: nil,
},
} {
t.Run(tt.title, func(t *testing.T) {
kubeClient, dynamicClient := setupUnstructuredTestClients(t, tt.cfg.resources, tt.objects)
var selector labels.Selector
if tt.cfg.labelFilter != "" {
var err error
selector, err = labels.Parse(tt.cfg.labelFilter)
require.NoError(t, err)
} else {
selector = labels.Everything()
}
src, err := NewUnstructuredFQDNSource(
t.Context(),
dynamicClient,
kubeClient,
&Config{
LabelFilter: selector,
UnstructuredResources: tt.cfg.resources,
TemplateEngine: templatetest.MustEngine(t, tt.cfg.fqdnTemplate, tt.cfg.targetTemplate, tt.cfg.fqdnTargetTemplate, tt.cfg.combine),
},
)
require.NoError(t, err)
endpoints, err := src.Endpoints(t.Context())
require.NoError(t, err)
testutils.ValidateEndpoints(t, endpoints, tt.expected)
})
}
}
func TestUnstructuredWrapper_Templating(t *testing.T) {
tests := []struct {
name string
tmpl string
obj *unstructured.Unstructured
want []string
wantErr bool
}{
{
name: "typed-style Name and Namespace access",
tmpl: "{{.Name}}.{{.Namespace}}.example.com",
obj: &unstructured.Unstructured{
Object: map[string]any{
"apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachineInstance",
"metadata": map[string]any{
"name": "my-vm",
"namespace": "default",
},
},
},
want: []string{"my-vm.default.example.com"},
},
{
name: "raw Metadata map access",
tmpl: "{{.Metadata.name}}.{{.Metadata.namespace}}.example.com",
obj: &unstructured.Unstructured{
Object: map[string]any{
"apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachineInstance",
"metadata": map[string]any{
"name": "my-vm",
"namespace": "default",
},
},
},
want: []string{"my-vm.default.example.com"},
},
{
name: "nested Status field access",
tmpl: "{{.Status.atProvider.endpoint.address}}",
obj: &unstructured.Unstructured{
Object: map[string]any{
"apiVersion": "rds.aws.crossplane.io/v1alpha1",
"kind": "RDSInstance",
"metadata": map[string]any{
"name": "prod-db",
"namespace": "default",
},
"status": map[string]any{
"atProvider": map[string]any{
"endpoint": map[string]any{
"address": "prod-db.abc123.rds.amazonaws.com",
},
},
},
},
},
want: []string{"prod-db.abc123.rds.amazonaws.com"},
},
{
name: "array index access via Status",
tmpl: `{{index .Status.interfaces 0 "ipAddress"}}`,
obj: &unstructured.Unstructured{
Object: map[string]any{
"apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachineInstance",
"metadata": map[string]any{
"name": "vm-1",
"namespace": "default",
},
"status": map[string]any{
"interfaces": []any{
map[string]any{
"ipAddress": "10.244.1.50",
"name": "default",
},
},
},
},
},
want: []string{"10.244.1.50"},
},
{
name: "typed-style Labels access",
tmpl: `{{index .Labels "app.kubernetes.io/instance"}}.example.com`,
obj: &unstructured.Unstructured{
Object: map[string]any{
"apiVersion": "argoproj.io/v1alpha1",
"kind": "Application",
"metadata": map[string]any{
"name": "guestbook",
"namespace": "argocd",
"labels": map[string]any{
"app.kubernetes.io/instance": "guestbook-prod",
},
},
},
},
want: []string{"guestbook-prod.example.com"},
},
{
name: "Kind and APIVersion access",
tmpl: "{{.Kind}}.{{.APIVersion}}.example.com",
obj: &unstructured.Unstructured{
Object: map[string]any{
"apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachineInstance",
"metadata": map[string]any{
"name": "test",
"namespace": "default",
},
},
},
want: []string{"VirtualMachineInstance.kubevirt.io/v1.example.com"},
},
{
name: "Spec hosts array",
tmpl: `{{index .Spec.hosts 0}}`,
obj: &unstructured.Unstructured{
Object: map[string]any{
"apiVersion": "networking.istio.io/v1beta1",
"kind": "VirtualService",
"metadata": map[string]any{
"name": "reviews",
"namespace": "bookinfo",
},
"spec": map[string]any{
"hosts": []any{
"reviews.bookinfo.svc.cluster.local",
},
},
},
},
want: []string{"reviews.bookinfo.svc.cluster.local"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
engine := templatetest.MustEngine(t, tt.tmpl, "", "", false)
wrapped := newUnstructuredWrapper(tt.obj)
got, err := engine.ExecFQDN(wrapped)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}