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-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-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"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/test/api"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/test/filter"
2023-09-04 21:56:23 +02:00
cobrautils "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/cobra"
2023-09-04 11:34:27 +02:00
reportutils "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/report"
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"
2023-09-04 11:34:27 +02:00
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
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
)
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-08-29 21:28:26 +02:00
var registryAccess , failOnly , removeColor , detailedResults bool
2021-02-18 01:00:41 +05:30
cmd = & cobra . Command {
2023-09-04 21:56:23 +02:00
Use : "test [local folder or git repository]..." ,
Args : cobra . MinimumNArgs ( 1 ) ,
2023-09-05 04:14:28 +02:00
Short : cobrautils . FormatDescription ( true , websiteUrl , false , description ... ) ,
Long : cobrautils . FormatDescription ( false , websiteUrl , false , description ... ) ,
2023-09-04 21:56:23 +02:00
Example : cobrautils . FormatExamples ( examples ... ) ,
2021-02-01 23:34:15 +05:30
RunE : func ( cmd * cobra . Command , dirPath [ ] string ) ( err error ) {
2023-07-06 13:48:19 +02:00
color . 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" )
}
}
} ( )
2023-08-29 21:28:26 +02:00
store . SetRegistryAccess ( registryAccess )
2023-09-05 13:09:45 +02:00
return testCommandExecute ( dirPath , fileName , gitBranch , testCase , 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 (
dirPath [ ] string ,
fileName string ,
gitBranch string ,
testCase string ,
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-05 13:09:45 +02:00
return 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
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 {
fmt . Println ( )
fmt . Println ( "Filter errors:" )
for _ , e := range errors {
2023-09-05 13:09:45 +02:00
fmt . Println ( " Error:" , e )
2023-08-30 12:24:43 +02:00
}
}
2023-04-12 17:17:51 +02:00
// 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 {
2023-09-05 13:09:45 +02:00
return fmt . Errorf ( "unable to create open api controller, %w" , err )
2022-02-24 15:34:12 +00: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 {
fmt . Println ( )
2023-09-05 13:09:45 +02:00
fmt . Println ( "Error loading tests:" , err )
return err
2023-09-04 13:36:34 +02:00
}
if len ( tests ) == 0 {
2023-08-29 00:04:00 +02:00
fmt . Println ( )
fmt . Println ( "No test yamls available" )
}
2023-09-04 13:36:34 +02:00
if errs := tests . Errors ( ) ; len ( errs ) > 0 {
2023-08-29 00:04:00 +02:00
fmt . Println ( )
fmt . Println ( "Test errors:" )
2023-09-04 13:36:34 +02:00
for _ , e := range errs {
2023-09-05 13:09:45 +02:00
fmt . Println ( " Path:" , e . Path )
fmt . Println ( " 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-09-05 15:19:05 +02:00
// filter results
var filteredResults [ ] api . TestResults
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-09-05 15:19:05 +02:00
responses , err := runTest ( openApiManager , test , false )
if err != nil {
return sanitizederror . NewWithError ( "failed to run test" , err )
}
t , err := printTestResult ( filteredResults , responses , rc , failOnly , detailedResults , test . Fs , resourcePath )
if err != nil {
2023-09-05 13:09:45 +02:00
return sanitizederror . NewWithError ( "failed to print test result:" , err )
}
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 {
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 ( )
2023-06-30 07:39:04 -05:00
if rc . Fail > 0 {
if ! failOnly {
2023-07-31 17:15:47 +03:00
printFailedTestResult ( 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-04 11:34:27 +02:00
func checkResult ( test api . TestResults , fs billy . Filesystem , resoucePath string , response engineapi . EngineResponse , rule engineapi . RuleResponse ) ( bool , string , string ) {
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"
}
}
result := reportutils . ComputePolicyReportResult ( false , response , rule )
if result . Result != expected {
return false , result . Message , fmt . Sprintf ( "Want %s, got %s" , expected , result . Result )
}
return true , result . Message , "Ok"
}
func lookupEngineResponses ( test api . TestResults , resourceName string , responses ... engineapi . EngineResponse ) [ ] engineapi . EngineResponse {
var matches [ ] engineapi . EngineResponse
for _ , response := range responses {
policy := response . Policy ( )
resource := response . Resource
if policy . GetName ( ) != test . Policy {
continue
}
if test . Kind != resource . GetKind ( ) {
continue
}
if resourceName != "" && resourceName != resource . GetName ( ) {
continue
}
if test . Namespace != "" && test . Namespace != resource . GetNamespace ( ) {
continue
}
matches = append ( matches , response )
}
return matches
}
func lookupRuleResponses ( test api . TestResults , responses ... engineapi . RuleResponse ) [ ] engineapi . RuleResponse {
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
}