1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-06 16:06:56 +00:00
kyverno/pkg/engine/context/loaders/variable.go
Khaled Emara c2646f7a9d
feat(json): reduce reliance on DocumentToUntyped() (#10724)
Signed-off-by: Khaled Emara <khaled.emara@nirmata.com>
Co-authored-by: Mariam Fahmy <mariam.fahmy@nirmata.com>
2024-07-29 11:57:20 +00:00

125 lines
3.6 KiB
Go

package loaders
import (
"encoding/json"
"fmt"
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/jmespath"
"github.com/kyverno/kyverno/pkg/engine/jsonutils"
"github.com/kyverno/kyverno/pkg/engine/variables"
)
type variableLoader struct {
logger logr.Logger
entry kyvernov1.ContextEntry
enginectx enginecontext.Interface
jp jmespath.Interface
data []byte
}
func NewVariableLoader(
logger logr.Logger,
entry kyvernov1.ContextEntry,
enginectx enginecontext.Interface,
jp jmespath.Interface,
) enginecontext.Loader {
return &variableLoader{
logger: logger,
entry: entry,
enginectx: enginectx,
jp: jp,
}
}
func (vl *variableLoader) HasLoaded() bool {
return vl.data != nil
}
func (vl *variableLoader) LoadData() error {
return vl.loadVariable()
}
func (vl *variableLoader) loadVariable() (err error) {
logger := vl.logger
ctx := vl.enginectx
entry := vl.entry
path := ""
if entry.Variable.JMESPath != "" {
jp, err := variables.SubstituteAll(logger, ctx, entry.Variable.JMESPath)
if err != nil {
return fmt.Errorf("failed to substitute variables in context entry %s %s: %v", entry.Name, entry.Variable.JMESPath, err)
}
var ok bool
path, ok = jp.(string)
if !ok {
return fmt.Errorf("jmespath value must be a string %s %s: %v", entry.Name, entry.Variable.JMESPath, err)
}
logger.V(4).Info("evaluated jmespath", "variable name", entry.Name, "jmespath", path)
}
var defaultValue interface{} = nil
if entry.Variable.GetDefault() != nil {
value, err := jsonutils.DocumentToUntyped(entry.Variable.GetDefault())
if err != nil {
return fmt.Errorf("invalid default for variable %s", entry.Name)
}
defaultValue, err = variables.SubstituteAll(logger, ctx, value)
if err != nil {
return fmt.Errorf("failed to substitute variables in context entry %s %s: %v", entry.Name, entry.Variable.GetDefault(), err)
}
logger.V(4).Info("evaluated default value", "variable name", entry.Name, "jmespath", defaultValue)
}
var output interface{} = defaultValue
if entry.Variable.GetValue() != nil {
value, _ := jsonutils.DocumentToUntyped(entry.Variable.GetValue())
variable, err := variables.SubstituteAll(logger, ctx, value)
if err != nil {
return fmt.Errorf("failed to substitute variables in context entry %s %s: %v", entry.Name, entry.Variable.GetValue(), err)
}
if path != "" {
variable, err := applyJMESPath(vl.jp, path, variable)
if err == nil {
output = variable
} else if defaultValue == nil {
return fmt.Errorf("failed to apply jmespath %s to variable %v: %v", path, variable, err)
}
} else {
output = variable
}
} else {
if path != "" {
if variable, err := ctx.Query(path); err == nil {
if variable != nil {
output = variable
}
} else if defaultValue == nil {
return fmt.Errorf("failed to apply jmespath %s to variable %v: %v", path, variable, err)
}
}
}
logger.V(4).Info("evaluated output", "variable name", entry.Name, "output", output)
if output == nil {
return fmt.Errorf("failed to add context entry for variable %s since it evaluated to nil", entry.Name)
}
vl.data, err = json.Marshal(output)
if err != nil {
return fmt.Errorf("failed to add context entry for variable %s: %v", entry.Name, err)
}
return ctx.ReplaceContextEntry(entry.Name, vl.data)
}
func applyJMESPath(jp jmespath.Interface, query string, data interface{}) (interface{}, error) {
q, err := jp.Query(query)
if err != nil {
return nil, fmt.Errorf("failed to compile JMESPath: %s, error: %v", query, err)
}
return q.Search(data)
}