mirror of
https://github.com/kyverno/kyverno.git
synced 2025-04-18 02:06:52 +00:00
Synchronize data for generated resources (#933)
* 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:
parent
7cb49b6b34
commit
01724d63cf
11 changed files with 101 additions and 46 deletions
|
@ -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:
|
||||
|
|
|
@ -105,6 +105,8 @@ spec:
|
|||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
synchronize:
|
||||
type: boolean
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
|
|
|
@ -244,6 +244,8 @@ spec:
|
|||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
synchronize:
|
||||
type: boolean
|
||||
clone:
|
||||
type: object
|
||||
required:
|
||||
|
|
|
@ -110,6 +110,8 @@ spec:
|
|||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
synchronize:
|
||||
type: boolean
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
|
|
|
@ -110,6 +110,8 @@ spec:
|
|||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
synchronize:
|
||||
type: boolean
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Add table
Reference in a new issue