/
policy_resolver_node.go
376 lines (304 loc) · 11.9 KB
/
policy_resolver_node.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
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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
package resolve
import (
"fmt"
"github.com/Aptomi/aptomi/pkg/event"
"github.com/Aptomi/aptomi/pkg/lang"
"github.com/Aptomi/aptomi/pkg/plugin/k8s"
"github.com/Aptomi/aptomi/pkg/runtime"
"github.com/Aptomi/aptomi/pkg/util"
)
// This is a special internal structure that gets used by the engine, while we traverse the policy graph for a given claim
// It gets incrementally populated with data, as policy evaluation goes on for a given claim
type resolutionNode struct {
// pointer to the policy resolver
resolver *PolicyResolver
// pointer to event log (local to the node)
eventLog *event.Log
// combined event logs from all resolution nodes in the subtree
eventLogsCombined []*event.Log
// new instance of PolicyResolution, where resolution resolution will be stored
resolution *PolicyResolution
// depth we are currently on (as we are traversing policy graph), with initial claim being on depth 0
depth int
// reference to initial claim
claim *lang.Claim
// reference to user who requested this claim
user *lang.User
// reference to the namespace & service we are currently resolving
namespace string
serviceName string
service *lang.Service
// reference to the current set of labels
labels *lang.LabelSet
// reference to the context & the corresponding bundle that were matched
context *lang.Context
bundle *lang.Bundle
// reference to the allocation keys that were resolved
allocationKeysResolved []string
// reference to the current node in discovery tree for components announcing their discovery properties
// component1...component2...component3 -> component instance key
discoveryTreeNode util.NestedParameterMap
// reference to the current component in discovery tree
component *lang.BundleComponent
// reference to the current component key
componentKey *ComponentInstanceKey
// reference to the current bundle key
bundleKey *ComponentInstanceKey
// reference to the last key we arrived with, so we can reconstruct graph edges between keys
arrivalKey *ComponentInstanceKey
// path that we traveled so far (to detect cycles)
path []string
}
// Creates a new empty resolution node
func (resolver *PolicyResolver) newResolutionNode() *resolutionNode {
eventLog := event.NewLog(resolver.eventLog.GetLevel(), resolver.eventLog.GetScope())
return &resolutionNode{
resolver: resolver,
eventLog: eventLog,
eventLogsCombined: []*event.Log{eventLog},
resolution: NewPolicyResolution(),
depth: 0,
// empty discovery tree
discoveryTreeNode: util.NestedParameterMap{},
// empty path
path: []string{},
}
}
// Initialized a newly created resolution node as a starting point for resolving a particular claim.
// Adds claim and user labels into it.
func (resolver *PolicyResolver) initResolutionNode(node *resolutionNode, claim *lang.Claim) {
// populate user, claim
node.claim = claim
user := resolver.externalData.UserLoader.LoadUserByName(claim.User)
node.user = user
// start with the namespace & service specified in the claim
node.namespace = claim.Namespace
node.serviceName = claim.Service
// create a starting set of labels, combining user labels and claim labels
node.labels = lang.NewLabelSet(claim.Labels)
if user != nil {
node.labels.AddLabels(user.Labels)
}
}
// Creates a new resolution node (as we are processing claim on another bundle)
func (node *resolutionNode) createChildNode() *resolutionNode {
eventLog := event.NewLog(node.eventLog.GetLevel(), node.eventLog.GetScope())
return &resolutionNode{
resolver: node.resolver,
eventLog: eventLog,
eventLogsCombined: []*event.Log{eventLog},
resolution: node.resolution,
depth: node.depth + 1,
claim: node.claim,
user: node.user,
// we take the current component we are iterating over, and get its service name
namespace: node.namespace,
serviceName: node.component.Service,
// proceed with the current set of labels
labels: lang.NewLabelSet(node.labels.Labels),
// move further by the discovery tree via component name link
discoveryTreeNode: node.discoveryTreeNode.GetNestedMap(node.component.Name),
// remember the last arrival key
arrivalKey: node.componentKey,
// copy path
path: util.CopySliceOfStrings(node.path),
}
}
// As the resolution goes on, this method is called when objects become resolved and available in the context
// Right now we only call it for claim, service, and bundle
func (node *resolutionNode) objectResolved(object runtime.Storable) {
node.eventLog.AddFixedField(object.GetKind()+"Id", runtime.KeyForStorable(object))
}
// Helper to check that user exists
func (node *resolutionNode) checkUserExists() error {
if node.user == nil {
return node.errorUserDoesNotExist()
}
return nil
}
// Helper to get a service
func (node *resolutionNode) getService(policy *lang.Policy) (*lang.Service, error) {
serviceObj, err := policy.GetObject(lang.ServiceObject.Kind, node.serviceName, node.namespace)
if serviceObj == nil || err != nil {
panic(fmt.Sprintf("Can't get service '%s/%s': %s", node.namespace, node.serviceName, err))
}
service := serviceObj.(*lang.Service) // nolint: errcheck
// User should have permissions to consume the service according to the ACL
userView := node.resolver.policy.View(node.user)
canConsume, err := userView.CanConsume(service)
if !canConsume {
return nil, node.userNotAllowedToConsumeService(err)
}
node.logServiceFound(service)
return service, nil
}
// Helper to get a matched context
func (node *resolutionNode) getMatchedContext(policy *lang.Policy) (*lang.Context, error) {
// Locate the list of contexts for bundle
node.logStartMatchingContexts()
// Find matching context
contextualData := node.getContextualDataForContextExpression()
var contextMatched *lang.Context
for _, context := range node.service.Contexts {
// Check if context matches (based on criteria)
matched, err := context.Matches(contextualData, node.resolver.expressionCache)
if err != nil {
// Propagate error up
return nil, node.errorWhenTestingContext(context, err)
}
node.logTestedContextCriteria(context, matched)
if matched {
contextMatched = context
break
}
}
if contextMatched == nil {
return nil, node.errorContextNotMatched()
}
node.logContextMatched(contextMatched)
return contextMatched, nil
}
// Helper to get a matched bundle
func (node *resolutionNode) getMatchedBundle(policy *lang.Policy) (*lang.Bundle, error) {
bundleObj, err := policy.GetObject(lang.BundleObject.Kind, node.context.Allocation.Bundle, node.namespace)
if bundleObj == nil || err != nil {
panic(fmt.Sprintf("Can't get bundle '%s/%s': %s", node.namespace, node.context.Allocation.Bundle, err))
}
bundle := bundleObj.(*lang.Bundle) // nolint: errcheck
// Bundle should be located in the same namespace as service
if bundle.Namespace != node.service.Namespace {
return nil, node.errorBundleIsNotInSameNamespaceAsService(bundle)
}
node.logBundleFound(bundle)
return bundle, nil
}
// Helper to resolve allocation keys
func (node *resolutionNode) resolveAllocationKeys(policy *lang.Policy) ([]string, error) {
// Resolve allocation keys (they can be dynamic, depending on user labels)
result, err := node.context.ResolveKeys(node.getContextualDataForContextAllocationTemplate(), node.resolver.templateCache)
if err != nil {
return nil, node.errorWhenResolvingAllocationKeys(err)
}
node.logAllocationKeysSuccessfullyResolved(result)
return result, nil
}
// checks if component criteria holds or not (i.e. whether component should be included or excluded from processing)
func (node *resolutionNode) componentMatches(component *lang.BundleComponent) (bool, error) {
contextualData := node.getContextualDataForComponentCriteria()
matched, err := component.Matches(contextualData, node.resolver.expressionCache)
if err != nil {
// Propagate error up
return false, node.errorWhenTestingComponent(component, err)
}
if !matched {
node.logComponentNotMatched(component)
}
return matched, nil
}
// createComponentKey creates a component key
func (node *resolutionNode) createComponentKey(component *lang.BundleComponent) (*ComponentInstanceKey, error) {
targetLabel := node.labels.Labels[lang.LabelTarget]
if len(targetLabel) <= 0 {
return nil, node.errorTargetNotSet()
}
target := lang.NewTarget(targetLabel)
cluster, err := target.GetCluster(node.resolver.policy, node.namespace)
if err != nil {
return nil, node.errorClusterLookup(target.ClusterName, err)
}
// handle default namespace for kubernetes clusters
if len(target.Suffix) <= 0 && cluster.Type == "kubernetes" {
k8sClusterConfig := &k8s.ClusterConfig{}
err := cluster.ParseConfigInto(k8sClusterConfig)
// if it's a k8s cluster, let's grab default namespace from it
if err == nil {
target.Suffix = k8sClusterConfig.DefaultNamespace
}
// if it's still empty, use default
if len(target.Suffix) <= 0 {
target.Suffix = "default"
}
}
return NewComponentInstanceKey(
cluster,
target.Suffix,
node.service,
node.context,
node.allocationKeysResolved,
node.bundle,
component,
), nil
}
func (node *resolutionNode) transformLabels(labels *lang.LabelSet, operations lang.LabelOperations) {
changedLabels := labels.ApplyTransform(operations)
if changedLabels {
node.logLabels(labels, "after transform")
}
}
func (node *resolutionNode) processRulesWithinNamespace(policyNamespace *lang.PolicyNamespace, result *lang.RuleActionResult) error {
if policyNamespace == nil {
return nil
}
rules := lang.GetRulesSortedByWeight(policyNamespace.Rules)
contextualData := node.getContextualDataForRuleExpression()
for _, rule := range rules {
matched, err := rule.Matches(contextualData, node.resolver.expressionCache)
if err != nil {
return node.errorWhenProcessingRule(rule, err)
}
node.logTestedRuleMatch(rule, matched)
if matched {
rule.ApplyActions(result)
// if a claim has been rejected, handle it right away and return that we cannot resolve it
if result.RejectClaim {
return node.errorClaimNotAllowedByRules()
}
if result.ChangedLabelsOnLastApply {
node.logLabels(result.Labels, "after transform")
}
}
}
node.logRulesProcessingResult(policyNamespace, result)
return nil
}
func (node *resolutionNode) processRules() (*lang.RuleActionResult, error) {
result := lang.NewRuleActionResult(node.labels)
// process rules within the current namespace
var err = node.processRulesWithinNamespace(node.resolver.policy.Namespace[node.namespace], result)
if err != nil {
return nil, err
}
// process rules globally (within system namespace)
err = node.processRulesWithinNamespace(node.resolver.policy.Namespace[runtime.SystemNS], result)
if err != nil {
return nil, err
}
return result, nil
}
func (node *resolutionNode) calculateAndStoreCodeParams() error {
componentCodeParams, err := util.ProcessParameterTree(node.component.Code.Params, node.getContextualDataForCodeDiscoveryTemplate(), node.resolver.templateCache, util.ModeEvaluate)
if err != nil {
return node.errorWhenProcessingCodeParams(err)
}
err = node.resolution.RecordCodeParams(node.componentKey, componentCodeParams)
if err != nil {
return node.errorWhenProcessingCodeParams(err)
}
return nil
}
func (node *resolutionNode) calculateAndStoreDiscoveryParams() error {
componentDiscoveryParams, err := util.ProcessParameterTree(node.component.Discovery, node.getContextualDataForCodeDiscoveryTemplate(), node.resolver.templateCache, util.ModeEvaluate)
if err != nil {
return node.errorWhenProcessingDiscoveryParams(err)
}
err = node.resolution.RecordDiscoveryParams(node.componentKey, componentDiscoveryParams)
if err != nil {
return node.errorWhenProcessingDiscoveryParams(err)
}
// Populate discovery tree (allow this component to announce its discovery properties in the discovery tree)
node.discoveryTreeNode.GetNestedMap(node.component.Name)["instance"] = util.EscapeName(node.componentKey.GetDeployName())
for k, v := range componentDiscoveryParams {
node.discoveryTreeNode.GetNestedMap(node.component.Name)[k] = v
}
return nil
}