1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-31 03:45:17 +00:00

Support Mutate from command line

This commit is contained in:
shuting 2019-05-20 13:02:55 -07:00
parent 4b380f758f
commit ffe644f821
8 changed files with 207 additions and 9 deletions

View file

@ -31,3 +31,7 @@ required = ["k8s.io/code-generator/cmd/client-gen"]
[[override]]
name = "github.com/golang/protobuf"
version = "v1.2.1"
[[constraint]]
name = "gopkg.in/yaml.v2"
version = "2.2.2"

1
cmd/build Executable file
View file

@ -0,0 +1 @@
go build -o kyverno

17
cmd/kyverno.go Normal file
View file

@ -0,0 +1,17 @@
package main
import (
"fmt"
"os"
kyverno "github.com/nirmata/kube-policy/pkg/kyverno"
)
func main() {
cmd := kyverno.NewDefaultKyvernoCommand()
if err := cmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
}

View file

@ -10,8 +10,10 @@ import (
// Mutate performs mutation. Overlay first and then mutation patches
// TODO: return events and violations
func Mutate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) []mutation.PatchBytes {
func Mutate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) ([]mutation.PatchBytes, []byte) {
var policyPatches []mutation.PatchBytes
var processedPatches []mutation.PatchBytes
patchedDocument := rawResource
for i, rule := range policy.Spec.Rules {
@ -55,7 +57,7 @@ func Mutate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersio
// Process Patches
if rule.Mutation.Patches != nil {
processedPatches, err := mutation.ProcessPatches(rule.Mutation.Patches, rawResource)
processedPatches, patchedDocument, err = mutation.ProcessPatches(rule.Mutation.Patches, patchedDocument)
if err != nil {
log.Printf("Patches application failed: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err)
} else {
@ -64,5 +66,5 @@ func Mutate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersio
}
}
return policyPatches
return policyPatches, patchedDocument
}

View file

@ -13,19 +13,20 @@ type PatchBytes []byte
// ProcessPatches Returns array from separate patches that can be applied to the document
// Returns error ONLY in case when creation of resource should be denied.
func ProcessPatches(patches []kubepolicy.Patch, resource []byte) ([]PatchBytes, error) {
func ProcessPatches(patches []kubepolicy.Patch, resource []byte) ([]PatchBytes, []byte, error) {
if len(resource) == 0 {
return nil, errors.New("Source document for patching is empty")
return nil, nil, errors.New("Source document for patching is empty")
}
var appliedPatches []PatchBytes
patchedDocument := resource
for i, patch := range patches {
patchRaw, err := json.Marshal(patch)
if err != nil {
return nil, err
return nil, nil, err
}
_, err = applyPatch(resource, patchRaw)
patchedDocument, err = applyPatch(patchedDocument, patchRaw)
if err != nil {
// TODO: continue on error if one of the patches fails, will add the failure event in such case
log.Printf("Patch failed: patch number = %d, patch Operation = %s, err: %v", i, patch.Operation, err)
@ -34,7 +35,7 @@ func ProcessPatches(patches []kubepolicy.Patch, resource []byte) ([]PatchBytes,
appliedPatches = append(appliedPatches, patchRaw)
}
return appliedPatches, nil
return appliedPatches, patchedDocument, nil
}
// JoinPatches joins array of serialized JSON patches to the single JSONPatch array

148
pkg/kyverno/apply/apply.go Normal file
View file

@ -0,0 +1,148 @@
package apply
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"strings"
kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
"github.com/nirmata/kube-policy/pkg/engine"
"github.com/spf13/cobra"
yamlv2 "gopkg.in/yaml.v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
yaml "k8s.io/apimachinery/pkg/util/yaml"
)
const applyExample = ` # Apply a policy to the resource.
kyverno apply @policy.yaml @resource.yaml`
// NewCmdApply returns the apply command for kyverno
func NewCmdApply(in io.Reader, out, errout io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "apply",
Short: "Apply policy on the resource",
Example: applyExample,
Run: func(cmd *cobra.Command, args []string) {
// TODO: add pre-checks for policy and resource manifest
// order for policy and resource in args could be disordered
if len(args) != 2 {
log.Printf("Missing policy and/or resource manifest.")
return
}
// extract policy
policyDir := validateDir(args[0])
policy, err := extractPolicy(policyDir)
if err != nil {
log.Printf("failed to extract policy: %v", err)
os.Exit(1)
}
// fmt.Printf("policy name=%s, rule name=%s, %s/%s\n", policy.ObjectMeta.Name, policy.Spec.Rules[0].Name,
// policy.Spec.Rules[0].ResourceDescription.Kind, *policy.Spec.Rules[0].ResourceDescription.Name)
// extract rawResource
resourceDir := validateDir(args[1])
rawResource, gvk, err := extractResource(resourceDir)
if err != nil {
log.Printf("failed to load resource: %v", err)
os.Exit(1)
}
_, patchedDocument := engine.Mutate(*policy, rawResource, *gvk)
out, err := prettyPrint(patchedDocument)
if err != nil {
fmt.Printf("JSON parse error: %v\n", err)
fmt.Printf("%v\n", string(patchedDocument))
return
}
fmt.Printf("%v\n", string(out))
},
}
return cmd
}
func extractPolicy(fileDir string) (*kubepolicy.Policy, error) {
policy := &kubepolicy.Policy{}
file, err := loadFile(fileDir)
if err != nil {
return nil, fmt.Errorf("failed to load file: %v", err)
}
policyBytes, err := yaml.ToJSON(file)
if err != nil {
return nil, err
}
if err := json.Unmarshal(policyBytes, policy); err != nil {
return nil, fmt.Errorf("failed to decode policy %s, err: %v", policy.Name, err)
}
return policy, nil
}
func extractResource(fileDir string) ([]byte, *metav1.GroupVersionKind, error) {
file, err := loadFile(fileDir)
if err != nil {
return nil, nil, fmt.Errorf("failed to load file: %v", err)
}
data := make(map[interface{}]interface{})
if err = yamlv2.Unmarshal([]byte(file), &data); err != nil {
return nil, nil, fmt.Errorf("failed to parse resource: %v", err)
}
apiVersion, ok := data["apiVersion"].(string)
if !ok {
return nil, nil, fmt.Errorf("failed to parse apiversion: %v", err)
}
kind, ok := data["kind"].(string)
if !ok {
return nil, nil, fmt.Errorf("failed to parse kind of resource: %v", err)
}
var gvk *metav1.GroupVersionKind
gv := strings.Split(apiVersion, "/")
if len(gv) == 2 {
gvk = &metav1.GroupVersionKind{Group: gv[0], Version: gv[1], Kind: kind}
} else {
gvk = &metav1.GroupVersionKind{Version: gv[0], Kind: kind}
}
json, err := yaml.ToJSON(file)
return json, gvk, err
}
func loadFile(fileDir string) ([]byte, error) {
if _, err := os.Stat(fileDir); os.IsNotExist(err) {
return nil, err
}
return ioutil.ReadFile(fileDir)
}
func validateDir(dir string) string {
if strings.HasPrefix(dir, "@") {
return dir[1:]
}
return dir
}
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)
}

25
pkg/kyverno/cmd.go Normal file
View file

@ -0,0 +1,25 @@
package cmd
import (
"io"
"os"
"github.com/nirmata/kube-policy/pkg/kyverno/apply"
"github.com/spf13/cobra"
)
// NewDefaultKyvernoCommand ...
func NewDefaultKyvernoCommand() *cobra.Command {
return NewKyvernoCommand(os.Stdin, os.Stdout, os.Stderr)
}
// NewKyvernoCommand returns the new kynerno command
func NewKyvernoCommand(in io.Reader, out, errout io.Writer) *cobra.Command {
cmds := &cobra.Command{
Use: "kyverno",
Short: "kyverno manages native policies of Kubernetes",
}
cmds.AddCommand(apply.NewCmdApply(in, out, errout))
return cmds
}

View file

@ -147,7 +147,7 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) *v1be
for _, policy := range policies {
ws.logger.Printf("Applying policy %s with %d rules\n", policy.ObjectMeta.Name, len(policy.Spec.Rules))
policyPatches := engine.Mutate(*policy, request.Object.Raw, request.Kind)
policyPatches, _ := engine.Mutate(*policy, request.Object.Raw, request.Kind)
allPatches = append(allPatches, policyPatches...)
if len(policyPatches) > 0 {