1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00

chore: improve unit tests in cli (#8300)

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
Charles-Edouard Brétéché 2023-09-07 00:02:48 +02:00 committed by GitHub
parent 2d8c74eb12
commit 7065d5da37
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 606 additions and 89 deletions

View file

@ -0,0 +1,40 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: pod-requirements
annotations:
pod-policies.kyverno.io/autogen-controllers: none
policies.kyverno.io/severity: medium
policies.kyverno.io/category: Pod Security Standards (Restricted)
spec:
background: false
validationFailureAction: audit
rules:
- name: pods-require-account
match:
resources:
kinds:
- Pod
validate:
message: User pods must include an account for charging
pattern:
metadata:
labels:
account: "*?"
- name: pods-require-limits
match:
resources:
kinds:
- Pod
validate:
message: CPU and memory resource requests and limits are required for user pods
pattern:
spec:
containers:
- resources:
requests:
memory: "?*"
cpu: "?*"
limits:
memory: "?*"
cpu: "?*"

View file

@ -0,0 +1,41 @@
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: pod-requirements
namespace: test
annotations:
pod-policies.kyverno.io/autogen-controllers: none
policies.kyverno.io/severity: medium
policies.kyverno.io/category: Pod Security Standards (Restricted)
spec:
background: false
validationFailureAction: audit
rules:
- name: pods-require-account
match:
resources:
kinds:
- Pod
validate:
message: User pods must include an account for charging
pattern:
metadata:
labels:
account: "*?"
- name: pods-require-limits
match:
resources:
kinds:
- Pod
validate:
message: CPU and memory resource requests and limits are required for user pods
pattern:
spec:
containers:
- resources:
requests:
memory: "?*"
cpu: "?*"
limits:
memory: "?*"
cpu: "?*"

View file

@ -58,7 +58,7 @@ func ComputePolicyReportResult(auditWarn bool, engineResponse engineapi.EngineRe
}
func ComputePolicyReportResultsPerPolicy(auditWarn bool, engineResponses ...engineapi.EngineResponse) map[engineapi.GenericPolicy][]policyreportv1alpha2.PolicyReportResult {
results := make(map[engineapi.GenericPolicy][]policyreportv1alpha2.PolicyReportResult)
results := map[engineapi.GenericPolicy][]policyreportv1alpha2.PolicyReportResult{}
for _, engineResponse := range engineResponses {
if len(engineResponse.PolicyResponse.Rules) == 0 {
continue
@ -73,6 +73,9 @@ func ComputePolicyReportResultsPerPolicy(auditWarn bool, engineResponses ...engi
results[policy] = append(results[policy], result)
}
}
if len(results) == 0 {
return nil
}
return results
}

View file

@ -1,96 +1,26 @@
package report
import (
"encoding/json"
"reflect"
"testing"
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
policyreportv1alpha2 "github.com/kyverno/kyverno/api/policyreport/v1alpha2"
report "github.com/kyverno/kyverno/api/policyreport/v1alpha2"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/policy"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"gotest.tools/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
var rawPolicy = []byte(`
{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "pod-requirements",
"annotations": {
"pod-policies.kyverno.io/autogen-controllers": "none",
"policies.kyverno.io/severity": "medium",
"policies.kyverno.io/category": "Pod Security Standards (Restricted)"
}
},
"spec": {
"background": false,
"validationFailureAction": "audit",
"rules": [
{
"name": "pods-require-account",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"validate": {
"message": "User pods must include an account for charging",
"pattern": {
"metadata": {
"labels": {
"account": "*?"
}
}
}
}
},
{
"name": "pods-require-limits",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"validate": {
"message": "CPU and memory resource requests and limits are required for user pods",
"pattern": {
"spec": {
"containers": [
{
"resources": {
"requests": {
"memory": "?*",
"cpu": "?*"
},
"limits": {
"memory": "?*",
"cpu": "?*"
}
}
}
]
}
}
}
}
]
}
}
`)
func TestComputePolicyReports(t *testing.T) {
var policy kyverno.ClusterPolicy
err := json.Unmarshal(rawPolicy, &policy)
func TestComputeClusterPolicyReports(t *testing.T) {
policies, _, err := policy.Load(nil, "", "../_testdata/policies/cpol-pod-requirements.yaml")
assert.NilError(t, err)
assert.Equal(t, len(policies), 1)
policy := policies[0]
er := engineapi.EngineResponse{}
er = er.WithPolicy(engineapi.NewKyvernoPolicy(&policy))
er = er.WithPolicy(engineapi.NewKyvernoPolicy(policy))
er.PolicyResponse.Add(
engineapi.ExecutionStats{},
*engineapi.RuleFail(
@ -118,12 +48,48 @@ func TestComputePolicyReports(t *testing.T) {
}
}
func TestComputePolicyReportResultsPerPolicy(t *testing.T) {
var policy kyverno.ClusterPolicy
err := json.Unmarshal(rawPolicy, &policy)
func TestComputePolicyReports(t *testing.T) {
policies, _, err := policy.Load(nil, "", "../_testdata/policies/pol-pod-requirements.yaml")
assert.NilError(t, err)
assert.Equal(t, len(policies), 1)
policy := policies[0]
er := engineapi.EngineResponse{}
er = er.WithPolicy(engineapi.NewKyvernoPolicy(&policy))
er = er.WithPolicy(engineapi.NewKyvernoPolicy(policy))
er.PolicyResponse.Add(
engineapi.ExecutionStats{},
*engineapi.RuleFail(
"pods-require-account",
engineapi.Validation,
"validation error: User pods must include an account for charging. Rule pods-require-account failed at path /metadata/labels/",
),
*engineapi.RulePass(
"pods-require-limits",
engineapi.Validation,
"validation rule 'pods-require-limits' passed.",
),
)
clustered, namespaced := ComputePolicyReports(false, er)
assert.Equal(t, len(clustered), 0)
assert.Equal(t, len(namespaced), 1)
{
report := namespaced[0]
assert.Equal(t, report.GetName(), policy.GetName())
assert.Equal(t, report.GetNamespace(), policy.GetNamespace())
assert.Equal(t, report.Kind, "PolicyReport")
assert.Equal(t, len(report.Results), 2)
assert.Equal(t, report.Results[0].Severity, policyreportv1alpha2.SeverityMedium)
assert.Equal(t, report.Results[0].Category, "Pod Security Standards (Restricted)")
assert.Equal(t, report.Summary.Pass, 1)
}
}
func TestComputePolicyReportResultsPerPolicyOld(t *testing.T) {
policies, _, err := policy.Load(nil, "", "../_testdata/policies/cpol-pod-requirements.yaml")
assert.NilError(t, err)
assert.Equal(t, len(policies), 1)
policy := policies[0]
er := engineapi.EngineResponse{}
er = er.WithPolicy(engineapi.NewKyvernoPolicy(policy))
er.PolicyResponse.Add(
engineapi.ExecutionStats{}, *engineapi.RuleFail(
"pods-require-account",
@ -249,3 +215,145 @@ func TestMergeClusterReport(t *testing.T) {
assert.Equal(t, cpolr.Summary.Pass, 2)
assert.Equal(t, cpolr.Summary.Fail, 2)
}
func TestComputePolicyReportResult(t *testing.T) {
policies, _, err := policy.Load(nil, "", "../_testdata/policies/cpol-pod-requirements.yaml")
assert.NilError(t, err)
assert.Equal(t, len(policies), 1)
policy := policies[0]
tests := []struct {
name string
auditWarn bool
engineResponse engineapi.EngineResponse
ruleResponse engineapi.RuleResponse
want policyreportv1alpha2.PolicyReportResult
}{{
name: "skip",
auditWarn: false,
engineResponse: engineapi.NewEngineResponse(unstructured.Unstructured{}, engineapi.NewKyvernoPolicy(policy), nil),
ruleResponse: *engineapi.RuleSkip("xxx", engineapi.Mutation, "test"),
want: policyreportv1alpha2.PolicyReportResult{
Source: "kyverno",
Policy: "pod-requirements",
Rule: "xxx",
Result: policyreportv1alpha2.StatusSkip,
Resources: []corev1.ObjectReference{{}},
Message: "test",
Scored: true,
Category: "Pod Security Standards (Restricted)",
Severity: policyreportv1alpha2.SeverityMedium,
},
}, {
name: "pass",
auditWarn: false,
engineResponse: engineapi.NewEngineResponse(unstructured.Unstructured{}, engineapi.NewKyvernoPolicy(policy), nil),
ruleResponse: *engineapi.RulePass("xxx", engineapi.Mutation, "test"),
want: policyreportv1alpha2.PolicyReportResult{
Source: "kyverno",
Policy: "pod-requirements",
Rule: "xxx",
Result: policyreportv1alpha2.StatusPass,
Resources: []corev1.ObjectReference{{}},
Message: "test",
Scored: true,
Category: "Pod Security Standards (Restricted)",
Severity: policyreportv1alpha2.SeverityMedium,
},
}, {
name: "fail",
auditWarn: false,
engineResponse: engineapi.NewEngineResponse(unstructured.Unstructured{}, engineapi.NewKyvernoPolicy(policy), nil),
ruleResponse: *engineapi.RuleFail("xxx", engineapi.Mutation, "test"),
want: policyreportv1alpha2.PolicyReportResult{
Source: "kyverno",
Policy: "pod-requirements",
Rule: "xxx",
Result: policyreportv1alpha2.StatusFail,
Resources: []corev1.ObjectReference{{}},
Message: "test",
Scored: true,
Category: "Pod Security Standards (Restricted)",
Severity: policyreportv1alpha2.SeverityMedium,
},
}, {
name: "fail - audit warn",
auditWarn: true,
engineResponse: engineapi.NewEngineResponse(unstructured.Unstructured{}, engineapi.NewKyvernoPolicy(policy), nil),
ruleResponse: *engineapi.RuleFail("xxx", engineapi.Mutation, "test"),
want: policyreportv1alpha2.PolicyReportResult{
Source: "kyverno",
Policy: "pod-requirements",
Rule: "xxx",
Result: policyreportv1alpha2.StatusWarn,
Resources: []corev1.ObjectReference{{}},
Message: "test",
Scored: true,
Category: "Pod Security Standards (Restricted)",
Severity: policyreportv1alpha2.SeverityMedium,
},
}, {
name: "error",
auditWarn: false,
engineResponse: engineapi.NewEngineResponse(unstructured.Unstructured{}, engineapi.NewKyvernoPolicy(policy), nil),
ruleResponse: *engineapi.RuleError("xxx", engineapi.Mutation, "test", nil),
want: policyreportv1alpha2.PolicyReportResult{
Source: "kyverno",
Policy: "pod-requirements",
Rule: "xxx",
Result: policyreportv1alpha2.StatusError,
Resources: []corev1.ObjectReference{{}},
Message: "test",
Scored: true,
Category: "Pod Security Standards (Restricted)",
Severity: policyreportv1alpha2.SeverityMedium,
},
}, {
name: "warn",
auditWarn: false,
engineResponse: engineapi.NewEngineResponse(unstructured.Unstructured{}, engineapi.NewKyvernoPolicy(policy), nil),
ruleResponse: *engineapi.RuleWarn("xxx", engineapi.Mutation, "test"),
want: policyreportv1alpha2.PolicyReportResult{
Source: "kyverno",
Policy: "pod-requirements",
Rule: "xxx",
Result: policyreportv1alpha2.StatusError,
Resources: []corev1.ObjectReference{{}},
Message: "test",
Scored: true,
Category: "Pod Security Standards (Restricted)",
Severity: policyreportv1alpha2.SeverityMedium,
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ComputePolicyReportResult(tt.auditWarn, tt.engineResponse, tt.ruleResponse)
got.Timestamp = metav1.Timestamp{}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ComputePolicyReportResult() = %v, want %v", got, tt.want)
}
})
}
}
func TestComputePolicyReportResultsPerPolicy(t *testing.T) {
tests := []struct {
name string
auditWarn bool
engineResponses []engineapi.EngineResponse
want map[engineapi.GenericPolicy][]policyreportv1alpha2.PolicyReportResult
}{{
name: "empty",
auditWarn: false,
engineResponses: func() []engineapi.EngineResponse {
return []engineapi.EngineResponse{{}}
}(),
want: nil,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ComputePolicyReportResultsPerPolicy(tt.auditWarn, tt.engineResponses...); !reflect.DeepEqual(got, tt.want) {
t.Errorf("ComputePolicyReportResultsPerPolicy() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -14,13 +14,13 @@ import (
func load(fs billy.Filesystem, path string, resourcePath string) ([]byte, error) {
if fs != nil {
filep, err := fs.Open(filepath.Join(resourcePath, path))
file, err := fs.Open(filepath.Join(resourcePath, path))
if err != nil {
return nil, fmt.Errorf("Unable to open userInfo file: %s. \nerror: %s", path, err)
}
bytes, err := io.ReadAll(filep)
bytes, err := io.ReadAll(file)
if err != nil {
return nil, fmt.Errorf("Error: failed to read file %s: %w", filep.Name(), err)
return nil, fmt.Errorf("Error: failed to read file %s: %w", file.Name(), err)
}
return bytes, err
} else {

View file

@ -1,15 +1,34 @@
package userinfo
import (
"os"
"reflect"
"testing"
"github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/memfs"
kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1"
authenticationv1 "k8s.io/api/authentication/v1"
)
func TestLoad(t *testing.T) {
fs := func(path string) billy.Filesystem {
t.Helper()
f := memfs.New()
file, err := f.Create("valid.yaml")
if err != nil {
t.Fatal(err)
}
defer file.Close()
data, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
if _, err := file.Write(data); err != nil {
t.Fatal(err)
}
return f
}
tests := []struct {
name string
fs billy.Filesystem
@ -43,8 +62,33 @@ func TestLoad(t *testing.T) {
},
},
wantErr: false,
},
}
}, {
name: "empty (billy)",
fs: fs("../_testdata/user-infos/valid.yaml"),
path: "",
resourcePath: "",
want: nil,
wantErr: true,
}, {
name: "invalid (billy)",
fs: fs("../_testdata/user-infos/valid.yaml"),
path: "invalid.yaml",
resourcePath: "",
want: nil,
wantErr: true,
}, {
name: "valid (billy)",
fs: fs("../_testdata/user-infos/valid.yaml"),
path: "valid.yaml",
resourcePath: "",
want: &kyvernov1beta1.RequestInfo{
ClusterRoles: []string{"cluster-admin"},
AdmissionUserInfo: authenticationv1.UserInfo{
Username: "molybdenum@somecorp.com",
},
},
wantErr: false,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Load(tt.fs, tt.path, tt.resourcePath)

View file

@ -59,10 +59,10 @@ func Test_readFile(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer file.Close()
if _, err := file.Write([]byte("foo: bar")); err != nil {
t.Fatal(err)
}
defer file.Close()
return f
}(),
filepath: "valid.yaml",

View file

@ -0,0 +1,200 @@
package variables
import (
"reflect"
"testing"
"github.com/go-git/go-billy/v5"
valuesapi "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/apis/values"
)
func TestNew(t *testing.T) {
type args struct {
}
tests := []struct {
name string
fs billy.Filesystem
resourcePath string
path string
vals *valuesapi.Values
vars []string
want *Variables
wantErr bool
}{{
name: "empty",
fs: nil,
resourcePath: "",
path: "",
vals: nil,
vars: nil,
want: &Variables{},
wantErr: false,
}, {
name: "vars",
fs: nil,
resourcePath: "",
path: "",
vals: nil,
vars: []string{
"foo=bar",
},
want: &Variables{
variables: map[string]string{
"foo": "bar",
},
},
wantErr: false,
}, {
name: "values",
fs: nil,
resourcePath: "",
path: "",
vals: &valuesapi.Values{
GlobalValues: map[string]string{
"bar": "baz",
},
},
vars: nil,
want: &Variables{
values: &valuesapi.Values{
GlobalValues: map[string]string{
"bar": "baz",
},
},
},
wantErr: false,
}, {
name: "values and vars",
fs: nil,
resourcePath: "",
path: "",
vals: &valuesapi.Values{
GlobalValues: map[string]string{
"bar": "baz",
},
},
vars: []string{
"foo=bar",
},
want: &Variables{
values: &valuesapi.Values{
GlobalValues: map[string]string{
"bar": "baz",
},
},
variables: map[string]string{
"foo": "bar",
},
},
wantErr: false,
}, {
name: "values file",
fs: nil,
resourcePath: "",
path: "../_testdata/values/valid.yaml",
vals: nil,
vars: nil,
want: &Variables{
values: &valuesapi.Values{
NamespaceSelectors: []valuesapi.NamespaceSelector{{
Name: "test1",
Labels: map[string]string{
"foo.com/managed-state": "managed",
},
}},
Policies: []valuesapi.Policy{{
Name: "limit-configmap-for-sa",
Resources: []valuesapi.Resource{{
Name: "any-configmap-name-good",
Values: map[string]interface{}{
"request.operation": "UPDATE",
},
}, {
Name: "any-configmap-name-bad",
Values: map[string]interface{}{
"request.operation": "UPDATE",
},
}},
}},
},
},
wantErr: false,
}, {
name: "values file and vars",
fs: nil,
resourcePath: "",
path: "../_testdata/values/valid.yaml",
vals: nil,
vars: []string{
"foo=bar",
},
want: &Variables{
values: &valuesapi.Values{
NamespaceSelectors: []valuesapi.NamespaceSelector{{
Name: "test1",
Labels: map[string]string{
"foo.com/managed-state": "managed",
},
}},
Policies: []valuesapi.Policy{{
Name: "limit-configmap-for-sa",
Resources: []valuesapi.Resource{{
Name: "any-configmap-name-good",
Values: map[string]interface{}{
"request.operation": "UPDATE",
},
}, {
Name: "any-configmap-name-bad",
Values: map[string]interface{}{
"request.operation": "UPDATE",
},
}},
}},
},
variables: map[string]string{
"foo": "bar",
},
},
wantErr: false,
}, {
name: "bad values file",
fs: nil,
resourcePath: "",
path: "../_testdata/values/bad-format.yaml",
vals: nil,
vars: nil,
want: nil,
wantErr: true,
}, {
name: "values and values file",
fs: nil,
resourcePath: "",
path: "../_testdata/values/valid.yaml",
vals: &valuesapi.Values{
GlobalValues: map[string]string{
"bar": "baz",
},
},
vars: nil,
want: &Variables{
values: &valuesapi.Values{
GlobalValues: map[string]string{
"bar": "baz",
},
},
},
wantErr: false,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := New(tt.fs, tt.resourcePath, tt.path, tt.vals, tt.vars...)
if (err != nil) != tt.wantErr {
t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("New() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -0,0 +1,81 @@
package variables
import (
"reflect"
"testing"
)
func Test_parse(t *testing.T) {
tests := []struct {
name string
vars []string
want map[string]string
}{{
name: "nil",
vars: nil,
want: nil,
}, {
name: "empty",
vars: []string{},
want: nil,
}, {
name: "request.object",
vars: []string{
"request.object.spec=something",
},
want: nil,
}, {
name: "duplicate",
vars: []string{
"foo=something",
"foo=something-else",
},
want: map[string]string{
"foo": "something",
},
}, {
name: "invalid",
vars: []string{
"foo",
},
want: nil,
}, {
name: "valid",
vars: []string{
"object.data=123",
},
want: map[string]string{
"object.data": "123",
},
}, {
name: "valid",
vars: []string{
"object.data=123",
"object.spec=abc",
},
want: map[string]string{
"object.data": "123",
"object.spec": "abc",
},
}, {
name: "mixed",
vars: []string{
"object.data=123",
"bar",
"foo=",
"object.spec=abc",
"=baz",
},
want: map[string]string{
"object.data": "123",
"object.spec": "abc",
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := parse(tt.vars...); !reflect.DeepEqual(got, tt.want) {
t.Errorf("parse() = %v, want %v", got, tt.want)
}
})
}
}