forked from projectcontour/contour
/
k8s.go
154 lines (135 loc) · 4.95 KB
/
k8s.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// Copyright © 2018 Heptio
// 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 contour contains the translation business logic that listens
// to Kubernetes ResourceEventHandler events and translates those into
// additions/deletions in caches connected to the Envoy xDS gRPC API server.
package contour
import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
ingressroutev1 "github.com/heptio/contour/apis/contour/v1beta1"
"github.com/heptio/contour/internal/dag"
"github.com/heptio/contour/internal/metrics"
"github.com/prometheus/client_golang/prometheus"
"github.com/sirupsen/logrus"
"k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const DEFAULT_INGRESS_CLASS = "contour"
// ResourceEventHandler implements cache.ResourceEventHandler, filters
// k8s watcher events towards a dag.Builder (which also implements the
// same interface) and calls through to the CacheHandler to notify it
// that the contents of the dag.Builder have changed.
type ResourceEventHandler struct {
// Contour's IngressClass.
// If not set, defaults to DEFAULT_INGRESS_CLASS.
IngressClass string
dag.Builder
Notifier
*metrics.Metrics
logrus.FieldLogger
}
// Notifier supplies a callback to be called when changes occur
// to a dag.Builder.
type Notifier interface {
// OnChange is called to notify the callee that the
// contents of the *dag.Builder have changed.
OnChange(*dag.Builder)
}
func (reh *ResourceEventHandler) OnAdd(obj interface{}) {
timer := prometheus.NewTimer(reh.ResourceEventHandlerSummary.With(prometheus.Labels{"op": "OnAdd"}))
defer timer.ObserveDuration()
if !reh.validIngressClass(obj) {
return
}
reh.WithField("op", "add").Debugf("%T", obj)
reh.Insert(obj)
reh.update()
}
func (reh *ResourceEventHandler) OnUpdate(oldObj, newObj interface{}) {
oldValid, newValid := reh.validIngressClass(oldObj), reh.validIngressClass(newObj)
switch {
case !oldValid && !newValid:
// the old object did not match the ingress class, nor does
// the new object, nothing to do
case oldValid && !newValid:
// if the old object was valid, and the replacement is not, then we need
// to remove the old object and _not_ insert the new object.
reh.OnDelete(oldObj)
default:
if cmp.Equal(oldObj, newObj,
cmpopts.IgnoreFields(ingressroutev1.IngressRoute{}, "Status"),
cmpopts.IgnoreFields(metav1.ObjectMeta{}, "ResourceVersion")) {
reh.WithField("op", "update").Debugf("%T skipping update, only status has changed", newObj)
return
}
timer := prometheus.NewTimer(reh.ResourceEventHandlerSummary.With(prometheus.Labels{"op": "OnUpdate"}))
defer timer.ObserveDuration()
reh.WithField("op", "update").Debugf("%T", newObj)
reh.Remove(oldObj)
reh.Insert(newObj)
reh.update()
}
}
func (reh *ResourceEventHandler) OnDelete(obj interface{}) {
timer := prometheus.NewTimer(reh.ResourceEventHandlerSummary.With(prometheus.Labels{"op": "OnDelete"}))
defer timer.ObserveDuration()
// no need to check ingress class here
reh.WithField("op", "delete").Debugf("%T", obj)
reh.Remove(obj)
reh.update()
}
func (reh *ResourceEventHandler) update() {
reh.OnChange(&reh.Builder)
}
// validIngressClass returns true iff:
//
// 1. obj is not of type *v1beta1.Ingress or ingressroutev1.IngressRoute.
// 2. obj has no ingress.class annotation.
// 2. obj's ingress.class annotation matches d.IngressClass.
func (reh *ResourceEventHandler) validIngressClass(obj interface{}) bool {
switch i := obj.(type) {
case *ingressroutev1.IngressRoute:
class, ok := getIngressClassAnnotation(i.Annotations)
return !ok || class == reh.ingressClass()
case *v1beta1.Ingress:
class, ok := getIngressClassAnnotation(i.Annotations)
return !ok || class == reh.ingressClass()
default:
return true
}
}
// ingressClass returns the IngressClass
// or DEFAULT_INGRESS_CLASS if not configured.
func (reh *ResourceEventHandler) ingressClass() string {
if reh.IngressClass != "" {
return reh.IngressClass
}
return DEFAULT_INGRESS_CLASS
}
// getIngressClassAnnotation checks for the acceptable ingress class annotations
// 1. contour.heptio.com/ingress.class
// 2. kubernetes.io/ingress.class
//
// it returns the first matching ingress annotation (in the above order) with test
func getIngressClassAnnotation(annotations map[string]string) (string, bool) {
class, ok := annotations["contour.heptio.com/ingress.class"]
if ok {
return class, true
}
class, ok = annotations["kubernetes.io/ingress.class"]
if ok {
return class, true
}
return "", false
}