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:
parent
863ed5c384
commit
7b7d64dcf2
34 changed files with 436 additions and 391 deletions
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
104
pkg/background/generate/clone.go
Normal file
104
pkg/background/generate/clone.go
Normal 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
|
||||
}
|
50
pkg/background/generate/data.go
Normal file
50
pkg/background/generate/data.go
Normal 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)
|
||||
}
|
|
@ -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{
|
||||
|
|
59
pkg/background/generate/response.go
Normal file
59
pkg/background/generate/response.go
Normal 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
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- policy.yaml
|
||||
assert:
|
||||
- policy-ready.yaml
|
|
@ -0,0 +1,4 @@
|
|||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: cpol-data-sync-remove-list-element-ns
|
|
@ -0,0 +1,4 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
assert:
|
||||
- netpol.yaml
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- policy-remove-egress.yaml
|
||||
assert:
|
||||
- policy-ready.yaml
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
assert:
|
||||
- netpol-new.yaml
|
||||
error:
|
||||
- netpol.yaml
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -4,5 +4,5 @@ data:
|
|||
kind: Secret
|
||||
metadata:
|
||||
name: newsecret
|
||||
namespace: default
|
||||
namespace: pol-clone-nosync-modify-downstream-ns
|
||||
type: Opaque
|
|
@ -4,5 +4,5 @@ data:
|
|||
kind: Secret
|
||||
metadata:
|
||||
name: newsecret
|
||||
namespace: default
|
||||
namespace: pol-clone-nosync-modify-downstream-ns
|
||||
type: Opaque
|
|
@ -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
|
|
@ -4,5 +4,5 @@ data:
|
|||
kind: Secret
|
||||
metadata:
|
||||
name: newsecret
|
||||
namespace: default
|
||||
namespace: pol-clone-nosync-modify-downstream-ns
|
||||
type: Opaque
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -3,6 +3,6 @@ data:
|
|||
foo: YmFy
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newsecret
|
||||
name: pol-clone-nosync-modify-source-newsecret
|
||||
namespace: default
|
||||
type: Opaque
|
|
@ -3,6 +3,6 @@ data:
|
|||
foo: YmFy
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newsecret
|
||||
name: pol-clone-nosync-modify-source-newsecret
|
||||
namespace: default
|
||||
type: Opaque
|
|
@ -23,7 +23,7 @@ spec:
|
|||
generate:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
name: newsecret
|
||||
name: pol-clone-nosync-modify-source-newsecret
|
||||
namespace: default
|
||||
synchronize: false
|
||||
clone:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in a new issue