mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-31 03:45:17 +00:00
Merge branch 'master' into 544_documentation
# Conflicts: # pkg/engine/overlay_test.go
This commit is contained in:
commit
340dee24bc
16 changed files with 308 additions and 131 deletions
cmd
pkg
dclient
engine
event
namespace
policy
webhooks
|
@ -7,6 +7,7 @@ import (
|
|||
"flag"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/nirmata/kyverno/pkg/config"
|
||||
|
@ -41,7 +42,7 @@ func main() {
|
|||
|
||||
// DYNAMIC CLIENT
|
||||
// - client for all registered resources
|
||||
client, err := client.NewClient(clientConfig)
|
||||
client, err := client.NewClient(clientConfig, 10*time.Second, stopCh)
|
||||
if err != nil {
|
||||
glog.Fatalf("Error creating client: %v\n", err)
|
||||
}
|
||||
|
|
|
@ -60,12 +60,13 @@ func main() {
|
|||
|
||||
// DYNAMIC CLIENT
|
||||
// - client for all registered resources
|
||||
client, err := dclient.NewClient(clientConfig)
|
||||
// - invalidate local cache of registered resource every 10 seconds
|
||||
client, err := dclient.NewClient(clientConfig, 10*time.Second, stopCh)
|
||||
if err != nil {
|
||||
glog.Fatalf("Error creating client: %v\n", err)
|
||||
}
|
||||
// CRD CHECK
|
||||
// - verify if the CRD for Policy & PolicyViolation are avialalbe
|
||||
// - verify if the CRD for Policy & PolicyViolation are available
|
||||
if !utils.CRDInstalled(client.DiscoveryClient) {
|
||||
glog.Fatalf("Required CRDs unavailable")
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ type Client struct {
|
|||
}
|
||||
|
||||
//NewClient creates new instance of client
|
||||
func NewClient(config *rest.Config) (*Client, error) {
|
||||
func NewClient(config *rest.Config, resync time.Duration, stopCh <-chan struct{}) (*Client, error) {
|
||||
dclient, err := dynamic.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -52,6 +52,13 @@ func NewClient(config *rest.Config) (*Client, error) {
|
|||
}
|
||||
// Set discovery client
|
||||
discoveryClient := ServerPreferredResources{memory.NewMemCacheClient(kclient.Discovery())}
|
||||
// client will invalidate registered resources cache every x seconds,
|
||||
// As there is no way to identify if the registered resource is available or not
|
||||
// we will be invalidating the local cache, so the next request get a fresh cache
|
||||
// If a resource is removed then and cache is not invalidate yet, we will not detect the removal
|
||||
// but the re-sync shall re-evaluate
|
||||
go discoveryClient.Poll(resync, stopCh)
|
||||
|
||||
client.SetDiscovery(discoveryClient)
|
||||
return &client, nil
|
||||
}
|
||||
|
@ -266,6 +273,25 @@ type ServerPreferredResources struct {
|
|||
cachedClient discovery.CachedDiscoveryInterface
|
||||
}
|
||||
|
||||
//Poll will keep invalidate the local cache
|
||||
func (c ServerPreferredResources) Poll(resync time.Duration, stopCh <-chan struct{}) {
|
||||
// start a ticker
|
||||
ticker := time.NewTicker(resync)
|
||||
defer func() { ticker.Stop() }()
|
||||
glog.Infof("Starting registered resources sync: every %d seconds", resync)
|
||||
for {
|
||||
select {
|
||||
case <-stopCh:
|
||||
glog.Info("Stopping registered resources sync")
|
||||
return
|
||||
case <-ticker.C:
|
||||
// set cache as stale
|
||||
glog.V(6).Info("invalidating local client cache for registered resources")
|
||||
c.cachedClient.Invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//GetGVRFromKind get the Group Version Resource from kind
|
||||
// if kind is not found in first attempt we invalidate the cache,
|
||||
// the retry will then fetch the new registered resources and check again
|
||||
|
|
|
@ -281,7 +281,7 @@ func applyOverlayToArrayOfMaps(resource, overlay []interface{}, path string) ([]
|
|||
|
||||
if len(anchors) > 0 {
|
||||
// If we have anchors - choose corresponding resource element and mutate it
|
||||
patches, err := applyOverlayWithAnchors(resource, overlayElement, anchors, path)
|
||||
patches, err := applyOverlayWithAnchors(resource, overlayElement, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -312,21 +312,17 @@ func applyOverlayToArrayOfMaps(resource, overlay []interface{}, path string) ([]
|
|||
return appliedPatches, nil
|
||||
}
|
||||
|
||||
func applyOverlayWithAnchors(resource []interface{}, overlay interface{}, anchors map[string]interface{}, path string) ([][]byte, error) {
|
||||
func applyOverlayWithAnchors(resource []interface{}, overlay interface{}, path string) ([][]byte, error) {
|
||||
var appliedPatches [][]byte
|
||||
|
||||
for i, resourceElement := range resource {
|
||||
typedResource := resourceElement.(map[string]interface{})
|
||||
|
||||
currentPath := path + strconv.Itoa(i) + "/"
|
||||
// currentPath example: /spec/template/spec/containers/3/
|
||||
if !skipArrayObject(typedResource, anchors) {
|
||||
patches, err := applyOverlay(resourceElement, overlay, currentPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appliedPatches = append(appliedPatches, patches...)
|
||||
patches, err := applyOverlay(resourceElement, overlay, currentPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appliedPatches = append(appliedPatches, patches...)
|
||||
}
|
||||
|
||||
return appliedPatches, nil
|
||||
|
|
|
@ -1071,3 +1071,88 @@ func Test_wrapBoolean(t *testing.T) {
|
|||
assert.Assert(t, testcase.expected == out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyOverlay_ConditionOnArray(t *testing.T) {
|
||||
resourceRaw := []byte(`
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": {
|
||||
"name": "myapp-pod",
|
||||
"labels": {
|
||||
"app": "myapp",
|
||||
"dedicated": "spark"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"name": "myapp-container",
|
||||
"image": "busybox",
|
||||
"command": [
|
||||
"sh",
|
||||
"-c",
|
||||
"echo Hello Kubernetes! && sleep 3600"
|
||||
]
|
||||
}
|
||||
],
|
||||
"affinity": {
|
||||
"nodeAffinity": {
|
||||
"a": {
|
||||
"b": [
|
||||
{
|
||||
"matchExpressions": [
|
||||
{
|
||||
"key": "dedicated",
|
||||
"operator": "NotIn",
|
||||
"values": [
|
||||
"spark"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
overlayRaw := []byte(`
|
||||
{
|
||||
"spec": {
|
||||
"affinity": {
|
||||
"nodeAffinity": {
|
||||
"a": {
|
||||
"b": [
|
||||
{
|
||||
"matchExpressions": [
|
||||
{
|
||||
"(key)": "dedicated",
|
||||
"operator": "In",
|
||||
"(values)": [
|
||||
"spark"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
var resource, overlay interface{}
|
||||
|
||||
assert.NilError(t, json.Unmarshal(resourceRaw, &resource))
|
||||
assert.NilError(t, json.Unmarshal(overlayRaw, &overlay))
|
||||
|
||||
expectedPatches := []byte(`[
|
||||
{ "op": "replace", "path": "/spec/affinity/nodeAffinity/a/b/0/matchExpressions/0/operator", "value":"In" }
|
||||
]`)
|
||||
p, err := applyOverlay(resource, overlay, "/")
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, string(JoinPatches(p)) == string(expectedPatches))
|
||||
}
|
||||
|
|
|
@ -7,8 +7,8 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"github.com/minio/minio/pkg/wildcard"
|
||||
apiresource "k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
// Operator is string alias that represents selection operators enum
|
||||
|
@ -32,6 +32,14 @@ const (
|
|||
const relativePrefix Operator = "./"
|
||||
const referenceSign Operator = "$()"
|
||||
|
||||
type quantity int
|
||||
|
||||
const (
|
||||
equal quantity = 0
|
||||
lessThan quantity = -1
|
||||
greaterThan quantity = 1
|
||||
)
|
||||
|
||||
// ValidateValueWithPattern validates value with operators and wildcards
|
||||
func ValidateValueWithPattern(value, pattern interface{}) bool {
|
||||
switch typedPattern := pattern.(type) {
|
||||
|
@ -187,7 +195,7 @@ func validateValueWithStringPattern(value interface{}, pattern string) bool {
|
|||
return validateString(value, str, operator)
|
||||
}
|
||||
|
||||
return validateNumberWithStr(value, number, str, operator)
|
||||
return validateNumberWithStr(value, pattern, operator)
|
||||
}
|
||||
|
||||
// Handler for string values
|
||||
|
@ -212,53 +220,50 @@ func validateString(value interface{}, pattern string, operator Operator) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// validateNumberWithStr applies wildcard to suffix and operator to numerical part
|
||||
func validateNumberWithStr(value interface{}, patternNumber, patternStr string, operator Operator) bool {
|
||||
// pattern has suffix
|
||||
if "" != patternStr {
|
||||
typedValue, ok := value.(string)
|
||||
if !ok {
|
||||
glog.Warningf("Number must have suffix: %s", patternStr)
|
||||
return false
|
||||
}
|
||||
|
||||
valueNumber, valueStr := getNumberAndStringPartsFromPattern(typedValue)
|
||||
if !wildcard.Match(patternStr, valueStr) {
|
||||
glog.Warningf("Suffix %s has not passed wildcard check: %s", valueStr, patternStr)
|
||||
return false
|
||||
}
|
||||
|
||||
return validateNumber(valueNumber, patternNumber, operator)
|
||||
// validateNumberWithStr compares quantity if pattern type is quantity
|
||||
// or a wildcard match to pattern string
|
||||
func validateNumberWithStr(value interface{}, pattern string, operator Operator) bool {
|
||||
typedValue, err := convertToString(value)
|
||||
if err != nil {
|
||||
glog.Warning(err)
|
||||
return false
|
||||
}
|
||||
|
||||
return validateNumber(value, patternNumber, operator)
|
||||
patternQuan, err := apiresource.ParseQuantity(pattern)
|
||||
// 1. nil error - quantity comparison
|
||||
if err == nil {
|
||||
valueQuan, err := apiresource.ParseQuantity(typedValue)
|
||||
if err != nil {
|
||||
glog.Warningf("Invalid quantity in resource %s, err: %v\n", typedValue, err)
|
||||
return false
|
||||
}
|
||||
|
||||
return compareQuantity(valueQuan, patternQuan, operator)
|
||||
}
|
||||
|
||||
// 2. wildcard match
|
||||
if !wildcard.Match(pattern, typedValue) {
|
||||
glog.Warningf("Value '%s' has not passed wildcard check: %s", typedValue, pattern)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// validateNumber compares two numbers with operator
|
||||
func validateNumber(value, pattern interface{}, operator Operator) bool {
|
||||
floatPattern, err := convertToFloat(pattern)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
floatValue, err := convertToFloat(value)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
func compareQuantity(value, pattern apiresource.Quantity, operator Operator) bool {
|
||||
result := value.Cmp(pattern)
|
||||
switch operator {
|
||||
case Equal:
|
||||
return floatValue == floatPattern
|
||||
return result == int(equal)
|
||||
case NotEqual:
|
||||
return floatValue != floatPattern
|
||||
return result != int(equal)
|
||||
case More:
|
||||
return floatValue > floatPattern
|
||||
case MoreEqual:
|
||||
return floatValue >= floatPattern
|
||||
return result == int(greaterThan)
|
||||
case Less:
|
||||
return floatValue < floatPattern
|
||||
return result == int(lessThan)
|
||||
case MoreEqual:
|
||||
return (result == int(equal)) || (result == int(greaterThan))
|
||||
case LessEqual:
|
||||
return floatValue <= floatPattern
|
||||
return (result == int(equal)) || (result == int(lessThan))
|
||||
}
|
||||
|
||||
return false
|
||||
|
|
|
@ -152,6 +152,10 @@ func TestValidateValueWithPattern_StringsLogicalOr(t *testing.T) {
|
|||
assert.Assert(t, ValidateValueWithPattern(value, pattern))
|
||||
}
|
||||
|
||||
func TestValidateValueWithPattern_EqualTwoFloats(t *testing.T) {
|
||||
assert.Assert(t, ValidateValueWithPattern(7.0, 7.000))
|
||||
}
|
||||
|
||||
func TestValidateValueWithNilPattern_NullPatternStringValue(t *testing.T) {
|
||||
assert.Assert(t, !validateValueWithNilPattern("value"))
|
||||
}
|
||||
|
@ -242,32 +246,36 @@ func TestGetNumberAndStringPartsFromPattern_Empty(t *testing.T) {
|
|||
assert.Equal(t, str, "")
|
||||
}
|
||||
|
||||
func TestValidateNumber_EqualTwoFloats(t *testing.T) {
|
||||
assert.Assert(t, validateNumber(7.0, 7.000, Equal))
|
||||
func TestValidateNumberWithStr_LessFloatAndInt(t *testing.T) {
|
||||
assert.Assert(t, validateNumberWithStr(7.00001, "7.000001", More))
|
||||
assert.Assert(t, validateNumberWithStr(7.00001, "7", NotEqual))
|
||||
|
||||
assert.Assert(t, validateNumberWithStr(7.0000, "7", Equal))
|
||||
assert.Assert(t, !validateNumberWithStr(6.000000001, "6", Less))
|
||||
}
|
||||
|
||||
func TestValidateNumber_LessFloatAndInt(t *testing.T) {
|
||||
assert.Assert(t, validateNumber(7, 7.00001, Less))
|
||||
assert.Assert(t, validateNumber(7, 7.00001, NotEqual))
|
||||
|
||||
assert.Assert(t, !validateNumber(7, 7.0000, NotEqual))
|
||||
assert.Assert(t, !validateNumber(6, 6.000000001, More))
|
||||
func TestValidateQuantity_InvalidQuantity(t *testing.T) {
|
||||
assert.Assert(t, !validateNumberWithStr("1024Gi", "", Equal))
|
||||
assert.Assert(t, !validateNumberWithStr("gii", "1024Gi", Equal))
|
||||
}
|
||||
|
||||
func TestValidateNumberWithStr_Equal(t *testing.T) {
|
||||
assert.Assert(t, validateNumberWithStr("1024Gi", "1024", "Gi", Equal))
|
||||
func TestValidateQuantity_Equal(t *testing.T) {
|
||||
assert.Assert(t, validateNumberWithStr("1024Gi", "1024Gi", Equal))
|
||||
assert.Assert(t, validateNumberWithStr("1024Mi", "1Gi", Equal))
|
||||
assert.Assert(t, validateNumberWithStr("0.2", "200m", Equal))
|
||||
assert.Assert(t, validateNumberWithStr("500", "500", Equal))
|
||||
assert.Assert(t, !validateNumberWithStr("2048", "1024", Equal))
|
||||
assert.Assert(t, validateNumberWithStr(1024, "1024", Equal))
|
||||
}
|
||||
|
||||
func TestValidateNumberWithStr_More(t *testing.T) {
|
||||
assert.Assert(t, !validateNumberWithStr("512Gi", "1024", "Gi", More))
|
||||
}
|
||||
|
||||
func TestValidateNumberWithStr_MoreAndWildCard(t *testing.T) {
|
||||
assert.Assert(t, validateNumberWithStr("2048Gi", "1024", "G?", More))
|
||||
}
|
||||
|
||||
func TestValidateNumberWithStr_NoStr(t *testing.T) {
|
||||
assert.Assert(t, validateNumberWithStr(2048, "1024", "", More))
|
||||
func TestValidateQuantity_Operation(t *testing.T) {
|
||||
assert.Assert(t, validateNumberWithStr("1Gi", "1000Mi", More))
|
||||
assert.Assert(t, validateNumberWithStr("1G", "1Gi", Less))
|
||||
assert.Assert(t, validateNumberWithStr("500m", "0.5", MoreEqual))
|
||||
assert.Assert(t, validateNumberWithStr("1", "500m", MoreEqual))
|
||||
assert.Assert(t, validateNumberWithStr("0.5", ".5", LessEqual))
|
||||
assert.Assert(t, validateNumberWithStr("0.2", ".5", LessEqual))
|
||||
assert.Assert(t, validateNumberWithStr("0.2", ".5", NotEqual))
|
||||
}
|
||||
|
||||
func TestGetOperatorFromStringPattern_OneChar(t *testing.T) {
|
||||
|
|
|
@ -38,6 +38,11 @@ type ResourceSpec struct {
|
|||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
//GetKey returns the key
|
||||
func (rs ResourceSpec) GetKey() string {
|
||||
return rs.Kind + "/" + rs.Namespace + "/" + rs.Name
|
||||
}
|
||||
|
||||
//PolicyStats stores statistics for the single policy application
|
||||
type PolicyStats struct {
|
||||
// time required to process the policy rules on a resource
|
||||
|
|
|
@ -318,25 +318,19 @@ func removeAnchor(key string) string {
|
|||
return key
|
||||
}
|
||||
|
||||
// convertToFloat converts string and any other value to float64
|
||||
func convertToFloat(value interface{}) (float64, error) {
|
||||
// convertToString converts value to string
|
||||
func convertToString(value interface{}) (string, error) {
|
||||
switch typed := value.(type) {
|
||||
case string:
|
||||
var err error
|
||||
floatValue, err := strconv.ParseFloat(typed, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return floatValue, nil
|
||||
return string(typed), nil
|
||||
case float64:
|
||||
return typed, nil
|
||||
return fmt.Sprintf("%f", typed), nil
|
||||
case int64:
|
||||
return float64(typed), nil
|
||||
return strconv.FormatInt(typed, 10), nil
|
||||
case int:
|
||||
return float64(typed), nil
|
||||
return strconv.Itoa(typed), nil
|
||||
default:
|
||||
return 0, fmt.Errorf("Could not convert %T to float64", value)
|
||||
return "", fmt.Errorf("Could not convert %T to string", value)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,8 +26,14 @@ type Generator struct {
|
|||
pLister kyvernolister.ClusterPolicyLister
|
||||
// returns true if the cluster policy store has been synced at least once
|
||||
pSynced cache.InformerSynced
|
||||
// queue to store event generation requests
|
||||
queue workqueue.RateLimitingInterface
|
||||
recorder record.EventRecorder
|
||||
// events generated at policy controller
|
||||
policyCtrRecorder record.EventRecorder
|
||||
// events generated at admission control
|
||||
admissionCtrRecorder record.EventRecorder
|
||||
// events generated at namespaced policy controller to process 'generate' rule
|
||||
genPolicyRecorder record.EventRecorder
|
||||
}
|
||||
|
||||
//Interface to generate event
|
||||
|
@ -39,17 +45,19 @@ type Interface interface {
|
|||
func NewEventGenerator(client *client.Client, pInformer kyvernoinformer.ClusterPolicyInformer) *Generator {
|
||||
|
||||
gen := Generator{
|
||||
client: client,
|
||||
pLister: pInformer.Lister(),
|
||||
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), eventWorkQueueName),
|
||||
pSynced: pInformer.Informer().HasSynced,
|
||||
recorder: initRecorder(client),
|
||||
}
|
||||
client: client,
|
||||
pLister: pInformer.Lister(),
|
||||
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), eventWorkQueueName),
|
||||
pSynced: pInformer.Informer().HasSynced,
|
||||
policyCtrRecorder: initRecorder(client, PolicyController),
|
||||
admissionCtrRecorder: initRecorder(client, AdmissionController),
|
||||
genPolicyRecorder: initRecorder(client, GeneratePolicyController),
|
||||
|
||||
}
|
||||
return &gen
|
||||
}
|
||||
|
||||
func initRecorder(client *client.Client) record.EventRecorder {
|
||||
func initRecorder(client *client.Client, eventSource Source) record.EventRecorder {
|
||||
// Initliaze Event Broadcaster
|
||||
err := scheme.AddToScheme(scheme.Scheme)
|
||||
if err != nil {
|
||||
|
@ -68,7 +76,7 @@ func initRecorder(client *client.Client) record.EventRecorder {
|
|||
Interface: eventInterface})
|
||||
recorder := eventBroadcaster.NewRecorder(
|
||||
scheme.Scheme,
|
||||
v1.EventSource{Component: eventSource})
|
||||
v1.EventSource{Component: eventSource.String()})
|
||||
return recorder
|
||||
}
|
||||
|
||||
|
@ -113,7 +121,7 @@ func (gen *Generator) handleErr(err error, key interface{}) {
|
|||
}
|
||||
// This controller retries if something goes wrong. After that, it stops trying.
|
||||
if gen.queue.NumRequeues(key) < workQueueRetryLimit {
|
||||
glog.Warningf("Error syncing events %v: %v", key, err)
|
||||
glog.Warningf("Error syncing events %v(re-queuing request, the resource might not have been created yet): %v", key, err)
|
||||
// Re-enqueue the key rate limited. Based on the rate limiter on the
|
||||
// queue and the re-enqueue history, the key will be processed later again.
|
||||
gen.queue.AddRateLimited(key)
|
||||
|
@ -159,47 +167,45 @@ func (gen *Generator) syncHandler(key Info) error {
|
|||
//TODO: policy is clustered resource so wont need namespace
|
||||
robj, err = gen.pLister.Get(key.Name)
|
||||
if err != nil {
|
||||
glog.Errorf("Error creating event: unable to get policy %s, will retry ", key.Name)
|
||||
glog.V(4).Infof("Error creating event: unable to get policy %s, will retry ", key.Name)
|
||||
return err
|
||||
}
|
||||
default:
|
||||
robj, err = gen.client.GetResource(key.Kind, key.Namespace, key.Name)
|
||||
if err != nil {
|
||||
glog.Errorf("Error creating event: unable to get resource %s, %s, will retry ", key.Kind, key.Namespace+"/"+key.Name)
|
||||
glog.V(4).Infof("Error creating event: unable to get resource %s, %s, will retry ", key.Kind, key.Namespace+"/"+key.Name)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// set the event type based on reason
|
||||
eventType := v1.EventTypeWarning
|
||||
if key.Reason == PolicyApplied.String() {
|
||||
gen.recorder.Event(robj, v1.EventTypeNormal, key.Reason, key.Message)
|
||||
} else {
|
||||
gen.recorder.Event(robj, v1.EventTypeWarning, key.Reason, key.Message)
|
||||
eventType = v1.EventTypeNormal
|
||||
}
|
||||
|
||||
// based on the source of event generation, use different event recorders
|
||||
switch key.Source {
|
||||
case AdmissionController:
|
||||
gen.admissionCtrRecorder.Event(robj, eventType, key.Reason, key.Message)
|
||||
case PolicyController:
|
||||
gen.policyCtrRecorder.Event(robj, eventType, key.Reason, key.Message)
|
||||
case GeneratePolicyController:
|
||||
gen.genPolicyRecorder.Event(robj, eventType, key.Reason, key.Message)
|
||||
default:
|
||||
glog.Info("info.source not defined for the event generator request")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//TODO: check if we need this ?
|
||||
//NewEvent returns a new event
|
||||
func NewEvent(rkind string, rnamespace string, rname string, reason Reason, message MsgKey, args ...interface{}) *Info {
|
||||
msgText, err := getEventMsg(message, args...)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
}
|
||||
return &Info{
|
||||
Kind: rkind,
|
||||
Name: rname,
|
||||
Namespace: rnamespace,
|
||||
Reason: reason.String(),
|
||||
Message: msgText,
|
||||
}
|
||||
}
|
||||
|
||||
func NewEventNew(
|
||||
//NewEvent builds a event creation request
|
||||
func NewEvent(
|
||||
rkind,
|
||||
rapiVersion,
|
||||
rnamespace,
|
||||
rname,
|
||||
reason string,
|
||||
source Source,
|
||||
message MsgKey,
|
||||
args ...interface{}) Info {
|
||||
msgText, err := getEventMsg(message, args...)
|
||||
|
@ -211,6 +217,7 @@ func NewEventNew(
|
|||
Name: rname,
|
||||
Namespace: rnamespace,
|
||||
Reason: reason,
|
||||
Source: source,
|
||||
Message: msgText,
|
||||
}
|
||||
}
|
||||
|
|
21
pkg/event/source.go
Normal file
21
pkg/event/source.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package event
|
||||
|
||||
//Source of event generation
|
||||
type Source int
|
||||
|
||||
const (
|
||||
// AdmissionController : event generated in admission-control webhook
|
||||
AdmissionController Source = iota
|
||||
// PolicyController : event generated in policy-controller
|
||||
PolicyController
|
||||
// GeneratePolicyController : event generated in generate policyController
|
||||
GeneratePolicyController
|
||||
)
|
||||
|
||||
func (s Source) String() string {
|
||||
return [...]string{
|
||||
"admission-controller",
|
||||
"policy-controller",
|
||||
"generate-policy-controller",
|
||||
}[s]
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
package event
|
||||
|
||||
const eventSource = "policy-controller"
|
||||
|
||||
const eventWorkQueueName = "policy-controller-events"
|
||||
const eventWorkQueueName = "kyverno-events"
|
||||
|
||||
const eventWorkerThreadCount = 1
|
||||
|
||||
|
@ -15,4 +13,5 @@ type Info struct {
|
|||
Namespace string
|
||||
Reason string
|
||||
Message string
|
||||
Source Source
|
||||
}
|
||||
|
|
|
@ -89,6 +89,7 @@ func generateEventsPerEr(er engine.EngineResponse) []event.Info {
|
|||
e.Namespace = "" // event generate on namespace resource
|
||||
e.Name = er.PolicyResponse.Resource.Name
|
||||
e.Reason = "Failure"
|
||||
e.Source = event.GeneratePolicyController
|
||||
e.Message = fmt.Sprintf("policy '%s' (%s) rule '%s' not satisfied. %v", er.PolicyResponse.Policy, rule.Type, rule.Name, rule.Message)
|
||||
eventInfos = append(eventInfos, e)
|
||||
}
|
||||
|
@ -102,6 +103,7 @@ func generateEventsPerEr(er engine.EngineResponse) []event.Info {
|
|||
e.Namespace = ""
|
||||
e.Name = er.PolicyResponse.Policy
|
||||
e.Reason = "Failure"
|
||||
e.Source = event.GeneratePolicyController
|
||||
e.Message = fmt.Sprintf("policy '%s' rules '%v' on resource '%s/%s/%s' not stasified", er.PolicyResponse.Policy, er.GetFailedRules(), er.PolicyResponse.Resource.Kind, er.PolicyResponse.Resource.Namespace, er.PolicyResponse.Resource.Name)
|
||||
return eventInfos
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package policy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
|
@ -108,7 +110,6 @@ func getFailedOverallRuleInfo(resource unstructured.Unstructured, engineResponse
|
|||
if len(rule.Patches) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
patch, err := jsonpatch.DecodePatch(utils.JoinPatches(rule.Patches))
|
||||
if err != nil {
|
||||
glog.V(4).Infof("unable to decode patch %s: %v", rule.Patches, err)
|
||||
|
@ -121,12 +122,32 @@ func getFailedOverallRuleInfo(resource unstructured.Unstructured, engineResponse
|
|||
glog.V(4).Infof("unable to apply patch %s: %v", rule.Patches, err)
|
||||
return engine.EngineResponse{}, err
|
||||
}
|
||||
|
||||
if !jsonpatch.Equal(patchedResource, rawResource) {
|
||||
glog.V(4).Infof("policy %s rule %s condition not satisifed by existing resource", engineResponse.PolicyResponse.Policy, rule.Name)
|
||||
engineResponse.PolicyResponse.Rules[index].Success = false
|
||||
engineResponse.PolicyResponse.Rules[index].Message = fmt.Sprintf("rule not satisfied by existing resource.")
|
||||
engineResponse.PolicyResponse.Rules[index].Message = fmt.Sprintf("mutation json patches not found at resource path %s", extractPatchPath(rule.Patches))
|
||||
}
|
||||
}
|
||||
return engineResponse, nil
|
||||
}
|
||||
|
||||
type jsonPatch struct {
|
||||
Op string `json:"op"`
|
||||
Path string `json:"path"`
|
||||
Value interface{} `json:"value"`
|
||||
}
|
||||
|
||||
func extractPatchPath(patches [][]byte) string {
|
||||
var resultPath []string
|
||||
// extract the patch path and value
|
||||
for _, patch := range patches {
|
||||
glog.V(4).Infof("expected json patch not found in resource: %s", string(patch))
|
||||
var data jsonPatch
|
||||
if err := json.Unmarshal(patch, &data); err != nil {
|
||||
glog.V(4).Infof("Failed to decode the generated patch %v: Error %v", string(patch), err)
|
||||
continue
|
||||
}
|
||||
resultPath = append(resultPath, data.Path)
|
||||
}
|
||||
return strings.Join(resultPath, ";")
|
||||
}
|
||||
|
|
|
@ -109,6 +109,7 @@ func generateEventsPerEr(er engine.EngineResponse) []event.Info {
|
|||
e.Namespace = er.PolicyResponse.Resource.Namespace
|
||||
e.Name = er.PolicyResponse.Resource.Name
|
||||
e.Reason = event.PolicyViolation.String()
|
||||
e.Source = event.PolicyController
|
||||
e.Message = fmt.Sprintf("policy '%s' (%s) rule '%s' not satisfied. %v", er.PolicyResponse.Policy, rule.Type, rule.Name, rule.Message)
|
||||
eventInfos = append(eventInfos, e)
|
||||
}
|
||||
|
@ -123,6 +124,7 @@ func generateEventsPerEr(er engine.EngineResponse) []event.Info {
|
|||
e.Namespace = ""
|
||||
e.Name = er.PolicyResponse.Policy
|
||||
e.Reason = event.PolicyViolation.String()
|
||||
e.Source = event.PolicyController
|
||||
e.Message = fmt.Sprintf("policy '%s' rules '%v' not satisfied on resource '%s/%s/%s'", er.PolicyResponse.Policy, er.GetFailedRules(), er.PolicyResponse.Resource.Kind, er.PolicyResponse.Resource.Namespace, er.PolicyResponse.Resource.Name)
|
||||
eventInfos = append(eventInfos, e)
|
||||
return eventInfos
|
||||
|
|
|
@ -32,12 +32,13 @@ func generateEvents(engineResponses []engine.EngineResponse, onUpdate bool) []ev
|
|||
var e event.Info
|
||||
// UPDATE
|
||||
// event on resource
|
||||
e = event.NewEventNew(
|
||||
e = event.NewEvent(
|
||||
er.PolicyResponse.Resource.Kind,
|
||||
er.PolicyResponse.Resource.APIVersion,
|
||||
er.PolicyResponse.Resource.Namespace,
|
||||
er.PolicyResponse.Resource.Name,
|
||||
reason.String(),
|
||||
event.AdmissionController,
|
||||
event.FPolicyApplyBlockUpdate,
|
||||
filedRulesStr,
|
||||
er.PolicyResponse.Policy,
|
||||
|
@ -46,14 +47,15 @@ func generateEvents(engineResponses []engine.EngineResponse, onUpdate bool) []ev
|
|||
events = append(events, e)
|
||||
|
||||
// event on policy
|
||||
e = event.NewEventNew(
|
||||
e = event.NewEvent(
|
||||
"ClusterPolicy",
|
||||
kyverno.SchemeGroupVersion.String(),
|
||||
"",
|
||||
er.PolicyResponse.Policy,
|
||||
reason.String(),
|
||||
event.AdmissionController,
|
||||
event.FPolicyBlockResourceUpdate,
|
||||
er.PolicyResponse.Resource.Namespace+"/"+er.PolicyResponse.Resource.Name,
|
||||
er.PolicyResponse.Resource.GetKey(),
|
||||
filedRulesStr,
|
||||
)
|
||||
glog.V(4).Infof("UPDATE event on policy %s", er.PolicyResponse.Policy)
|
||||
|
@ -62,14 +64,15 @@ func generateEvents(engineResponses []engine.EngineResponse, onUpdate bool) []ev
|
|||
} else {
|
||||
// CREATE
|
||||
// event on policy
|
||||
e := event.NewEventNew(
|
||||
e := event.NewEvent(
|
||||
"ClusterPolicy",
|
||||
kyverno.SchemeGroupVersion.String(),
|
||||
"",
|
||||
er.PolicyResponse.Policy,
|
||||
event.RequestBlocked.String(),
|
||||
reason.String(),
|
||||
event.AdmissionController,
|
||||
event.FPolicyApplyBlockCreate,
|
||||
er.PolicyResponse.Resource.Namespace+"/"+er.PolicyResponse.Resource.Name,
|
||||
er.PolicyResponse.Resource.GetKey(),
|
||||
filedRulesStr,
|
||||
)
|
||||
glog.V(4).Infof("CREATE event on policy %s", er.PolicyResponse.Policy)
|
||||
|
@ -85,12 +88,13 @@ func generateEvents(engineResponses []engine.EngineResponse, onUpdate bool) []ev
|
|||
successRules := er.GetSuccessRules()
|
||||
successRulesStr := strings.Join(successRules, ";")
|
||||
// event on resource
|
||||
e := event.NewEventNew(
|
||||
e := event.NewEvent(
|
||||
er.PolicyResponse.Resource.Kind,
|
||||
er.PolicyResponse.Resource.APIVersion,
|
||||
er.PolicyResponse.Resource.Namespace,
|
||||
er.PolicyResponse.Resource.Name,
|
||||
event.PolicyApplied.String(),
|
||||
event.AdmissionController,
|
||||
event.SRulesApply,
|
||||
successRulesStr,
|
||||
er.PolicyResponse.Policy,
|
||||
|
|
Loading…
Add table
Reference in a new issue