diff --git a/pkg/engine/context/context.go b/pkg/engine/context/context.go index 7e167a05cd7a..1490bcb7d694 100644 --- a/pkg/engine/context/context.go +++ b/pkg/engine/context/context.go @@ -12,6 +12,7 @@ import ( "github.com/kyverno/kyverno/pkg/engine/jmespath" "github.com/kyverno/kyverno/pkg/engine/jsonutils" "github.com/kyverno/kyverno/pkg/logging" + "github.com/kyverno/kyverno/pkg/toggle" apiutils "github.com/kyverno/kyverno/pkg/utils/api" admissionv1 "k8s.io/api/admission/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -296,21 +297,25 @@ func (ctx *context) AddImageInfo(info apiutils.ImageInfo, cfg config.Configurati } func (ctx *context) AddImageInfos(resource *unstructured.Unstructured, cfg config.Configuration) error { - images, err := apiutils.ExtractImagesFromResource(*resource, nil, cfg) - if err != nil { - return err - } - if len(images) == 0 { - return nil + imageInfoLoader := &ImageInfoLoader{ + resource: resource, + eCtx: ctx, + cfg: cfg, } - ctx.images = images - utm, err := convertImagesToUntyped(images) + dl, err := NewDeferredLoader("images", imageInfoLoader, logger) if err != nil { return err } - - logging.V(4).Info("updated image info", "images", utm) - return addToContext(ctx, utm, "images") + if toggle.EnableDeferredLoading.Enabled() { + if err := ctx.AddDeferredLoader(dl); err != nil { + return err + } + } else { + if err := imageInfoLoader.LoadData(); err != nil { + return err + } + } + return nil } func convertImagesToUntyped(images map[string]map[string]apiutils.ImageInfo) (map[string]interface{}, error) { @@ -335,6 +340,35 @@ func convertImagesToUntyped(images map[string]map[string]apiutils.ImageInfo) (ma return results, nil } +type ImageInfoLoader struct { + resource *unstructured.Unstructured + hasLoaded bool + eCtx *context + cfg config.Configuration +} + +func (l *ImageInfoLoader) HasLoaded() bool { + return l.hasLoaded +} + +func (l *ImageInfoLoader) LoadData() error { + images, err := apiutils.ExtractImagesFromResource(*l.resource, nil, l.cfg) + if err != nil { + return err + } + if len(images) == 0 { + return nil + } + l.eCtx.images = images + utm, err := convertImagesToUntyped(images) + if err != nil { + return err + } + + logging.V(4).Info("updated image info", "images", utm) + return addToContext(l.eCtx, utm, "images") +} + func (ctx *context) GenerateCustomImageInfo(resource *unstructured.Unstructured, imageExtractorConfigs kyvernov1.ImageExtractorConfigs, cfg config.Configuration) (map[string]map[string]apiutils.ImageInfo, error) { images, err := apiutils.ExtractImagesFromResource(*resource, imageExtractorConfigs, cfg) if err != nil { @@ -350,6 +384,12 @@ func (ctx *context) GenerateCustomImageInfo(resource *unstructured.Unstructured, } func (ctx *context) ImageInfo() map[string]map[string]apiutils.ImageInfo { + // force load of image info from deferred loader + if len(ctx.images) == 0 { + if err := ctx.loadDeferred("images"); err != nil { + return map[string]map[string]apiutils.ImageInfo{} + } + } return ctx.images } diff --git a/pkg/engine/context/context_test.go b/pkg/engine/context/context_test.go index d24f4980760c..6a32ecb4c21d 100644 --- a/pkg/engine/context/context_test.go +++ b/pkg/engine/context/context_test.go @@ -7,10 +7,15 @@ import ( urkyverno "github.com/kyverno/kyverno/api/kyverno/v1beta1" "github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/engine/jmespath" + kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" + "github.com/stretchr/testify/assert" authenticationv1 "k8s.io/api/authentication/v1" ) -var jp = jmespath.New(config.NewDefaultConfiguration(false)) +var ( + jp = jmespath.New(config.NewDefaultConfiguration(false)) + cfg = config.NewDefaultConfiguration(false) +) func Test_addResourceAndUserContext(t *testing.T) { var err error @@ -123,3 +128,56 @@ func Test_addResourceAndUserContext(t *testing.T) { t.Error("expected result does not match") } } + +func Test_ImageInfoLoader(t *testing.T) { + resource1, err := kubeutils.BytesToUnstructured([]byte(`{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "test-pod", + "namespace": "default" + }, + "spec": { + "containers": [{ + "name": "test_container", + "image": "nginx:latest" + }] + } + }`)) + assert.Nil(t, err) + newctx := newContext() + err = newctx.AddImageInfos(resource1, cfg) + assert.Nil(t, err) + // images not loaded + assert.Nil(t, newctx.images) + // images loaded on Query + name, err := newctx.Query("images.containers.test_container.name") + assert.Nil(t, err) + assert.Equal(t, name, "nginx") +} + +func Test_ImageInfoLoader_OnDirectCall(t *testing.T) { + resource1, err := kubeutils.BytesToUnstructured([]byte(`{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "test-pod", + "namespace": "default" + }, + "spec": { + "containers": [{ + "name": "test_container", + "image": "nginx:latest" + }] + } + }`)) + assert.Nil(t, err) + newctx := newContext() + err = newctx.AddImageInfos(resource1, cfg) + assert.Nil(t, err) + // images not loaded + assert.Nil(t, newctx.images) + // images loaded on explicit call to ImageInfo + imageinfos := newctx.ImageInfo() + assert.Equal(t, imageinfos["containers"]["test_container"].Name, "nginx") +}