mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-31 03:45:17 +00:00
To support gitURLs for "apply" command (#4502)
* apply cmd support gitURL Signed-off-by: viveksahu26 <vivekkumarsahu650@gmail.com> * Added e2e test cases Signed-off-by: viveksahu26 <vivekkumarsahu650@gmail.com> * fix e2e test case Signed-off-by: viveksahu26 <vivekkumarsahu650@gmail.com> * added functions to make it simple Signed-off-by: viveksahu26 <vivekkumarsahu650@gmail.com> * fixed tests Signed-off-by: viveksahu26 <vivekkumarsahu650@gmail.com> * added more e2e test Signed-off-by: viveksahu26 <vivekkumarsahu650@gmail.com> * typo mistake Signed-off-by: viveksahu26 <vivekkumarsahu650@gmail.com> * removed uncovert linters Signed-off-by: viveksahu26 <vivekkumarsahu650@gmail.com> * renamed variables, functions Signed-off-by: viveksahu26 <vivekkumarsahu650@gmail.com> * test: to check urls are git source or not. Signed-off-by: viveksahu26 <vivekkumarsahu650@gmail.com> * remove misleading message Signed-off-by: viveksahu26 <vivekkumarsahu650@gmail.com> Signed-off-by: viveksahu26 <vivekkumarsahu650@gmail.com> Co-authored-by: Charles-Edouard Brétéché <charled.breteche@gmail.com>
This commit is contained in:
parent
f8ed1a9301
commit
82e1fd417d
6 changed files with 169 additions and 6 deletions
|
@ -3,13 +3,17 @@ package apply
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-git/go-billy/v5/memfs"
|
"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/api/kyverno/v1beta1"
|
||||||
|
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/test"
|
||||||
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/common"
|
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/common"
|
||||||
sanitizederror "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/sanitizedError"
|
sanitizederror "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/sanitizedError"
|
||||||
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/store"
|
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/store"
|
||||||
|
@ -59,6 +63,7 @@ type ApplyCommandConfig struct {
|
||||||
AuditWarn bool
|
AuditWarn bool
|
||||||
ResourcePaths []string
|
ResourcePaths []string
|
||||||
PolicyPaths []string
|
PolicyPaths []string
|
||||||
|
GitBranch string
|
||||||
}
|
}
|
||||||
|
|
||||||
var applyHelp = `
|
var applyHelp = `
|
||||||
|
@ -72,6 +77,9 @@ To apply on a folder of resources:
|
||||||
To apply on a cluster:
|
To apply on a cluster:
|
||||||
kyverno apply /path/to/policy.yaml /path/to/folderOfPolicies --cluster
|
kyverno apply /path/to/policy.yaml /path/to/folderOfPolicies --cluster
|
||||||
|
|
||||||
|
To apply policies from a gitSourceURL on a cluster:
|
||||||
|
Example: Taking github.com as a gitSourceURL here. Some other standards gitSourceURL are: gitlab.com , bitbucket.org , etc.
|
||||||
|
kyverno apply https://github.com/kyverno/policies/openshift/ --git-branch main --cluster
|
||||||
|
|
||||||
To apply policy with variables:
|
To apply policy with variables:
|
||||||
|
|
||||||
|
@ -165,6 +173,7 @@ func Command() *cobra.Command {
|
||||||
cmd.Flags().BoolVarP(&applyCommandConfig.RegistryAccess, "registry", "", false, "If set to true, access the image registry using local docker credentials to populate external data")
|
cmd.Flags().BoolVarP(&applyCommandConfig.RegistryAccess, "registry", "", false, "If set to true, access the image registry using local docker credentials to populate external data")
|
||||||
cmd.Flags().StringVarP(&applyCommandConfig.KubeConfig, "kubeconfig", "", "", "path to kubeconfig file with authorization and master location information")
|
cmd.Flags().StringVarP(&applyCommandConfig.KubeConfig, "kubeconfig", "", "", "path to kubeconfig file with authorization and master location information")
|
||||||
cmd.Flags().StringVarP(&applyCommandConfig.Context, "context", "", "", "The name of the kubeconfig context to use")
|
cmd.Flags().StringVarP(&applyCommandConfig.Context, "context", "", "", "The name of the kubeconfig context to use")
|
||||||
|
cmd.Flags().StringVarP(&applyCommandConfig.GitBranch, "git-branch", "b", "", "test git repository branch")
|
||||||
cmd.Flags().BoolVarP(&applyCommandConfig.AuditWarn, "audit-warn", "", false, "If set to true, will flag audit policies as warnings instead of failures")
|
cmd.Flags().BoolVarP(&applyCommandConfig.AuditWarn, "audit-warn", "", false, "If set to true, will flag audit policies as warnings instead of failures")
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
@ -222,7 +231,40 @@ func (c *ApplyCommandConfig) applyCommandHelper() (rc *common.ResultCounts, reso
|
||||||
return rc, resources, skipInvalidPolicies, pvInfos, sanitizederror.NewWithError("a stdin pipe can be used for either policies or resources, not both", err)
|
return rc, resources, skipInvalidPolicies, pvInfos, sanitizederror.NewWithError("a stdin pipe can be used for either policies or resources, not both", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
policies, err := common.GetPoliciesFromPaths(fs, c.PolicyPaths, false, "")
|
isGit := common.IsGitSourcePath(c.PolicyPaths)
|
||||||
|
var policies []kyvernov1.PolicyInterface
|
||||||
|
gitSourceURL, err := url.Parse(c.PolicyPaths[0])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error: failed to load policies\nCause: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pathElems := strings.Split(gitSourceURL.Path[1:], "/")
|
||||||
|
if len(pathElems) <= 1 {
|
||||||
|
err := fmt.Errorf("invalid URL path %s - expected https://<any_git_source_domain>/:owner/:repository/:branch (without --git-branch flag) OR https://<any_git_source_domain>/:owner/:repository/:directory (with --git-branch flag)", gitSourceURL.Path)
|
||||||
|
fmt.Printf("Error: failed to parse URL \nCause: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
gitSourceURL.Path = strings.Join([]string{pathElems[0], pathElems[1]}, "/")
|
||||||
|
repoURL := gitSourceURL.String()
|
||||||
|
var gitPathToYamls string
|
||||||
|
if isGit {
|
||||||
|
c.GitBranch, gitPathToYamls = common.GetGitBranchOrPolicyPaths(c.GitBranch, repoURL, c.PolicyPaths)
|
||||||
|
_, cloneErr := test.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)
|
||||||
|
if err != nil {
|
||||||
|
return rc, resources, skipInvalidPolicies, pvInfos, sanitizederror.NewWithError("failed to list YAMLs in repository", err)
|
||||||
|
}
|
||||||
|
c.PolicyPaths = policyYamls
|
||||||
|
sort.Strings(policyYamls)
|
||||||
|
}
|
||||||
|
policies, err = common.GetPoliciesFromPaths(fs, c.PolicyPaths, isGit, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error: failed to load policies\nCause: %s\n", err)
|
fmt.Printf("Error: failed to load policies\nCause: %s\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
|
@ -10,7 +10,9 @@ import (
|
||||||
func Test_Apply(t *testing.T) {
|
func Test_Apply(t *testing.T) {
|
||||||
type TestCase struct {
|
type TestCase struct {
|
||||||
PolicyPaths []string
|
PolicyPaths []string
|
||||||
|
GitBranch string
|
||||||
ResourcePaths []string
|
ResourcePaths []string
|
||||||
|
Cluster bool
|
||||||
expectedPolicyReports []preport.PolicyReport
|
expectedPolicyReports []preport.PolicyReport
|
||||||
config ApplyCommandConfig
|
config ApplyCommandConfig
|
||||||
}
|
}
|
||||||
|
@ -70,6 +72,25 @@ func Test_Apply(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
config: ApplyCommandConfig{
|
||||||
|
PolicyPaths: []string{"https://github.com/kyverno/policies/openshift/team-validate-ns-name/"},
|
||||||
|
GitBranch: "main",
|
||||||
|
PolicyReport: true,
|
||||||
|
Cluster: true,
|
||||||
|
},
|
||||||
|
expectedPolicyReports: []preport.PolicyReport{
|
||||||
|
{
|
||||||
|
Summary: preport.PolicyReportSummary{
|
||||||
|
Pass: 9,
|
||||||
|
Fail: 0,
|
||||||
|
Skip: 0,
|
||||||
|
Error: 0,
|
||||||
|
Warn: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
config: ApplyCommandConfig{
|
config: ApplyCommandConfig{
|
||||||
PolicyPaths: []string{"../../../../test/best_practices/disallow_latest_tag.yaml"},
|
PolicyPaths: []string{"../../../../test/best_practices/disallow_latest_tag.yaml"},
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"github.com/go-git/go-git/v5/storage/memory"
|
"github.com/go-git/go-git/v5/storage/memory"
|
||||||
)
|
)
|
||||||
|
|
||||||
func clone(path string, fs billy.Filesystem, branch string) (*git.Repository, error) {
|
func Clone(path string, fs billy.Filesystem, branch string) (*git.Repository, error) {
|
||||||
return git.Clone(memory.NewStorage(), fs, &git.CloneOptions{
|
return git.Clone(memory.NewStorage(), fs, &git.CloneOptions{
|
||||||
URL: path,
|
URL: path,
|
||||||
ReferenceName: plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", branch)),
|
ReferenceName: plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", branch)),
|
||||||
|
@ -21,7 +21,7 @@ func clone(path string, fs billy.Filesystem, branch string) (*git.Repository, er
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func listYAMLs(fs billy.Filesystem, path string) ([]string, error) {
|
func ListYAMLs(fs billy.Filesystem, path string) ([]string, error) {
|
||||||
path = filepath.Clean(path)
|
path = filepath.Clean(path)
|
||||||
|
|
||||||
if _, err := fs.Stat(path); err != nil {
|
if _, err := fs.Stat(path); err != nil {
|
||||||
|
@ -37,7 +37,7 @@ func listYAMLs(fs billy.Filesystem, path string) ([]string, error) {
|
||||||
for _, fi := range fis {
|
for _, fi := range fis {
|
||||||
name := filepath.Join(path, fi.Name())
|
name := filepath.Join(path, fi.Name())
|
||||||
if fi.IsDir() {
|
if fi.IsDir() {
|
||||||
moreYAMLs, err := listYAMLs(fs, name)
|
moreYAMLs, err := ListYAMLs(fs, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -404,14 +404,14 @@ func testCommandExecute(dirPath []string, fileName string, gitBranch string, tes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, cloneErr := clone(repoURL, fs, gitBranch)
|
_, cloneErr := Clone(repoURL, fs, gitBranch)
|
||||||
if cloneErr != nil {
|
if cloneErr != nil {
|
||||||
fmt.Printf("Error: failed to clone repository \nCause: %s\n", cloneErr)
|
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)
|
log.Log.V(3).Info(fmt.Sprintf("failed to clone repository %v as it is not valid", repoURL), "error", cloneErr)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
policyYamls, err := listYAMLs(fs, gitPathToYamls)
|
policyYamls, err := ListYAMLs(fs, gitPathToYamls)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return rc, sanitizederror.NewWithError("failed to list YAMLs in repository", err)
|
return rc, sanitizederror.NewWithError("failed to list YAMLs in repository", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1120,3 +1120,31 @@ func GetUserInfoFromPath(fs billy.Filesystem, path string, isGit bool, policyRes
|
||||||
}
|
}
|
||||||
return *userInfo, *subjectInfo, nil
|
return *userInfo, *subjectInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsGitSourcePath(policyPaths []string) bool {
|
||||||
|
return strings.Contains(policyPaths[0], "https://")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGitBranchOrPolicyPaths(gitBranch, repoURL string, policyPaths []string) (string, string) {
|
||||||
|
var gitPathToYamls string
|
||||||
|
if gitBranch == "" {
|
||||||
|
gitPathToYamls = "/"
|
||||||
|
if string(policyPaths[0][len(policyPaths[0])-1]) == "/" {
|
||||||
|
gitBranch = strings.ReplaceAll(policyPaths[0], repoURL+"/", "")
|
||||||
|
} else {
|
||||||
|
gitBranch = strings.ReplaceAll(policyPaths[0], repoURL, "")
|
||||||
|
}
|
||||||
|
if gitBranch == "" {
|
||||||
|
gitBranch = "main"
|
||||||
|
} else if string(gitBranch[0]) == "/" {
|
||||||
|
gitBranch = gitBranch[1:]
|
||||||
|
}
|
||||||
|
return gitBranch, gitPathToYamls
|
||||||
|
}
|
||||||
|
if string(policyPaths[0][len(policyPaths[0])-1]) == "/" {
|
||||||
|
gitPathToYamls = strings.ReplaceAll(policyPaths[0], repoURL+"/", "/")
|
||||||
|
} else {
|
||||||
|
gitPathToYamls = strings.ReplaceAll(policyPaths[0], repoURL, "/")
|
||||||
|
}
|
||||||
|
return gitBranch, gitPathToYamls
|
||||||
|
}
|
||||||
|
|
|
@ -115,3 +115,75 @@ func Test_NamespaceSelector(t *testing.T) {
|
||||||
assert.Equal(t, int64(rc.Error), int64(tc.result.Error))
|
assert.Equal(t, int64(rc.Error), int64(tc.result.Error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_IsGitSourcePath(t *testing.T) {
|
||||||
|
type TestCase struct {
|
||||||
|
path []string
|
||||||
|
actual bool
|
||||||
|
desired bool
|
||||||
|
}
|
||||||
|
testcases := []TestCase{
|
||||||
|
{
|
||||||
|
path: []string{"https://github.com/kyverno/policies/openshift/team-validate-ns-name/"},
|
||||||
|
desired: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: []string{"/kyverno/policies/openshift/team-validate-ns-name/"},
|
||||||
|
desired: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: []string{"https://bitbucket.org/kyverno/policies/openshift/team-validate-ns-name"},
|
||||||
|
desired: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: []string{"https://anydomain.com/kyverno/policies/openshift/team-validate-ns-name"},
|
||||||
|
desired: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testcases {
|
||||||
|
tc.actual = IsGitSourcePath(tc.path)
|
||||||
|
if tc.actual != tc.desired {
|
||||||
|
t.Errorf("%s is not a git URL", tc.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_GetGitBranchOrPolicyPaths(t *testing.T) {
|
||||||
|
type TestCase struct {
|
||||||
|
gitBranch string
|
||||||
|
repoURL string
|
||||||
|
policyPath []string
|
||||||
|
desiredBranch, actualBranch string
|
||||||
|
desiredPathToYAMLs, actualPathToYAMLs string
|
||||||
|
}
|
||||||
|
testcases := []TestCase{
|
||||||
|
{
|
||||||
|
gitBranch: "main",
|
||||||
|
repoURL: "https://github.com/kyverno/policies",
|
||||||
|
policyPath: []string{"https://github.com/kyverno/policies/openshift/team-validate-ns-name/"},
|
||||||
|
desiredBranch: "main",
|
||||||
|
desiredPathToYAMLs: "/openshift/team-validate-ns-name/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gitBranch: "",
|
||||||
|
repoURL: "https://github.com/kyverno/policies",
|
||||||
|
policyPath: []string{"https://github.com/kyverno/policies/"},
|
||||||
|
desiredBranch: "main",
|
||||||
|
desiredPathToYAMLs: "/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gitBranch: "",
|
||||||
|
repoURL: "https://github.com/kyverno/policies",
|
||||||
|
policyPath: []string{"https://github.com/kyverno/policies"},
|
||||||
|
desiredBranch: "main",
|
||||||
|
desiredPathToYAMLs: "/",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testcases {
|
||||||
|
tc.actualBranch, tc.actualPathToYAMLs = GetGitBranchOrPolicyPaths(tc.gitBranch, tc.repoURL, tc.policyPath)
|
||||||
|
if tc.actualBranch != tc.desiredBranch || tc.actualPathToYAMLs != tc.desiredPathToYAMLs {
|
||||||
|
t.Errorf("Want %q got %q OR Want %q got %q", tc.desiredBranch, tc.actualBranch, tc.desiredPathToYAMLs, tc.actualPathToYAMLs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue