1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00
kyverno/cmd/cli/kubectl-kyverno/commands/test/output.go
Mariam Fahmy 716611b7ea
fix: return all the exceptions that match the incoming resource (#10722)
* fix: return all the exceptions that match the incoming resource

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

* fix: modify log messages

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

---------

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>
2024-07-25 17:36:19 +00:00

272 lines
8.2 KiB
Go

package test
import (
"context"
"fmt"
"io"
"github.com/go-git/go-billy/v5"
"github.com/kyverno/kyverno-json/pkg/engine/assert"
policyreportv1alpha2 "github.com/kyverno/kyverno/api/policyreport/v1alpha2"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/apis/v1alpha1"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/output/color"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/output/table"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"k8s.io/apimachinery/pkg/runtime"
)
func printCheckResult(
checks []v1alpha1.CheckResult,
responses []engineapi.EngineResponse,
rc *resultCounts,
resultsTable *table.Table,
) error {
ctx := context.Background()
testCount := 1
for _, check := range checks {
// filter engine responses
matchingEngineResponses := responses
// 1. by resource
if check.Match.Resource != nil {
var filtered []engineapi.EngineResponse
for _, response := range matchingEngineResponses {
errs, err := assert.Assert(ctx, nil, assert.Parse(ctx, check.Match.Resource.Value), response.Resource.UnstructuredContent(), nil)
if err != nil {
return err
}
if len(errs) == 0 {
filtered = append(filtered, response)
}
}
matchingEngineResponses = filtered
}
// 2. by policy
if check.Match.Policy != nil {
var filtered []engineapi.EngineResponse
for _, response := range matchingEngineResponses {
data, err := runtime.DefaultUnstructuredConverter.ToUnstructured(response.Policy().MetaObject())
if err != nil {
return err
}
errs, err := assert.Assert(ctx, nil, assert.Parse(ctx, check.Match.Policy.Value), data, nil)
if err != nil {
return err
}
if len(errs) == 0 {
filtered = append(filtered, response)
}
}
matchingEngineResponses = filtered
}
for _, response := range matchingEngineResponses {
// filter rule responses
matchingRuleResponses := response.PolicyResponse.Rules
if check.Match.Rule != nil {
var filtered []engineapi.RuleResponse
for _, response := range matchingRuleResponses {
data := map[string]any{
"name": response.Name(),
}
errs, err := assert.Assert(ctx, nil, assert.Parse(ctx, check.Match.Rule.Value), data, nil)
if err != nil {
return err
}
if len(errs) == 0 {
filtered = append(filtered, response)
}
}
matchingRuleResponses = filtered
}
for _, rule := range matchingRuleResponses {
// perform check
data := map[string]any{
"name": rule.Name(),
"ruleType": rule.RuleType(),
"message": rule.Message(),
"status": string(rule.Status()),
// generatedResource unstructured.Unstructured
// patchedTarget *unstructured.Unstructured
// patchedTargetParentResourceGVR metav1.GroupVersionResource
// patchedTargetSubresourceName string
// podSecurityChecks contains pod security checks (only if this is a pod security rule)
"podSecurityChecks": rule.PodSecurityChecks(),
"exceptions": rule.Exceptions(),
}
if check.Assert.Value != nil {
errs, err := assert.Assert(ctx, nil, assert.Parse(ctx, check.Assert.Value), data, nil)
if err != nil {
return err
}
row := table.Row{
RowCompact: table.RowCompact{
ID: testCount,
Policy: color.Policy("", response.Policy().GetName()),
Rule: color.Rule(rule.Name()),
Resource: color.Resource(response.Resource.GetKind(), response.Resource.GetNamespace(), response.Resource.GetName()),
IsFailure: len(errs) != 0,
},
Message: rule.Message(),
}
if len(errs) == 0 {
row.Result = color.ResultPass()
row.Reason = "Ok"
if rule.Status() == engineapi.RuleStatusSkip {
rc.Skip++
} else {
rc.Pass++
}
} else {
row.Result = color.ResultFail()
row.Reason = errs.ToAggregate().Error()
rc.Fail++
}
resultsTable.Add(row)
testCount++
}
if check.Error.Value != nil {
errs, err := assert.Assert(ctx, nil, assert.Parse(ctx, check.Error.Value), data, nil)
if err != nil {
return err
}
row := table.Row{
RowCompact: table.RowCompact{
ID: testCount,
Policy: color.Policy("", response.Policy().GetName()),
Rule: color.Rule(rule.Name()),
Resource: color.Resource(response.Resource.GetKind(), response.Resource.GetNamespace(), response.Resource.GetName()),
IsFailure: len(errs) != 0,
},
Message: rule.Message(),
}
if len(errs) != 0 {
row.Result = color.ResultPass()
row.Reason = errs.ToAggregate().Error()
if rule.Status() == engineapi.RuleStatusSkip {
rc.Skip++
} else {
rc.Pass++
}
} else {
row.Result = color.ResultFail()
row.Reason = "The assertion succeeded but was expected to fail"
rc.Fail++
}
resultsTable.Add(row)
testCount++
}
}
}
}
return nil
}
func printTestResult(
tests []v1alpha1.TestResult,
responses []engineapi.EngineResponse,
rc *resultCounts,
resultsTable *table.Table,
fs billy.Filesystem,
resoucePath string,
) error {
testCount := 1
for _, test := range tests {
// lookup matching engine responses (without the resource name)
// to reduce the search scope
responses := lookupEngineResponses(test, "", responses...)
// TODO fix deprecated fields
// identify the resources to be looked up
var resources []string
if test.Resources != nil {
resources = append(resources, test.Resources...)
} else if test.Resource != "" {
resources = append(resources, test.Resource)
}
for _, resource := range resources {
var rows []table.Row
// lookup matching engine responses (with the resource name this time)
for _, response := range lookupEngineResponses(test, resource, responses...) {
// lookup matching rule responses
for _, rule := range lookupRuleResponses(test, response.PolicyResponse.Rules...) {
// perform test checks
ok, message, reason := checkResult(test, fs, resoucePath, response, rule)
// if checks failed but we were expecting a fail it's considered a success
success := ok || (!ok && test.Result == policyreportv1alpha2.StatusFail)
row := table.Row{
RowCompact: table.RowCompact{
ID: testCount,
Policy: color.Policy("", test.Policy),
Rule: color.Rule(test.Rule),
Resource: color.Resource(test.Kind, test.Namespace, resource),
Reason: reason,
IsFailure: !success,
},
Message: message,
}
if success {
row.Result = color.ResultPass()
if test.Result == policyreportv1alpha2.StatusSkip {
rc.Skip++
} else {
rc.Pass++
}
} else {
row.Result = color.ResultFail()
rc.Fail++
}
testCount++
rows = append(rows, row)
}
// if there are no RuleResponse, the resource has been excluded. This is a pass.
if len(rows) == 0 {
row := table.Row{
RowCompact: table.RowCompact{
ID: testCount,
Policy: color.Policy("", test.Policy),
Rule: color.Rule(test.Rule),
Resource: color.Resource(test.Kind, test.Namespace, resource),
Result: color.ResultPass(),
Reason: color.Excluded(),
IsFailure: false,
},
Message: color.Excluded(),
}
rc.Skip++
testCount++
rows = append(rows, row)
}
}
// if not found
if len(rows) == 0 {
row := table.Row{
RowCompact: table.RowCompact{
ID: testCount,
Policy: color.Policy("", test.Policy),
Rule: color.Rule(test.Rule),
Resource: color.Resource(test.Kind, test.Namespace, resource),
IsFailure: true,
Result: color.ResultFail(),
Reason: color.NotFound(),
},
Message: color.NotFound(),
}
testCount++
resultsTable.Add(row)
rc.Fail++
} else {
resultsTable.Add(rows...)
}
}
}
return nil
}
func printFailedTestResult(out io.Writer, resultsTable table.Table, detailedResults bool) {
printer := table.NewTablePrinter(out)
for i := range resultsTable.RawRows {
resultsTable.RawRows[i].ID = i + 1
}
fmt.Fprintf(out, "Aggregated Failed Test Cases : ")
fmt.Fprintln(out)
printer.Print(resultsTable.Rows(detailedResults))
}