2021-02-07 20:26:56 -08:00
package test
2021-02-01 16:22:41 +05:30
import (
2021-02-07 20:26:56 -08:00
"fmt"
2021-02-01 16:22:41 +05:30
"os"
2021-02-07 20:26:56 -08:00
2022-05-17 13:12:43 +02:00
policyreportv1alpha2 "github.com/kyverno/kyverno/api/policyreport/v1alpha2"
2022-12-03 19:56:09 +01:00
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/test/api"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/test/manifest"
2022-04-14 17:50:18 +05:30
sanitizederror "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/sanitizedError"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/store"
2021-02-01 16:22:41 +05:30
"github.com/kyverno/kyverno/pkg/openapi"
2021-02-07 20:26:56 -08:00
"github.com/spf13/cobra"
2022-12-09 22:15:23 +05:30
"sigs.k8s.io/controller-runtime/pkg/log"
2021-02-01 16:22:41 +05:30
)
// Command returns version command
func Command ( ) * cobra . Command {
2021-02-18 01:00:41 +05:30
var cmd * cobra . Command
2022-03-09 13:10:53 +05:30
var testCase string
var fileName , gitBranch string
2023-04-18 14:08:17 +02:00
var registryAccess , failOnly , removeColor , manifestValidate , manifestMutate , compact bool
2021-02-18 01:00:41 +05:30
cmd = & cobra . Command {
2022-02-21 10:53:29 +05:30
Use : "test <path_to_folder_Containing_test.yamls> [flags]\n kyverno test <path_to_gitRepository_with_dir> --git-branch <branchName>\n kyverno test --manifest-mutate > kyverno-test.yaml\n kyverno test --manifest-validate > kyverno-test.yaml" ,
// Args: cobra.ExactArgs(1),
2022-12-27 22:46:01 +08:00
Short : "Run tests from directory." ,
2021-10-01 14:16:33 +05:30
Long : longHelp ,
Example : exampleHelp ,
2021-02-01 23:34:15 +05:30
RunE : func ( cmd * cobra . Command , dirPath [ ] string ) ( err error ) {
2023-04-18 14:08:17 +02:00
initColors ( removeColor )
2021-02-01 16:22:41 +05:30
defer func ( ) {
if err != nil {
if ! sanitizederror . IsErrorSanitized ( err ) {
log . Log . Error ( err , "failed to sanitize" )
err = fmt . Errorf ( "internal error" )
}
}
} ( )
2022-12-03 19:56:09 +01:00
if manifestMutate {
manifest . PrintMutate ( )
} else if manifestValidate {
manifest . PrintValidate ( )
} else {
store . SetRegistryAccess ( registryAccess )
2023-04-18 14:08:17 +02:00
_ , err = testCommandExecute ( dirPath , fileName , gitBranch , testCase , failOnly , false , compact )
2022-12-03 19:56:09 +01:00
if err != nil {
log . Log . V ( 3 ) . Info ( "a directory is required" )
return err
}
2021-02-01 16:22:41 +05:30
}
return nil
} ,
}
2021-12-17 12:01:34 +05:30
cmd . Flags ( ) . StringVarP ( & fileName , "file-name" , "f" , "kyverno-test.yaml" , "test filename" )
2021-12-20 11:39:53 +05:30
cmd . Flags ( ) . StringVarP ( & gitBranch , "git-branch" , "b" , "" , "test github repository branch" )
2022-03-09 13:10:53 +05:30
cmd . Flags ( ) . StringVarP ( & testCase , "test-case-selector" , "t" , "" , ` run some specific test cases by passing a string argument in double quotes to this flag like - "policy=<policy_name>, rule=<rule_name>, resource=<resource_name". The argument could be any combination of policy, rule and resource. ` )
2022-12-03 19:56:09 +01:00
cmd . Flags ( ) . BoolVarP ( & manifestMutate , "manifest-mutate" , "" , false , "prints out a template test manifest for a mutate policy" )
cmd . Flags ( ) . BoolVarP ( & manifestValidate , "manifest-validate" , "" , false , "prints out a template test manifest for a validate policy" )
2022-03-16 09:56:47 +05:30
cmd . Flags ( ) . BoolVarP ( & registryAccess , "registry" , "" , false , "If set to true, access the image registry using local docker credentials to populate external data" )
2022-07-22 20:02:12 +05:30
cmd . Flags ( ) . BoolVarP ( & failOnly , "fail-only" , "" , false , "If set to true, display all the failing test only as output for the test command" )
2022-08-19 19:11:19 +05:30
cmd . Flags ( ) . BoolVarP ( & removeColor , "remove-color" , "" , false , "Remove any color from output" )
2023-04-18 14:08:17 +02:00
cmd . Flags ( ) . BoolVarP ( & compact , "compact" , "" , true , "Does not show detailed results" )
2021-02-18 01:00:41 +05:30
return cmd
2021-02-01 16:22:41 +05:30
}
type Table struct {
2023-04-18 14:08:17 +02:00
rows [ ] Row
}
func ( t * Table ) Rows ( compact bool ) interface { } {
if ! compact {
return t . rows
}
var rows [ ] CompactRow
for _ , row := range t . rows {
rows = append ( rows , row . CompactRow )
}
return rows
}
func ( t * Table ) AddFailed ( rows ... Row ) {
for _ , row := range rows {
if row . isFailure {
t . rows = append ( t . rows , row )
}
}
}
func ( t * Table ) Add ( rows ... Row ) {
t . rows = append ( t . rows , rows ... )
}
type CompactRow struct {
isFailure bool
ID int ` header:"id" `
Policy string ` header:"policy" `
Rule string ` header:"rule" `
Resource string ` header:"resource" `
Result string ` header:"result" `
}
type Row struct {
CompactRow ` header:"inline" `
Message string ` header:"message" `
2021-02-01 16:22:41 +05:30
}
2021-03-05 05:39:18 +05:30
type resultCounts struct {
2021-09-02 23:11:35 +05:30
Skip int
Pass int
Fail int
2021-03-05 05:39:18 +05:30
}
2023-04-12 14:51:03 +02:00
func testCommandExecute (
dirPath [ ] string ,
fileName string ,
gitBranch string ,
testCase string ,
failOnly bool ,
auditWarn bool ,
2023-04-18 14:08:17 +02:00
compact bool ,
2023-04-12 14:51:03 +02:00
) ( rc * resultCounts , err error ) {
2023-04-12 17:17:51 +02:00
// check input dir
2021-02-01 23:34:15 +05:30
if len ( dirPath ) == 0 {
2022-05-09 18:55:35 +02:00
return rc , sanitizederror . NewWithError ( "a directory is required" , err )
2021-02-07 20:26:56 -08:00
}
2023-04-12 17:17:51 +02:00
// parse filter
filter := parseFilter ( testCase )
// init openapi manager
2023-02-09 16:15:51 +01:00
openApiManager , err := openapi . NewManager ( log . Log )
2022-02-24 15:34:12 +00:00
if err != nil {
return rc , fmt . Errorf ( "unable to create open api controller, %w" , err )
}
2023-04-18 14:08:17 +02:00
// load tests
fs , policies , errors := loadTests ( dirPath , fileName , gitBranch )
if len ( policies ) == 0 {
fmt . Printf ( "\n No test yamls available \n" )
}
2023-04-12 17:17:51 +02:00
rc = & resultCounts { }
2023-04-18 14:08:17 +02:00
var table Table
for _ , p := range policies {
if reports , tests , err := applyPoliciesFromPath (
fs ,
p . bytes ,
fs != nil ,
p . resourcePath ,
rc ,
openApiManager ,
filter ,
auditWarn ,
) ; err != nil {
return rc , sanitizederror . NewWithError ( "failed to apply test command" , err )
} else if t , err := printTestResult ( reports , tests , rc , failOnly , compact ) ; err != nil {
return rc , sanitizederror . NewWithError ( "failed to print test result:" , err )
2021-12-20 11:39:53 +05:30
} else {
2023-04-18 14:08:17 +02:00
table . AddFailed ( t . rows ... )
2022-01-14 14:15:59 +05:30
}
2021-05-03 08:20:22 -04:00
}
if len ( errors ) > 0 && log . Log . V ( 1 ) . Enabled ( ) {
2023-04-18 14:08:17 +02:00
fmt . Println ( "test errors:" )
2021-05-03 08:20:22 -04:00
for _ , e := range errors {
fmt . Printf ( " %v \n" , e . Error ( ) )
2021-02-01 16:22:41 +05:30
}
}
2022-07-22 20:02:12 +05:30
if ! failOnly {
fmt . Printf ( "\nTest Summary: %d tests passed and %d tests failed\n" , rc . Pass + rc . Skip , rc . Fail )
} else {
fmt . Printf ( "\nTest Summary: %d out of %d tests failed\n" , rc . Fail , rc . Pass + rc . Skip + rc . Fail )
}
2023-04-18 14:08:17 +02:00
fmt . Println ( )
2022-07-22 20:02:12 +05:30
if rc . Fail > 0 && ! failOnly {
2023-04-18 14:08:17 +02:00
printFailedTestResult ( table , compact )
2021-03-05 05:39:18 +05:30
os . Exit ( 1 )
}
os . Exit ( 0 )
return rc , nil
2021-02-01 16:22:41 +05:30
}
2021-02-01 23:34:15 +05:30
2023-04-18 14:08:17 +02:00
func printTestResult ( resps map [ string ] policyreportv1alpha2 . PolicyReportResult , testResults [ ] api . TestResults , rc * resultCounts , failOnly bool , compact bool ) ( Table , error ) {
printer := newTablePrinter ( )
var table Table
2022-08-24 15:08:24 +02:00
var countDeprecatedResource int
2022-10-11 18:00:11 +05:30
testCount := 1
for _ , v := range testResults {
2023-04-18 14:08:17 +02:00
var row Row
row . ID = testCount
2022-10-11 18:00:11 +05:30
if v . Resources == nil {
testCount ++
}
2023-04-18 14:08:17 +02:00
row . Policy = boldFgCyan . Sprint ( v . Policy )
row . Rule = boldFgCyan . Sprint ( v . Rule )
2021-10-01 22:43:21 +05:30
2022-06-20 12:08:13 +05:30
if v . Resources != nil {
for _ , resource := range v . Resources {
2023-04-18 14:08:17 +02:00
row . ID = testCount
2022-10-11 18:00:11 +05:30
testCount ++
2023-04-18 14:08:17 +02:00
row . Resource = boldFgCyan . Sprint ( v . Namespace ) + "/" + boldFgCyan . Sprint ( v . Kind ) + "/" + boldFgCyan . Sprint ( resource )
2022-06-20 12:08:13 +05:30
var ruleNameInResultKey string
if v . AutoGeneratedRule != "" {
ruleNameInResultKey = fmt . Sprintf ( "%s-%s" , v . AutoGeneratedRule , v . Rule )
} else {
ruleNameInResultKey = v . Rule
}
2021-10-01 22:43:21 +05:30
2022-06-20 12:08:13 +05:30
resultKey := fmt . Sprintf ( "%s-%s-%s-%s" , v . Policy , ruleNameInResultKey , v . Kind , resource )
found , _ := isNamespacedPolicy ( v . Policy )
var ns string
ns , v . Policy = getUserDefinedPolicyNameAndNamespace ( v . Policy )
if found && v . Namespace != "" {
resultKey = fmt . Sprintf ( "%s-%s-%s-%s-%s-%s" , ns , v . Policy , ruleNameInResultKey , v . Namespace , v . Kind , resource )
} else if found {
resultKey = fmt . Sprintf ( "%s-%s-%s-%s-%s" , ns , v . Policy , ruleNameInResultKey , v . Kind , resource )
2023-04-18 14:08:17 +02:00
row . Policy = boldFgCyan . Sprint ( ns ) + "/" + boldFgCyan . Sprint ( v . Policy )
row . Resource = boldFgCyan . Sprint ( v . Namespace ) + "/" + boldFgCyan . Sprint ( v . Kind ) + "/" + boldFgCyan . Sprint ( resource )
2022-06-20 12:08:13 +05:30
} else if v . Namespace != "" {
2023-04-18 14:08:17 +02:00
row . Resource = boldFgCyan . Sprint ( v . Namespace ) + "/" + boldFgCyan . Sprint ( v . Kind ) + "/" + boldFgCyan . Sprint ( resource )
2022-06-20 12:08:13 +05:30
resultKey = fmt . Sprintf ( "%s-%s-%s-%s-%s" , v . Policy , ruleNameInResultKey , v . Namespace , v . Kind , resource )
}
var testRes policyreportv1alpha2 . PolicyReportResult
if val , ok := resps [ resultKey ] ; ok {
testRes = val
} else {
log . Log . V ( 2 ) . Info ( "result not found" , "key" , resultKey )
2023-04-18 14:08:17 +02:00
row . Result = boldYellow . Sprint ( "Not found" )
2022-06-20 12:08:13 +05:30
rc . Fail ++
2023-04-18 14:08:17 +02:00
row . isFailure = true
table . Add ( row )
2022-06-20 12:08:13 +05:30
continue
}
2023-04-18 14:08:17 +02:00
row . Message = testRes . Message
2022-06-20 12:08:13 +05:30
if v . Result == "" && v . Status != "" {
v . Result = v . Status
}
if testRes . Result == v . Result {
2023-04-18 14:08:17 +02:00
row . Result = boldGreen . Sprint ( "Pass" )
2022-08-19 19:11:19 +05:30
if testRes . Result == policyreportv1alpha2 . StatusSkip {
2022-06-20 12:08:13 +05:30
rc . Skip ++
} else {
rc . Pass ++
}
} else {
log . Log . V ( 2 ) . Info ( "result mismatch" , "expected" , v . Result , "received" , testRes . Result , "key" , resultKey )
2023-04-18 14:08:17 +02:00
row . Result = boldRed . Sprint ( "Fail" )
2022-06-20 12:08:13 +05:30
rc . Fail ++
2023-04-18 14:08:17 +02:00
row . isFailure = true
2022-06-20 12:08:13 +05:30
}
2022-07-22 20:02:12 +05:30
if failOnly {
2023-04-18 14:08:17 +02:00
if row . Result == boldRed . Sprintf ( "Fail" ) || row . Result == "Fail" {
table . Add ( row )
2022-07-22 20:02:12 +05:30
}
} else {
2023-04-18 14:08:17 +02:00
table . Add ( row )
2022-07-22 20:02:12 +05:30
}
2022-06-20 12:08:13 +05:30
}
} else if v . Resource != "" {
countDeprecatedResource ++
2023-04-18 14:08:17 +02:00
row . Resource = boldFgCyan . Sprint ( v . Namespace ) + "/" + boldFgCyan . Sprint ( v . Kind ) + "/" + boldFgCyan . Sprint ( v . Resource )
2022-06-20 12:08:13 +05:30
var ruleNameInResultKey string
if v . AutoGeneratedRule != "" {
ruleNameInResultKey = fmt . Sprintf ( "%s-%s" , v . AutoGeneratedRule , v . Rule )
} else {
ruleNameInResultKey = v . Rule
}
2021-10-01 22:43:21 +05:30
2022-06-20 12:08:13 +05:30
resultKey := fmt . Sprintf ( "%s-%s-%s-%s" , v . Policy , ruleNameInResultKey , v . Kind , v . Resource )
found , _ := isNamespacedPolicy ( v . Policy )
var ns string
ns , v . Policy = getUserDefinedPolicyNameAndNamespace ( v . Policy )
if found && v . Namespace != "" {
resultKey = fmt . Sprintf ( "%s-%s-%s-%s-%s-%s" , ns , v . Policy , ruleNameInResultKey , v . Namespace , v . Kind , v . Resource )
} else if found {
resultKey = fmt . Sprintf ( "%s-%s-%s-%s-%s" , ns , v . Policy , ruleNameInResultKey , v . Kind , v . Resource )
2023-04-18 14:08:17 +02:00
row . Policy = boldFgCyan . Sprint ( ns ) + "/" + boldFgCyan . Sprint ( v . Policy )
row . Resource = boldFgCyan . Sprint ( v . Namespace ) + "/" + boldFgCyan . Sprint ( v . Kind ) + "/" + boldFgCyan . Sprint ( v . Resource )
2022-06-20 12:08:13 +05:30
} else if v . Namespace != "" {
2023-04-18 14:08:17 +02:00
row . Resource = boldFgCyan . Sprint ( v . Namespace ) + "/" + boldFgCyan . Sprint ( v . Kind ) + "/" + boldFgCyan . Sprint ( v . Resource )
2022-06-20 12:08:13 +05:30
resultKey = fmt . Sprintf ( "%s-%s-%s-%s-%s" , v . Policy , ruleNameInResultKey , v . Namespace , v . Kind , v . Resource )
}
2021-10-01 22:43:21 +05:30
2022-06-20 12:08:13 +05:30
var testRes policyreportv1alpha2 . PolicyReportResult
if val , ok := resps [ resultKey ] ; ok {
testRes = val
} else {
log . Log . V ( 2 ) . Info ( "result not found" , "key" , resultKey )
2023-04-18 14:08:17 +02:00
row . Result = boldYellow . Sprint ( "Not found" )
2022-06-20 12:08:13 +05:30
rc . Fail ++
2023-04-18 14:08:17 +02:00
row . isFailure = true
table . Add ( row )
2022-06-20 12:08:13 +05:30
continue
}
2023-04-18 14:08:17 +02:00
row . Message = testRes . Message
2022-06-20 12:08:13 +05:30
if v . Result == "" && v . Status != "" {
v . Result = v . Status
}
2021-10-01 22:43:21 +05:30
2022-06-20 12:08:13 +05:30
if testRes . Result == v . Result {
2023-04-18 14:08:17 +02:00
row . Result = boldGreen . Sprint ( "Pass" )
2022-08-19 19:11:19 +05:30
if testRes . Result == policyreportv1alpha2 . StatusSkip {
2022-06-20 12:08:13 +05:30
rc . Skip ++
} else {
rc . Pass ++
}
2021-05-07 19:27:15 -04:00
} else {
2022-06-20 12:08:13 +05:30
log . Log . V ( 2 ) . Info ( "result mismatch" , "expected" , v . Result , "received" , testRes . Result , "key" , resultKey )
2023-04-18 14:08:17 +02:00
row . Result = boldRed . Sprint ( "Fail" )
2022-06-20 12:08:13 +05:30
rc . Fail ++
2023-04-18 14:08:17 +02:00
row . isFailure = true
2021-02-01 16:22:41 +05:30
}
2021-10-01 22:43:21 +05:30
2022-07-22 20:02:12 +05:30
if failOnly {
2023-04-18 14:08:17 +02:00
if row . Result == boldRed . Sprintf ( "Fail" ) || row . Result == "Fail" {
table . Add ( row )
2022-07-22 20:02:12 +05:30
}
} else {
2023-04-18 14:08:17 +02:00
table . Add ( row )
2022-07-22 20:02:12 +05:30
}
2022-06-20 12:08:13 +05:30
}
2021-02-07 20:26:56 -08:00
}
2021-10-01 14:16:33 +05:30
fmt . Printf ( "\n" )
2023-04-18 14:08:17 +02:00
printer . Print ( table . Rows ( compact ) )
return table , nil
2021-02-07 20:26:56 -08:00
}
2022-08-19 19:11:19 +05:30
2023-04-18 14:08:17 +02:00
func printFailedTestResult ( table Table , compact bool ) {
printer := newTablePrinter ( )
for i := range table . rows {
table . rows [ i ] . ID = i + 1
2022-06-02 23:01:46 +05:30
}
fmt . Printf ( "Aggregated Failed Test Cases : " )
2023-04-18 14:08:17 +02:00
fmt . Println ( )
printer . Print ( table . Rows ( compact ) )
2022-06-02 23:01:46 +05:30
}