forked from openshift/origin
-
Notifications
You must be signed in to change notification settings - Fork 1
/
labelcondition.go
123 lines (109 loc) · 4.15 KB
/
labelcondition.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
package namespaceconditions
import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apiserver/pkg/admission"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
corev1lister "k8s.io/client-go/listers/core/v1"
)
const runLevelLabel = "openshift.io/run-level"
var (
skipRunLevelZeroSelector labels.Selector
skipRunLevelOneSelector labels.Selector
)
func init() {
var err error
skipRunLevelZeroSelector, err = labels.Parse(runLevelLabel + " notin ( 0 )")
if err != nil {
panic(err)
}
skipRunLevelOneSelector, err = labels.Parse(runLevelLabel + " notin ( 0,1 )")
if err != nil {
panic(err)
}
}
// pluginHandlerWithNamespaceLabelConditions wraps an admission plugin in a conditional skip based on namespace labels
type pluginHandlerWithNamespaceLabelConditions struct {
admissionPlugin admission.Interface
namespaceClient corev1client.NamespacesGetter
namespaceLister corev1lister.NamespaceLister
namespaceSelector labels.Selector
}
var _ admission.ValidationInterface = &pluginHandlerWithNamespaceLabelConditions{}
var _ admission.MutationInterface = &pluginHandlerWithNamespaceLabelConditions{}
func (p pluginHandlerWithNamespaceLabelConditions) Handles(operation admission.Operation) bool {
return p.admissionPlugin.Handles(operation)
}
// Admit performs a mutating admission control check and emit metrics.
func (p pluginHandlerWithNamespaceLabelConditions) Admit(a admission.Attributes) error {
if !p.shouldRunAdmission(a) {
return nil
}
mutatingHandler, ok := p.admissionPlugin.(admission.MutationInterface)
if !ok {
return nil
}
return mutatingHandler.Admit(a)
}
// Validate performs a non-mutating admission control check and emits metrics.
func (p pluginHandlerWithNamespaceLabelConditions) Validate(a admission.Attributes) error {
if !p.shouldRunAdmission(a) {
return nil
}
validatingHandler, ok := p.admissionPlugin.(admission.ValidationInterface)
if !ok {
return nil
}
return validatingHandler.Validate(a)
}
// MatchNamespaceSelector decideds whether the request matches the
// namespaceSelctor of the webhook. Only when they match, the webhook is called.
func (p pluginHandlerWithNamespaceLabelConditions) shouldRunAdmission(attr admission.Attributes) bool {
namespaceName := attr.GetNamespace()
if len(namespaceName) == 0 && attr.GetResource().Resource != "namespaces" {
// cluster scoped resources always run admission
return true
}
namespaceLabels, err := p.getNamespaceLabels(attr)
if err != nil {
// default to running the hook so we don't leak namespace existence information
return true
}
// TODO: adding an LRU cache to cache the match decision
return p.namespaceSelector.Matches(labels.Set(namespaceLabels))
}
// getNamespaceLabels gets the labels of the namespace related to the attr.
func (p pluginHandlerWithNamespaceLabelConditions) getNamespaceLabels(attr admission.Attributes) (map[string]string, error) {
// If the request itself is creating or updating a namespace, then get the
// labels from attr.Object, because namespaceLister doesn't have the latest
// namespace yet.
//
// However, if the request is deleting a namespace, then get the label from
// the namespace in the namespaceLister, because a delete request is not
// going to change the object, and attr.Object will be a DeleteOptions
// rather than a namespace object.
if attr.GetResource().Resource == "namespaces" &&
len(attr.GetSubresource()) == 0 &&
(attr.GetOperation() == admission.Create || attr.GetOperation() == admission.Update) {
accessor, err := meta.Accessor(attr.GetObject())
if err != nil {
return nil, err
}
return accessor.GetLabels(), nil
}
namespaceName := attr.GetNamespace()
namespace, err := p.namespaceLister.Get(namespaceName)
if err != nil && !apierrors.IsNotFound(err) {
return nil, err
}
if apierrors.IsNotFound(err) {
// in case of latency in our caches, make a call direct to storage to verify that it truly exists or not
namespace, err = p.namespaceClient.Namespaces().Get(namespaceName, metav1.GetOptions{})
if err != nil {
return nil, err
}
}
return namespace.Labels, nil
}