1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00

Merge pull request #128 from nirmata/65_CLI_unmarshall_resource

Decode resource yaml into kubernetes struct
This commit is contained in:
Shivkumar Dudhani 2019-06-03 16:21:08 -07:00 committed by GitHub
commit 21b3c3cc21
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 186 additions and 112 deletions

View file

@ -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

View file

@ -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"

View file

@ -4,58 +4,48 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"github.com/golang/glog"
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) {
defer glog.Flush()
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 {
@ -71,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)
@ -80,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 {
@ -118,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 {
@ -137,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
}

98
pkg/kyverno/apply/util.go Normal file
View file

@ -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
}