package apply import ( "context" "fmt" "io" "net/url" "os" "path/filepath" "strings" "time" "github.com/go-git/go-billy/v5/memfs" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" kyvernov2 "github.com/kyverno/kyverno/api/kyverno/v2" policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/command" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/deprecations" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/exception" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/log" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/output/color" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/policy" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/processor" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/source" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/store" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/userinfo" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/common" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/variables" "github.com/kyverno/kyverno/pkg/autogen" "github.com/kyverno/kyverno/pkg/cel/engine" celpolicy "github.com/kyverno/kyverno/pkg/cel/policy" "github.com/kyverno/kyverno/pkg/clients/dclient" "github.com/kyverno/kyverno/pkg/config" engineapi "github.com/kyverno/kyverno/pkg/engine/api" "github.com/kyverno/kyverno/pkg/imagedataloader" gitutils "github.com/kyverno/kyverno/pkg/utils/git" policyvalidation "github.com/kyverno/kyverno/pkg/validation/policy" "github.com/spf13/cobra" admissionv1 "k8s.io/api/admission/v1" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" ) type SkippedInvalidPolicies struct { skipped []string invalid []string } type ApplyCommandConfig struct { KubeConfig string Context string Namespace string MutateLogPath string Variables []string ValuesFile string UserInfoPath string Cluster bool PolicyReport bool OutputFormat string Stdin bool RegistryAccess bool AuditWarn bool ResourcePaths []string PolicyPaths []string TargetResourcePaths []string GitBranch string warnExitCode int warnNoPassed bool Exception []string ContinueOnFail bool inlineExceptions bool GenerateExceptions bool GeneratedExceptionTTL time.Duration } func Command() *cobra.Command { var removeColor, detailedResults, table bool applyCommandConfig := &ApplyCommandConfig{} cmd := &cobra.Command{ Use: "apply", Short: command.FormatDescription(true, websiteUrl, false, description...), Long: command.FormatDescription(false, websiteUrl, false, description...), Example: command.FormatExamples(examples...), SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) (err error) { out := cmd.OutOrStdout() color.Init(removeColor) applyCommandConfig.PolicyPaths = args rc, _, skipInvalidPolicies, responses, err := applyCommandConfig.applyCommandHelper(out) if err != nil { return err } cmd.SilenceErrors = true printSkippedAndInvalidPolicies(out, skipInvalidPolicies) if applyCommandConfig.PolicyReport { printReports(out, responses, applyCommandConfig.AuditWarn, applyCommandConfig.OutputFormat) } else if applyCommandConfig.GenerateExceptions { printExceptions(out, responses, applyCommandConfig.AuditWarn, applyCommandConfig.OutputFormat, applyCommandConfig.GeneratedExceptionTTL) } else if table { printTable(out, detailedResults, applyCommandConfig.AuditWarn, responses...) } else { for _, response := range responses { var failedRules []engineapi.RuleResponse resPath := fmt.Sprintf("%s/%s/%s", response.Resource.GetNamespace(), response.Resource.GetKind(), response.Resource.GetName()) for _, rule := range response.PolicyResponse.Rules { if rule.Status() == engineapi.RuleStatusFail { failedRules = append(failedRules, rule) } if rule.RuleType() == engineapi.Mutation { if rule.Status() == engineapi.RuleStatusSkip { fmt.Fprintln(out, "\nskipped mutate policy", response.Policy().GetName(), "->", "resource", resPath) } else if rule.Status() == engineapi.RuleStatusError { fmt.Fprintln(out, "\nerror while applying mutate policy", response.Policy().GetName(), "->", "resource", resPath, "\nerror: ", rule.Message()) } } } if len(failedRules) > 0 { auditWarn := false if applyCommandConfig.AuditWarn && response.GetValidationFailureAction().Audit() { auditWarn = true } if auditWarn { fmt.Fprintln(out, "policy", response.Policy().GetName(), "->", "resource", resPath, "failed as audit warning:") } else { fmt.Fprintln(out, "policy", response.Policy().GetName(), "->", "resource", resPath, "failed:") } for i, rule := range failedRules { fmt.Fprintln(out, i+1, "-", rule.Name(), rule.Message()) } fmt.Fprintln(out, "") } } printViolations(out, rc) } return exit(out, rc, applyCommandConfig.warnExitCode, applyCommandConfig.warnNoPassed) }, } cmd.Flags().StringSliceVarP(&applyCommandConfig.ResourcePaths, "resource", "r", []string{}, "Path to resource files") cmd.Flags().StringSliceVarP(&applyCommandConfig.ResourcePaths, "resources", "", []string{}, "Path to resource files") cmd.Flags().StringSliceVarP(&applyCommandConfig.TargetResourcePaths, "target-resource", "", []string{}, "Path to individual files containing target resources files for policies that have mutate existing") cmd.Flags().StringSliceVarP(&applyCommandConfig.TargetResourcePaths, "target-resources", "", []string{}, "Path to a directory containing target resources files for policies that have mutate existing") cmd.Flags().BoolVarP(&applyCommandConfig.Cluster, "cluster", "c", false, "Checks if policies should be applied to cluster in the current context") cmd.Flags().StringVarP(&applyCommandConfig.MutateLogPath, "output", "o", "", "Prints the mutated/generated resources in provided file/directory") // currently `set` flag supports variable for single policy applied on single resource cmd.Flags().StringVarP(&applyCommandConfig.UserInfoPath, "userinfo", "u", "", "Admission Info including Roles, Cluster Roles and Subjects") cmd.Flags().StringSliceVarP(&applyCommandConfig.Variables, "set", "s", nil, "Variables that are required") cmd.Flags().StringVarP(&applyCommandConfig.ValuesFile, "values-file", "f", "", "File containing values for policy variables") cmd.Flags().BoolVarP(&applyCommandConfig.PolicyReport, "policy-report", "p", false, "Generates policy report when passed (default policyviolation)") cmd.Flags().StringVarP(&applyCommandConfig.OutputFormat, "output-format", "", "yaml", "Specifies the policy report format (json or yaml). Default: yaml.") cmd.Flags().StringVarP(&applyCommandConfig.Namespace, "namespace", "n", "", "Optional Policy parameter passed with cluster flag") cmd.Flags().BoolVarP(&applyCommandConfig.Stdin, "stdin", "i", false, "Optional mutate policy parameter to pipe directly through to kubectl") cmd.Flags().BoolVar(&applyCommandConfig.RegistryAccess, "registry", false, "If set to true, access the image registry using local docker credentials to populate external data") cmd.Flags().StringVar(&applyCommandConfig.KubeConfig, "kubeconfig", "", "path to kubeconfig file with authorization and master location information") cmd.Flags().StringVar(&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().BoolVar(&applyCommandConfig.AuditWarn, "audit-warn", false, "If set to true, will flag audit policies as warnings instead of failures") cmd.Flags().IntVar(&applyCommandConfig.warnExitCode, "warn-exit-code", 0, "Set the exit code for warnings; if failures or errors are found, will exit 1") cmd.Flags().BoolVar(&applyCommandConfig.warnNoPassed, "warn-no-pass", false, "Specify if warning exit code should be raised if no objects satisfied a policy; can be used together with --warn-exit-code flag") cmd.Flags().BoolVar(&removeColor, "remove-color", false, "Remove any color from output") cmd.Flags().BoolVar(&detailedResults, "detailed-results", false, "If set to true, display detailed results") cmd.Flags().BoolVarP(&table, "table", "t", false, "Show results in table format") cmd.Flags().StringSliceVarP(&applyCommandConfig.Exception, "exception", "e", nil, "Policy exception to be considered when evaluating policies against resources") cmd.Flags().StringSliceVarP(&applyCommandConfig.Exception, "exceptions", "", nil, "Policy exception to be considered when evaluating policies against resources") cmd.Flags().BoolVar(&applyCommandConfig.ContinueOnFail, "continue-on-fail", false, "If set to true, will continue to apply policies on the next resource upon failure to apply to the current resource instead of exiting out") cmd.Flags().BoolVarP(&applyCommandConfig.inlineExceptions, "exceptions-with-resources", "", false, "Evaluate policy exceptions from the resources path") cmd.Flags().BoolVarP(&applyCommandConfig.GenerateExceptions, "generate-exceptions", "", false, "Generate policy exceptions for each violation") cmd.Flags().DurationVarP(&applyCommandConfig.GeneratedExceptionTTL, "generated-exception-ttl", "", time.Hour*24*30, "Default TTL for generated exceptions") return cmd } func (c *ApplyCommandConfig) applyCommandHelper(out io.Writer) (*processor.ResultCounts, []*unstructured.Unstructured, SkippedInvalidPolicies, []engineapi.EngineResponse, error) { var skippedInvalidPolicies SkippedInvalidPolicies err := c.checkArguments() if err != nil { return nil, nil, skippedInvalidPolicies, nil, err } mutateLogPathIsDir, err := c.getMutateLogPathIsDir() if err != nil { return nil, nil, skippedInvalidPolicies, nil, err } if err := c.cleanPreviousContent(mutateLogPathIsDir); err != nil { return nil, nil, skippedInvalidPolicies, nil, err } var userInfo *kyvernov2.RequestInfo if c.UserInfoPath != "" { info, err := userinfo.Load(nil, c.UserInfoPath, "") if err != nil { return nil, nil, skippedInvalidPolicies, nil, fmt.Errorf("failed to load request info (%w)", err) } deprecations.CheckUserInfo(out, c.UserInfoPath, info) userInfo = &info.RequestInfo } variables, err := variables.New(out, nil, "", c.ValuesFile, nil, c.Variables...) if err != nil { return nil, nil, skippedInvalidPolicies, nil, fmt.Errorf("failed to decode yaml (%w)", err) } var store store.Store policies, vaps, vapBindings, vps, err := c.loadPolicies() if err != nil { return nil, nil, skippedInvalidPolicies, nil, err } var targetResources []*unstructured.Unstructured if len(c.TargetResourcePaths) > 0 { targetResources, err = c.loadResources(out, c.TargetResourcePaths, policies, vaps, nil) if err != nil { return nil, nil, skippedInvalidPolicies, nil, err } } dClient, err := c.initStoreAndClusterClient(&store, targetResources...) if err != nil { return nil, nil, skippedInvalidPolicies, nil, err } resources, err := c.loadResources(out, c.ResourcePaths, policies, vaps, dClient) if err != nil { return nil, nil, skippedInvalidPolicies, nil, err } var exceptions []*kyvernov2.PolicyException var celexceptions []*policiesv1alpha1.CELPolicyException if c.inlineExceptions { exceptions = exception.SelectFrom(resources) } else { results, err := exception.Load(c.Exception...) if err != nil { return nil, nil, skippedInvalidPolicies, nil, fmt.Errorf("Error: failed to load exceptions (%s)", err) } if results != nil { exceptions = results.Exceptions celexceptions = results.CELExceptions } } if !c.Stdin && !c.PolicyReport && !c.GenerateExceptions { var policyRulesCount int for _, policy := range policies { policyRulesCount += len(autogen.Default.ComputeRules(policy, "")) } // account for vaps policyRulesCount += len(vaps) // account for vps policyRulesCount += len(vps) exceptionsCount := len(exceptions) exceptionsCount += len(celexceptions) if exceptionsCount > 0 { fmt.Fprintf(out, "\nApplying %d policy rule(s) to %d resource(s) with %d exception(s)...\n", policyRulesCount, len(resources), exceptionsCount) } else { fmt.Fprintf(out, "\nApplying %d policy rule(s) to %d resource(s)...\n", policyRulesCount, len(resources)) } } rc, resources1, responses1, err := c.applyPolicies( out, &store, variables, policies, resources, exceptions, &skippedInvalidPolicies, dClient, userInfo, mutateLogPathIsDir, ) if err != nil { return rc, resources1, skippedInvalidPolicies, responses1, err } responses2, err := c.applyValidatingAdmissionPolicies(vaps, vapBindings, resources1, variables.NamespaceSelectors(), rc, dClient) if err != nil { return rc, resources1, skippedInvalidPolicies, responses1, err } responses3, err := c.applyValidatingPolicies(vps, celexceptions, resources1, variables.Namespace, rc, dClient) if err != nil { return rc, resources1, skippedInvalidPolicies, responses1, err } var responses []engineapi.EngineResponse responses = append(responses, responses1...) responses = append(responses, responses2...) responses = append(responses, responses3...) return rc, resources1, skippedInvalidPolicies, responses, nil } func (c *ApplyCommandConfig) getMutateLogPathIsDir() (bool, error) { mutateLogPathIsDir, err := checkMutateLogPath(c.MutateLogPath) if err != nil { return false, fmt.Errorf("failed to create file/folder (%w)", err) } return mutateLogPathIsDir, nil } func (c *ApplyCommandConfig) applyValidatingAdmissionPolicies( vaps []admissionregistrationv1.ValidatingAdmissionPolicy, vapBindings []admissionregistrationv1.ValidatingAdmissionPolicyBinding, resources []*unstructured.Unstructured, namespaceSelectorMap map[string]map[string]string, rc *processor.ResultCounts, dClient dclient.Interface, ) ([]engineapi.EngineResponse, error) { var responses []engineapi.EngineResponse for _, resource := range resources { processor := processor.ValidatingAdmissionPolicyProcessor{ Policies: vaps, Bindings: vapBindings, Resource: resource, NamespaceSelectorMap: namespaceSelectorMap, PolicyReport: c.PolicyReport, Rc: rc, Client: dClient, } ers, err := processor.ApplyPolicyOnResource() if err != nil { if c.ContinueOnFail { fmt.Printf("failed to apply policies on resource %s (%v)\n", resource.GetName(), err) continue } return responses, fmt.Errorf("failed to apply policies on resource %s (%w)", resource.GetName(), err) } responses = append(responses, ers...) } return responses, nil } func (c *ApplyCommandConfig) applyValidatingPolicies( vps []policiesv1alpha1.ValidatingPolicy, exceptions []*policiesv1alpha1.CELPolicyException, resources []*unstructured.Unstructured, namespaceProvider func(string) *corev1.Namespace, rc *processor.ResultCounts, dclient dclient.Interface, ) ([]engineapi.EngineResponse, error) { ctx := context.TODO() compiler := celpolicy.NewCompiler() provider, err := engine.NewProvider(compiler, vps, exceptions) if err != nil { return nil, err } eng := engine.NewEngine(provider, namespaceProvider, nil) // TODO: mock when no cluster provided var contextProvider celpolicy.Context if dclient != nil { contextProvider, err = celpolicy.NewContextProvider( dclient.GetKubeClient(), []imagedataloader.Option{imagedataloader.WithLocalCredentials(c.RegistryAccess)}, ) if err != nil { return nil, err } } responses := make([]engineapi.EngineResponse, 0) for _, resource := range resources { request := engine.Request( contextProvider, resource.GroupVersionKind(), // TODO schema.GroupVersionResource{}, // TODO "", resource.GetName(), resource.GetNamespace(), admissionv1.Create, resource, nil, false, nil, ) response, err := eng.Handle(ctx, request) if err != nil { if c.ContinueOnFail { fmt.Printf("failed to apply validating policies on resource %s (%v)\n", resource.GetName(), err) continue } return responses, fmt.Errorf("failed to apply validating policies on resource %s (%w)", resource.GetName(), err) } // transform response into legacy engine responses for _, r := range response.Policies { engineResponse := engineapi.EngineResponse{ Resource: *response.Resource, PolicyResponse: engineapi.PolicyResponse{ Rules: r.Rules, }, } engineResponse = engineResponse.WithPolicy(engineapi.NewValidatingPolicy(&r.Policy)) rc.AddValidatingPolicyResponse(engineResponse) responses = append(responses, engineResponse) } } return responses, nil } func (c *ApplyCommandConfig) applyPolicies( out io.Writer, store *store.Store, vars *variables.Variables, policies []kyvernov1.PolicyInterface, resources []*unstructured.Unstructured, exceptions []*kyvernov2.PolicyException, skipInvalidPolicies *SkippedInvalidPolicies, dClient dclient.Interface, userInfo *kyvernov2.RequestInfo, mutateLogPathIsDir bool, ) (*processor.ResultCounts, []*unstructured.Unstructured, []engineapi.EngineResponse, error) { if vars != nil { vars.SetInStore(store) } var rc processor.ResultCounts // validate policies validPolicies := make([]kyvernov1.PolicyInterface, 0, len(policies)) for _, pol := range policies { // TODO we should return this info to the caller sa := config.KyvernoUserName(config.KyvernoServiceAccountName()) _, err := policyvalidation.Validate(pol, nil, nil, true, sa, sa) if err != nil { log.Log.Error(err, "policy validation error") rc.IncrementError(1) if strings.HasPrefix(err.Error(), "variable 'element.name'") { skipInvalidPolicies.invalid = append(skipInvalidPolicies.invalid, pol.GetName()) } else { skipInvalidPolicies.skipped = append(skipInvalidPolicies.skipped, pol.GetName()) } continue } validPolicies = append(validPolicies, pol) } var responses []engineapi.EngineResponse for _, resource := range resources { processor := processor.PolicyProcessor{ Store: store, Policies: validPolicies, Resource: *resource, PolicyExceptions: exceptions, MutateLogPath: c.MutateLogPath, MutateLogPathIsDir: mutateLogPathIsDir, Variables: vars, UserInfo: userInfo, PolicyReport: c.PolicyReport, NamespaceSelectorMap: vars.NamespaceSelectors(), Stdin: c.Stdin, Rc: &rc, PrintPatchResource: true, Cluster: c.Cluster, Client: dClient, AuditWarn: c.AuditWarn, Subresources: vars.Subresources(), Out: out, } ers, err := processor.ApplyPoliciesOnResource() if err != nil { if c.ContinueOnFail { log.Log.V(2).Info(fmt.Sprintf("failed to apply policies on resource %s (%s)\n", resource.GetName(), err.Error())) continue } return &rc, resources, responses, fmt.Errorf("failed to apply policies on resource %s (%w)", resource.GetName(), err) } responses = append(responses, ers...) } for _, policy := range validPolicies { if policy.GetNamespace() == "" && policy.GetKind() == "Policy" { log.Log.V(3).Info(fmt.Sprintf("Policy %s has no namespace detected. Ensure that namespaced policies are correctly loaded.", policy.GetNamespace())) } } return &rc, resources, responses, nil } func (c *ApplyCommandConfig) loadResources(out io.Writer, paths []string, policies []kyvernov1.PolicyInterface, vap []admissionregistrationv1.ValidatingAdmissionPolicy, dClient dclient.Interface) ([]*unstructured.Unstructured, error) { resources, err := common.GetResourceAccordingToResourcePath(out, nil, paths, c.Cluster, policies, vap, dClient, c.Namespace, c.PolicyReport, "") if err != nil { return resources, fmt.Errorf("failed to load resources (%w)", err) } return resources, nil } func (c *ApplyCommandConfig) loadPolicies() ( []kyvernov1.PolicyInterface, []admissionregistrationv1.ValidatingAdmissionPolicy, []admissionregistrationv1.ValidatingAdmissionPolicyBinding, []policiesv1alpha1.ValidatingPolicy, error, ) { // load policies var policies []kyvernov1.PolicyInterface var vaps []admissionregistrationv1.ValidatingAdmissionPolicy var vapBindings []admissionregistrationv1.ValidatingAdmissionPolicyBinding var vps []policiesv1alpha1.ValidatingPolicy for _, path := range c.PolicyPaths { isGit := source.IsGit(path) if isGit { gitSourceURL, err := url.Parse(path) if err != nil { return nil, nil, nil, nil, fmt.Errorf("failed to load policies (%w)", err) } pathElems := strings.Split(gitSourceURL.Path[1:], "/") if len(pathElems) <= 1 { err := fmt.Errorf("invalid URL path %s - expected https:///:owner/:repository/:branch (without --git-branch flag) OR https:///:owner/:repository/:directory (with --git-branch flag)", gitSourceURL.Path) return nil, nil, nil, nil, fmt.Errorf("failed to parse URL (%w)", err) } gitSourceURL.Path = strings.Join([]string{pathElems[0], pathElems[1]}, "/") repoURL := gitSourceURL.String() var gitPathToYamls string c.GitBranch, gitPathToYamls = common.GetGitBranchOrPolicyPaths(c.GitBranch, repoURL, path) fs := memfs.New() if _, err := gitutils.Clone(repoURL, fs, c.GitBranch); err != nil { log.Log.V(3).Info(fmt.Sprintf("failed to clone repository %v as it is not valid", repoURL), "error", err) return nil, nil, nil, nil, fmt.Errorf("failed to clone repository (%w)", err) } policyYamls, err := gitutils.ListYamls(fs, gitPathToYamls) if err != nil { return nil, nil, nil, nil, fmt.Errorf("failed to list YAMLs in repository (%w)", err) } for _, policyYaml := range policyYamls { loaderResults, err := policy.Load(fs, "", policyYaml) if loaderResults != nil && loaderResults.NonFatalErrors != nil { for _, err := range loaderResults.NonFatalErrors { log.Log.Error(err.Error, "Non-fatal parsing error for single document") } } if err != nil { continue } policies = append(policies, loaderResults.Policies...) vaps = append(vaps, loaderResults.VAPs...) vapBindings = append(vapBindings, loaderResults.VAPBindings...) vps = append(vps, loaderResults.ValidatingPolicies...) } } else { loaderResults, err := policy.Load(nil, "", path) if loaderResults != nil && loaderResults.NonFatalErrors != nil { for _, err := range loaderResults.NonFatalErrors { log.Log.Error(err.Error, "Non-fatal parsing error for single document") } } if err != nil { log.Log.V(3).Info("skipping invalid YAML file", "path", path, "error", err) } else { policies = append(policies, loaderResults.Policies...) vaps = append(vaps, loaderResults.VAPs...) vapBindings = append(vapBindings, loaderResults.VAPBindings...) vps = append(vps, loaderResults.ValidatingPolicies...) } } for _, policy := range policies { if policy.GetNamespace() == "" && policy.GetKind() == "Policy" { log.Log.V(3).Info(fmt.Sprintf("Namespace is empty for a namespaced Policy %s. This might cause incorrect report generation.", policy.GetNamespace())) } } } return policies, vaps, vapBindings, vps, nil } func (c *ApplyCommandConfig) initStoreAndClusterClient(store *store.Store, targetResources ...*unstructured.Unstructured) (dclient.Interface, error) { store.SetLocal(true) store.SetRegistryAccess(c.RegistryAccess) if c.Cluster { store.AllowApiCall(true) } var err error var dClient dclient.Interface if c.Cluster { restConfig, err := config.CreateClientConfigWithContext(c.KubeConfig, c.Context) if err != nil { return nil, err } kubeClient, err := kubernetes.NewForConfig(restConfig) if err != nil { return nil, err } dynamicClient, err := dynamic.NewForConfig(restConfig) if err != nil { return nil, err } dClient, err = dclient.NewClient(context.Background(), dynamicClient, kubeClient, 15*time.Minute) if err != nil { return nil, err } } if len(targetResources) > 0 && !c.Cluster { var targets []runtime.Object for _, t := range targetResources { targets = append(targets, t) } dClient, err = dclient.NewFakeClient(runtime.NewScheme(), map[schema.GroupVersionResource]string{}, targets...) dClient.SetDiscovery(dclient.NewFakeDiscoveryClient(nil)) if err != nil { return nil, err } } return dClient, err } func (c *ApplyCommandConfig) cleanPreviousContent(mutateLogPathIsDir bool) error { // empty the previous contents of the file just in case if the file already existed before with some content(so as to perform overwrites) // the truncation of files for the case when mutateLogPath is dir, is handled under pkg/kyverno/apply/common.go if !mutateLogPathIsDir && c.MutateLogPath != "" { c.MutateLogPath = filepath.Clean(c.MutateLogPath) // Necessary for us to include the file via variable as it is part of the CLI. _, err := os.OpenFile(c.MutateLogPath, os.O_TRUNC|os.O_WRONLY, 0o600) // #nosec G304 if err != nil { return fmt.Errorf("failed to truncate the existing file at %s (%w)", c.MutateLogPath, err) } } return nil } func (c *ApplyCommandConfig) checkArguments() error { if c.ValuesFile != "" && c.Variables != nil { return fmt.Errorf("pass the values either using set flag or values_file flag") } if len(c.PolicyPaths) == 0 { return fmt.Errorf("require policy") } if (len(c.PolicyPaths) > 0 && c.PolicyPaths[0] == "-") && len(c.ResourcePaths) > 0 && c.ResourcePaths[0] == "-" { return fmt.Errorf("a stdin pipe can be used for either policies or resources, not both") } if len(c.ResourcePaths) == 0 && !c.Cluster { return fmt.Errorf("resource file(s) or cluster required") } return nil } type WarnExitCodeError struct { ExitCode int } func (w WarnExitCodeError) Error() string { return fmt.Sprintf("exit as warnExitCode is %d", w.ExitCode) } func exit(out io.Writer, rc *processor.ResultCounts, warnExitCode int, warnNoPassed bool) error { if rc.Fail > 0 { return fmt.Errorf("exit as there are policy violations") } else if rc.Error > 0 { return fmt.Errorf("exit as there are policy errors") } else if rc.Warn > 0 && warnExitCode != 0 { fmt.Printf("exit as warnExitCode is %d", warnExitCode) return WarnExitCodeError{ ExitCode: warnExitCode, } } else if rc.Pass == 0 && warnNoPassed { fmt.Println(out, "exit as no objects satisfied policy") return WarnExitCodeError{ ExitCode: warnExitCode, } } return nil }