-
Notifications
You must be signed in to change notification settings - Fork 25
/
policy.go
190 lines (168 loc) · 6.61 KB
/
policy.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
package extramanifest
import (
"context"
"encoding/json"
"errors"
"fmt"
"sort"
"strconv"
"strings"
"github.com/go-logr/logr"
"github.com/openshift-kni/lifecycle-agent/internal/common"
"github.com/samber/lo"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
policyv1 "open-cluster-management.io/config-policy-controller/api/v1"
policiesv1 "open-cluster-management.io/governance-policy-propagator/api/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// +kubebuilder:rbac:groups=policy.open-cluster-management.io,resources=policies,verbs=get;list;watch
// This annotation helps with the ordering as well as whether the policy should be applied. This is the same expectation as
// the normal ZTP process post non-image-based cluster installation.
const ztpDeployWaveAnnotation = "ran.openshift.io/ztp-deploy-wave"
const TargetOcpVersionManifestCountAnnotation = "lca.openshift.io/target-ocp-version-manifest-count"
const TargetOcpVersionLabel = "lca.openshift.io/target-ocp-version"
// GetPolicies gets the policies matching the labels from the namespace and sort them by the ztp wave annotation value
func (h *EMHandler) GetPolicies(ctx context.Context, labels map[string]string) ([]*policiesv1.Policy, error) {
listOpts := []client.ListOption{
client.MatchingLabels(labels),
}
policies := &policiesv1.PolicyList{}
if err := h.Client.List(ctx, policies, listOpts...); err != nil {
return nil, fmt.Errorf("failed to list policies: %w", err)
}
var policyWaveMap = make(map[*policiesv1.Policy]int)
for i := range policies.Items {
policy := &policies.Items[i]
// CRs from enforce mode policy gets applied by ACM policy engine
// We only care about the inform ones that typically get enforced by TALM through the ZTP CGU
if strings.EqualFold(string(policy.Spec.RemediationAction), "enforce") {
h.Log.Info(fmt.Sprintf("Ignoring policy %s with remediationAction enforce", policy.Name))
continue
}
deployWave, found := policy.GetAnnotations()[ztpDeployWaveAnnotation]
if found {
deployWaveInt, err := strconv.Atoi(deployWave)
if err != nil {
// err convert from string to int
h.Log.Error(err, "Annotation "+ztpDeployWaveAnnotation+" is not an interger", "policy name", policy.GetName())
}
_, err = getParentPolicyNameAndNamespace(policy.GetName())
if err != nil {
h.Log.Info(fmt.Sprintf("Ignoring policy %s with invalid name", policy.Name))
continue
}
policyWaveMap[policy] = deployWaveInt
} else {
h.Log.Info(fmt.Sprintf("Ignoring policy %s without the %s annotation", policy.Name, ztpDeployWaveAnnotation))
}
}
return sortPolicyMap(policyWaveMap), nil
}
// Gets encapsulated objects from policy
func getConfigurationObjects(log *logr.Logger, policy *policiesv1.Policy, objectLabels map[string]string) ([]*unstructured.Unstructured, error) {
var uobjects []*unstructured.Unstructured
var objects []runtime.RawExtension
if !policy.Spec.Disabled {
for _, template := range policy.Spec.PolicyTemplates {
o := *template.ObjectDefinition.DeepCopy()
objects = append(objects, o)
}
}
for _, ob := range objects {
var pol policyv1.ConfigurationPolicy
err := json.Unmarshal(ob.DeepCopy().Raw, &pol)
if err != nil {
return uobjects, fmt.Errorf("failed to unmarshal ConfigurationPolicy: %w", err)
}
for _, ot := range pol.Spec.ObjectTemplates {
if strings.EqualFold(string(ot.ComplianceType), string(policyv1.MustNotHave)) {
continue
}
var object unstructured.Unstructured
err = object.UnmarshalJSON(ot.ObjectDefinition.DeepCopy().Raw)
if err != nil {
return uobjects, fmt.Errorf("failed to unmarshal ObjectTemplate: %w", err)
}
if len(objectLabels) > 0 {
if metadata, exists := object.Object["metadata"].(map[string]interface{}); exists {
if labels, exists := metadata["labels"].(map[string]interface{}); exists {
labelsFound := true
for oLabel, oValue := range objectLabels {
value, exists := labels[oLabel]
if !exists {
labelsFound = false
break
}
if oLabel == TargetOcpVersionLabel {
expectedValues := strings.Split(oValue, ",")
if !lo.Contains(expectedValues, value.(string)) {
log.Info(fmt.Sprintf("Found %s label in manifest %s %s, but its value %s is not in %v, skipping", oLabel, object.GetKind(), object.GetName(), value, expectedValues))
labelsFound = false
break
}
} else if oValue != value.(string) {
labelsFound = false
break
}
}
if !labelsFound {
continue
}
annotations, exists := metadata["annotations"].(map[string]interface{})
if !exists {
annotations = make(map[string]interface{})
}
applyType := getApplyType(string(ot.ComplianceType))
if applyType == "" {
return uobjects, fmt.Errorf("unsupported compliance type: %s", ot.ComplianceType)
}
annotations[common.ApplyTypeAnnotation] = applyType
} else {
continue
}
} else {
continue
}
}
object.Object["status"] = map[string]interface{}{} // remove status, we can't apply it
uobjects = append(uobjects, &object)
}
}
return uobjects, nil
}
func getApplyType(complianceType string) string {
var applyType string
if strings.EqualFold(complianceType, string(policyv1.MustHave)) {
applyType = common.ApplyTypeMerge
} else if strings.EqualFold(complianceType, string(policyv1.MustOnlyHave)) {
applyType = common.ApplyTypeReplace
}
return applyType
}
// getParentPolicyNameAndNamespace gets the parent policy name and namespace from a given child policy
// returns: []string a two-element slice which the first element is policy namespace and the second one is policy name
func getParentPolicyNameAndNamespace(childPolicyName string) ([]string, error) {
// The format of a child policy name is parent_policy_namespace.parent_policy_name.
// Extract the parent policy name and namespace by splitting the child policy name into two substrings separated by "."
// and we are safe to split with the separator "." as the namespace is disallowed to contain "."
res := strings.SplitN(childPolicyName, ".", 2)
if len(res) != 2 {
return nil, errors.New("child policy name " + childPolicyName + " is not valid.")
}
return res, nil
}
func sortPolicyMap(sortMap map[*policiesv1.Policy]int) []*policiesv1.Policy {
var keys []*policiesv1.Policy
for key := range sortMap {
keys = append(keys, key)
}
sort.Slice(keys, func(i, j int) bool {
// for equal elements, sort string alphabetically
if sortMap[keys[i]] == sortMap[keys[j]] {
return keys[i].Name < keys[j].Name
}
return sortMap[keys[i]] < sortMap[keys[j]]
})
return keys
}