1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-06 16:06:56 +00:00
kyverno/pkg/engine/variables/vars.go

176 lines
5.9 KiB
Go
Raw Normal View History

package variables
import (
"fmt"
"regexp"
"strconv"
"strings"
2020-03-17 11:05:20 -07:00
"github.com/go-logr/logr"
"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
//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-03-17 11:05:20 -07:00
func SubstituteVars(log logr.Logger, ctx context.EvalInterface, pattern interface{}) (interface{}, error) {
errs := []error{}
2020-03-17 11:05:20 -07:00
pattern = subVars(log, ctx, pattern, "", &errs)
if len(errs) == 0 {
// no error while parsing the pattern
2020-02-14 11:59:28 -08:00
return pattern, nil
}
2020-02-26 16:41:48 -08:00
return pattern, fmt.Errorf("%v", errs)
}
2020-03-17 11:05:20 -07:00
func subVars(log logr.Logger, ctx context.EvalInterface, pattern interface{}, path string, errs *[]error) interface{} {
switch typedPattern := pattern.(type) {
case map[string]interface{}:
2020-04-09 22:00:24 +05:30
mapCopy := make(map[string]interface{})
for k, v := range typedPattern {
mapCopy[k] = v
}
return subMap(log, ctx, mapCopy, path, errs)
case []interface{}:
2020-04-09 22:00:24 +05:30
sliceCopy := make([]interface{}, len(typedPattern))
copy(sliceCopy, typedPattern)
return subArray(log, ctx, sliceCopy, path, errs)
case string:
2020-03-17 11:05:20 -07:00
return subValR(log, ctx, typedPattern, path, errs)
default:
return pattern
}
}
2020-03-17 11:05:20 -07:00
func subMap(log logr.Logger, ctx context.EvalInterface, patternMap map[string]interface{}, path string, errs *[]error) map[string]interface{} {
for key, patternElement := range patternMap {
curPath := path + "/" + key
2020-03-17 11:05:20 -07:00
value := subVars(log, ctx, patternElement, curPath, errs)
patternMap[key] = value
}
return patternMap
}
2020-03-17 11:05:20 -07:00
func subArray(log logr.Logger, ctx context.EvalInterface, patternList []interface{}, path string, errs *[]error) []interface{} {
for idx, patternElement := range patternList {
curPath := path + "/" + strconv.Itoa(idx)
2020-03-17 11:05:20 -07:00
value := subVars(log, ctx, patternElement, curPath, errs)
patternList[idx] = value
}
return patternList
}
2020-02-26 16:41:48 -08:00
// subValR resolves the variables if defined
2020-03-17 11:05:20 -07:00
func subValR(log logr.Logger, ctx context.EvalInterface, valuePattern string, path string, errs *[]error) interface{} {
2020-02-26 16:41:48 -08:00
// 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
2020-03-17 11:05:20 -07:00
if ok, retVal := processIfSingleVariable(log, ctx, valuePattern, path, errs); ok {
2020-02-26 16:41:48 -08:00
return retVal
}
2020-02-26 16:41:48 -08:00
// var emptyInterface interface{}
var failedVars []string
// process type string
for {
valueStr := valuePattern
if len(failedVars) != 0 {
2020-03-17 11:05:20 -07:00
log.Info("failed to resolve variablesl short-circuiting")
2020-02-26 16:41:48 -08:00
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-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-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
2020-03-17 11:05:20 -07:00
log.Info("variable resolves to non-scalar value. Non-Scalar values are not supported for nested variables", "variable", k, "value", v)
2020-02-26 16:41:48 -08:00
failedVars = append(failedVars, k)
}
valuePattern = newVal
}
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-26 16:41:48 -08:00
return valuePattern
}
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
2020-03-17 11:05:20 -07:00
func processIfSingleVariable(log logr.Logger, ctx context.EvalInterface, valuePattern interface{}, path string, errs *[]error) (bool, interface{}) {
2020-02-26 16:41:48 -08:00
valueStr, ok := valuePattern.(string)
if !ok {
2020-03-17 11:05:20 -07:00
log.Info("failed to convert to string", "pattern", valuePattern)
2020-02-26 16:41:48 -08:00
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-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-26 17:02:16 -08:00
return true, variable
}