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"
2023-09-14 01:55:19 +02:00
"io"
2023-09-04 13:36:34 +02:00
"path/filepath"
2021-02-07 20:26:56 -08:00
2023-09-04 11:34:27 +02:00
"github.com/go-git/go-billy/v5"
2023-09-17 22:50:17 +02:00
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/apis/v1alpha1"
2023-09-06 16:44:50 +02:00
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/command"
2023-12-20 13:45:26 +01:00
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/deprecations"
2023-09-05 10:55:01 +02:00
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/output/color"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/output/table"
2023-09-06 18:02:23 +02:00
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/report"
2023-09-05 10:55:01 +02:00
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/test/filter"
2023-09-04 11:34:27 +02:00
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
2021-02-07 20:26:56 -08:00
"github.com/spf13/cobra"
2023-09-12 16:33:26 +02:00
"k8s.io/client-go/tools/cache"
2021-02-01 16:22:41 +05:30
)
func Command ( ) * cobra . Command {
2022-03-09 13:10:53 +05:30
var testCase string
var fileName , gitBranch string
2023-08-29 21:28:26 +02:00
var registryAccess , failOnly , removeColor , detailedResults bool
2023-09-12 16:33:26 +02:00
cmd := & cobra . Command {
2023-09-13 11:53:19 +02:00
Use : "test [local folder or git repository]..." ,
Short : command . FormatDescription ( true , websiteUrl , false , description ... ) ,
Long : command . FormatDescription ( false , websiteUrl , false , description ... ) ,
Example : command . FormatExamples ( examples ... ) ,
Args : cobra . MinimumNArgs ( 1 ) ,
SilenceUsage : true ,
2021-02-01 23:34:15 +05:30
RunE : func ( cmd * cobra . Command , dirPath [ ] string ) ( err error ) {
2023-10-27 11:50:36 +02:00
color . Init ( removeColor )
2023-12-19 15:45:53 +01:00
return testCommandExecute ( cmd . OutOrStdout ( ) , dirPath , fileName , gitBranch , testCase , registryAccess , failOnly , detailedResults )
2021-02-01 16:22:41 +05:30
} ,
}
2023-09-04 21:56:23 +02:00
cmd . Flags ( ) . StringVarP ( & fileName , "file-name" , "f" , "kyverno-test.yaml" , "Test filename" )
cmd . Flags ( ) . StringVarP ( & gitBranch , "git-branch" , "b" , "" , "Test github repository branch" )
cmd . Flags ( ) . StringVarP ( & testCase , "test-case-selector" , "t" , "policy=*,rule=*,resource=*" , "Filter test cases to run" )
2023-07-03 12:04:07 +02:00
cmd . Flags ( ) . BoolVar ( & registryAccess , "registry" , false , "If set to true, access the image registry using local docker credentials to populate external data" )
cmd . Flags ( ) . BoolVar ( & failOnly , "fail-only" , false , "If set to true, display all the failing test only as output for the test command" )
cmd . Flags ( ) . BoolVar ( & removeColor , "remove-color" , false , "Remove any color from output" )
2023-07-31 17:15:47 +03:00
cmd . Flags ( ) . BoolVar ( & detailedResults , "detailed-results" , false , "If set to true, display detailed results" )
2021-02-18 01:00:41 +05:30
return cmd
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 (
2023-09-14 01:55:19 +02:00
out io . Writer ,
2023-04-12 14:51:03 +02:00
dirPath [ ] string ,
fileName string ,
gitBranch string ,
testCase string ,
2023-12-19 15:45:53 +01:00
registryAccess bool ,
2023-04-12 14:51:03 +02:00
failOnly bool ,
2023-07-31 17:15:47 +03:00
detailedResults bool ,
2023-09-05 13:09:45 +02:00
) ( 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 {
2023-09-12 18:07:06 +02:00
return fmt . Errorf ( "a directory is required" )
2021-02-07 20:26:56 -08:00
}
2023-04-12 17:17:51 +02:00
// parse filter
2023-09-05 10:55:01 +02:00
filter , errors := filter . ParseFilter ( testCase )
2023-08-30 12:24:43 +02:00
if len ( errors ) > 0 {
2023-09-14 01:55:19 +02:00
fmt . Fprintln ( out )
fmt . Fprintln ( out , "Filter errors:" )
2023-08-30 12:24:43 +02:00
for _ , e := range errors {
2023-09-14 01:55:19 +02:00
fmt . Fprintln ( out , " Error:" , e )
2023-08-30 12:24:43 +02:00
}
}
2023-04-18 14:08:17 +02:00
// load tests
2023-09-04 22:27:20 +02:00
tests , err := loadTests ( dirPath , fileName , gitBranch )
2023-09-04 13:36:34 +02:00
if err != nil {
2023-09-14 01:55:19 +02:00
fmt . Fprintln ( out )
fmt . Fprintln ( out , "Error loading tests:" , err )
2023-09-05 13:09:45 +02:00
return err
2023-09-04 13:36:34 +02:00
}
if len ( tests ) == 0 {
2023-09-14 01:55:19 +02:00
fmt . Fprintln ( out )
fmt . Fprintln ( out , "No test yamls available" )
2023-08-29 00:04:00 +02:00
}
2023-09-04 13:36:34 +02:00
if errs := tests . Errors ( ) ; len ( errs ) > 0 {
2023-09-14 01:55:19 +02:00
fmt . Fprintln ( out )
fmt . Fprintln ( out , "Test errors:" )
2023-09-04 13:36:34 +02:00
for _ , e := range errs {
2023-09-14 01:55:19 +02:00
fmt . Fprintln ( out , " Path:" , e . Path )
fmt . Fprintln ( out , " Error:" , e . Err )
2023-08-29 00:04:00 +02:00
}
}
2023-09-04 13:36:34 +02:00
if len ( tests ) == 0 {
2023-08-29 00:04:00 +02:00
if len ( errors ) == 0 {
2023-09-05 15:19:05 +02:00
return nil
2023-08-29 00:04:00 +02:00
} else {
2023-09-05 15:19:05 +02:00
// TODO aggregate errors
return errors [ 0 ]
2023-08-29 00:04:00 +02:00
}
2023-04-18 14:08:17 +02:00
}
2023-09-05 13:09:45 +02:00
rc := & resultCounts { }
2023-07-06 13:48:19 +02:00
var table table . Table
2023-09-05 13:09:45 +02:00
for _ , test := range tests {
if test . Err == nil {
2023-12-20 13:45:26 +01:00
deprecations . CheckTest ( out , test . Path , test . Test )
2023-09-05 15:19:05 +02:00
// filter results
2023-09-17 22:50:17 +02:00
var filteredResults [ ] v1alpha1 . TestResult
2023-09-05 15:19:05 +02:00
for _ , res := range test . Test . Results {
if filter . Apply ( res ) {
filteredResults = append ( filteredResults , res )
}
}
if len ( filteredResults ) == 0 {
continue
}
2023-09-05 13:09:45 +02:00
resourcePath := filepath . Dir ( test . Path )
2023-12-19 15:45:53 +01:00
responses , err := runTest ( out , test , registryAccess , false )
2023-09-05 15:19:05 +02:00
if err != nil {
2023-09-12 18:07:06 +02:00
return fmt . Errorf ( "failed to run test (%w)" , err )
2023-09-05 15:19:05 +02:00
}
2023-09-14 13:45:18 +02:00
fmt . Fprintln ( out , " Checking results ..." )
2023-09-14 01:55:19 +02:00
t , err := printTestResult ( out , filteredResults , responses , rc , failOnly , detailedResults , test . Fs , resourcePath )
2023-09-05 15:19:05 +02:00
if err != nil {
2023-09-12 18:07:06 +02:00
return fmt . Errorf ( "failed to print test result (%w)" , err )
2023-09-05 13:09:45 +02:00
}
2023-09-05 15:19:05 +02:00
table . AddFailed ( t . RawRows ... )
2022-01-14 14:15:59 +05:30
}
2021-05-03 08:20:22 -04:00
}
2022-07-22 20:02:12 +05:30
if ! failOnly {
2023-09-14 01:55:19 +02:00
fmt . Fprintf ( out , "\nTest Summary: %d tests passed and %d tests failed\n" , rc . Pass + rc . Skip , rc . Fail )
2022-07-22 20:02:12 +05:30
} else {
2023-09-14 01:55:19 +02:00
fmt . Fprintf ( out , "\nTest Summary: %d out of %d tests failed\n" , rc . Fail , rc . Pass + rc . Skip + rc . Fail )
2022-07-22 20:02:12 +05:30
}
2023-09-14 01:55:19 +02:00
fmt . Fprintln ( out )
2023-06-30 07:39:04 -05:00
if rc . Fail > 0 {
if ! failOnly {
2023-09-14 01:55:19 +02:00
printFailedTestResult ( out , table , detailedResults )
2023-06-30 07:39:04 -05:00
}
2023-09-05 15:19:05 +02:00
return fmt . Errorf ( "%d tests failed" , rc . Fail )
2021-03-05 05:39:18 +05:30
}
2023-09-05 13:09:45 +02:00
return nil
2021-02-01 16:22:41 +05:30
}
2021-02-01 23:34:15 +05:30
2023-09-17 22:50:17 +02:00
func checkResult ( test v1alpha1 . TestResult , fs billy . Filesystem , resoucePath string , response engineapi . EngineResponse , rule engineapi . RuleResponse ) ( bool , string , string ) {
2023-09-04 11:34:27 +02:00
expected := test . Result
// fallback to the deprecated field
if expected == "" {
expected = test . Status
}
// fallback on deprecated field
if test . PatchedResource != "" {
2023-09-05 01:25:06 +02:00
equals , err := getAndCompareResource ( response . PatchedResource , fs , filepath . Join ( resoucePath , test . PatchedResource ) )
2023-09-04 11:34:27 +02:00
if err != nil {
return false , err . Error ( ) , "Resource error"
}
if ! equals {
return false , "Patched resource didn't match the patched resource in the test result" , "Resource diff"
}
}
if test . GeneratedResource != "" {
2023-09-05 01:25:06 +02:00
equals , err := getAndCompareResource ( rule . GeneratedResource ( ) , fs , filepath . Join ( resoucePath , test . GeneratedResource ) )
2023-09-04 11:34:27 +02:00
if err != nil {
return false , err . Error ( ) , "Resource error"
}
if ! equals {
return false , "Generated resource didn't match the generated resource in the test result" , "Resource diff"
}
}
2023-09-12 16:33:26 +02:00
result := report . ComputePolicyReportResult ( false , response , rule )
2023-09-04 11:34:27 +02:00
if result . Result != expected {
return false , result . Message , fmt . Sprintf ( "Want %s, got %s" , expected , result . Result )
}
return true , result . Message , "Ok"
}
2023-09-17 22:50:17 +02:00
func lookupEngineResponses ( test v1alpha1 . TestResult , resourceName string , responses ... engineapi . EngineResponse ) [ ] engineapi . EngineResponse {
2023-09-04 11:34:27 +02:00
var matches [ ] engineapi . EngineResponse
for _ , response := range responses {
policy := response . Policy ( )
resource := response . Resource
2023-09-12 16:33:26 +02:00
pName := cache . MetaObjectToName ( policy . MetaObject ( ) ) . String ( )
rName := cache . MetaObjectToName ( & resource ) . String ( )
2023-09-04 11:34:27 +02:00
if test . Kind != resource . GetKind ( ) {
continue
}
2023-09-12 16:33:26 +02:00
if pName != test . Policy {
2023-09-04 11:34:27 +02:00
continue
}
2023-09-12 16:33:26 +02:00
if resourceName != "" && rName != resourceName && resource . GetName ( ) != resourceName {
2023-09-04 11:34:27 +02:00
continue
}
matches = append ( matches , response )
}
return matches
}
2023-09-17 22:50:17 +02:00
func lookupRuleResponses ( test v1alpha1 . TestResult , responses ... engineapi . RuleResponse ) [ ] engineapi . RuleResponse {
2023-09-04 11:34:27 +02:00
var matches [ ] engineapi . RuleResponse
2023-09-04 19:32:23 +03:00
// Since there are no rules in case of validating admission policies, responses are returned without checking rule names.
if test . IsValidatingAdmissionPolicy {
matches = responses
} else {
for _ , response := range responses {
rule := response . Name ( )
if rule != test . Rule && rule != "autogen-" + test . Rule && rule != "autogen-cronjob-" + test . Rule {
continue
}
matches = append ( matches , response )
2023-09-04 11:34:27 +02:00
}
}
return matches
}