mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-31 03:45:17 +00:00
refactor generate to use variable substition at rule level
This commit is contained in:
parent
f4c1973c36
commit
d855247a98
7 changed files with 443 additions and 279 deletions
pkg
api/kyverno/v1
engine
generate
|
@ -216,8 +216,8 @@ type Validation struct {
|
|||
// Generation describes which resources will be created when other resource is created
|
||||
type Generation struct {
|
||||
ResourceSpec
|
||||
Data interface{} `json:"data"`
|
||||
Clone CloneFrom `json:"clone"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
Clone CloneFrom `json:"clone,omitempty"`
|
||||
}
|
||||
|
||||
// CloneFrom - location of the resource
|
||||
|
|
|
@ -41,6 +41,17 @@ func ValidateResourceWithPattern(ctx context.EvalInterface, resource, pattern in
|
|||
return "", ValidationError{}
|
||||
}
|
||||
|
||||
// ValidateResourceWithPattern1 is a start of element-by-element validation process
|
||||
// It assumes that validation is started from root, so "/" is passed
|
||||
func ValidateResourceWithPattern1(resource, pattern interface{}) (string, error) {
|
||||
path, err := validateResourceElement(resource, pattern, pattern, "/")
|
||||
if err != nil {
|
||||
return path, err
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func copyInterface(original interface{}) (interface{}, error) {
|
||||
tempData, err := json.Marshal(original)
|
||||
if err != nil {
|
||||
|
|
167
pkg/engine/variables/vars.go
Normal file
167
pkg/engine/variables/vars.go
Normal file
|
@ -0,0 +1,167 @@
|
|||
package variables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/nirmata/kyverno/pkg/engine/context"
|
||||
"github.com/nirmata/kyverno/pkg/engine/operator"
|
||||
)
|
||||
|
||||
//SubstituteVars replaces the variables with the values defined in the context
|
||||
// - if any variable is invaid or has nil value, it is considered as a failed varable substitution
|
||||
func SubstituteVars(ctx context.EvalInterface, pattern interface{}) error {
|
||||
errs := []error{}
|
||||
subVars(ctx, pattern, "", &errs)
|
||||
if len(errs) == 0 {
|
||||
// no error while parsing the pattern
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("variable(s) not found or has nil values: %v", errs)
|
||||
}
|
||||
|
||||
func subVars(ctx context.EvalInterface, pattern interface{}, path string, errs *[]error) interface{} {
|
||||
switch typedPattern := pattern.(type) {
|
||||
case map[string]interface{}:
|
||||
return subMap(ctx, typedPattern, path, errs)
|
||||
case []interface{}:
|
||||
return subArray(ctx, typedPattern, path, errs)
|
||||
case string:
|
||||
return subVal(ctx, typedPattern, path, errs)
|
||||
default:
|
||||
return pattern
|
||||
}
|
||||
}
|
||||
|
||||
func subMap(ctx context.EvalInterface, patternMap map[string]interface{}, path string, errs *[]error) map[string]interface{} {
|
||||
for key, patternElement := range patternMap {
|
||||
curPath := path + "/" + key
|
||||
value := subVars(ctx, patternElement, curPath, errs)
|
||||
patternMap[key] = value
|
||||
|
||||
}
|
||||
return patternMap
|
||||
}
|
||||
|
||||
func subArray(ctx context.EvalInterface, patternList []interface{}, path string, errs *[]error) []interface{} {
|
||||
for idx, patternElement := range patternList {
|
||||
curPath := path + "/" + strconv.Itoa(idx)
|
||||
value := subVars(ctx, patternElement, curPath, errs)
|
||||
patternList[idx] = value
|
||||
}
|
||||
return patternList
|
||||
}
|
||||
|
||||
func subVal(ctx context.EvalInterface, valuePattern string, path string, errs *[]error) interface{} {
|
||||
operatorVariable := getOp(valuePattern)
|
||||
variable := valuePattern[len(operatorVariable):]
|
||||
// substitute variable with value
|
||||
value, failedVars := getValQuery(ctx, variable)
|
||||
// if there are failedVars at this level
|
||||
// capture as error and the path to the variables
|
||||
for _, failedVar := range failedVars {
|
||||
failedPath := path + "/" + failedVar
|
||||
*errs = append(*errs, NewInvalidPath(failedPath))
|
||||
}
|
||||
if operatorVariable == "" {
|
||||
// default or operator.Equal
|
||||
// equal + string value
|
||||
// object variable
|
||||
return value
|
||||
}
|
||||
// operator + string variable
|
||||
switch value.(type) {
|
||||
case string:
|
||||
return string(operatorVariable) + value.(string)
|
||||
default:
|
||||
glog.Infof("cannot use operator with object variables. operator used %s in pattern %v", string(operatorVariable), valuePattern)
|
||||
var emptyInterface interface{}
|
||||
return emptyInterface
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getOp(pattern string) string {
|
||||
operatorVariable := operator.GetOperatorFromStringPattern(pattern)
|
||||
if operatorVariable == operator.Equal {
|
||||
return ""
|
||||
}
|
||||
return string(operatorVariable)
|
||||
}
|
||||
|
||||
func getValQuery(ctx context.EvalInterface, valuePattern string) (interface{}, []string) {
|
||||
var emptyInterface interface{}
|
||||
validRegex := regexp.MustCompile(variableRegex)
|
||||
groups := validRegex.FindAllStringSubmatch(valuePattern, -1)
|
||||
// there can be multiple varialbes in a single value pattern
|
||||
varMap, failedVars := getVal(ctx, groups)
|
||||
if len(varMap) == 0 && len(failedVars) == 0 {
|
||||
// no variables
|
||||
// return original value
|
||||
return valuePattern, nil
|
||||
}
|
||||
if isAllStrings(varMap) {
|
||||
newVal := valuePattern
|
||||
for key, value := range varMap {
|
||||
if val, ok := value.(string); ok {
|
||||
newVal = strings.Replace(newVal, key, val, -1)
|
||||
}
|
||||
}
|
||||
return newVal, failedVars
|
||||
}
|
||||
// multiple substitution per statement for non-string types are not supported
|
||||
for _, value := range varMap {
|
||||
return value, failedVars
|
||||
}
|
||||
return emptyInterface, failedVars
|
||||
}
|
||||
|
||||
func getVal(ctx context.EvalInterface, groups [][]string) (map[string]interface{}, []string) {
|
||||
substiutions := map[string]interface{}{}
|
||||
var failedVars []string
|
||||
for _, group := range groups {
|
||||
// 0th is the string
|
||||
varName := group[0]
|
||||
varValue := group[1]
|
||||
variable, err := ctx.Query(varValue)
|
||||
// err !=nil -> invalid expression
|
||||
// err == nil && variable == nil -> variable is empty or path is not present
|
||||
// a variable with empty value is considered as a failed variable
|
||||
if err != nil || (err == nil && variable == nil) {
|
||||
// could not find the variable at the given path
|
||||
failedVars = append(failedVars, varName)
|
||||
continue
|
||||
}
|
||||
substiutions[varName] = variable
|
||||
}
|
||||
return substiutions, failedVars
|
||||
}
|
||||
|
||||
func isAllStrings(subVar map[string]interface{}) bool {
|
||||
if len(subVar) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, value := range subVar {
|
||||
if _, ok := value.(string); !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
//InvalidPath stores the path to failed variable
|
||||
type InvalidPath struct {
|
||||
path string
|
||||
}
|
||||
|
||||
func (e *InvalidPath) Error() string {
|
||||
return e.path
|
||||
}
|
||||
|
||||
//NewInvalidPath returns a new Invalid Path error
|
||||
func NewInvalidPath(path string) *InvalidPath {
|
||||
return &InvalidPath{path: path}
|
||||
}
|
130
pkg/engine/variables/vars_test.go
Normal file
130
pkg/engine/variables/vars_test.go
Normal file
|
@ -0,0 +1,130 @@
|
|||
package variables
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/nirmata/kyverno/pkg/engine/context"
|
||||
)
|
||||
|
||||
func Test_subVars_success(t *testing.T) {
|
||||
patternMap := []byte(`
|
||||
{
|
||||
"kind": "{{request.object.metadata.name}}",
|
||||
"name": "ns-owner-{{request.object.metadata.name}}",
|
||||
"data": {
|
||||
"rules": [
|
||||
{
|
||||
"apiGroups": [
|
||||
"{{request.object.metadata.name}}"
|
||||
],
|
||||
"resources": [
|
||||
"namespaces"
|
||||
],
|
||||
"verbs": [
|
||||
"*"
|
||||
],
|
||||
"resourceNames": [
|
||||
"{{request.object.metadata.name}}"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
resourceRaw := []byte(`
|
||||
{
|
||||
"metadata": {
|
||||
"name": "temp",
|
||||
"namespace": "n1"
|
||||
},
|
||||
"spec": {
|
||||
"namespace": "n1",
|
||||
"name": "temp1"
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
var pattern, resource interface{}
|
||||
var err error
|
||||
err = json.Unmarshal(patternMap, &pattern)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = json.Unmarshal(resourceRaw, &resource)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
// context
|
||||
ctx := context.NewContext()
|
||||
err = ctx.AddResource(resourceRaw)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if err := SubstituteVars(ctx, pattern); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_subVars_failed(t *testing.T) {
|
||||
patternMap := []byte(`
|
||||
{
|
||||
"kind": "{{request.object.metadata.name1}}",
|
||||
"name": "ns-owner-{{request.object.metadata.name}}",
|
||||
"data": {
|
||||
"rules": [
|
||||
{
|
||||
"apiGroups": [
|
||||
"{{request.object.metadata.name}}"
|
||||
],
|
||||
"resources": [
|
||||
"namespaces"
|
||||
],
|
||||
"verbs": [
|
||||
"*"
|
||||
],
|
||||
"resourceNames": [
|
||||
"{{request.object.metadata.name1}}"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
resourceRaw := []byte(`
|
||||
{
|
||||
"metadata": {
|
||||
"name": "temp",
|
||||
"namespace": "n1"
|
||||
},
|
||||
"spec": {
|
||||
"namespace": "n1",
|
||||
"name": "temp1"
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
var pattern, resource interface{}
|
||||
var err error
|
||||
err = json.Unmarshal(patternMap, &pattern)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = json.Unmarshal(resourceRaw, &resource)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
// context
|
||||
ctx := context.NewContext()
|
||||
err = ctx.AddResource(resourceRaw)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if err := SubstituteVars(ctx, pattern); err == nil {
|
||||
t.Error("error is expected")
|
||||
}
|
||||
}
|
|
@ -16,8 +16,8 @@ func (c *Controller) processGR(gr kyverno.GenerateRequest) error {
|
|||
if err := deleteGeneratedResources(c.client, gr); err != nil {
|
||||
return err
|
||||
}
|
||||
// - trigger-resource is delted
|
||||
// - generated-resources are delted
|
||||
// - trigger-resource is deleted
|
||||
// - generated-resources are deleted
|
||||
// - > Now delete the GenerateRequest CR
|
||||
return c.control.Delete(gr.Name)
|
||||
}
|
||||
|
|
|
@ -2,9 +2,7 @@ package generate
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/golang/glog"
|
||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
||||
|
@ -13,10 +11,8 @@ import (
|
|||
"github.com/nirmata/kyverno/pkg/engine/context"
|
||||
"github.com/nirmata/kyverno/pkg/engine/validate"
|
||||
"github.com/nirmata/kyverno/pkg/engine/variables"
|
||||
"github.com/nirmata/kyverno/pkg/policyviolation"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
func (c *Controller) processGR(gr *kyverno.GenerateRequest) error {
|
||||
|
@ -30,24 +26,10 @@ func (c *Controller) processGR(gr *kyverno.GenerateRequest) error {
|
|||
glog.V(4).Infof("resource does not exist or is yet to be created, requeuing: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 2 - Apply the generate policy on the resource
|
||||
genResources, err = c.applyGenerate(*resource, *gr)
|
||||
switch e := err.(type) {
|
||||
case *Violation:
|
||||
// Generate event
|
||||
// - resource -> rule failed and created PV
|
||||
// - policy -> failed to apply of resource and created PV
|
||||
c.pvGenerator.Add(generatePV(*gr, *resource, e))
|
||||
default:
|
||||
// Generate event
|
||||
// - resource -> rule failed
|
||||
// - policy -> failed tp apply on resource
|
||||
glog.V(4).Info(e)
|
||||
}
|
||||
// 3 - Report Events
|
||||
reportEvents(err, c.eventGen, *gr, *resource)
|
||||
|
||||
// 4 - Update Status
|
||||
return updateStatus(c.statusControl, *gr, err, genResources)
|
||||
}
|
||||
|
@ -97,13 +79,6 @@ func (c *Controller) applyGenerate(resource unstructured.Unstructured, gr kyvern
|
|||
return nil, fmt.Errorf("policy %s, dont not apply to resource %v", gr.Spec.Policy, gr.Spec.Resource)
|
||||
}
|
||||
|
||||
if pv := buildPathNotPresentPV(engineResponse); pv != nil {
|
||||
c.pvGenerator.Add(pv...)
|
||||
// variable substitiution fails in ruleInfo (match,exclude,condition)
|
||||
// the overall policy should not apply to resource
|
||||
return nil, fmt.Errorf("referenced path not present in generate policy %s", policy.Name)
|
||||
}
|
||||
|
||||
// Apply the generate rule on resource
|
||||
return applyGeneratePolicy(c.client, policyContext)
|
||||
}
|
||||
|
@ -152,61 +127,57 @@ func applyRule(client *dclient.Client, rule kyverno.Rule, resource unstructured.
|
|||
var mode ResourceMode
|
||||
var noGenResource kyverno.ResourceSpec
|
||||
|
||||
if invalidPaths := variables.ValidateVariables(ctx, rule.Generation.ResourceSpec); len(invalidPaths) != 0 {
|
||||
return noGenResource, NewViolation(rule.Name, fmt.Errorf("path not present in generate resource spec: %s", invalidPaths))
|
||||
// convert to unstructured Resource
|
||||
genUnst, err := getUnstrRule(rule.Generation.DeepCopy())
|
||||
if err != nil {
|
||||
return noGenResource, err
|
||||
}
|
||||
glog.V(4).Info("applyRule1 %v", genUnst)
|
||||
|
||||
// Variable substitutions
|
||||
// format : {{<variable_name}}
|
||||
// - if there is variables that are not defined the context -> results in error and rule is not applied
|
||||
// - valid variables are replaced with the values
|
||||
if err := variables.SubstituteVars(ctx, genUnst.Object); err != nil {
|
||||
return noGenResource, err
|
||||
}
|
||||
glog.V(4).Info("applyRule2 %v", genUnst.Object)
|
||||
genKind, _, err := unstructured.NestedString(genUnst.Object, "kind")
|
||||
if err != nil {
|
||||
return noGenResource, err
|
||||
}
|
||||
genName, _, err := unstructured.NestedString(genUnst.Object, "name")
|
||||
if err != nil {
|
||||
return noGenResource, err
|
||||
}
|
||||
genNamespace, _, err := unstructured.NestedString(genUnst.Object, "namespace")
|
||||
if err != nil {
|
||||
return noGenResource, err
|
||||
}
|
||||
|
||||
// variable substitution
|
||||
// - name
|
||||
// - namespace
|
||||
// - clone.name
|
||||
// - clone.namespace
|
||||
gen := variableSubsitutionForAttributes(rule.Generation, ctx)
|
||||
// Resource to be generated
|
||||
newGenResource := kyverno.ResourceSpec{
|
||||
Kind: gen.Kind,
|
||||
Namespace: gen.Namespace,
|
||||
Name: gen.Name,
|
||||
Kind: genKind,
|
||||
Namespace: genNamespace,
|
||||
Name: genName,
|
||||
}
|
||||
genData, _, err := unstructured.NestedMap(genUnst.Object, "data")
|
||||
if err != nil {
|
||||
return noGenResource, err
|
||||
}
|
||||
genCopy, _, err := unstructured.NestedMap(genUnst.Object, "clone")
|
||||
if err != nil {
|
||||
return noGenResource, err
|
||||
}
|
||||
|
||||
// DATA
|
||||
if gen.Data != nil {
|
||||
if rdata, mode, err = handleData(rule.Name, gen, client, resource, ctx); err != nil {
|
||||
glog.V(4).Info(err)
|
||||
switch e := err.(type) {
|
||||
case *ParseFailed, *NotFound, *ConfigNotFound:
|
||||
// handled errors
|
||||
return noGenResource, e
|
||||
case *Violation:
|
||||
// create policy violation
|
||||
return noGenResource, e
|
||||
default:
|
||||
// errors that cant be handled
|
||||
return noGenResource, e
|
||||
}
|
||||
}
|
||||
if rdata == nil {
|
||||
// existing resource contains the configuration
|
||||
return newGenResource, nil
|
||||
}
|
||||
if genData != nil {
|
||||
rdata, mode, err = manageData(genKind, genNamespace, genName, genData, client, resource)
|
||||
} else {
|
||||
rdata, mode, err = manageClone(genKind, genNamespace, genName, genCopy, client, resource)
|
||||
}
|
||||
// CLONE
|
||||
if gen.Clone != (kyverno.CloneFrom{}) {
|
||||
if rdata, mode, err = handleClone(rule.Name, gen, client, resource, ctx); err != nil {
|
||||
glog.V(4).Info(err)
|
||||
switch e := err.(type) {
|
||||
case *NotFound:
|
||||
// handled errors
|
||||
return noGenResource, e
|
||||
default:
|
||||
// errors that cant be handled
|
||||
return noGenResource, e
|
||||
}
|
||||
}
|
||||
if rdata == nil {
|
||||
// resource already exists
|
||||
return newGenResource, nil
|
||||
}
|
||||
if rdata == nil {
|
||||
// existing resource contains the configuration
|
||||
return newGenResource, nil
|
||||
}
|
||||
if processExisting {
|
||||
// handle existing resources
|
||||
|
@ -218,8 +189,8 @@ func applyRule(client *dclient.Client, rule kyverno.Rule, resource unstructured.
|
|||
// build the resource template
|
||||
newResource := &unstructured.Unstructured{}
|
||||
newResource.SetUnstructuredContent(rdata)
|
||||
newResource.SetName(gen.Name)
|
||||
newResource.SetNamespace(gen.Namespace)
|
||||
newResource.SetName(genName)
|
||||
newResource.SetNamespace(genNamespace)
|
||||
|
||||
// manage labels
|
||||
// - app.kubernetes.io/managed-by: kyverno
|
||||
|
@ -230,58 +201,88 @@ func applyRule(client *dclient.Client, rule kyverno.Rule, resource unstructured.
|
|||
// Reset resource version
|
||||
newResource.SetResourceVersion("")
|
||||
// Create the resource
|
||||
glog.V(4).Infof("Creating new resource %s/%s/%s", gen.Kind, gen.Namespace, gen.Name)
|
||||
_, err = client.CreateResource(gen.Kind, gen.Namespace, newResource, false)
|
||||
glog.V(4).Infof("Creating new resource %s/%s/%s", genKind, genNamespace, genName)
|
||||
_, err = client.CreateResource(genKind, genNamespace, newResource, false)
|
||||
if err != nil {
|
||||
// Failed to create resource
|
||||
return noGenResource, err
|
||||
}
|
||||
glog.V(4).Infof("Created new resource %s/%s/%s", gen.Kind, gen.Namespace, gen.Name)
|
||||
glog.V(4).Infof("Created new resource %s/%s/%s", genKind, genNamespace, genName)
|
||||
|
||||
} else if mode == Update {
|
||||
glog.V(4).Infof("Updating existing resource %s/%s/%s", gen.Kind, gen.Namespace, gen.Name)
|
||||
glog.V(4).Infof("Updating existing resource %s/%s/%s", genKind, genNamespace, genName)
|
||||
// Update the resource
|
||||
_, err := client.UpdateResource(gen.Kind, gen.Namespace, newResource, false)
|
||||
_, err := client.UpdateResource(genKind, genNamespace, newResource, false)
|
||||
if err != nil {
|
||||
// Failed to update resource
|
||||
return noGenResource, err
|
||||
}
|
||||
glog.V(4).Infof("Updated existing resource %s/%s/%s", gen.Kind, gen.Namespace, gen.Name)
|
||||
glog.V(4).Infof("Updated existing resource %s/%s/%s", genKind, genNamespace, genName)
|
||||
}
|
||||
|
||||
return newGenResource, nil
|
||||
}
|
||||
|
||||
func variableSubsitutionForAttributes(gen kyverno.Generation, ctx context.EvalInterface) kyverno.Generation {
|
||||
// Name
|
||||
name := gen.Name
|
||||
namespace := gen.Namespace
|
||||
newNameVar := variables.SubstituteVariables(ctx, name)
|
||||
|
||||
if newName, ok := newNameVar.(string); ok {
|
||||
gen.Name = newName
|
||||
func manageData(kind, namespace, name string, data map[string]interface{}, client *dclient.Client, resource unstructured.Unstructured) (map[string]interface{}, ResourceMode, error) {
|
||||
// check if resource to be generated exists
|
||||
obj, err := client.GetResource(kind, namespace, name)
|
||||
if apierrors.IsNotFound(err) {
|
||||
glog.V(4).Infof("Resource %s/%s/%s does not exists, will try to create", kind, namespace, name)
|
||||
return data, Create, nil
|
||||
}
|
||||
if err != nil {
|
||||
//something wrong while fetching resource
|
||||
// client-errors
|
||||
return nil, Skip, err
|
||||
}
|
||||
// Resource exists; verfiy the content of the resource
|
||||
err = checkResource(data, obj)
|
||||
if err == nil {
|
||||
// Existing resource does contain the mentioned configuration in spec, skip processing the resource as it is already in expected state
|
||||
return nil, Skip, nil
|
||||
}
|
||||
|
||||
newNamespaceVar := variables.SubstituteVariables(ctx, namespace)
|
||||
if newNamespace, ok := newNamespaceVar.(string); ok {
|
||||
gen.Namespace = newNamespace
|
||||
glog.V(4).Infof("Resource %s/%s/%s exists but missing required configuration, will try to update", kind, namespace, name)
|
||||
return data, Update, nil
|
||||
|
||||
}
|
||||
|
||||
func manageClone(kind, namespace, name string, clone map[string]interface{}, client *dclient.Client, resource unstructured.Unstructured) (map[string]interface{}, ResourceMode, error) {
|
||||
// check if resource to be generated exists
|
||||
_, err := client.GetResource(kind, namespace, name)
|
||||
if err == nil {
|
||||
// resource does exists, not need to process further as it is already in expected state
|
||||
return nil, Skip, nil
|
||||
}
|
||||
//TODO: check this
|
||||
if !apierrors.IsNotFound(err) {
|
||||
//something wrong while fetching resource
|
||||
return nil, Skip, err
|
||||
}
|
||||
|
||||
if gen.Clone != (kyverno.CloneFrom{}) {
|
||||
// Clone
|
||||
cloneName := gen.Clone.Name
|
||||
cloneNamespace := gen.Clone.Namespace
|
||||
|
||||
newcloneNameVar := variables.SubstituteVariables(ctx, cloneName)
|
||||
if newcloneName, ok := newcloneNameVar.(string); ok {
|
||||
gen.Clone.Name = newcloneName
|
||||
}
|
||||
newcloneNamespaceVar := variables.SubstituteVariables(ctx, cloneNamespace)
|
||||
if newcloneNamespace, ok := newcloneNamespaceVar.(string); ok {
|
||||
gen.Clone.Namespace = newcloneNamespace
|
||||
}
|
||||
newRNs, _, err := unstructured.NestedString(clone, "namespace")
|
||||
if err != nil {
|
||||
return nil, Skip, err
|
||||
}
|
||||
return gen
|
||||
newRName, _, err := unstructured.NestedString(clone, "name")
|
||||
|
||||
if err != nil {
|
||||
return nil, Skip, err
|
||||
}
|
||||
// Short-circuit if the resource to be generated and the clone is the same
|
||||
if newRNs == namespace && newRName == name {
|
||||
// attempting to clone it self, this will fail -> short-ciruit it
|
||||
return nil, Skip, nil
|
||||
}
|
||||
|
||||
// check if the resource as reference in clone exists?
|
||||
obj, err := client.GetResource(kind, newRNs, newRName)
|
||||
if err != nil {
|
||||
return nil, Skip, err
|
||||
}
|
||||
// create the resource based on the reference clone
|
||||
return obj.UnstructuredContent(), Create, nil
|
||||
|
||||
}
|
||||
|
||||
// ResourceMode defines the mode for generated resource
|
||||
|
@ -292,130 +293,33 @@ const (
|
|||
Skip ResourceMode = "SKIP"
|
||||
//Create : create a new resource
|
||||
Create = "CREATE"
|
||||
//Update : update/override the new resource
|
||||
//Update : update/overwrite the new resource
|
||||
Update = "UPDATE"
|
||||
)
|
||||
|
||||
func copyInterface(original interface{}) (interface{}, error) {
|
||||
tempData, err := json.Marshal(original)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fmt.Println(string(tempData))
|
||||
var temp interface{}
|
||||
err = json.Unmarshal(tempData, &temp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return temp, nil
|
||||
}
|
||||
|
||||
// manage the creation/update of resource to be generated using the spec defined in the policy
|
||||
func handleData(ruleName string, generateRule kyverno.Generation, client *dclient.Client, resource unstructured.Unstructured, ctx context.EvalInterface) (map[string]interface{}, ResourceMode, error) {
|
||||
//work on copy of the data
|
||||
// as the type of data stored in interface is not know,
|
||||
// we marshall the data and unmarshal it into a new resource to create a copy
|
||||
dataCopy, err := copyInterface(generateRule.Data)
|
||||
if err != nil {
|
||||
glog.V(4).Infof("failed to create a copy of the interface %v", generateRule.Data)
|
||||
return nil, Skip, err
|
||||
}
|
||||
// replace variables with the corresponding values
|
||||
newData := variables.SubstituteVariables(ctx, dataCopy)
|
||||
// if any variable defined in the data is not avaialbe in the context
|
||||
if invalidPaths := variables.ValidateVariables(ctx, newData); len(invalidPaths) != 0 {
|
||||
return nil, Skip, NewViolation(ruleName, fmt.Errorf("path not present in generate data: %s", invalidPaths))
|
||||
}
|
||||
|
||||
// check if resource exists
|
||||
obj, err := client.GetResource(generateRule.Kind, generateRule.Namespace, generateRule.Name)
|
||||
if apierrors.IsNotFound(err) {
|
||||
// Resource does not exist
|
||||
// Processing the request first time
|
||||
rdata, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&newData)
|
||||
if err != nil {
|
||||
return nil, Skip, NewParseFailed(newData, err)
|
||||
}
|
||||
glog.V(4).Infof("Resource %s/%s/%s does not exists, will try to create", generateRule.Kind, generateRule.Namespace, generateRule.Name)
|
||||
return rdata, Create, nil
|
||||
}
|
||||
if err != nil {
|
||||
//something wrong while fetching resource
|
||||
return nil, Skip, err
|
||||
}
|
||||
// Resource exists; verfiy the content of the resource
|
||||
ok, err := checkResource(ctx, newData, obj)
|
||||
if err != nil {
|
||||
// error while evaluating if the existing resource contains the required information
|
||||
return nil, Skip, err
|
||||
}
|
||||
|
||||
if !ok {
|
||||
// existing resource does not contain the configuration mentioned in spec, will try to update
|
||||
rdata, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&newData)
|
||||
if err != nil {
|
||||
return nil, Skip, NewParseFailed(newData, err)
|
||||
}
|
||||
|
||||
glog.V(4).Infof("Resource %s/%s/%s exists but missing required configuration, will try to update", generateRule.Kind, generateRule.Namespace, generateRule.Name)
|
||||
return rdata, Update, nil
|
||||
}
|
||||
// Existing resource does contain the mentioned configuration in spec, skip processing the resource as it is already in expected state
|
||||
return nil, Skip, nil
|
||||
}
|
||||
|
||||
// manage the creation/update based on the reference clone resource
|
||||
func handleClone(ruleName string, generateRule kyverno.Generation, client *dclient.Client, resource unstructured.Unstructured, ctx context.EvalInterface) (map[string]interface{}, ResourceMode, error) {
|
||||
// if any variable defined in the data is not avaialbe in the context
|
||||
if invalidPaths := variables.ValidateVariables(ctx, generateRule.Clone); len(invalidPaths) != 0 {
|
||||
return nil, Skip, NewViolation(ruleName, fmt.Errorf("path not present in generate clone: %s", invalidPaths))
|
||||
}
|
||||
|
||||
// check if resource to be generated exists
|
||||
_, err := client.GetResource(generateRule.Kind, generateRule.Namespace, generateRule.Name)
|
||||
if err == nil {
|
||||
// resource does exists, not need to process further as it is already in expected state
|
||||
return nil, Skip, nil
|
||||
}
|
||||
if !apierrors.IsNotFound(err) {
|
||||
//something wrong while fetching resource
|
||||
return nil, Skip, err
|
||||
}
|
||||
|
||||
// get clone resource reference in the rule
|
||||
obj, err := client.GetResource(generateRule.Kind, generateRule.Clone.Namespace, generateRule.Clone.Name)
|
||||
if apierrors.IsNotFound(err) {
|
||||
// reference resource does not exist, cant generate the resources
|
||||
return nil, Skip, NewNotFound(generateRule.Kind, generateRule.Clone.Namespace, generateRule.Clone.Name)
|
||||
}
|
||||
if err != nil {
|
||||
//something wrong while fetching resource
|
||||
return nil, Skip, err
|
||||
}
|
||||
// create the resource based on the reference clone
|
||||
return obj.UnstructuredContent(), Create, nil
|
||||
}
|
||||
|
||||
func checkResource(ctx context.EvalInterface, newResourceSpec interface{}, resource *unstructured.Unstructured) (bool, error) {
|
||||
func checkResource(newResourceSpec interface{}, resource *unstructured.Unstructured) error {
|
||||
// check if the resource spec if a subset of the resource
|
||||
path, err := validate.ValidateResourceWithPattern(ctx, resource.Object, newResourceSpec)
|
||||
if !reflect.DeepEqual(err, validate.ValidationError{}) {
|
||||
glog.V(4).Infof("config not a subset of resource. failed at path %s: %v", path, err)
|
||||
return false, errors.New(err.ErrorMsg)
|
||||
if path, err := validate.ValidateResourceWithPattern1(resource.Object, newResourceSpec); err != nil {
|
||||
glog.V(4).Info("Failed to match the resource at path %s: err", path, err)
|
||||
return err
|
||||
}
|
||||
return true, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func generatePV(gr kyverno.GenerateRequest, resource unstructured.Unstructured, err *Violation) policyviolation.Info {
|
||||
|
||||
info := policyviolation.Info{
|
||||
PolicyName: gr.Spec.Policy,
|
||||
Resource: resource,
|
||||
Rules: []kyverno.ViolatedRule{{
|
||||
Name: err.rule,
|
||||
Type: "Generation",
|
||||
Message: err.Error(),
|
||||
}},
|
||||
func getUnstrRule(rule *kyverno.Generation) (*unstructured.Unstructured, error) {
|
||||
ruleData, err := json.Marshal(rule)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return info
|
||||
return ConvertToUnstructured(ruleData)
|
||||
}
|
||||
|
||||
//ConvertToUnstructured converts the resource to unstructured format
|
||||
func ConvertToUnstructured(data []byte) (*unstructured.Unstructured, error) {
|
||||
resource := &unstructured.Unstructured{}
|
||||
err := resource.UnmarshalJSON(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resource, nil
|
||||
}
|
||||
|
|
|
@ -5,9 +5,7 @@ import (
|
|||
|
||||
"github.com/golang/glog"
|
||||
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
|
||||
"github.com/nirmata/kyverno/pkg/engine/response"
|
||||
"github.com/nirmata/kyverno/pkg/event"
|
||||
"github.com/nirmata/kyverno/pkg/policyviolation"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
|
@ -20,45 +18,9 @@ func reportEvents(err error, eventGen event.Interface, gr kyverno.GenerateReques
|
|||
eventGen.Add(events...)
|
||||
return
|
||||
}
|
||||
switch e := err.(type) {
|
||||
case *Violation:
|
||||
// - resource -> rule failed and created PV
|
||||
// - policy -> failed to apply of resource and created PV
|
||||
glog.V(4).Infof("reporing events for %v", e)
|
||||
events := failedEventsPV(err, gr, resource)
|
||||
eventGen.Add(events...)
|
||||
default:
|
||||
// - resource -> rule failed
|
||||
// - policy -> failed tp apply on resource
|
||||
glog.V(4).Infof("reporing events for %v", e)
|
||||
events := failedEvents(err, gr, resource)
|
||||
eventGen.Add(events...)
|
||||
}
|
||||
}
|
||||
|
||||
func failedEventsPV(err error, gr kyverno.GenerateRequest, resource unstructured.Unstructured) []event.Info {
|
||||
var events []event.Info
|
||||
// Cluster Policy
|
||||
pe := event.Info{}
|
||||
pe.Kind = "ClusterPolicy"
|
||||
// cluserwide-resource
|
||||
pe.Name = gr.Spec.Policy
|
||||
pe.Reason = event.PolicyViolation.String()
|
||||
pe.Source = event.GeneratePolicyController
|
||||
pe.Message = fmt.Sprintf("policy failed to apply on resource %s/%s/%s creating violation: %v", resource.GetKind(), resource.GetNamespace(), resource.GetName(), err)
|
||||
events = append(events, pe)
|
||||
|
||||
// Resource
|
||||
re := event.Info{}
|
||||
re.Kind = resource.GetKind()
|
||||
re.Namespace = resource.GetNamespace()
|
||||
re.Name = resource.GetName()
|
||||
re.Reason = event.PolicyViolation.String()
|
||||
re.Source = event.GeneratePolicyController
|
||||
re.Message = fmt.Sprintf("policy %s failed to apply created violation: %v", gr.Spec.Policy, err)
|
||||
events = append(events, re)
|
||||
|
||||
return events
|
||||
glog.V(4).Infof("reporing events for %v", err)
|
||||
events := failedEvents(err, gr, resource)
|
||||
eventGen.Add(events...)
|
||||
}
|
||||
|
||||
func failedEvents(err error, gr kyverno.GenerateRequest, resource unstructured.Unstructured) []event.Info {
|
||||
|
@ -110,13 +72,3 @@ func successEvents(gr kyverno.GenerateRequest, resource unstructured.Unstructure
|
|||
|
||||
return events
|
||||
}
|
||||
|
||||
// buildPathNotPresentPV build violation info when referenced path not found
|
||||
func buildPathNotPresentPV(er response.EngineResponse) []policyviolation.Info {
|
||||
for _, rr := range er.PolicyResponse.Rules {
|
||||
if rr.PathNotPresent {
|
||||
return policyviolation.GeneratePVsFromEngineResponse([]response.EngineResponse{er})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue