mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
522 adding force mutate function
This commit is contained in:
parent
7aa1e1515b
commit
4db0cf7a87
4 changed files with 133 additions and 69 deletions
|
@ -116,7 +116,7 @@ func processOverlayPatches(resource, overlay interface{}) ([][]byte, overlayErro
|
|||
}
|
||||
}
|
||||
|
||||
patchBytes, err := mutateResourceWithOverlay(resource, overlay)
|
||||
patchBytes, err := MutateResourceWithOverlay(resource, overlay)
|
||||
if err != nil {
|
||||
return patchBytes, newOverlayError(overlayFailure, err.Error())
|
||||
}
|
||||
|
@ -124,8 +124,8 @@ func processOverlayPatches(resource, overlay interface{}) ([][]byte, overlayErro
|
|||
return patchBytes, overlayError{}
|
||||
}
|
||||
|
||||
// mutateResourceWithOverlay is a start of overlaying process
|
||||
func mutateResourceWithOverlay(resource, pattern interface{}) ([][]byte, error) {
|
||||
// MutateResourceWithOverlay is a start of overlaying process
|
||||
func MutateResourceWithOverlay(resource, pattern interface{}) ([][]byte, error) {
|
||||
// It assumes that mutation is started from root, so "/" is passed
|
||||
return applyOverlay(resource, pattern, "/")
|
||||
}
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nirmata/kyverno/pkg/engine/utils"
|
||||
|
||||
"github.com/nirmata/kyverno/pkg/engine/context"
|
||||
|
||||
"github.com/golang/glog"
|
||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
||||
"github.com/nirmata/kyverno/pkg/engine/mutate"
|
||||
|
@ -122,6 +127,71 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) {
|
|||
resp.PatchedResource = patchedResource
|
||||
return resp
|
||||
}
|
||||
|
||||
func mutateResourceWithOverlay(resource unstructured.Unstructured, overlay interface{}) (unstructured.Unstructured, error) {
|
||||
patches, err := mutate.MutateResourceWithOverlay(resource.UnstructuredContent(), overlay)
|
||||
if err != nil {
|
||||
return unstructured.Unstructured{}, err
|
||||
}
|
||||
if len(patches) == 0 {
|
||||
return resource, nil
|
||||
}
|
||||
|
||||
// convert to RAW
|
||||
resourceRaw, err := resource.MarshalJSON()
|
||||
if err != nil {
|
||||
return unstructured.Unstructured{}, err
|
||||
}
|
||||
|
||||
var patchResource []byte
|
||||
patchResource, err = utils.ApplyPatches(resourceRaw, patches)
|
||||
if err != nil {
|
||||
return unstructured.Unstructured{}, err
|
||||
}
|
||||
|
||||
resource = unstructured.Unstructured{}
|
||||
err = resource.UnmarshalJSON(patchResource)
|
||||
if err != nil {
|
||||
return unstructured.Unstructured{}, err
|
||||
}
|
||||
|
||||
return resource, nil
|
||||
}
|
||||
|
||||
// ForceMutate does not check any conditions, it simply mutates the given resource
|
||||
func ForceMutate(ctx context.EvalInterface, policy kyverno.ClusterPolicy, resource unstructured.Unstructured) (unstructured.Unstructured, error) {
|
||||
var err error
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
if !rule.HasMutate() {
|
||||
continue
|
||||
}
|
||||
|
||||
mutation := rule.Mutation.DeepCopy()
|
||||
|
||||
if mutation.Overlay != nil {
|
||||
overlay := mutation.Overlay
|
||||
if overlay, err = variables.SubstituteVars(ctx, overlay); err != nil {
|
||||
return unstructured.Unstructured{}, err
|
||||
}
|
||||
|
||||
resource, err = mutateResourceWithOverlay(resource, overlay)
|
||||
if err != nil {
|
||||
return unstructured.Unstructured{}, fmt.Errorf("could not mutate resource with overlay on rule %v:%v", rule.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
if rule.Mutation.Patches != nil {
|
||||
var resp response.RuleResponse
|
||||
resp, resource = mutate.ProcessPatches(rule, resource)
|
||||
if !resp.Success {
|
||||
return unstructured.Unstructured{}, fmt.Errorf(resp.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resource, nil
|
||||
}
|
||||
|
||||
func incrementAppliedRuleCount(resp *response.EngineResponse) {
|
||||
resp.PolicyResponse.RulesAppliedCount++
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
|
@ -66,7 +68,23 @@ func (c *crdSync) sync() {
|
|||
return
|
||||
}
|
||||
|
||||
deleteCRDFromPreviousSync()
|
||||
|
||||
for _, crd := range crds.Items {
|
||||
parseCRD(crd)
|
||||
}
|
||||
}
|
||||
|
||||
func deleteCRDFromPreviousSync() {
|
||||
for _, crd := range openApiGlobalState.crdList {
|
||||
delete(openApiGlobalState.kindToDefinitionName, crd)
|
||||
delete(openApiGlobalState.definitions, crd)
|
||||
}
|
||||
|
||||
openApiGlobalState.crdList = []string{}
|
||||
}
|
||||
|
||||
func parseCRD(crd unstructured.Unstructured) {
|
||||
var crdDefinition crdDefinition
|
||||
crdRaw, _ := json.Marshal(crd.Object)
|
||||
_ = json.Unmarshal(crdRaw, &crdDefinition)
|
||||
|
@ -74,7 +92,7 @@ func (c *crdSync) sync() {
|
|||
crdName := crdDefinition.Spec.Names.Kind
|
||||
if len(crdDefinition.Spec.Versions) < 1 {
|
||||
glog.V(4).Infof("could not parse crd schema, no versions present")
|
||||
continue
|
||||
return
|
||||
}
|
||||
|
||||
var schema yaml.MapSlice
|
||||
|
@ -84,10 +102,11 @@ func (c *crdSync) sync() {
|
|||
parsedSchema, err := openapi_v2.NewSchema(schema, compiler.NewContext("schema", nil))
|
||||
if err != nil {
|
||||
glog.V(4).Infof("could not parse crd schema:%v", err)
|
||||
continue
|
||||
return
|
||||
}
|
||||
|
||||
openApiGlobalState.crdList = append(openApiGlobalState.crdList, crdName)
|
||||
|
||||
openApiGlobalState.kindToDefinitionName[crdName] = crdName
|
||||
openApiGlobalState.definitions[crdName] = parsedSchema
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,12 +29,11 @@ var openApiGlobalState struct {
|
|||
document *openapi_v2.Document
|
||||
definitions map[string]*openapi_v2.Schema
|
||||
kindToDefinitionName map[string]string
|
||||
crdList []string
|
||||
models proto.Models
|
||||
isSet bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
if !openApiGlobalState.isSet {
|
||||
defaultDoc, err := getSchemaDocument()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -45,17 +44,11 @@ func init() {
|
|||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ValidatePolicyMutation(policy v1.ClusterPolicy) error {
|
||||
openApiGlobalState.mutex.RLock()
|
||||
defer openApiGlobalState.mutex.RUnlock()
|
||||
|
||||
if !openApiGlobalState.isSet {
|
||||
glog.V(4).Info("Cannot Validate policy: Validation global state not set")
|
||||
return nil
|
||||
}
|
||||
|
||||
var kindToRules = make(map[string][]v1.Rule)
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
if rule.HasMutate() {
|
||||
|
@ -73,7 +66,7 @@ func ValidatePolicyMutation(policy v1.ClusterPolicy) error {
|
|||
}
|
||||
|
||||
for kind, rules := range kindToRules {
|
||||
newPolicy := policy
|
||||
newPolicy := *policy.DeepCopy()
|
||||
newPolicy.Spec.Rules = rules
|
||||
resource, _ := generateEmptyResource(openApiGlobalState.definitions[openApiGlobalState.kindToDefinitionName[kind]]).(map[string]interface{})
|
||||
if resource == nil {
|
||||
|
@ -89,22 +82,11 @@ func ValidatePolicyMutation(policy v1.ClusterPolicy) error {
|
|||
glog.V(4).Infof("Failed to load service account in context:%v", err)
|
||||
}
|
||||
|
||||
policyContext := engine.PolicyContext{
|
||||
Policy: newPolicy,
|
||||
NewResource: newResource,
|
||||
Context: ctx,
|
||||
patchedResource, err := engine.ForceMutate(ctx, *newPolicy.DeepCopy(), newResource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp := engine.Mutate(policyContext)
|
||||
if len(resp.GetSuccessRules()) != len(rules) {
|
||||
var errMessages []string
|
||||
for _, rule := range resp.PolicyResponse.Rules {
|
||||
if !rule.Success {
|
||||
errMessages = append(errMessages, fmt.Sprintf("Invalid rule : %v, %v", rule.Name, rule.Message))
|
||||
}
|
||||
}
|
||||
return fmt.Errorf(strings.Join(errMessages, "\n"))
|
||||
}
|
||||
err = ValidateResource(*resp.PatchedResource.DeepCopy(), kind)
|
||||
err = ValidateResource(*patchedResource.DeepCopy(), kind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -113,22 +95,11 @@ func ValidatePolicyMutation(policy v1.ClusterPolicy) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// For crd, we do not store definition in document
|
||||
func getSchemaFromDefinitions(kind string) (proto.Schema, error) {
|
||||
path := proto.NewPath(kind)
|
||||
return (&proto.Definitions{}).ParseSchema(openApiGlobalState.definitions[kind], &path)
|
||||
}
|
||||
|
||||
func ValidateResource(patchedResource unstructured.Unstructured, kind string) error {
|
||||
openApiGlobalState.mutex.RLock()
|
||||
defer openApiGlobalState.mutex.RUnlock()
|
||||
var err error
|
||||
|
||||
if !openApiGlobalState.isSet {
|
||||
glog.V(4).Info("Cannot Validate resource: Validation global state not set")
|
||||
return nil
|
||||
}
|
||||
|
||||
kind = openApiGlobalState.kindToDefinitionName[kind]
|
||||
schema := openApiGlobalState.models.LookupModel(kind)
|
||||
if schema == nil {
|
||||
|
@ -171,8 +142,6 @@ func useOpenApiDocument(customDoc *openapi_v2.Document) error {
|
|||
return err
|
||||
}
|
||||
|
||||
openApiGlobalState.isSet = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -186,6 +155,12 @@ func getSchemaDocument() (*openapi_v2.Document, error) {
|
|||
return openapi_v2.NewDocument(spec, compiler.NewContext("$root", nil))
|
||||
}
|
||||
|
||||
// For crd, we do not store definition in document
|
||||
func getSchemaFromDefinitions(kind string) (proto.Schema, error) {
|
||||
path := proto.NewPath(kind)
|
||||
return (&proto.Definitions{}).ParseSchema(openApiGlobalState.definitions[kind], &path)
|
||||
}
|
||||
|
||||
func generateEmptyResource(kindSchema *openapi_v2.Schema) interface{} {
|
||||
|
||||
types := kindSchema.GetType().GetValue()
|
||||
|
|
Loading…
Reference in a new issue