1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00

fix: array element removal should be synced to the downstream resource with a generate data sync rule (#7417)

* refactor

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* add kuttl tests

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix downstream update

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix panic

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix flaky test

Signed-off-by: ShutingZhao <shuting@nirmata.com>

---------

Signed-off-by: ShutingZhao <shuting@nirmata.com>
This commit is contained in:
shuting 2023-06-06 18:07:07 +08:00 committed by GitHub
parent 863ed5c384
commit 7b7d64dcf2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 436 additions and 391 deletions

View file

@ -643,6 +643,14 @@ func (g *Generation) Validate(path *field.Path, clusterResources sets.Set[string
errs = append(errs, field.Forbidden(path.Child("generate").Child("clone/cloneList"), "Generation Rule Clone/CloneList should not have variables"))
}
if len(g.CloneList.Kinds) == 0 {
if g.Kind == "" {
errs = append(errs, field.Forbidden(path.Child("generate").Child("kind"), "kind can not be empty"))
}
if g.Name == "" {
errs = append(errs, field.Forbidden(path.Child("generate").Child("name"), "name can not be empty"))
}
}
return errs
}

View file

@ -913,12 +913,13 @@ func handleGeneratePolicy(generateResponse *engineapi.EngineResponse, policyCont
return nil, err
}
unstrGenResource, err := c.GetUnstrResource(genResource[0])
if err != nil {
return nil, err
if genResource != nil {
unstrGenResource, err := c.GetUnstrResource(genResource[0])
if err != nil {
return nil, err
}
newRuleResponse = append(newRuleResponse, *rule.WithGeneratedResource(*unstrGenResource))
}
newRuleResponse = append(newRuleResponse, *rule.WithGeneratedResource(*unstrGenResource))
}
return newRuleResponse, nil

View file

@ -0,0 +1,104 @@
package generate
import (
"context"
"fmt"
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
apierrors "k8s.io/apimachinery/pkg/api/errors"
)
func manageClone(log logr.Logger, target, sourceSpec kyvernov1.ResourceSpec, policy kyvernov1.PolicyInterface, ur kyvernov1beta1.UpdateRequest, rule kyvernov1.Rule, client dclient.Interface) generateResponse {
source := sourceSpec
clone := rule.Generation
if clone.Clone.Name != "" {
source = kyvernov1.ResourceSpec{
APIVersion: target.GetAPIVersion(),
Kind: target.GetKind(),
Namespace: clone.Clone.Namespace,
Name: clone.Clone.Name,
}
}
if source.GetNamespace() == "" {
log.V(4).Info("namespace is optional in case of cluster scope resource", "source namespace", source.GetNamespace())
}
if source.GetNamespace() == target.GetNamespace() && source.GetName() == target.GetName() ||
(rule.Generation.CloneList.Kinds != nil) && (source.GetNamespace() == target.GetNamespace()) {
log.V(4).Info("skip resource self-clone")
return newSkipGenerateResponse(nil, target, nil)
}
sourceObj, err := client.GetResource(context.TODO(), source.GetAPIVersion(), source.GetKind(), source.GetNamespace(), source.GetName())
if err != nil {
return newSkipGenerateResponse(nil, target, fmt.Errorf("source resource %s not found: %v", target.String(), err))
}
if err := updateSourceLabel(client, sourceObj, ur.Spec.Resource, policy, rule); err != nil {
log.Error(err, "failed to add labels to the source", "kind", sourceObj.GetKind(), "namespace", sourceObj.GetNamespace(), "name", sourceObj.GetName())
}
sourceObjCopy := sourceObj.DeepCopy()
targetObj, err := client.GetResource(context.TODO(), target.GetAPIVersion(), target.GetKind(), target.GetNamespace(), target.GetName())
if err != nil {
if apierrors.IsNotFound(err) && len(ur.Status.GeneratedResources) != 0 && !clone.Synchronize {
log.V(4).Info("synchronization is disabled, recreation will be skipped", "target resource", targetObj)
return newSkipGenerateResponse(nil, target, nil)
}
if apierrors.IsNotFound(err) {
return newCreateGenerateResponse(sourceObjCopy.UnstructuredContent(), target, nil)
}
return newSkipGenerateResponse(nil, target, fmt.Errorf("failed to get the target source: %v", err))
}
if sourceObjCopy.GetNamespace() != target.GetNamespace() && sourceObjCopy.GetOwnerReferences() != nil {
sourceObjCopy.SetOwnerReferences(nil)
}
if targetObj != nil {
sourceObjCopy.SetUID(targetObj.GetUID())
sourceObjCopy.SetSelfLink(targetObj.GetSelfLink())
sourceObjCopy.SetCreationTimestamp(targetObj.GetCreationTimestamp())
sourceObjCopy.SetManagedFields(targetObj.GetManagedFields())
sourceObjCopy.SetResourceVersion(targetObj.GetResourceVersion())
if datautils.DeepEqual(sourceObjCopy, targetObj) {
return newSkipGenerateResponse(nil, target, nil)
}
return newUpdateGenerateResponse(sourceObjCopy.UnstructuredContent(), target, nil)
}
return newCreateGenerateResponse(sourceObjCopy.UnstructuredContent(), target, nil)
}
func manageCloneList(log logr.Logger, targetNamespace string, ur kyvernov1beta1.UpdateRequest, policy kyvernov1.PolicyInterface, rule kyvernov1.Rule, client dclient.Interface) []generateResponse {
var responses []generateResponse
cloneList := rule.Generation.CloneList
sourceNamespace := cloneList.Namespace
kinds := cloneList.Kinds
for _, kind := range kinds {
apiVersion, kind := kubeutils.GetKindFromGVK(kind)
sources, err := client.ListResource(context.TODO(), apiVersion, kind, sourceNamespace, cloneList.Selector)
if err != nil {
responses = append(responses,
newSkipGenerateResponse(
nil,
newResourceSpec(apiVersion, kind, targetNamespace, ""),
fmt.Errorf("failed to list source resource for cloneList %s %s/%s. %v", apiVersion, kind, sourceNamespace, err),
),
)
}
for _, source := range sources.Items {
target := newResourceSpec(source.GetAPIVersion(), source.GetKind(), targetNamespace, source.GetName())
responses = append(responses,
manageClone(log, target, newResourceSpec(source.GetAPIVersion(), source.GetKind(), source.GetNamespace(), source.GetName()), policy, ur, rule, client))
}
}
return responses
}

View file

@ -0,0 +1,50 @@
package generate
import (
"context"
"fmt"
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func manageData(log logr.Logger, target kyvernov1.ResourceSpec, data interface{}, synchronize bool, ur kyvernov1beta1.UpdateRequest, client dclient.Interface) generateResponse {
if data == nil {
log.V(4).Info("data is nil - skipping update")
return newSkipGenerateResponse(nil, target, nil)
}
resource, err := datautils.ToMap(data)
if err != nil {
return newSkipGenerateResponse(nil, target, err)
}
targetObj, err := client.GetResource(context.TODO(), target.GetAPIVersion(), target.GetKind(), target.GetNamespace(), target.GetName())
if err != nil {
if apierrors.IsNotFound(err) && len(ur.Status.GeneratedResources) != 0 && !synchronize {
log.V(4).Info("synchronize is disable - skip re-create")
return newSkipGenerateResponse(nil, target, nil)
}
if apierrors.IsNotFound(err) {
return newCreateGenerateResponse(resource, target, nil)
}
return newSkipGenerateResponse(nil, target, fmt.Errorf("failed to get the target source: %v", err))
}
log.V(4).Info("found target resource")
if !synchronize {
log.V(4).Info("synchronize disabled, skip updating target resource for data")
return newSkipGenerateResponse(nil, target, nil)
}
updateObj := &unstructured.Unstructured{}
updateObj.SetUnstructuredContent(resource)
updateObj.SetResourceVersion(targetObj.GetResourceVersion())
return newUpdateGenerateResponse(updateObj.UnstructuredContent(), target, nil)
}

View file

@ -24,7 +24,6 @@ import (
"github.com/kyverno/kyverno/pkg/engine/variables"
"github.com/kyverno/kyverno/pkg/event"
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
engineutils "github.com/kyverno/kyverno/pkg/utils/engine"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
"github.com/pkg/errors"
@ -342,155 +341,87 @@ func (c *GenerateController) ApplyGeneratePolicy(log logr.Logger, policyContext
return genResources, nil
}
func getResourceInfo(object map[string]interface{}) (kind, name, namespace, apiversion string, err error) {
if kind, _, err = unstructured.NestedString(object, "kind"); err != nil {
return "", "", "", "", err
}
if name, _, err = unstructured.NestedString(object, "name"); err != nil {
return "", "", "", "", err
}
if namespace, _, err = unstructured.NestedString(object, "namespace"); err != nil {
return "", "", "", "", err
}
if apiversion, _, err = unstructured.NestedString(object, "apiVersion"); err != nil {
return "", "", "", "", err
}
return
}
func getResourceInfoForDataAndClone(rule kyvernov1.Rule) (kind, name, namespace, apiversion string, err error) {
if len(rule.Generation.CloneList.Kinds) == 0 {
if kind = rule.Generation.Kind; kind == "" {
return "", "", "", "", fmt.Errorf("%s", "kind can not be empty")
}
if name = rule.Generation.Name; name == "" {
return "", "", "", "", fmt.Errorf("%s", "name can not be empty")
}
}
namespace = rule.Generation.Namespace
apiversion = rule.Generation.APIVersion
return
}
func applyRule(log logr.Logger, client dclient.Interface, rule kyvernov1.Rule, trigger unstructured.Unstructured, ctx enginecontext.EvalInterface, policy kyvernov1.PolicyInterface, ur kyvernov1beta1.UpdateRequest) ([]kyvernov1.ResourceSpec, error) {
rdatas := []GenerateResponse{}
var cresp, dresp map[string]interface{}
responses := []generateResponse{}
var err error
var mode ResourceMode
var noGenResource kyvernov1.ResourceSpec
var newGenResources []kyvernov1.ResourceSpec
genKind, genName, genNamespace, genAPIVersion, err := getResourceInfoForDataAndClone(rule)
if err != nil {
newGenResources = append(newGenResources, noGenResource)
return newGenResources, err
}
logger := log.WithValues("genKind", genKind, "genAPIVersion", genAPIVersion, "genNamespace", genNamespace, "genName", genName)
target := rule.Generation.ResourceSpec
logger := log.WithValues("target", target.String())
if rule.Generation.Clone.Name != "" {
cresp, mode, err = manageClone(logger, genAPIVersion, genKind, genNamespace, genName, policy, ur, rule, client)
rdatas = append(rdatas, GenerateResponse{
Data: cresp,
Action: mode,
GenName: genName,
GenKind: genKind,
GenNamespace: genNamespace,
GenAPIVersion: genAPIVersion,
Error: err,
})
resp := manageClone(logger.WithValues("type", "clone"), target, kyvernov1.ResourceSpec{}, policy, ur, rule, client)
responses = append(responses, resp)
} else if len(rule.Generation.CloneList.Kinds) != 0 {
rdatas = manageCloneList(logger, genNamespace, ur, policy, rule, client)
responses = manageCloneList(logger.WithValues("type", "cloneList"), target.GetNamespace(), ur, policy, rule, client)
} else {
dresp, mode, err = manageData(logger, genAPIVersion, genKind, genNamespace, genName, rule.Generation.RawData, rule.Generation.Synchronize, ur, client)
rdatas = append(rdatas, GenerateResponse{
Data: dresp,
Action: mode,
GenName: genName,
GenKind: genKind,
GenNamespace: genNamespace,
GenAPIVersion: genAPIVersion,
Error: err,
})
resp := manageData(logger.WithValues("type", "data"), target, rule.Generation.RawData, rule.Generation.Synchronize, ur, client)
responses = append(responses, resp)
}
for _, rdata := range rdatas {
if rdata.Error != nil {
logger.Error(err, "failed to generate resource", "mode", rdata.Action)
newGenResources = append(newGenResources, noGenResource)
for _, response := range responses {
targetMeta := response.GetTarget()
if response.GetError() != nil {
logger.Error(response.GetError(), "failed to generate resource", "mode", response.GetAction())
return newGenResources, err
}
logger.V(3).Info("applying generate rule", "mode", rdata.Action)
// skip processing the response in case of skip action
if rdata.Action == Skip {
if response.GetAction() == Skip {
continue
}
if rdata.Data == nil && rdata.Action == Update {
logger.V(3).Info("applying generate rule", "mode", response.GetAction())
if response.GetData() == nil && response.GetAction() == Update {
logger.V(4).Info("no changes required for generate target resource")
newGenResources = append(newGenResources, noGenResource)
return newGenResources, nil
}
// build the resource template
newResource := &unstructured.Unstructured{}
newResource.SetUnstructuredContent(rdata.Data)
newResource.SetName(rdata.GenName)
newResource.SetNamespace(rdata.GenNamespace)
newResource.SetUnstructuredContent(response.GetData())
newResource.SetName(targetMeta.GetName())
newResource.SetNamespace(targetMeta.GetNamespace())
if newResource.GetKind() == "" {
newResource.SetKind(rdata.GenKind)
newResource.SetKind(targetMeta.GetKind())
}
newResource.SetAPIVersion(rdata.GenAPIVersion)
newResource.SetAPIVersion(targetMeta.GetAPIVersion())
common.ManageLabels(newResource, trigger, policy, rule.Name)
if rdata.Action == Create {
if response.GetAction() == Create {
newResource.SetResourceVersion("")
_, err = client.CreateResource(context.TODO(), rdata.GenAPIVersion, rdata.GenKind, rdata.GenNamespace, newResource, false)
_, err = client.CreateResource(context.TODO(), targetMeta.GetAPIVersion(), targetMeta.GetKind(), targetMeta.GetNamespace(), newResource, false)
if err != nil {
if !apierrors.IsAlreadyExists(err) {
newGenResources = append(newGenResources, noGenResource)
return newGenResources, err
}
}
logger.V(2).Info("created generate target resource")
newGenResources = append(newGenResources, newGenResource(rdata.GenAPIVersion, rdata.GenKind, rdata.GenNamespace, rdata.GenName))
} else if rdata.Action == Update {
generatedObj, err := client.GetResource(context.TODO(), rdata.GenAPIVersion, rdata.GenKind, rdata.GenNamespace, rdata.GenName)
newGenResources = append(newGenResources, targetMeta)
} else if response.GetAction() == Update {
generatedObj, err := client.GetResource(context.TODO(), targetMeta.GetAPIVersion(), targetMeta.GetKind(), targetMeta.GetNamespace(), targetMeta.GetName())
if err != nil {
logger.Error(err, fmt.Sprintf("generated resource not found name:%v namespace:%v kind:%v", genName, genNamespace, genKind))
logger.V(2).Info(fmt.Sprintf("creating generate resource name:name:%v namespace:%v kind:%v", genName, genNamespace, genKind))
_, err = client.CreateResource(context.TODO(), rdata.GenAPIVersion, rdata.GenKind, rdata.GenNamespace, newResource, false)
logger.V(2).Info("target resource not found, creating new target")
_, err = client.CreateResource(context.TODO(), targetMeta.GetAPIVersion(), targetMeta.GetKind(), targetMeta.GetNamespace(), newResource, false)
if err != nil {
newGenResources = append(newGenResources, noGenResource)
return newGenResources, err
}
newGenResources = append(newGenResources, newGenResource(rdata.GenAPIVersion, rdata.GenKind, rdata.GenNamespace, rdata.GenName))
newGenResources = append(newGenResources, targetMeta)
} else {
// if synchronize is true - update the label and generated resource with generate policy data
if rule.Generation.Synchronize {
logger.V(4).Info("updating existing resource")
if rdata.GenAPIVersion == "" {
generatedResourceAPIVersion := generatedObj.GetAPIVersion()
newResource.SetAPIVersion(generatedResourceAPIVersion)
}
if rdata.GenNamespace == "" {
newResource.SetNamespace("default")
}
if !rule.Generation.Synchronize {
logger.V(4).Info("synchronize disabled, skip syncing changes")
}
logger.V(4).Info("updating existing resource")
if targetMeta.GetAPIVersion() == "" {
generatedResourceAPIVersion := generatedObj.GetAPIVersion()
newResource.SetAPIVersion(generatedResourceAPIVersion)
}
if targetMeta.GetNamespace() == "" {
newResource.SetNamespace("default")
}
if _, err := ValidateResourceWithPattern(logger, generatedObj.Object, newResource.Object); err != nil {
_, err = client.UpdateResource(context.TODO(), rdata.GenAPIVersion, rdata.GenKind, rdata.GenNamespace, newResource, false)
if err != nil {
logger.Error(err, "failed to update resource")
newGenResources = append(newGenResources, noGenResource)
return newGenResources, err
}
}
_, err = client.UpdateResource(context.TODO(), targetMeta.GetAPIVersion(), targetMeta.GetKind(), targetMeta.GetNamespace(), newResource, false)
if err != nil {
logger.Error(err, "failed to update resource")
return newGenResources, err
}
}
logger.V(3).Info("updated generate target resource")
@ -499,240 +430,6 @@ func applyRule(log logr.Logger, client dclient.Interface, rule kyvernov1.Rule, t
return newGenResources, nil
}
func newGenResource(genAPIVersion, genKind, genNamespace, genName string) kyvernov1.ResourceSpec {
// Resource to be generated
newGenResource := kyvernov1.ResourceSpec{
APIVersion: genAPIVersion,
Kind: genKind,
Namespace: genNamespace,
Name: genName,
}
return newGenResource
}
func manageData(log logr.Logger, apiVersion, kind, namespace, name string, data interface{}, synchronize bool, ur kyvernov1beta1.UpdateRequest, client dclient.Interface) (map[string]interface{}, ResourceMode, error) {
resource, err := datautils.ToMap(data)
if err != nil {
return nil, Skip, err
}
obj, err := client.GetResource(context.TODO(), apiVersion, kind, namespace, name)
if err != nil {
if apierrors.IsNotFound(err) && len(ur.Status.GeneratedResources) != 0 && !synchronize {
log.V(4).Info("synchronize is disable - skip re-create", "resource", obj)
return nil, Skip, nil
}
if apierrors.IsNotFound(err) {
return resource, Create, nil
}
log.Error(err, "failed to get resource")
return nil, Skip, err
}
log.V(3).Info("found target resource", "resource", obj)
if data == nil {
log.V(3).Info("data is nil - skipping update", "resource", obj)
return nil, Skip, nil
}
updateObj := &unstructured.Unstructured{}
updateObj.SetUnstructuredContent(resource)
updateObj.SetResourceVersion(obj.GetResourceVersion())
return updateObj.UnstructuredContent(), Update, nil
}
func manageClone(log logr.Logger, apiVersion, kind, namespace, name string, policy kyvernov1.PolicyInterface, ur kyvernov1beta1.UpdateRequest, rule kyvernov1.Rule, client dclient.Interface) (map[string]interface{}, ResourceMode, error) {
clone := rule.Generation
// resource namespace can be nil in case of clusters scope resource
rNamespace := clone.Clone.Namespace
if rNamespace == "" {
log.V(4).Info("resource namespace %s , optional in case of cluster scope resource", rNamespace)
}
rName := clone.Clone.Name
if rName == "" {
return nil, Skip, fmt.Errorf("failed to find source name")
}
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(context.TODO(), apiVersion, kind, rNamespace, rName)
if err != nil {
return nil, Skip, fmt.Errorf("source resource %s %s/%s/%s not found. %v", apiVersion, kind, rNamespace, rName, err)
}
if err := updateSourceLabel(client, obj, ur.Spec.Resource, policy, rule); err != nil {
log.Error(err, "failed to add labels to the source", "kind", obj.GetKind(), "namespace", obj.GetNamespace(), "name", obj.GetName())
}
// check if cloned resource exists
cobj, err := client.GetResource(context.TODO(), apiVersion, kind, namespace, name)
if err != nil {
if apierrors.IsNotFound(err) && len(ur.Status.GeneratedResources) != 0 && !clone.Synchronize {
log.V(4).Info("synchronization is disabled, recreation will be skipped", "resource", cobj)
return nil, Skip, nil
}
}
// remove ownerReferences when cloning resources to other namespace
if rNamespace != namespace && obj.GetOwnerReferences() != nil {
obj.SetOwnerReferences(nil)
}
// check if resource to be generated exists
newResource, err := client.GetResource(context.TODO(), apiVersion, 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 datautils.DeepEqual(obj, newResource) {
return nil, Skip, nil
}
return obj.UnstructuredContent(), Update, nil
}
// create the resource based on the reference clone
return obj.UnstructuredContent(), Create, nil
}
func manageCloneList(log logr.Logger, namespace string, ur kyvernov1beta1.UpdateRequest, policy kyvernov1.PolicyInterface, rule kyvernov1.Rule, client dclient.Interface) []GenerateResponse {
var response []GenerateResponse
clone := rule.Generation
rNamespace := clone.CloneList.Namespace
if rNamespace == "" {
log.V(4).Info("resource namespace %s , optional in case of cluster scope resource", rNamespace)
}
kinds := clone.CloneList.Kinds
if len(kinds) == 0 {
response = append(response, GenerateResponse{
Data: nil,
Action: Skip,
Error: fmt.Errorf("failed to find kinds list"),
})
}
for _, kind := range kinds {
apiVersion, kind := kubeutils.GetKindFromGVK(kind)
resources, err := client.ListResource(context.TODO(), apiVersion, kind, rNamespace, clone.CloneList.Selector)
if err != nil {
response = append(response, GenerateResponse{
Data: nil,
Action: Skip,
Error: fmt.Errorf("failed to list resource %s %s/%s. %v", apiVersion, kind, rNamespace, err),
})
}
for _, rName := range resources.Items {
if rNamespace == namespace {
log.V(4).Info("skip resource self-clone")
response = append(response, GenerateResponse{
Data: nil,
Action: Skip,
Error: nil,
})
}
// check if the resource as reference in clone exists?
obj, err := client.GetResource(context.TODO(), apiVersion, kind, rNamespace, rName.GetName())
if err != nil {
log.Error(err, "failed to get resource", apiVersion, "apiVersion", kind, "kind", rNamespace, "rNamespace", rName.GetName(), "name")
response = append(response, GenerateResponse{
Data: nil,
Action: Skip,
Error: fmt.Errorf("source resource %s %s/%s/%s not found. %v", apiVersion, kind, rNamespace, rName.GetName(), err),
})
return response
}
if err := updateSourceLabel(client, obj, ur.Spec.Resource, policy, rule); err != nil {
log.Error(err, "failed to add labels to the source", "kind", obj.GetKind(), "namespace", obj.GetNamespace(), "name", obj.GetName())
}
// check if cloned resource exists
cobj, err := client.GetResource(context.TODO(), apiVersion, kind, namespace, rName.GetName())
if apierrors.IsNotFound(err) && len(ur.Status.GeneratedResources) != 0 && !clone.Synchronize {
log.V(4).Info("synchronization is disabled, recreation will be skipped", "resource", cobj)
response = append(response, GenerateResponse{
Data: nil,
Action: Skip,
Error: nil,
})
}
// remove ownerReferences when cloning resources to other namespace
if rNamespace != namespace && obj.GetOwnerReferences() != nil {
obj.SetOwnerReferences(nil)
}
// check if resource to be generated exists
newResource, err := client.GetResource(context.TODO(), apiVersion, kind, namespace, rName.GetName())
if err == nil && newResource != nil {
obj.SetUID(newResource.GetUID())
obj.SetSelfLink(newResource.GetSelfLink())
obj.SetCreationTimestamp(newResource.GetCreationTimestamp())
obj.SetManagedFields(newResource.GetManagedFields())
obj.SetResourceVersion(newResource.GetResourceVersion())
if datautils.DeepEqual(obj, newResource) {
response = append(response, GenerateResponse{
Data: nil,
Action: Skip,
Error: nil,
})
} else {
response = append(response, GenerateResponse{
Data: obj.UnstructuredContent(),
Action: Update,
GenKind: kind,
GenName: rName.GetName(),
GenNamespace: namespace,
GenAPIVersion: apiVersion,
Error: nil,
})
}
}
// create the resource based on the reference clone
response = append(response, GenerateResponse{
Data: obj.UnstructuredContent(),
Action: Create,
GenKind: kind,
GenName: rName.GetName(),
GenNamespace: namespace,
GenAPIVersion: apiVersion,
Error: nil,
})
}
}
return response
}
type GenerateResponse struct {
Data map[string]interface{}
Action ResourceMode
GenKind, GenName, GenNamespace, GenAPIVersion string
Error error
}
// ResourceMode defines the mode for generated resource
type ResourceMode string
const (
// Skip : failed to process rule, will not update the resource
Skip ResourceMode = "SKIP"
// Create : create a new resource
Create = "CREATE"
// Update : update/overwrite the new resource
Update = "UPDATE"
)
func GetUnstrRule(rule *kyvernov1.Generation) (*unstructured.Unstructured, error) {
ruleData, err := json.Marshal(rule)
if err != nil {
@ -741,20 +438,6 @@ func GetUnstrRule(rule *kyvernov1.Generation) (*unstructured.Unstructured, error
return kubeutils.BytesToUnstructured(ruleData)
}
func (c *GenerateController) ApplyResource(resource *unstructured.Unstructured) error {
kind, _, namespace, apiVersion, err := getResourceInfo(resource.Object)
if err != nil {
return err
}
_, err = c.client.CreateResource(context.TODO(), apiVersion, kind, namespace, resource, false)
if err != nil {
return err
}
return nil
}
// NewGenerateControllerWithOnlyClient returns an instance of Controller with only the client.
func NewGenerateControllerWithOnlyClient(client dclient.Interface, engine engineapi.Engine) *GenerateController {
c := GenerateController{

View file

@ -0,0 +1,59 @@
package generate
import kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
// ResourceMode defines the mode for generated resource
type resourceMode string
const (
// Skip : failed to process rule, will not update the resource
Skip resourceMode = "SKIP"
// Create : create a new resource
Create = "CREATE"
// Update : update/overwrite the new resource
Update = "UPDATE"
)
type generateResponse struct {
data map[string]interface{}
action resourceMode
target kyvernov1.ResourceSpec
err error
}
func newGenerateResponse(data map[string]interface{}, action resourceMode, target kyvernov1.ResourceSpec, err error) generateResponse {
return generateResponse{
data: data,
action: action,
target: target,
err: err,
}
}
func newSkipGenerateResponse(data map[string]interface{}, target kyvernov1.ResourceSpec, err error) generateResponse {
return newGenerateResponse(data, Skip, target, err)
}
func newUpdateGenerateResponse(data map[string]interface{}, target kyvernov1.ResourceSpec, err error) generateResponse {
return newGenerateResponse(data, Update, target, err)
}
func newCreateGenerateResponse(data map[string]interface{}, target kyvernov1.ResourceSpec, err error) generateResponse {
return newGenerateResponse(data, Create, target, err)
}
func (resp *generateResponse) GetData() map[string]interface{} {
return resp.data
}
func (resp *generateResponse) GetAction() resourceMode {
return resp.action
}
func (resp *generateResponse) GetTarget() kyvernov1.ResourceSpec {
return resp.target
}
func (resp *generateResponse) GetError() error {
return resp.err
}

View file

@ -16,6 +16,15 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func newResourceSpec(genAPIVersion, genKind, genNamespace, genName string) kyvernov1.ResourceSpec {
return kyvernov1.ResourceSpec{
APIVersion: genAPIVersion,
Kind: genKind,
Namespace: genNamespace,
Name: genName,
}
}
func increaseRetryAnnotation(ur *kyvernov1beta1.UpdateRequest) (int, map[string]string, error) {
urAnnotations := ur.Annotations
if len(urAnnotations) == 0 {

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- policy.yaml
assert:
- policy-ready.yaml

View file

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: cpol-data-sync-remove-list-element-ns

View file

@ -0,0 +1,4 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
assert:
- netpol.yaml

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- policy-remove-egress.yaml
assert:
- policy-ready.yaml

View file

@ -0,0 +1,5 @@
# A command can only run a single command, not a pipeline and not a script. The program called must exist on the system where the test is run.
apiVersion: kuttl.dev/v1beta1
kind: TestStep
commands:
- command: sleep 3

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
assert:
- netpol-new.yaml
error:
- netpol.yaml

View file

@ -0,0 +1,11 @@
## Description
This test checks the removal of an array element is synced to the downstream resource correctly.
## Expected Behavior
When the `Egress` is removed from the data generate policy, this change should be synced to the downstream generated resource. The test passes if the `Egress` is removed from the networkpolicy `cpol-data-sync-remove-list-element-ns/default-netpol`, otherwise fails.
## Reference Issue(s)
n/a

View file

@ -0,0 +1,8 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-netpol
namespace: cpol-data-sync-remove-list-element-ns
spec:
policyTypes:
- Ingress

View file

@ -0,0 +1,9 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-netpol
namespace: cpol-data-sync-remove-list-element-ns
spec:
policyTypes:
- Ingress
- Egress

View file

@ -0,0 +1,9 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: cpol-data-sync-remove-list-element-cpol
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready

View file

@ -0,0 +1,32 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: cpol-data-sync-remove-list-element-cpol
annotations:
policies.kyverno.io/category: Workload Management
policies.kyverno.io/description: By default, Kubernetes allows communications across
all pods within a cluster. Network policies and, a CNI that supports network policies,
must be used to restrict communinications. A default NetworkPolicy should be configured
for each namespace to default deny all ingress traffic to the pods in the namespace.
Application teams can then configure additional NetworkPolicy resources to allow
desired traffic to application pods from select sources.
spec:
validationFailureAction: audit
rules:
- name: cpol-data-sync-remove-list-element-rule
match:
resources:
kinds:
- Namespace
generate:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
name: default-netpol
namespace: "{{request.object.metadata.name}}"
synchronize : true
data:
spec:
# select all pods in the namespace
podSelector: {}
policyTypes:
- Ingress

View file

@ -0,0 +1,33 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: cpol-data-sync-remove-list-element-cpol
annotations:
policies.kyverno.io/category: Workload Management
policies.kyverno.io/description: By default, Kubernetes allows communications across
all pods within a cluster. Network policies and, a CNI that supports network policies,
must be used to restrict communinications. A default NetworkPolicy should be configured
for each namespace to default deny all ingress traffic to the pods in the namespace.
Application teams can then configure additional NetworkPolicy resources to allow
desired traffic to application pods from select sources.
spec:
validationFailureAction: audit
rules:
- name: cpol-data-sync-remove-list-element-rule
match:
resources:
kinds:
- Namespace
generate:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
name: default-netpol
namespace: "{{request.object.metadata.name}}"
synchronize : true
data:
spec:
# select all pods in the namespace
podSelector: {}
policyTypes:
- Ingress
- Egress

View file

@ -4,5 +4,5 @@ data:
kind: Secret
metadata:
name: newsecret
namespace: default
namespace: pol-clone-nosync-modify-downstream-ns
type: Opaque

View file

@ -4,5 +4,5 @@ data:
kind: Secret
metadata:
name: newsecret
namespace: default
namespace: pol-clone-nosync-modify-downstream-ns
type: Opaque

View file

@ -1,7 +0,0 @@
# This clean-up stage is necessary because of https://github.com/kyverno/kyverno/issues/5101
apiVersion: kuttl.dev/v1beta1
kind: TestStep
commands:
- script: |
kubectl delete ur -A --all
kubectl delete -f cloned-secret.yaml --ignore-not-found=true

View file

@ -4,5 +4,5 @@ data:
kind: Secret
metadata:
name: newsecret
namespace: default
namespace: pol-clone-nosync-modify-downstream-ns
type: Opaque

View file

@ -2,7 +2,7 @@ apiVersion: v1
kind: ConfigMap
metadata:
name: mycm
namespace: default
namespace: pol-clone-nosync-modify-downstream-ns
data:
food: cheese
day: monday

View file

@ -1,17 +1,22 @@
apiVersion: v1
kind: Namespace
metadata:
name: pol-clone-nosync-modify-downstream-ns
---
apiVersion: v1
data:
foo: YmFy
kind: Secret
metadata:
name: regcred
namespace: default
namespace: pol-clone-nosync-modify-downstream-ns
type: Opaque
---
apiVersion: kyverno.io/v2beta1
kind: Policy
metadata:
name: pol-clone-nosync-modify-downstream
namespace: default
namespace: pol-clone-nosync-modify-downstream-ns
spec:
rules:
- name: pol-clone-nosync-modify-downstream-rule
@ -24,8 +29,8 @@ spec:
apiVersion: v1
kind: Secret
name: newsecret
namespace: default
namespace: pol-clone-nosync-modify-downstream-ns
synchronize: false
clone:
name: regcred
namespace: default
namespace: pol-clone-nosync-modify-downstream-ns

View file

@ -2,7 +2,7 @@ apiVersion: kyverno.io/v2beta1
kind: Policy
metadata:
name: pol-clone-nosync-modify-downstream
namespace: default
namespace: pol-clone-nosync-modify-downstream-ns
status:
conditions:
- reason: Succeeded

View file

@ -3,6 +3,6 @@ data:
foo: YmFy
kind: Secret
metadata:
name: newsecret
name: pol-clone-nosync-modify-source-newsecret
namespace: default
type: Opaque

View file

@ -3,6 +3,6 @@ data:
foo: YmFy
kind: Secret
metadata:
name: newsecret
name: pol-clone-nosync-modify-source-newsecret
namespace: default
type: Opaque

View file

@ -23,7 +23,7 @@ spec:
generate:
apiVersion: v1
kind: Secret
name: newsecret
name: pol-clone-nosync-modify-source-newsecret
namespace: default
synchronize: false
clone:

View file

@ -2,13 +2,13 @@
apiVersion: v1
kind: Namespace
metadata:
name: pol-data-sync-modify-downstream
name: pol-data-sync-modify-downstream-ns
---
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: pol-data-sync
namespace: pol-data-sync-modify-downstream
namespace: pol-data-sync-modify-downstream-ns
spec:
rules:
- name: gen-zk
@ -22,7 +22,7 @@ spec:
apiVersion: v1
kind: ConfigMap
name: zk-kafka-address
namespace: pol-data-sync-modify-downstream
namespace: pol-data-sync-modify-downstream-ns
data:
kind: ConfigMap
metadata:

View file

@ -4,5 +4,5 @@ data:
kind: Secret
metadata:
name: test
namespace: pol-data-sync-modify-downstream
namespace: pol-data-sync-modify-downstream-ns
type: Opaque

View file

@ -7,4 +7,4 @@ metadata:
labels:
somekey: somevalue
name: zk-kafka-address
namespace: pol-data-sync-modify-downstream
namespace: pol-data-sync-modify-downstream-ns

View file

@ -7,4 +7,4 @@ metadata:
labels:
somekey: somevalue
name: zk-kafka-address
namespace: pol-data-sync-modify-downstream
namespace: pol-data-sync-modify-downstream-ns

View file

@ -7,4 +7,4 @@ metadata:
labels:
somekey: somevalue
name: zk-kafka-address
namespace: pol-data-sync-modify-downstream
namespace: pol-data-sync-modify-downstream-ns