Skip to content

Commit 6138fbf

Browse files
mprahlopenshift-merge-robot
authored andcommitted
Add the spec.copyPolicyMetadata field
This new field allows a replicated policy to not have labels and annotations that are not managed by the policy framework. This is useful in the case where policies are deployed with ArgoCD and you don't want them to show up in the ArgoCD UI. Additionally, the argocd.argoproj.io/compare-options annotation is now always set to IgnoreExtraneous on the replicated policies to avoid ArgoCD trying to manage the replicated policies. Relates: https://issues.redhat.com/browse/ACM-1690 Signed-off-by: mprahl <mprahl@users.noreply.github.com>
1 parent 44d5274 commit 6138fbf

File tree

5 files changed

+259
-0
lines changed

5 files changed

+259
-0
lines changed

api/v1/policy_types.go

+6
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ type PolicySpec struct {
6969
// This provides the ability to enable and disable your policies.
7070
Disabled bool `json:"disabled"`
7171

72+
// If set to true (default), all the policy's labels and annotations will be copied to the replicated policy.
73+
// If set to false, only the policy framework specific policy labels and annotations will be copied to the
74+
// replicated policy.
75+
// +kubebuilder:validation:Optional
76+
CopyPolicyMetadata *bool `json:"copyPolicyMetadata,omitempty"`
77+
7278
// This value (Enforce or Inform) will override the remediationAction on each template
7379
RemediationAction RemediationAction `json:"remediationAction,omitempty"`
7480

controllers/propagator/replication.go

+32
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import (
1616
"open-cluster-management.io/governance-policy-propagator/controllers/common"
1717
)
1818

19+
const argoCDCompareOptionsAnnotation = "argocd.argoproj.io/compare-options"
20+
1921
// equivalentReplicatedPolicies compares replicated policies. Returns true if they match.
2022
func equivalentReplicatedPolicies(plc1 *policiesv1.Policy, plc2 *policiesv1.Policy) bool {
2123
// Compare annotations
@@ -52,13 +54,43 @@ func (r *PolicyReconciler) buildReplicatedPolicy(
5254
labels = map[string]string{}
5355
}
5456

57+
if root.Spec.CopyPolicyMetadata != nil && !*root.Spec.CopyPolicyMetadata {
58+
originalLabels := replicated.GetLabels()
59+
60+
for label := range originalLabels {
61+
if !strings.HasPrefix(label, policiesv1.GroupVersion.Group+"/") {
62+
delete(labels, label)
63+
}
64+
}
65+
}
66+
5567
// Extra labels on replicated policies
5668
labels[common.ClusterNameLabel] = decision.ClusterName
5769
labels[common.ClusterNamespaceLabel] = decision.ClusterNamespace
5870
labels[common.RootPolicyLabel] = replicatedName
5971

6072
replicated.SetLabels(labels)
6173

74+
annotations := replicated.GetAnnotations()
75+
if annotations == nil {
76+
annotations = map[string]string{}
77+
}
78+
79+
if root.Spec.CopyPolicyMetadata != nil && !*root.Spec.CopyPolicyMetadata {
80+
originalAnnotations := replicated.GetAnnotations()
81+
82+
for annotation := range originalAnnotations {
83+
if !strings.HasPrefix(annotation, policiesv1.GroupVersion.Group+"/") {
84+
delete(annotations, annotation)
85+
}
86+
}
87+
}
88+
89+
// Always set IgnoreExtraneous to avoid ArgoCD managing the replicated policy.
90+
annotations[argoCDCompareOptionsAnnotation] = "IgnoreExtraneous"
91+
92+
replicated.SetAnnotations(annotations)
93+
6294
var err error
6395

6496
replicated.Spec.Dependencies, err = r.canonicalizeDependencies(replicated.Spec.Dependencies, root.Namespace)

deploy/crds/kustomize/policy.open-cluster-management.io_policies.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ spec:
4848
spec:
4949
description: PolicySpec defines the desired state of Policy
5050
properties:
51+
copyPolicyMetadata:
52+
description: If set to true (default), all the policy's labels and
53+
annotations will be copied to the replicated policy. If set to false,
54+
only the policy framework specific policy labels and annotations
55+
will be copied to the replicated policy.
56+
type: boolean
5157
dependencies:
5258
description: PolicyDependencies that apply to each template in this
5359
Policy

deploy/crds/policy.open-cluster-management.io_policies.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ spec:
4848
spec:
4949
description: PolicySpec defines the desired state of Policy
5050
properties:
51+
copyPolicyMetadata:
52+
description: If set to true (default), all the policy's labels and
53+
annotations will be copied to the replicated policy. If set to false,
54+
only the policy framework specific policy labels and annotations
55+
will be copied to the replicated policy.
56+
type: boolean
5157
dependencies:
5258
description: PolicyDependencies that apply to each template in this
5359
Policy

test/e2e/case1_propagation_test.go

+209
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ import (
88

99
. "github.com/onsi/ginkgo/v2"
1010
. "github.com/onsi/gomega"
11+
"k8s.io/apimachinery/pkg/api/errors"
1112
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1213
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
14+
"k8s.io/apimachinery/pkg/runtime"
15+
"k8s.io/client-go/dynamic"
1316
appsv1 "open-cluster-management.io/multicloud-operators-subscription/pkg/apis/apps/placementrule/v1"
1417

1518
policiesv1 "open-cluster-management.io/governance-policy-propagator/api/v1"
@@ -536,4 +539,210 @@ var _ = Describe("Test policy propagation", func() {
536539
utils.ListWithTimeout(clientHubDynamic, gvrPolicy, opt, 0, false, 10)
537540
})
538541
})
542+
543+
Describe("Test spec.copyPolicyMetadata", Ordered, func() {
544+
const policyName = "case1-metadata"
545+
policyClient := func() dynamic.ResourceInterface {
546+
return clientHubDynamic.Resource(gvrPolicy).Namespace(testNamespace)
547+
}
548+
549+
getPolicy := func() *policiesv1.Policy {
550+
return &policiesv1.Policy{
551+
TypeMeta: metav1.TypeMeta{
552+
Kind: policiesv1.Kind,
553+
APIVersion: policiesv1.GroupVersion.String(),
554+
},
555+
ObjectMeta: metav1.ObjectMeta{
556+
Name: policyName,
557+
Namespace: testNamespace,
558+
},
559+
Spec: policiesv1.PolicySpec{
560+
Disabled: false,
561+
PolicyTemplates: []*policiesv1.PolicyTemplate{},
562+
},
563+
}
564+
}
565+
566+
BeforeAll(func() {
567+
By("Creating a PlacementRule")
568+
plr := &unstructured.Unstructured{
569+
Object: map[string]interface{}{
570+
"apiVersion": "apps.open-cluster-management.io/v1",
571+
"kind": "PlacementRule",
572+
"metadata": map[string]interface{}{
573+
"name": policyName,
574+
"namespace": testNamespace,
575+
},
576+
"spec": map[string]interface{}{},
577+
},
578+
}
579+
var err error
580+
plr, err = clientHubDynamic.Resource(gvrPlacementRule).Namespace(testNamespace).Create(
581+
context.TODO(), plr, metav1.CreateOptions{},
582+
)
583+
Expect(err).To(BeNil())
584+
585+
plr.Object["status"] = utils.GeneratePlrStatus("managed1")
586+
_, err = clientHubDynamic.Resource(gvrPlacementRule).Namespace(testNamespace).UpdateStatus(
587+
context.TODO(), plr, metav1.UpdateOptions{},
588+
)
589+
Expect(err).To(BeNil())
590+
591+
By("Creating a PlacementBinding")
592+
plb := &unstructured.Unstructured{
593+
Object: map[string]interface{}{
594+
"apiVersion": policiesv1.GroupVersion.String(),
595+
"kind": "PlacementBinding",
596+
"metadata": map[string]interface{}{
597+
"name": policyName,
598+
"namespace": testNamespace,
599+
},
600+
"placementRef": map[string]interface{}{
601+
"apiGroup": gvrPlacementRule.Group,
602+
"kind": "PlacementRule",
603+
"name": policyName,
604+
},
605+
"subjects": []interface{}{
606+
map[string]interface{}{
607+
"apiGroup": policiesv1.GroupVersion.Group,
608+
"kind": policiesv1.Kind,
609+
"name": policyName,
610+
},
611+
},
612+
},
613+
}
614+
615+
_, err = clientHubDynamic.Resource(gvrPlacementBinding).Namespace(testNamespace).Create(
616+
context.TODO(), plb, metav1.CreateOptions{},
617+
)
618+
Expect(err).To(BeNil())
619+
})
620+
621+
AfterEach(func() {
622+
By("Removing the policy")
623+
err := policyClient().Delete(context.TODO(), policyName, metav1.DeleteOptions{})
624+
if !errors.IsNotFound(err) {
625+
Expect(err).To(BeNil())
626+
}
627+
628+
replicatedPolicy := utils.GetWithTimeout(
629+
clientHubDynamic,
630+
gvrPolicy,
631+
testNamespace+"."+policyName,
632+
"managed1",
633+
false,
634+
defaultTimeoutSeconds,
635+
)
636+
Expect(replicatedPolicy).To(BeNil())
637+
})
638+
639+
AfterAll(func() {
640+
By("Removing the PlacementRule")
641+
err := clientHubDynamic.Resource(gvrPlacementRule).Namespace(testNamespace).Delete(
642+
context.TODO(), policyName, metav1.DeleteOptions{},
643+
)
644+
if !errors.IsNotFound(err) {
645+
Expect(err).To(BeNil())
646+
}
647+
648+
By("Removing the PlacementBinding")
649+
err = clientHubDynamic.Resource(gvrPlacementBinding).Namespace(testNamespace).Delete(
650+
context.TODO(), policyName, metav1.DeleteOptions{},
651+
)
652+
if !errors.IsNotFound(err) {
653+
Expect(err).To(BeNil())
654+
}
655+
})
656+
657+
It("Verifies that spec.copyPolicyMetadata defaults to unset", func() {
658+
By("Creating an empty policy")
659+
policy := getPolicy()
660+
policyMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(policy)
661+
Expect(err).To(BeNil())
662+
663+
policyRV, err := policyClient().Create(
664+
context.TODO(), &unstructured.Unstructured{Object: policyMap}, metav1.CreateOptions{},
665+
)
666+
Expect(err).To(BeNil())
667+
668+
_, found, _ := unstructured.NestedBool(policyRV.Object, "spec", "copyPolicyMetadata")
669+
Expect(found).To(BeFalse())
670+
})
671+
672+
It("verifies that the labels and annotations are copied with spec.copyPolicyMetadata=true", func() {
673+
By("Creating a policy with labels and annotations")
674+
policy := getPolicy()
675+
copyPolicyMetadata := true
676+
policy.Spec.CopyPolicyMetadata = &copyPolicyMetadata
677+
policy.SetAnnotations(map[string]string{"do": "copy", "please": "do-copy"})
678+
policy.SetLabels(map[string]string{"do": "copy", "please": "do-copy"})
679+
680+
policyMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(policy)
681+
Expect(err).To(BeNil())
682+
683+
_, err = policyClient().Create(
684+
context.TODO(), &unstructured.Unstructured{Object: policyMap}, metav1.CreateOptions{},
685+
)
686+
Expect(err).To(BeNil())
687+
688+
Eventually(func(g Gomega) {
689+
replicatedPlc := utils.GetWithTimeout(
690+
clientHubDynamic,
691+
gvrPolicy,
692+
testNamespace+"."+policyName,
693+
"managed1",
694+
true,
695+
defaultTimeoutSeconds,
696+
)
697+
698+
annotations := replicatedPlc.GetAnnotations()
699+
g.Expect(annotations["do"]).To(Equal("copy"))
700+
g.Expect(annotations["please"]).To(Equal("do-copy"))
701+
// This annotation is always set.
702+
g.Expect(annotations["argocd.argoproj.io/compare-options"]).To(Equal("IgnoreExtraneous"))
703+
704+
labels := replicatedPlc.GetLabels()
705+
g.Expect(labels["do"]).To(Equal("copy"))
706+
g.Expect(labels["please"]).To(Equal("do-copy"))
707+
}, defaultTimeoutSeconds, 1).Should(Succeed())
708+
})
709+
710+
It("verifies that the labels and annotations are not copied with spec.copyPolicyMetadata=false", func() {
711+
By("Creating a policy with labels and annotations")
712+
policy := getPolicy()
713+
copyPolicyMetadata := false
714+
policy.Spec.CopyPolicyMetadata = &copyPolicyMetadata
715+
policy.SetAnnotations(map[string]string{"do-not": "copy", "please": "do-not-copy"})
716+
policy.SetLabels(map[string]string{"do-not": "copy", "please": "do-not-copy"})
717+
718+
policyMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(policy)
719+
Expect(err).To(BeNil())
720+
721+
_, err = policyClient().Create(
722+
context.TODO(), &unstructured.Unstructured{Object: policyMap}, metav1.CreateOptions{},
723+
)
724+
Expect(err).To(BeNil())
725+
726+
Eventually(func(g Gomega) {
727+
replicatedPlc := utils.GetWithTimeout(
728+
clientHubDynamic,
729+
gvrPolicy,
730+
testNamespace+"."+policyName,
731+
"managed1",
732+
true,
733+
defaultTimeoutSeconds,
734+
)
735+
736+
annotations := replicatedPlc.GetAnnotations()
737+
g.Expect(annotations["do-not"]).To(Equal(""))
738+
g.Expect(annotations["please"]).To(Equal(""))
739+
// This annotation is always set.
740+
g.Expect(annotations["argocd.argoproj.io/compare-options"]).To(Equal("IgnoreExtraneous"))
741+
742+
labels := replicatedPlc.GetLabels()
743+
g.Expect(labels["do-not"]).To(Equal(""))
744+
g.Expect(labels["please"]).To(Equal(""))
745+
}, defaultTimeoutSeconds, 1).Should(Succeed())
746+
})
747+
})
539748
})

0 commit comments

Comments
 (0)