• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

openshift-kni / cluster-group-upgrades-operator / #3

20 Nov 2025 11:55AM UTC coverage: 61.674% (+17.2%) from 44.472%
#3

push

web-flow
chore(deps): update topology-aware-lifecycle-manager-precache-4-21 to e280b86 (#5031)

Image created from 'https://github.com/openshift-kni/cluster-group-upgrades-operator?rev=e0953ccb4'

Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com>
Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com>

4039 of 6549 relevant lines covered (61.67%)

0.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

66.45
/controllers/validation.go
1
package controllers
2

3
import (
4
        "errors"
5
        "fmt"
6

7
        "github.com/openshift-kni/cluster-group-upgrades-operator/controllers/utils"
8
        ranv1alpha1 "github.com/openshift-kni/cluster-group-upgrades-operator/pkg/api/clustergroupupgrades/v1alpha1"
9
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10
        "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
11
)
12

13
type ocpVersionInfo struct {
14
        upstream              string
15
        channel               string
16
        version               string
17
        image                 string
18
        clusterVersionCRFound bool
19
}
20

21
func extractOCPVersionInfoFromPolicies(policies []*unstructured.Unstructured) (ocpVersionInfo, error) {
1✔
22

1✔
23
        result := ocpVersionInfo{}
1✔
24

1✔
25
        // validate ClusterVersionGroupVersionKind and keep track to upstream, channel, version, image
1✔
26
        for _, policy := range policies {
2✔
27
                objects, err := stripPolicy(policy.Object)
1✔
28
                if err != nil {
1✔
29
                        return result, err
×
30
                }
×
31
                for _, object := range objects {
2✔
32
                        kind := object["kind"]
1✔
33
                        switch kind {
1✔
34
                        case utils.ClusterVersionGroupVersionKind().Kind:
1✔
35
                                _, foundSpec := object["spec"]
1✔
36
                                if !foundSpec || object["spec"] == nil {
1✔
37
                                        continue
×
38
                                }
39

40
                                if object["spec"].(map[string]interface{})["upstream"] != nil {
2✔
41
                                        nextUpstream := object["spec"].(map[string]interface{})["upstream"].(string)
1✔
42

1✔
43
                                        if nextUpstream == utils.Placeholder {
1✔
44
                                                return result, errors.New("templating cluster version fields not supported")
×
45
                                        }
×
46

47
                                        if result.upstream == "" {
2✔
48
                                                result.upstream = nextUpstream
1✔
49
                                        } else if result.upstream != nextUpstream {
1✔
50
                                                return result, errors.New("platform image defined more then once with conflicting upstream values")
×
51
                                        }
×
52
                                }
53

54
                                if object["spec"].(map[string]interface{})["channel"] != nil {
2✔
55
                                        nextChannel := object["spec"].(map[string]interface{})["channel"].(string)
1✔
56

1✔
57
                                        if nextChannel == utils.Placeholder {
1✔
58
                                                return result, errors.New("templating cluster version fields not supported")
×
59
                                        }
×
60

61
                                        if result.channel == "" {
2✔
62
                                                result.channel = nextChannel
1✔
63
                                        } else if result.channel != nextChannel {
1✔
64
                                                return result, errors.New("platform image defined more then once with conflicting channel values")
×
65
                                        }
×
66
                                }
67

68
                                if object["spec"].(map[string]interface{})["desiredUpdate"] != nil {
2✔
69
                                        if object["spec"].(map[string]interface{})["desiredUpdate"].(map[string]interface{})["version"] != nil {
2✔
70
                                                nextVersion := object["spec"].(map[string]interface{})["desiredUpdate"].(map[string]interface{})["version"].(string)
1✔
71

1✔
72
                                                if nextVersion == utils.Placeholder {
1✔
73
                                                        return result, errors.New("templating cluster version fields not supported")
×
74
                                                }
×
75

76
                                                if result.version == "" {
2✔
77
                                                        result.version = nextVersion
1✔
78
                                                } else if result.version != nextVersion {
1✔
79
                                                        return result, errors.New("platform image defined more then once with conflicting version values")
×
80
                                                }
×
81
                                        }
82
                                        if object["spec"].(map[string]interface{})["desiredUpdate"].(map[string]interface{})["image"] != nil {
1✔
83
                                                nextImage := object["spec"].(map[string]interface{})["desiredUpdate"].(map[string]interface{})["image"].(string)
×
84

×
85
                                                if nextImage == utils.Placeholder {
×
86
                                                        return result, errors.New("templating cluster version fields not supported")
×
87
                                                }
×
88

89
                                                if result.image == "" {
×
90
                                                        result.image = nextImage
×
91
                                                } else if result.image != nextImage {
×
92
                                                        return result, errors.New("platform image defined more then once with conflicting image values")
×
93
                                                }
×
94
                                        }
95
                                }
96

97
                                result.clusterVersionCRFound = true
1✔
98
                        default:
1✔
99
                                continue
1✔
100
                        }
101
                }
102
        }
103
        return result, nil
1✔
104
}
105

106
// extractOCPImageFromPolicies validates that there's ClusterVersion policy, validates the content of ClusterVersion and extracts Image if needed
107
func (r *ClusterGroupUpgradeReconciler) extractOCPImageFromPolicies(
108
        policies []*unstructured.Unstructured) (string, error) {
1✔
109

1✔
110
        versionInfo, err := extractOCPVersionInfoFromPolicies(policies)
1✔
111

1✔
112
        if err != nil {
1✔
113
                return "", err
×
114
        }
×
115

116
        if !versionInfo.clusterVersionCRFound {
2✔
117
                return "", nil
1✔
118
        }
1✔
119

120
        // return early if policy is valid and user provided .Spec.DesiredUpdate.image in ClusterVersion
121
        if versionInfo.image != "" {
×
122
                return versionInfo.image, nil
×
123
        }
×
124

125
        image, err := r.getImageForVersionFromUpdateGraph(versionInfo.upstream, versionInfo.channel, versionInfo.version)
×
126
        if err != nil {
×
127
                return "", err
×
128
        }
×
129
        if image == "" {
×
130
                return "", errors.New("unable to find platform image for specified upstream, channel, and version")
×
131
        }
×
132

133
        return image, nil
×
134
}
135

136
func (r *ClusterGroupUpgradeReconciler) validateOpenshiftUpgradeVersion(
137
        clusterGroupUpgrade *ranv1alpha1.ClusterGroupUpgrade, policies []*unstructured.Unstructured) error {
1✔
138

1✔
139
        versionInfo, err := extractOCPVersionInfoFromPolicies(policies)
1✔
140

1✔
141
        if err == nil {
2✔
142
                if !versionInfo.clusterVersionCRFound || versionInfo.image != "" {
2✔
143
                        return nil
1✔
144
                }
1✔
145

146
                // Using a few temporary variables here makes the if below much more readable
147
                versionInfoContainsEmptyString := versionInfo.upstream == "" || versionInfo.channel == "" || versionInfo.version == ""
1✔
148
                versionInfoContainsTemplate := utils.ContainsTemplates(versionInfo.upstream) || utils.ContainsTemplates(versionInfo.channel) || utils.ContainsTemplates(versionInfo.version)
1✔
149
                versionInfoContainsPlaceholder := versionInfo.upstream == utils.Placeholder || versionInfo.channel == utils.Placeholder || versionInfo.version == utils.Placeholder
1✔
150

1✔
151
                // Check for all the required parameters needed to make the update graph HTTP call and retrieve the image
1✔
152
                // nolint: gocritic
1✔
153
                if versionInfoContainsEmptyString {
1✔
154
                        err = errors.New("policy with ClusterVersion must have upstream, channel, and version when image is not provided")
×
155
                } else if versionInfoContainsTemplate || versionInfoContainsPlaceholder {
1✔
156
                        if clusterGroupUpgrade.Spec.PreCaching {
×
157
                                // return error if the fields contain templates
×
158
                                err = errors.New("templatized ClusterVersion fields not supported with precaching")
×
159
                        }
×
160
                } else {
1✔
161
                        _, err = r.getImageForVersionFromUpdateGraph(versionInfo.upstream, versionInfo.channel, versionInfo.version)
1✔
162
                }
1✔
163
        }
164

165
        if err != nil {
1✔
166
                utils.SetStatusCondition(
×
167
                        &clusterGroupUpgrade.Status.Conditions,
×
168
                        utils.ConditionTypes.Validated,
×
169
                        utils.ConditionReasons.InvalidPlatformImage,
×
170
                        metav1.ConditionFalse,
×
171
                        err.Error(),
×
172
                )
×
173
        }
×
174

175
        return err
1✔
176
}
177

178
func indexOf(element ranv1alpha1.ManagedPolicyForUpgrade, data []ranv1alpha1.ManagedPolicyForUpgrade) (int, error) {
1✔
179
        for k, v := range data {
2✔
180
                if element.Name == v.Name && element.Namespace == v.Namespace {
2✔
181
                        return k, nil
1✔
182
                }
1✔
183
        }
184
        return -1, errors.New("element not found in data")
×
185
}
186

187
func (r *ClusterGroupUpgradeReconciler) validatePoliciesDependenciesOrder(
188
        clusterGroupUpgrade *ranv1alpha1.ClusterGroupUpgrade, managedPoliciesForUpgrade []*unstructured.Unstructured) error {
1✔
189
        for _, managedPolicy := range managedPoliciesForUpgrade {
2✔
190
                managedPolicyIndex, _ := indexOf(
1✔
191
                        ranv1alpha1.ManagedPolicyForUpgrade{Name: managedPolicy.GetName(), Namespace: managedPolicy.GetNamespace()},
1✔
192
                        clusterGroupUpgrade.Status.ManagedPoliciesForUpgrade)
1✔
193
                specObject := managedPolicy.Object["spec"].(map[string]interface{})
1✔
194
                dependencies := specObject["dependencies"]
1✔
195
                if dependencies == nil {
2✔
196
                        continue
1✔
197
                }
198
                dependenciesArr := dependencies.([]interface{})
1✔
199

1✔
200
                for _, d := range dependenciesArr {
2✔
201
                        name := d.(map[string]interface{})["name"]
1✔
202
                        namespace := d.(map[string]interface{})["namespace"]
1✔
203
                        dependecyIndex, err := indexOf(
1✔
204
                                ranv1alpha1.ManagedPolicyForUpgrade{Name: name.(string), Namespace: namespace.(string)},
1✔
205
                                clusterGroupUpgrade.Status.ManagedPoliciesForUpgrade)
1✔
206
                        if err == nil && dependecyIndex > managedPolicyIndex {
2✔
207
                                utils.SetStatusCondition(
1✔
208
                                        &clusterGroupUpgrade.Status.Conditions,
1✔
209
                                        utils.ConditionTypes.Validated,
1✔
210
                                        utils.ConditionReasons.UnresolvableDenpendency,
1✔
211
                                        metav1.ConditionFalse,
1✔
212
                                        fmt.Sprintf("Managed Policy %s depends on %s, which is to be remediated later", managedPolicy.GetName(), name),
1✔
213
                                )
1✔
214
                                return errors.New("invalid dependency order")
1✔
215
                        }
1✔
216
                }
217
        }
218
        return nil
1✔
219
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc