mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-31 03:45:17 +00:00
Merge pull request #1391 from kyverno/1390_generate_no_data
allow generate with no data/status
This commit is contained in:
commit
6e25ef8af3
8 changed files with 976 additions and 3095 deletions
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -301,23 +301,27 @@ type Generation struct {
|
|||
ResourceSpec `json:",omitempty" yaml:",omitempty"`
|
||||
|
||||
// Synchronize controls if generated resources should be kept in-sync with their source resource.
|
||||
// If Synchronize is set to "true" changes to generated resources will be overwritten with resource
|
||||
// data from Data or the resource specified in the Clone declaration.
|
||||
// Optional. Defaults to "false" if not specified.
|
||||
// +optional
|
||||
Synchronize bool `json:"synchronize,omitempty" yaml:"synchronize,omitempty"`
|
||||
|
||||
// Data provides the resource manifest to used to populate each generated resource.
|
||||
// Exactly one of Data or Clone must be specified.
|
||||
// Data provides the resource declaration used to populate each generated resource.
|
||||
// At most one of Data or Clone must be specified. If neither are provided, the generated
|
||||
// resource will be created with default data only.
|
||||
// +kubebuilder:pruning:PreserveUnknownFields
|
||||
// +optional
|
||||
Data apiextensions.JSON `json:"data,omitempty" yaml:"data,omitempty"`
|
||||
|
||||
// Clone specified the source resource used to populate each generated resource.
|
||||
// Exactly one of Data or Clone must be specified.
|
||||
// Clone specifies the source resource used to populate each generated resource.
|
||||
// At most one of Data or Clone can be specified. If neither are provided, the generated
|
||||
// resource will be created with default data only.
|
||||
// +optional
|
||||
Clone CloneFrom `json:"clone,omitempty" yaml:"clone,omitempty"`
|
||||
}
|
||||
|
||||
// CloneFrom provides the location of the source resource used to generate additional resources.
|
||||
// CloneFrom provides the location of the source resource used to generate target resources.
|
||||
// The resource kind is derived from the match criteria.
|
||||
type CloneFrom struct {
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/engine"
|
||||
"github.com/kyverno/kyverno/pkg/engine/context"
|
||||
"github.com/kyverno/kyverno/pkg/engine/utils"
|
||||
"github.com/kyverno/kyverno/pkg/engine/validate"
|
||||
"github.com/kyverno/kyverno/pkg/engine/variables"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
@ -301,6 +300,7 @@ func applyRule(log logr.Logger, client *dclient.Client, rule kyverno.Rule, resou
|
|||
if err != nil {
|
||||
return noGenResource, err
|
||||
}
|
||||
|
||||
// Variable substitutions
|
||||
// format : {{<variable_name}}
|
||||
// - if there is variables that are not defined the context -> results in error and rule is not applied
|
||||
|
@ -316,6 +316,8 @@ func applyRule(log logr.Logger, client *dclient.Client, rule kyverno.Rule, resou
|
|||
return noGenResource, err
|
||||
}
|
||||
|
||||
logger := log.WithValues("genKind", genKind, "genAPIVersion", genAPIVersion, "genNamespace", genNamespace, "genName", genName)
|
||||
|
||||
// Resource to be generated
|
||||
newGenResource := kyverno.ResourceSpec{
|
||||
APIVersion: genAPIVersion,
|
||||
|
@ -326,26 +328,32 @@ func applyRule(log logr.Logger, client *dclient.Client, rule kyverno.Rule, resou
|
|||
|
||||
genData, _, err := unstructured.NestedMap(genUnst.Object, "data")
|
||||
if err != nil {
|
||||
return noGenResource, err
|
||||
return noGenResource, fmt.Errorf("failed to read `data`: %v", err.Error())
|
||||
}
|
||||
|
||||
genCopy, _, err := unstructured.NestedMap(genUnst.Object, "clone")
|
||||
genClone, _, err := unstructured.NestedMap(genUnst.Object, "clone")
|
||||
if err != nil {
|
||||
return noGenResource, err
|
||||
return noGenResource, fmt.Errorf("failed to read `clone`: %v", err.Error())
|
||||
}
|
||||
|
||||
if genData != nil {
|
||||
rdata, mode, err = manageData(log, genAPIVersion, genKind, genNamespace, genName, genData, client, resource)
|
||||
if genClone != nil && len(genClone) != 0 {
|
||||
rdata, mode, err = manageClone(logger, genAPIVersion, genKind, genNamespace, genName, genClone, client)
|
||||
} else {
|
||||
rdata, mode, err = manageClone(log, genAPIVersion, genKind, genNamespace, genName, genCopy, client, resource)
|
||||
rdata, mode, err = manageData(logger, genAPIVersion, genKind, genNamespace, genName, genData, client)
|
||||
}
|
||||
|
||||
logger := log.WithValues("genKind", genKind, "genAPIVersion", genAPIVersion, "genNamespace", genNamespace, "genName", genName)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to generate resource", "data", rdata, "mode", mode)
|
||||
return newGenResource, err
|
||||
}
|
||||
|
||||
if rdata == nil {
|
||||
// existing resource contains the configuration
|
||||
logger.V(2).Info("applying generate rule", "data", rdata, "mode", mode)
|
||||
|
||||
if rdata == nil && mode == Update {
|
||||
logger.V(4).Info("no changes required for target resource")
|
||||
return newGenResource, nil
|
||||
}
|
||||
|
||||
if processExisting {
|
||||
return noGenResource, nil
|
||||
}
|
||||
|
@ -375,6 +383,7 @@ func applyRule(log logr.Logger, client *dclient.Client, rule kyverno.Rule, resou
|
|||
} else {
|
||||
label["policy.kyverno.io/synchronize"] = "disable"
|
||||
}
|
||||
|
||||
// Reset resource version
|
||||
newResource.SetResourceVersion("")
|
||||
// Create the resource
|
||||
|
@ -383,7 +392,7 @@ func applyRule(log logr.Logger, client *dclient.Client, rule kyverno.Rule, resou
|
|||
return noGenResource, err
|
||||
}
|
||||
|
||||
logger.V(2).Info("created resource")
|
||||
logger.V(2).Info("generated target resource")
|
||||
|
||||
} else if mode == Update {
|
||||
label := newResource.GetLabels()
|
||||
|
@ -401,50 +410,56 @@ func applyRule(log logr.Logger, client *dclient.Client, rule kyverno.Rule, resou
|
|||
logger.Error(err, "updating existing resource")
|
||||
return noGenResource, err
|
||||
}
|
||||
logger.V(2).Info("updated generated resource")
|
||||
logger.V(2).Info("updated target resource")
|
||||
}
|
||||
}
|
||||
|
||||
return newGenResource, nil
|
||||
}
|
||||
|
||||
func manageData(log logr.Logger, apiVersion, 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
|
||||
func manageData(log logr.Logger, apiVersion, kind, namespace, name string, data map[string]interface{}, client *dclient.Client) (map[string]interface{}, ResourceMode, error) {
|
||||
obj, err := client.GetResource(apiVersion, kind, namespace, name)
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
log.V(3).Info("resource does not exist, will try to create", "genKind", kind, "genAPIVersion", apiVersion, "genNamespace", namespace, "genName", name)
|
||||
return data, Create, nil
|
||||
}
|
||||
//something wrong while fetching resource
|
||||
// client-errors
|
||||
|
||||
log.Error(err, "failed to get resource")
|
||||
return nil, Skip, err
|
||||
}
|
||||
|
||||
log.Info("found target resource", "resource", obj)
|
||||
if data == nil {
|
||||
log.Info("data is nil - skipping update", "resource", obj)
|
||||
return nil, Skip, nil
|
||||
}
|
||||
|
||||
updateObj := &unstructured.Unstructured{}
|
||||
updateObj.SetUnstructuredContent(data)
|
||||
updateObj.SetResourceVersion(obj.GetResourceVersion())
|
||||
return updateObj.UnstructuredContent(), Update, nil
|
||||
}
|
||||
|
||||
func manageClone(log logr.Logger, apiVersion, kind, namespace, name string, clone map[string]interface{}, client *dclient.Client, resource unstructured.Unstructured) (map[string]interface{}, ResourceMode, error) {
|
||||
newRNs, _, err := unstructured.NestedString(clone, "namespace")
|
||||
func manageClone(log logr.Logger, apiVersion, kind, namespace, name string, clone map[string]interface{}, client *dclient.Client) (map[string]interface{}, ResourceMode, error) {
|
||||
rNamespace, _, err := unstructured.NestedString(clone, "namespace")
|
||||
if err != nil {
|
||||
return nil, Skip, err
|
||||
}
|
||||
newRName, _, err := unstructured.NestedString(clone, "name")
|
||||
if err != nil {
|
||||
return nil, Skip, err
|
||||
return nil, Skip, fmt.Errorf("failed to find source namespace: %v", 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
|
||||
rName, _, err := unstructured.NestedString(clone, "name")
|
||||
if err != nil {
|
||||
return nil, Skip, fmt.Errorf("failed to find source name: %v", err)
|
||||
}
|
||||
|
||||
if rNamespace == namespace && rName == name {
|
||||
log.V(4).Info("skip resource self-clone")
|
||||
return nil, Skip, nil
|
||||
}
|
||||
|
||||
// check if the resource as reference in clone exists?
|
||||
obj, err := client.GetResource(apiVersion, kind, newRNs, newRName)
|
||||
obj, err := client.GetResource(apiVersion, kind, rNamespace, rName)
|
||||
if err != nil {
|
||||
return nil, Skip, fmt.Errorf("reference clone resource %s/%s/%s/%s not found. %v", apiVersion, kind, newRNs, newRName, err)
|
||||
return nil, Skip, fmt.Errorf("source resource %s %s/%s/%s not found. %v", apiVersion, kind, rNamespace, rName, err)
|
||||
}
|
||||
|
||||
// check if resource to be generated exists
|
||||
|
@ -478,15 +493,6 @@ const (
|
|||
Update = "UPDATE"
|
||||
)
|
||||
|
||||
func checkResource(log logr.Logger, newResourceSpec interface{}, resource *unstructured.Unstructured) error {
|
||||
// check if the resource spec if a subset of the resource
|
||||
if path, err := validate.ValidateResourceWithPattern(log, resource.Object, newResourceSpec); err != nil {
|
||||
log.Error(err, "Failed to match the resource ", "path", path)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getUnstrRule(rule *kyverno.Generation) (*unstructured.Unstructured, error) {
|
||||
ruleData, err := json.Marshal(rule)
|
||||
if err != nil {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package generate
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
|
@ -263,15 +264,19 @@ func (c *Controller) Run(workers int, stopCh <-chan struct{}) {
|
|||
logger.Info("failed to sync informer cache")
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < workers; i++ {
|
||||
go wait.Until(c.worker, constant.GenerateControllerResync, stopCh)
|
||||
}
|
||||
|
||||
<-stopCh
|
||||
}
|
||||
|
||||
// worker runs a worker thread that just dequeues items, processes them, and marks them done.
|
||||
// It enforces that the syncHandler is never invoked concurrently with the same key.
|
||||
func (c *Controller) worker() {
|
||||
log.Log.Info("starting new worker...")
|
||||
|
||||
for c.processNextWorkItem() {
|
||||
}
|
||||
}
|
||||
|
@ -281,10 +286,10 @@ func (c *Controller) processNextWorkItem() bool {
|
|||
if quit {
|
||||
return false
|
||||
}
|
||||
|
||||
defer c.queue.Done(key)
|
||||
err := c.syncGenerateRequest(key.(string))
|
||||
c.handleErr(err, key)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
@ -36,12 +36,10 @@ func NewGenerateFactory(client *dclient.Client, rule kyverno.Generation, log log
|
|||
//Validate validates the 'generate' rule
|
||||
func (g *Generate) Validate() (string, error) {
|
||||
rule := g.rule
|
||||
if rule.Data == nil && rule.Clone == (kyverno.CloneFrom{}) {
|
||||
return "", fmt.Errorf("clone or data are required")
|
||||
}
|
||||
if rule.Data != nil && rule.Clone != (kyverno.CloneFrom{}) {
|
||||
return "", fmt.Errorf("only one operation allowed per generate rule(data or clone)")
|
||||
return "", fmt.Errorf("only one of data or clone can be specified")
|
||||
}
|
||||
|
||||
kind, name, namespace := rule.Kind, rule.Name, rule.Namespace
|
||||
|
||||
if name == "" {
|
||||
|
|
|
@ -33,7 +33,7 @@ type GeneratorChannel struct {
|
|||
action v1beta1.Operation
|
||||
}
|
||||
|
||||
// Generator defines the implmentation to mange generate request resource
|
||||
// Generator defines the implementation to mange generate request resource
|
||||
type Generator struct {
|
||||
// channel to receive request
|
||||
ch chan GeneratorChannel
|
||||
|
@ -58,15 +58,17 @@ func NewGenerator(client *kyvernoclient.Clientset, grInformer kyvernoinformer.Ge
|
|||
return gen
|
||||
}
|
||||
|
||||
// Apply creates generate request resoruce (blocking call if channel is full)
|
||||
// Apply creates generate request resource (blocking call if channel is full)
|
||||
func (g *Generator) Apply(gr kyverno.GenerateRequestSpec, action v1beta1.Operation) error {
|
||||
logger := g.log
|
||||
logger.V(4).Info("creating Generate Request", "request", gr)
|
||||
|
||||
// Update to channel
|
||||
message := GeneratorChannel{
|
||||
action: action,
|
||||
spec: gr,
|
||||
}
|
||||
|
||||
select {
|
||||
case g.ch <- message:
|
||||
return nil
|
||||
|
@ -80,6 +82,7 @@ func (g *Generator) Apply(gr kyverno.GenerateRequestSpec, action v1beta1.Operati
|
|||
func (g *Generator) Run(workers int, stopCh <-chan struct{}) {
|
||||
logger := g.log
|
||||
defer utilruntime.HandleCrash()
|
||||
|
||||
logger.V(4).Info("starting")
|
||||
defer func() {
|
||||
logger.V(4).Info("shutting down")
|
||||
|
@ -93,6 +96,7 @@ func (g *Generator) Run(workers int, stopCh <-chan struct{}) {
|
|||
for i := 0; i < workers; i++ {
|
||||
go wait.Until(g.processApply, constant.GenerateControllerResync, g.stopCh)
|
||||
}
|
||||
|
||||
<-g.stopCh
|
||||
}
|
||||
|
||||
|
@ -118,12 +122,9 @@ func (g *Generator) generate(grSpec kyverno.GenerateRequestSpec, action v1beta1.
|
|||
// -> receiving channel to take requests to create request
|
||||
// use worker pattern to read and create the CR resource
|
||||
|
||||
func retryApplyResource(client *kyvernoclient.Clientset,
|
||||
grSpec kyverno.GenerateRequestSpec,
|
||||
log logr.Logger,
|
||||
action v1beta1.Operation,
|
||||
grLister kyvernolister.GenerateRequestNamespaceLister,
|
||||
) error {
|
||||
func retryApplyResource(client *kyvernoclient.Clientset, grSpec kyverno.GenerateRequestSpec,
|
||||
log logr.Logger, action v1beta1.Operation, grLister kyvernolister.GenerateRequestNamespaceLister) error {
|
||||
|
||||
var i int
|
||||
var err error
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue