Skip to content

Commit

Permalink
feat: add new validator to only check the usage of deprecate apis (#156)
Browse files Browse the repository at this point in the history
* feat: add new validator to only check the usage of deprecate apis

* applying review sugesstions
  • Loading branch information
camilamacedo86 authored Sep 16, 2021
1 parent a80624e commit e254156
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 201 deletions.
3 changes: 3 additions & 0 deletions pkg/validation/internal/community.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ const IndexImagePathKey = "index-path"
// where the bundle will be distributed
const ocpLabelindex = "com.redhat.openshift.versions"

// OCP version where the apis v1beta1 is no longer supported
const ocpVerV1beta1Unsupported = "4.9"

// CommunityOperatorValidator validates the bundle manifests against the required criteria to publish
// the projects on the community operators
//
Expand Down
81 changes: 1 addition & 80 deletions pkg/validation/internal/operatorhub.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,6 @@ import (
interfaces "github.com/operator-framework/api/pkg/validation/interfaces"
)

// k8sVersionKey defines the key which can be used by its consumers
// to inform what is the K8S version that should be used to do the tests against.
const k8sVersionKey = "k8s-version"

const minKubeVersionWarnMessage = "csv.Spec.minKubeVersion is not informed. It is recommended you provide this information. " +
"Otherwise, it would mean that your operator project can be distributed and installed in any cluster version " +
"available, which is not necessarily the case for all projects."

// OperatorHubValidator validates the bundle manifests against the required criteria to publish
// the projects on OperatorHub.io.
//
Expand Down Expand Up @@ -111,7 +103,7 @@ func validateBundleOperatorHub(bundle *manifests.Bundle, k8sVersion string) erro
result.Add(errors.WarnInvalidCSV(warn.Error(), bundle.CSV.GetName()))
}

errs, warns := validateHubDeprecatedAPIS(bundle, k8sVersion)
errs, warns := validateDeprecatedAPIS(bundle, k8sVersion)
for _, err := range errs {
result.Add(errors.ErrFailedValidation(err.Error(), bundle.CSV.GetName()))
}
Expand Down Expand Up @@ -153,77 +145,6 @@ func validateHubChannels(channels []string) error {
return nil
}

// validateHubDeprecatedAPIS will check if the operator bundle is using a deprecated or no longer supported k8s api
// Note if the k8s was informed via "k8s=1.22" it will be used. Otherwise, we will use the minKubeVersion in
// the CSV to do the checks. So, the criteria is >=minKubeVersion. By last, if the minKubeVersion is not provided
// then, we should consider the operator bundle is intend to work well in any Kubernetes version.
// Then, it means that:
//--optional-values="k8s-version=value" flag with a value => 1.16 <= 1.22 the validator will return result as warning.
//--optional-values="k8s-version=value" flag with a value => 1.22 the validator will return result as error.
//minKubeVersion >= 1.22 return the error result.
//minKubeVersion empty returns a warning since it would mean the same of allow install in any supported version
func validateHubDeprecatedAPIS(bundle *manifests.Bundle, versionProvided string) (errs, warns []error) {
// K8s version where the apis v1betav1 is no longer supported
const k8sVerV1betav1Unsupported = "1.22.0"
// K8s version where the apis v1betav1 was deprecated
const k8sVerV1betav1Deprecated = "1.16.0"
// semver of the K8s version where the apis v1betav1 is no longer supported to allow us compare
semVerK8sVerV1betav1Unsupported := semver.MustParse(k8sVerV1betav1Unsupported)
// semver of the K8s version where the apis v1betav1 is deprecated to allow us compare
semVerk8sVerV1betav1Deprecated := semver.MustParse(k8sVerV1betav1Deprecated)
// isVersionProvided defines if the k8s version to test against was or not informed
isVersionProvided := len(versionProvided) > 0

// Transform the key/option versionProvided in semver Version to compare
var semVerVersionProvided semver.Version
if isVersionProvided {
var err error
semVerVersionProvided, err = semver.ParseTolerant(versionProvided)
if err != nil {
errs = append(errs, fmt.Errorf("invalid value informed via the k8s key option : %s", versionProvided))
} else {
// we might want to return it as info instead of warning in the future.
warns = append(warns, fmt.Errorf("checking APIs against Kubernetes version : %s", versionProvided))
}
}

// Transform the spec minKubeVersion in semver Version to compare
var semverMinKube semver.Version
if len(bundle.CSV.Spec.MinKubeVersion) > 0 {
var err error
if semverMinKube, err = semver.ParseTolerant(bundle.CSV.Spec.MinKubeVersion); err != nil {
errs = append(errs, fmt.Errorf("unable to use csv.Spec.MinKubeVersion to verify the CRD/Webhook apis "+
"because it has an invalid value: %s", bundle.CSV.Spec.MinKubeVersion))
}
}

// if the k8s value was informed and it is >=1.16 we should check
// if the k8s value was not informed we also should check since the
// check should occurs with any minKubeVersion value:
// - if minKubeVersion empty then means that the project can be installed in any version
// - if minKubeVersion any version defined it means that we are considering install
// in any upper version from that where the check is always applied
if !isVersionProvided || semVerVersionProvided.GE(semVerk8sVerV1betav1Deprecated) {
deprecatedAPIs := getRemovedAPIsOn1_22From(bundle)
if len(deprecatedAPIs) > 0 {
deprecatedAPIsMessage := generateMessageWithDeprecatedAPIs(deprecatedAPIs)
// isUnsupported is true only if the key/value OR minKubeVersion were informed and are >= 1.22
isUnsupported := semVerVersionProvided.GE(semVerK8sVerV1betav1Unsupported) ||
semverMinKube.GE(semVerK8sVerV1betav1Unsupported)
// We only raise an error when the version >= 1.22 was informed via
// the k8s key/value option or is specifically defined in the CSV
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)
if isUnsupported {
errs = append(errs, msg)
} else {
warns = append(warns, msg)
}
}
}

return errs, warns
}

// validateHubCSVSpec will check the CSV against the criteria to publish an
// operator bundle in the OperatorHub.io
func validateHubCSVSpec(csv v1alpha1.ClusterServiceVersion) CSVChecks {
Expand Down
119 changes: 0 additions & 119 deletions pkg/validation/internal/operatorhub_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,125 +343,6 @@ func TestCheckSpecMinKubeVersion(t *testing.T) {
}
}

func TestValidateHubDeprecatedAPIS(t *testing.T) {
type args struct {
minKubeVersion string
k8sVersion string
directory string
}
tests := []struct {
name string
args args
wantError bool
wantWarning bool
errStrings []string
warnStrings []string
}{
{
name: "should not return error or warning when the k8sVersion is <= 1.15",
args: args{
k8sVersion: "1.15",
minKubeVersion: "",
directory: "./testdata/valid_bundle_v1beta1",
},
wantWarning: true,
warnStrings: []string{"checking APIs against Kubernetes version : 1.15"},
},
{
name: "should return a warning when has the CRD v1beta1 and minKubeVersion is informed",
args: args{
k8sVersion: "",
minKubeVersion: "1.11.3",
directory: "./testdata/valid_bundle_v1beta1",
},
wantWarning: true,
warnStrings: []string{"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 CRD: ([\"etcdbackups.etcd.database.coreos.com\" " +
"\"etcdclusters.etcd.database.coreos.com\" \"etcdrestores.etcd.database.coreos.com\"])"},
},
{
name: "should not return a warning or error when has minKubeVersion but the k8sVersion informed is <= 1.15",
args: args{
k8sVersion: "1.15",
minKubeVersion: "1.11.3",
directory: "./testdata/valid_bundle_v1beta1",
},
wantWarning: true,
warnStrings: []string{"checking APIs against Kubernetes version : 1.15"},
},
{
name: "should return an error when the k8sVersion is >= 1.22 and has the deprecated API",
args: args{
k8sVersion: "1.22",
minKubeVersion: "",
directory: "./testdata/valid_bundle_v1beta1",
},
wantError: true,
errStrings: []string{"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 CRD: ([\"etcdbackups.etcd.database.coreos.com\"" +
" \"etcdclusters.etcd.database.coreos.com\" \"etcdrestores.etcd.database.coreos.com\"])"},
wantWarning: true,
warnStrings: []string{"checking APIs against Kubernetes version : 1.22"},
},
{
name: "should return an error when the k8sVersion informed is invalid",
args: args{
k8sVersion: "invalid",
minKubeVersion: "",
directory: "./testdata/valid_bundle_v1beta1",
},
wantError: true,
errStrings: []string{"invalid value informed via the k8s key option : invalid"},
},
{
name: "should return an error when the csv.spec.minKubeVersion informed is invalid",
args: args{
minKubeVersion: "invalid",
directory: "./testdata/valid_bundle_v1beta1",
},
wantError: true,
wantWarning: true,
errStrings: []string{"unable to use csv.Spec.MinKubeVersion to verify the CRD/Webhook apis because it " +
"has an invalid value: invalid"},
warnStrings: []string{"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 CRD: ([\"etcdbackups.etcd.database.coreos.com\" " +
"\"etcdclusters.etcd.database.coreos.com\" \"etcdrestores.etcd.database.coreos.com\"])"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Validate the bundle object
bundle, err := manifests.GetBundleFromDir(tt.args.directory)
require.NoError(t, err)

bundle.CSV.Spec.MinKubeVersion = tt.args.minKubeVersion

errsResult, warnsResult := validateHubDeprecatedAPIS(bundle, tt.args.k8sVersion)

require.Equal(t, tt.wantWarning, len(warnsResult) > 0)
if tt.wantWarning {
require.Equal(t, len(tt.warnStrings), len(warnsResult))
for _, w := range warnsResult {
wString := w.Error()
require.Contains(t, tt.warnStrings, wString)
}
}

require.Equal(t, tt.wantError, len(errsResult) > 0)
if tt.wantError {
require.Equal(t, len(tt.errStrings), len(errsResult))
for _, err := range errsResult {
errString := err.Error()
require.Contains(t, tt.errStrings, errString)
}
}
})
}
}

func TestValidateHubChannels(t *testing.T) {
type args struct {
channels []string
Expand Down
138 changes: 136 additions & 2 deletions pkg/validation/internal/removed_apis.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,147 @@ package internal

import (
"fmt"
"github.com/blang/semver"

"github.com/operator-framework/api/pkg/manifests"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

"github.com/operator-framework/api/pkg/validation/errors"
interfaces "github.com/operator-framework/api/pkg/validation/interfaces"
)

// OCP version where the apis v1beta1 is no longer supported
const ocpVerV1beta1Unsupported = "4.9"
// k8sVersionKey defines the key which can be used by its consumers
// to inform what is the K8S version that should be used to do the tests against.
const k8sVersionKey = "k8s-version"

const minKubeVersionWarnMessage = "csv.Spec.minKubeVersion is not informed. It is recommended you provide this information. " +
"Otherwise, it would mean that your operator project can be distributed and installed in any cluster version " +
"available, which is not necessarily the case for all projects."

// K8s version where the apis v1betav1 is no longer supported
const k8sVerV1betav1Unsupported = "1.22.0"
// K8s version where the apis v1betav1 was deprecated
const k8sVerV1betav1Deprecated = "1.16.0"

// AlphaDeprecatedAPIsValidator validates if the bundles is using versions API version which are deprecate or
// removed in specific Kubernetes versions informed via optional key value `k8s-version`.
var AlphaDeprecatedAPIsValidator interfaces.Validator = interfaces.ValidatorFunc(validateDeprecatedAPIsValidator)

func validateDeprecatedAPIsValidator(objs ...interface{}) (results []errors.ManifestResult) {

// Obtain the k8s version if informed via the objects an optional
k8sVersion := ""
for _, obj := range objs {
switch obj.(type) {
case map[string]string:
k8sVersion = obj.(map[string]string)[k8sVersionKey]
if len(k8sVersion) > 0 {
break
}
}
}

for _, obj := range objs {
switch v := obj.(type) {
case *manifests.Bundle:
results = append(results, validateDeprecatedAPIs(v, k8sVersion))
}
}

return results
}

func validateDeprecatedAPIs(bundle *manifests.Bundle, k8sVersion string) errors.ManifestResult {
result := errors.ManifestResult{Name: bundle.Name}

if bundle == nil {
result.Add(errors.ErrInvalidBundle("Bundle is nil", nil))
return result
}

if bundle.CSV == nil {
result.Add(errors.ErrInvalidBundle("Bundle csv is nil", bundle.Name))
return result
}

errs, warns := validateDeprecatedAPIS(bundle, k8sVersion)
for _, err := range errs {
result.Add(errors.ErrFailedValidation(err.Error(), bundle.CSV.GetName()))
}
for _, warn := range warns {
result.Add(errors.WarnFailedValidation(warn.Error(), bundle.CSV.GetName()))
}

return result
}

// validateDeprecatedAPIS will check if the operator bundle is using a deprecated or no longer supported k8s api
// Note if the k8s was informed via "k8s=1.22" it will be used. Otherwise, we will use the minKubeVersion in
// the CSV to do the checks. So, the criteria is >=minKubeVersion. By last, if the minKubeVersion is not provided
// then, we should consider the operator bundle is intend to work well in any Kubernetes version.
// Then, it means that:
//--optional-values="k8s-version=value" flag with a value => 1.16 <= 1.22 the validator will return result as warning.
//--optional-values="k8s-version=value" flag with a value => 1.22 the validator will return result as error.
//minKubeVersion >= 1.22 return the error result.
//minKubeVersion empty returns a warning since it would mean the same of allow install in any supported version
func validateDeprecatedAPIS(bundle *manifests.Bundle, versionProvided string) (errs, warns []error) {

// semver of the K8s version where the apis v1betav1 is no longer supported to allow us compare
semVerK8sVerV1betav1Unsupported := semver.MustParse(k8sVerV1betav1Unsupported)
// semver of the K8s version where the apis v1betav1 is deprecated to allow us compare
semVerk8sVerV1betav1Deprecated := semver.MustParse(k8sVerV1betav1Deprecated)
// isVersionProvided defines if the k8s version to test against was or not informed
isVersionProvided := len(versionProvided) > 0

// Transform the key/option versionProvided in semver Version to compare
var semVerVersionProvided semver.Version
if isVersionProvided {
var err error
semVerVersionProvided, err = semver.ParseTolerant(versionProvided)
if err != nil {
errs = append(errs, fmt.Errorf("invalid value informed via the k8s key option : %s", versionProvided))
} else {
// we might want to return it as info instead of warning in the future.
warns = append(warns, fmt.Errorf("checking APIs against Kubernetes version : %s", versionProvided))
}
}

// Transform the spec minKubeVersion in semver Version to compare
var semverMinKube semver.Version
if len(bundle.CSV.Spec.MinKubeVersion) > 0 {
var err error
if semverMinKube, err = semver.ParseTolerant(bundle.CSV.Spec.MinKubeVersion); err != nil {
errs = append(errs, fmt.Errorf("unable to use csv.Spec.MinKubeVersion to verify the CRD/Webhook apis "+
"because it has an invalid value: %s", bundle.CSV.Spec.MinKubeVersion))
}
}

// if the k8s value was informed and it is >=1.16 we should check
// if the k8s value was not informed we also should check since the
// check should occurs with any minKubeVersion value:
// - if minKubeVersion empty then means that the project can be installed in any version
// - if minKubeVersion any version defined it means that we are considering install
// in any upper version from that where the check is always applied
if !isVersionProvided || semVerVersionProvided.GE(semVerk8sVerV1betav1Deprecated) {
deprecatedAPIs := getRemovedAPIsOn1_22From(bundle)
if len(deprecatedAPIs) > 0 {
deprecatedAPIsMessage := generateMessageWithDeprecatedAPIs(deprecatedAPIs)
// isUnsupported is true only if the key/value OR minKubeVersion were informed and are >= 1.22
isUnsupported := semVerVersionProvided.GE(semVerK8sVerV1betav1Unsupported) ||
semverMinKube.GE(semVerK8sVerV1betav1Unsupported)
// We only raise an error when the version >= 1.22 was informed via
// the k8s key/value option or is specifically defined in the CSV
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)
if isUnsupported {
errs = append(errs, msg)
} else {
warns = append(warns, msg)
}
}
}

return errs, warns
}

// generateMessageWithDeprecatedAPIs will return a list with the kind and the name
// of the resource which were found and required to be upgraded
Expand Down
Loading

0 comments on commit e254156

Please sign in to comment.