mirror of
				https://github.com/kubernetes-sigs/external-dns.git
				synced 2025-11-04 04:31:00 +01:00 
			
		
		
		
	chore(source/node): template expansion (#5498)
This commit is contained in:
		
							parent
							
								
									93d4d47bff
								
							
						
					
					
						commit
						f55be38b45
					
				@ -316,7 +316,7 @@ By setting the hostname annotation in the ingress resource, ExternalDNS construc
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
```yml
 | 
					```yml
 | 
				
			||||||
args:
 | 
					args:
 | 
				
			||||||
  - --fqdn-template="{{range .Status.Addresses}}{{if and (eq .Type \"ExternalIP\") (isIPv4 .Address)}}{{.Address | replace \".\" \"-\"}}{{break}}{{end}}{{end}}.example.com
 | 
					  - --fqdn-template="{{range .Status.Addresses}}{{if and (eq .Type \"ExternalIP\") (isIPv4 .Address)}}{{.Address | replace \".\" \"-\"}}{{break}}{{end}}{{end}}.example.com"
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This is a complex template that iternates through a list of a Node's Addresses and creates a FQDN with public IPv4 addresses.
 | 
					This is a complex template that iternates through a list of a Node's Addresses and creates a FQDN with public IPv4 addresses.
 | 
				
			||||||
 | 
				
			|||||||
@ -48,7 +48,7 @@ type nodeSource struct {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewNodeSource creates a new nodeSource with the given config.
 | 
					// NewNodeSource creates a new nodeSource with the given config.
 | 
				
			||||||
func NewNodeSource(ctx context.Context, kubeClient kubernetes.Interface, annotationFilter, fqdnTemplate string, labelSelector labels.Selector, exposeInternalIPv6 bool, excludeUnschedulable bool) (Source, error) {
 | 
					func NewNodeSource(ctx context.Context, kubeClient kubernetes.Interface, annotationFilter, fqdnTemplate string, labelSelector labels.Selector, exposeInternalIPv6, excludeUnschedulable bool) (Source, error) {
 | 
				
			||||||
	tmpl, err := fqdn.ParseTemplate(fqdnTemplate)
 | 
						tmpl, err := fqdn.ParseTemplate(fqdnTemplate)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
@ -103,8 +103,7 @@ func (ns *nodeSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, erro
 | 
				
			|||||||
	// create endpoints for all nodes
 | 
						// create endpoints for all nodes
 | 
				
			||||||
	for _, node := range nodes {
 | 
						for _, node := range nodes {
 | 
				
			||||||
		// Check controller annotation to see if we are responsible.
 | 
							// Check controller annotation to see if we are responsible.
 | 
				
			||||||
		controller, ok := node.Annotations[controllerAnnotationKey]
 | 
							if controller, ok := node.Annotations[controllerAnnotationKey]; ok && controller != controllerAnnotationValue {
 | 
				
			||||||
		if ok && controller != controllerAnnotationValue {
 | 
					 | 
				
			||||||
			log.Debugf("Skipping node %s because controller value does not match, found: %s, required: %s",
 | 
								log.Debugf("Skipping node %s because controller value does not match, found: %s, required: %s",
 | 
				
			||||||
				node.Name, controller, controllerAnnotationValue)
 | 
									node.Name, controller, controllerAnnotationValue)
 | 
				
			||||||
			continue
 | 
								continue
 | 
				
			||||||
@ -119,28 +118,8 @@ func (ns *nodeSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, erro
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		ttl := annotations.TTLFromAnnotations(node.Annotations, fmt.Sprintf("node/%s", node.Name))
 | 
							ttl := annotations.TTLFromAnnotations(node.Annotations, fmt.Sprintf("node/%s", node.Name))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// create new endpoint with the information we already have
 | 
					 | 
				
			||||||
		ep := &endpoint.Endpoint{
 | 
					 | 
				
			||||||
			RecordTTL: ttl,
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if ns.fqdnTemplate != nil {
 | 
					 | 
				
			||||||
			hostnames, err := fqdn.ExecTemplate(ns.fqdnTemplate, node)
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				return nil, err
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			hostname := ""
 | 
					 | 
				
			||||||
			if len(hostnames) > 0 {
 | 
					 | 
				
			||||||
				hostname = hostnames[0]
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			ep.DNSName = hostname
 | 
					 | 
				
			||||||
			log.Debugf("applied template for %s, converting to %s", node.Name, ep.DNSName)
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			ep.DNSName = node.Name
 | 
					 | 
				
			||||||
			log.Debugf("not applying template for %s", node.Name)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		addrs := annotations.TargetsFromTargetAnnotation(node.Annotations)
 | 
							addrs := annotations.TargetsFromTargetAnnotation(node.Annotations)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if len(addrs) == 0 {
 | 
							if len(addrs) == 0 {
 | 
				
			||||||
			addrs, err = ns.nodeAddresses(node)
 | 
								addrs, err = ns.nodeAddresses(node)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
@ -148,12 +127,32 @@ func (ns *nodeSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, erro
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		ep.Labels = endpoint.NewLabels()
 | 
							dnsNames := make(map[string]bool)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if ns.fqdnTemplate != nil {
 | 
				
			||||||
 | 
								hostnames, err := fqdn.ExecTemplate(ns.fqdnTemplate, node)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for _, name := range hostnames {
 | 
				
			||||||
 | 
									dnsNames[name] = true
 | 
				
			||||||
 | 
									log.Debugf("applied template for %s, converting to %s", node.Name, name)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								dnsNames[node.Name] = true
 | 
				
			||||||
 | 
								log.Debugf("not applying template for %s", node.Name)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for dns := range dnsNames {
 | 
				
			||||||
 | 
								log.Debugf("adding endpoint with %d targets", len(addrs))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			for _, addr := range addrs {
 | 
								for _, addr := range addrs {
 | 
				
			||||||
 | 
									ep := endpoint.NewEndpointWithTTL(dns, suitableType(addr), ttl)
 | 
				
			||||||
				log.Debugf("adding endpoint %s target %s", ep, addr)
 | 
									log.Debugf("adding endpoint %s target %s", ep, addr)
 | 
				
			||||||
				key := endpoint.EndpointKey{
 | 
									key := endpoint.EndpointKey{
 | 
				
			||||||
					DNSName:    ep.DNSName,
 | 
										DNSName:    ep.DNSName,
 | 
				
			||||||
				RecordType: suitableType(addr),
 | 
										RecordType: ep.RecordType,
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				if _, ok := endpoints[key]; !ok {
 | 
									if _, ok := endpoints[key]; !ok {
 | 
				
			||||||
					epCopy := *ep
 | 
										epCopy := *ep
 | 
				
			||||||
@ -163,6 +162,7 @@ func (ns *nodeSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, erro
 | 
				
			|||||||
				endpoints[key].Targets = append(endpoints[key].Targets, addr)
 | 
									endpoints[key].Targets = append(endpoints[key].Targets, addr)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	endpointsSlice := []*endpoint.Endpoint{}
 | 
						endpointsSlice := []*endpoint.Endpoint{}
 | 
				
			||||||
	for _, ep := range endpoints {
 | 
						for _, ep := range endpoints {
 | 
				
			||||||
@ -172,10 +172,10 @@ func (ns *nodeSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, erro
 | 
				
			|||||||
	return endpointsSlice, nil
 | 
						return endpointsSlice, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (ns *nodeSource) AddEventHandler(ctx context.Context, handler func()) {
 | 
					func (ns *nodeSource) AddEventHandler(_ context.Context, _ func()) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// nodeAddress returns node's externalIP and if that's not found, node's internalIP
 | 
					// nodeAddress returns the node's externalIP and if that's not found, the node's internalIP
 | 
				
			||||||
// basically what k8s.io/kubernetes/pkg/util/node.GetPreferredNodeAddress does
 | 
					// basically what k8s.io/kubernetes/pkg/util/node.GetPreferredNodeAddress does
 | 
				
			||||||
func (ns *nodeSource) nodeAddresses(node *v1.Node) ([]string, error) {
 | 
					func (ns *nodeSource) nodeAddresses(node *v1.Node) ([]string, error) {
 | 
				
			||||||
	addresses := map[v1.NodeAddressType][]string{
 | 
						addresses := map[v1.NodeAddressType][]string{
 | 
				
			||||||
@ -223,7 +223,7 @@ func (ns *nodeSource) filterByAnnotations(nodes []*v1.Node) ([]*v1.Node, error)
 | 
				
			|||||||
	var filteredList []*v1.Node
 | 
						var filteredList []*v1.Node
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, node := range nodes {
 | 
						for _, node := range nodes {
 | 
				
			||||||
		// include node if its annotations match the selector
 | 
							// include a node if its annotations match the selector
 | 
				
			||||||
		if selector.Matches(labels.Set(node.Annotations)) {
 | 
							if selector.Matches(labels.Set(node.Annotations)) {
 | 
				
			||||||
			filteredList = append(filteredList, node)
 | 
								filteredList = append(filteredList, node)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										322
									
								
								source/node_fqdn_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										322
									
								
								source/node_fqdn_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,322 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2025 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"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/require"
 | 
				
			||||||
 | 
						v1 "k8s.io/api/core/v1"
 | 
				
			||||||
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/labels"
 | 
				
			||||||
 | 
						"k8s.io/client-go/kubernetes/fake"
 | 
				
			||||||
 | 
						"sigs.k8s.io/external-dns/endpoint"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestNodeSourceNewNodeSourceWithFqdn(t *testing.T) {
 | 
				
			||||||
 | 
						for _, tt := range []struct {
 | 
				
			||||||
 | 
							title            string
 | 
				
			||||||
 | 
							annotationFilter string
 | 
				
			||||||
 | 
							fqdnTemplate     string
 | 
				
			||||||
 | 
							expectError      bool
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								title:        "invalid template",
 | 
				
			||||||
 | 
								expectError:  true,
 | 
				
			||||||
 | 
								fqdnTemplate: "{{.Name",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								title:       "valid empty template",
 | 
				
			||||||
 | 
								expectError: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								title:        "valid template",
 | 
				
			||||||
 | 
								expectError:  false,
 | 
				
			||||||
 | 
								fqdnTemplate: "{{.Name}}-{{.Namespace}}.ext-dns.test.com",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								title:        "complex template",
 | 
				
			||||||
 | 
								expectError:  false,
 | 
				
			||||||
 | 
								fqdnTemplate: "{{range .Status.Addresses}}{{if and (eq .Type \"ExternalIP\") (isIPv4 .Address)}}{{.Address | replace \".\" \"-\"}}{{break}}{{end}}{{end}}.ext-dns.test.com",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						} {
 | 
				
			||||||
 | 
							t.Run(tt.title, func(t *testing.T) {
 | 
				
			||||||
 | 
								_, err := NewNodeSource(
 | 
				
			||||||
 | 
									t.Context(),
 | 
				
			||||||
 | 
									fake.NewClientset(),
 | 
				
			||||||
 | 
									tt.annotationFilter,
 | 
				
			||||||
 | 
									tt.fqdnTemplate,
 | 
				
			||||||
 | 
									labels.Everything(),
 | 
				
			||||||
 | 
									true,
 | 
				
			||||||
 | 
									true,
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
								if tt.expectError {
 | 
				
			||||||
 | 
									assert.Error(t, err)
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									assert.NoError(t, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestNodeSourceFqdnTemplatingExamples(t *testing.T) {
 | 
				
			||||||
 | 
						for _, tt := range []struct {
 | 
				
			||||||
 | 
							title        string
 | 
				
			||||||
 | 
							nodes        []*v1.Node
 | 
				
			||||||
 | 
							fqdnTemplate string
 | 
				
			||||||
 | 
							expected     []*endpoint.Endpoint
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								title: "templating expansion with multiple domains",
 | 
				
			||||||
 | 
								nodes: []*v1.Node{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Name: "ip-10-1-176-5.internal",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Status: v1.NodeStatus{
 | 
				
			||||||
 | 
											Addresses: []v1.NodeAddress{
 | 
				
			||||||
 | 
												{Type: v1.NodeInternalIP, Address: "10.1.176.1"},
 | 
				
			||||||
 | 
												{Type: v1.NodeInternalIP, Address: "fc00:f853:ccd:e793::1"},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								fqdnTemplate: "{{.Name}}.domainA.com,{{.Name}}.domainB.com",
 | 
				
			||||||
 | 
								expected: []*endpoint.Endpoint{
 | 
				
			||||||
 | 
									{DNSName: "ip-10-1-176-5.internal.domainA.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.1.176.1"}},
 | 
				
			||||||
 | 
									{DNSName: "ip-10-1-176-5.internal.domainA.com", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"fc00:f853:ccd:e793::1"}},
 | 
				
			||||||
 | 
									{DNSName: "ip-10-1-176-5.internal.domainB.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.1.176.1"}},
 | 
				
			||||||
 | 
									{DNSName: "ip-10-1-176-5.internal.domainB.com", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"fc00:f853:ccd:e793::1"}},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								title: "templating contains namespace when node namespace is not a valid variable",
 | 
				
			||||||
 | 
								nodes: []*v1.Node{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Name: "node-name",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Status: v1.NodeStatus{
 | 
				
			||||||
 | 
											Addresses: []v1.NodeAddress{
 | 
				
			||||||
 | 
												{Type: v1.NodeInternalIP, Address: "10.1.176.1"},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								fqdnTemplate: "{{.Name}}.domainA.com,{{ .Name }}.{{ .Namespace }}.example.tld",
 | 
				
			||||||
 | 
								expected: []*endpoint.Endpoint{
 | 
				
			||||||
 | 
									{DNSName: "node-name.domainA.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.1.176.1"}},
 | 
				
			||||||
 | 
									{DNSName: "node-name..example.tld", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.1.176.1"}},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								title: "templating with external IP and range of addresses",
 | 
				
			||||||
 | 
								nodes: []*v1.Node{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Name: "ip-10-1-176-1",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Status: v1.NodeStatus{
 | 
				
			||||||
 | 
											Addresses: []v1.NodeAddress{
 | 
				
			||||||
 | 
												{Type: v1.NodeExternalIP, Address: "243.186.136.160"},
 | 
				
			||||||
 | 
												{Type: v1.NodeInternalIP, Address: "fc00:f853:ccd:e793::1"},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								fqdnTemplate: "{{ range .Status.Addresses }}{{if and (eq .Type \"ExternalIP\") (isIPv4 .Address)}}ip-{{ .Address | replace \".\" \"-\" }}{{ break }}{{ end }}{{ end }}.example.com",
 | 
				
			||||||
 | 
								expected: []*endpoint.Endpoint{
 | 
				
			||||||
 | 
									{DNSName: "ip-243-186-136-160.example.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"243.186.136.160"}},
 | 
				
			||||||
 | 
									{DNSName: "ip-243-186-136-160.example.com", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"fc00:f853:ccd:e793::1"}},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								title: "templating with name definition and ipv4 check",
 | 
				
			||||||
 | 
								nodes: []*v1.Node{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Name: "node-name-ip",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Status: v1.NodeStatus{
 | 
				
			||||||
 | 
											Addresses: []v1.NodeAddress{
 | 
				
			||||||
 | 
												{Type: v1.NodeExternalIP, Address: "243.186.136.160"},
 | 
				
			||||||
 | 
												{Type: v1.NodeInternalIP, Address: "fc00:f853:ccd:e793::1"},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								fqdnTemplate: "{{ $name := .Name }}{{ range .Status.Addresses }}{{if (isIPv4 .Address)}}{{ $name }}.ipv4{{ break }}{{ end }}{{ end }}.example.com",
 | 
				
			||||||
 | 
								expected: []*endpoint.Endpoint{
 | 
				
			||||||
 | 
									{DNSName: "node-name-ip.ipv4.example.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"243.186.136.160"}},
 | 
				
			||||||
 | 
									{DNSName: "node-name-ip.ipv4.example.com", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"fc00:f853:ccd:e793::1"}},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								title: "templating with hostname annotation",
 | 
				
			||||||
 | 
								nodes: []*v1.Node{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Name: "ip-10-1-176-1",
 | 
				
			||||||
 | 
											Annotations: map[string]string{
 | 
				
			||||||
 | 
												"external-dns.alpha.kubernetes.io/hostname": "ip-10-1-176-1.internal.domain.com",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Status: v1.NodeStatus{
 | 
				
			||||||
 | 
											Addresses: []v1.NodeAddress{
 | 
				
			||||||
 | 
												{Type: v1.NodeExternalIP, Address: "243.186.136.160"},
 | 
				
			||||||
 | 
												{Type: v1.NodeInternalIP, Address: "fc00:f853:ccd:e793::1"},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								fqdnTemplate: "{{.Name}}.example.com",
 | 
				
			||||||
 | 
								expected: []*endpoint.Endpoint{
 | 
				
			||||||
 | 
									{DNSName: "ip-10-1-176-1.example.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"243.186.136.160"}},
 | 
				
			||||||
 | 
									{DNSName: "ip-10-1-176-1.example.com", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"fc00:f853:ccd:e793::1"}},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								title: "templating when target annotation and no external IP",
 | 
				
			||||||
 | 
								nodes: []*v1.Node{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Name:   "node-name",
 | 
				
			||||||
 | 
											Labels: nil,
 | 
				
			||||||
 | 
											Annotations: map[string]string{
 | 
				
			||||||
 | 
												"external-dns.alpha.kubernetes.io/target": "203.2.45.22",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Status: v1.NodeStatus{
 | 
				
			||||||
 | 
											Addresses: []v1.NodeAddress{
 | 
				
			||||||
 | 
												{Type: v1.NodeExternalIP, Address: "243.186.136.160"},
 | 
				
			||||||
 | 
												{Type: v1.NodeInternalIP, Address: "fc00:f853:ccd:e793::1"},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								fqdnTemplate: "{{.Name}}.example.com",
 | 
				
			||||||
 | 
								expected: []*endpoint.Endpoint{
 | 
				
			||||||
 | 
									{DNSName: "node-name.example.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"203.2.45.22"}},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								title: "templating with simple annotation expansion",
 | 
				
			||||||
 | 
								nodes: []*v1.Node{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Name: "node-name",
 | 
				
			||||||
 | 
											Annotations: map[string]string{
 | 
				
			||||||
 | 
												"workload": "cluster-resources",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Status: v1.NodeStatus{
 | 
				
			||||||
 | 
											Addresses: []v1.NodeAddress{
 | 
				
			||||||
 | 
												{Type: v1.NodeExternalIP, Address: "243.186.136.160"},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								fqdnTemplate: "{{ .Name }}.{{ .Annotations.workload }}.domain.tld",
 | 
				
			||||||
 | 
								expected: []*endpoint.Endpoint{
 | 
				
			||||||
 | 
									{DNSName: "node-name.cluster-resources.domain.tld", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"243.186.136.160"}},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								title: "templating with complex labels expansion",
 | 
				
			||||||
 | 
								nodes: []*v1.Node{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Name: "node-name",
 | 
				
			||||||
 | 
											Labels: map[string]string{
 | 
				
			||||||
 | 
												"topology.kubernetes.io/region": "eu-west-1",
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Annotations: nil,
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Spec: v1.NodeSpec{
 | 
				
			||||||
 | 
											Unschedulable: false,
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Status: v1.NodeStatus{
 | 
				
			||||||
 | 
											Addresses: []v1.NodeAddress{
 | 
				
			||||||
 | 
												{Type: v1.NodeExternalIP, Address: "243.186.136.160"},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								fqdnTemplate: "{{ .Name }}.{{ index .ObjectMeta.Labels \"topology.kubernetes.io/region\" }}.domain.tld",
 | 
				
			||||||
 | 
								expected: []*endpoint.Endpoint{
 | 
				
			||||||
 | 
									{DNSName: "node-name.eu-west-1.domain.tld", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"243.186.136.160"}},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								title: "templating with shared all domain",
 | 
				
			||||||
 | 
								nodes: []*v1.Node{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Name: "node-name-1",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Status: v1.NodeStatus{
 | 
				
			||||||
 | 
											Addresses: []v1.NodeAddress{
 | 
				
			||||||
 | 
												{Type: v1.NodeExternalIP, Address: "243.186.136.160"},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Name: "node-name-2",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Status: v1.NodeStatus{
 | 
				
			||||||
 | 
											Addresses: []v1.NodeAddress{
 | 
				
			||||||
 | 
												{Type: v1.NodeExternalIP, Address: "243.186.136.178"},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								fqdnTemplate: "{{ .Name }}.domain.tld,all.example.com",
 | 
				
			||||||
 | 
								expected: []*endpoint.Endpoint{
 | 
				
			||||||
 | 
									{DNSName: "all.example.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"243.186.136.160", "243.186.136.178"}},
 | 
				
			||||||
 | 
									{DNSName: "node-name-1.domain.tld", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"243.186.136.160"}},
 | 
				
			||||||
 | 
									{DNSName: "node-name-2.domain.tld", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"243.186.136.178"}},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						} {
 | 
				
			||||||
 | 
							t.Run(tt.title, func(t *testing.T) {
 | 
				
			||||||
 | 
								kubeClient := fake.NewClientset()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for _, node := range tt.nodes {
 | 
				
			||||||
 | 
									_, err := kubeClient.CoreV1().Nodes().Create(t.Context(), node, metav1.CreateOptions{})
 | 
				
			||||||
 | 
									require.NoError(t, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								src, err := NewNodeSource(
 | 
				
			||||||
 | 
									t.Context(),
 | 
				
			||||||
 | 
									kubeClient,
 | 
				
			||||||
 | 
									"",
 | 
				
			||||||
 | 
									tt.fqdnTemplate,
 | 
				
			||||||
 | 
									labels.Everything(),
 | 
				
			||||||
 | 
									true,
 | 
				
			||||||
 | 
									true,
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
								require.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								endpoints, err := src.Endpoints(t.Context())
 | 
				
			||||||
 | 
								require.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								validateEndpoints(t, endpoints, tt.expected)
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -18,10 +18,15 @@ package source
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"maps"
 | 
				
			||||||
 | 
						"math/rand"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log "github.com/sirupsen/logrus"
 | 
						log "github.com/sirupsen/logrus"
 | 
				
			||||||
	"github.com/sirupsen/logrus/hooks/test"
 | 
						"github.com/sirupsen/logrus/hooks/test"
 | 
				
			||||||
 | 
						"k8s.io/client-go/kubernetes"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"sigs.k8s.io/external-dns/internal/testutils"
 | 
						"sigs.k8s.io/external-dns/internal/testutils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -84,7 +89,7 @@ func testNodeSourceNewNodeSource(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			_, err := NewNodeSource(
 | 
								_, err := NewNodeSource(
 | 
				
			||||||
				context.TODO(),
 | 
									context.TODO(),
 | 
				
			||||||
				fake.NewSimpleClientset(),
 | 
									fake.NewClientset(),
 | 
				
			||||||
				ti.annotationFilter,
 | 
									ti.annotationFilter,
 | 
				
			||||||
				ti.fqdnTemplate,
 | 
									ti.fqdnTemplate,
 | 
				
			||||||
				labels.Everything(),
 | 
									labels.Everything(),
 | 
				
			||||||
@ -407,7 +412,7 @@ func testNodeSourceEndpoints(t *testing.T) {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Create a Kubernetes testing client
 | 
								// Create a Kubernetes testing client
 | 
				
			||||||
			kubernetes := fake.NewSimpleClientset()
 | 
								kubeClient := fake.NewClientset()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			node := &v1.Node{
 | 
								node := &v1.Node{
 | 
				
			||||||
				ObjectMeta: metav1.ObjectMeta{
 | 
									ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
@ -423,13 +428,13 @@ func testNodeSourceEndpoints(t *testing.T) {
 | 
				
			|||||||
				},
 | 
									},
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			_, err := kubernetes.CoreV1().Nodes().Create(context.Background(), node, metav1.CreateOptions{})
 | 
								_, err := kubeClient.CoreV1().Nodes().Create(context.Background(), node, metav1.CreateOptions{})
 | 
				
			||||||
			require.NoError(t, err)
 | 
								require.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Create our object under test and get the endpoints.
 | 
								// Create our object under test and get the endpoints.
 | 
				
			||||||
			client, err := NewNodeSource(
 | 
								client, err := NewNodeSource(
 | 
				
			||||||
				context.TODO(),
 | 
									context.TODO(),
 | 
				
			||||||
				kubernetes,
 | 
									kubeClient,
 | 
				
			||||||
				tc.annotationFilter,
 | 
									tc.annotationFilter,
 | 
				
			||||||
				tc.fqdnTemplate,
 | 
									tc.fqdnTemplate,
 | 
				
			||||||
				labelSelector,
 | 
									labelSelector,
 | 
				
			||||||
@ -519,7 +524,7 @@ func testNodeEndpointsWithIPv6(t *testing.T) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Create a Kubernetes testing client
 | 
							// Create a Kubernetes testing client
 | 
				
			||||||
		kubernetes := fake.NewSimpleClientset()
 | 
							kubeClient := fake.NewClientset()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		node := &v1.Node{
 | 
							node := &v1.Node{
 | 
				
			||||||
			ObjectMeta: metav1.ObjectMeta{
 | 
								ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
@ -535,7 +540,7 @@ func testNodeEndpointsWithIPv6(t *testing.T) {
 | 
				
			|||||||
			},
 | 
								},
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		_, err := kubernetes.CoreV1().Nodes().Create(context.Background(), node, metav1.CreateOptions{})
 | 
							_, err := kubeClient.CoreV1().Nodes().Create(t.Context(), node, metav1.CreateOptions{})
 | 
				
			||||||
		require.NoError(t, err)
 | 
							require.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		var hook *test.Hook
 | 
							var hook *test.Hook
 | 
				
			||||||
@ -545,8 +550,8 @@ func testNodeEndpointsWithIPv6(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// Create our object under test and get the endpoints.
 | 
							// Create our object under test and get the endpoints.
 | 
				
			||||||
		client, err := NewNodeSource(
 | 
							client, err := NewNodeSource(
 | 
				
			||||||
			context.TODO(),
 | 
								t.Context(),
 | 
				
			||||||
			kubernetes,
 | 
								kubeClient,
 | 
				
			||||||
			tc.annotationFilter,
 | 
								tc.annotationFilter,
 | 
				
			||||||
			tc.fqdnTemplate,
 | 
								tc.fqdnTemplate,
 | 
				
			||||||
			labelSelector,
 | 
								labelSelector,
 | 
				
			||||||
@ -555,7 +560,7 @@ func testNodeEndpointsWithIPv6(t *testing.T) {
 | 
				
			|||||||
		)
 | 
							)
 | 
				
			||||||
		require.NoError(t, err)
 | 
							require.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		endpoints, err := client.Endpoints(context.Background())
 | 
							endpoints, err := client.Endpoints(t.Context())
 | 
				
			||||||
		if tc.expectError {
 | 
							if tc.expectError {
 | 
				
			||||||
			require.Error(t, err)
 | 
								require.Error(t, err)
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
@ -570,3 +575,101 @@ func testNodeEndpointsWithIPv6(t *testing.T) {
 | 
				
			|||||||
		validateEndpoints(t, endpoints, tc.expected)
 | 
							validateEndpoints(t, endpoints, tc.expected)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestResourceLabelIsSetForEachNodeEndpoint(t *testing.T) {
 | 
				
			||||||
 | 
						kubeClient := fake.NewClientset()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nodes := helperNodeBuilder().
 | 
				
			||||||
 | 
							withNode(nil).
 | 
				
			||||||
 | 
							withNode(nil).
 | 
				
			||||||
 | 
							withNode(nil).
 | 
				
			||||||
 | 
							withNode(nil).
 | 
				
			||||||
 | 
							build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, node := range nodes.Items {
 | 
				
			||||||
 | 
							_, err := kubeClient.CoreV1().Nodes().Create(t.Context(), &node, metav1.CreateOptions{})
 | 
				
			||||||
 | 
							require.NoError(t, err, "Failed to create node %s", node.Name)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client, err := NewNodeSource(
 | 
				
			||||||
 | 
							t.Context(),
 | 
				
			||||||
 | 
							kubeClient,
 | 
				
			||||||
 | 
							"",
 | 
				
			||||||
 | 
							"",
 | 
				
			||||||
 | 
							labels.Everything(),
 | 
				
			||||||
 | 
							false,
 | 
				
			||||||
 | 
							true,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						require.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						got, err := client.Endpoints(t.Context())
 | 
				
			||||||
 | 
						require.NoError(t, err)
 | 
				
			||||||
 | 
						for _, ep := range got {
 | 
				
			||||||
 | 
							// TODO: node source should always set the resource label key. currently not supported by the node source.
 | 
				
			||||||
 | 
							assert.Empty(t, ep.Labels, "Labels should not be empty for endpoint %s", ep.DNSName)
 | 
				
			||||||
 | 
							assert.NotContains(t, ep.Labels, endpoint.ResourceLabelKey)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type nodeListBuilder struct {
 | 
				
			||||||
 | 
						nodes []v1.Node
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func helperNodeBuilder() *nodeListBuilder {
 | 
				
			||||||
 | 
						return &nodeListBuilder{nodes: []v1.Node{}}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (b *nodeListBuilder) withNode(labels map[string]string) *nodeListBuilder {
 | 
				
			||||||
 | 
						idx := len(b.nodes) + 1
 | 
				
			||||||
 | 
						nodeName := fmt.Sprintf("ip-10-1-176-%d.internal", idx)
 | 
				
			||||||
 | 
						b.nodes = append(b.nodes, v1.Node{
 | 
				
			||||||
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
								Name: nodeName,
 | 
				
			||||||
 | 
								Labels: func() map[string]string {
 | 
				
			||||||
 | 
									base := map[string]string{
 | 
				
			||||||
 | 
										"test-label":                    "test-value",
 | 
				
			||||||
 | 
										"name":                          nodeName,
 | 
				
			||||||
 | 
										"topology.kubernetes.io/region": "eu-west-1",
 | 
				
			||||||
 | 
										"node.kubernetes.io/lifecycle":  "spot",
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									maps.Copy(base, labels)
 | 
				
			||||||
 | 
									return base
 | 
				
			||||||
 | 
								}(),
 | 
				
			||||||
 | 
								Annotations: map[string]string{
 | 
				
			||||||
 | 
									"volumes.kubernetes.io/controller-managed-attach-detach": "true",
 | 
				
			||||||
 | 
									"alpha.kubernetes.io/provided-node-ip":                   fmt.Sprintf("10.1.176.%d", idx),
 | 
				
			||||||
 | 
									"external-dns.alpha.kubernetes.io/hostname":              fmt.Sprintf("node-%d.example.com", idx),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Spec: v1.NodeSpec{
 | 
				
			||||||
 | 
								Unschedulable: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Status: v1.NodeStatus{
 | 
				
			||||||
 | 
								Addresses: []v1.NodeAddress{
 | 
				
			||||||
 | 
									{Type: v1.NodeInternalIP, Address: fmt.Sprintf("10.1.176.%d", idx)},
 | 
				
			||||||
 | 
									{Type: v1.NodeInternalIP, Address: fmt.Sprintf("fc00:f853:ccd:e793::%d", idx)},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return b
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (b *nodeListBuilder) build() v1.NodeList {
 | 
				
			||||||
 | 
						if len(b.nodes) > 1 {
 | 
				
			||||||
 | 
							// Shuffle the result to ensure randomness in the order.
 | 
				
			||||||
 | 
							rand.New(rand.NewSource(time.Now().UnixNano()))
 | 
				
			||||||
 | 
							rand.Shuffle(len(b.nodes), func(i, j int) {
 | 
				
			||||||
 | 
								b.nodes[i], b.nodes[j] = b.nodes[j], b.nodes[i]
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return v1.NodeList{Items: b.nodes}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (b *nodeListBuilder) apply(t *testing.T, kubeClient kubernetes.Interface) v1.NodeList {
 | 
				
			||||||
 | 
						for _, node := range b.nodes {
 | 
				
			||||||
 | 
							_, err := kubeClient.CoreV1().Nodes().Create(t.Context(), &node, metav1.CreateOptions{})
 | 
				
			||||||
 | 
							require.NoError(t, err, "Failed to create node %s", node.Name)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return v1.NodeList{Items: b.nodes}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user