open-cluster-management.io/governance-policy-propagator@v0.13.0/controllers/propagator/replication.go (about) 1 package propagator 2 3 import ( 4 "context" 5 "strings" 6 7 k8sdepwatches "github.com/stolostron/kubernetes-dependency-watches/client" 8 "k8s.io/apimachinery/pkg/api/equality" 9 k8serrors "k8s.io/apimachinery/pkg/api/errors" 10 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 "k8s.io/apimachinery/pkg/types" 12 13 policiesv1 "open-cluster-management.io/governance-policy-propagator/api/v1" 14 policiesv1beta1 "open-cluster-management.io/governance-policy-propagator/api/v1beta1" 15 "open-cluster-management.io/governance-policy-propagator/controllers/common" 16 ) 17 18 const argoCDCompareOptionsAnnotation = "argocd.argoproj.io/compare-options" 19 20 // equivalentReplicatedPolicies compares replicated policies. Returns true if they match. 21 func equivalentReplicatedPolicies(plc1 *policiesv1.Policy, plc2 *policiesv1.Policy) bool { 22 // Compare annotations 23 if !equality.Semantic.DeepEqual(plc1.GetAnnotations(), plc2.GetAnnotations()) { 24 return false 25 } 26 27 // Compare labels 28 if !equality.Semantic.DeepEqual(plc1.GetLabels(), plc2.GetLabels()) { 29 return false 30 } 31 32 // Compare the specs 33 return equality.Semantic.DeepEqual(plc1.Spec, plc2.Spec) 34 } 35 36 // buildReplicatedPolicy constructs a replicated policy based on a root policy and a placementDecision. 37 // In particular, it adds labels that the policy framework uses, and ensures that policy dependencies 38 // are in a consistent format suited for use on managed clusters. 39 // It can return an error if it needed to canonicalize a dependency, but a PolicySet lookup failed. 40 func (r *ReplicatedPolicyReconciler) buildReplicatedPolicy(ctx context.Context, 41 root *policiesv1.Policy, clusterDec clusterDecision, 42 ) (*policiesv1.Policy, error) { 43 clusterName := clusterDec.Cluster 44 replicatedName := common.FullNameForPolicy(root) 45 46 // Create the copy this way to avoid copying the root policy's status which can be quite large in large 47 // environments. 48 replicated := &policiesv1.Policy{} 49 replicated.TypeMeta = root.TypeMeta 50 root.ObjectMeta.DeepCopyInto(&replicated.ObjectMeta) 51 root.Spec.DeepCopyInto(&replicated.Spec) 52 replicated.SetName(replicatedName) 53 replicated.SetNamespace(clusterName) 54 replicated.SetResourceVersion("") 55 replicated.SetFinalizers(nil) 56 replicated.SetOwnerReferences(nil) 57 58 labels := root.GetLabels() 59 if labels == nil { 60 labels = map[string]string{} 61 } 62 63 if root.Spec.CopyPolicyMetadata != nil && !*root.Spec.CopyPolicyMetadata { 64 originalLabels := replicated.GetLabels() 65 66 for label := range originalLabels { 67 if !strings.HasPrefix(label, policiesv1.GroupVersion.Group+"/") { 68 delete(labels, label) 69 } 70 } 71 } 72 73 // Extra labels on replicated policies 74 labels[common.ClusterNameLabel] = clusterName 75 labels[common.ClusterNamespaceLabel] = clusterName 76 labels[common.RootPolicyLabel] = replicatedName 77 78 replicated.SetLabels(labels) 79 80 annotations := replicated.GetAnnotations() 81 if annotations == nil { 82 annotations = map[string]string{} 83 } 84 85 if root.Spec.CopyPolicyMetadata != nil && !*root.Spec.CopyPolicyMetadata { 86 originalAnnotations := replicated.GetAnnotations() 87 88 for annotation := range originalAnnotations { 89 if !strings.HasPrefix(annotation, policiesv1.GroupVersion.Group+"/") { 90 delete(annotations, annotation) 91 } 92 } 93 } 94 95 // Always set IgnoreExtraneous to avoid ArgoCD managing the replicated policy. 96 annotations[argoCDCompareOptionsAnnotation] = "IgnoreExtraneous" 97 98 replicated.SetAnnotations(annotations) 99 100 // Override the replicated policy remediationAction when it's selected to be enforced 101 if !strings.EqualFold(string(replicated.Spec.RemediationAction), string(policiesv1.Enforce)) { 102 if clusterDec.PolicyOverrides.RemediationAction != "" { 103 replicated.Spec.RemediationAction = policiesv1.RemediationAction( 104 clusterDec.PolicyOverrides.RemediationAction) 105 } 106 } 107 108 var err error 109 110 replicated.Spec.Dependencies, err = r.canonicalizeDependencies(ctx, replicated.Spec.Dependencies, root.Namespace) 111 if err != nil { 112 return replicated, err 113 } 114 115 for i, template := range replicated.Spec.PolicyTemplates { 116 replicated.Spec.PolicyTemplates[i].ExtraDependencies, err = r.canonicalizeDependencies( 117 ctx, template.ExtraDependencies, root.Namespace) 118 if err != nil { 119 return replicated, err 120 } 121 } 122 123 return replicated, nil 124 } 125 126 // depIsPolicySet returns true if the given PolicyDependency is a PolicySet 127 func depIsPolicySet(dep policiesv1.PolicyDependency) bool { 128 return dep.Kind == policiesv1.PolicySetKind && 129 dep.APIVersion == policiesv1beta1.GroupVersion.String() 130 } 131 132 // depIsPolicy returns true if the given PolicyDependency is a Policy 133 func depIsPolicy(dep policiesv1.PolicyDependency) bool { 134 return dep.Kind == policiesv1.Kind && 135 dep.APIVersion == policiesv1.GroupVersion.String() 136 } 137 138 // canonicalizeDependencies returns an adjusted list of the input dependencies, ensuring that 139 // Policies are in a consistent format (omitting the namespace, and using the <namespace>.<name> 140 // format as in replicated Policies), and that PolicySets are replaced with their constituent 141 // Policies. If a PolicySet could not be found, that dependency will be copied as-is. It will 142 // return an error if there is an unexpected error looking up a PolicySet to replace. 143 func (r *Propagator) canonicalizeDependencies( 144 ctx context.Context, rawDeps []policiesv1.PolicyDependency, defaultNamespace string, 145 ) ([]policiesv1.PolicyDependency, error) { 146 deps := make([]policiesv1.PolicyDependency, 0) 147 148 for _, dep := range rawDeps { 149 if depIsPolicySet(dep) { 150 plcset := &policiesv1beta1.PolicySet{} 151 152 if dep.Namespace == "" { 153 dep.Namespace = defaultNamespace 154 } 155 156 err := r.Get(ctx, types.NamespacedName{ 157 Namespace: dep.Namespace, 158 Name: dep.Name, 159 }, plcset) 160 if err != nil { 161 if k8serrors.IsNotFound(err) { 162 // If the PolicySet does not exist, that's ok - the propagator doesn't need to 163 // do anything special because there will be a useful status message created by 164 // the framework on the managed cluster. 165 deps = append(deps, dep) 166 167 continue 168 } 169 170 return deps, err 171 } 172 173 for _, plc := range plcset.Spec.Policies { 174 deps = append(deps, policiesv1.PolicyDependency{ 175 TypeMeta: v1.TypeMeta{ 176 Kind: policiesv1.Kind, 177 APIVersion: policiesv1.GroupVersion.String(), 178 }, 179 Name: dep.Namespace + "." + string(plc), 180 Namespace: "", 181 Compliance: dep.Compliance, 182 }) 183 } 184 } else if depIsPolicy(dep) { 185 split := strings.Split(dep.Name, ".") 186 if len(split) == 2 { // assume it's already in the correct <namespace>.<name> format 187 deps = append(deps, dep) 188 } else { 189 if dep.Namespace == "" { 190 // use the namespace from the dependent policy when otherwise not provided 191 dep.Namespace = defaultNamespace 192 } 193 194 dep.Name = dep.Namespace + "." + dep.Name 195 dep.Namespace = "" 196 197 deps = append(deps, dep) 198 } 199 } else { 200 deps = append(deps, dep) 201 } 202 } 203 204 return deps, nil 205 } 206 207 // getPolicySetDependencies find all dependencies and extraDependencies in the given policy that 208 // are PolicySets, since those objects will need to be watched. 209 func getPolicySetDependencies(root *policiesv1.Policy) map[k8sdepwatches.ObjectIdentifier]bool { 210 policySetIDs := make(map[k8sdepwatches.ObjectIdentifier]bool) 211 212 for _, dep := range root.Spec.Dependencies { 213 if depIsPolicySet(dep) { 214 ns := dep.Namespace 215 if ns == "" { 216 ns = root.Namespace 217 } 218 219 policySetIDs[k8sdepwatches.ObjectIdentifier{ 220 Group: common.APIGroup, 221 Version: "v1beta1", 222 Kind: policiesv1.PolicySetKind, 223 Namespace: ns, 224 Name: dep.Name, 225 }] = true 226 } 227 } 228 229 for _, tmpl := range root.Spec.PolicyTemplates { 230 for _, dep := range tmpl.ExtraDependencies { 231 if depIsPolicySet(dep) { 232 ns := dep.Namespace 233 if ns == "" { 234 ns = root.Namespace 235 } 236 237 policySetIDs[k8sdepwatches.ObjectIdentifier{ 238 Group: common.APIGroup, 239 Version: "v1beta1", 240 Kind: policiesv1.PolicySetKind, 241 Namespace: ns, 242 Name: dep.Name, 243 }] = true 244 } 245 } 246 } 247 248 return policySetIDs 249 }