1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-04-18 02:06:52 +00:00

Synchronize data for generated resources ()

* Generate request added fro update resource

* synchronize flag added

* documentation added for keeping resource synchronized

Signed-off-by: Yuvraj <yuvraj.yad001@gmail.com>
This commit is contained in:
Yuvraj 2020-06-22 18:49:43 -07:00 committed by GitHub
parent 7cb49b6b34
commit 01724d63cf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 101 additions and 46 deletions

View file

@ -144,6 +144,7 @@ spec:
name: zk-kafka-address
# generate the resource in the new namespace
namespace: "{{request.object.metadata.name}}"
synchronize : true
data:
kind: ConfigMap
data:

View file

@ -105,6 +105,8 @@ spec:
type: string
namespace:
type: string
synchronize:
type: boolean
required:
- kind
- name

View file

@ -244,6 +244,8 @@ spec:
type: string
namespace:
type: string
synchronize:
type: boolean
clone:
type: object
required:

View file

@ -110,6 +110,8 @@ spec:
type: string
namespace:
type: string
synchronize:
type: boolean
required:
- kind
- name

View file

@ -110,6 +110,8 @@ spec:
type: string
namespace:
type: string
synchronize:
type: boolean
required:
- kind
- name

View file

@ -6,7 +6,8 @@ The ```generate``` rule can used to create additional resources when a new resou
The `generate` rule supports `match` and `exclude` blocks, like other rules. Hence, the trigger for applying this rule can be the creation of any resource and its possible to match or exclude API requests based on subjects, roles, etc.
Currently, the generate rule only triggers during an API request and does not support [background processing](/documentation/writing-policies-background.md). Keeping resources synchhronized is planned for a future release (see https://github.com/nirmata/kyverno/issues/560).
Currently, the generate rule only triggers during an API request and does not support [background processing](/documentation/writing-policies-background.md). To keep resources synchronized across changes, you can use `synchronize : true`. Synchronize is disabled for the pre-existing generate policy that means User has to manually add `synchronize: true` for pre-existing generate policy
## Example 1
@ -26,6 +27,7 @@ spec:
kind: ConfigMap # Kind of resource
name: default-config # Name of the new Resource
namespace: "{{request.object.metadata.name}}" # namespace that triggers this rule
synchronize : true
clone:
namespace: default
name: config-template

View file

@ -223,8 +223,9 @@ type Deny struct {
// Generation describes which resources will be created when other resource is created
type Generation struct {
ResourceSpec
Data interface{} `json:"data,omitempty"`
Clone CloneFrom `json:"clone,omitempty"`
Synchronize bool `json:"synchronize,omitempty"`
Data interface{} `json:"data,omitempty"`
Clone CloneFrom `json:"clone,omitempty"`
}
// CloneFrom - location of the resource

View file

@ -3,6 +3,7 @@ package generate
import (
"encoding/json"
"fmt"
"reflect"
"time"
"github.com/go-logr/logr"
@ -240,6 +241,7 @@ func applyRule(log logr.Logger, client *dclient.Client, rule kyverno.Rule, resou
// policy was generated after the resource
// we do not create new resource
return noGenResource, err
}
// build the resource template
@ -269,14 +271,19 @@ func applyRule(log logr.Logger, client *dclient.Client, rule kyverno.Rule, resou
logger.V(4).Info("created new resource")
} else if mode == Update {
logger.V(4).Info("updating existing resource")
// Update the resource
_, err := client.UpdateResource(genKind, genNamespace, newResource, false)
if err != nil {
// Failed to update resource
return noGenResource, err
if rule.Generation.Synchronize {
logger.V(4).Info("updating existing resource")
// Update the resource
_, err := client.UpdateResource(genKind, genNamespace, newResource, false)
if err != nil {
// Failed to update resource
return noGenResource, err
}
logger.V(4).Info("updated new resource")
} else {
logger.V(4).Info("Synchronize resource is disabled")
}
logger.V(4).Info("updated new resource")
}
return newGenResource, nil
@ -306,19 +313,6 @@ func manageData(log logr.Logger, kind, namespace, name string, data map[string]i
}
func manageClone(log logr.Logger, 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) {
log.Error(err, "reference/clone resource is not found", "genKind", kind, "genNamespace", namespace, "genName", name)
//something wrong while fetching resource
return nil, Skip, err
}
newRNs, _, err := unstructured.NestedString(clone, "namespace")
if err != nil {
return nil, Skip, err
@ -332,11 +326,34 @@ func manageClone(log logr.Logger, kind, namespace, name string, clone map[string
// 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, fmt.Errorf("reference clone resource %s/%s/%s not found. %v", kind, newRNs, newRName, err)
}
// check if resource to be generated exists
newResource, err := client.GetResource(kind, namespace, name)
if err == nil {
obj.SetUID(newResource.GetUID())
obj.SetSelfLink(newResource.GetSelfLink())
obj.SetCreationTimestamp(newResource.GetCreationTimestamp())
obj.SetManagedFields(newResource.GetManagedFields())
obj.SetResourceVersion(newResource.GetResourceVersion())
if reflect.DeepEqual(obj, newResource) {
return nil, Skip, nil
}
return obj.UnstructuredContent(), Update, nil
}
//TODO: check this
if !apierrors.IsNotFound(err) {
log.Error(err, "reference/clone resource is not found", "genKind", kind, "genNamespace", namespace, "genName", name)
//something wrong while fetching resource
return nil, Skip, err
}
// create the resource based on the reference clone
return obj.UnstructuredContent(), Create, nil

View file

@ -2,6 +2,7 @@ package generate
import (
"fmt"
"k8s.io/api/admission/v1beta1"
"time"
backoff "github.com/cenkalti/backoff"
@ -15,13 +16,18 @@ import (
//GenerateRequests provides interface to manage generate requests
type GenerateRequests interface {
Create(gr kyverno.GenerateRequestSpec) error
Apply(gr kyverno.GenerateRequestSpec,action v1beta1.Operation) error
}
type GeneratorChannel struct {
spec kyverno.GenerateRequestSpec
action v1beta1.Operation
}
// Generator defines the implmentation to mange generate request resource
type Generator struct {
// channel to receive request
ch chan kyverno.GenerateRequestSpec
ch chan GeneratorChannel
client *kyvernoclient.Clientset
stopCh <-chan struct{}
log logr.Logger
@ -30,7 +36,7 @@ type Generator struct {
//NewGenerator returns a new instance of Generate-Request resource generator
func NewGenerator(client *kyvernoclient.Clientset, stopCh <-chan struct{}, log logr.Logger) *Generator {
gen := &Generator{
ch: make(chan kyverno.GenerateRequestSpec, 1000),
ch: make(chan GeneratorChannel, 1000),
client: client,
stopCh: stopCh,
log: log,
@ -39,12 +45,16 @@ func NewGenerator(client *kyvernoclient.Clientset, stopCh <-chan struct{}, log l
}
//Create to create generate request resoruce (blocking call if channel is full)
func (g *Generator) Create(gr kyverno.GenerateRequestSpec) error {
func (g *Generator) Apply(gr kyverno.GenerateRequestSpec,action v1beta1.Operation) error {
logger := g.log
logger.V(4).Info("creating Generate Request", "request", gr)
// Send to channel
message := GeneratorChannel{
action: action,
spec : gr,
}
select {
case g.ch <- gr:
case g.ch <- message:
return nil
case <-g.stopCh:
logger.Info("shutting down channel")
@ -61,24 +71,24 @@ func (g *Generator) Run(workers int) {
logger.V(4).Info("shutting down")
}()
for i := 0; i < workers; i++ {
go wait.Until(g.process, constant.GenerateControllerResync, g.stopCh)
go wait.Until(g.processApply, constant.GenerateControllerResync, g.stopCh)
}
<-g.stopCh
}
func (g *Generator) process() {
func (g *Generator) processApply() {
logger := g.log
for r := range g.ch {
logger.V(4).Info("recieved generate request", "request", r)
if err := g.generate(r); err != nil {
if err := g.generate(r.spec,r.action); err != nil {
logger.Error(err, "failed to generate request CR")
}
}
}
func (g *Generator) generate(grSpec kyverno.GenerateRequestSpec) error {
// create a generate request
if err := retryCreateResource(g.client, grSpec, g.log); err != nil {
func (g *Generator) generate(grSpec kyverno.GenerateRequestSpec,action v1beta1.Operation) error {
// create/update a generate request
if err := retryApplyResource(g.client, grSpec, g.log,action); err != nil {
return err
}
return nil
@ -87,15 +97,17 @@ func (g *Generator) generate(grSpec kyverno.GenerateRequestSpec) error {
// -> receiving channel to take requests to create request
// use worker pattern to read and create the CR resource
func retryCreateResource(client *kyvernoclient.Clientset,
func retryApplyResource(client *kyvernoclient.Clientset,
grSpec kyverno.GenerateRequestSpec,
log logr.Logger,
action v1beta1.Operation,
) error {
var i int
var err error
createResource := func() error {
applyResource := func() error {
gr := kyverno.GenerateRequest{
Spec: grSpec,
Spec: grSpec,
}
gr.SetGenerateName("gr-")
gr.SetNamespace("kyverno")
@ -103,11 +115,21 @@ func retryCreateResource(client *kyvernoclient.Clientset,
// TODO: status is not updated
// gr.Status.State = kyverno.Pending
// generate requests created in kyverno namespace
_, err = client.KyvernoV1().GenerateRequests("kyverno").Create(&gr)
log.V(4).Info("retrying create generate request CR", "retryCount", i, "name", gr.GetGenerateName(), "namespace", gr.GetNamespace())
if action == v1beta1.Create {
_, err = client.KyvernoV1().GenerateRequests("kyverno").Create(&gr)
}
if action == v1beta1.Update {
gr.SetLabels(map[string]string{
"resources-update": "true",
})
_, err = client.KyvernoV1().GenerateRequests("kyverno").Update(&gr)
}
log.V(4).Info("retrying update generate request CR", "retryCount", i, "name", gr.GetGenerateName(), "namespace", gr.GetNamespace())
i++
return err
}
exbackoff := &backoff.ExponentialBackOff{
InitialInterval: 500 * time.Millisecond,
RandomizationFactor: 0.5,
@ -118,7 +140,9 @@ func retryCreateResource(client *kyvernoclient.Clientset,
}
exbackoff.Reset()
err = backoff.Retry(createResource, exbackoff)
err = backoff.Retry(applyResource, exbackoff)
if err != nil {
return err
}

View file

@ -50,10 +50,12 @@ func (ws *WebhookServer) HandleGenerate(request *v1beta1.AdmissionRequest, polic
}
}
// Adds Generate Request to a channel(queue size 1000) to generators
if err := createGenerateRequest(ws.grGenerator, userRequestInfo, engineResponses...); err != nil {
if err := applyGenerateRequest(ws.grGenerator, userRequestInfo,request.Operation, engineResponses...); err != nil {
//TODO: send appropriate error
return false, "Kyverno blocked: failed to create Generate Requests"
}
// Generate Stats wont be used here, as we delegate the generate rule
// - Filter policies that apply on this resource
// - - build CR context(userInfo+roles+clusterRoles)
@ -65,9 +67,9 @@ func (ws *WebhookServer) HandleGenerate(request *v1beta1.AdmissionRequest, polic
return true, ""
}
func createGenerateRequest(gnGenerator generate.GenerateRequests, userRequestInfo kyverno.RequestInfo, engineResponses ...response.EngineResponse) error {
func applyGenerateRequest(gnGenerator generate.GenerateRequests, userRequestInfo kyverno.RequestInfo,action v1beta1.Operation, engineResponses ...response.EngineResponse) error {
for _, er := range engineResponses {
if err := gnGenerator.Create(transform(userRequestInfo, er)); err != nil {
if err := gnGenerator.Apply(transform(userRequestInfo, er),action); err != nil {
return err
}
}

View file

@ -156,7 +156,6 @@ func NewWebhookServer(
// Fail this request if Kubernetes should restart this instance
mux.HandlerFunc("GET", config.LivenessServicePath, func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
w.WriteHeader(http.StatusOK)
})
@ -226,6 +225,7 @@ func writeResponse(rw http.ResponseWriter, admissionReview *v1beta1.AdmissionRev
}
func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
if excludeKyvernoResources(request.Kind.Kind) {
return &v1beta1.AdmissionResponse{
Allowed: true,
@ -331,10 +331,10 @@ func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1
}
// GENERATE
// Only applied during resource creation
// Only applied during resource creation and update
// Success -> Generate Request CR created successsfully
// Failed -> Failed to create Generate Request CR
if request.Operation == v1beta1.Create {
if request.Operation == v1beta1.Create || request.Operation == v1beta1.Update {
ok, msg := ws.HandleGenerate(request, policies, ctx, userRequestInfo)
if !ok {
logger.Info("admission request denied")