mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-15 17:51:20 +00:00
Merge pull request #128 from nirmata/65_CLI_unmarshall_resource
Decode resource yaml into kubernetes struct
This commit is contained in:
commit
21b3c3cc21
4 changed files with 186 additions and 112 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 {
|
||||
dynamicClient, err := dynamic.NewForConfig(clientConfig)
|
||||
if err != nil {
|
||||
fmt.Printf("prevent panic by handling failure accessing a path %q: %v\n", dir, err)
|
||||
return err
|
||||
return obj, err
|
||||
}
|
||||
/* if len(strings.Split(path, "/")) == 4 {
|
||||
fmt.Println(path)
|
||||
} */
|
||||
res = append(res, path)
|
||||
return nil
|
||||
})
|
||||
|
||||
kclient, err := kubernetes.NewForConfig(clientConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error walking the path %q: %v", dir, err)
|
||||
return obj, err
|
||||
}
|
||||
|
||||
return res[1:], nil
|
||||
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
98
pkg/kyverno/apply/util.go
Normal 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
|
||||
}
|
Loading…
Reference in a new issue