2020-02-13 13:57:48 -08:00
|
|
|
package variables
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"regexp"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/golang/glog"
|
|
|
|
"github.com/nirmata/kyverno/pkg/engine/context"
|
|
|
|
)
|
|
|
|
|
2020-02-26 16:41:48 -08:00
|
|
|
const (
|
|
|
|
variableRegex = `\{\{([^{}]*)\}\}`
|
|
|
|
singleVarRegex = `^\{\{([^{}]*)\}\}$`
|
|
|
|
)
|
2020-02-14 11:59:28 -08:00
|
|
|
|
2020-02-13 13:57:48 -08:00
|
|
|
//SubstituteVars replaces the variables with the values defined in the context
|
|
|
|
// - if any variable is invaid or has nil value, it is considered as a failed varable substitution
|
2020-02-14 11:59:28 -08:00
|
|
|
func SubstituteVars(ctx context.EvalInterface, pattern interface{}) (interface{}, error) {
|
2020-02-13 13:57:48 -08:00
|
|
|
errs := []error{}
|
2020-02-14 11:59:28 -08:00
|
|
|
pattern = subVars(ctx, pattern, "", &errs)
|
2020-02-13 13:57:48 -08:00
|
|
|
if len(errs) == 0 {
|
|
|
|
// no error while parsing the pattern
|
2020-02-14 11:59:28 -08:00
|
|
|
return pattern, nil
|
2020-02-13 13:57:48 -08:00
|
|
|
}
|
2020-02-26 16:41:48 -08:00
|
|
|
return pattern, fmt.Errorf("%v", errs)
|
2020-02-13 13:57:48 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
func subVars(ctx context.EvalInterface, pattern interface{}, path string, errs *[]error) interface{} {
|
|
|
|
switch typedPattern := pattern.(type) {
|
|
|
|
case map[string]interface{}:
|
|
|
|
return subMap(ctx, typedPattern, path, errs)
|
|
|
|
case []interface{}:
|
|
|
|
return subArray(ctx, typedPattern, path, errs)
|
|
|
|
case string:
|
2020-02-26 16:41:48 -08:00
|
|
|
return subValR(ctx, typedPattern, path, errs)
|
2020-02-13 13:57:48 -08:00
|
|
|
default:
|
|
|
|
return pattern
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func subMap(ctx context.EvalInterface, patternMap map[string]interface{}, path string, errs *[]error) map[string]interface{} {
|
|
|
|
for key, patternElement := range patternMap {
|
|
|
|
curPath := path + "/" + key
|
|
|
|
value := subVars(ctx, patternElement, curPath, errs)
|
|
|
|
patternMap[key] = value
|
|
|
|
|
|
|
|
}
|
|
|
|
return patternMap
|
|
|
|
}
|
|
|
|
|
|
|
|
func subArray(ctx context.EvalInterface, patternList []interface{}, path string, errs *[]error) []interface{} {
|
|
|
|
for idx, patternElement := range patternList {
|
|
|
|
curPath := path + "/" + strconv.Itoa(idx)
|
|
|
|
value := subVars(ctx, patternElement, curPath, errs)
|
|
|
|
patternList[idx] = value
|
|
|
|
}
|
|
|
|
return patternList
|
|
|
|
}
|
|
|
|
|
2020-02-26 16:41:48 -08:00
|
|
|
// subValR resolves the variables if defined
|
|
|
|
func subValR(ctx context.EvalInterface, valuePattern string, path string, errs *[]error) interface{} {
|
|
|
|
|
|
|
|
// variable values can be scalar values(string,int, float) or they can be obects(map,slice)
|
|
|
|
// - {{variable}}
|
|
|
|
// there is a single variable resolution so the value can be scalar or object
|
|
|
|
// - {{variable1--{{variable2}}}}}
|
|
|
|
// variable2 is evaluted first as an individual variable and can be have scalar or object values
|
|
|
|
// but resolving the outer variable, {{variable--<value>}}
|
|
|
|
// if <value> is scalar then it can replaced, but for object types its tricky
|
|
|
|
// as object cannot be directy replaced, if the object is stringyfied then it loses it structure.
|
|
|
|
// since this might be a potential place for error, required better error reporting and handling
|
|
|
|
|
|
|
|
// object values are only suported for single variable substitution
|
|
|
|
if ok, retVal := processIfSingleVariable(ctx, valuePattern, path, errs); ok {
|
|
|
|
return retVal
|
2020-02-13 13:57:48 -08:00
|
|
|
}
|
2020-02-26 16:41:48 -08:00
|
|
|
// var emptyInterface interface{}
|
|
|
|
var failedVars []string
|
|
|
|
// process type string
|
|
|
|
for {
|
|
|
|
valueStr := valuePattern
|
|
|
|
if len(failedVars) != 0 {
|
|
|
|
glog.Info("some failed variables short-circuiting")
|
|
|
|
break
|
|
|
|
}
|
|
|
|
// get variables at this level
|
2020-02-26 17:02:16 -08:00
|
|
|
validRegex := regexp.MustCompile(variableRegex)
|
2020-02-26 16:41:48 -08:00
|
|
|
groups := validRegex.FindAllStringSubmatch(valueStr, -1)
|
|
|
|
if len(groups) == 0 {
|
|
|
|
// there was no match
|
|
|
|
// not variable defined
|
|
|
|
break
|
|
|
|
}
|
|
|
|
subs := map[string]interface{}{}
|
|
|
|
for _, group := range groups {
|
|
|
|
if _, ok := subs[group[0]]; ok {
|
|
|
|
// value has already been substituted
|
|
|
|
continue
|
2020-02-13 13:57:48 -08:00
|
|
|
}
|
2020-02-26 16:41:48 -08:00
|
|
|
// here we do the querying of the variables from the context
|
|
|
|
variable, err := ctx.Query(group[1])
|
|
|
|
if err != nil {
|
|
|
|
// error while evaluating
|
|
|
|
failedVars = append(failedVars, group[1])
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// path not found in context and value stored in null/nill
|
|
|
|
if variable == nil {
|
|
|
|
failedVars = append(failedVars, group[1])
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// get values for each and replace
|
|
|
|
subs[group[0]] = variable
|
2020-02-13 13:57:48 -08:00
|
|
|
}
|
2020-02-26 16:41:48 -08:00
|
|
|
// perform substitutions
|
|
|
|
newVal := valueStr
|
|
|
|
for k, v := range subs {
|
|
|
|
// if value is of type string then cast else consider it as direct replacement
|
|
|
|
if val, ok := v.(string); ok {
|
|
|
|
newVal = strings.Replace(newVal, k, val, -1)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// if type is not scalar then consider this as a failed variable
|
|
|
|
glog.Infof("variable %s resolves to non-scalar value %v. Non-Scalar values are not supported for nested variables", k, v)
|
|
|
|
failedVars = append(failedVars, k)
|
|
|
|
}
|
|
|
|
valuePattern = newVal
|
2020-02-13 13:57:48 -08:00
|
|
|
}
|
2020-02-26 16:41:48 -08:00
|
|
|
// update errors if any
|
|
|
|
if len(failedVars) > 0 {
|
|
|
|
*errs = append(*errs, fmt.Errorf("failed to resolve %v at path %s", failedVars, path))
|
2020-02-13 13:57:48 -08:00
|
|
|
}
|
|
|
|
|
2020-02-26 16:41:48 -08:00
|
|
|
return valuePattern
|
2020-02-13 13:57:48 -08:00
|
|
|
}
|
|
|
|
|
2020-02-26 16:41:48 -08:00
|
|
|
// processIfSingleVariable will process the evaluation of single variables
|
|
|
|
// {{variable-{{variable}}}} -> compound/nested variables
|
|
|
|
// {{variable}}{{variable}} -> multiple variables
|
|
|
|
// {{variable}} -> single variable
|
|
|
|
// if the value can be evaluted return the value
|
|
|
|
// -> return value can be scalar or object type
|
|
|
|
// -> if the variable is not present in the context then add an error and dont process further
|
|
|
|
func processIfSingleVariable(ctx context.EvalInterface, valuePattern interface{}, path string, errs *[]error) (bool, interface{}) {
|
|
|
|
valueStr, ok := valuePattern.(string)
|
|
|
|
if !ok {
|
|
|
|
glog.Infof("failed to convert %v to string", valuePattern)
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
// get variables at this level
|
|
|
|
validRegex := regexp.MustCompile(singleVarRegex)
|
|
|
|
groups := validRegex.FindAllStringSubmatch(valueStr, -1)
|
|
|
|
if len(groups) == 0 {
|
|
|
|
return false, nil
|
2020-02-13 13:57:48 -08:00
|
|
|
}
|
2020-02-26 16:41:48 -08:00
|
|
|
// as there will be exactly one variable based on the above regex
|
2020-02-26 17:02:16 -08:00
|
|
|
group := groups[0]
|
|
|
|
variable, err := ctx.Query(group[1])
|
|
|
|
if err != nil || variable == nil {
|
|
|
|
*errs = append(*errs, fmt.Errorf("failed to resolve %v at path %s", group[1], path))
|
|
|
|
// return the same value pattern, and add un-resolvable variable error
|
|
|
|
return true, valuePattern
|
2020-02-13 13:57:48 -08:00
|
|
|
}
|
2020-02-26 17:02:16 -08:00
|
|
|
return true, variable
|
2020-02-13 13:57:48 -08:00
|
|
|
}
|