@@ -2,13 +2,147 @@ package internal
22
33import (
44 "fmt"
5+ "github.com/blang/semver"
56
67 "github.com/operator-framework/api/pkg/manifests"
78 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
9+
10+ "github.com/operator-framework/api/pkg/validation/errors"
11+ interfaces "github.com/operator-framework/api/pkg/validation/interfaces"
812)
913
10- // OCP version where the apis v1beta1 is no longer supported
11- const ocpVerV1beta1Unsupported = "4.9"
14+ // k8sVersionKey defines the key which can be used by its consumers
15+ // to inform what is the K8S version that should be used to do the tests against.
16+ const k8sVersionKey = "k8s-version"
17+
18+ const minKubeVersionWarnMessage = "csv.Spec.minKubeVersion is not informed. It is recommended you provide this information. " +
19+ "Otherwise, it would mean that your operator project can be distributed and installed in any cluster version " +
20+ "available, which is not necessarily the case for all projects."
21+
22+ // K8s version where the apis v1betav1 is no longer supported
23+ const k8sVerV1betav1Unsupported = "1.22.0"
24+ // K8s version where the apis v1betav1 was deprecated
25+ const k8sVerV1betav1Deprecated = "1.16.0"
26+
27+ // AlphaDeprecatedAPIsValidator validates if the bundles is using versions API version which are deprecate or
28+ // removed in specific Kubernetes versions informed via optional key value `k8s-version`.
29+ var AlphaDeprecatedAPIsValidator interfaces.Validator = interfaces .ValidatorFunc (validateDeprecatedAPIsValidator )
30+
31+ func validateDeprecatedAPIsValidator (objs ... interface {}) (results []errors.ManifestResult ) {
32+
33+ // Obtain the k8s version if informed via the objects an optional
34+ k8sVersion := ""
35+ for _ , obj := range objs {
36+ switch obj .(type ) {
37+ case map [string ]string :
38+ k8sVersion = obj .(map [string ]string )[k8sVersionKey ]
39+ if len (k8sVersion ) > 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 , validateDeprecatedAPIs (v , k8sVersion ))
49+ }
50+ }
51+
52+ return results
53+ }
54+
55+ func validateDeprecatedAPIs (bundle * manifests.Bundle , k8sVersion string ) errors.ManifestResult {
56+ result := errors.ManifestResult {Name : bundle .Name }
57+
58+ if bundle == nil {
59+ result .Add (errors .ErrInvalidBundle ("Bundle is nil" , nil ))
60+ return result
61+ }
62+
63+ if bundle .CSV == nil {
64+ result .Add (errors .ErrInvalidBundle ("Bundle csv is nil" , bundle .Name ))
65+ return result
66+ }
67+
68+ errs , warns := validateDeprecatedAPIS (bundle , k8sVersion )
69+ for _ , err := range errs {
70+ result .Add (errors .ErrFailedValidation (err .Error (), bundle .CSV .GetName ()))
71+ }
72+ for _ , warn := range warns {
73+ result .Add (errors .WarnFailedValidation (warn .Error (), bundle .CSV .GetName ()))
74+ }
75+
76+ return result
77+ }
78+
79+ // validateDeprecatedAPIS will check if the operator bundle is using a deprecated or no longer supported k8s api
80+ // Note if the k8s was informed via "k8s=1.22" it will be used. Otherwise, we will use the minKubeVersion in
81+ // the CSV to do the checks. So, the criteria is >=minKubeVersion. By last, if the minKubeVersion is not provided
82+ // then, we should consider the operator bundle is intend to work well in any Kubernetes version.
83+ // Then, it means that:
84+ //--optional-values="k8s-version=value" flag with a value => 1.16 <= 1.22 the validator will return result as warning.
85+ //--optional-values="k8s-version=value" flag with a value => 1.22 the validator will return result as error.
86+ //minKubeVersion >= 1.22 return the error result.
87+ //minKubeVersion empty returns a warning since it would mean the same of allow install in any supported version
88+ func validateDeprecatedAPIS (bundle * manifests.Bundle , versionProvided string ) (errs , warns []error ) {
89+
90+ // semver of the K8s version where the apis v1betav1 is no longer supported to allow us compare
91+ semVerK8sVerV1betav1Unsupported := semver .MustParse (k8sVerV1betav1Unsupported )
92+ // semver of the K8s version where the apis v1betav1 is deprecated to allow us compare
93+ semVerk8sVerV1betav1Deprecated := semver .MustParse (k8sVerV1betav1Deprecated )
94+ // isVersionProvided defines if the k8s version to test against was or not informed
95+ isVersionProvided := len (versionProvided ) > 0
96+
97+ // Transform the key/option versionProvided in semver Version to compare
98+ var semVerVersionProvided semver.Version
99+ if isVersionProvided {
100+ var err error
101+ semVerVersionProvided , err = semver .ParseTolerant (versionProvided )
102+ if err != nil {
103+ errs = append (errs , fmt .Errorf ("invalid value informed via the k8s key option : %s" , versionProvided ))
104+ } else {
105+ // we might want to return it as info instead of warning in the future.
106+ warns = append (warns , fmt .Errorf ("checking APIs against Kubernetes version : %s" , versionProvided ))
107+ }
108+ }
109+
110+ // Transform the spec minKubeVersion in semver Version to compare
111+ var semverMinKube semver.Version
112+ if len (bundle .CSV .Spec .MinKubeVersion ) > 0 {
113+ var err error
114+ if semverMinKube , err = semver .ParseTolerant (bundle .CSV .Spec .MinKubeVersion ); err != nil {
115+ errs = append (errs , fmt .Errorf ("unable to use csv.Spec.MinKubeVersion to verify the CRD/Webhook apis " +
116+ "because it has an invalid value: %s" , bundle .CSV .Spec .MinKubeVersion ))
117+ }
118+ }
119+
120+ // if the k8s value was informed and it is >=1.16 we should check
121+ // if the k8s value was not informed we also should check since the
122+ // check should occurs with any minKubeVersion value:
123+ // - if minKubeVersion empty then means that the project can be installed in any version
124+ // - if minKubeVersion any version defined it means that we are considering install
125+ // in any upper version from that where the check is always applied
126+ if ! isVersionProvided || semVerVersionProvided .GE (semVerk8sVerV1betav1Deprecated ) {
127+ deprecatedAPIs := getRemovedAPIsOn1_22From (bundle )
128+ if len (deprecatedAPIs ) > 0 {
129+ deprecatedAPIsMessage := generateMessageWithDeprecatedAPIs (deprecatedAPIs )
130+ // isUnsupported is true only if the key/value OR minKubeVersion were informed and are >= 1.22
131+ isUnsupported := semVerVersionProvided .GE (semVerK8sVerV1betav1Unsupported ) ||
132+ semverMinKube .GE (semVerK8sVerV1betav1Unsupported )
133+ // We only raise an error when the version >= 1.22 was informed via
134+ // the k8s key/value option or is specifically defined in the CSV
135+ msg := fmt .Errorf ("this bundle is using APIs which were deprecated and removed in v1.22. More info: https://kubernetes.io/docs/reference/using-api/deprecation-guide/#v1-22. Migrate the API(s) for %s" , deprecatedAPIsMessage )
136+ if isUnsupported {
137+ errs = append (errs , msg )
138+ } else {
139+ warns = append (warns , msg )
140+ }
141+ }
142+ }
143+
144+ return errs , warns
145+ }
12146
13147// generateMessageWithDeprecatedAPIs will return a list with the kind and the name
14148// of the resource which were found and required to be upgraded
0 commit comments