forked from open-policy-agent/frameworks
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtemplate_client.go
More file actions
218 lines (179 loc) · 7.1 KB
/
template_client.go
File metadata and controls
218 lines (179 loc) · 7.1 KB
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
package client
import (
"fmt"
apiconstraints "github.com/open-policy-agent/frameworks/constraint/pkg/apis/constraints"
"github.com/open-policy-agent/frameworks/constraint/pkg/client/crds"
clienterrors "github.com/open-policy-agent/frameworks/constraint/pkg/client/errors"
constraintlib "github.com/open-policy-agent/frameworks/constraint/pkg/core/constraints"
"github.com/open-policy-agent/frameworks/constraint/pkg/core/templates"
"github.com/open-policy-agent/frameworks/constraint/pkg/handler"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// templateClient handles per-ConstraintTemplate operations.
//
// Not threadsafe.
type templateClient struct {
// targets are the Targets which this Template is executed for.
targets []handler.TargetHandler
// template is a copy of the original ConstraintTemplate added to Client.
template *templates.ConstraintTemplate
// constraints are all currently-known Constraints for this Template.
constraints map[string]*constraintClient
// crd is a cache of the generated CustomResourceDefinition generated from
// this Template. This is used to validate incoming Constraints before adding
// them.
crd *apiextensions.CustomResourceDefinition
// if, for some reason, there was an error adding a pre-cached constraint after
// a driver switch, AddTemplate returns an error. We should preserve that state
// so that we know a constraint replay should be attempted the next time AddTemplate
// is called.
needsConstraintReplay bool
// activeDrivers keeps track of drivers that are in an ambiguous state due to a failed
// cross-driver update. This allows us to clean up stale state on old drivers.
activeDrivers map[string]bool
}
func newTemplateClient() *templateClient {
return &templateClient{
constraints: make(map[string]*constraintClient),
activeDrivers: make(map[string]bool),
}
}
func (e *templateClient) ValidateConstraint(constraint *unstructured.Unstructured) error {
for _, target := range e.targets {
err := target.ValidateConstraint(constraint)
if err != nil {
return fmt.Errorf("%w: %v", apiconstraints.ErrInvalidConstraint, err)
}
}
return crds.ValidateCR(constraint, e.crd)
}
// ApplyDefaultParams will apply any default parameters defined in the CRD of the constraint's
// corresponding template.
// Assumes ValidateConstraint() is called so the constraint is a valid CRD.
func (e *templateClient) ApplyDefaultParams(constraint *unstructured.Unstructured) (*unstructured.Unstructured, error) {
structural, err := schema.NewStructural(e.crd.Spec.Validation.OpenAPIV3Schema)
if err != nil {
return nil, err
}
defaulting.Default(constraint.Object, structural)
return constraint, nil
}
func (e *templateClient) getTemplate() *templates.ConstraintTemplate {
return e.template.DeepCopy()
}
func (e *templateClient) Update(templ *templates.ConstraintTemplate, crd *apiextensions.CustomResourceDefinition, targets ...handler.TargetHandler) {
cpy := templ.DeepCopy()
cpy.Status = templates.ConstraintTemplateStatus{}
// Updating e.template must happen after any operations which may fail have
// completed successfully. This ensures the SemanticEqual exit-early is not
// triggered unless the Template was previously successfully added.
e.template = cpy
e.crd = crd
e.targets = targets
}
// AddConstraint adds the Constraint to the Template.
// Returns true and no error if the Constraint was changed successfully.
// Returns false and no error if the Constraint was not updated due to being
// identical to the stored version.
func (e *templateClient) AddConstraint(constraint *unstructured.Unstructured, enforcementPoints []string) (bool, error) {
enforcementActionsForEPs := make(map[string][]string)
enforcementAction, err := apiconstraints.GetEnforcementAction(constraint)
if err != nil {
return false, err
}
if apiconstraints.IsEnforcementActionScoped(enforcementAction) {
enforcementActionsForEPs, err = apiconstraints.GetEnforcementActionsForEP(constraint, enforcementPoints)
if err != nil {
return false, err
}
} else {
for _, ep := range enforcementPoints {
enforcementActionsForEPs[ep] = append(enforcementActionsForEPs[ep], enforcementAction)
}
}
// Compare with the already-existing Constraint.
// If identical, exit early.
cached, found := e.constraints[constraint.GetName()]
if found && constraintlib.SemanticEqualWithLabelsAndAnnotations(cached.constraint, constraint) {
return false, nil
}
matchers, err := makeMatchers(e.targets, constraint)
if err != nil {
return false, err
}
cpy := constraint.DeepCopy()
delete(cpy.Object, statusField)
e.constraints[constraint.GetName()] = &constraintClient{
constraint: cpy,
matchers: matchers,
enforcementAction: enforcementAction,
enforcementActionsForEP: enforcementActionsForEPs,
}
return true, nil
}
// GetConstraint returns the Constraint with name for this Template.
func (e *templateClient) GetConstraint(name string) (*unstructured.Unstructured, error) {
constraint, found := e.constraints[name]
if !found {
kind := e.template.Spec.CRD.Spec.Names.Kind
return nil, fmt.Errorf("%w: %q %q", ErrMissingConstraint, kind, name)
}
return constraint.getConstraint(), nil
}
func (e *templateClient) RemoveConstraint(name string) {
delete(e.constraints, name)
}
// Matches returns a map from Constraint names to the results of running Matchers
// against the passed review.
//
// ignoredTargets specifies the targets whose matchers to not run.
func (e *templateClient) Matches(target string, review interface{}, enforcementPoints []string) map[string]constraintMatchResult {
result := make(map[string]constraintMatchResult)
for name, constraint := range e.constraints {
cResult := constraint.matches(target, review, enforcementPoints...)
if cResult != nil {
result[name] = *cResult
}
}
return result
}
func makeMatchers(targets []handler.TargetHandler, constraint *unstructured.Unstructured) (map[string]constraintlib.Matcher, error) {
result := make(map[string]constraintlib.Matcher)
errs := clienterrors.ErrorMap{}
for _, target := range targets {
name := target.GetName()
matcher, err := target.ToMatcher(constraint)
if err != nil {
errs.Add(name, fmt.Errorf("%w: %v", apiconstraints.ErrInvalidConstraint, err))
}
result[name] = matcher
}
if len(errs) > 0 {
return nil, &errs
}
return result, nil
}
// MatchesOperation checks if the given operation type matches any of the template's target operations.
func (e *templateClient) MatchesOperation(operation string) bool {
if len(e.template.Spec.Targets) != 1 {
// for backward compatibility, matching all templates by default
return true
}
target := e.template.Spec.Targets[0]
// If no operations are specified, match all operations by default to maintain backward compatibility.
if len(target.Operations) == 0 {
return true
}
for _, targetOp := range target.Operations {
if operation == string(targetOp) {
return true
}
if string(targetOp) == "*" {
return true
}
}
return false
}