mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-15 12:17:56 +00:00
Fix deferred loading (#7597)
* handle nested contexts Signed-off-by: Jim Bugwadia <jim@nirmata.com> * add feature flag Signed-off-by: Jim Bugwadia <jim@nirmata.com> * fix tests Signed-off-by: Jim Bugwadia <jim@nirmata.com> * add kuttl tests Signed-off-by: Jim Bugwadia <jim@nirmata.com> * fix linter issues Signed-off-by: Jim Bugwadia <jim@nirmata.com> * fix CLI regclient Signed-off-by: Jim Bugwadia <jim@nirmata.com> * fix: token permissions on report vulns workflow (#7611) Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * fix: token permissions (#7619) Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * fix: update the flag descriptions of the reports-controller (#7617) Signed-off-by: emmanuel-ferdman <emmanuelferdman@gmail.com> * fix: panic if env var not defined (#7613) * fix: panic if env var not defined Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * fix Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * fix Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> --------- Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * use toggles instead of a flag Signed-off-by: Jim Bugwadia <jim@nirmata.com> * update toggle name Signed-off-by: Jim Bugwadia <jim@nirmata.com> * update toggle name Signed-off-by: Jim Bugwadia <jim@nirmata.com> * fix roles Signed-off-by: Jim Bugwadia <jim@nirmata.com> * fix role Signed-off-by: Jim Bugwadia <jim@nirmata.com> * update manifests Signed-off-by: Jim Bugwadia <jim@nirmata.com> * remove extra unlock Signed-off-by: Jim Bugwadia <jim@nirmata.com> * fix loader reset Signed-off-by: Jim Bugwadia <jim@nirmata.com> * add tests Signed-off-by: Jim Bugwadia <jim@nirmata.com> * propagate context Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * cm resolver Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * level management Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * address review comments Signed-off-by: Jim Bugwadia <jim@nirmata.com> * add enableDeferredLoading to other controllers Signed-off-by: Jim Bugwadia <jim@nirmata.com> * re-enable ACR credhelper Signed-off-by: Jim Bugwadia <jim@nirmata.com> * improve tests Signed-off-by: Jim Bugwadia <jim@nirmata.com> * remove image registry client init Signed-off-by: Jim Bugwadia <jim@nirmata.com> * check for invalid reset/restore Signed-off-by: Jim Bugwadia <jim@nirmata.com> * recursive kuttl test Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * add pre/post queries Signed-off-by: Jim Bugwadia <jim@nirmata.com> * add check for a recursive match Signed-off-by: Jim Bugwadia <jim@nirmata.com> * new test suite Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * eval loaders at creation level Signed-off-by: Jim Bugwadia <jim@nirmata.com> * kuttl test Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * add an index for resolving deps in order Signed-off-by: Jim Bugwadia <jim@nirmata.com> * improve comment Signed-off-by: Jim Bugwadia <jim@nirmata.com> * extract remove method Signed-off-by: Jim Bugwadia <jim@nirmata.com> * merge main Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * flags Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * feature flag Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * fix flag Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * update unit tests Signed-off-by: ShutingZhao <shuting@nirmata.com> * two rules kuttl test Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * update unit tests Signed-off-by: ShutingZhao <shuting@nirmata.com> * revert Signed-off-by: ShutingZhao <shuting@nirmata.com> * per rule checkpoint Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * fix Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * fix mutate chained rules Signed-off-by: ShutingZhao <shuting@nirmata.com> * per rule checpoint/restore Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * log error Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> --------- Signed-off-by: Jim Bugwadia <jim@nirmata.com> Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> Signed-off-by: emmanuel-ferdman <emmanuelferdman@gmail.com> Signed-off-by: ShutingZhao <shuting@nirmata.com> Co-authored-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> Co-authored-by: Emmanuel Ferdman <emmanuelferdman@gmail.com> Co-authored-by: shuting <shuting@nirmata.com>
This commit is contained in:
parent
df5f54198d
commit
b98c0775f2
49 changed files with 1165 additions and 71 deletions
1
.github/workflows/conformance.yaml
vendored
1
.github/workflows/conformance.yaml
vendored
|
@ -58,6 +58,7 @@ jobs:
|
||||||
tests:
|
tests:
|
||||||
- autogen
|
- autogen
|
||||||
- cleanup
|
- cleanup
|
||||||
|
- deferred
|
||||||
- events
|
- events
|
||||||
- exceptions
|
- exceptions
|
||||||
- generate/clusterpolicy
|
- generate/clusterpolicy
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -653,7 +653,7 @@ test-cli-local: $(CLI_BIN)
|
||||||
|
|
||||||
.PHONY: test-cli-local-mutate
|
.PHONY: test-cli-local-mutate
|
||||||
test-cli-local-mutate: $(CLI_BIN)
|
test-cli-local-mutate: $(CLI_BIN)
|
||||||
# @$(CLI_BIN) test ./test/cli/test-mutate
|
@$(CLI_BIN) test ./test/cli/test-mutate
|
||||||
|
|
||||||
.PHONY: test-cli-local-generate
|
.PHONY: test-cli-local-generate
|
||||||
test-cli-local-generate: $(CLI_BIN)
|
test-cli-local-generate: $(CLI_BIN)
|
||||||
|
|
|
@ -113,17 +113,32 @@ func (h *handlers) lookupPolicy(namespace, name string) (kyvernov2alpha1.Cleanup
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handlers) executePolicy(ctx context.Context, logger logr.Logger, policy kyvernov2alpha1.CleanupPolicyInterface, cfg config.Configuration) error {
|
func (h *handlers) executePolicy(
|
||||||
|
ctx context.Context,
|
||||||
|
logger logr.Logger,
|
||||||
|
policy kyvernov2alpha1.CleanupPolicyInterface,
|
||||||
|
cfg config.Configuration,
|
||||||
|
) error {
|
||||||
spec := policy.GetSpec()
|
spec := policy.GetSpec()
|
||||||
kinds := sets.New(spec.MatchResources.GetKinds()...)
|
kinds := sets.New(spec.MatchResources.GetKinds()...)
|
||||||
debug := logger.V(4)
|
debug := logger.V(4)
|
||||||
var errs []error
|
var errs []error
|
||||||
|
|
||||||
enginectx := enginecontext.NewContext(h.jp)
|
enginectx := enginecontext.NewContext(h.jp)
|
||||||
factory := factories.DefaultContextLoaderFactory(h.cmResolver)
|
ctxFactory := factories.DefaultContextLoaderFactory(h.cmResolver)
|
||||||
loader := factory(nil, kyvernov1.Rule{})
|
|
||||||
if err := loader.Load(ctx, h.jp, h.client, nil, spec.Context, enginectx); err != nil {
|
loader := ctxFactory(nil, kyvernov1.Rule{})
|
||||||
|
if err := loader.Load(
|
||||||
|
ctx,
|
||||||
|
h.jp,
|
||||||
|
h.client,
|
||||||
|
nil,
|
||||||
|
spec.Context,
|
||||||
|
enginectx,
|
||||||
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for kind := range kinds {
|
for kind := range kinds {
|
||||||
commonLabels := []attribute.KeyValue{
|
commonLabels := []attribute.KeyValue{
|
||||||
attribute.String("policy_type", policy.GetKind()),
|
attribute.String("policy_type", policy.GetKind()),
|
||||||
|
|
|
@ -144,7 +144,7 @@ func KyvernoUserName(serviceaccount string) string {
|
||||||
type Configuration interface {
|
type Configuration interface {
|
||||||
// GetDefaultRegistry return default image registry
|
// GetDefaultRegistry return default image registry
|
||||||
GetDefaultRegistry() string
|
GetDefaultRegistry() string
|
||||||
// GetEnableDefaultRegistryMutation return if should mutate image registry
|
// GetEnableDefaultRegistryMutation returns true if image references should be mutated
|
||||||
GetEnableDefaultRegistryMutation() bool
|
GetEnableDefaultRegistryMutation() bool
|
||||||
// IsExcluded checks exlusions/inclusions to determine if the admission request should be excluded or not
|
// IsExcluded checks exlusions/inclusions to determine if the admission request should be excluded or not
|
||||||
IsExcluded(username string, groups []string, roles []string, clusterroles []string) bool
|
IsExcluded(username string, groups []string, roles []string, clusterroles []string) bool
|
||||||
|
|
|
@ -130,6 +130,7 @@ func Test_namespacedResourceResolverChain_Get(t *testing.T) {
|
||||||
if !checkError(tt.wantErr, err) {
|
if !checkError(tt.wantErr, err) {
|
||||||
t.Errorf("ConfigmapResolver.Get() %s error = %v, wantErr %v", tt.name, err, tt.wantErr)
|
t.Errorf("ConfigmapResolver.Get() %s error = %v, wantErr %v", tt.name, err, tt.wantErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(got, tt.wantCm) {
|
if !reflect.DeepEqual(got, tt.wantCm) {
|
||||||
t.Errorf("ConfigmapResolver.Get() = %v, want %v", got, tt.wantCm)
|
t.Errorf("ConfigmapResolver.Get() = %v, want %v", got, tt.wantCm)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
var logger = logging.WithName("context")
|
var logger = logging.WithName("context")
|
||||||
|
|
||||||
// EvalInterface is used to query and inspect context data
|
// EvalInterface is used to query and inspect context data
|
||||||
|
// TODO: move to contextapi to prevent circular dependencies
|
||||||
type EvalInterface interface {
|
type EvalInterface interface {
|
||||||
// Query accepts a JMESPath expression and returns matching data
|
// Query accepts a JMESPath expression and returns matching data
|
||||||
Query(query string) (interface{}, error)
|
Query(query string) (interface{}, error)
|
||||||
|
@ -32,6 +33,7 @@ type EvalInterface interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interface to manage context operations
|
// Interface to manage context operations
|
||||||
|
// TODO: move to contextapi to prevent circular dependencies
|
||||||
type Interface interface {
|
type Interface interface {
|
||||||
// AddRequest marshals and adds the admission request to the context
|
// AddRequest marshals and adds the admission request to the context
|
||||||
AddRequest(request admissionv1.AdmissionRequest) error
|
AddRequest(request admissionv1.AdmissionRequest) error
|
||||||
|
@ -76,7 +78,8 @@ type Interface interface {
|
||||||
AddImageInfos(resource *unstructured.Unstructured, cfg config.Configuration) error
|
AddImageInfos(resource *unstructured.Unstructured, cfg config.Configuration) error
|
||||||
|
|
||||||
// AddDeferredLoader adds a loader that is executed on first use (query)
|
// AddDeferredLoader adds a loader that is executed on first use (query)
|
||||||
AddDeferredLoader(name string, loader Loader) error
|
// If deferred loading is disabled the loader is immediately executed.
|
||||||
|
AddDeferredLoader(loader DeferredLoader) error
|
||||||
|
|
||||||
// ImageInfo returns image infos present in the context
|
// ImageInfo returns image infos present in the context
|
||||||
ImageInfo() map[string]map[string]apiutils.ImageInfo
|
ImageInfo() map[string]map[string]apiutils.ImageInfo
|
||||||
|
@ -107,12 +110,7 @@ type context struct {
|
||||||
jsonRaw []byte
|
jsonRaw []byte
|
||||||
jsonRawCheckpoints [][]byte
|
jsonRawCheckpoints [][]byte
|
||||||
images map[string]map[string]apiutils.ImageInfo
|
images map[string]map[string]apiutils.ImageInfo
|
||||||
deferred deferredLoaders
|
deferred DeferredLoaders
|
||||||
}
|
|
||||||
|
|
||||||
type deferredLoaders struct {
|
|
||||||
mutex sync.Mutex
|
|
||||||
loaders map[string]Loader
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewContext returns a new context
|
// NewContext returns a new context
|
||||||
|
@ -126,9 +124,7 @@ func NewContextFromRaw(jp jmespath.Interface, raw []byte) Interface {
|
||||||
jp: jp,
|
jp: jp,
|
||||||
jsonRaw: raw,
|
jsonRaw: raw,
|
||||||
jsonRawCheckpoints: make([][]byte, 0),
|
jsonRawCheckpoints: make([][]byte, 0),
|
||||||
deferred: deferredLoaders{
|
deferred: NewDeferredLoaders(),
|
||||||
loaders: make(map[string]Loader),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -335,24 +331,32 @@ func (ctx *context) Reset() {
|
||||||
ctx.reset(false)
|
ctx.reset(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *context) reset(remove bool) {
|
func (ctx *context) reset(restore bool) {
|
||||||
|
if ctx.resetCheckpoint(restore) {
|
||||||
|
ctx.deferred.Reset(restore, len(ctx.jsonRawCheckpoints))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *context) resetCheckpoint(removeCheckpoint bool) bool {
|
||||||
ctx.mutex.Lock()
|
ctx.mutex.Lock()
|
||||||
defer ctx.mutex.Unlock()
|
defer ctx.mutex.Unlock()
|
||||||
|
|
||||||
if len(ctx.jsonRawCheckpoints) == 0 {
|
if len(ctx.jsonRawCheckpoints) == 0 {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
n := len(ctx.jsonRawCheckpoints) - 1
|
n := len(ctx.jsonRawCheckpoints) - 1
|
||||||
jsonRawCheckpoint := ctx.jsonRawCheckpoints[n]
|
jsonRawCheckpoint := ctx.jsonRawCheckpoints[n]
|
||||||
ctx.jsonRaw = make([]byte, len(jsonRawCheckpoint))
|
ctx.jsonRaw = make([]byte, len(jsonRawCheckpoint))
|
||||||
copy(ctx.jsonRaw, jsonRawCheckpoint)
|
copy(ctx.jsonRaw, jsonRawCheckpoint)
|
||||||
if remove {
|
if removeCheckpoint {
|
||||||
ctx.jsonRawCheckpoints = ctx.jsonRawCheckpoints[:n]
|
ctx.jsonRawCheckpoints = ctx.jsonRawCheckpoints[:n]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *context) AddDeferredLoader(name string, loader Loader) error {
|
func (ctx *context) AddDeferredLoader(dl DeferredLoader) error {
|
||||||
ctx.deferred.mutex.Lock()
|
ctx.deferred.Add(dl, len(ctx.jsonRawCheckpoints))
|
||||||
defer ctx.deferred.mutex.Unlock()
|
|
||||||
ctx.deferred.loaders[name] = loader
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
177
pkg/engine/context/deferred.go
Normal file
177
pkg/engine/context/deferred.go
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type deferredLoader struct {
|
||||||
|
name string
|
||||||
|
matcher regexp.Regexp
|
||||||
|
loader Loader
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDeferredLoader(name string, loader Loader) (DeferredLoader, error) {
|
||||||
|
// match on ASCII word boundaries except do not allow starting with a `.`
|
||||||
|
// this allows `x` to match `x.y` but not `y.x` or `y.x.z`
|
||||||
|
matcher, err := regexp.Compile(`(?:\A|\z|\s|[^.0-9A-Za-z])` + name + `\b`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &deferredLoader{
|
||||||
|
name: name,
|
||||||
|
matcher: *matcher,
|
||||||
|
loader: loader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dl *deferredLoader) Name() string {
|
||||||
|
return dl.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dl *deferredLoader) HasLoaded() bool {
|
||||||
|
return dl.loader.HasLoaded()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dl *deferredLoader) LoadData() error {
|
||||||
|
return dl.loader.LoadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *deferredLoader) Matches(query string) bool {
|
||||||
|
return d.matcher.MatchString(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
type leveledLoader struct {
|
||||||
|
level int
|
||||||
|
matched bool
|
||||||
|
loader DeferredLoader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *leveledLoader) Level() int {
|
||||||
|
return cl.level
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *leveledLoader) Name() string {
|
||||||
|
return cl.loader.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *leveledLoader) Matches(query string) bool {
|
||||||
|
return cl.loader.Matches(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *leveledLoader) HasLoaded() bool {
|
||||||
|
return cl.loader.HasLoaded()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *leveledLoader) LoadData() error {
|
||||||
|
return cl.loader.LoadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
type deferredLoaders struct {
|
||||||
|
level int
|
||||||
|
index int
|
||||||
|
loaders []*leveledLoader
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDeferredLoaders() DeferredLoaders {
|
||||||
|
return &deferredLoaders{
|
||||||
|
loaders: make([]*leveledLoader, 0),
|
||||||
|
level: -1,
|
||||||
|
index: -1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *deferredLoaders) Add(dl DeferredLoader, level int) {
|
||||||
|
d.loaders = append(d.loaders, &leveledLoader{level, false, dl})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *deferredLoaders) Reset(restore bool, level int) {
|
||||||
|
d.clearMatches()
|
||||||
|
for i := 0; i < len(d.loaders); i++ {
|
||||||
|
l := d.loaders[i]
|
||||||
|
if l.level > level {
|
||||||
|
i = d.removeLoader(i)
|
||||||
|
} else {
|
||||||
|
if l.loader.HasLoaded() {
|
||||||
|
// reload data into the current context for restore, and
|
||||||
|
// for a reset but only if loader is at a prior level
|
||||||
|
if restore || (l.level < level) {
|
||||||
|
if err := d.loadData(l, i); err != nil {
|
||||||
|
logger.Error(err, "failed to reload context entry", "name", l.loader.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if l.level == level {
|
||||||
|
i = d.removeLoader(i)
|
||||||
|
}
|
||||||
|
} else if !restore {
|
||||||
|
if l.level == level {
|
||||||
|
i = d.removeLoader(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeLoader removes loader at the specified index
|
||||||
|
// and returns the prior index
|
||||||
|
func (d *deferredLoaders) removeLoader(i int) int {
|
||||||
|
d.loaders = append(d.loaders[:i], d.loaders[i+1:]...)
|
||||||
|
return i - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *deferredLoaders) clearMatches() {
|
||||||
|
for _, dl := range d.loaders {
|
||||||
|
dl.matched = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *deferredLoaders) LoadMatching(query string, level int) error {
|
||||||
|
if d.level >= 0 {
|
||||||
|
level = d.level
|
||||||
|
}
|
||||||
|
|
||||||
|
index := len(d.loaders)
|
||||||
|
if d.index >= 0 {
|
||||||
|
index = d.index
|
||||||
|
}
|
||||||
|
|
||||||
|
for l, idx := d.match(query, level, index); l != nil; l, idx = d.match(query, level, index) {
|
||||||
|
if err := d.loadData(l, idx); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *deferredLoaders) loadData(l *leveledLoader, index int) error {
|
||||||
|
d.setLevelAndIndex(l.level, index)
|
||||||
|
defer d.setLevelAndIndex(-1, -1)
|
||||||
|
if err := l.LoadData(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *deferredLoaders) setLevelAndIndex(level, index int) {
|
||||||
|
d.level = level
|
||||||
|
d.index = index
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *deferredLoaders) match(query string, level, index int) (*leveledLoader, int) {
|
||||||
|
for i := 0; i < index; i++ {
|
||||||
|
dl := d.loaders[i]
|
||||||
|
if dl.matched || dl.loader.HasLoaded() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if dl.Matches(query) && dl.level <= level {
|
||||||
|
idx := i
|
||||||
|
d.loaders[i].matched = true
|
||||||
|
return dl, idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, -1
|
||||||
|
}
|
443
pkg/engine/context/deferred_test.go
Normal file
443
pkg/engine/context/deferred_test.go
Normal file
|
@ -0,0 +1,443 @@
|
||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gotest.tools/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDeferredLoaderMatch(t *testing.T) {
|
||||||
|
ctx := newContext()
|
||||||
|
mockLoader, _ := addDeferred(ctx, "one", "1")
|
||||||
|
assert.Equal(t, 0, mockLoader.invocations)
|
||||||
|
|
||||||
|
val, err := ctx.Query("one")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, "1", val)
|
||||||
|
assert.Equal(t, 1, mockLoader.invocations)
|
||||||
|
|
||||||
|
_, _ = ctx.Query("one")
|
||||||
|
assert.Equal(t, 1, mockLoader.invocations)
|
||||||
|
|
||||||
|
ctx = newContext()
|
||||||
|
ml, _ := addDeferred(ctx, "one", "1")
|
||||||
|
testCheckMatch(t, ctx, "one<two", "one", "1", ml)
|
||||||
|
|
||||||
|
ml, _ = addDeferred(ctx, "one", "1")
|
||||||
|
testCheckMatch(t, ctx, "(one)", "one", "1", ml)
|
||||||
|
|
||||||
|
ml, _ = addDeferred(ctx, "one", "1")
|
||||||
|
testCheckMatch(t, ctx, "one.two.three", "one", "1", ml)
|
||||||
|
|
||||||
|
ml, _ = addDeferred(ctx, "one", "1")
|
||||||
|
testCheckMatch(t, ctx, "one-two", "one", "1", ml)
|
||||||
|
|
||||||
|
ml, _ = addDeferred(ctx, "one", "1")
|
||||||
|
testCheckMatch(t, ctx, "one; two; three", "one", "1", ml)
|
||||||
|
|
||||||
|
ml, _ = addDeferred(ctx, "one", "1")
|
||||||
|
testCheckMatch(t, ctx, "one>two", "one", "1", ml)
|
||||||
|
|
||||||
|
ml, _ = addDeferred(ctx, "one", "1")
|
||||||
|
testCheckMatch(t, ctx, "one, two, three", "one", "1", ml)
|
||||||
|
|
||||||
|
ml, _ = addDeferred(ctx, "one1", "11")
|
||||||
|
testCheckMatch(t, ctx, "one1", "one1", "11", ml)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCheckMatch(t *testing.T, ctx *context, query, name, value string, ml *mockLoader) {
|
||||||
|
var events []string
|
||||||
|
hdlr := func(name string) {
|
||||||
|
events = append(events, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
ml.setEventHandler(hdlr)
|
||||||
|
|
||||||
|
err := ctx.deferred.LoadMatching(query, len(ctx.jsonRawCheckpoints))
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, 1, len(events), "deferred loader %s not executed for query %s", name, query)
|
||||||
|
expected := fmt.Sprintf("%s=%s", name, value)
|
||||||
|
assert.Equal(t, expected, events[0], "deferred loader %s name mismatch for query %s; received %s", name, query, events[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeferredLoaderMismatch(t *testing.T) {
|
||||||
|
ctx := newContext()
|
||||||
|
addDeferred(ctx, "one", "1")
|
||||||
|
|
||||||
|
_, err := ctx.Query("oneTwoThree")
|
||||||
|
assert.ErrorContains(t, err, `Unknown key "oneTwoThree" in path`)
|
||||||
|
|
||||||
|
_, err = ctx.Query("one1")
|
||||||
|
assert.ErrorContains(t, err, `Unknown key "one1" in path`)
|
||||||
|
|
||||||
|
_, err = ctx.Query("one_two")
|
||||||
|
assert.ErrorContains(t, err, `Unknown key "one_two" in path`)
|
||||||
|
|
||||||
|
_, err = ctx.Query("\"one-two\"")
|
||||||
|
assert.ErrorContains(t, err, `Unknown key "one-two" in path`)
|
||||||
|
|
||||||
|
ctx.AddVariable("two.one", "0")
|
||||||
|
val, err := ctx.Query("two.one")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, "0", val)
|
||||||
|
|
||||||
|
val, err = ctx.Query("one.two.three")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, nil, val)
|
||||||
|
|
||||||
|
val, err = ctx.Query("one")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, "1", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newContext() *context {
|
||||||
|
return &context{
|
||||||
|
jp: jp,
|
||||||
|
jsonRaw: []byte(`{}`),
|
||||||
|
jsonRawCheckpoints: make([][]byte, 0),
|
||||||
|
deferred: NewDeferredLoaders(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockLoader struct {
|
||||||
|
name string
|
||||||
|
level int
|
||||||
|
value interface{}
|
||||||
|
query string
|
||||||
|
hasLoaded bool
|
||||||
|
invocations int
|
||||||
|
eventHandler func(event string)
|
||||||
|
ctx *context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ml *mockLoader) Name() string {
|
||||||
|
return ml.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ml *mockLoader) SetLevel(level int) {
|
||||||
|
ml.level = level
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ml *mockLoader) GetLevel() int {
|
||||||
|
return ml.level
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ml *mockLoader) HasLoaded() bool {
|
||||||
|
return ml.hasLoaded
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ml *mockLoader) LoadData() error {
|
||||||
|
ml.invocations++
|
||||||
|
ml.ctx.AddVariable(ml.name, ml.value)
|
||||||
|
|
||||||
|
// simulate a JMESPath evaluation after loading
|
||||||
|
if err := ml.executeQuery(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ml.hasLoaded = true
|
||||||
|
if ml.eventHandler != nil {
|
||||||
|
event := fmt.Sprintf("%s=%v", ml.name, ml.value)
|
||||||
|
ml.eventHandler(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ml *mockLoader) executeQuery() error {
|
||||||
|
if ml.query == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
results, err := ml.ctx.Query(ml.query)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ml.ctx.AddVariable(ml.name, results)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ml *mockLoader) setEventHandler(eventHandler func(string)) {
|
||||||
|
ml.eventHandler = eventHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func addDeferred(ctx *context, name string, value interface{}) (*mockLoader, error) {
|
||||||
|
return addDeferredWithQuery(ctx, name, value, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func addDeferredWithQuery(ctx *context, name string, value interface{}, query string) (*mockLoader, error) {
|
||||||
|
loader := &mockLoader{
|
||||||
|
name: name,
|
||||||
|
value: value,
|
||||||
|
ctx: ctx,
|
||||||
|
query: query,
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := NewDeferredLoader(name, loader)
|
||||||
|
if err != nil {
|
||||||
|
return loader, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.AddDeferredLoader(d)
|
||||||
|
return loader, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeferredReset(t *testing.T) {
|
||||||
|
ctx := newContext()
|
||||||
|
addDeferred(ctx, "value", "0")
|
||||||
|
|
||||||
|
ctx.Checkpoint()
|
||||||
|
val, err := ctx.Query("value")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, "0", val)
|
||||||
|
ctx.Reset()
|
||||||
|
|
||||||
|
val, err = ctx.Query("value")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, "0", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeferredCheckpointRestore(t *testing.T) {
|
||||||
|
ctx := newContext()
|
||||||
|
|
||||||
|
ctx.Checkpoint()
|
||||||
|
unused, _ := addDeferred(ctx, "unused", "unused")
|
||||||
|
mock, _ := addDeferred(ctx, "one", "1")
|
||||||
|
ctx.Restore()
|
||||||
|
assert.Equal(t, 0, mock.invocations)
|
||||||
|
assert.Equal(t, 0, unused.invocations)
|
||||||
|
|
||||||
|
err := ctx.deferred.LoadMatching("unused", len(ctx.jsonRawCheckpoints))
|
||||||
|
assert.NilError(t, err)
|
||||||
|
_, err = ctx.Query("unused")
|
||||||
|
assert.ErrorContains(t, err, "Unknown key \"unused\" in path")
|
||||||
|
|
||||||
|
err = ctx.deferred.LoadMatching("one", len(ctx.jsonRawCheckpoints))
|
||||||
|
assert.NilError(t, err)
|
||||||
|
_, err = ctx.Query("one")
|
||||||
|
assert.ErrorContains(t, err, "Unknown key \"one\" in path")
|
||||||
|
|
||||||
|
_, _ = addDeferred(ctx, "one", "1")
|
||||||
|
ctx.Checkpoint()
|
||||||
|
one, err := ctx.Query("one")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, "1", one)
|
||||||
|
|
||||||
|
ctx.Restore()
|
||||||
|
_, err = ctx.Query("one")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, "1", one)
|
||||||
|
|
||||||
|
ctx.Restore()
|
||||||
|
_, err = ctx.Query("one")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, "1", one)
|
||||||
|
|
||||||
|
mock, _ = addDeferred(ctx, "one", "1")
|
||||||
|
ctx.Checkpoint()
|
||||||
|
val, err := ctx.Query("one")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, "1", val)
|
||||||
|
assert.Equal(t, 1, mock.invocations)
|
||||||
|
|
||||||
|
mock2, _ := addDeferred(ctx, "two", "2")
|
||||||
|
val, err = ctx.Query("two")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, "2", val)
|
||||||
|
assert.Equal(t, 1, mock2.invocations)
|
||||||
|
|
||||||
|
ctx.Restore()
|
||||||
|
val, err = ctx.Query("one")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, "1", val)
|
||||||
|
assert.Equal(t, 2, mock.invocations)
|
||||||
|
|
||||||
|
_, _ = ctx.Query("one")
|
||||||
|
assert.Equal(t, 2, mock.invocations)
|
||||||
|
|
||||||
|
_, err = ctx.Query("two")
|
||||||
|
assert.ErrorContains(t, err, `Unknown key "two" in path`)
|
||||||
|
|
||||||
|
ctx.Checkpoint()
|
||||||
|
val, err = ctx.Query("one")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, "1", val)
|
||||||
|
assert.Equal(t, 2, mock.invocations)
|
||||||
|
|
||||||
|
_, err = ctx.Query("two")
|
||||||
|
assert.ErrorContains(t, err, `Unknown key "two" in path`)
|
||||||
|
|
||||||
|
mock3, _ := addDeferred(ctx, "three", "3")
|
||||||
|
val, err = ctx.Query("three")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, "3", val)
|
||||||
|
assert.Equal(t, 1, mock3.invocations)
|
||||||
|
|
||||||
|
ctx.Reset()
|
||||||
|
val, err = ctx.Query("one")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, "1", val)
|
||||||
|
assert.Equal(t, 2, mock.invocations)
|
||||||
|
|
||||||
|
_, err = ctx.Query("two")
|
||||||
|
assert.ErrorContains(t, err, `Unknown key "two" in path`)
|
||||||
|
|
||||||
|
_, err = ctx.Query("three")
|
||||||
|
assert.ErrorContains(t, err, `Unknown key "three" in path`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeferredForloop(t *testing.T) {
|
||||||
|
ctx := newContext()
|
||||||
|
addDeferred(ctx, "value", -1)
|
||||||
|
|
||||||
|
ctx.Checkpoint()
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
val, err := ctx.Query("value")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, float64(i-1), val)
|
||||||
|
|
||||||
|
ctx.Reset()
|
||||||
|
mock, _ := addDeferred(ctx, "value", i)
|
||||||
|
val, err = ctx.Query("value")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, float64(i), val)
|
||||||
|
assert.Equal(t, 1, mock.invocations)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Restore()
|
||||||
|
val, err := ctx.Query("value")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, float64(-1), val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeferredInvalidReset(t *testing.T) {
|
||||||
|
ctx := newContext()
|
||||||
|
|
||||||
|
addDeferred(ctx, "value", "0")
|
||||||
|
ctx.Reset() // no checkpoint
|
||||||
|
val, err := ctx.Query("value")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, "0", val)
|
||||||
|
|
||||||
|
addDeferred(ctx, "value", "0")
|
||||||
|
ctx.Restore() // no checkpoint
|
||||||
|
val, err = ctx.Query("value")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, "0", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeferredValidResetRestore(t *testing.T) {
|
||||||
|
ctx := newContext()
|
||||||
|
addDeferred(ctx, "value", "0")
|
||||||
|
|
||||||
|
ctx.Checkpoint()
|
||||||
|
addDeferred(ctx, "leak", "leak")
|
||||||
|
ctx.Reset()
|
||||||
|
|
||||||
|
_, err := ctx.Query("leak")
|
||||||
|
assert.ErrorContains(t, err, `Unknown key "leak" in path`)
|
||||||
|
|
||||||
|
addDeferred(ctx, "value", "0")
|
||||||
|
ctx.Checkpoint()
|
||||||
|
addDeferred(ctx, "leak", "leak")
|
||||||
|
ctx.Restore()
|
||||||
|
|
||||||
|
_, err = ctx.Query("leak")
|
||||||
|
assert.ErrorContains(t, err, `Unknown key "leak" in path`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeferredSameName(t *testing.T) {
|
||||||
|
ctx := newContext()
|
||||||
|
var sequence []string
|
||||||
|
hdlr := func(name string) {
|
||||||
|
sequence = append(sequence, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
mock1, _ := addDeferred(ctx, "value", "0")
|
||||||
|
mock1.setEventHandler(hdlr)
|
||||||
|
|
||||||
|
mock2, _ := addDeferred(ctx, "value", "1")
|
||||||
|
mock2.setEventHandler(hdlr)
|
||||||
|
|
||||||
|
val, err := ctx.Query("value")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, "1", val)
|
||||||
|
|
||||||
|
assert.Equal(t, 1, mock1.invocations)
|
||||||
|
assert.Equal(t, 1, mock2.invocations)
|
||||||
|
assert.Equal(t, 2, len(sequence))
|
||||||
|
assert.Equal(t, sequence[0], "value=0")
|
||||||
|
assert.Equal(t, sequence[1], "value=1")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeferredRecursive(t *testing.T) {
|
||||||
|
ctx := newContext()
|
||||||
|
addDeferredWithQuery(ctx, "value", "0", "value")
|
||||||
|
ctx.Checkpoint()
|
||||||
|
val, err := ctx.Query("value")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, "0", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJMESPathDependency(t *testing.T) {
|
||||||
|
ctx := newContext()
|
||||||
|
addDeferred(ctx, "foo", "foo")
|
||||||
|
addDeferredWithQuery(ctx, "one", "1", "foo")
|
||||||
|
|
||||||
|
val, err := ctx.Query("one")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, "foo", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeferredHiddenEval(t *testing.T) {
|
||||||
|
ctx := newContext()
|
||||||
|
addDeferred(ctx, "foo", "foo")
|
||||||
|
|
||||||
|
ctx.Checkpoint()
|
||||||
|
addDeferred(ctx, "foo", "bar")
|
||||||
|
|
||||||
|
val, err := ctx.Query("foo")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, "bar", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeferredNotHidden(t *testing.T) {
|
||||||
|
ctx := newContext()
|
||||||
|
addDeferred(ctx, "foo", "foo")
|
||||||
|
addDeferredWithQuery(ctx, "one", "1", "foo")
|
||||||
|
|
||||||
|
ctx.Checkpoint()
|
||||||
|
addDeferred(ctx, "foo", "bar")
|
||||||
|
|
||||||
|
val, err := ctx.Query("one")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, "foo", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeferredNotHiddenOrdered(t *testing.T) {
|
||||||
|
ctx := newContext()
|
||||||
|
addDeferred(ctx, "foo", "foo")
|
||||||
|
addDeferredWithQuery(ctx, "one", "1", "foo")
|
||||||
|
addDeferred(ctx, "foo", "baz")
|
||||||
|
|
||||||
|
ctx.Checkpoint()
|
||||||
|
addDeferred(ctx, "foo", "bar")
|
||||||
|
val, err := ctx.Query("one")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, "foo", val)
|
||||||
|
|
||||||
|
val, err = ctx.Query("foo")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, "bar", val)
|
||||||
|
|
||||||
|
ctx.Restore()
|
||||||
|
|
||||||
|
val, err = ctx.Query("one")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, "foo", val)
|
||||||
|
|
||||||
|
val, err = ctx.Query("foo")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, "baz", val)
|
||||||
|
}
|
|
@ -39,35 +39,8 @@ func (ctx *context) Query(query string) (interface{}, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *context) loadDeferred(query string) error {
|
func (ctx *context) loadDeferred(query string) error {
|
||||||
loaders := ctx.getMatchingLoaders(query)
|
level := len(ctx.jsonRawCheckpoints)
|
||||||
for _, loader := range loaders {
|
return ctx.deferred.LoadMatching(query, level)
|
||||||
err := ctx.evaluateLoader(loader)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *context) getMatchingLoaders(query string) []string {
|
|
||||||
ctx.deferred.mutex.Lock()
|
|
||||||
defer ctx.deferred.mutex.Unlock()
|
|
||||||
var matchingLoaders []string
|
|
||||||
for name := range ctx.deferred.loaders {
|
|
||||||
if strings.Contains(query, name) {
|
|
||||||
matchingLoaders = append(matchingLoaders, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return matchingLoaders
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *context) evaluateLoader(name string) error {
|
|
||||||
loader, ok := ctx.deferred.loaders[name]
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
delete(ctx.deferred.loaders, name)
|
|
||||||
return loader.LoadData()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *context) HasChanged(jmespath string) (bool, error) {
|
func (ctx *context) HasChanged(jmespath string) (bool, error) {
|
||||||
|
|
|
@ -14,3 +14,30 @@ type Loader interface {
|
||||||
// executed and stored data in a context
|
// executed and stored data in a context
|
||||||
HasLoaded() bool
|
HasLoaded() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeferredLoader wraps a Loader and implements context specific behaviors.
|
||||||
|
// A `level` is used to track the checkpoint level at which the loader was
|
||||||
|
// created. If the level when loading occurs matches the loader's creation
|
||||||
|
// level, the loader is discarded after execution. Otherwise, the loader is
|
||||||
|
// retained so that it can be applied to the prior level when the checkpoint
|
||||||
|
// is restored or reset.
|
||||||
|
type DeferredLoader interface {
|
||||||
|
Name() string
|
||||||
|
Matches(query string) bool
|
||||||
|
HasLoaded() bool
|
||||||
|
LoadData() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// LeveledLoader is a DeferredLoader with a Level
|
||||||
|
type LeveledLoader interface {
|
||||||
|
// Level provides the declaration level for the DeferredLoader
|
||||||
|
Level() int
|
||||||
|
DeferredLoader
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeferredLoaders manages a list of DeferredLoader instances
|
||||||
|
type DeferredLoaders interface {
|
||||||
|
Add(loader DeferredLoader, level int)
|
||||||
|
LoadMatching(query string, level int) error
|
||||||
|
Reset(removeCheckpoint bool, level int)
|
||||||
|
}
|
||||||
|
|
|
@ -240,7 +240,7 @@ func (e *engine) invokeRuleHandler(
|
||||||
ctx,
|
ctx,
|
||||||
"pkg/engine",
|
"pkg/engine",
|
||||||
fmt.Sprintf("RULE %s", rule.Name),
|
fmt.Sprintf("RULE %s", rule.Name),
|
||||||
func(ctx context.Context, span trace.Span) (unstructured.Unstructured, []engineapi.RuleResponse) {
|
func(ctx context.Context, span trace.Span) (patchedResource unstructured.Unstructured, results []engineapi.RuleResponse) {
|
||||||
// check if resource and rule match
|
// check if resource and rule match
|
||||||
if err := e.matches(rule, policyContext, resource); err != nil {
|
if err := e.matches(rule, policyContext, resource); err != nil {
|
||||||
logger.V(4).Info("rule not matched", "reason", err.Error())
|
logger.V(4).Info("rule not matched", "reason", err.Error())
|
||||||
|
@ -255,6 +255,15 @@ func (e *engine) invokeRuleHandler(
|
||||||
if ruleResp := e.hasPolicyExceptions(logger, ruleType, policyContext, rule); ruleResp != nil {
|
if ruleResp := e.hasPolicyExceptions(logger, ruleType, policyContext, rule); ruleResp != nil {
|
||||||
return resource, handlers.WithResponses(ruleResp)
|
return resource, handlers.WithResponses(ruleResp)
|
||||||
}
|
}
|
||||||
|
policyContext.JSONContext().Checkpoint()
|
||||||
|
defer func() {
|
||||||
|
policyContext.JSONContext().Restore()
|
||||||
|
if patchedResource.Object != nil {
|
||||||
|
if err := policyContext.JSONContext().AddResource(patchedResource.Object); err != nil {
|
||||||
|
logger.Error(err, "failed to add resource in the json context")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
// load rule context
|
// load rule context
|
||||||
contextLoader := e.ContextLoader(policyContext.Policy(), rule)
|
contextLoader := e.ContextLoader(policyContext.Policy(), rule)
|
||||||
if err := contextLoader(ctx, rule.Context, policyContext.JSONContext()); err != nil {
|
if err := contextLoader(ctx, rule.Context, policyContext.JSONContext()); err != nil {
|
||||||
|
|
|
@ -61,11 +61,13 @@ func (l *contextLoader) Load(
|
||||||
}
|
}
|
||||||
if loader != nil {
|
if loader != nil {
|
||||||
if toggle.FromContext(ctx).EnableDeferredLoading() {
|
if toggle.FromContext(ctx).EnableDeferredLoading() {
|
||||||
if err := jsonContext.AddDeferredLoader(entry.Name, loader); err != nil {
|
if err := jsonContext.AddDeferredLoader(loader); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return loader.LoadData()
|
if err := loader.LoadData(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,11 +81,11 @@ func (l *contextLoader) newLoader(
|
||||||
rclientFactory engineapi.RegistryClientFactory,
|
rclientFactory engineapi.RegistryClientFactory,
|
||||||
entry kyvernov1.ContextEntry,
|
entry kyvernov1.ContextEntry,
|
||||||
jsonContext enginecontext.Interface,
|
jsonContext enginecontext.Interface,
|
||||||
) (enginecontext.Loader, error) {
|
) (enginecontext.DeferredLoader, error) {
|
||||||
if entry.ConfigMap != nil {
|
if entry.ConfigMap != nil {
|
||||||
if l.cmResolver != nil {
|
if l.cmResolver != nil {
|
||||||
l := loaders.NewConfigMapLoader(ctx, l.logger, entry, l.cmResolver, jsonContext)
|
l := loaders.NewConfigMapLoader(ctx, l.logger, entry, l.cmResolver, jsonContext)
|
||||||
return l, nil
|
return enginecontext.NewDeferredLoader(entry.Name, l)
|
||||||
} else {
|
} else {
|
||||||
l.logger.Info("disabled loading of ConfigMap context entry %s", entry.Name)
|
l.logger.Info("disabled loading of ConfigMap context entry %s", entry.Name)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -91,7 +93,7 @@ func (l *contextLoader) newLoader(
|
||||||
} else if entry.APICall != nil {
|
} else if entry.APICall != nil {
|
||||||
if client != nil {
|
if client != nil {
|
||||||
l := loaders.NewAPILoader(ctx, l.logger, entry, jsonContext, jp, client)
|
l := loaders.NewAPILoader(ctx, l.logger, entry, jsonContext, jp, client)
|
||||||
return l, nil
|
return enginecontext.NewDeferredLoader(entry.Name, l)
|
||||||
} else {
|
} else {
|
||||||
l.logger.Info("disabled loading of APICall context entry %s", entry.Name)
|
l.logger.Info("disabled loading of APICall context entry %s", entry.Name)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -99,14 +101,14 @@ func (l *contextLoader) newLoader(
|
||||||
} else if entry.ImageRegistry != nil {
|
} else if entry.ImageRegistry != nil {
|
||||||
if rclientFactory != nil {
|
if rclientFactory != nil {
|
||||||
l := loaders.NewImageDataLoader(ctx, l.logger, entry, jsonContext, jp, rclientFactory)
|
l := loaders.NewImageDataLoader(ctx, l.logger, entry, jsonContext, jp, rclientFactory)
|
||||||
return l, nil
|
return enginecontext.NewDeferredLoader(entry.Name, l)
|
||||||
} else {
|
} else {
|
||||||
l.logger.Info("disabled loading of ImageRegistry context entry %s", entry.Name)
|
l.logger.Info("disabled loading of ImageRegistry context entry %s", entry.Name)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
} else if entry.Variable != nil {
|
} else if entry.Variable != nil {
|
||||||
l := loaders.NewVariableLoader(l.logger, entry, jsonContext, jp)
|
l := loaders.NewVariableLoader(l.logger, entry, jsonContext, jp)
|
||||||
return l, nil
|
return enginecontext.NewDeferredLoader(entry.Name, l)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("missing ConfigMap|APICall|ImageRegistry|Variable in context entry %s", entry.Name)
|
return nil, fmt.Errorf("missing ConfigMap|APICall|ImageRegistry|Variable in context entry %s", entry.Name)
|
||||||
}
|
}
|
||||||
|
|
|
@ -269,6 +269,7 @@ func Test_variableSubstitutionCLI(t *testing.T) {
|
||||||
policyContext,
|
policyContext,
|
||||||
ctxLoaderFactory,
|
ctxLoaderFactory,
|
||||||
)
|
)
|
||||||
|
|
||||||
require.Equal(t, 1, len(er.PolicyResponse.Rules))
|
require.Equal(t, 1, len(er.PolicyResponse.Rules))
|
||||||
|
|
||||||
patched := er.PatchedResource
|
patched := er.PatchedResource
|
||||||
|
|
|
@ -232,7 +232,7 @@ func NewPolicyContextFromAdmissionRequest(
|
||||||
gvk schema.GroupVersionKind,
|
gvk schema.GroupVersionKind,
|
||||||
configuration config.Configuration,
|
configuration config.Configuration,
|
||||||
) (*PolicyContext, error) {
|
) (*PolicyContext, error) {
|
||||||
ctx, err := newVariablesContext(jp, request, &admissionInfo)
|
engineCtx, err := newJsonContext(jp, request, &admissionInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create policy rule context: %w", err)
|
return nil, fmt.Errorf("failed to create policy rule context: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -240,10 +240,10 @@ func NewPolicyContextFromAdmissionRequest(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse resource: %w", err)
|
return nil, fmt.Errorf("failed to parse resource: %w", err)
|
||||||
}
|
}
|
||||||
if err := ctx.AddImageInfos(&newResource, configuration); err != nil {
|
if err := engineCtx.AddImageInfos(&newResource, configuration); err != nil {
|
||||||
return nil, fmt.Errorf("failed to add image information to the policy rule context: %w", err)
|
return nil, fmt.Errorf("failed to add image information to the policy rule context: %w", err)
|
||||||
}
|
}
|
||||||
policyContext := newPolicyContextWithJsonContext(kyvernov1.AdmissionOperation(request.Operation), ctx).
|
policyContext := newPolicyContextWithJsonContext(kyvernov1.AdmissionOperation(request.Operation), engineCtx).
|
||||||
WithNewResource(newResource).
|
WithNewResource(newResource).
|
||||||
WithOldResource(oldResource).
|
WithOldResource(oldResource).
|
||||||
WithAdmissionInfo(admissionInfo).
|
WithAdmissionInfo(admissionInfo).
|
||||||
|
@ -253,20 +253,20 @@ func NewPolicyContextFromAdmissionRequest(
|
||||||
return policyContext, nil
|
return policyContext, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newVariablesContext(
|
func newJsonContext(
|
||||||
jp jmespath.Interface,
|
jp jmespath.Interface,
|
||||||
request admissionv1.AdmissionRequest,
|
request admissionv1.AdmissionRequest,
|
||||||
userRequestInfo *kyvernov1beta1.RequestInfo,
|
userRequestInfo *kyvernov1beta1.RequestInfo,
|
||||||
) (enginectx.Interface, error) {
|
) (enginectx.Interface, error) {
|
||||||
ctx := enginectx.NewContext(jp)
|
engineCtx := enginectx.NewContext(jp)
|
||||||
if err := ctx.AddRequest(request); err != nil {
|
if err := engineCtx.AddRequest(request); err != nil {
|
||||||
return nil, fmt.Errorf("failed to load incoming request in context: %w", err)
|
return nil, fmt.Errorf("failed to load incoming request in context: %w", err)
|
||||||
}
|
}
|
||||||
if err := ctx.AddUserInfo(*userRequestInfo); err != nil {
|
if err := engineCtx.AddUserInfo(*userRequestInfo); err != nil {
|
||||||
return nil, fmt.Errorf("failed to load userInfo in context: %w", err)
|
return nil, fmt.Errorf("failed to load userInfo in context: %w", err)
|
||||||
}
|
}
|
||||||
if err := ctx.AddServiceAccount(userRequestInfo.AdmissionUserInfo.Username); err != nil {
|
if err := engineCtx.AddServiceAccount(userRequestInfo.AdmissionUserInfo.Username); err != nil {
|
||||||
return nil, fmt.Errorf("failed to load service account in context: %w", err)
|
return nil, fmt.Errorf("failed to load service account in context: %w", err)
|
||||||
}
|
}
|
||||||
return ctx, nil
|
return engineCtx, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||||
kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1"
|
kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1"
|
||||||
"github.com/kyverno/kyverno/pkg/config"
|
"github.com/kyverno/kyverno/pkg/config"
|
||||||
|
@ -50,7 +51,7 @@ func testValidate(
|
||||||
func newPolicyContext(
|
func newPolicyContext(
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
resource unstructured.Unstructured,
|
resource unstructured.Unstructured,
|
||||||
operation kyvernov1.AdmissionOperation,
|
operation kyverno.AdmissionOperation,
|
||||||
admissionInfo *kyvernov1beta1.RequestInfo,
|
admissionInfo *kyvernov1beta1.RequestInfo,
|
||||||
) *PolicyContext {
|
) *PolicyContext {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
@ -2142,7 +2143,7 @@ func executeTest(t *testing.T, test testCase) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pc := newPolicyContext(t, newR, kyvernov1.AdmissionOperation(request.Operation), &userInfo).
|
pc := newPolicyContext(t, newR, kyverno.AdmissionOperation(request.Operation), &userInfo).
|
||||||
WithPolicy(&policy).
|
WithPolicy(&policy).
|
||||||
WithOldResource(oldR)
|
WithOldResource(oldR)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
apiVersion: kuttl.dev/v1beta1
|
||||||
|
kind: TestStep
|
||||||
|
apply:
|
||||||
|
- manifests.yaml
|
||||||
|
assert:
|
||||||
|
- policy-assert.yaml
|
|
@ -0,0 +1,5 @@
|
||||||
|
apiVersion: kuttl.dev/v1beta1
|
||||||
|
kind: TestStep
|
||||||
|
apply:
|
||||||
|
- file: deploy.yaml
|
||||||
|
shouldFail: true
|
12
test/conformance/kuttl/deferred/dependencies/README.md
Normal file
12
test/conformance/kuttl/deferred/dependencies/README.md
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
## Description
|
||||||
|
|
||||||
|
This test checks for handling of variable dependencies with deferred lookups
|
||||||
|
|
||||||
|
## Expected Behavior
|
||||||
|
|
||||||
|
The deployment should fail
|
||||||
|
|
||||||
|
## Reference Issues
|
||||||
|
|
||||||
|
https://github.com/kyverno/kyverno/issues/7486
|
||||||
|
|
28
test/conformance/kuttl/deferred/dependencies/deploy.yaml
Normal file
28
test/conformance/kuttl/deferred/dependencies/deploy.yaml
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: test
|
||||||
|
namespace: acme-fitness
|
||||||
|
labels:
|
||||||
|
app: kubecost-cost-analyzer
|
||||||
|
spec:
|
||||||
|
replicas: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: kubecost-cost-analyzer
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: kubecost-cost-analyzer
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: cost-model
|
||||||
|
image: nginx:1.14.2
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 350m
|
||||||
|
memory: 500Mi
|
||||||
|
limits:
|
||||||
|
memory: 2Gi
|
73
test/conformance/kuttl/deferred/dependencies/manifests.yaml
Normal file
73
test/conformance/kuttl/deferred/dependencies/manifests.yaml
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: acme-fitness
|
||||||
|
---
|
||||||
|
apiVersion: kyverno.io/v1
|
||||||
|
kind: ClusterPolicy
|
||||||
|
metadata:
|
||||||
|
name: enforce-company-budget
|
||||||
|
spec:
|
||||||
|
validationFailureAction: Enforce
|
||||||
|
rules:
|
||||||
|
- name: check-kubecost-budget
|
||||||
|
match:
|
||||||
|
any:
|
||||||
|
- resources:
|
||||||
|
kinds:
|
||||||
|
- Deployment
|
||||||
|
operations:
|
||||||
|
- CREATE
|
||||||
|
context:
|
||||||
|
# Mocked response from the Kubecost prediction API until it natively supports JSON input.
|
||||||
|
# Get the predicted amount of the Deployment and transform to get the totalMonthlyRate.
|
||||||
|
- name: predictedcost
|
||||||
|
variable:
|
||||||
|
jmesPath: '[0].costChange.totalMonthlyRate'
|
||||||
|
value:
|
||||||
|
- namespace: acme-fitness
|
||||||
|
controllerKind: deployment
|
||||||
|
controllerName: test
|
||||||
|
costBefore:
|
||||||
|
totalMonthlyRate: 0
|
||||||
|
cpuMonthlyRate: 0
|
||||||
|
ramMonthlyRate: 0
|
||||||
|
gpuMonthlyRate: 0
|
||||||
|
monthlyCPUCoreHours: 0
|
||||||
|
monthlyRAMByteHours: 0
|
||||||
|
monthlyGPUHours: 0
|
||||||
|
costAfter:
|
||||||
|
totalMonthlyRate: 28.839483652409793
|
||||||
|
cpuMonthlyRate: 24.295976357646456
|
||||||
|
ramMonthlyRate: 4.543507294763337
|
||||||
|
gpuMonthlyRate: 0
|
||||||
|
monthlyCPUCoreHours: 766.5
|
||||||
|
monthlyRAMByteHours: 1.14819072e+12
|
||||||
|
monthlyGPUHours: 0
|
||||||
|
costChange:
|
||||||
|
totalMonthlyRate: 92.839483652409793
|
||||||
|
cpuMonthlyRate: 24.295976357646456
|
||||||
|
ramMonthlyRate: 4.543507294763337
|
||||||
|
gpuMonthlyRate: 0
|
||||||
|
monthlyCPUCoreHours: 766.5
|
||||||
|
monthlyRAMByteHours: 1.14819072e+12
|
||||||
|
monthlyGPUHours: 0
|
||||||
|
- name: budget
|
||||||
|
variable:
|
||||||
|
value:
|
||||||
|
spendLimit: 100.0
|
||||||
|
currentSpend: 73.0
|
||||||
|
# Calculate the budget that remains from the window by subtracting the currentSpend from the spendLimit.
|
||||||
|
- name: remainingbudget
|
||||||
|
variable:
|
||||||
|
jmesPath: subtract(`{{budget.spendLimit}}`,`{{budget.currentSpend}}`)
|
||||||
|
validate:
|
||||||
|
# Need to improve this by rounding.
|
||||||
|
message: "This Deployment, which costs ${{ predictedcost }} to run for a month, will overrun the remaining budget of ${{ remainingbudget }}. Please seek approval."
|
||||||
|
deny:
|
||||||
|
conditions:
|
||||||
|
all:
|
||||||
|
- key: "{{ predictedcost }}"
|
||||||
|
operator: GreaterThan
|
||||||
|
value: "{{ remainingbudget }}"
|
|
@ -0,0 +1,9 @@
|
||||||
|
apiVersion: kyverno.io/v1
|
||||||
|
kind: ClusterPolicy
|
||||||
|
metadata:
|
||||||
|
name: enforce-company-budget
|
||||||
|
status:
|
||||||
|
conditions:
|
||||||
|
- reason: Succeeded
|
||||||
|
status: "True"
|
||||||
|
type: Ready
|
6
test/conformance/kuttl/deferred/foreach/01-apply.yaml
Normal file
6
test/conformance/kuttl/deferred/foreach/01-apply.yaml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
apiVersion: kuttl.dev/v1beta1
|
||||||
|
kind: TestStep
|
||||||
|
apply:
|
||||||
|
- manifests.yaml
|
||||||
|
assert:
|
||||||
|
- policy-assert.yaml
|
7
test/conformance/kuttl/deferred/foreach/02-testcase.yaml
Normal file
7
test/conformance/kuttl/deferred/foreach/02-testcase.yaml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
apiVersion: kuttl.dev/v1beta1
|
||||||
|
kind: TestStep
|
||||||
|
apply:
|
||||||
|
- file: cm.yaml
|
||||||
|
shouldFail: false
|
||||||
|
assert:
|
||||||
|
- cm-assert.yaml
|
11
test/conformance/kuttl/deferred/foreach/README.md
Normal file
11
test/conformance/kuttl/deferred/foreach/README.md
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
## Description
|
||||||
|
|
||||||
|
This test checks for deferred variable substitutions in foreach loops
|
||||||
|
|
||||||
|
## Expected Behavior
|
||||||
|
|
||||||
|
The CM should be created with three new entries
|
||||||
|
|
||||||
|
## Reference Issues
|
||||||
|
|
||||||
|
https://github.com/kyverno/kyverno/issues/7532
|
8
test/conformance/kuttl/deferred/foreach/cm-assert.yaml
Normal file
8
test/conformance/kuttl/deferred/foreach/cm-assert.yaml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: testcase-7fki3-resource
|
||||||
|
data:
|
||||||
|
from_loop_1: AAA
|
||||||
|
from_loop_2: AAA
|
||||||
|
from_loop_3: AAA
|
4
test/conformance/kuttl/deferred/foreach/cm.yaml
Normal file
4
test/conformance/kuttl/deferred/foreach/cm.yaml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: testcase-7fki3-resource
|
45
test/conformance/kuttl/deferred/foreach/manifests.yaml
Normal file
45
test/conformance/kuttl/deferred/foreach/manifests.yaml
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
apiVersion: kyverno.io/v1
|
||||||
|
kind: ClusterPolicy
|
||||||
|
metadata:
|
||||||
|
name: testcase-7fki3
|
||||||
|
spec:
|
||||||
|
schemaValidation: false
|
||||||
|
background: false
|
||||||
|
validationFailureAction: Enforce
|
||||||
|
rules:
|
||||||
|
- name: mutate1
|
||||||
|
match:
|
||||||
|
all:
|
||||||
|
- resources:
|
||||||
|
kinds:
|
||||||
|
- v1/ConfigMap
|
||||||
|
names:
|
||||||
|
- testcase-7fki3-resource
|
||||||
|
context:
|
||||||
|
- name: var1
|
||||||
|
variable:
|
||||||
|
value: AAA
|
||||||
|
preconditions:
|
||||||
|
all:
|
||||||
|
- key: "{{ request.operation }}"
|
||||||
|
operator: In
|
||||||
|
value:
|
||||||
|
- CREATE
|
||||||
|
- UPDATE
|
||||||
|
mutate:
|
||||||
|
foreach:
|
||||||
|
# first loop
|
||||||
|
- list: "['dummy']"
|
||||||
|
patchStrategicMerge:
|
||||||
|
data:
|
||||||
|
from_loop_1: "{{ var1 || '!!!variable not resolved!!!' }}"
|
||||||
|
# second loop
|
||||||
|
- list: "['dummy']"
|
||||||
|
patchStrategicMerge:
|
||||||
|
data:
|
||||||
|
from_loop_2: "{{ var1 || '!!!variable not resolved!!!' }}"
|
||||||
|
# third loop
|
||||||
|
- list: "['dummy']"
|
||||||
|
patchStrategicMerge:
|
||||||
|
data:
|
||||||
|
from_loop_3: "{{ var1 || '!!!variable not resolved!!!' }}"
|
|
@ -0,0 +1,9 @@
|
||||||
|
apiVersion: kyverno.io/v1
|
||||||
|
kind: ClusterPolicy
|
||||||
|
metadata:
|
||||||
|
name: testcase-7fki3
|
||||||
|
status:
|
||||||
|
conditions:
|
||||||
|
- reason: Succeeded
|
||||||
|
status: "True"
|
||||||
|
type: Ready
|
6
test/conformance/kuttl/deferred/recursive/01-policy.yaml
Normal file
6
test/conformance/kuttl/deferred/recursive/01-policy.yaml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
apiVersion: kuttl.dev/v1beta1
|
||||||
|
kind: TestStep
|
||||||
|
apply:
|
||||||
|
- policy.yaml
|
||||||
|
assert:
|
||||||
|
- policy-assert.yaml
|
|
@ -0,0 +1,6 @@
|
||||||
|
apiVersion: kuttl.dev/v1beta1
|
||||||
|
kind: TestStep
|
||||||
|
apply:
|
||||||
|
- file: resource.yaml
|
||||||
|
assert:
|
||||||
|
- resource-assert.yaml
|
7
test/conformance/kuttl/deferred/recursive/README.md
Normal file
7
test/conformance/kuttl/deferred/recursive/README.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
## Description
|
||||||
|
|
||||||
|
This test checks for handling of variable dependencies with the same name with deferred lookups in a foreach
|
||||||
|
|
||||||
|
## Expected Behavior
|
||||||
|
|
||||||
|
The configmap should create fine and contain `one: one` in the data.
|
|
@ -0,0 +1,9 @@
|
||||||
|
apiVersion: kyverno.io/v1
|
||||||
|
kind: ClusterPolicy
|
||||||
|
metadata:
|
||||||
|
name: one
|
||||||
|
status:
|
||||||
|
conditions:
|
||||||
|
- reason: Succeeded
|
||||||
|
status: "True"
|
||||||
|
type: Ready
|
26
test/conformance/kuttl/deferred/recursive/policy.yaml
Normal file
26
test/conformance/kuttl/deferred/recursive/policy.yaml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
apiVersion: kyverno.io/v1
|
||||||
|
kind: ClusterPolicy
|
||||||
|
metadata:
|
||||||
|
name: one
|
||||||
|
spec:
|
||||||
|
validationFailureAction: Enforce
|
||||||
|
rules:
|
||||||
|
- name: one
|
||||||
|
match:
|
||||||
|
all:
|
||||||
|
- resources:
|
||||||
|
kinds:
|
||||||
|
- v1/ConfigMap
|
||||||
|
context:
|
||||||
|
- name: one
|
||||||
|
variable:
|
||||||
|
value: one
|
||||||
|
- name: one
|
||||||
|
variable:
|
||||||
|
jmesPath: one
|
||||||
|
mutate:
|
||||||
|
foreach:
|
||||||
|
- list: "['dummy']"
|
||||||
|
patchStrategicMerge:
|
||||||
|
data:
|
||||||
|
one: "{{ one }}"
|
|
@ -0,0 +1,6 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: one
|
||||||
|
data:
|
||||||
|
one: one
|
4
test/conformance/kuttl/deferred/recursive/resource.yaml
Normal file
4
test/conformance/kuttl/deferred/recursive/resource.yaml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: one
|
|
@ -0,0 +1,6 @@
|
||||||
|
apiVersion: kuttl.dev/v1beta1
|
||||||
|
kind: TestStep
|
||||||
|
apply:
|
||||||
|
- policy.yaml
|
||||||
|
assert:
|
||||||
|
- policy-assert.yaml
|
|
@ -0,0 +1,6 @@
|
||||||
|
apiVersion: kuttl.dev/v1beta1
|
||||||
|
kind: TestStep
|
||||||
|
apply:
|
||||||
|
- file: resource.yaml
|
||||||
|
assert:
|
||||||
|
- resource-assert.yaml
|
|
@ -0,0 +1,9 @@
|
||||||
|
## Description
|
||||||
|
|
||||||
|
This test checks for handling of variable dependencies with the same name:
|
||||||
|
- the same name is used twice in the rule context
|
||||||
|
- the same name is also used in a foreach context
|
||||||
|
|
||||||
|
## Expected Behavior
|
||||||
|
|
||||||
|
The configmap should create fine and contain `one: one` in the data.
|
|
@ -0,0 +1,9 @@
|
||||||
|
apiVersion: kyverno.io/v1
|
||||||
|
kind: ClusterPolicy
|
||||||
|
metadata:
|
||||||
|
name: one
|
||||||
|
status:
|
||||||
|
conditions:
|
||||||
|
- reason: Succeeded
|
||||||
|
status: "True"
|
||||||
|
type: Ready
|
|
@ -0,0 +1,33 @@
|
||||||
|
apiVersion: kyverno.io/v1
|
||||||
|
kind: ClusterPolicy
|
||||||
|
metadata:
|
||||||
|
name: one
|
||||||
|
spec:
|
||||||
|
validationFailureAction: Enforce
|
||||||
|
rules:
|
||||||
|
- name: one
|
||||||
|
match:
|
||||||
|
all:
|
||||||
|
- resources:
|
||||||
|
kinds:
|
||||||
|
- v1/ConfigMap
|
||||||
|
context:
|
||||||
|
- name: foo
|
||||||
|
variable:
|
||||||
|
value: foo
|
||||||
|
- name: one
|
||||||
|
variable:
|
||||||
|
jmesPath: foo
|
||||||
|
- name: foo
|
||||||
|
variable:
|
||||||
|
value: baz
|
||||||
|
mutate:
|
||||||
|
foreach:
|
||||||
|
- list: "['dummy']"
|
||||||
|
context:
|
||||||
|
- name: foo
|
||||||
|
variable:
|
||||||
|
value: bar
|
||||||
|
patchStrategicMerge:
|
||||||
|
data:
|
||||||
|
one: "{{ one }}"
|
|
@ -0,0 +1,6 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: one
|
||||||
|
data:
|
||||||
|
one: foo
|
|
@ -0,0 +1,4 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: one
|
6
test/conformance/kuttl/deferred/two-rules/01-policy.yaml
Normal file
6
test/conformance/kuttl/deferred/two-rules/01-policy.yaml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
apiVersion: kuttl.dev/v1beta1
|
||||||
|
kind: TestStep
|
||||||
|
apply:
|
||||||
|
- policy.yaml
|
||||||
|
assert:
|
||||||
|
- policy-assert.yaml
|
|
@ -0,0 +1,6 @@
|
||||||
|
apiVersion: kuttl.dev/v1beta1
|
||||||
|
kind: TestStep
|
||||||
|
apply:
|
||||||
|
- file: resource.yaml
|
||||||
|
assert:
|
||||||
|
- resource-assert.yaml
|
13
test/conformance/kuttl/deferred/two-rules/README.md
Normal file
13
test/conformance/kuttl/deferred/two-rules/README.md
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
## Description
|
||||||
|
|
||||||
|
This test checks that variables don't leak from one rule to the next.
|
||||||
|
The second rule tries to use a variable from the first rule, it should not find it.
|
||||||
|
|
||||||
|
## Expected Behavior
|
||||||
|
|
||||||
|
The configmap creates fine with the data:
|
||||||
|
```yaml
|
||||||
|
data:
|
||||||
|
one: test
|
||||||
|
two: "null"
|
||||||
|
```
|
|
@ -0,0 +1,9 @@
|
||||||
|
apiVersion: kyverno.io/v1
|
||||||
|
kind: ClusterPolicy
|
||||||
|
metadata:
|
||||||
|
name: one
|
||||||
|
status:
|
||||||
|
conditions:
|
||||||
|
- reason: Succeeded
|
||||||
|
status: "True"
|
||||||
|
type: Ready
|
35
test/conformance/kuttl/deferred/two-rules/policy.yaml
Normal file
35
test/conformance/kuttl/deferred/two-rules/policy.yaml
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
apiVersion: kyverno.io/v1
|
||||||
|
kind: ClusterPolicy
|
||||||
|
metadata:
|
||||||
|
name: one
|
||||||
|
spec:
|
||||||
|
validationFailureAction: Enforce
|
||||||
|
rules:
|
||||||
|
- name: one
|
||||||
|
match:
|
||||||
|
all:
|
||||||
|
- resources:
|
||||||
|
kinds:
|
||||||
|
- v1/ConfigMap
|
||||||
|
context:
|
||||||
|
- name: var
|
||||||
|
variable:
|
||||||
|
value: test
|
||||||
|
mutate:
|
||||||
|
foreach:
|
||||||
|
- list: "['dummy']"
|
||||||
|
patchStrategicMerge:
|
||||||
|
data:
|
||||||
|
one: "{{ to_string(var) }}"
|
||||||
|
- name: two
|
||||||
|
match:
|
||||||
|
all:
|
||||||
|
- resources:
|
||||||
|
kinds:
|
||||||
|
- v1/ConfigMap
|
||||||
|
mutate:
|
||||||
|
foreach:
|
||||||
|
- list: "['dummy']"
|
||||||
|
patchStrategicMerge:
|
||||||
|
data:
|
||||||
|
two: "{{ to_string(var) }}"
|
|
@ -0,0 +1,7 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: one
|
||||||
|
data:
|
||||||
|
one: test
|
||||||
|
two: "null"
|
4
test/conformance/kuttl/deferred/two-rules/resource.yaml
Normal file
4
test/conformance/kuttl/deferred/two-rules/resource.yaml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: one
|
Loading…
Add table
Reference in a new issue