Skip to content

Commit 2c7ca5c

Browse files
camilamacedo86timflannagan
authored andcommitted
additionals and improvements to dealing with deprecated API(s) (openshift#120)
- add new validator ( Community Operator ) - improve the operatorhub.io validator to check the possible deprecated apis kinds mapped on the project Upstream-repository: api Upstream-commit: b9384fb125e65953a102a5098592d2235010e63e
1 parent ac555ac commit 2c7ca5c

File tree

38 files changed

+1689
-16
lines changed

38 files changed

+1689
-16
lines changed

staging/api/crds/zz_defs.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
package internal
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"github.com/blang/semver"
7+
"io/ioutil"
8+
"os"
9+
"strings"
10+
11+
"github.com/operator-framework/api/pkg/manifests"
12+
"github.com/operator-framework/api/pkg/validation/errors"
13+
interfaces "github.com/operator-framework/api/pkg/validation/interfaces"
14+
)
15+
16+
// IndexImagePathKey defines the key which can be used by its consumers
17+
// to inform where their index image path is to be checked
18+
const IndexImagePathKey = "index-path"
19+
20+
// ocpLabelindex defines the OCP label which allow configure the OCP versions
21+
// where the bundle will be distributed
22+
const ocpLabelindex = "com.redhat.openshift.versions"
23+
24+
// CommunityOperatorValidator validates the bundle manifests against the required criteria to publish
25+
// the projects on the community operators
26+
//
27+
// Note that this validator allows to receive a List of optional values as key=values. Currently, only the
28+
// `index-path` key is allowed. If informed, it will check the labels on the image index according to its criteria.
29+
var CommunityOperatorValidator interfaces.Validator = interfaces.ValidatorFunc(communityValidator)
30+
31+
func communityValidator(objs ...interface{}) (results []errors.ManifestResult) {
32+
33+
// Obtain the k8s version if informed via the objects an optional
34+
var indexImagePath = ""
35+
for _, obj := range objs {
36+
switch obj.(type) {
37+
case map[string]string:
38+
indexImagePath = obj.(map[string]string)[IndexImagePathKey]
39+
if len(indexImagePath) > 0 {
40+
break
41+
}
42+
}
43+
}
44+
45+
for _, obj := range objs {
46+
switch v := obj.(type) {
47+
case *manifests.Bundle:
48+
results = append(results, validateCommunityBundle(v, indexImagePath))
49+
}
50+
}
51+
52+
return results
53+
}
54+
55+
type CommunityOperatorChecks struct {
56+
bundle manifests.Bundle
57+
indexImagePath string
58+
indexImage string
59+
errs []error
60+
warns []error
61+
}
62+
63+
// validateCommunityBundle will check the bundle against the community-operator criterias
64+
func validateCommunityBundle(bundle *manifests.Bundle, indexImagePath string) errors.ManifestResult {
65+
result := errors.ManifestResult{Name: bundle.Name}
66+
if bundle == nil {
67+
result.Add(errors.ErrInvalidBundle("Bundle is nil", nil))
68+
return result
69+
}
70+
71+
if bundle.CSV == nil {
72+
result.Add(errors.ErrInvalidBundle("Bundle csv is nil", bundle.Name))
73+
return result
74+
}
75+
76+
checks := CommunityOperatorChecks{bundle: *bundle, indexImagePath: indexImagePath, errs: []error{}, warns: []error{}}
77+
78+
deprecatedAPIs := getRemovedAPIsOn1_22From(bundle)
79+
// Check if has deprecated apis then, check the olm.maxOpenShiftVersion property
80+
if len(deprecatedAPIs) > 0 {
81+
deprecatedAPIsMessage := generateMessageWithDeprecatedAPIs(deprecatedAPIs)
82+
checks = checkMaxOpenShiftVersion(checks, deprecatedAPIsMessage)
83+
checks = checkOCPLabelsWithHasDeprecatedAPIs(checks, deprecatedAPIsMessage)
84+
for _, err := range checks.errs {
85+
result.Add(errors.ErrInvalidCSV(err.Error(), bundle.CSV.GetName()))
86+
}
87+
for _, warn := range checks.warns {
88+
result.Add(errors.WarnInvalidCSV(warn.Error(), bundle.CSV.GetName()))
89+
}
90+
}
91+
92+
return result
93+
}
94+
95+
type propertiesAnnotation struct {
96+
Type string
97+
Value string
98+
}
99+
100+
// checkMaxOpenShiftVersion will verify if the OpenShiftVersion property was informed
101+
func checkMaxOpenShiftVersion(checks CommunityOperatorChecks, v1beta1MsgForResourcesFound string) CommunityOperatorChecks {
102+
// Ensure that has the OCPMaxAnnotation
103+
const olmproperties = "olm.properties"
104+
const olmmaxOpenShiftVersion = "olm.maxOpenShiftVersion"
105+
semVerOCPV1beta1Unsupported, _ := semver.ParseTolerant(ocpVerV1beta1Unsupported)
106+
107+
properties := checks.bundle.CSV.Annotations[olmproperties]
108+
if len(properties) == 0 {
109+
checks.errs = append(checks.errs, fmt.Errorf("csv.Annotations not specified %s for an "+
110+
"OCP version < %s. This annotation is required to prevent the user from upgrading their OCP cluster "+
111+
"before they have installed a version of their operator which is compatible with %s. This bundle is %s which are no "+
112+
"longer supported on %s. Migrate the API(s) for %s or use the annotation",
113+
olmmaxOpenShiftVersion,
114+
ocpVerV1beta1Unsupported,
115+
ocpVerV1beta1Unsupported,
116+
k8sApiDeprecatedInfo,
117+
ocpVerV1beta1Unsupported,
118+
v1beta1MsgForResourcesFound))
119+
return checks
120+
}
121+
122+
var properList []propertiesAnnotation
123+
if err := json.Unmarshal([]byte(properties), &properList); err != nil {
124+
checks.errs = append(checks.errs, fmt.Errorf("csv.Annotations has an invalid value specified for %s. "+
125+
"Please, check the value (%s) and ensure that it is an array such as: "+
126+
"\"olm.properties\": '[{\"type\": \"key name\", \"value\": \"key value\"}]'",
127+
olmproperties, properties))
128+
return checks
129+
}
130+
131+
hasOlmMaxOpenShiftVersion := false
132+
olmMaxOpenShiftVersionValue := ""
133+
for _, v := range properList {
134+
if v.Type == olmmaxOpenShiftVersion {
135+
hasOlmMaxOpenShiftVersion = true
136+
olmMaxOpenShiftVersionValue = v.Value
137+
break
138+
}
139+
}
140+
141+
if !hasOlmMaxOpenShiftVersion {
142+
checks.errs = append(checks.errs, fmt.Errorf("csv.Annotations.%s with the "+
143+
"key `%s` and a value with an OCP version which is < %s is required for any operator "+
144+
"bundle that is %s. Migrate the API(s) for %s or use the annotation",
145+
olmproperties,
146+
olmmaxOpenShiftVersion,
147+
ocpVerV1beta1Unsupported,
148+
k8sApiDeprecatedInfo,
149+
v1beta1MsgForResourcesFound))
150+
return checks
151+
}
152+
153+
semVerVersionMaxOcp, err := semver.ParseTolerant(olmMaxOpenShiftVersionValue)
154+
if err != nil {
155+
checks.errs = append(checks.errs, fmt.Errorf("csv.Annotations.%s has an invalid value."+
156+
"Unable to parse (%s) using semver : %s",
157+
olmproperties, olmMaxOpenShiftVersionValue, err))
158+
return checks
159+
}
160+
161+
if semVerVersionMaxOcp.GE(semVerOCPV1beta1Unsupported) {
162+
checks.errs = append(checks.errs, fmt.Errorf("csv.Annotations.%s with the "+
163+
"key and value for %s has the OCP version value %s which is >= of %s. This bundle is %s. "+
164+
"Migrate the API(s) for %s "+
165+
"or inform in this property an OCP version which is < %s",
166+
olmproperties,
167+
olmmaxOpenShiftVersion,
168+
olmMaxOpenShiftVersionValue,
169+
ocpVerV1beta1Unsupported,
170+
k8sApiDeprecatedInfo,
171+
v1beta1MsgForResourcesFound,
172+
ocpVerV1beta1Unsupported))
173+
return checks
174+
}
175+
176+
return checks
177+
}
178+
179+
// checkOCPLabels will ensure that OCP labels are set and with a ocp target < 4.9
180+
func checkOCPLabelsWithHasDeprecatedAPIs(checks CommunityOperatorChecks, deprecatedAPImsg string) CommunityOperatorChecks {
181+
// Note that we cannot make mandatory because the package format still valid
182+
if len(checks.indexImagePath) == 0 && len(checks.indexImage) == 0 {
183+
checks.warns = append(checks.errs, fmt.Errorf("please, inform the path of "+
184+
"its index image file via the the optional key values and the key %s to allow this validator check the labels "+
185+
"configuration or migrate the API(s) for %s. "+
186+
"(e.g. %s=./mypath/bundle.Dockerfile). This bundle is %s ",
187+
IndexImagePathKey,
188+
deprecatedAPImsg,
189+
IndexImagePathKey,
190+
k8sApiDeprecatedInfo))
191+
return checks
192+
}
193+
194+
return validateImageFile(checks, deprecatedAPImsg)
195+
}
196+
197+
func validateImageFile(checks CommunityOperatorChecks, deprecatedAPImsg string) CommunityOperatorChecks {
198+
if len(checks.indexImagePath) == 0 {
199+
return checks
200+
}
201+
202+
info, err := os.Stat(checks.indexImagePath)
203+
if err != nil {
204+
checks.errs = append(checks.errs, fmt.Errorf("the index image in the path "+
205+
"(%s) was not found. Please, inform the path of the bundle operator index image via the the optional key values and the key %s. "+
206+
"(e.g. %s=./mypath/bundle.Dockerfile). Error : %s", checks.indexImagePath, IndexImagePathKey, IndexImagePathKey, err))
207+
return checks
208+
}
209+
if info.IsDir() {
210+
checks.errs = append(checks.errs, fmt.Errorf("the index image in the path "+
211+
"(%s) is not file. Please, inform the path of its index image via the the optional key values and the key %s. "+
212+
"(e.g. %s=./mypath/bundle.Dockerfile). The value informed is a diretory and not a file", checks.indexImagePath, IndexImagePathKey, IndexImagePathKey))
213+
return checks
214+
}
215+
216+
b, err := ioutil.ReadFile(checks.indexImagePath)
217+
if err != nil {
218+
checks.errs = append(checks.errs, fmt.Errorf("unable to read the index image in the path "+
219+
"(%s). Error : %s", checks.indexImagePath, err))
220+
return checks
221+
}
222+
223+
indexPathContent := string(b)
224+
hasOCPLabel := strings.Contains(indexPathContent, ocpLabelindex)
225+
if hasOCPLabel {
226+
semVerOCPV1beta1Unsupported, _ := semver.ParseTolerant(ocpVerV1beta1Unsupported)
227+
// the OCP range informed cannot allow carry on to OCP 4.9+
228+
line := strings.Split(indexPathContent, "\n")
229+
for i := 0; i < len(line); i++ {
230+
if strings.Contains(line[i], ocpLabelindex) {
231+
if !strings.Contains(line[i], "=") {
232+
checks.errs = append(checks.errs, fmt.Errorf("invalid syntax (%s) on the LABEL %s. Migrate the API(s) "+
233+
"for %s or use the OCP labels. (e.g. LABEL %s='4.6-4.8')",
234+
line[i],
235+
deprecatedAPImsg,
236+
ocpLabelindex,
237+
ocpLabelindex))
238+
return checks
239+
}
240+
241+
value := strings.Split(line[i], "=")
242+
indexRange := value[1]
243+
doubleCote := "\""
244+
singleCote := "'"
245+
indexRange = strings.ReplaceAll(indexRange, singleCote, "")
246+
indexRange = strings.ReplaceAll(indexRange, doubleCote, "")
247+
if len(indexRange) > 1 {
248+
// if has the = then, the value needs to be < 4.9
249+
if strings.Contains(indexRange, "=") {
250+
version := strings.Split(indexRange, "=")[1]
251+
verParsed, err := semver.ParseTolerant(version)
252+
if err != nil {
253+
checks.errs = append(checks.errs, fmt.Errorf("unable to parse the value (%s) on (%s)",
254+
version, ocpLabelindex))
255+
return checks
256+
}
257+
258+
if verParsed.GE(semVerOCPV1beta1Unsupported) {
259+
checks.errs = append(checks.errs, fmt.Errorf("this bundle is %s. Migrate the API(s) "+
260+
"for %s or use the OCP labels for compatible version(s). (e.g. LABEL %s='=v4.8')",
261+
k8sApiDeprecatedInfo,
262+
deprecatedAPImsg,
263+
ocpLabelindex))
264+
return checks
265+
}
266+
} else {
267+
// if not has not the = then the value needs contains - value less < 4.9
268+
if !strings.Contains(indexRange, "-") {
269+
checks.errs = append(checks.errs, fmt.Errorf("this bundle is %s. "+
270+
"The %s allows to distribute it on >= %s. Migrate the API(s) for "+
271+
"%s or provide comatible version(s) via the labels. (e.g. LABEL %s='4.6-4.8')",
272+
deprecatedAPImsg,
273+
indexRange,
274+
ocpVerV1beta1Unsupported,
275+
deprecatedAPImsg,
276+
ocpLabelindex))
277+
return checks
278+
}
279+
280+
version := strings.Split(indexRange, "-")[1]
281+
verParsed, err := semver.ParseTolerant(version)
282+
if err != nil {
283+
checks.errs = append(checks.errs, fmt.Errorf("unable to parse the value (%s) on (%s)",
284+
version, ocpLabelindex))
285+
return checks
286+
}
287+
288+
if verParsed.GE(semVerOCPV1beta1Unsupported) {
289+
checks.errs = append(checks.errs, fmt.Errorf("this bundle is %s. Upgrade the APIs from "+
290+
"(v1beta1) to (v1) for %s or provide com[atible version(s) via the labels. (e.g. LABEL %s='4.6-4.8')",
291+
k8sApiDeprecatedInfo,
292+
deprecatedAPImsg,
293+
ocpLabelindex))
294+
return checks
295+
}
296+
297+
}
298+
} else {
299+
checks.errs = append(checks.errs, fmt.Errorf("unable to get the range informed on %s",
300+
ocpLabelindex))
301+
return checks
302+
}
303+
break
304+
}
305+
}
306+
} else {
307+
checks.errs = append(checks.errs, fmt.Errorf("this bundle is %s. Migrate the APIs "+
308+
"for %s or provide compatible version(s) via the labels. (e.g. LABEL %s='4.6-4.8')",
309+
k8sApiDeprecatedInfo,
310+
deprecatedAPImsg,
311+
ocpLabelindex))
312+
return checks
313+
}
314+
return checks
315+
}

0 commit comments

Comments
 (0)