• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubernetes-sigs / external-dns / 14954656447

11 May 2025 10:00AM UTC coverage: 72.755% (+0.5%) from 72.283%
14954656447

Pull #5353

github

ivankatliarchuk
chore(docs): update aws permissions

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>
Pull Request #5353: WIP chore(docs): update aws role requirements with conditions

14842 of 20400 relevant lines covered (72.75%)

692.31 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.03
/source/contour_httpproxy.go
1
/*
2
Copyright 2020 The Kubernetes Authors.
3

4
Licensed under the Apache License, Version 2.0 (the "License");
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
7

8
    http://www.apache.org/licenses/LICENSE-2.0
9

10
Unless required by applicable law or agreed to in writing, software
11
distributed under the License is distributed on an "AS IS" BASIS,
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
See the License for the specific language governing permissions and
14
limitations under the License.
15
*/
16

17
package source
18

19
import (
20
        "context"
21
        "errors"
22
        "fmt"
23
        "sort"
24
        "text/template"
25

26
        projectcontour "github.com/projectcontour/contour/apis/projectcontour/v1"
27
        log "github.com/sirupsen/logrus"
28
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29
        "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
30
        "k8s.io/apimachinery/pkg/labels"
31
        "k8s.io/client-go/dynamic"
32
        "k8s.io/client-go/dynamic/dynamicinformer"
33
        "k8s.io/client-go/informers"
34
        "k8s.io/client-go/tools/cache"
35

36
        "sigs.k8s.io/external-dns/endpoint"
37
)
38

39
// HTTPProxySource is an implementation of Source for ProjectContour HTTPProxy objects.
40
// The HTTPProxy implementation uses the spec.virtualHost.fqdn value for the hostname.
41
// Use targetAnnotationKey to explicitly set Endpoint.
42
type httpProxySource struct {
43
        dynamicKubeClient        dynamic.Interface
44
        namespace                string
45
        annotationFilter         string
46
        fqdnTemplate             *template.Template
47
        combineFQDNAnnotation    bool
48
        ignoreHostnameAnnotation bool
49
        httpProxyInformer        informers.GenericInformer
50
        unstructuredConverter    *UnstructuredConverter
51
}
52

53
// NewContourHTTPProxySource creates a new contourHTTPProxySource with the given config.
54
func NewContourHTTPProxySource(
55
        ctx context.Context,
56
        dynamicKubeClient dynamic.Interface,
57
        namespace string,
58
        annotationFilter string,
59
        fqdnTemplate string,
60
        combineFqdnAnnotation bool,
61
        ignoreHostnameAnnotation bool,
62
) (Source, error) {
63
        tmpl, err := parseTemplate(fqdnTemplate)
64
        if err != nil {
37✔
65
                return nil, err
37✔
66
        }
38✔
67

1✔
68
        // Use shared informer to listen for add/update/delete of HTTPProxys in the specified namespace.
1✔
69
        // Set resync period to 0, to prevent processing when nothing has changed.
70
        informerFactory := dynamicinformer.NewFilteredDynamicSharedInformerFactory(dynamicKubeClient, 0, namespace, nil)
71
        httpProxyInformer := informerFactory.ForResource(projectcontour.HTTPProxyGVR)
72

36✔
73
        // Add default resource event handlers to properly initialize informer.
36✔
74
        httpProxyInformer.Informer().AddEventHandler(
36✔
75
                cache.ResourceEventHandlerFuncs{
36✔
76
                        AddFunc: func(obj interface{}) {
36✔
77
                        },
36✔
78
                },
69✔
79
        )
33✔
80

81
        informerFactory.Start(ctx.Done())
82

83
        // wait for the local cache to be populated.
36✔
84
        if err := waitForDynamicCacheSync(context.Background(), informerFactory); err != nil {
36✔
85
                return nil, err
36✔
86
        }
36✔
87

×
88
        uc, err := NewUnstructuredConverter()
×
89
        if err != nil {
90
                return nil, fmt.Errorf("failed to setup Unstructured Converter: %w", err)
36✔
91
        }
36✔
92

×
93
        return &httpProxySource{
×
94
                dynamicKubeClient:        dynamicKubeClient,
95
                namespace:                namespace,
36✔
96
                annotationFilter:         annotationFilter,
36✔
97
                fqdnTemplate:             tmpl,
36✔
98
                combineFQDNAnnotation:    combineFqdnAnnotation,
36✔
99
                ignoreHostnameAnnotation: ignoreHostnameAnnotation,
36✔
100
                httpProxyInformer:        httpProxyInformer,
36✔
101
                unstructuredConverter:    uc,
36✔
102
        }, nil
36✔
103
}
36✔
104

36✔
105
// Endpoints returns endpoint objects for each host-target combination that should be processed.
106
// Retrieves all HTTPProxy resources in the source's namespace(s).
107
func (sc *httpProxySource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) {
108
        hps, err := sc.httpProxyInformer.Lister().ByNamespace(sc.namespace).List(labels.Everything())
109
        if err != nil {
24✔
110
                return nil, err
24✔
111
        }
24✔
112

×
113
        // Convert to []*projectcontour.HTTPProxy
×
114
        var httpProxies []*projectcontour.HTTPProxy
115
        for _, hp := range hps {
116
                unstructuredHP, ok := hp.(*unstructured.Unstructured)
24✔
117
                if !ok {
57✔
118
                        return nil, errors.New("could not convert")
33✔
119
                }
33✔
120

×
121
                hpConverted := &projectcontour.HTTPProxy{}
×
122
                err := sc.unstructuredConverter.scheme.Convert(unstructuredHP, hpConverted, nil)
123
                if err != nil {
33✔
124
                        return nil, fmt.Errorf("failed to convert to HTTPProxy: %w", err)
33✔
125
                }
33✔
126
                httpProxies = append(httpProxies, hpConverted)
×
127
        }
×
128

33✔
129
        httpProxies, err = sc.filterByAnnotations(httpProxies)
130
        if err != nil {
131
                return nil, fmt.Errorf("failed to filter HTTPProxies: %w", err)
24✔
132
        }
25✔
133

1✔
134
        endpoints := []*endpoint.Endpoint{}
1✔
135

136
        for _, hp := range httpProxies {
23✔
137
                // Check controller annotation to see if we are responsible.
23✔
138
                controller, ok := hp.Annotations[controllerAnnotationKey]
53✔
139
                if ok && controller != controllerAnnotationValue {
30✔
140
                        log.Debugf("Skipping HTTPProxy %s/%s because controller value does not match, found: %s, required: %s",
30✔
141
                                hp.Namespace, hp.Name, controller, controllerAnnotationValue)
32✔
142
                        continue
2✔
143
                }
2✔
144

2✔
145
                hpEndpoints, err := sc.endpointsFromHTTPProxy(hp)
146
                if err != nil {
147
                        return nil, fmt.Errorf("failed to get endpoints from HTTPProxy: %w", err)
28✔
148
                }
28✔
149

×
150
                // apply template if fqdn is missing on HTTPProxy
×
151
                if (sc.combineFQDNAnnotation || len(hpEndpoints) == 0) && sc.fqdnTemplate != nil {
152
                        tmplEndpoints, err := sc.endpointsFromTemplate(hp)
153
                        if err != nil {
37✔
154
                                return nil, fmt.Errorf("failed to get endpoints from template: %w", err)
9✔
155
                        }
9✔
156

×
157
                        if sc.combineFQDNAnnotation {
×
158
                                hpEndpoints = append(hpEndpoints, tmplEndpoints...)
159
                        } else {
11✔
160
                                hpEndpoints = tmplEndpoints
2✔
161
                        }
9✔
162
                }
7✔
163

7✔
164
                if len(hpEndpoints) == 0 {
165
                        log.Debugf("No endpoints could be generated from HTTPProxy %s/%s", hp.Namespace, hp.Name)
166
                        continue
30✔
167
                }
2✔
168

2✔
169
                log.Debugf("Endpoints generated from HTTPProxy: %s/%s: %v", hp.Namespace, hp.Name, hpEndpoints)
170
                endpoints = append(endpoints, hpEndpoints...)
171
        }
26✔
172

26✔
173
        for _, ep := range endpoints {
174
                sort.Sort(ep.Targets)
175
        }
65✔
176

42✔
177
        return endpoints, nil
42✔
178
}
179

23✔
180
func (sc *httpProxySource) endpointsFromTemplate(httpProxy *projectcontour.HTTPProxy) ([]*endpoint.Endpoint, error) {
181
        hostnames, err := execTemplate(sc.fqdnTemplate, httpProxy)
182
        if err != nil {
9✔
183
                return nil, err
9✔
184
        }
9✔
185

×
186
        resource := fmt.Sprintf("HTTPProxy/%s/%s", httpProxy.Namespace, httpProxy.Name)
×
187

188
        ttl := getTTLFromAnnotations(httpProxy.Annotations, resource)
9✔
189

9✔
190
        targets := getTargetsFromTargetAnnotation(httpProxy.Annotations)
9✔
191
        if len(targets) == 0 {
9✔
192
                for _, lb := range httpProxy.Status.LoadBalancer.Ingress {
9✔
193
                        if lb.IP != "" {
14✔
194
                                targets = append(targets, lb.IP)
9✔
195
                        }
7✔
196
                        if lb.Hostname != "" {
3✔
197
                                targets = append(targets, lb.Hostname)
3✔
198
                        }
5✔
199
                }
1✔
200
        }
1✔
201

202
        providerSpecific, setIdentifier := getProviderSpecificAnnotations(httpProxy.Annotations)
203

204
        var endpoints []*endpoint.Endpoint
9✔
205
        for _, hostname := range hostnames {
9✔
206
                endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific, setIdentifier, resource)...)
9✔
207
        }
21✔
208
        return endpoints, nil
12✔
209
}
12✔
210

9✔
211
// filterByAnnotations filters a list of configs by a given annotation selector.
212
func (sc *httpProxySource) filterByAnnotations(httpProxies []*projectcontour.HTTPProxy) ([]*projectcontour.HTTPProxy, error) {
213
        labelSelector, err := metav1.ParseToLabelSelector(sc.annotationFilter)
214
        if err != nil {
24✔
215
                return nil, err
24✔
216
        }
25✔
217
        selector, err := metav1.LabelSelectorAsSelector(labelSelector)
1✔
218
        if err != nil {
1✔
219
                return nil, err
23✔
220
        }
23✔
221

×
222
        // empty filter returns original list
×
223
        if selector.Empty() {
224
                return httpProxies, nil
225
        }
42✔
226

19✔
227
        filteredList := []*projectcontour.HTTPProxy{}
19✔
228

229
        for _, httpProxy := range httpProxies {
4✔
230
                // convert the HTTPProxy's annotations to an equivalent label selector
4✔
231
                annotations := labels.Set(httpProxy.Annotations)
8✔
232

4✔
233
                // include HTTPProxy if its annotations match the selector
6✔
234
                if selector.Matches(annotations) {
2✔
235
                        filteredList = append(filteredList, httpProxy)
2✔
236
                }
237
        }
238

4✔
239
        return filteredList, nil
240
}
241

242
// endpointsFromHTTPProxyConfig extracts the endpoints from a Contour HTTPProxy object
34✔
243
func (sc *httpProxySource) endpointsFromHTTPProxy(httpProxy *projectcontour.HTTPProxy) ([]*endpoint.Endpoint, error) {
34✔
244
        resource := fmt.Sprintf("HTTPProxy/%s/%s", httpProxy.Namespace, httpProxy.Name)
34✔
245

34✔
246
        ttl := getTTLFromAnnotations(httpProxy.Annotations, resource)
34✔
247

34✔
248
        targets := getTargetsFromTargetAnnotation(httpProxy.Annotations)
34✔
249

57✔
250
        if len(targets) == 0 {
52✔
251
                for _, lb := range httpProxy.Status.LoadBalancer.Ingress {
47✔
252
                        if lb.IP != "" {
18✔
253
                                targets = append(targets, lb.IP)
18✔
254
                        }
40✔
255
                        if lb.Hostname != "" {
11✔
256
                                targets = append(targets, lb.Hostname)
11✔
257
                        }
258
                }
259
        }
260

34✔
261
        providerSpecific, setIdentifier := getProviderSpecificAnnotations(httpProxy.Annotations)
34✔
262

34✔
263
        var endpoints []*endpoint.Endpoint
34✔
264

67✔
265
        if virtualHost := httpProxy.Spec.VirtualHost; virtualHost != nil {
57✔
266
                if fqdn := virtualHost.Fqdn; fqdn != "" {
24✔
267
                        endpoints = append(endpoints, endpointsForHostname(fqdn, targets, ttl, providerSpecific, setIdentifier, resource)...)
24✔
268
                }
269
        }
270

271
        // Skip endpoints if we do not want entries from annotations
66✔
272
        if !sc.ignoreHostnameAnnotation {
32✔
273
                hostnameList := getHostnamesFromAnnotations(httpProxy.Annotations)
36✔
274
                for _, hostname := range hostnameList {
4✔
275
                        endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific, setIdentifier, resource)...)
4✔
276
                }
277
        }
278

34✔
279
        return endpoints, nil
280
}
281

×
282
func (sc *httpProxySource) AddEventHandler(ctx context.Context, handler func()) {
×
283
        log.Debug("Adding event handler for httpproxy")
×
284

×
285
        // Right now there is no way to remove event handler from informer, see:
×
286
        // https://github.com/kubernetes/kubernetes/issues/79610
×
287
        sc.httpProxyInformer.Informer().AddEventHandler(eventHandlerFunc(handler))
×
288
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc