From e54c0213533f66b53909ae87fce75bff89ef037d Mon Sep 17 00:00:00 2001 From: shuting Date: Fri, 31 May 2019 16:57:39 -0700 Subject: [PATCH 1/2] update cli example --- examples/cli/policy_deployment.yaml | 4 ++-- examples/cli/resources/ghost.yaml | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/examples/cli/policy_deployment.yaml b/examples/cli/policy_deployment.yaml index f307427428..5957912c55 100644 --- a/examples/cli/policy_deployment.yaml +++ b/examples/cli/policy_deployment.yaml @@ -27,12 +27,12 @@ spec : matchLabels : cli: test validate: - message: "The imagePullPolicy must be Always when :latest is used as a tag" + message: "The imagePullPolicy must be Always when using image nginx" pattern: spec: template: spec: containers: - - (name): "*:latest" + - (image): "nginx*" imagePullPolicy: Always diff --git a/examples/cli/resources/ghost.yaml b/examples/cli/resources/ghost.yaml index 8459622f2d..47ef19feb7 100644 --- a/examples/cli/resources/ghost.yaml +++ b/examples/cli/resources/ghost.yaml @@ -1,5 +1,5 @@ kind: "Deployment" -apiVersion: "extensions/v1beta1" +apiVersion: "apps/v1" metadata: name: "ghost" labels: @@ -28,8 +28,7 @@ spec: spec: containers: - name: "ghost" - image: "ghost:2.9.1-alpine" - imagePullPolicy: Always + image: "nginx:latest" ports: - containerPort: 8080 protocol: "TCP" From 90a918941247c6466f9b5de5d2c40e039f6569d1 Mon Sep 17 00:00:00 2001 From: shuting Date: Fri, 31 May 2019 16:58:36 -0700 Subject: [PATCH 2/2] Decode resource yaml into actual kubernetes object if kubeconfig exists. --- pkg/kyverno/apply/apply.go | 190 ++++++++++++++++--------------------- pkg/kyverno/apply/util.go | 98 +++++++++++++++++++ 2 files changed, 182 insertions(+), 106 deletions(-) create mode 100644 pkg/kyverno/apply/util.go diff --git a/pkg/kyverno/apply/apply.go b/pkg/kyverno/apply/apply.go index 785cfc15bf..575b4d47dc 100644 --- a/pkg/kyverno/apply/apply.go +++ b/pkg/kyverno/apply/apply.go @@ -4,57 +4,48 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "log" "os" - "path/filepath" - "strings" kubepolicy "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1" "github.com/nirmata/kyverno/pkg/engine" "github.com/spf13/cobra" - yamlv2 "gopkg.in/yaml.v2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" yaml "k8s.io/apimachinery/pkg/util/yaml" + memory "k8s.io/client-go/discovery/cached/memory" + dynamic "k8s.io/client-go/dynamic" + kubernetes "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/restmapper" ) const applyExample = ` # Apply a policy to the resource. kyverno apply @policy.yaml @resource.yaml - kyverno apply @policy.yaml @resourceDir/` + kyverno apply @policy.yaml @resourceDir/ + kyverno apply @policy.yaml @resource.yaml --kubeconfig=$PATH_TO_KUBECONFIG_FILE` // NewCmdApply returns the apply command for kyverno func NewCmdApply(in io.Reader, out, errout io.Writer) *cobra.Command { + var kubeconfig string cmd := &cobra.Command{ Use: "apply", Short: "Apply policy on the resource(s)", Example: applyExample, Run: func(cmd *cobra.Command, args []string) { - var output string - policy, resources := complete(args) - - for _, resource := range resources { - patchedDocument, err := applyPolicy(policy, resource.rawResource, resource.gvk) - if err != nil { - fmt.Println(err) - os.Exit(1) - } - - out, err := prettyPrint(patchedDocument) - if err != nil { - fmt.Printf("JSON parse error: %v\n", err) - fmt.Printf("%v\n", string(patchedDocument)) - return - } - - output = output + fmt.Sprintf("---\n%s", string(out)) - } + policy, resources := complete(kubeconfig, args) + output := applyPolicy(policy, resources) fmt.Printf("%v\n", output) }, } + + cmd.Flags().StringVar(&kubeconfig, "kubeconfig", "", "path to kubeconfig file") return cmd } -func complete(args []string) (*kubepolicy.Policy, []*resourceInfo) { +func complete(kubeconfig string, args []string) (*kubepolicy.Policy, []*resourceInfo) { policyDir, resourceDir, err := validateDir(args) if err != nil { @@ -70,7 +61,7 @@ func complete(args []string) (*kubepolicy.Policy, []*resourceInfo) { } // extract rawResource - resources, err := extractResource(resourceDir) + resources, err := extractResource(resourceDir, kubeconfig) if err != nil { log.Printf("failed to parse resource: %v", err) os.Exit(1) @@ -79,7 +70,26 @@ func complete(args []string) (*kubepolicy.Policy, []*resourceInfo) { return policy, resources } -func applyPolicy(policy *kubepolicy.Policy, rawResource []byte, gvk *metav1.GroupVersionKind) ([]byte, error) { +func applyPolicy(policy *kubepolicy.Policy, resources []*resourceInfo) (output string) { + for _, resource := range resources { + patchedDocument, err := applyPolicyOnRaw(policy, resource.rawResource, resource.gvk) + if err != nil { + fmt.Printf("Error applying policy on resource %s, err: %v\n", resource.gvk.Kind, err) + continue + } + + out, err := prettyPrint(patchedDocument) + if err != nil { + fmt.Printf("JSON parse error: %v\n", err) + continue + } + + output = output + fmt.Sprintf("---\n%s", string(out)) + } + return +} + +func applyPolicyOnRaw(policy *kubepolicy.Policy, rawResource []byte, gvk *metav1.GroupVersionKind) ([]byte, error) { _, patchedDocument := engine.Mutate(*policy, rawResource, *gvk) if err := engine.Validate(*policy, patchedDocument, *gvk); err != nil { @@ -117,9 +127,10 @@ type resourceInfo struct { gvk *metav1.GroupVersionKind } -func extractResource(fileDir string) ([]*resourceInfo, error) { +func extractResource(fileDir, kubeconfig string) ([]*resourceInfo, error) { var files []string var resources []*resourceInfo + // check if applied on multiple resources isDir, err := isDir(fileDir) if err != nil { @@ -136,102 +147,69 @@ func extractResource(fileDir string) ([]*resourceInfo, error) { } for _, dir := range files { - file, err := loadFile(dir) + data, err := loadFile(dir) if err != nil { - return nil, fmt.Errorf("failed to load file: %v", err) + fmt.Printf("Warning: errpr while loading file: %v\n", err) + continue } - data := make(map[interface{}]interface{}) - - if err = yamlv2.Unmarshal([]byte(file), &data); err != nil { - return nil, fmt.Errorf("failed to parse resource: %v", err) + decode := scheme.Codecs.UniversalDeserializer().Decode + obj, gvk, err := decode([]byte(data), nil, nil) + if err != nil { + fmt.Printf("Warning: error while decoding YAML object, err: %s\n", err) + continue } - apiVersion, ok := data["apiVersion"].(string) - if !ok { - return nil, fmt.Errorf("failed to parse apiversion: %v", err) + actualObj, err := convertToActualObject(kubeconfig, gvk, obj) + if err != nil { + fmt.Printf("Warning: error while converting resource %s to actual k8s object, err: %v\n", gvk.Kind, err) + fmt.Printf("Apply policy on raw resource.\n") } - kind, ok := data["kind"].(string) - if !ok { - return nil, fmt.Errorf("failed to parse kind of resource: %v", err) + raw, err := json.Marshal(actualObj) + if err != nil { + fmt.Printf("Warning: error while marshalling manifest, err: %v\n", err) + continue } - var gvkInfo *metav1.GroupVersionKind - gv := strings.Split(apiVersion, "/") - if len(gv) == 2 { - gvkInfo = &metav1.GroupVersionKind{Group: gv[0], Version: gv[1], Kind: kind} - } else { - gvkInfo = &metav1.GroupVersionKind{Version: gv[0], Kind: kind} - } - - json, err := yaml.ToJSON(file) - - resources = append(resources, &resourceInfo{rawResource: json, gvk: gvkInfo}) + gvkInfo := &metav1.GroupVersionKind{Group: gvk.Group, Version: gvk.Version, Kind: gvk.Kind} + resources = append(resources, &resourceInfo{rawResource: raw, gvk: gvkInfo}) } return resources, err } -func loadFile(fileDir string) ([]byte, error) { - if _, err := os.Stat(fileDir); os.IsNotExist(err) { - return nil, err - } - - return ioutil.ReadFile(fileDir) -} - -func validateDir(args []string) (policyDir, resourceDir string, err error) { - if len(args) != 2 { - return "", "", fmt.Errorf("missing policy and/or resource manifest") - } - - if strings.HasPrefix(args[0], "@") { - policyDir = args[0][1:] - } - - if strings.HasPrefix(args[1], "@") { - resourceDir = args[1][1:] - } - return -} - -func prettyPrint(data []byte) ([]byte, error) { - out := make(map[interface{}]interface{}) - if err := yamlv2.Unmarshal(data, &out); err != nil { - return nil, err - } - - return yamlv2.Marshal(&out) -} - -func isDir(dir string) (bool, error) { - fi, err := os.Stat(dir) +func convertToActualObject(kubeconfig string, gvk *schema.GroupVersionKind, obj runtime.Object) (interface{}, error) { + clientConfig, err := createClientConfig(kubeconfig) if err != nil { - return false, err + return obj, err } - return fi.IsDir(), nil -} - -func ScanDir(dir string) ([]string, error) { - var res []string - - err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if err != nil { - fmt.Printf("prevent panic by handling failure accessing a path %q: %v\n", dir, err) - return err - } - /* if len(strings.Split(path, "/")) == 4 { - fmt.Println(path) - } */ - res = append(res, path) - return nil - }) - + dynamicClient, err := dynamic.NewForConfig(clientConfig) if err != nil { - return nil, fmt.Errorf("error walking the path %q: %v", dir, err) + return obj, err } - return res[1:], nil + kclient, err := kubernetes.NewForConfig(clientConfig) + if err != nil { + return obj, err + } + + asUnstructured := &unstructured.Unstructured{} + if err := scheme.Scheme.Convert(obj, asUnstructured, nil); err != nil { + return obj, err + } + + mapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(kclient.Discovery())) + mapping, err := mapper.RESTMapping(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind}, gvk.Version) + if err != nil { + return obj, err + } + + actualObj, err := dynamicClient.Resource(mapping.Resource).Namespace("default").Create(asUnstructured, metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}}) + if err != nil { + return obj, err + } + + return actualObj, nil } diff --git a/pkg/kyverno/apply/util.go b/pkg/kyverno/apply/util.go new file mode 100644 index 0000000000..e20514aa74 --- /dev/null +++ b/pkg/kyverno/apply/util.go @@ -0,0 +1,98 @@ +package apply + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + "strings" + + yamlv2 "gopkg.in/yaml.v2" + rest "k8s.io/client-go/rest" + clientcmd "k8s.io/client-go/tools/clientcmd" +) + +func createClientConfig(kubeconfig string) (*rest.Config, error) { + if kubeconfig == "" { + defaultKC := defaultKubeconfigPath() + if _, err := os.Stat(defaultKC); err == nil { + kubeconfig = defaultKC + } + } + + return clientcmd.BuildConfigFromFlags("", kubeconfig) +} + +func defaultKubeconfigPath() string { + home, err := os.UserHomeDir() + if err != nil { + log.Printf("Warning: failed to get home dir: %v\n", err) + return "" + } + + return filepath.Join(home, ".kube", "config") +} + +func loadFile(fileDir string) ([]byte, error) { + if _, err := os.Stat(fileDir); os.IsNotExist(err) { + return nil, err + } + + return ioutil.ReadFile(fileDir) +} + +func validateDir(args []string) (policyDir, resourceDir string, err error) { + if len(args) != 2 { + return "", "", fmt.Errorf("missing policy and/or resource manifest") + } + + if strings.HasPrefix(args[0], "@") { + policyDir = args[0][1:] + } + + if strings.HasPrefix(args[1], "@") { + resourceDir = args[1][1:] + } + return +} + +func prettyPrint(data []byte) ([]byte, error) { + out := make(map[interface{}]interface{}) + if err := yamlv2.Unmarshal(data, &out); err != nil { + return nil, err + } + + return yamlv2.Marshal(&out) +} + +func isDir(dir string) (bool, error) { + fi, err := os.Stat(dir) + if err != nil { + return false, err + } + + return fi.IsDir(), nil +} + +func ScanDir(dir string) ([]string, error) { + var res []string + + err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + fmt.Printf("prevent panic by handling failure accessing a path %q: %v\n", dir, err) + return err + } + /* if len(strings.Split(path, "/")) == 4 { + fmt.Println(path) + } */ + res = append(res, path) + return nil + }) + + if err != nil { + return nil, fmt.Errorf("error walking the path %q: %v", dir, err) + } + + return res[1:], nil +}