Merge "kartongips: implement proper diffing of aggregated ClusterRoles"
diff --git a/cluster/tools/kartongips/pkg/kubecfg/diff.go b/cluster/tools/kartongips/pkg/kubecfg/diff.go
index f1136be..5d32b9e 100644
--- a/cluster/tools/kartongips/pkg/kubecfg/diff.go
+++ b/cluster/tools/kartongips/pkg/kubecfg/diff.go
@@ -91,6 +91,7 @@
 		liveObjObject := liveObj.Object
 		if c.DiffStrategy == "subset" {
 			liveObjObject = removeMapFields(obj.Object, liveObjObject)
+			liveObjObject = removeClusterRoleAggregatedRules(liveObjObject)
 		}
 
 		liveObjText, _ := json.MarshalIndent(liveObjObject, "", "  ")
@@ -211,6 +212,38 @@
 	return result
 }
 
+// removeClusterRoleAggregatedRules clears the rules field from live
+// ClusterRole objects which have an aggregationRule. This allows us to diff a
+// config object (which doesn't have these rules materialized) against a live
+// obejct (which does have these rules materialized) without spurious diffs.
+//
+// See the Aggregated ClusterRole section of the Kubernetes RBAC docuementation
+// for more information:
+//
+// https://kubernetes.io/docs/reference/access-authn-authz/rbac/#aggregated-clusterroles
+func removeClusterRoleAggregatedRules(live map[string]interface{}) map[string]interface{} {
+	if version, ok := live["apiVersion"].(string); !ok || version != "rbac.authorization.k8s.io/v1" {
+		return live
+	}
+
+	if kind, ok := live["kind"].(string); !ok || kind != "ClusterRole" {
+		return live
+	}
+
+	if _, ok := live["aggregationRule"].(map[string]interface{}); !ok {
+		return live
+	}
+
+	// Make copy of map.
+	res := make(map[string]interface{})
+	for k, v := range live {
+		res[k] = v
+	}
+	// Clear rules field.
+	res["rules"] = []interface{}{}
+	return res
+}
+
 func removeListFields(config, live []interface{}) []interface{} {
 	// If live is longer than config, then the extra elements at the end of the
 	// list will be returned as is so they appear in the diff.