Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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