1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-31 03:45:17 +00:00

refactor: engine responses (#6738)

* refactor: engine responses

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>

* 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>
This commit is contained in:
Charles-Edouard Brétéché 2023-03-30 13:59:32 +02:00 committed by GitHub
parent af99bb1d0c
commit eaaa8a0236
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 120 additions and 122 deletions

View file

@ -2,6 +2,7 @@ package api
import (
"fmt"
"time"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
@ -22,6 +23,8 @@ type EngineResponse struct {
PatchedResource unstructured.Unstructured
// PolicyResponse contains the engine policy response
PolicyResponse PolicyResponse
// Stats contains engine statistics
Stats ExecutionStats
}
func Resource(policyContext PolicyContext) unstructured.Unstructured {
@ -32,15 +35,13 @@ func Resource(policyContext PolicyContext) unstructured.Unstructured {
return resource
}
func NewEngineResponseFromPolicyContext(
policyContext PolicyContext,
policyResponse *PolicyResponse,
) EngineResponse {
func NewEngineResponseFromPolicyContext(policyContext PolicyContext, timestamp time.Time) EngineResponse {
return NewEngineResponse(
Resource(policyContext),
policyContext.Policy(),
policyContext.NamespaceLabels(),
policyResponse,
nil,
timestamp,
)
}
@ -49,18 +50,36 @@ func NewEngineResponse(
policy kyvernov1.PolicyInterface,
namespaceLabels map[string]string,
policyResponse *PolicyResponse,
timestamp time.Time,
) EngineResponse {
response := EngineResponse{
Resource: resource,
Policy: policy,
NamespaceLabels: namespaceLabels,
PatchedResource: resource,
Stats: NewExecutionStats(timestamp),
}
if policyResponse != nil {
response.PolicyResponse = *policyResponse
response = response.WithPolicyResponse(*policyResponse)
}
return response
}
func (er EngineResponse) WithPolicyResponse(policyResponse PolicyResponse) EngineResponse {
er.PolicyResponse = policyResponse
return er
}
func (er EngineResponse) WithPatchedResource(patchedResource unstructured.Unstructured) EngineResponse {
er.PatchedResource = patchedResource
return er
}
func (er EngineResponse) Done(timestamp time.Time) EngineResponse {
er.Stats.Done(timestamp)
return er
}
// IsOneOf checks if any rule has status in a given list
func (er EngineResponse) IsOneOf(status ...RuleStatus) bool {
for _, r := range er.PolicyResponse.Rules {
@ -158,7 +177,7 @@ func (er EngineResponse) getRulesWithErrors(predicate func(RuleResponse) bool) [
return rules
}
func (er *EngineResponse) GetValidationFailureAction() kyvernov1.ValidationFailureAction {
func (er EngineResponse) GetValidationFailureAction() kyvernov1.ValidationFailureAction {
spec := er.Policy.GetSpec()
for _, v := range spec.ValidationFailureActionOverrides {
if !v.Action.IsValid() {

View file

@ -7,3 +7,16 @@ type PolicyResponse struct {
// Rules contains policy rules responses
Rules []RuleResponse
}
func (pr *PolicyResponse) Add(rr RuleResponse) {
pr.Rules = append(pr.Rules, rr)
if rr.Status == RuleStatusPass || rr.Status == RuleStatusFail {
pr.Stats.RulesAppliedCount++
} else if rr.Status == RuleStatusError {
pr.Stats.RulesErrorCount++
}
}
func NewPolicyResponse() PolicyResponse {
return PolicyResponse{}
}

View file

@ -12,10 +12,18 @@ type ExecutionStats struct {
Timestamp int64
}
func NewExecutionStats(timestamp time.Time) ExecutionStats {
return ExecutionStats{
Timestamp: timestamp.Unix(),
}
}
func (s *ExecutionStats) Done(timestamp time.Time) {
s.ProcessingTime = timestamp.Sub(time.Unix(s.Timestamp, 0))
}
// PolicyStats stores statistics for the single policy application
type PolicyStats struct {
// ExecutionStats policy execution stats
ExecutionStats
// RulesAppliedCount is the count of rules that were applied successfully
RulesAppliedCount int
// RulesErrorCount is the count of rules that with execution errors

View file

@ -23,7 +23,7 @@ func (e *engine) applyBackgroundChecks(
ctx context.Context,
logger logr.Logger,
policyContext engineapi.PolicyContext,
) engineapi.EngineResponse {
) engineapi.PolicyResponse {
return e.filterRules(policyContext, logger, time.Now())
}
@ -31,21 +31,14 @@ func (e *engine) filterRules(
policyContext engineapi.PolicyContext,
logger logr.Logger,
startTime time.Time,
) engineapi.EngineResponse {
) engineapi.PolicyResponse {
policy := policyContext.Policy()
resp := engineapi.NewEngineResponseFromPolicyContext(policyContext, nil)
resp.PolicyResponse = engineapi.PolicyResponse{
Stats: engineapi.PolicyStats{
ExecutionStats: engineapi.ExecutionStats{
Timestamp: startTime.Unix(),
},
},
}
resp := engineapi.NewPolicyResponse()
applyRules := policy.GetSpec().GetApplyRules()
for _, rule := range autogen.ComputeRules(policy) {
logger := internal.LoggerWithRule(logger, rule)
if ruleResp := e.filterRule(rule, logger, policyContext); ruleResp != nil {
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp)
resp.Rules = append(resp.Rules, *ruleResp)
if applyRules == kyvernov1.ApplyOne && ruleResp.Status != engineapi.RuleStatusSkip {
break
}

View file

@ -3,6 +3,7 @@ package engine
import (
"context"
"fmt"
"time"
"github.com/go-logr/logr"
gojmespath "github.com/jmespath/go-jmespath"
@ -76,44 +77,55 @@ func (e *engine) Validate(
ctx context.Context,
policyContext engineapi.PolicyContext,
) engineapi.EngineResponse {
response := engineapi.NewEngineResponseFromPolicyContext(policyContext, time.Now())
logger := internal.LoggerWithPolicyContext(logging.WithName("engine.validate"), policyContext)
if !internal.MatchPolicyContext(logger, policyContext, e.configuration) {
return engineapi.NewEngineResponseFromPolicyContext(policyContext, nil)
if internal.MatchPolicyContext(logger, policyContext, e.configuration) {
policyResponse := e.validate(ctx, logger, policyContext)
response = response.WithPolicyResponse(policyResponse)
}
return e.validate(ctx, logger, policyContext)
return response.Done(time.Now())
}
func (e *engine) Mutate(
ctx context.Context,
policyContext engineapi.PolicyContext,
) engineapi.EngineResponse {
response := engineapi.NewEngineResponseFromPolicyContext(policyContext, time.Now())
logger := internal.LoggerWithPolicyContext(logging.WithName("engine.mutate"), policyContext)
if !internal.MatchPolicyContext(logger, policyContext, e.configuration) {
return engineapi.NewEngineResponseFromPolicyContext(policyContext, nil)
if internal.MatchPolicyContext(logger, policyContext, e.configuration) {
policyResponse, patchedResource := e.mutate(ctx, logger, policyContext)
response = response.
WithPolicyResponse(policyResponse).
WithPatchedResource(patchedResource)
}
return e.mutate(ctx, logger, policyContext)
return response.Done(time.Now())
}
func (e *engine) VerifyAndPatchImages(
ctx context.Context,
policyContext engineapi.PolicyContext,
) (engineapi.EngineResponse, engineapi.ImageVerificationMetadata) {
response := engineapi.NewEngineResponseFromPolicyContext(policyContext, time.Now())
ivm := engineapi.ImageVerificationMetadata{}
logger := internal.LoggerWithPolicyContext(logging.WithName("engine.verify"), policyContext)
if !internal.MatchPolicyContext(logger, policyContext, e.configuration) {
return engineapi.NewEngineResponseFromPolicyContext(policyContext, nil), engineapi.ImageVerificationMetadata{}
if internal.MatchPolicyContext(logger, policyContext, e.configuration) {
policyResponse, innerIvm := e.verifyAndPatchImages(ctx, logger, policyContext)
response, ivm = response.WithPolicyResponse(policyResponse), innerIvm
}
return e.verifyAndPatchImages(ctx, logger, policyContext)
return response.Done(time.Now()), ivm
}
func (e *engine) ApplyBackgroundChecks(
ctx context.Context,
policyContext engineapi.PolicyContext,
) engineapi.EngineResponse {
response := engineapi.NewEngineResponseFromPolicyContext(policyContext, time.Now())
logger := internal.LoggerWithPolicyContext(logging.WithName("engine.background"), policyContext)
if !internal.MatchPolicyContext(logger, policyContext, e.configuration) {
return engineapi.NewEngineResponseFromPolicyContext(policyContext, nil)
if internal.MatchPolicyContext(logger, policyContext, e.configuration) {
policyResponse := e.applyBackgroundChecks(ctx, logger, policyContext)
response = response.WithPolicyResponse(policyResponse)
}
return e.applyBackgroundChecks(ctx, logger, policyContext)
return response.Done(time.Now())
}
func (e *engine) GenerateResponse(
@ -121,11 +133,13 @@ func (e *engine) GenerateResponse(
policyContext engineapi.PolicyContext,
gr kyvernov1beta1.UpdateRequest,
) engineapi.EngineResponse {
response := engineapi.NewEngineResponseFromPolicyContext(policyContext, time.Now())
logger := internal.LoggerWithPolicyContext(logging.WithName("engine.generate"), policyContext)
if !internal.MatchPolicyContext(logger, policyContext, e.configuration) {
return engineapi.NewEngineResponseFromPolicyContext(policyContext, nil)
if internal.MatchPolicyContext(logger, policyContext, e.configuration) {
policyResponse := e.generateResponse(ctx, logger, policyContext, gr)
response = response.WithPolicyResponse(policyResponse)
}
return e.generateResponse(ctx, logger, policyContext, gr)
return response.Done(time.Now())
}
func (e *engine) ContextLoader(

View file

@ -17,7 +17,7 @@ func (e *engine) generateResponse(
logger logr.Logger,
policyContext engineapi.PolicyContext,
gr kyvernov1beta1.UpdateRequest,
) engineapi.EngineResponse {
) engineapi.PolicyResponse {
return e.filterGenerateRules(policyContext, logger, gr.Spec.Policy, time.Now())
}
@ -26,19 +26,12 @@ func (e *engine) filterGenerateRules(
logger logr.Logger,
policyNameKey string,
startTime time.Time,
) engineapi.EngineResponse {
resp := engineapi.NewEngineResponseFromPolicyContext(policyContext, nil)
resp.PolicyResponse = engineapi.PolicyResponse{
Stats: engineapi.PolicyStats{
ExecutionStats: engineapi.ExecutionStats{
Timestamp: startTime.Unix(),
},
},
}
) engineapi.PolicyResponse {
resp := engineapi.NewPolicyResponse()
for _, rule := range autogen.ComputeRules(policyContext.Policy()) {
logger := internal.LoggerWithRule(logger, rule)
if ruleResp := e.filterRule(rule, logger, policyContext); ruleResp != nil {
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp)
resp.Rules = append(resp.Rules, *ruleResp)
}
}
return resp

View file

@ -21,20 +21,9 @@ func (e *engine) verifyAndPatchImages(
ctx context.Context,
logger logr.Logger,
policyContext engineapi.PolicyContext,
) (engineapi.EngineResponse, engineapi.ImageVerificationMetadata) {
) (engineapi.PolicyResponse, engineapi.ImageVerificationMetadata) {
policy := policyContext.Policy()
resp := engineapi.NewEngineResponseFromPolicyContext(policyContext, nil)
startTime := time.Now()
defer func() {
internal.BuildResponse(policyContext, &resp, startTime)
logger.V(4).Info("processed image verification rules",
"time", resp.PolicyResponse.Stats.ProcessingTime.String(),
"applied", resp.PolicyResponse.Stats.RulesAppliedCount,
"successful", resp.IsSuccessful(),
)
}()
resp := engineapi.NewPolicyResponse()
policyContext.JSONContext().Checkpoint()
defer policyContext.JSONContext().Restore()
@ -52,11 +41,11 @@ func (e *engine) verifyAndPatchImages(
},
)
if applyRules == kyvernov1.ApplyOne && resp.PolicyResponse.Stats.RulesAppliedCount > 0 {
if applyRules == kyvernov1.ApplyOne && resp.Stats.RulesAppliedCount > 0 {
break
}
}
internal.BuildResponse(policyContext, &resp, startTime)
// TODO: i doesn't make sense to not return the patched resource here
return resp, ivm
}
@ -65,7 +54,7 @@ func (e *engine) doVerifyAndPatch(
logger logr.Logger,
policyContext engineapi.PolicyContext,
rule kyvernov1.Rule,
resp *engineapi.EngineResponse,
resp *engineapi.PolicyResponse,
ivm *engineapi.ImageVerificationMetadata,
) {
if len(rule.VerifyImages) == 0 {
@ -82,7 +71,7 @@ func (e *engine) doVerifyAndPatch(
// check if there is a corresponding policy exception
ruleResp := e.hasPolicyExceptions(logger, engineapi.ImageVerify, policyContext, rule)
if ruleResp != nil {
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp)
resp.Rules = append(resp.Rules, *ruleResp)
return
}
@ -96,7 +85,7 @@ func (e *engine) doVerifyAndPatch(
)
if err != nil {
internal.AddRuleResponse(
&resp.PolicyResponse,
resp,
internal.RuleError(rule, engineapi.ImageVerify, "failed to extract images", err),
startTime,
)
@ -104,7 +93,7 @@ func (e *engine) doVerifyAndPatch(
}
if len(ruleImages) == 0 {
internal.AddRuleResponse(
&resp.PolicyResponse,
resp,
internal.RuleSkip(
rule,
engineapi.ImageVerify,
@ -117,7 +106,7 @@ func (e *engine) doVerifyAndPatch(
policyContext.JSONContext().Restore()
if err := internal.LoadContext(ctx, e, policyContext, rule); err != nil {
internal.AddRuleResponse(
&resp.PolicyResponse,
resp,
internal.RuleError(rule, engineapi.ImageVerify, "failed to load context", err),
startTime,
)
@ -126,7 +115,7 @@ func (e *engine) doVerifyAndPatch(
ruleCopy, err := substituteVariables(&rule, policyContext.JSONContext(), logger)
if err != nil {
internal.AddRuleResponse(
&resp.PolicyResponse,
resp,
internal.RuleError(rule, engineapi.ImageVerify, "failed to substitute variables", err),
startTime,
)
@ -141,7 +130,7 @@ func (e *engine) doVerifyAndPatch(
)
for _, imageVerify := range ruleCopy.VerifyImages {
for _, r := range iv.Verify(ctx, imageVerify, ruleImages, e.configuration) {
internal.AddRuleResponse(&resp.PolicyResponse, r, startTime)
internal.AddRuleResponse(resp, r, startTime)
}
}
}

View file

@ -50,7 +50,7 @@ func BuildResponse(ctx engineapi.PolicyContext, resp *engineapi.EngineResponse,
}
resp.PatchedResource = resource
}
resp.PolicyResponse.Stats.ProcessingTime = time.Since(startTime)
resp.PolicyResponse.Stats.Timestamp = startTime.Unix()
resp.Stats.ProcessingTime = time.Since(startTime)
resp.Stats.Timestamp = startTime.Unix()
return resp
}

View file

@ -12,25 +12,23 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// Mutate performs mutation. Overlay first and then mutation patches
// mutate performs mutation. Overlay first and then mutation patches
func (e *engine) mutate(
ctx context.Context,
logger logr.Logger,
policyContext engineapi.PolicyContext,
) engineapi.EngineResponse {
startTime := time.Now()
resp := engineapi.NewEngineResponseFromPolicyContext(policyContext, nil)
) (engineapi.PolicyResponse, unstructured.Unstructured) {
resp := engineapi.NewPolicyResponse()
policy := policyContext.Policy()
matchedResource := policyContext.NewResource()
startMutateResultResponse(&resp, policy, matchedResource)
policyContext.JSONContext().Checkpoint()
defer policyContext.JSONContext().Restore()
applyRules := policy.GetSpec().GetApplyRules()
for _, rule := range autogen.ComputeRules(policy) {
startTime := time.Now()
logger := internal.LoggerWithRule(logger, rule)
if !rule.HasMutate() {
continue
@ -43,29 +41,12 @@ func (e *engine) mutate(
matchedResource = resource
for _, ruleResp := range ruleResp {
ruleResp := ruleResp
internal.AddRuleResponse(&resp.PolicyResponse, &ruleResp, startTime)
internal.AddRuleResponse(&resp, &ruleResp, startTime)
logger.V(4).Info("finished processing rule", "processingTime", ruleResp.Stats.ProcessingTime.String())
}
if applyRules == kyvernov1.ApplyOne && resp.PolicyResponse.Stats.RulesAppliedCount > 0 {
if applyRules == kyvernov1.ApplyOne && resp.Stats.RulesAppliedCount > 0 {
break
}
}
resp.PatchedResource = matchedResource
endMutateResultResponse(logger, &resp, startTime)
return resp
}
func startMutateResultResponse(resp *engineapi.EngineResponse, policy kyvernov1.PolicyInterface, resource unstructured.Unstructured) {
if resp == nil {
return
}
}
func endMutateResultResponse(logger logr.Logger, resp *engineapi.EngineResponse, startTime time.Time) {
if resp == nil {
return
}
resp.PolicyResponse.Stats.ProcessingTime = time.Since(startTime)
resp.PolicyResponse.Stats.Timestamp = startTime.Unix()
logger.V(5).Info("finished processing policy", "processingTime", resp.PolicyResponse.Stats.ProcessingTime.String(), "mutationRulesApplied", resp.PolicyResponse.Stats.RulesAppliedCount)
return resp, matchedResource
}

View file

@ -16,21 +16,7 @@ func (e *engine) validate(
ctx context.Context,
logger logr.Logger,
policyContext engineapi.PolicyContext,
) engineapi.EngineResponse {
startTime := time.Now()
logger.V(4).Info("start validate policy processing", "startTime", startTime)
policyResponse := e.validateResource(ctx, logger, policyContext)
defer logger.V(4).Info("finished policy processing", "processingTime", policyResponse.Stats.ProcessingTime.String(), "validationRulesApplied", policyResponse.Stats.RulesAppliedCount)
engineResponse := engineapi.NewEngineResponseFromPolicyContext(policyContext, nil)
engineResponse.PolicyResponse = *policyResponse
return *internal.BuildResponse(policyContext, &engineResponse, startTime)
}
func (e *engine) validateResource(
ctx context.Context,
logger logr.Logger,
policyContext engineapi.PolicyContext,
) *engineapi.PolicyResponse {
) engineapi.PolicyResponse {
resp := &engineapi.PolicyResponse{}
policyContext.JSONContext().Checkpoint()
@ -72,5 +58,5 @@ func (e *engine) validateResource(
break
}
}
return resp
return *resp
}

View file

@ -2,6 +2,7 @@ package utils
import (
"testing"
"time"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
@ -35,7 +36,7 @@ func newEngineResponse(policy, rule string, patchesStr []string, status engineap
},
}
policyResponse := newPolicyResponse(rule, patchesStr, status)
response := engineapi.NewEngineResponse(unstructured.Unstructured{}, p, nil, &policyResponse)
response := engineapi.NewEngineResponse(unstructured.Unstructured{}, p, nil, &policyResponse, time.Now())
response.PatchedResource = unstructured.Unstructured{
Object: map[string]interface{}{
"metadata": map[string]interface{}{

View file

@ -2,6 +2,7 @@ package utils
import (
"testing"
"time"
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
@ -92,7 +93,7 @@ func TestBlockRequest(t *testing.T) {
Message: "message fail",
},
},
}),
}, time.Now()),
},
failurePolicy: kyvernov1.Fail,
log: logr.Discard(),
@ -110,7 +111,7 @@ func TestBlockRequest(t *testing.T) {
Message: "message fail",
},
},
}),
}, time.Now()),
},
failurePolicy: kyvernov1.Fail,
log: logr.Discard(),
@ -128,7 +129,7 @@ func TestBlockRequest(t *testing.T) {
Message: "message error",
},
},
}),
}, time.Now()),
},
failurePolicy: kyvernov1.Fail,
log: logr.Discard(),
@ -146,7 +147,7 @@ func TestBlockRequest(t *testing.T) {
Message: "message error",
},
},
}),
}, time.Now()),
},
failurePolicy: kyvernov1.Ignore,
log: logr.Discard(),
@ -164,7 +165,7 @@ func TestBlockRequest(t *testing.T) {
Message: "message warning",
},
},
}),
}, time.Now()),
},
failurePolicy: kyvernov1.Ignore,
log: logr.Discard(),
@ -182,7 +183,7 @@ func TestBlockRequest(t *testing.T) {
Message: "message warning",
},
},
}),
}, time.Now()),
},
failurePolicy: kyvernov1.Fail,
log: logr.Discard(),
@ -234,7 +235,7 @@ func TestGetBlockedMessages(t *testing.T) {
Message: "message fail",
},
},
}),
}, time.Now()),
},
},
want: "\n\npolicy foo/bar/baz for resource violation: \n\ntest:\n rule-fail: message fail\n",
@ -250,7 +251,7 @@ func TestGetBlockedMessages(t *testing.T) {
Message: "message error",
},
},
}),
}, time.Now()),
},
},
want: "\n\npolicy foo/bar/baz for resource error: \n\ntest:\n rule-error: message error\n",
@ -271,7 +272,7 @@ func TestGetBlockedMessages(t *testing.T) {
Message: "message error",
},
},
}),
}, time.Now()),
},
},
want: "\n\npolicy foo/bar/baz for resource violation: \n\ntest:\n rule-error: message error\n rule-fail: message fail\n",