Skip to content

Commit

Permalink
Add config/operator/cert policy transform (#298)
Browse files Browse the repository at this point in the history
Add properties such as responseAction, compliant, annotations, severity, parentPolicyName, and parentPolicyNs to config/operator/cert policy.
Ref: https://issues.redhat.com/browse/ACM-13031
Signed-off-by: yiraeChristineKim <[email protected]>
  • Loading branch information
yiraeChristineKim authored Aug 29, 2024
1 parent 9d94083 commit c9bd4b7
Show file tree
Hide file tree
Showing 8 changed files with 553 additions and 29 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ require (
k8s.io/apimachinery v0.28.2
k8s.io/client-go v13.0.0+incompatible
k8s.io/helm v2.17.0+incompatible
k8s.io/klog/v2 v2.100.1 // indirect
k8s.io/klog/v2 v2.100.1
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
open-cluster-management.io/multicloud-operators-channel v0.8.0
open-cluster-management.io/multicloud-operators-subscription v0.8.0 //Use 2.0 when available
Expand Down
38 changes: 19 additions & 19 deletions pkg/reconciler/reconciler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func initTestReconciler() *Reconciler {
func createNodeEvents() []tr.NodeEvent {
events := NodeEdge{}
nodeEvents := []tr.NodeEvent{}
//First Node
// First Node
unstructuredInput := unstructured.Unstructured{
Object: map[string]interface{}{
"kind": "testowner",
Expand Down Expand Up @@ -77,7 +77,7 @@ func createNodeEvents() []tr.NodeEvent {
events.BuildNode = append(events.BuildNode, podNode)
events.BuildEdges = append(events.BuildEdges, podEdges)

//Convert events to node events
// Convert events to node events
for i := range events.BuildNode {
ne := tr.NodeEvent{
Time: time.Now().Unix(),
Expand Down Expand Up @@ -237,10 +237,10 @@ func TestReconcilerRedundant(t *testing.T) {

func TestReconcilerAddEdges(t *testing.T) {
testReconciler := initTestReconciler()
//Add events
// Add events
events := createNodeEvents()

//Input node events to reconciler
// Input node events to reconciler
go func() {
for _, ne := range events {
testReconciler.Input <- ne
Expand All @@ -250,16 +250,16 @@ func TestReconcilerAddEdges(t *testing.T) {
for range events {
testReconciler.reconcileNode()
}
//Build edges
// Build edges
edgeMap1 := testReconciler.allEdges()

//Expected edge
// Expected edge
edgeMap2 := make(map[string]map[string]tr.Edge, 1)
edge := tr.Edge{EdgeType: "ownedBy", SourceUID: "local-cluster/5678", DestUID: "local-cluster/1234", SourceKind: "Pod", DestKind: "testowner"}
edgeMap2["local-cluster/5678"] = map[string]tr.Edge{}
edgeMap2["local-cluster/5678"]["local-cluster/1234"] = edge

//Check if the actual and expected edges are the same
// Check if the actual and expected edges are the same
if !reflect.DeepEqual(edgeMap1, edgeMap2) {
t.Fatal("Expected edges not found")
} else {
Expand All @@ -269,17 +269,17 @@ func TestReconcilerAddEdges(t *testing.T) {

func TestReconcilerDiff(t *testing.T) {
testReconciler := initTestReconciler()
//Add a node to reconciler previous nodes
// Add a node to reconciler previous nodes
testReconciler.previousNodes["local-cluster/1234"] = tr.Node{
UID: "local-cluster/1234",
Properties: map[string]interface{}{
"very": "important",
},
}
//Add events
// Add events
events := createNodeEvents()

//Input node events to reconciler
// Input node events to reconciler
go func() {
for _, ne := range events {
testReconciler.Input <- ne
Expand All @@ -289,9 +289,9 @@ func TestReconcilerDiff(t *testing.T) {
for range events {
testReconciler.reconcileNode()
}
//Compute reconciler diff - this time there should be 1 node and edge to add, 1 node to update
// Compute reconciler diff - this time there should be 1 node and edge to add, 1 node to update
diff := testReconciler.Diff()
//Compute reconciler diff again - this time there shouldn't be any new edges or nodes to add/update
// Compute reconciler diff again - this time there shouldn't be any new edges or nodes to add/update
nextDiff := testReconciler.Diff()

if (len(diff.AddNodes) != 1 || len(diff.UpdateNodes) != 1 || len(diff.AddEdges) != 1) ||
Expand All @@ -306,7 +306,7 @@ func TestReconcilerComplete(t *testing.T) {
input := make(chan *tr.Event)
output := make(chan tr.NodeEvent)
ts := time.Now().Unix()
//Read all files in test-data
// Read all files in test-data
dir := "../../test-data"
files, err := os.ReadDir(dir)
if err != nil {
Expand All @@ -316,19 +316,19 @@ func TestReconcilerComplete(t *testing.T) {
events := make([]tr.Event, 0)
var appInput unstructured.Unstructured

//Variables to keep track of helm release object
// Variables to keep track of helm release object
var c v1.ConfigMap
var rls release.Release
rlsFileCount := 0
var rlsEvnt = &tr.Event{}
rlsEvnt := &tr.Event{}

//Convert to events
// Convert to events
for _, file := range files {
filePath := dir + "/" + file.Name()
if file.Name() != "helmrelease-release.json" {
tr.UnmarshalFile(filePath, &appInput, t)
appInputLocal := appInput
var in = &tr.Event{
in := &tr.Event{
Time: ts,
Operation: tr.Create,
Resource: &appInputLocal,
Expand All @@ -350,7 +350,7 @@ func TestReconcilerComplete(t *testing.T) {
testReconciler := initTestReconciler()
go tr.TransformRoutine(input, output)

//Convert events to Node events
// Convert events to Node events
go func() {
for _, ev := range events {
localEv := &ev
Expand Down Expand Up @@ -378,7 +378,7 @@ func TestReconcilerComplete(t *testing.T) {
// Checks the count of nodes and edges based on the JSON files in pkg/test-data
// Update counts when the test data is changed
// We don't create Nodes for kind = Event
const Nodes = 36
const Nodes = 39
const Edges = 51
if len(com.Edges) != Edges || com.TotalEdges != Edges || len(com.Nodes) != Nodes || com.TotalNodes != Nodes {
ns := tr.NodeStore{
Expand Down
97 changes: 94 additions & 3 deletions pkg/transforms/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ Copyright (c) 2020 Red Hat, Inc.
package transforms

import (
p "github.com/stolostron/governance-policy-propagator/api/v1"
"strings"

policy "github.com/stolostron/governance-policy-propagator/api/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

// PolicyResource ...
Expand All @@ -20,7 +24,7 @@ type PolicyResource struct {
}

// PolicyResourceBuilder ...
func PolicyResourceBuilder(p *p.Policy) *PolicyResource {
func PolicyResourceBuilder(p *policy.Policy) *PolicyResource {
node := transformCommon(p) // Start off with the common properties
apiGroupVersion(p.TypeMeta, &node) // add kind, apigroup and version
// Extract the properties specific to this type
Expand All @@ -37,6 +41,93 @@ func PolicyResourceBuilder(p *p.Policy) *PolicyResource {
if okns && okpp {
node.Properties["_parentPolicy"] = pnamespace + "/" + ppolicy
}

return &PolicyResource{node: node}
}

// For cert, config, operator policies.
// This function returns `annotations`, `_isExternal` for `source`,
// and `severity`, `compliant`, and `remediationAction`.
func getPolicyCommonProperties(c *unstructured.Unstructured, node Node) Node {
node.Properties["_isExternal"] = false

for _, m := range c.GetManagedFields() {
if m.Manager == "multicluster-operators-subscription" ||
strings.Contains(m.Manager, "argocd") {
node.Properties["_isExternal"] = true

break
}
}

typeMeta := metav1.TypeMeta{
Kind: c.GetKind(),
APIVersion: c.GetAPIVersion(),
}

apiGroupVersion(typeMeta, &node) // add kind, apigroup and version

node.Properties["compliant"], _, _ = unstructured.NestedString(c.Object, "status", "compliant")

node.Properties["severity"], _, _ = unstructured.NestedString(c.Object, "spec", "severity")

node.Properties["remediationAction"], _, _ = unstructured.NestedString(c.Object, "spec", "remediationAction")

node.Properties["disabled"], _, _ = unstructured.NestedBool(c.Object, "spec", "disabled")

return node
}

// For cert, config policies.
func StandalonePolicyResourceBuilder(c *unstructured.Unstructured) *PolicyResource {
node := transformCommon(c) // Start off with the common properties

return &PolicyResource{node: getPolicyCommonProperties(c, node)}
}

func OperatorPolicyResourceBuilder(c *unstructured.Unstructured) *PolicyResource {
node := transformCommon(c) // Start off with the common properties

node = getPolicyCommonProperties(c, node)

var deploymentAvailable bool
var upgradeAvailable bool

conditions, _, _ := unstructured.NestedSlice(c.Object, "status", "conditions")
for _, condition := range conditions {
mapCondition, ok := condition.(map[string]interface{})
if !ok {
continue
}

conditionType, found, err := unstructured.NestedString(mapCondition, "type")
if !found || err != nil {
continue
}

conditionReason, found, err := unstructured.NestedString(mapCondition, "reason")
if !found || err != nil {
continue
}

if conditionType == "InstallPlanCompliant" {
if conditionReason == "InstallPlanRequiresApproval" || conditionReason == "InstallPlanApproved" {
upgradeAvailable = true
} else {
upgradeAvailable = false
}
} else if conditionType == "DeploymentCompliant" {
if conditionReason == "DeploymentsAvailable" {
deploymentAvailable = true
} else {
deploymentAvailable = false
}
}
}

node.Properties["deploymentAvailable"] = deploymentAvailable
node.Properties["upgradeAvailable"] = upgradeAvailable

return &PolicyResource{node: node}
}

Expand All @@ -47,6 +138,6 @@ func (p PolicyResource) BuildNode() Node {

// BuildEdges construct the edges for Policy Resources
func (p PolicyResource) BuildEdges(ns NodeStore) []Edge {
//no op for now to implement interface
// no op for now to implement interface
return []Edge{}
}
62 changes: 62 additions & 0 deletions pkg/transforms/policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"testing"

policy "github.com/stolostron/governance-policy-propagator/api/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

func TestTransformPolicy(t *testing.T) {
Expand All @@ -26,3 +27,64 @@ func TestTransformPolicy(t *testing.T) {
AssertEqual("disabled", node.Properties["disabled"], false, t)
AssertEqual("numRules", node.Properties["numRules"], 1, t)
}

func TestTransformConfigPolicy(t *testing.T) {
var object map[string]interface{}
UnmarshalFile("configurationpolicy.json", &object, t)

unstructured := &unstructured.Unstructured{
Object: object,
}

configResource := StandalonePolicyResourceBuilder(unstructured)

node := configResource.BuildNode()

// Test only the fields that exist in policy - the common test will test the other bits
AssertEqual("compliant", node.Properties["compliant"], "NonCompliant", t)
AssertEqual("remediationAction", node.Properties["remediationAction"], "inform", t)
AssertEqual("severity", node.Properties["severity"], "low", t)
AssertEqual("disabled", node.Properties["disabled"], false, t)
AssertEqual("_isExternal", node.Properties["_isExternal"], true, t)
}

func TestTransformOperatorPolicy(t *testing.T) {
var object map[string]interface{}
UnmarshalFile("operatorpolicy.json", &object, t)

unstructured := &unstructured.Unstructured{
Object: object,
}

operatorResource := OperatorPolicyResourceBuilder(unstructured)

node := operatorResource.BuildNode()

// Test only the fields that exist in policy - the common test will test the other bits
AssertEqual("compliant", node.Properties["compliant"], "NonCompliant", t)
AssertEqual("remediationAction", node.Properties["remediationAction"], "inform", t)
AssertEqual("severity", node.Properties["severity"], "critical", t)
AssertEqual("deploymentAvailable", node.Properties["deploymentAvailable"], false, t)
AssertEqual("upgradeAvailable", node.Properties["upgradeAvailable"], true, t)
AssertEqual("disabled", node.Properties["disabled"], false, t)
AssertEqual("_isExternal", node.Properties["_isExternal"], false, t)
}

func TestTransformCertPolicy(t *testing.T) {
var object map[string]interface{}
UnmarshalFile("certpolicy.json", &object, t)

unstructured := &unstructured.Unstructured{
Object: object,
}

certResource := StandalonePolicyResourceBuilder(unstructured)

node := certResource.BuildNode()

// Test only the fields that exist in policy - the common test will test the other bits
AssertEqual("compliant", node.Properties["compliant"], "Compliant", t)
AssertEqual("severity", node.Properties["severity"], "low", t)
AssertEqual("disabled", node.Properties["disabled"], false, t)
AssertEqual("_isExternal", node.Properties["_isExternal"], true, t)
}
Loading

0 comments on commit c9bd4b7

Please sign in to comment.