-
Notifications
You must be signed in to change notification settings - Fork 23
/
Copy pathresolver.go
391 lines (360 loc) · 14.5 KB
/
resolver.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
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
// Copyright 2018 Bull S.A.S. Atos Technologies - Bull, Rue Jean Jaures, B.P.68, 78340, Les Clayes-sous-Bois, France.
//
// 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 deployments
import (
"context"
"fmt"
"strconv"
"strings"
"github.com/pkg/errors"
"github.com/ystia/yorc/v4/events"
"github.com/ystia/yorc/v4/helper/collections"
"github.com/ystia/yorc/v4/log"
"github.com/ystia/yorc/v4/tosca"
"github.com/ystia/yorc/v4/vault"
)
// DefaultVaultClient is the default Vault Client used to resolve get_secret functions
// it is nil by default and should be set by the one who created the client
var DefaultVaultClient vault.Client
const funcKeywordSELF string = "SELF"
const funcKeywordHOST string = "HOST"
const funcKeywordSOURCE string = "SOURCE"
const funcKeywordTARGET string = "TARGET"
// R_TARGET has a special meaning for A4C but we just consider it as an alias for TARGET
const funcKeywordRTARGET string = "R_TARGET"
const funcKeywordREQTARGET string = "REQ_TARGET"
// functionResolver is used to resolve TOSCA functions
type functionResolver struct {
deploymentID string
nodeName string
instanceName string
requirementIndex string
inputs map[string]tosca.ParameterDefinition
}
type resolverContext func(*functionResolver)
func (fr *functionResolver) context(contexts ...resolverContext) *functionResolver {
for _, ctx := range contexts {
ctx(fr)
}
return fr
}
func resolver(deploymentID string) *functionResolver {
return &functionResolver{deploymentID: deploymentID}
}
func withNodeName(nodeName string) resolverContext {
return func(fr *functionResolver) {
fr.nodeName = nodeName
}
}
func withInstanceName(instanceName string) resolverContext {
return func(fr *functionResolver) {
fr.instanceName = instanceName
}
}
func withRequirementIndex(reqIndex string) resolverContext {
return func(fr *functionResolver) {
fr.requirementIndex = reqIndex
}
}
func withInputParameters(inputs map[string]tosca.ParameterDefinition) resolverContext {
return func(fr *functionResolver) {
fr.inputs = inputs
}
}
func (fr *functionResolver) resolveFunction(ctx context.Context, fn *tosca.Function) (*TOSCAValue, error) {
if fn == nil {
return nil, errors.Errorf("Trying to resolve a nil function")
}
operands := make([]string, len(fn.Operands))
var hasSecret bool
for i, op := range fn.Operands {
if op.IsLiteral() {
var err error
s := op.String()
if isQuoted(s) {
s, err = strconv.Unquote(s)
if err != nil {
return nil, errors.Wrapf(err, "failed to unquote literal operand of function %v", fn)
}
}
operands[i] = s
} else {
subFn := op.(*tosca.Function)
r, err := fr.resolveFunction(ctx, subFn)
if err != nil {
return nil, err
}
if r != nil {
if r.IsSecret {
hasSecret = true
}
operands[i] = r.RawString()
}
}
}
switch fn.Operator {
case tosca.ConcatOperator:
return &TOSCAValue{Value: strings.Join(operands, ""), IsSecret: hasSecret}, nil
case tosca.GetInputOperator:
res, err := fr.resolveGetInput(ctx, operands)
return &TOSCAValue{Value: res}, err
case tosca.GetSecretOperator:
res, err := fr.resolveGetSecret(operands)
return &TOSCAValue{Value: res, IsSecret: true}, err
case tosca.GetOperationOutputOperator:
res, err := fr.resolveGetOperationOutput(ctx, operands)
return &TOSCAValue{Value: res}, err
case tosca.GetPropertyOperator:
res, err := fr.resolveGetPropertyOrAttribute(ctx, "property", operands)
if res != nil && hasSecret {
res.IsSecret = true
}
return res, err
case tosca.GetAttributeOperator:
res, err := fr.resolveGetPropertyOrAttribute(ctx, "attribute", operands)
if res != nil && hasSecret {
res.IsSecret = true
}
return res, err
}
return nil, errors.Errorf("Unsupported function %q", string(fn.Operator))
}
func (fr *functionResolver) resolveGetInput(ctx context.Context, operands []string) (string, error) {
if len(operands) < 1 {
return "", errors.Errorf("expecting at least one parameter for a get_input function")
}
args := getFuncNestedArgs(operands...)
return GetInputValue(ctx, fr.inputs, fr.deploymentID, args[0], args[1:]...)
}
func (fr *functionResolver) resolveGetOperationOutput(ctx context.Context, operands []string) (string, error) {
if len(operands) != 4 {
return "", errors.Errorf("expecting exactly four parameters for a get_operation_output function")
}
entity := operands[0]
ifName := operands[1]
opName := operands[2]
varName := operands[3]
if fr.instanceName == "" {
return "", errors.Errorf(`Can't resolve "get_operation_output: [%s]" without a specified instance name`, strings.Join(operands, ", "))
}
switch entity {
case funcKeywordHOST:
return "", errors.Errorf("Keyword %q not supported for function get_operation_output", funcKeywordHOST)
case funcKeywordSELF, funcKeywordSOURCE:
if fr.requirementIndex != "" {
// Relationship case
return GetOperationOutputForRelationship(ctx, fr.deploymentID, fr.nodeName, fr.instanceName, fr.requirementIndex, ifName, opName, varName)
}
// Node case
// Check entity
if entity == funcKeywordSOURCE {
return "", errors.Errorf("Keyword %q not supported for an node expression (only supported in relationships)", funcKeywordSOURCE)
}
output, err := GetOperationOutputForNode(ctx, fr.deploymentID, fr.nodeName, fr.instanceName, ifName, opName, varName)
if err != nil || output != "" {
return output, err
}
// Workaround to be backward compatible lets look at relationships
return getOperationOutputForRequirements(ctx, fr.deploymentID, fr.nodeName, fr.instanceName, ifName, opName, varName)
case funcKeywordTARGET, funcKeywordRTARGET:
if fr.requirementIndex != "" {
return "", errors.Errorf("Keyword %q not supported for an node expression (only supported in relationships)", funcKeywordTARGET)
}
targetNode, err := GetTargetNodeForRequirement(ctx, fr.deploymentID, fr.nodeName, fr.requirementIndex)
if err != nil {
return "", err
}
return GetOperationOutputForRelationship(ctx, fr.deploymentID, targetNode, fr.instanceName, fr.requirementIndex, ifName, opName, varName)
default:
instanceIDs, err := GetNodeInstancesIds(ctx, fr.deploymentID, entity)
if err != nil {
return "", err
}
if collections.ContainsString(instanceIDs, fr.instanceName) {
return GetOperationOutputForNode(ctx, fr.deploymentID, entity, fr.instanceName, ifName, opName, varName)
}
for _, id := range instanceIDs {
// by default take the first one
return GetOperationOutputForNode(ctx, fr.deploymentID, entity, id, ifName, opName, varName)
}
return "", errors.Errorf(`Can't resolve "get_operation_output: [%s]" can't find a valid instance for %q`, strings.Join(operands, ", "), entity)
}
}
func (fr *functionResolver) resolveGetPropertyOrAttribute(ctx context.Context, rType string, operands []string) (*TOSCAValue, error) {
funcString := fmt.Sprintf("get_%s: [%s]", rType, strings.Join(operands, ", "))
if len(operands) < 2 {
return nil, errors.Errorf("expecting at least two parameters for a get_%s function (%s)", rType, funcString)
}
if rType == "attribute" && fr.instanceName == "" {
return nil, errors.Errorf(`Can't resolve %q without a specified instance name`, funcString)
}
entity := operands[0]
if entity == funcKeywordHOST && fr.requirementIndex != "" {
return nil, errors.Errorf(`Can't resolve %q %s keyword is not supported in the context of a relationship`, funcString, funcKeywordHOST)
} else if fr.requirementIndex == "" && (entity == funcKeywordSOURCE || entity == funcKeywordTARGET || entity == funcKeywordRTARGET) {
return nil, errors.Errorf(`Can't resolve %q %s keyword is supported only in the context of a relationship`, funcString, entity)
}
// First get the node on which we should resolve the get_property
var err error
var actualNode string
switch entity {
case funcKeywordSELF, funcKeywordSOURCE:
actualNode = fr.nodeName
case funcKeywordHOST:
actualNode, err = GetHostedOnNode(ctx, fr.deploymentID, fr.nodeName)
if err != nil {
return nil, err
}
case funcKeywordTARGET, funcKeywordRTARGET:
actualNode, err = GetTargetNodeForRequirement(ctx, fr.deploymentID, fr.nodeName, fr.requirementIndex)
if err != nil {
return nil, err
}
case funcKeywordREQTARGET:
actualNode, err = GetTargetNodeForRequirementByName(ctx, fr.deploymentID, fr.nodeName, operands[1])
if err != nil {
return nil, err
}
if actualNode == "" {
nodeType, err := GetNodeType(ctx, fr.deploymentID, fr.nodeName)
if err != nil {
return nil, err
}
req, err := GetRequirementDefinitionOnTypeByName(ctx, fr.deploymentID, nodeType, operands[1])
if err != nil {
return nil, err
}
if req == nil {
return nil, errors.Errorf("missing requirement %q on node %q", operands[1], fr.nodeName)
}
if req.Occurrences.LowerBound != 0 {
return nil, errors.Errorf("requirement %q on node %q not found while not optional (occurrences lower bound set to %d)", operands[1], fr.nodeName, req.Occurrences.LowerBound)
}
// this means that the relationship between those 2 nodes does not exist
// and the requirement definition occurrence lower bound is set to 0 meaning it is optional.
return nil, nil
}
default:
actualNode = entity
}
if actualNode == "" {
return nil, errors.Errorf(`Can't resolve %q without a specified node name`, funcString)
}
var args []string
var result *TOSCAValue
// Check if second param is a capability or requirement
// this does not makes sense for a REQTARGET
if entity != funcKeywordREQTARGET {
if len(operands) > 2 {
cap, err := GetNodeCapabilityType(ctx, fr.deploymentID, actualNode, operands[1])
if err != nil {
return nil, err
}
if cap != "" {
args := getFuncNestedArgs(operands[2:]...)
if rType == "attribute" {
result, err = GetInstanceCapabilityAttributeValue(ctx, fr.deploymentID, actualNode, fr.instanceName, operands[1], args[0], args[1:]...)
} else {
result, err = GetCapabilityPropertyValue(ctx, fr.deploymentID, actualNode, operands[1], args[0], args[1:]...)
}
if err != nil || result != nil {
return result, err
}
// Else lets continue
}
// TODO requirement not supported in Alien
}
args = getFuncNestedArgs(operands[1:]...)
} else {
args = getFuncNestedArgs(operands[2:]...)
}
if rType == "attribute" {
if entity == funcKeywordSELF && fr.requirementIndex != "" {
result, err = GetRelationshipAttributeValueFromRequirement(ctx, fr.deploymentID, actualNode, fr.instanceName, fr.requirementIndex, args[0], args[1:]...)
} else {
result, err = GetInstanceAttributeValue(ctx, fr.deploymentID, actualNode, fr.instanceName, args[0], args[1:]...)
}
} else {
if entity == funcKeywordSELF && fr.requirementIndex != "" {
result, err = GetRelationshipPropertyValueFromRequirement(ctx, fr.deploymentID, actualNode, fr.requirementIndex, args[0], args[1:]...)
} else {
result, err = GetNodePropertyValue(ctx, fr.deploymentID, actualNode, args[0], args[1:]...)
}
}
if err != nil || result != nil {
return result, err
}
// A not found attribute is considered as acceptable for GetAttribute function and so doesn't return any error
if rType == "attribute" {
ctx := events.NewContext(context.Background(), events.LogOptionalFields{events.NodeID: fr.nodeName, events.InstanceID: fr.instanceName})
events.WithContextOptionalFields(ctx).NewLogEntry(events.LogLevelWARN, fr.deploymentID).Registerf("[WARNING] The attribute %q hasn't be found for deployment: %q, node: %q, instance: %q in expression %q", args[0], fr.deploymentID, fr.nodeName, fr.instanceName, funcString)
return nil, nil
}
log.Debugf("Deployment %q, node %q, can't resolve expression %q", fr.deploymentID, fr.nodeName, funcString)
return nil, errors.Errorf("Can't resolve expression %q", funcString)
}
func getFuncNestedArgs(nestedKeys ...string) []string {
res := make([]string, 0, len(nestedKeys))
for _, key := range nestedKeys {
// Alien way for nested keys . to separe keys and [index] for arrays
// ex: complex_prop.nested_array[0]
res = append(res, strings.Split(strings.Replace(strings.Replace(key, "]", "", -1), "[", ".", -1), ".")...)
}
return res
}
func resolveValueAssignmentAsString(ctx context.Context, deploymentID, nodeName, instanceName, requirementIndex, valueAssignment string, nestedKeys ...string) (*TOSCAValue, error) {
return resolveValueAssignment(ctx, deploymentID, nodeName, instanceName, requirementIndex, &TOSCAValue{Value: valueAssignment}, nestedKeys...)
}
func resolveValueAssignment(ctx context.Context, deploymentID, nodeName, instanceName, requirementIndex string, valueAssignment *TOSCAValue, nestedKeys ...string) (*TOSCAValue, error) {
// Function
fromSecret := valueAssignment.IsSecret
f, err := tosca.ParseFunction(valueAssignment.RawString())
if err != nil {
return nil, err
}
r := resolver(deploymentID).context(withNodeName(nodeName), withInstanceName(instanceName), withRequirementIndex(requirementIndex))
valueAssignment, err = r.resolveFunction(ctx, f)
if err != nil {
return nil, err
}
if valueAssignment == nil {
ctx := events.NewContext(context.Background(), events.LogOptionalFields{events.NodeID: nodeName, events.InstanceID: instanceName})
events.WithContextOptionalFields(ctx).NewLogEntry(events.LogLevelWARN, deploymentID).Registerf("[WARNING] The value assignment %q hasn't be resolved for deployment: %q, node: %q, instance: %q. An empty string is returned instead", valueAssignment, deploymentID, nodeName, instanceName)
valueAssignment = &TOSCAValue{Value: ""}
}
if fromSecret {
valueAssignment.IsSecret = true
}
return valueAssignment, nil
}
func isQuoted(s string) bool {
return len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"'
}
func (fr *functionResolver) resolveGetSecret(operands []string) (string, error) {
if len(operands) < 1 {
return "", errors.New("expecting at least one parameter for a get_secret function")
}
if DefaultVaultClient == nil {
return "", errors.New("can't resolve get_secret function there is no vault client configured")
}
var options []string
if len(operands) > 1 {
options = operands[1:]
}
secret, err := DefaultVaultClient.GetSecret(operands[0], options...)
if err != nil {
return "", err
}
return secret.String(), nil
}