1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-05 07:26:55 +00:00

refactor: cli test command (#5550)

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
Charles-Edouard Brétéché 2022-12-03 19:56:09 +01:00 committed by GitHub
parent a7b4089613
commit 6893842226
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 172 additions and 143 deletions

View file

@ -13,7 +13,6 @@ import (
"github.com/go-git/go-billy/v5/memfs"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/api/kyverno/v1beta1"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/test"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/common"
sanitizederror "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/sanitizedError"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/store"
@ -21,6 +20,7 @@ import (
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/openapi"
policy2 "github.com/kyverno/kyverno/pkg/policy"
gitutils "github.com/kyverno/kyverno/pkg/utils/git"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/dynamic"
@ -251,13 +251,13 @@ func (c *ApplyCommandConfig) applyCommandHelper() (rc *common.ResultCounts, reso
var gitPathToYamls string
if isGit {
c.GitBranch, gitPathToYamls = common.GetGitBranchOrPolicyPaths(c.GitBranch, repoURL, c.PolicyPaths)
_, cloneErr := test.Clone(repoURL, fs, c.GitBranch)
_, cloneErr := gitutils.Clone(repoURL, fs, c.GitBranch)
if cloneErr != nil {
fmt.Printf("Error: failed to clone repository \nCause: %s\n", cloneErr)
log.Log.V(3).Info(fmt.Sprintf("failed to clone repository %v as it is not valid", repoURL), "error", cloneErr)
os.Exit(1)
}
policyYamls, err := test.ListYAMLs(fs, gitPathToYamls)
policyYamls, err := gitutils.ListYamls(fs, gitPathToYamls)
if err != nil {
return rc, resources, skipInvalidPolicies, pvInfos, sanitizederror.NewWithError("failed to list YAMLs in repository", err)
}

View file

@ -0,0 +1,67 @@
package api
import (
policyreportv1alpha2 "github.com/kyverno/kyverno/api/policyreport/v1alpha2"
corev1 "k8s.io/api/core/v1"
)
type Test struct {
Name string `json:"name"`
Policies []string `json:"policies"`
Resources []string `json:"resources"`
Variables string `json:"variables"`
UserInfo string `json:"userinfo"`
Results []TestResults `json:"results"`
}
type TestResults struct {
// Policy mentions the name of the policy.
Policy string `json:"policy"`
// Rule mentions the name of the rule in the policy.
Rule string `json:"rule"`
// Result mentions the result that the user is expecting.
// Possible values are pass, fail and skip.
Result policyreportv1alpha2.PolicyResult `json:"result"`
// Status mentions the status that the user is expecting.
// Possible values are pass, fail and skip.
Status policyreportv1alpha2.PolicyResult `json:"status"`
// Resource mentions the name of the resource on which the policy is to be applied.
Resource string `json:"resource"`
// Resources gives us the list of resources on which the policy is going to be applied.
Resources []string `json:"resources"`
// Kind mentions the kind of the resource on which the policy is to be applied.
Kind string `json:"kind"`
// Namespace mentions the namespace of the policy which has namespace scope.
Namespace string `json:"namespace"`
// PatchedResource takes a resource configuration file in yaml format from
// the user to compare it against the Kyverno mutated resource configuration.
PatchedResource string `json:"patchedResource"`
// AutoGeneratedRule is internally set by the CLI command. It takes values either
// autogen or autogen-cronjob.
AutoGeneratedRule string `json:"auto_generated_rule"`
// GeneratedResource takes a resource configuration file in yaml format from
// the user to compare it against the Kyverno generated resource configuration.
GeneratedResource string `json:"generatedResource"`
// CloneSourceResource takes the resource configuration file in yaml format
// from the user which is meant to be cloned by the generate rule.
CloneSourceResource string `json:"cloneSourceResource"`
}
type ReportResult struct {
TestResults
Resources []*corev1.ObjectReference `json:"resources"`
}
type Resource struct {
Name string `json:"name"`
Values map[string]string `json:"values"`
}
type Policy struct {
Name string `json:"name"`
Resources []Resource `json:"resources"`
}
type Values struct {
Policies []Policy `json:"policies"`
}

View file

@ -0,0 +1,21 @@
package manifest
func PrintValidate() {
print(`
name: <test_name>
policies:
- <path/to/policy1.yaml>
- <path/to/policy2.yaml>
resources:
- <path/to/resource1.yaml>
- <path/to/resource2.yaml>
variables: <variable_file> (OPTIONAL)
results:
- policy: <name> (For Namespaced [Policy] files, format is <policy_namespace>/<policy_name>)
rule: <name>
resource: <name>
namespace: <name> (OPTIONAL)
kind: <name>
patchedResource: <path/to/patched/resource.yaml>
result: <pass|fail|skip>`)
}

View file

@ -0,0 +1,7 @@
package manifest
import "fmt"
func print(manifest string) {
fmt.Println(manifest)
}

View file

@ -0,0 +1,20 @@
package manifest
func PrintMutate() {
print(`
name: <test_name>
policies:
- <path/to/policy1.yaml>
- <path/to/policy2.yaml>
resources:
- <path/to/resource1.yaml>
- <path/to/resource2.yaml>
variables: <variable_file> (OPTIONAL)
results:
- policy: <name> (For Namespaced [Policy] files, format is <policy_namespace>/<policy_name>)
rule: <name>
resource: <name>
namespace: <name> (OPTIONAL)
kind: <name>
result: <pass|fail|skip>`)
}

View file

@ -20,6 +20,8 @@ import (
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/api/kyverno/v1beta1"
policyreportv1alpha2 "github.com/kyverno/kyverno/api/policyreport/v1alpha2"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/test/api"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/test/manifest"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/common"
sanitizederror "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/sanitizedError"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/store"
@ -29,6 +31,7 @@ import (
"github.com/kyverno/kyverno/pkg/engine/response"
"github.com/kyverno/kyverno/pkg/openapi"
policy2 "github.com/kyverno/kyverno/pkg/policy"
gitutils "github.com/kyverno/kyverno/pkg/utils/git"
"github.com/lensesio/tableprinter"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"
@ -154,9 +157,8 @@ For more information visit https://kyverno.io/docs/kyverno-cli/#test
func Command() *cobra.Command {
var cmd *cobra.Command
var testCase string
var testFile []byte
var fileName, gitBranch string
var registryAccess, failOnly, removeColor bool
var registryAccess, failOnly, removeColor, manifestValidate, manifestMutate bool
cmd = &cobra.Command{
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),
@ -172,121 +174,32 @@ func Command() *cobra.Command {
}
}
}()
mStatus, _ := cmd.Flags().GetBool("manifest-mutate")
vStatus, _ := cmd.Flags().GetBool("manifest-validate")
if mStatus {
testFile = []byte(`name: <test_name>
policies:
- <path/to/policy1.yaml>
- <path/to/policy2.yaml>
resources:
- <path/to/resource1.yaml>
- <path/to/resource2.yaml>
variables: <variable_file> (OPTIONAL)
results:
- policy: <name> (For Namespaced [Policy] files, format is <policy_namespace>/<policy_name>)
rule: <name>
resource: <name>
namespace: <name> (OPTIONAL)
kind: <name>
patchedResource: <path/to/patched/resource.yaml>
result: <pass|fail|skip>`)
fmt.Println(string(testFile))
return nil
if manifestMutate {
manifest.PrintMutate()
} else if manifestValidate {
manifest.PrintValidate()
} else {
store.SetRegistryAccess(registryAccess)
_, err = testCommandExecute(dirPath, fileName, gitBranch, testCase, failOnly, removeColor)
if err != nil {
log.Log.V(3).Info("a directory is required")
return err
}
}
if vStatus {
testFile = []byte(`name: <test_name>
policies:
- <path/to/policy1.yaml>
- <path/to/policy2.yaml>
resources:
- <path/to/resource1.yaml>
- <path/to/resource2.yaml>
variables: <variable_file> (OPTIONAL)
results:
- policy: <name> (For Namespaced [Policy] files, format is <policy_namespace>/<policy_name>)
rule: <name>
resource: <name>
namespace: <name> (OPTIONAL)
kind: <name>
result: <pass|fail|skip>`)
fmt.Println(string(testFile))
return nil
}
store.SetRegistryAccess(registryAccess)
_, err = testCommandExecute(dirPath, fileName, gitBranch, testCase, failOnly, removeColor)
if err != nil {
log.Log.V(3).Info("a directory is required")
return err
}
return nil
},
}
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", "", `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.`)
cmd.Flags().BoolP("manifest-mutate", "", false, "prints out a template test manifest for a mutate policy")
cmd.Flags().BoolP("manifest-validate", "", false, "prints out a template test manifest for a validate policy")
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")
cmd.Flags().BoolVarP(&registryAccess, "registry", "", false, "If set to true, access the image registry using local docker credentials to populate external data")
cmd.Flags().BoolVarP(&failOnly, "fail-only", "", false, "If set to true, display all the failing test only as output for the test command")
cmd.Flags().BoolVarP(&removeColor, "remove-color", "", false, "Remove any color from output")
return cmd
}
type Test struct {
Name string `json:"name"`
Policies []string `json:"policies"`
Resources []string `json:"resources"`
Variables string `json:"variables"`
UserInfo string `json:"userinfo"`
Results []TestResults `json:"results"`
}
type TestResults struct {
// Policy mentions the name of the policy.
Policy string `json:"policy"`
// Rule mentions the name of the rule in the policy.
Rule string `json:"rule"`
// Result mentions the result that the user is expecting.
// Possible values are pass, fail and skip.
Result policyreportv1alpha2.PolicyResult `json:"result"`
// Status mentions the status that the user is expecting.
// Possible values are pass, fail and skip.
Status policyreportv1alpha2.PolicyResult `json:"status"`
// Resource mentions the name of the resource on which the policy is to be applied.
Resource string `json:"resource"`
// Resources gives us the list of resources on which the policy is going to be applied.
Resources []string `json:"resources"`
// Kind mentions the kind of the resource on which the policy is to be applied.
Kind string `json:"kind"`
// Namespace mentions the namespace of the policy which has namespace scope.
Namespace string `json:"namespace"`
// PatchedResource takes a resource configuration file in yaml format from
// the user to compare it against the Kyverno mutated resource configuration.
PatchedResource string `json:"patchedResource"`
// AutoGeneratedRule is internally set by the CLI command. It takes values either
// autogen or autogen-cronjob.
AutoGeneratedRule string `json:"auto_generated_rule"`
// GeneratedResource takes a resource configuration file in yaml format from
// the user to compare it against the Kyverno generated resource configuration.
GeneratedResource string `json:"generatedResource"`
// CloneSourceResource takes the resource configuration file in yaml format
// from the user which is meant to be cloned by the generate rule.
CloneSourceResource string `json:"cloneSourceResource"`
}
type ReportResult struct {
TestResults
Resources []*corev1.ObjectReference `json:"resources"`
}
type Resource struct {
Name string `json:"name"`
Values map[string]string `json:"values"`
}
type Table struct {
ID int `header:"#"`
Policy string `header:"policy"`
@ -294,14 +207,6 @@ type Table struct {
Resource string `header:"resource"`
Result string `header:"result"`
}
type Policy struct {
Name string `json:"name"`
Resources []Resource `json:"resources"`
}
type Values struct {
Policies []Policy `json:"policies"`
}
type resultCounts struct {
Skip int
@ -404,14 +309,14 @@ func testCommandExecute(dirPath []string, fileName string, gitBranch string, tes
}
}
_, cloneErr := Clone(repoURL, fs, gitBranch)
_, cloneErr := gitutils.Clone(repoURL, fs, gitBranch)
if cloneErr != nil {
fmt.Printf("Error: failed to clone repository \nCause: %s\n", cloneErr)
log.Log.V(3).Info(fmt.Sprintf("failed to clone repository %v as it is not valid", repoURL), "error", cloneErr)
os.Exit(1)
}
policyYamls, err := ListYAMLs(fs, gitPathToYamls)
policyYamls, err := gitutils.ListYamls(fs, gitPathToYamls)
if err != nil {
return rc, sanitizederror.NewWithError("failed to list YAMLs in repository", err)
}
@ -513,7 +418,7 @@ func getLocalDirTestFiles(fs billy.Filesystem, path, fileName string, rc *result
return errors
}
func buildPolicyResults(engineResponses []*response.EngineResponse, testResults []TestResults, infos []common.Info, policyResourcePath string, fs billy.Filesystem, isGit bool) (map[string]policyreportv1alpha2.PolicyReportResult, []TestResults) {
func buildPolicyResults(engineResponses []*response.EngineResponse, testResults []api.TestResults, infos []common.Info, policyResourcePath string, fs billy.Filesystem, isGit bool) (map[string]policyreportv1alpha2.PolicyReportResult, []api.TestResults) {
results := make(map[string]policyreportv1alpha2.PolicyReportResult)
now := metav1.Timestamp{Seconds: time.Now().Unix()}
@ -819,7 +724,7 @@ func getFullPath(paths []string, policyResourcePath string, isGit bool) []string
func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, isGit bool, policyResourcePath string, rc *resultCounts, openApiManager openapi.Manager, tf *testFilter, failOnly, removeColor bool) (err error) {
engineResponses := make([]*response.EngineResponse, 0)
var dClient dclient.Interface
values := &Test{}
values := &api.Test{}
var variablesString string
var pvInfos []common.Info
var resultCounts common.ResultCounts
@ -830,7 +735,7 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, isGit bool,
}
if tf.enabled {
var filteredResults []TestResults
var filteredResults []api.TestResults
for _, res := range values.Results {
if (len(tf.policy) == 0 || tf.policy == res.Policy) && (len(tf.resource) == 0 || tf.resource == res.Resource) && (len(tf.rule) == 0 || tf.rule == res.Rule) {
filteredResults = append(filteredResults, res)
@ -1045,7 +950,7 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, isGit bool,
return
}
func printTestResult(resps map[string]policyreportv1alpha2.PolicyReportResult, testResults []TestResults, rc *resultCounts, failOnly, removeColor bool) error {
func printTestResult(resps map[string]policyreportv1alpha2.PolicyReportResult, testResults []api.TestResults, rc *resultCounts, failOnly, removeColor bool) error {
printer := tableprinter.New(os.Stdout)
table := []Table{}
boldGreen := color.New(color.FgGreen).Add(color.Bold)

View file

@ -1,7 +1,8 @@
package test
package git
import (
"fmt"
"io/fs"
"os"
"path/filepath"
@ -21,37 +22,31 @@ func Clone(path string, fs billy.Filesystem, branch string) (*git.Repository, er
})
}
func ListYAMLs(fs billy.Filesystem, path string) ([]string, error) {
func ListFiles(fs billy.Filesystem, path string, predicate func(fs.FileInfo) bool) ([]string, error) {
path = filepath.Clean(path)
if _, err := fs.Stat(path); err != nil {
return nil, err
}
fis, err := fs.ReadDir(path)
files, err := fs.ReadDir(path)
if err != nil {
return nil, err
}
yamls := make([]string, 0)
for _, fi := range fis {
name := filepath.Join(path, fi.Name())
if fi.IsDir() {
moreYAMLs, err := ListYAMLs(fs, name)
var results []string
for _, file := range files {
name := filepath.Join(path, file.Name())
if file.IsDir() {
children, err := ListFiles(fs, name, predicate)
if err != nil {
return nil, err
}
yamls = append(yamls, moreYAMLs...)
continue
results = append(results, children...)
} else if predicate(file) {
results = append(results, name)
}
ext := filepath.Ext(name)
if ext != ".yml" && ext != ".yaml" {
continue
}
yamls = append(yamls, name)
}
return yamls, nil
return results, nil
}
func ListYamls(f billy.Filesystem, path string) ([]string, error) {
return ListFiles(f, path, IsYaml)
}

View file

@ -0,0 +1,14 @@
package git
import (
"io/fs"
"path/filepath"
)
func IsYaml(file fs.FileInfo) bool {
if file.IsDir() {
return false
}
ext := filepath.Ext(file.Name())
return ext == ".yml" || ext == ".yaml"
}