Repository: kyverno/kyverno
Pull request #3563 (2022-04-07 11:51:21)

refactor: engine context
This PR refactor the engine context package.
- Do not expose the concret context, only the interface
- Add missing methods to the interface
- Simplify a couple of utils
- Hide the `AddJSON` method (way too broad and error prone)

One last instace of `AddJSONObject`, dunno how to remove it yet.

JimBugwadia: > @realshuting @prateekpandey14 @JimBugwadia is this expected that images are not part of a context checkpoint ?

Currently the `images` variable is a global that is built when the admission request is received, and currently does not get updated while processing rules or sub-elements in a foreach.

However, that is about to change when @samj1912 integrates https://github.com/kyverno/kyverno/issues/2938. With this feature, the user will be able to add to the `images` data by providing a path to where an image is referenced in a CR.

So, we can leave as is for now, or remove / update to prepare for the above change.

codecov-commenter: # [Codecov](https://codecov.io/gh/kyverno/kyverno/pull/3563?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> Merging [#3563](https://codecov.io/gh/kyverno/kyverno/pull/3563?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (fd6afb9) into [main](https://codecov.io/gh/kyverno/kyverno/commit/9834feea74888a5c8515916f9b1a5623a606881f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (9834fee) will **decrease** coverage by `0.15%`.
> The diff coverage is `30.68%`.

```diff
@@            Coverage Diff             @@
##             main    #3563      +/-   ##
==========================================
- Coverage   26.38%   26.22%   -0.16%     
==========================================
  Files         137      138       +1     
  Lines       19329    19208     -121     
==========================================
- Hits         5099     5038      -61     
+ Misses      13606    13551      -55     
+ Partials      624      619       -5     
```


| [Impacted Files](https://codecov.io/gh/kyverno/kyverno/pull/3563?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Coverage Δ | |
|---|---|---|
| [pkg/engine/jsonContext.go](https://codecov.io/gh/kyverno/kyverno/pull/3563/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2VuZ2luZS9qc29uQ29udGV4dC5nbw==) | `9.05% <0.00%> (-0.61%)` | :arrow_down: |
| [pkg/engine/mutation.go](https://codecov.io/gh/kyverno/kyverno/pull/3563/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2VuZ2luZS9tdXRhdGlvbi5nbw==) | `58.65% <0.00%> (ø)` | |
| [pkg/engine/policyContext.go](https://codecov.io/gh/kyverno/kyverno/pull/3563/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2VuZ2luZS9wb2xpY3lDb250ZXh0Lmdv) | `100.00% <ø> (ø)` | |
| [pkg/engine/validation.go](https://codecov.io/gh/kyverno/kyverno/pull/3563/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2VuZ2luZS92YWxpZGF0aW9uLmdv) | `69.10% <0.00%> (-0.35%)` | :arrow_down: |
| [pkg/engine/variables/vars.go](https://codecov.io/gh/kyverno/kyverno/pull/3563/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2VuZ2luZS92YXJpYWJsZXMvdmFycy5nbw==) | `51.52% <0.00%> (ø)` | |
| [pkg/generate/generate.go](https://codecov.io/gh/kyverno/kyverno/pull/3563/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2dlbmVyYXRlL2dlbmVyYXRlLmdv) | `0.00% <0.00%> (ø)` | |
| [pkg/policy/apply.go](https://codecov.io/gh/kyverno/kyverno/pull/3563/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL3BvbGljeS9hcHBseS5nbw==) | `0.00% <0.00%> (ø)` | |
| [pkg/webhooks/common.go](https://codecov.io/gh/kyverno/kyverno/pull/3563/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL3dlYmhvb2tzL2NvbW1vbi5nbw==) | `7.27% <0.00%> (ø)` | |
| [pkg/webhooks/generation.go](https://codecov.io/gh/kyverno/kyverno/pull/3563/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL3dlYmhvb2tzL2dlbmVyYXRpb24uZ28=) | `13.93% <ø> (ø)` | |
| [pkg/webhooks/handlers.go](https://codecov.io/gh/kyverno/kyverno/pull/3563/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL3dlYmhvb2tzL2hhbmRsZXJzLmdv) | `0.00% <0.00%> (ø)` | |
| ... and [13 more](https://codecov.io/gh/kyverno/kyverno/pull/3563/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/kyverno/kyverno/pull/3563?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/kyverno/kyverno/pull/3563?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None). Last update [9834fee...fd6afb9](https://codecov.io/gh/kyverno/kyverno/pull/3563?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).

eddycharly: Not sure why it is both injected in the context and stored in a separate field.

https://github.com/kyverno/kyverno/blob/9834feea74888a5c8515916f9b1a5623a606881f/pkg/engine/context/context.go#L306
JimBugwadia: We loop over `images` when processing the `imageVerify` rules. It can be replaced by a method that extracts from the JSON data, but may be a bit of a pain to unmarshall each time.
eddycharly: > However, that is about to change when @samj1912 integrates https://github.com/kyverno/kyverno/issues/2938

In my latest refactoring iteration (didn’t push yet) I introduced a registrable image extractor from resources.
Could come handy to implement the mentioned feature. 

Patch:
diff --git a/pkg/engine/context/context.go b/pkg/engine/context/context.go
index 67eac25c1..59e24fe16 100644
--- a/pkg/engine/context/context.go
+++ b/pkg/engine/context/context.go
@@ -5,30 +5,48 @@ import (
 	"strings"
 	"sync"
 
-	"github.com/pkg/errors"
-
 	jsonpatch "github.com/evanphx/json-patch/v5"
-	"github.com/go-logr/logr"
 	kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
+	pkgcommon "github.com/kyverno/kyverno/pkg/common"
+	"github.com/pkg/errors"
 	admissionv1 "k8s.io/api/admission/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"sigs.k8s.io/controller-runtime/pkg/log"
 )
 
-//Interface to manage context operations
-type Interface interface {
+var logger = log.Log.WithName("context")
+
+// EvalInterface is used to query and inspect context data
+type EvalInterface interface {
+	// Query accepts a JMESPath expression and returns matching data
+	Query(query string) (interface{}, error)
 
+	// HasChanged accepts a JMESPath expression and compares matching data in the
+	// request.object and request.oldObject context fields. If the data has changed
+	// it return `true`. If the data has not changed it returns false. If either
+	// request.object or request.oldObject are not found, an error is returned.
+	HasChanged(jmespath string) (bool, error)
+}
+
+// Interface to manage context operations
+type Interface interface {
 	// AddRequest marshals and adds the admission request to the context
 	AddRequest(request *admissionv1.AdmissionRequest) error
 
-	// AddJSON  merges the json with context
-	AddJSON(dataRaw []byte) error
+	// AddVariable adds a variable to the context
+	AddVariable(key, value string) error
+
+	// AddContextEntry adds a context entry to the context
+	AddContextEntry(name string, dataRaw []byte) error
 
 	// AddResource merges resource json under request.object
-	AddResource(dataRaw []byte) error
+	AddResource(data map[string]interface{}) error
+
+	// AddOldResource merges resource json under request.oldObject
+	AddOldResource(data map[string]interface{}) error
 
 	// AddUserInfo merges userInfo json under kyverno.userInfo
-	AddUserInfo(userInfo kyverno.UserInfo) error
+	AddUserInfo(userInfo kyverno.RequestInfo) error
 
 	// AddServiceAccount merges ServiceAccount types
 	AddServiceAccount(userName string) error
@@ -36,194 +54,102 @@ type Interface interface {
 	// AddNamespace merges resource json under request.namespace
 	AddNamespace(namespace string) error
 
-	EvalInterface
-}
+	// AddElement adds element info to the context
+	AddElement(data map[string]interface{}, index int) error
 
-//EvalInterface is used to query and inspect context data
-type EvalInterface interface {
+	// AddImageInfo adds image info to the context
+	AddImageInfo(info *ImageInfo) error
 
-	// Query accepts a JMESPath expression and returns matching data
-	Query(query string) (interface{}, error)
+	// AddImageInfos adds image infos to the context
+	AddImageInfos(resource *unstructured.Unstructured) error
 
-	// HasChanged accepts a JMESPath expression and compares matching data in the
-	// request.object and request.oldObject context fields. If the data has changed
-	// it return `true`. If the data has not changed it returns false. If either
-	// request.object or request.oldObject are not found, an error is returned.
-	HasChanged(jmespath string) (bool, error)
+	// ImageInfo returns image infos present in the context
+	ImageInfo() *Images
+
+	// Checkpoint creates a copy of the current internal state and pushes it into a stack of stored states.
+	Checkpoint()
+
+	// Restore sets the internal state to the last checkpoint, and removes the checkpoint.
+	Restore()
+
+	// Reset sets the internal state to the last checkpoint, but does not remove the checkpoint.
+	Reset()
+
+	EvalInterface
+
+	// AddJSON  merges the json with context
+	addJSON(dataRaw []byte) error
 }
 
-//Context stores the data resources as JSON
-type Context struct {
+// Context stores the data resources as JSON
+type context struct {
 	mutex              sync.RWMutex
 	jsonRaw            []byte
 	jsonRawCheckpoints [][]byte
 	images             *Images
-	log                logr.Logger
 }
 
-//NewContext returns a new context
-func NewContext() *Context {
-	ctx := Context{
-		jsonRaw:            []byte(`{}`), // empty json struct
-		log:                log.Log.WithName("context"),
+// NewContext returns a new context
+func NewContext() Interface {
+	return NewContextFromRaw([]byte(`{}`))
+}
+
+// NewContextFromRaw returns a new context initialized with raw data
+func NewContextFromRaw(raw []byte) Interface {
+	ctx := context{
+		jsonRaw:            raw,
 		jsonRawCheckpoints: make([][]byte, 0),
 	}
-
 	return &ctx
 }
 
-// AddJSON merges json data
-func (ctx *Context) AddJSON(dataRaw []byte) error {
-	var err error
+// addJSON merges json data
+func (ctx *context) addJSON(dataRaw []byte) error {
 	ctx.mutex.Lock()
 	defer ctx.mutex.Unlock()
-
-	ctx.jsonRaw, err = jsonpatch.MergeMergePatches(ctx.jsonRaw, dataRaw)
+	json, err := jsonpatch.MergeMergePatches(ctx.jsonRaw, dataRaw)
 	if err != nil {
 		return errors.Wrap(err, "failed to merge JSON data")
 	}
-
+	ctx.jsonRaw = json
 	return nil
 }
 
-// AddJSONObject merges json data
-func (ctx *Context) AddJSONObject(jsonData interface{}) error {
-	jsonBytes, err := json.Marshal(jsonData)
-	if err != nil {
-		return err
-	}
-
-	return ctx.AddJSON(jsonBytes)
-}
-
 // AddRequest adds an admission request to context
-func (ctx *Context) AddRequest(request *admissionv1.AdmissionRequest) error {
-	modifiedResource := struct {
-		Request interface{} `json:"request"`
-	}{
-		Request: request,
-	}
-
-	objRaw, err := json.Marshal(modifiedResource)
-	if err != nil {
-		ctx.log.Error(err, "failed to marshal the request")
-		return err
-	}
-
-	return ctx.AddJSON(objRaw)
+func (ctx *context) AddRequest(request *admissionv1.AdmissionRequest) error {
+	return addToContext(ctx, request, "request")
 }
 
-//AddResource data at path: request.object
-func (ctx *Context) AddResource(dataRaw []byte) error {
-
-	// unmarshal the resource struct
-	var data interface{}
-	if err := json.Unmarshal(dataRaw, &data); err != nil {
-		ctx.log.Error(err, "failed to unmarshal the resource")
-		return err
-	}
-
-	modifiedResource := struct {
-		Request interface{} `json:"request"`
-	}{
-		Request: struct {
-			Object interface{} `json:"object"`
-		}{
-			Object: data,
-		},
-	}
-
-	objRaw, err := json.Marshal(modifiedResource)
-	if err != nil {
-		ctx.log.Error(err, "failed to marshal the resource")
-		return err
-	}
-	return ctx.AddJSON(objRaw)
+func (ctx *context) AddVariable(key, value string) error {
+	return ctx.addJSON(pkgcommon.VariableToJSON(key, value))
 }
 
-//AddResourceInOldObject data at path: request.oldObject
-func (ctx *Context) AddResourceInOldObject(dataRaw []byte) error {
-
-	// unmarshal the resource struct
+func (ctx *context) AddContextEntry(name string, dataRaw []byte) error {
 	var data interface{}
 	if err := json.Unmarshal(dataRaw, &data); err != nil {
-		ctx.log.Error(err, "failed to unmarshal the resource")
-		return err
-	}
-
-	modifiedResource := struct {
-		Request interface{} `json:"request"`
-	}{
-		Request: struct {
-			OldObject interface{} `json:"oldObject"`
-		}{
-			OldObject: data,
-		},
-	}
-
-	objRaw, err := json.Marshal(modifiedResource)
-	if err != nil {
-		ctx.log.Error(err, "failed to marshal the resource")
-		return err
-	}
-	return ctx.AddJSON(objRaw)
-}
-
-func (ctx *Context) AddResourceAsObject(data interface{}) error {
-	return ctx.addToRequest(data, "object")
-}
-
-func (ctx *Context) ReplaceResourceAsObject(data interface{}) error {
-	if err := ctx.addToRequest(nil, "object"); err != nil {
+		logger.Error(err, "failed to unmarshal the resource")
 		return err
 	}
-
-	return ctx.addToRequest(data, "object")
+	return addToContext(ctx, data, name)
 }
 
-func (ctx *Context) ReplaceResourceAsOldObject(data interface{}) error {
-	if err := ctx.addToRequest(nil, "oldObject"); err != nil {
-		return err
-	}
-
-	return ctx.addToRequest(data, "oldObject")
+// AddResource data at path: request.object
+func (ctx *context) AddResource(data map[string]interface{}) error {
+	return addToContext(ctx, data, "request", "object")
 }
 
-func (ctx *Context) addToRequest(data interface{}, tag string) error {
-	requestData := make(map[string]interface{})
-	requestData[tag] = data
-	request := map[string]interface{}{
-		"request": requestData,
-	}
-
-	objRaw, err := json.Marshal(request)
-	if err != nil {
-		ctx.log.Error(err, "failed to marshal the resource")
-		return err
-	}
-
-	return ctx.AddJSON(objRaw)
+// AddOldResource data at path: request.oldObject
+func (ctx *context) AddOldResource(data map[string]interface{}) error {
+	return addToContext(ctx, data, "request", "oldObject")
 }
 
-//AddUserInfo adds userInfo at path request.userInfo
-func (ctx *Context) AddUserInfo(userRequestInfo kyverno.RequestInfo) error {
-	modifiedResource := struct {
-		Request interface{} `json:"request"`
-	}{
-		Request: userRequestInfo,
-	}
-
-	objRaw, err := json.Marshal(modifiedResource)
-	if err != nil {
-		ctx.log.Error(err, "failed to marshal the UserInfo")
-		return err
-	}
-	ctx.log.V(4).Info("Adding user info logs", "userRequestInfo", userRequestInfo)
-	return ctx.AddJSON(objRaw)
+// AddUserInfo adds userInfo at path request.userInfo
+func (ctx *context) AddUserInfo(userRequestInfo kyverno.RequestInfo) error {
+	return addToContext(ctx, userRequestInfo, "request")
 }
 
-//AddServiceAccount removes prefix 'system:serviceaccount:' and namespace, then loads only SA name and SA namespace
-func (ctx *Context) AddServiceAccount(userName string) error {
+// AddServiceAccount removes prefix 'system:serviceaccount:' and namespace, then loads only SA name and SA namespace
+func (ctx *context) AddServiceAccount(userName string) error {
 	saPrefix := "system:serviceaccount:"
 	var sa string
 	saName := ""
@@ -239,7 +165,6 @@ func (ctx *Context) AddServiceAccount(userName string) error {
 		saName = groups[1]
 		saNamespace = groups[0]
 	}
-
 	saNameObj := struct {
 		SA string `json:"serviceAccountName"`
 	}{
@@ -247,10 +172,10 @@ func (ctx *Context) AddServiceAccount(userName string) error {
 	}
 	saNameRaw, err := json.Marshal(saNameObj)
 	if err != nil {
-		ctx.log.Error(err, "failed to marshal the SA")
+		logger.Error(err, "failed to marshal the SA")
 		return err
 	}
-	if err := ctx.AddJSON(saNameRaw); err != nil {
+	if err := ctx.addJSON(saNameRaw); err != nil {
 		return err
 	}
 
@@ -261,103 +186,85 @@ func (ctx *Context) AddServiceAccount(userName string) error {
 	}
 	saNsRaw, err := json.Marshal(saNsObj)
 	if err != nil {
-		ctx.log.Error(err, "failed to marshal the SA namespace")
+		logger.Error(err, "failed to marshal the SA namespace")
 		return err
 	}
-	if err := ctx.AddJSON(saNsRaw); err != nil {
+	if err := ctx.addJSON(saNsRaw); err != nil {
 		return err
 	}
-	ctx.log.V(4).Info("Adding service account", "service account name", saName, "service account namespace", saNamespace)
+	logger.V(4).Info("Adding service account", "service account name", saName, "service account namespace", saNamespace)
 	return nil
 }
 
 // AddNamespace merges resource json under request.namespace
-func (ctx *Context) AddNamespace(namespace string) error {
-	modifiedResource := struct {
-		Request interface{} `json:"request"`
-	}{
-		Request: struct {
-			Namespace string `json:"namespace"`
-		}{
-			Namespace: namespace,
-		},
-	}
+func (ctx *context) AddNamespace(namespace string) error {
+	return addToContext(ctx, namespace, "request", "namespace")
+}
 
-	objRaw, err := json.Marshal(modifiedResource)
-	if err != nil {
-		ctx.log.Error(err, "failed to marshal the resource")
-		return err
+func (ctx *context) AddElement(data map[string]interface{}, index int) error {
+	data = map[string]interface{}{
+		"element":      data,
+		"elementIndex": index,
 	}
+	return addToContext(ctx, data)
+}
 
-	return ctx.AddJSON(objRaw)
+func (ctx *context) AddImageInfo(info *ImageInfo) error {
+	data := map[string]interface{}{
+		"image":    info.String(),
+		"registry": info.Registry,
+		"path":     info.Path,
+		"name":     info.Name,
+		"tag":      info.Tag,
+		"digest":   info.Digest,
+	}
+	return addToContext(ctx, data, "image")
 }
 
-func (ctx *Context) AddImageInfo(resource *unstructured.Unstructured) error {
-	initContainersImgs, containersImgs, ephemeralContainersImgs := extractImageInfo(resource, ctx.log)
+func (ctx *context) AddImageInfos(resource *unstructured.Unstructured) error {
+	initContainersImgs, containersImgs, ephemeralContainersImgs := extractImageInfo(resource, logger)
 	if len(initContainersImgs) == 0 && len(containersImgs) == 0 && len(ephemeralContainersImgs) == 0 {
 		return nil
 	}
-
 	images := newImages(initContainersImgs, containersImgs, ephemeralContainersImgs)
-	if images == nil {
-		return nil
-	}
-
-	ctx.images = images
-	imagesTag := struct {
-		Images interface{} `json:"images"`
-	}{
-		Images: images,
-	}
-
-	objRaw, err := json.Marshal(imagesTag)
-	if err != nil {
-		return err
-	}
-
-	return ctx.AddJSON(objRaw)
+	ctx.images = &images
+	return addToContext(ctx, images, "images")
 }
 
-func (ctx *Context) ImageInfo() *Images {
+func (ctx *context) ImageInfo() *Images {
 	return ctx.images
 }
 
 // Checkpoint creates a copy of the current internal state and
 // pushes it into a stack of stored states.
-func (ctx *Context) Checkpoint() {
+func (ctx *context) Checkpoint() {
 	ctx.mutex.Lock()
 	defer ctx.mutex.Unlock()
-
 	jsonRawCheckpoint := make([]byte, len(ctx.jsonRaw))
 	copy(jsonRawCheckpoint, ctx.jsonRaw)
-
 	ctx.jsonRawCheckpoints = append(ctx.jsonRawCheckpoints, jsonRawCheckpoint)
 }
 
 // Restore sets the internal state to the last checkpoint, and removes the checkpoint.
-func (ctx *Context) Restore() {
+func (ctx *context) Restore() {
 	ctx.reset(true)
 }
 
 // Reset sets the internal state to the last checkpoint, but does not remove the checkpoint.
-func (ctx *Context) Reset() {
+func (ctx *context) Reset() {
 	ctx.reset(false)
 }
 
-func (ctx *Context) reset(remove bool) {
+func (ctx *context) reset(remove bool) {
 	ctx.mutex.Lock()
 	defer ctx.mutex.Unlock()
-
 	if len(ctx.jsonRawCheckpoints) == 0 {
 		return
 	}
-
 	n := len(ctx.jsonRawCheckpoints) - 1
 	jsonRawCheckpoint := ctx.jsonRawCheckpoints[n]
-
 	ctx.jsonRaw = make([]byte, len(jsonRawCheckpoint))
 	copy(ctx.jsonRaw, jsonRawCheckpoint)
-
 	if remove {
 		ctx.jsonRawCheckpoints = ctx.jsonRawCheckpoints[:n]
 	}
diff --git a/pkg/engine/context/evaluate.go b/pkg/engine/context/evaluate.go
index 9a836eea9..dd30fa2b2 100644
--- a/pkg/engine/context/evaluate.go
+++ b/pkg/engine/context/evaluate.go
@@ -11,61 +11,46 @@ import (
 	jmespath "github.com/kyverno/kyverno/pkg/engine/jmespath"
 )
 
-//Query the JSON context with JMESPATH search path
-func (ctx *Context) Query(query string) (interface{}, error) {
+// Query the JSON context with JMESPATH search path
+func (ctx *context) Query(query string) (interface{}, error) {
 	query = strings.TrimSpace(query)
 	if query == "" {
 		return nil, fmt.Errorf("invalid query (nil)")
 	}
-
-	var emptyResult interface{}
-
 	// compile the query
 	queryPath, err := jmespath.New(query)
 	if err != nil {
-		ctx.log.Error(err, "incorrect query", "query", query)
-		return emptyResult, fmt.Errorf("incorrect query %s: %v", query, err)
+		logger.Error(err, "incorrect query", "query", query)
+		return nil, fmt.Errorf("incorrect query %s: %v", query, err)
 	}
-
 	// search
 	ctx.mutex.RLock()
 	defer ctx.mutex.RUnlock()
-
 	var data interface{}
 	if err := json.Unmarshal(ctx.jsonRaw, &data); err != nil {
-		return emptyResult, errors.Wrap(err, "failed to unmarshal context")
+		return nil, errors.Wrap(err, "failed to unmarshal context")
 	}
-
 	result, err := queryPath.Search(data)
 	if err != nil {
-		return emptyResult, errors.Wrap(err, "JMESPath query failed")
+		return nil, errors.Wrap(err, "JMESPath query failed")
 	}
-
 	return result, nil
 }
 
-func (ctx *Context) HasChanged(jmespath string) (bool, error) {
+func (ctx *context) HasChanged(jmespath string) (bool, error) {
 	objData, err := ctx.Query("request.object." + jmespath)
 	if err != nil {
 		return false, errors.Wrap(err, "failed to query request.object")
 	}
-
 	if objData == nil {
 		return false, fmt.Errorf("request.object.%s not found", jmespath)
 	}
-
 	oldObjData, err := ctx.Query("request.oldObject." + jmespath)
 	if err != nil {
 		return false, errors.Wrap(err, "failed to query request.object")
 	}
-
 	if oldObjData == nil {
 		return false, fmt.Errorf("request.oldObject.%s not found", jmespath)
 	}
-
-	if reflect.DeepEqual(objData, oldObjData) {
-		return false, nil
-	}
-
-	return true, nil
+	return !reflect.DeepEqual(objData, oldObjData), nil
 }
diff --git a/pkg/engine/context/imageutils.go b/pkg/engine/context/imageutils.go
index a74dd674e..8d1ee2d50 100644
--- a/pkg/engine/context/imageutils.go
+++ b/pkg/engine/context/imageutils.go
@@ -13,7 +13,6 @@ import (
 )
 
 type ImageInfo struct {
-
 	// Registry is the URL address of the image registry e.g. `docker.io`
 	Registry string `json:"registry,omitempty"`
 
@@ -43,124 +42,96 @@ func (i *ImageInfo) String() string {
 }
 
 type ContainerImage struct {
-	Name  string
-	Image *ImageInfo
+	ImageInfo
+	Name string
 }
 
 type Images struct {
-	InitContainers      map[string]*ImageInfo `json:"initContainers,omitempty"`
-	Containers          map[string]*ImageInfo `json:"containers"`
-	EphemeralContainers map[string]*ImageInfo `json:"ephemeralContainers"`
+	InitContainers      map[string]ImageInfo `json:"initContainers,omitempty"`
+	Containers          map[string]ImageInfo `json:"containers"`
+	EphemeralContainers map[string]ImageInfo `json:"ephemeralContainers"`
 }
 
-func newImages(initContainersImgs, containersImgs, ephemeralContainersImgs []*ContainerImage) *Images {
-	initContainers := make(map[string]*ImageInfo)
+func newImages(initContainersImgs, containersImgs, ephemeralContainersImgs []ContainerImage) Images {
+	initContainers := make(map[string]ImageInfo)
 	for _, resource := range initContainersImgs {
-		initContainers[resource.Name] = resource.Image
+		initContainers[resource.Name] = resource.ImageInfo
 	}
-
-	containers := make(map[string]*ImageInfo)
+	containers := make(map[string]ImageInfo)
 	for _, resource := range containersImgs {
-		containers[resource.Name] = resource.Image
+		containers[resource.Name] = resource.ImageInfo
 	}
-
-	ephemeralContainers := make(map[string]*ImageInfo)
+	ephemeralContainers := make(map[string]ImageInfo)
 	for _, resource := range ephemeralContainersImgs {
-		ephemeralContainers[resource.Name] = resource.Image
+		ephemeralContainers[resource.Name] = resource.ImageInfo
 	}
-
-	return &Images{
+	return Images{
 		InitContainers:      initContainers,
 		Containers:          containers,
 		EphemeralContainers: ephemeralContainers,
 	}
 }
 
-func extractImageInfo(resource *unstructured.Unstructured, log logr.Logger) (initContainersImgs, containersImgs, ephemeralContainersImgs []*ContainerImage) {
-	logger := log.WithName("extractImageInfo").WithValues("kind", resource.GetKind(), "ns", resource.GetNamespace(), "name", resource.GetName())
-
-	for _, tag := range []string{"initContainers", "containers", "ephemeralContainers"} {
-		switch resource.GetKind() {
-		case "Pod":
-			if containers, ok, _ := unstructured.NestedSlice(resource.UnstructuredContent(), "spec", tag); ok {
-				if tag == "initContainers" {
-					initContainersImgs = extractImageInfos(containers, initContainersImgs, "/spec/initContainers", logger)
-				} else if tag == "containers" {
-					containersImgs = extractImageInfos(containers, containersImgs, "/spec/containers", logger)
-				} else {
-					ephemeralContainersImgs = extractImageInfos(containers, ephemeralContainersImgs, "/spec/ephemeralContainers", logger)
-				}
-			}
-
-		case "CronJob":
-			if containers, ok, _ := unstructured.NestedSlice(resource.UnstructuredContent(), "spec", "jobTemplate", "spec", "template", "spec", tag); ok {
-				if tag == "initContainers" {
-					initContainersImgs = extractImageInfos(containers, initContainersImgs, "/spec/jobTemplate/spec/template/spec/initContainers", logger)
-				} else if tag == "containers" {
-					containersImgs = extractImageInfos(containers, containersImgs, "/spec/jobTemplate/spec/template/spec/containers", logger)
-				} else {
-					ephemeralContainersImgs = extractImageInfos(containers, ephemeralContainersImgs, "/spec/jobTemplate/spec/template/spec/ephemeralContainers", logger)
-				}
-			}
+type imageExtractor struct {
+	fields []string
+}
 
-		// handles "Deployment", "DaemonSet", "Job", "StatefulSet", and custom controllers with the same pattern
-		default:
-			if containers, ok, _ := unstructured.NestedSlice(resource.UnstructuredContent(), "spec", "template", "spec", tag); ok {
-				if tag == "initContainers" {
-					initContainersImgs = extractImageInfos(containers, initContainersImgs, "/spec/template/spec/initContainers", logger)
-				} else if tag == "containers" {
-					containersImgs = extractImageInfos(containers, containersImgs, "/spec/template/spec/containers", logger)
-				} else {
-					ephemeralContainersImgs = extractImageInfos(containers, ephemeralContainersImgs, "/spec/template/spec/ephemeralContainers", logger)
-				}
-			}
-		}
+func (i imageExtractor) extract(tag string, resource *unstructured.Unstructured) []ContainerImage {
+	f := append(i.fields[:len(i.fields):len(i.fields)], tag)
+	if containers, ok, _ := unstructured.NestedSlice(resource.UnstructuredContent(), f...); ok {
+		return extractImageInfos(containers, "/"+strings.Join(f, "/"))
 	}
+	return nil
+}
+
+var extractors = map[string]imageExtractor{
+	"Pod":         {[]string{"spec"}},
+	"CronJob":     {[]string{"spec", "jobTemplate", "spec", "template", "spec"}},
+	"Deployment":  {[]string{"spec", "template", "spec"}},
+	"DaemonSet":   {[]string{"spec", "template", "spec"}},
+	"Job":         {[]string{"spec", "template", "spec"}},
+	"StatefulSet": {[]string{"spec", "template", "spec"}},
+}
 
+func extractImageInfo(resource *unstructured.Unstructured, log logr.Logger) (initContainersImgs, containersImgs, ephemeralContainersImgs []ContainerImage) {
+	extractor := extractors[resource.GetKind()]
+	initContainersImgs = extractor.extract("initContainers", resource)
+	containersImgs = extractor.extract("containers", resource)
+	ephemeralContainersImgs = extractor.extract("ephemeralContainers", resource)
 	return
 }
 
-func extractImageInfos(containers []interface{}, images []*ContainerImage, jsonPath string, log logr.Logger) []*ContainerImage {
+func extractImageInfos(containers []interface{}, jsonPath string) []ContainerImage {
 	img, err := convertToImageInfo(containers, jsonPath)
 	if err != nil {
-		log.Error(err, "failed to extract image info", "element", containers)
+		logger.Error(err, "failed to extract image info", "element", containers)
 	}
-
-	return append(images, img...)
+	return img
 }
 
-func convertToImageInfo(containers []interface{}, jsonPath string) (images []*ContainerImage, err error) {
+func convertToImageInfo(containers []interface{}, jsonPath string) (images []ContainerImage, err error) {
 	var errs []string
 	var index = 0
 	for _, ctr := range containers {
 		if container, ok := ctr.(map[string]interface{}); ok {
 			var name, image string
-
 			name = container["name"].(string)
 			if _, ok := container["image"]; ok {
 				image = container["image"].(string)
 			}
-
 			jp := strings.Join([]string{jsonPath, strconv.Itoa(index), "image"}, "/")
 			imageInfo, err := newImageInfo(image, jp)
 			if err != nil {
 				errs = append(errs, err.Error())
 				continue
 			}
-
-			images = append(images, &ContainerImage{
-				Name:  name,
-				Image: imageInfo,
-			})
+			images = append(images, ContainerImage{*imageInfo, name})
 		}
-
 		index++
 	}
-
 	if len(errs) == 0 {
 		return images, nil
 	}
-
 	return images, errors.Errorf("%s", strings.Join(errs, ";"))
 }
 
@@ -170,27 +141,22 @@ func newImageInfo(image, jsonPointer string) (*ImageInfo, error) {
 	if err != nil {
 		return nil, errors.Wrapf(err, "bad image: %s", image)
 	}
-
 	var registry, path, name, tag, digest string
 	if named, ok := ref.(reference.Named); ok {
 		registry = reference.Domain(named)
 		path = reference.Path(named)
 		name = path[strings.LastIndex(path, "/")+1:]
 	}
-
 	if tagged, ok := ref.(reference.Tagged); ok {
 		tag = tagged.Tag()
 	}
-
 	if digested, ok := ref.(reference.Digested); ok {
 		digest = digested.Digest().String()
 	}
-
 	// set default tag - the domain is set via addDefaultDomain before parsing
 	if digest == "" && tag == "" {
 		tag = "latest"
 	}
-
 	return &ImageInfo{
 		Registry:    registry,
 		Name:        name,
@@ -206,41 +172,34 @@ func addDefaultDomain(name string) string {
 	if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost" && strings.ToLower(name[:i]) == name[:i]) {
 		return "docker.io/" + name
 	}
-
 	return name
 }
 
 // MutateResourceWithImageInfo will set images to their canonical form so that they can be compared
 // in a predictable manner. This sets the default registry as `docker.io` and the tag as `latest` if
 // these are missing.
-func MutateResourceWithImageInfo(raw []byte, ctx *Context) error {
+func MutateResourceWithImageInfo(raw []byte, ctx Interface) error {
 	images := ctx.ImageInfo()
 	if images == nil {
 		return nil
 	}
-
 	buildJSONPatch := func(op, path, value string) []byte {
 		p := fmt.Sprintf(`{ "op": "%s", "path": "%s", "value":"%s" }`, op, path, value)
 		return []byte(p)
 	}
-
 	var patches [][]byte
 	for _, info := range images.Containers {
 		patches = append(patches, buildJSONPatch("replace", info.JSONPointer, info.String()))
 	}
-
 	for _, info := range images.InitContainers {
 		patches = append(patches, buildJSONPatch("replace", info.JSONPointer, info.String()))
 	}
-
 	for _, info := range images.EphemeralContainers {
 		patches = append(patches, buildJSONPatch("replace", info.JSONPointer, info.String()))
 	}
-
 	patchedResource, err := engineutils.ApplyPatches(raw, patches)
 	if err != nil {
 		return err
 	}
-
-	return ctx.AddResource(patchedResource)
+	return AddResource(ctx, patchedResource)
 }
diff --git a/pkg/engine/context/utils.go b/pkg/engine/context/utils.go
new file mode 100644
index 000000000..8b51daa83
--- /dev/null
+++ b/pkg/engine/context/utils.go
@@ -0,0 +1,64 @@
+package context
+
+import (
+	"encoding/json"
+)
+
+// AddJSONObject merges json data
+func AddJSONObject(ctx Interface, data interface{}) error {
+	jsonBytes, err := json.Marshal(data)
+	if err != nil {
+		return err
+	}
+	return ctx.addJSON(jsonBytes)
+}
+
+func AddResource(ctx Interface, dataRaw []byte) error {
+	var data map[string]interface{}
+	if err := json.Unmarshal(dataRaw, &data); err != nil {
+		logger.Error(err, "failed to unmarshal the resource")
+		return err
+	}
+	return ctx.AddResource(data)
+}
+
+func ReplaceResource(ctx Interface, data map[string]interface{}) error {
+	if err := ctx.AddResource(nil); err != nil {
+		return err
+	}
+	return ctx.AddResource(data)
+}
+
+func AddOldResource(ctx Interface, dataRaw []byte) error {
+	var data map[string]interface{}
+	if err := json.Unmarshal(dataRaw, &data); err != nil {
+		logger.Error(err, "failed to unmarshal the resource")
+		return err
+	}
+	return ctx.AddOldResource(data)
+}
+
+func ReplaceOldResource(ctx Interface, data map[string]interface{}) error {
+	if err := ctx.AddOldResource(nil); err != nil {
+		return err
+	}
+	return ctx.AddOldResource(data)
+}
+
+func addToContext(ctx *context, data interface{}, tags ...string) error {
+	dataRaw, err := json.Marshal(push(data, tags...))
+	if err != nil {
+		logger.Error(err, "failed to marshal the resource")
+		return err
+	}
+	return ctx.addJSON(dataRaw)
+}
+
+func push(data interface{}, tags ...string) interface{} {
+	for i := len(tags) - 1; i >= 0; i-- {
+		data = map[string]interface{}{
+			tags[i]: data,
+		}
+	}
+	return data
+}
diff --git a/pkg/engine/forceMutate.go b/pkg/engine/forceMutate.go
index 595624537..010f53fd9 100644
--- a/pkg/engine/forceMutate.go
+++ b/pkg/engine/forceMutate.go
@@ -14,7 +14,7 @@ import (
 
 // ForceMutate does not check any conditions, it simply mutates the given resource
 // It is used to validate mutation logic, and for tests.
-func ForceMutate(ctx *context.Context, policy kyverno.PolicyInterface, resource unstructured.Unstructured) (unstructured.Unstructured, error) {
+func ForceMutate(ctx context.Interface, policy kyverno.PolicyInterface, resource unstructured.Unstructured) (unstructured.Unstructured, error) {
 	logger := log.Log.WithName("EngineForceMutate").WithValues("policy", policy.GetName(), "kind", resource.GetKind(),
 		"namespace", resource.GetNamespace(), "name", resource.GetName())
 
diff --git a/pkg/engine/imageVerify.go b/pkg/engine/imageVerify.go
index aae53700b..2ee6b198e 100644
--- a/pkg/engine/imageVerify.go
+++ b/pkg/engine/imageVerify.go
@@ -125,7 +125,7 @@ type imageVerifier struct {
 	resp          *response.EngineResponse
 }
 
-func (iv *imageVerifier) verify(imageVerify *v1.ImageVerification, images map[string]*context.ImageInfo) {
+func (iv *imageVerifier) verify(imageVerify *v1.ImageVerification, images map[string]context.ImageInfo) {
 	imagePattern := imageVerify.Image
 
 	for _, imageInfo := range images {
@@ -167,7 +167,7 @@ func getSignatureRepository(imageVerify *v1.ImageVerification) string {
 	return repository
 }
 
-func (iv *imageVerifier) verifySignature(imageVerify *v1.ImageVerification, imageInfo *context.ImageInfo) (*response.RuleResponse, string) {
+func (iv *imageVerifier) verifySignature(imageVerify *v1.ImageVerification, imageInfo context.ImageInfo) (*response.RuleResponse, string) {
 	image := imageInfo.String()
 	iv.logger.Info("verifying image", "image", image)
 
@@ -219,7 +219,7 @@ func (iv *imageVerifier) verifySignature(imageVerify *v1.ImageVerification, imag
 	return ruleResp, digest
 }
 
-func (iv *imageVerifier) patchDigest(imageInfo *context.ImageInfo, digest string, ruleResp *response.RuleResponse) {
+func (iv *imageVerifier) patchDigest(imageInfo context.ImageInfo, digest string, ruleResp *response.RuleResponse) {
 	if imageInfo.Digest == "" {
 		patch, err := makeAddDigestPatch(imageInfo, digest)
 		if err != nil {
@@ -231,7 +231,7 @@ func (iv *imageVerifier) patchDigest(imageInfo *context.ImageInfo, digest string
 	}
 }
 
-func makeAddDigestPatch(imageInfo *context.ImageInfo, digest string) ([]byte, error) {
+func makeAddDigestPatch(imageInfo context.ImageInfo, digest string) ([]byte, error) {
 	var patch = make(map[string]interface{})
 	patch["op"] = "replace"
 	patch["path"] = imageInfo.JSONPointer
@@ -239,7 +239,7 @@ func makeAddDigestPatch(imageInfo *context.ImageInfo, digest string) ([]byte, er
 	return json.Marshal(patch)
 }
 
-func (iv *imageVerifier) attestImage(imageVerify *v1.ImageVerification, imageInfo *context.ImageInfo) *response.RuleResponse {
+func (iv *imageVerifier) attestImage(imageVerify *v1.ImageVerification, imageInfo context.ImageInfo) *response.RuleResponse {
 	image := imageInfo.String()
 	start := time.Now()
 
@@ -291,7 +291,7 @@ func buildStatementMap(statements []map[string]interface{}) map[string][]map[str
 	return results
 }
 
-func (iv *imageVerifier) checkAttestations(a *v1.Attestation, s map[string]interface{}, img *context.ImageInfo) (bool, error) {
+func (iv *imageVerifier) checkAttestations(a *v1.Attestation, s map[string]interface{}, img context.ImageInfo) (bool, error) {
 	if len(a.Conditions) == 0 {
 		return true, nil
 	}
@@ -304,22 +304,11 @@ func (iv *imageVerifier) checkAttestations(a *v1.Attestation, s map[string]inter
 		return false, fmt.Errorf("failed to extract predicate from statement: %v", s)
 	}
 
-	if err := iv.policyContext.JSONContext.AddJSONObject(predicate); err != nil {
+	if err := context.AddJSONObject(iv.policyContext.JSONContext, predicate); err != nil {
 		return false, errors.Wrapf(err, fmt.Sprintf("failed to add Statement to the context %v", s))
 	}
 
-	imgMap := map[string]interface{}{
-		"image": map[string]interface{}{
-			"image":    img.String(),
-			"registry": img.Registry,
-			"path":     img.Path,
-			"name":     img.Name,
-			"tag":      img.Tag,
-			"digest":   img.Digest,
-		},
-	}
-
-	if err := iv.policyContext.JSONContext.AddJSONObject(imgMap); err != nil {
+	if err := iv.policyContext.JSONContext.AddImageInfo(&img); err != nil {
 		return false, errors.Wrapf(err, fmt.Sprintf("failed to add image to the context %v", s))
 	}
 
diff --git a/pkg/engine/jsonContext.go b/pkg/engine/jsonContext.go
index 5b00de2a4..97eb0a641 100644
--- a/pkg/engine/jsonContext.go
+++ b/pkg/engine/jsonContext.go
@@ -9,7 +9,6 @@ import (
 	"github.com/google/go-containerregistry/pkg/name"
 	"github.com/google/go-containerregistry/pkg/v1/remote"
 	kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
-	pkgcommon "github.com/kyverno/kyverno/pkg/common"
 	jmespath "github.com/kyverno/kyverno/pkg/engine/jmespath"
 	"github.com/kyverno/kyverno/pkg/engine/variables"
 	"github.com/kyverno/kyverno/pkg/kyverno/store"
@@ -44,9 +43,7 @@ func LoadContext(logger logr.Logger, contextEntries []kyverno.ContextEntry, ctx
 					value = string(newVal)
 				}
 
-				jsonData := pkgcommon.VariableToJSON(key, value)
-
-				if err := ctx.JSONContext.AddJSON(jsonData); err != nil {
+				if err := ctx.JSONContext.AddVariable(key, value); err != nil {
 					return err
 				}
 			}
@@ -81,7 +78,11 @@ func loadImageData(logger logr.Logger, entry kyverno.ContextEntry, ctx *PolicyCo
 	if err != nil {
 		return err
 	}
-	if err := ctx.JSONContext.AddJSONObject(imageData); err != nil {
+	jsonBytes, err := json.Marshal(imageData)
+	if err != nil {
+		return err
+	}
+	if err := ctx.JSONContext.AddContextEntry(entry.Name, jsonBytes); err != nil {
 		return fmt.Errorf("failed to add resource data to context: contextEntry: %v, error: %v", entry, err)
 	}
 	return nil
@@ -110,9 +111,7 @@ func fetchImageData(logger logr.Logger, entry kyverno.ContextEntry, ctx *PolicyC
 			return nil, fmt.Errorf("failed to apply JMESPath (%s) results to context entry %s, error: %v", entry.ImageRegistry.JMESPath, entry.Name, err)
 		}
 	}
-	return map[string]interface{}{
-		entry.Name: imageData,
-	}, nil
+	return imageData, nil
 }
 
 func fetchImageDataMap(ref string) (interface{}, error) {
@@ -169,7 +168,7 @@ func loadAPIData(logger logr.Logger, entry kyverno.ContextEntry, ctx *PolicyCont
 	}
 
 	if entry.APICall.JMESPath == "" {
-		err = ctx.JSONContext.AddJSON(jsonData)
+		err = ctx.JSONContext.AddContextEntry(entry.Name, jsonData)
 		if err != nil {
 			return fmt.Errorf("failed to add resource data to context: contextEntry: %v, error: %v", entry, err)
 		}
@@ -187,19 +186,17 @@ func loadAPIData(logger logr.Logger, entry kyverno.ContextEntry, ctx *PolicyCont
 		return err
 	}
 
-	contextNamedData := make(map[string]interface{})
-	contextNamedData[entry.Name] = results
-	contextData, err := json.Marshal(contextNamedData)
+	contextData, err := json.Marshal(results)
 	if err != nil {
-		return fmt.Errorf("failed to marshall data %v for context entry %v: %v", contextNamedData, entry, err)
+		return fmt.Errorf("failed to marshall data %v for context entry %v: %v", contextData, entry, err)
 	}
 
-	err = ctx.JSONContext.AddJSON(contextData)
+	err = ctx.JSONContext.AddContextEntry(entry.Name, contextData)
 	if err != nil {
 		return fmt.Errorf("failed to add JMESPath (%s) results to context, error: %v", entry.APICall.JMESPath, err)
 	}
 
-	logger.Info("added APICall context entry", "data", contextNamedData)
+	logger.Info("added APICall context entry", "data", contextData)
 	return nil
 }
 
@@ -286,7 +283,7 @@ func loadConfigMap(logger logr.Logger, entry kyverno.ContextEntry, ctx *PolicyCo
 		return fmt.Errorf("failed to retrieve config map for context entry %s: %v", entry.Name, err)
 	}
 
-	err = ctx.JSONContext.AddJSON(data)
+	err = ctx.JSONContext.AddContextEntry(entry.Name, data)
 	if err != nil {
 		return fmt.Errorf("failed to add config map for context entry %s: %v", entry.Name, err)
 	}
@@ -324,9 +321,7 @@ func fetchConfigMap(logger logr.Logger, entry kyverno.ContextEntry, ctx *PolicyC
 	// extract configmap data
 	contextData["data"] = unstructuredObj["data"]
 	contextData["metadata"] = unstructuredObj["metadata"]
-	contextNamedData := make(map[string]interface{})
-	contextNamedData[entry.Name] = contextData
-	data, err := json.Marshal(contextNamedData)
+	data, err := json.Marshal(contextData)
 	if err != nil {
 		return nil, fmt.Errorf("failed to unmarshal configmap %s/%s: %v", namespace, name, err)
 	}
diff --git a/pkg/engine/mutate/mutation.go b/pkg/engine/mutate/mutation.go
index 6334f1bd4..ab55dae3d 100644
--- a/pkg/engine/mutate/mutation.go
+++ b/pkg/engine/mutate/mutation.go
@@ -35,7 +35,7 @@ func newResponse(status response.RuleStatus, resource unstructured.Unstructured,
 	}
 }
 
-func Mutate(rule *kyverno.Rule, ctx *context.Context, resource unstructured.Unstructured, logger logr.Logger) *Response {
+func Mutate(rule *kyverno.Rule, ctx context.Interface, resource unstructured.Unstructured, logger logr.Logger) *Response {
 	updatedRule, err := variables.SubstituteAllInRule(logger, ctx, *rule)
 	if err != nil {
 		return newErrorResponse("variable substitution failed", err)
@@ -56,14 +56,14 @@ func Mutate(rule *kyverno.Rule, ctx *context.Context, resource unstructured.Unst
 		return newResponse(response.RuleStatusSkip, resource, nil, "no patches applied")
 	}
 
-	if err := ctx.AddResourceAsObject(patchedResource.Object); err != nil {
+	if err := ctx.AddResource(patchedResource.Object); err != nil {
 		return newErrorResponse("failed to update patched resource in the JSON context", err)
 	}
 
 	return newResponse(response.RuleStatusPass, patchedResource, resp.Patches, resp.Message)
 }
 
-func ForEach(name string, foreach *kyverno.ForEachMutation, ctx *context.Context, resource unstructured.Unstructured, logger logr.Logger) *Response {
+func ForEach(name string, foreach *kyverno.ForEachMutation, ctx context.Interface, resource unstructured.Unstructured, logger logr.Logger) *Response {
 	fe, err := substituteAllInForEach(foreach, ctx, logger)
 	if err != nil {
 		return newErrorResponse("variable substitution failed", err)
@@ -83,14 +83,14 @@ func ForEach(name string, foreach *kyverno.ForEachMutation, ctx *context.Context
 		return newResponse(response.RuleStatusSkip, unstructured.Unstructured{}, nil, "no patches applied")
 	}
 
-	if err := ctx.AddResourceAsObject(patchedResource.Object); err != nil {
+	if err := ctx.AddResource(patchedResource.Object); err != nil {
 		return newErrorResponse("failed to update patched resource in the JSON context", err)
 	}
 
 	return newResponse(response.RuleStatusPass, patchedResource, resp.Patches, resp.Message)
 }
 
-func substituteAllInForEach(fe *kyverno.ForEachMutation, ctx *context.Context, logger logr.Logger) (*kyverno.ForEachMutation, error) {
+func substituteAllInForEach(fe *kyverno.ForEachMutation, ctx context.Interface, logger logr.Logger) (*kyverno.ForEachMutation, error) {
 	jsonObj, err := utils.ToMap(fe)
 	if err != nil {
 		return nil, err
@@ -114,7 +114,7 @@ func substituteAllInForEach(fe *kyverno.ForEachMutation, ctx *context.Context, l
 	return &updatedForEach, nil
 }
 
-func NewPatcher(name string, strategicMergePatch apiextensions.JSON, jsonPatch string, r unstructured.Unstructured, ctx *context.Context, logger logr.Logger) patch.Patcher {
+func NewPatcher(name string, strategicMergePatch apiextensions.JSON, jsonPatch string, r unstructured.Unstructured, ctx context.Interface, logger logr.Logger) patch.Patcher {
 	if strategicMergePatch != nil {
 		return patch.NewPatchStrategicMerge(name, strategicMergePatch, r, ctx, logger)
 	}
diff --git a/pkg/engine/mutation.go b/pkg/engine/mutation.go
index 262b52953..9008910b0 100644
--- a/pkg/engine/mutation.go
+++ b/pkg/engine/mutation.go
@@ -61,7 +61,7 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) {
 		resource, err := policyContext.JSONContext.Query("request.object")
 		policyContext.JSONContext.Reset()
 		if err == nil && resource != nil {
-			if err := ctx.AddResourceAsObject(resource.(map[string]interface{})); err != nil {
+			if err := ctx.AddResource(resource.(map[string]interface{})); err != nil {
 				logger.Error(err, "unable to update resource object")
 			}
 		} else {
diff --git a/pkg/engine/policyContext.go b/pkg/engine/policyContext.go
index b7e420036..23eaaa5e8 100644
--- a/pkg/engine/policyContext.go
+++ b/pkg/engine/policyContext.go
@@ -33,7 +33,7 @@ type PolicyContext struct {
 	ExcludeResourceFunc func(kind, namespace, name string) bool
 
 	// JSONContext is the variable context
-	JSONContext *context.Context
+	JSONContext context.Interface
 
 	// NamespaceLabels stores the label of namespace to be processed by namespace selector
 	NamespaceLabels map[string]string
diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go
index 601b570d9..273c4ac57 100644
--- a/pkg/engine/validation.go
+++ b/pkg/engine/validation.go
@@ -10,6 +10,7 @@ import (
 	kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
 	"github.com/kyverno/kyverno/pkg/autogen"
 	"github.com/kyverno/kyverno/pkg/engine/common"
+	"github.com/kyverno/kyverno/pkg/engine/context"
 	"github.com/pkg/errors"
 	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
 
@@ -118,11 +119,11 @@ func validateOldObject(log logr.Logger, ctx *PolicyContext, rule *kyverno.Rule)
 	ctxCopy.NewResource = *ctxCopy.OldResource.DeepCopy()
 	ctxCopy.OldResource = unstructured.Unstructured{}
 
-	if err := ctxCopy.JSONContext.ReplaceResourceAsObject(ctxCopy.NewResource.Object); err != nil {
+	if err := context.ReplaceResource(ctxCopy.JSONContext, ctxCopy.NewResource.Object); err != nil {
 		return nil, errors.Wrapf(err, "failed to replace object in the JSON context")
 	}
 
-	if err := ctxCopy.JSONContext.ReplaceResourceAsOldObject(ctxCopy.OldResource.Object); err != nil {
+	if err := context.ReplaceOldResource(ctxCopy.JSONContext, ctxCopy.OldResource.Object); err != nil {
 		return nil, errors.Wrapf(err, "failed to replace old object in the JSON context")
 	}
 
@@ -322,22 +323,14 @@ func addElementToContext(ctx *PolicyContext, e interface{}, elementIndex int, el
 	if err != nil {
 		return err
 	}
-
-	jsonData := map[string]interface{}{
-		"element":      data,
-		"elementIndex": elementIndex,
-	}
-
-	if err := ctx.JSONContext.AddJSONObject(jsonData); err != nil {
+	if err := ctx.JSONContext.AddElement(data, elementIndex); err != nil {
 		return errors.Wrapf(err, "failed to add element (%v) to JSON context", e)
 	}
-
 	if elementScope {
 		u := unstructured.Unstructured{}
 		u.SetUnstructuredContent(data)
 		ctx.Element = u
 	}
-
 	return nil
 }
 
diff --git a/pkg/engine/variables/vars.go b/pkg/engine/variables/vars.go
index 9516397dd..38331c991 100644
--- a/pkg/engine/variables/vars.go
+++ b/pkg/engine/variables/vars.go
@@ -189,7 +189,7 @@ func substituteAll(log logr.Logger, ctx context.EvalInterface, document interfac
 	return substituteVars(log, ctx, document, resolver)
 }
 
-func SubstituteAllForceMutate(log logr.Logger, ctx *context.Context, typedRule kyverno.Rule) (_ kyverno.Rule, err error) {
+func SubstituteAllForceMutate(log logr.Logger, ctx context.Interface, typedRule kyverno.Rule) (_ kyverno.Rule, err error) {
 	var rule interface{}
 
 	rule, err = DocumentToUntyped(typedRule)
diff --git a/pkg/generate/generate.go b/pkg/generate/generate.go
index 115a6f251..4309e5e84 100644
--- a/pkg/generate/generate.go
+++ b/pkg/generate/generate.go
@@ -161,13 +161,7 @@ func (c *Controller) applyGenerate(resource unstructured.Unstructured, gr kyvern
 		return nil, false, err
 	}
 
-	resourceRaw, err := resource.MarshalJSON()
-	if err != nil {
-		logger.Error(err, "failed to marshal resource")
-		return nil, false, err
-	}
-
-	err = ctx.AddResource(resourceRaw)
+	err = ctx.AddResource(resource.Object)
 	if err != nil {
 		logger.Error(err, "failed to load resource in context")
 		return nil, false, err
@@ -185,7 +179,7 @@ func (c *Controller) applyGenerate(resource unstructured.Unstructured, gr kyvern
 		return nil, false, err
 	}
 
-	if err := ctx.AddImageInfo(&resource); err != nil {
+	if err := ctx.AddImageInfos(&resource); err != nil {
 		logger.Error(err, "unable to add image info to variables context")
 	}
 
diff --git a/pkg/kyverno/common/common.go b/pkg/kyverno/common/common.go
index 70a677968..063f933b4 100644
--- a/pkg/kyverno/common/common.go
+++ b/pkg/kyverno/common/common.go
@@ -21,7 +21,6 @@ import (
 	"github.com/go-logr/logr"
 	v1 "github.com/kyverno/kyverno/api/kyverno/v1"
 	report "github.com/kyverno/kyverno/api/policyreport/v1alpha2"
-	pkgcommon "github.com/kyverno/kyverno/pkg/common"
 	client "github.com/kyverno/kyverno/pkg/dclient"
 	"github.com/kyverno/kyverno/pkg/engine"
 	"github.com/kyverno/kyverno/pkg/engine/context"
@@ -512,9 +511,9 @@ OuterLoop:
 	ctx := context.NewContext()
 
 	if operationIsDelete {
-		err = ctx.AddResourceInOldObject(resourceRaw)
+		err = context.AddOldResource(ctx, resourceRaw)
 	} else {
-		err = ctx.AddResourceAsObject(updated_resource.Object)
+		err = context.AddResource(ctx, resourceRaw)
 	}
 
 	if err != nil {
@@ -522,14 +521,13 @@ OuterLoop:
 	}
 
 	for key, value := range variables {
-		jsonData := pkgcommon.VariableToJSON(key, value)
-		err = ctx.AddJSON(jsonData)
+		err = ctx.AddVariable(key, value)
 		if err != nil {
 			log.Log.Error(err, "failed to add variable to context")
 		}
 	}
 
-	if err := ctx.AddImageInfo(resource); err != nil {
+	if err := ctx.AddImageInfos(resource); err != nil {
 		if err != nil {
 			log.Log.Error(err, "failed to add image variables to context")
 		}
diff --git a/pkg/policy/apply.go b/pkg/policy/apply.go
index bf9c7d099..d50cb2300 100644
--- a/pkg/policy/apply.go
+++ b/pkg/policy/apply.go
@@ -38,7 +38,7 @@ func applyPolicy(policy kyverno.PolicyInterface, resource unstructured.Unstructu
 	var err error
 
 	ctx := context.NewContext()
-	err = ctx.AddResource(transformResource(resource))
+	err = context.AddResource(ctx, transformResource(resource))
 	if err != nil {
 		logger.Error(err, "failed to add transform resource to ctx")
 	}
@@ -48,7 +48,7 @@ func applyPolicy(policy kyverno.PolicyInterface, resource unstructured.Unstructu
 		logger.Error(err, "failed to add namespace to ctx")
 	}
 
-	if err := ctx.AddImageInfo(&resource); err != nil {
+	if err := ctx.AddImageInfos(&resource); err != nil {
 		logger.Error(err, "unable to add image info to variables context")
 	}
 
@@ -72,7 +72,7 @@ func applyPolicy(policy kyverno.PolicyInterface, resource unstructured.Unstructu
 	return engineResponses
 }
 
-func mutation(policy kyverno.PolicyInterface, resource unstructured.Unstructured, log logr.Logger, jsonContext *context.Context, namespaceLabels map[string]string) (*response.EngineResponse, error) {
+func mutation(policy kyverno.PolicyInterface, resource unstructured.Unstructured, log logr.Logger, jsonContext context.Interface, namespaceLabels map[string]string) (*response.EngineResponse, error) {
 
 	policyContext := &engine.PolicyContext{
 		Policy:          policy,
diff --git a/pkg/webhooks/common.go b/pkg/webhooks/common.go
index 12b825c39..b2198bf66 100644
--- a/pkg/webhooks/common.go
+++ b/pkg/webhooks/common.go
@@ -157,7 +157,7 @@ func excludeKyvernoResources(kind string) bool {
 	}
 }
 
-func newVariablesContext(request *admissionv1.AdmissionRequest, userRequestInfo *kyverno.RequestInfo) (*enginectx.Context, error) {
+func newVariablesContext(request *admissionv1.AdmissionRequest, userRequestInfo *kyverno.RequestInfo) (enginectx.Interface, error) {
 	ctx := enginectx.NewContext()
 	if err := ctx.AddRequest(request); err != nil {
 		return nil, errors.Wrap(err, "failed to load incoming request in context")
diff --git a/pkg/webhooks/generation.go b/pkg/webhooks/generation.go
index 1494a1fcf..6d4bbf360 100644
--- a/pkg/webhooks/generation.go
+++ b/pkg/webhooks/generation.go
@@ -45,7 +45,7 @@ func (ws *WebhookServer) applyGeneratePolicies(request *admissionv1.AdmissionReq
 func (ws *WebhookServer) handleGenerate(
 	request *admissionv1.AdmissionRequest,
 	policies []kyverno.PolicyInterface,
-	ctx *context.Context,
+	ctx context.Interface,
 	userRequestInfo kyverno.RequestInfo,
 	dynamicConfig config.Interface,
 	admissionRequestTimestamp int64,
diff --git a/pkg/webhooks/handlers.go b/pkg/webhooks/handlers.go
index 4d5f46f22..412513c7b 100644
--- a/pkg/webhooks/handlers.go
+++ b/pkg/webhooks/handlers.go
@@ -195,7 +195,7 @@ func (ws *WebhookServer) resourceValidation(request *admissionv1.AdmissionReques
 	if err != nil {
 		return errorResponse(logger, err, "failed create parse resource")
 	}
-	if err := ctx.AddImageInfo(&newResource); err != nil {
+	if err := ctx.AddImageInfos(&newResource); err != nil {
 		return errorResponse(logger, err, "failed add image information to policy rule context")
 	}
 	policyContext := &engine.PolicyContext{
diff --git a/pkg/webhooks/server.go b/pkg/webhooks/server.go
index 6c6b8405b..ab63fb2cd 100644
--- a/pkg/webhooks/server.go
+++ b/pkg/webhooks/server.go
@@ -228,7 +228,7 @@ func (ws *WebhookServer) buildPolicyContext(request *admissionv1.AdmissionReques
 		return nil, errors.Wrap(err, "failed to convert raw resource to unstructured format")
 	}
 
-	if err := ctx.AddImageInfo(&resource); err != nil {
+	if err := ctx.AddImageInfos(&resource); err != nil {
 		return nil, errors.Wrap(err, "failed to add image information to the policy rule context")
 	}
 
diff --git a/pkg/webhooks/validate_audit.go b/pkg/webhooks/validate_audit.go
index 1da0bf17d..33e9aa9eb 100644
--- a/pkg/webhooks/validate_audit.go
+++ b/pkg/webhooks/validate_audit.go
@@ -181,7 +181,7 @@ func (h *auditHandler) process(request *admissionv1.AdmissionRequest) error {
 		return errors.Wrap(err, "failed create parse resource")
 	}
 
-	if err := ctx.AddImageInfo(&newResource); err != nil {
+	if err := ctx.AddImageInfos(&newResource); err != nil {
 		return errors.Wrap(err, "failed add image information to policy rule context\"")
 	}
 


Test patch:
diff --git a/pkg/engine/context/context_test.go b/pkg/engine/context/context_test.go
index 10f172824..b72bbe68e 100644
--- a/pkg/engine/context/context_test.go
+++ b/pkg/engine/context/context_test.go
@@ -55,7 +55,7 @@ func Test_addResourceAndUserContext(t *testing.T) {
 
 	var expectedResult string
 	ctx := NewContext()
-	err = ctx.AddResource(rawResource)
+	err = AddResource(ctx, rawResource)
 	if err != nil {
 		t.Error(err)
 	}
diff --git a/pkg/engine/context/evaluate_test.go b/pkg/engine/context/evaluate_test.go
index 37ed26bd9..9816cf5ae 100644
--- a/pkg/engine/context/evaluate_test.go
+++ b/pkg/engine/context/evaluate_test.go
@@ -55,7 +55,7 @@ func TestMissingObject(t *testing.T) {
 	assert.Error(t, err)
 }
 
-func createTestContext(obj, oldObj string) *Context {
+func createTestContext(obj, oldObj string) Interface {
 	request := &admissionv1.AdmissionRequest{}
 	request.Operation = "UPDATE"
 	request.Object.Raw = []byte(obj)
diff --git a/pkg/engine/context/imageutils_test.go b/pkg/engine/context/imageutils_test.go
index 83030c810..1b43eb7f7 100644
--- a/pkg/engine/context/imageutils_test.go
+++ b/pkg/engine/context/imageutils_test.go
@@ -11,31 +11,31 @@ import (
 func Test_extractImageInfo(t *testing.T) {
 	tests := []struct {
 		raw                 []byte
-		containers          []*ContainerImage
-		initContainers      []*ContainerImage
-		ephemeralContainers []*ContainerImage
+		containers          []ContainerImage
+		initContainers      []ContainerImage
+		ephemeralContainers []ContainerImage
 	}{
 		{
 			raw:                 []byte(`{"apiVersion": "v1","kind": "Pod","metadata": {"name": "myapp"},"spec": {"initContainers": [{"name": "init","image": "index.docker.io/busybox:v1.2.3"}],"containers": [{"name": "nginx","image": "nginx:latest"}], "ephemeralContainers": [{"name": "ephemeral", "image":"test/nginx:latest"}]}}`),
-			initContainers:      []*ContainerImage{{Name: "init", Image: &ImageInfo{Registry: "index.docker.io", Name: "busybox", Path: "busybox", Tag: "v1.2.3", JSONPointer: "/spec/initContainers/0/image"}}},
-			containers:          []*ContainerImage{{Name: "nginx", Image: &ImageInfo{Registry: "docker.io", Name: "nginx", Path: "nginx", Tag: "latest", JSONPointer: "/spec/containers/0/image"}}},
-			ephemeralContainers: []*ContainerImage{{Name: "ephemeral", Image: &ImageInfo{Registry: "docker.io", Name: "nginx", Path: "test/nginx", Tag: "latest", JSONPointer: "/spec/ephemeralContainers/0/image"}}},
+			initContainers:      []ContainerImage{{Name: "init", ImageInfo: ImageInfo{Registry: "index.docker.io", Name: "busybox", Path: "busybox", Tag: "v1.2.3", JSONPointer: "/spec/initContainers/0/image"}}},
+			containers:          []ContainerImage{{Name: "nginx", ImageInfo: ImageInfo{Registry: "docker.io", Name: "nginx", Path: "nginx", Tag: "latest", JSONPointer: "/spec/containers/0/image"}}},
+			ephemeralContainers: []ContainerImage{{Name: "ephemeral", ImageInfo: ImageInfo{Registry: "docker.io", Name: "nginx", Path: "test/nginx", Tag: "latest", JSONPointer: "/spec/ephemeralContainers/0/image"}}},
 		},
 		{
 			raw:                 []byte(`{"apiVersion": "v1","kind": "Pod","metadata": {"name": "myapp"},"spec": {"containers": [{"name": "nginx","image": "test/nginx:latest"}]}}`),
-			initContainers:      []*ContainerImage{},
-			containers:          []*ContainerImage{{Name: "nginx", Image: &ImageInfo{Registry: "docker.io", Name: "nginx", Path: "test/nginx", Tag: "latest", JSONPointer: "/spec/containers/0/image"}}},
-			ephemeralContainers: []*ContainerImage{},
+			initContainers:      []ContainerImage{},
+			containers:          []ContainerImage{{Name: "nginx", ImageInfo: ImageInfo{Registry: "docker.io", Name: "nginx", Path: "test/nginx", Tag: "latest", JSONPointer: "/spec/containers/0/image"}}},
+			ephemeralContainers: []ContainerImage{},
 		},
 		{
 			raw:                 []byte(`{"apiVersion": "apps/v1","kind": "Deployment","metadata": {"name": "myapp"},"spec": {"selector": {"matchLabels": {"app": "myapp"}},"template": {"metadata": {"labels": {"app": "myapp"}},"spec": {"initContainers": [{"name": "init","image": "fictional.registry.example:10443/imagename:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}],"containers": [{"name": "myapp","image": "fictional.registry.example:10443/imagename"}],"ephemeralContainers": [{"name": "ephemeral","image": "fictional.registry.example:10443/imagename:tag@sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"}] }}}}`),
-			initContainers:      []*ContainerImage{{Name: "init", Image: &ImageInfo{Registry: "fictional.registry.example:10443", Name: "imagename", Path: "imagename", Tag: "tag", JSONPointer: "/spec/template/spec/initContainers/0/image", Digest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}}},
-			containers:          []*ContainerImage{{Name: "myapp", Image: &ImageInfo{Registry: "fictional.registry.example:10443", Name: "imagename", Path: "imagename", Tag: "latest", JSONPointer: "/spec/template/spec/containers/0/image"}}},
-			ephemeralContainers: []*ContainerImage{{Name: "ephemeral", Image: &ImageInfo{Registry: "fictional.registry.example:10443", Name: "imagename", Path: "imagename", Tag: "tag", JSONPointer: "/spec/template/spec/ephemeralContainers/0/image", Digest: "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"}}},
+			initContainers:      []ContainerImage{{Name: "init", ImageInfo: ImageInfo{Registry: "fictional.registry.example:10443", Name: "imagename", Path: "imagename", Tag: "tag", JSONPointer: "/spec/template/spec/initContainers/0/image", Digest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}}},
+			containers:          []ContainerImage{{Name: "myapp", ImageInfo: ImageInfo{Registry: "fictional.registry.example:10443", Name: "imagename", Path: "imagename", Tag: "latest", JSONPointer: "/spec/template/spec/containers/0/image"}}},
+			ephemeralContainers: []ContainerImage{{Name: "ephemeral", ImageInfo: ImageInfo{Registry: "fictional.registry.example:10443", Name: "imagename", Path: "imagename", Tag: "tag", JSONPointer: "/spec/template/spec/ephemeralContainers/0/image", Digest: "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"}}},
 		},
 		{
 			raw:        []byte(`{"apiVersion": "batch/v1beta1","kind": "CronJob","metadata": {"name": "hello"},"spec": {"schedule": "*/1 * * * *","jobTemplate": {"spec": {"template": {"spec": {"containers": [{"name": "hello","image": "test.example.com/test/my-app:v2"}]}}}}}}`),
-			containers: []*ContainerImage{{Name: "hello", Image: &ImageInfo{Registry: "test.example.com", Name: "my-app", Path: "test/my-app", Tag: "v2", JSONPointer: "/spec/jobTemplate/spec/template/spec/containers/0/image"}}},
+			containers: []ContainerImage{{Name: "hello", ImageInfo: ImageInfo{Registry: "test.example.com", Name: "my-app", Path: "test/my-app", Tag: "v2", JSONPointer: "/spec/jobTemplate/spec/template/spec/containers/0/image"}}},
 		},
 	}
 
diff --git a/pkg/engine/forceMutate_test.go b/pkg/engine/forceMutate_test.go
index f20ac4442..7e8f257c8 100644
--- a/pkg/engine/forceMutate_test.go
+++ b/pkg/engine/forceMutate_test.go
@@ -100,7 +100,7 @@ func Test_ForceMutateSubstituteVars(t *testing.T) {
 	resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
 	assert.NilError(t, err)
 	ctx := context.NewContext()
-	err = ctx.AddResource(rawResource)
+	err = context.AddResource(ctx, rawResource)
 	assert.NilError(t, err)
 
 	mutatedResource, err := ForceMutate(ctx, &policy, *resourceUnstructured)
@@ -205,7 +205,7 @@ func Test_ForceMutateSubstituteVarsWithPatchesJson6902(t *testing.T) {
 	resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
 	assert.NilError(t, err)
 	ctx := context.NewContext()
-	err = ctx.AddResource(rawResource)
+	err = context.AddResource(ctx, rawResource)
 	assert.NilError(t, err)
 
 	mutatedResource, err := ForceMutate(ctx, &policy, *resourceUnstructured)
@@ -291,7 +291,7 @@ func Test_ForceMutateSubstituteVarsWithPatchStrategicMerge(t *testing.T) {
 	resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
 	assert.NilError(t, err)
 	ctx := context.NewContext()
-	err = ctx.AddResource(rawResource)
+	err = context.AddResource(ctx, rawResource)
 	assert.NilError(t, err)
 
 	mutatedResource, err := ForceMutate(ctx, &policy, *resourceUnstructured)
diff --git a/pkg/engine/imageVerify_test.go b/pkg/engine/imageVerify_test.go
index 7eb3dd3d4..df4b9cb14 100644
--- a/pkg/engine/imageVerify_test.go
+++ b/pkg/engine/imageVerify_test.go
@@ -164,7 +164,7 @@ func buildContext(t *testing.T, policy, resource string) *PolicyContext {
 	resourceUnstructured, err := utils.ConvertToUnstructured(resourceRaw)
 	assert.NilError(t, err)
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	if err != nil {
 		t.Error(err)
 	}
@@ -174,7 +174,7 @@ func buildContext(t *testing.T, policy, resource string) *PolicyContext {
 		JSONContext: ctx,
 		NewResource: *resourceUnstructured}
 
-	if err := ctx.AddImageInfo(resourceUnstructured); err != nil {
+	if err := ctx.AddImageInfos(resourceUnstructured); err != nil {
 		t.Errorf("unable to add image info to variables context: %v", err)
 		t.Fail()
 	}
diff --git a/pkg/engine/mutation_test.go b/pkg/engine/mutation_test.go
index 03de8cced..2ce34cc94 100644
--- a/pkg/engine/mutation_test.go
+++ b/pkg/engine/mutation_test.go
@@ -75,7 +75,7 @@ func Test_VariableSubstitutionPatchStrategicMerge(t *testing.T) {
 	resourceUnstructured, err := utils.ConvertToUnstructured(resourceRaw)
 	assert.NilError(t, err)
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	if err != nil {
 		t.Error(err)
 	}
@@ -155,7 +155,7 @@ func Test_variableSubstitutionPathNotExist(t *testing.T) {
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	assert.NilError(t, err)
 
 	policyContext := &PolicyContext{
@@ -250,7 +250,7 @@ func Test_variableSubstitutionCLI(t *testing.T) {
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	assert.NilError(t, err)
 
 	policyContext := &PolicyContext{
@@ -353,7 +353,7 @@ func Test_chained_rules(t *testing.T) {
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	err = ctx.AddResourceAsObject(resource.Object)
+	err = ctx.AddResource(resource.Object)
 	assert.NilError(t, err)
 
 	policyContext := &PolicyContext{
@@ -362,7 +362,7 @@ func Test_chained_rules(t *testing.T) {
 		NewResource: *resource,
 	}
 
-	err = ctx.AddImageInfo(resource)
+	err = ctx.AddImageInfos(resource)
 	assert.NilError(t, err)
 
 	err = context.MutateResourceWithImageInfo(resourceRaw, ctx)
@@ -447,7 +447,7 @@ func Test_precondition(t *testing.T) {
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	assert.NilError(t, err)
 
 	policyContext := &PolicyContext{
@@ -544,7 +544,7 @@ func Test_nonZeroIndexNumberPatchesJson6902(t *testing.T) {
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	assert.NilError(t, err)
 
 	policyContext := &PolicyContext{
@@ -632,7 +632,7 @@ func Test_foreach(t *testing.T) {
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	err = ctx.AddResourceAsObject(resource.Object)
+	err = ctx.AddResource(resource.Object)
 	assert.NilError(t, err)
 
 	policyContext := &PolicyContext{
@@ -641,7 +641,7 @@ func Test_foreach(t *testing.T) {
 		NewResource: *resource,
 	}
 
-	err = ctx.AddImageInfo(resource)
+	err = ctx.AddImageInfos(resource)
 	assert.NilError(t, err)
 
 	err = context.MutateResourceWithImageInfo(resourceRaw, ctx)
@@ -739,7 +739,7 @@ func Test_foreach_element_mutation(t *testing.T) {
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	err = ctx.AddResourceAsObject(resource.Object)
+	err = ctx.AddResource(resource.Object)
 	assert.NilError(t, err)
 
 	policyContext := &PolicyContext{
@@ -748,7 +748,7 @@ func Test_foreach_element_mutation(t *testing.T) {
 		NewResource: *resource,
 	}
 
-	err = ctx.AddImageInfo(resource)
+	err = ctx.AddImageInfos(resource)
 	assert.NilError(t, err)
 
 	err = context.MutateResourceWithImageInfo(resourceRaw, ctx)
@@ -865,7 +865,7 @@ func Test_Container_InitContainer_foreach(t *testing.T) {
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	err = ctx.AddResourceAsObject(resource.Object)
+	err = ctx.AddResource(resource.Object)
 	assert.NilError(t, err)
 
 	policyContext := &PolicyContext{
@@ -874,7 +874,7 @@ func Test_Container_InitContainer_foreach(t *testing.T) {
 		NewResource: *resource,
 	}
 
-	err = ctx.AddImageInfo(resource)
+	err = ctx.AddImageInfos(resource)
 	assert.NilError(t, err)
 
 	err = context.MutateResourceWithImageInfo(resourceRaw, ctx)
@@ -992,7 +992,7 @@ func Test_foreach_order_mutation_(t *testing.T) {
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	err = ctx.AddResourceAsObject(resource.Object)
+	err = ctx.AddResource(resource.Object)
 	assert.NilError(t, err)
 
 	policyContext := &PolicyContext{
@@ -1001,7 +1001,7 @@ func Test_foreach_order_mutation_(t *testing.T) {
 		NewResource: *resource,
 	}
 
-	err = ctx.AddImageInfo(resource)
+	err = ctx.AddImageInfos(resource)
 	assert.NilError(t, err)
 
 	err = context.MutateResourceWithImageInfo(resourceRaw, ctx)
diff --git a/pkg/engine/validation_test.go b/pkg/engine/validation_test.go
index c2fff3757..71349cbe3 100644
--- a/pkg/engine/validation_test.go
+++ b/pkg/engine/validation_test.go
@@ -1471,7 +1471,7 @@ func Test_VariableSubstitutionPathNotExistInPattern(t *testing.T) {
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	assert.NilError(t, err)
 
 	policyContext := &PolicyContext{
@@ -1564,7 +1564,7 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_OnePatternStatisfiesButSu
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	assert.NilError(t, err)
 
 	policyContext := &PolicyContext{
@@ -1625,7 +1625,7 @@ func Test_VariableSubstitution_NotOperatorWithStringVariable(t *testing.T) {
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	assert.NilError(t, err)
 
 	policyContext := &PolicyContext{
@@ -1716,7 +1716,7 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathNotPresent(t *test
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	assert.NilError(t, err)
 
 	policyContext := &PolicyContext{
@@ -1809,7 +1809,7 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathPresent_NonePatter
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	assert.NilError(t, err)
 
 	policyContext := &PolicyContext{
@@ -1914,7 +1914,7 @@ func Test_VariableSubstitutionValidate_VariablesInMessageAreResolved(t *testing.
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	assert.NilError(t, err)
 
 	policyContext := &PolicyContext{
@@ -1967,7 +1967,7 @@ func Test_Flux_Kustomization_PathNotPresent(t *testing.T) {
 		assert.NilError(t, err)
 
 		ctx := context.NewContext()
-		err = ctx.AddResource(test.resourceRaw)
+		err = context.AddResource(ctx, test.resourceRaw)
 		assert.NilError(t, err)
 
 		policyContext := &PolicyContext{
@@ -2327,7 +2327,7 @@ func Test_EmptyStringInDenyCondition(t *testing.T) {
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	assert.NilError(t, err)
 
 	resourceUnstructured, err := utils.ConvertToUnstructured(resourceRaw)
@@ -2416,7 +2416,7 @@ func Test_StringInDenyCondition(t *testing.T) {
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	assert.NilError(t, err)
 
 	resourceUnstructured, err := utils.ConvertToUnstructured(resourceRaw)
@@ -3002,7 +3002,7 @@ func testForEach(t *testing.T, policyraw []byte, resourceRaw []byte, msg string,
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	assert.NilError(t, err)
 
 	policyContext := &PolicyContext{
@@ -3066,7 +3066,7 @@ func Test_delete_ignore_pattern(t *testing.T) {
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	assert.NilError(t, err)
 
 	policyContextCreate := &PolicyContext{
diff --git a/pkg/engine/variables/evaluate_test.go b/pkg/engine/variables/evaluate_test.go
index d5596d017..da2f8ca3f 100644
--- a/pkg/engine/variables/evaluate_test.go
+++ b/pkg/engine/variables/evaluate_test.go
@@ -402,7 +402,7 @@ func Test_Eval_Equal_Var_Pass(t *testing.T) {
 
 	// context
 	ctx := context.NewContext()
-	err := ctx.AddResource(resourceRaw)
+	err := context.AddResource(ctx, resourceRaw)
 	if err != nil {
 		t.Error(err)
 	}
@@ -444,7 +444,7 @@ func Test_Eval_Equal_Var_Fail(t *testing.T) {
 
 	// context
 	ctx := context.NewContext()
-	err := ctx.AddResource(resourceRaw)
+	err := context.AddResource(ctx, resourceRaw)
 	if err != nil {
 		t.Error(err)
 	}
diff --git a/pkg/engine/variables/variables_test.go b/pkg/engine/variables/variables_test.go
index a9e55b667..0d62f5dd2 100644
--- a/pkg/engine/variables/variables_test.go
+++ b/pkg/engine/variables/variables_test.go
@@ -76,7 +76,7 @@ func Test_variablesub1(t *testing.T) {
 	}
 	// context
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	if err != nil {
 		t.Error(err)
 	}
@@ -166,7 +166,7 @@ func Test_variablesub_multiple(t *testing.T) {
 
 	// context
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	if err != nil {
 		t.Error(err)
 	}
@@ -253,7 +253,7 @@ func Test_variablesubstitution(t *testing.T) {
 
 	// context
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	if err != nil {
 		t.Error(err)
 	}
@@ -319,7 +319,7 @@ func Test_variableSubstitutionValue(t *testing.T) {
 
 	// context
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	if err != nil {
 		t.Error(err)
 	}
@@ -377,7 +377,7 @@ func Test_variableSubstitutionValueOperatorNotEqual(t *testing.T) {
 
 	// context
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	if err != nil {
 		t.Error(err)
 	}
@@ -436,7 +436,7 @@ func Test_variableSubstitutionValueFail(t *testing.T) {
 
 	// context
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	if err != nil {
 		t.Error(err)
 	}
@@ -494,7 +494,7 @@ func Test_variableSubstitutionObject(t *testing.T) {
 
 	// context
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	if err != nil {
 		t.Error(err)
 	}
@@ -558,7 +558,7 @@ func Test_variableSubstitutionObjectOperatorNotEqualFail(t *testing.T) {
 
 	// context
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	if err != nil {
 		t.Error(err)
 	}
@@ -633,7 +633,7 @@ func Test_variableSubstitutionMultipleObject(t *testing.T) {
 
 	// context
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	if err != nil {
 		t.Error(err)
 	}
diff --git a/pkg/engine/variables/vars_test.go b/pkg/engine/variables/vars_test.go
index c92e21568..e9c443256 100644
--- a/pkg/engine/variables/vars_test.go
+++ b/pkg/engine/variables/vars_test.go
@@ -65,7 +65,7 @@ func Test_subVars_success(t *testing.T) {
 	}
 	// context
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	if err != nil {
 		t.Error(err)
 	}
@@ -126,7 +126,7 @@ func Test_subVars_failed(t *testing.T) {
 	}
 	// context
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	if err != nil {
 		t.Error(err)
 	}
@@ -220,7 +220,7 @@ func Test_subVars_with_JMESPath_At(t *testing.T) {
 	assert.NilError(t, err)
 	// context
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	assert.NilError(t, err)
 
 	output, err := SubstituteAll(log.Log, ctx, pattern)
@@ -279,7 +279,7 @@ func Test_subVars_withRegexMatch(t *testing.T) {
 	assert.NilError(t, err)
 	// context
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	assert.NilError(t, err)
 
 	output, err := SubstituteAll(log.Log, ctx, pattern)
@@ -309,7 +309,7 @@ func Test_subVars_withMerge(t *testing.T) {
 	assert.NilError(t, err)
 	// context
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	assert.NilError(t, err)
 
 	output, err := SubstituteAll(log.Log, ctx, pattern)
@@ -352,7 +352,7 @@ func Test_subVars_withRegexReplaceAll(t *testing.T) {
 	assert.NilError(t, err)
 	// context
 	ctx := context.NewContext()
-	err = ctx.AddResource(resourceRaw)
+	err = context.AddResource(ctx, resourceRaw)
 	assert.NilError(t, err)
 
 	output, err := SubstituteAll(log.Log, ctx, pattern)
@@ -396,8 +396,7 @@ func Test_ReplacingPathWhenDeleting(t *testing.T) {
 	if err != nil {
 		t.Error(err)
 	}
-	ctx := context.NewContext()
-	err = ctx.AddJSON(resourceRaw)
+	ctx := context.NewContextFromRaw(resourceRaw)
 	assert.NilError(t, err)
 
 	pattern, err = SubstituteAll(log.Log, ctx, pattern)
@@ -432,8 +431,7 @@ func Test_ReplacingNestedVariableWhenDeleting(t *testing.T) {
 	if err != nil {
 		t.Error(err)
 	}
-	ctx := context.NewContext()
-	err = ctx.AddJSON(resourceRaw)
+	ctx := context.NewContextFromRaw(resourceRaw)
 	assert.NilError(t, err)
 
 	pattern, err = SubstituteAll(log.Log, ctx, pattern)
@@ -460,7 +458,7 @@ var resourceRaw = []byte(`
 
 func Test_SubstituteSuccess(t *testing.T) {
 	ctx := context.NewContext()
-	assert.Assert(t, ctx.AddResource(resourceRaw))
+	assert.Assert(t, context.AddResource(ctx, resourceRaw))
 
 	var pattern interface{}
 	patternRaw := []byte(`"{{request.object.metadata.annotations.test}}"`)
@@ -484,7 +482,7 @@ func Test_SubstituteSuccess(t *testing.T) {
 
 func Test_SubstituteRecursiveErrors(t *testing.T) {
 	ctx := context.NewContext()
-	assert.Assert(t, ctx.AddResource(resourceRaw))
+	assert.Assert(t, context.AddResource(ctx, resourceRaw))
 
 	var pattern interface{}
 	patternRaw := []byte(`"{{request.object.metadata.{{request.object.metadata.annotations.test2}}}}"`)
@@ -516,7 +514,7 @@ func Test_SubstituteRecursiveErrors(t *testing.T) {
 
 func Test_SubstituteRecursive(t *testing.T) {
 	ctx := context.NewContext()
-	assert.Assert(t, ctx.AddResource(resourceRaw))
+	assert.Assert(t, context.AddResource(ctx, resourceRaw))
 
 	var pattern interface{}
 	patternRaw := []byte(`"{{request.object.metadata.{{request.object.metadata.annotations.test}}}}"`)
@@ -633,9 +631,8 @@ func Test_variableSubstitution_array(t *testing.T) {
 	err := json.Unmarshal(ruleRaw, &rule)
 	assert.NilError(t, err)
 
-	ctx := context.NewContext()
-	ctx.AddJSON(configmapRaw)
-	ctx.AddResource(resourceRaw)
+	ctx := context.NewContextFromRaw(configmapRaw)
+	context.AddResource(ctx, resourceRaw)
 
 	vars, err := SubstituteAllInRule(log.Log, ctx, rule)
 	assert.NilError(t, err)
@@ -681,7 +678,7 @@ func Test_SubstituteNull(t *testing.T) {
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	ctx.AddResource(variableObject)
+	context.AddResource(ctx, variableObject)
 
 	resolved, err := SubstituteAll(log.Log, ctx, pattern)
 	assert.NilError(t, err)
@@ -710,7 +707,7 @@ func Test_SubstituteNullInString(t *testing.T) {
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	ctx.AddResource(variableObject)
+	context.AddResource(ctx, variableObject)
 
 	resolved, err := SubstituteAll(log.Log, ctx, pattern)
 	assert.NilError(t, err)
@@ -739,7 +736,7 @@ func Test_SubstituteArray(t *testing.T) {
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	ctx.AddResource(variableObject)
+	context.AddResource(ctx, variableObject)
 
 	resolved, err := SubstituteAll(log.Log, ctx, pattern)
 	assert.NilError(t, err)
@@ -768,7 +765,7 @@ func Test_SubstituteArrayInString(t *testing.T) {
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	ctx.AddResource(variableObject)
+	context.AddResource(ctx, variableObject)
 
 	resolved, err := SubstituteAll(log.Log, ctx, pattern)
 	assert.NilError(t, err)
@@ -797,7 +794,7 @@ func Test_SubstituteInt(t *testing.T) {
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	ctx.AddResource(variableObject)
+	context.AddResource(ctx, variableObject)
 
 	resolved, err := SubstituteAll(log.Log, ctx, pattern)
 	assert.NilError(t, err)
@@ -826,7 +823,7 @@ func Test_SubstituteIntInString(t *testing.T) {
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	ctx.AddResource(variableObject)
+	context.AddResource(ctx, variableObject)
 
 	resolved, err := SubstituteAll(log.Log, ctx, pattern)
 	assert.NilError(t, err)
@@ -855,7 +852,7 @@ func Test_SubstituteBool(t *testing.T) {
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	ctx.AddResource(variableObject)
+	context.AddResource(ctx, variableObject)
 
 	resolved, err := SubstituteAll(log.Log, ctx, pattern)
 	assert.NilError(t, err)
@@ -884,7 +881,7 @@ func Test_SubstituteBoolInString(t *testing.T) {
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	ctx.AddResource(variableObject)
+	context.AddResource(ctx, variableObject)
 
 	resolved, err := SubstituteAll(log.Log, ctx, pattern)
 	assert.NilError(t, err)
@@ -913,7 +910,7 @@ func Test_SubstituteString(t *testing.T) {
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	ctx.AddResource(variableObject)
+	context.AddResource(ctx, variableObject)
 
 	resolved, err := SubstituteAll(log.Log, ctx, pattern)
 	assert.NilError(t, err)
@@ -942,7 +939,7 @@ func Test_SubstituteStringInString(t *testing.T) {
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	ctx.AddResource(variableObject)
+	context.AddResource(ctx, variableObject)
 
 	resolved, err := SubstituteAll(log.Log, ctx, pattern)
 	assert.NilError(t, err)
@@ -993,7 +990,7 @@ func Test_ReferenceSubstitution(t *testing.T) {
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	err = ctx.AddResource(jsonRaw)
+	err = context.AddResource(ctx, jsonRaw)
 	assert.NilError(t, err)
 
 	actualDocument, err := SubstituteAll(log.Log, ctx, document)
@@ -1137,7 +1134,7 @@ func Test_EscpReferenceSubstitution(t *testing.T) {
 	assert.NilError(t, err)
 
 	ctx := context.NewContext()
-	err = ctx.AddResource(jsonRaw)
+	err = context.AddResource(ctx, jsonRaw)
 	assert.NilError(t, err)
 
 	actualDocument, err := SubstituteAll(log.Log, ctx, document)
@@ -1172,8 +1169,7 @@ func Test_ReplacingEscpNestedVariableWhenDeleting(t *testing.T) {
 	if err != nil {
 		t.Error(err)
 	}
-	ctx := context.NewContext()
-	err = ctx.AddJSON(resourceRaw)
+	ctx := context.NewContextFromRaw(resourceRaw)
 	assert.NilError(t, err)
 
 	pattern, err = SubstituteAll(log.Log, ctx, pattern)