Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ change: move check bundle name to the Good Practices validator #238

Merged
merged 1 commit into from
Jun 24, 2022
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
38 changes: 37 additions & 1 deletion pkg/validation/internal/good_practices.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package internal

import (
goerrors "errors"
"fmt"
"regexp"
"strings"

goerrors "errors"
"github.com/blang/semver/v4"

"github.com/operator-framework/api/pkg/manifests"
"github.com/operator-framework/api/pkg/operators/v1alpha1"
operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
Expand All @@ -30,6 +33,9 @@ import (
// b) "An Operator shouldn't deploy or manage other operators (such patterns are known as meta or super operators or include CRDs in its Operands). It's the Operator Lifecycle Manager's job to manage the deployment and lifecycle of operators. For further information check Dependency Resolution: https://olm.operatorframework.io/docs/concepts/olm-architecture/dependency-resolution/"
//
// WARNING: if you create CRD's via the reconciliations or via the Operands then, OLM cannot handle CRDs migration and update, validation.
// - The bundle name (CSV.metadata.name) does not follow the naming convention: <operator-name>.v<semver> e.g. memcached-operator.v0.0.1
//
// NOTE: The bundle name must be 63 characters or less because it will be used as k8s ownerref label which only allows max of 63 characters.
var GoodPracticesValidator interfaces.Validator = interfaces.ValidatorFunc(goodPracticesValidator)

func goodPracticesValidator(objs ...interface{}) (results []errors.ManifestResult) {
Expand Down Expand Up @@ -60,6 +66,8 @@ func validateGoodPracticesFrom(bundle *manifests.Bundle) errors.ManifestResult {
warns = append(warns, validateCrdDescriptions(bundle.CSV.Spec.CustomResourceDefinitions)...)
warns = append(warns, validateHubChannels(bundle))
warns = append(warns, validateRBACForCRDsWith(bundle.CSV))
warns = append(warns, checkBundleName(bundle.CSV)...)

for _, err := range errs {
if err != nil {
result.Add(errors.ErrFailedValidation(err.Error(), bundle.CSV.GetName()))
Expand Down Expand Up @@ -96,6 +104,34 @@ func validateResourceRequests(csv *operatorsv1alpha1.ClusterServiceVersion) (err
return errs, warns
}

// checkBundleName will validate the operator bundle name informed via CSV.metadata.name.
// The motivation for the following check is to ensure that operators authors knows that operator bundles names should
// follow a name and versioning convention
func checkBundleName(csv *operatorsv1alpha1.ClusterServiceVersion) []error {
var warns []error
// Check if is following the semver
re := regexp.MustCompile("([0-9]+)\\.([0-9]+)\\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?(?:\\+[0-9A-Za-z-]+)?$")
grokspawn marked this conversation as resolved.
Show resolved Hide resolved
match := re.FindStringSubmatch(csv.Name)

if len(match) > 0 {
if _, err := semver.Parse(match[0]); err != nil {
warns = append(warns, fmt.Errorf("csv.metadata.Name %v is not following the versioning "+
"convention (MAJOR.MINOR.PATCH e.g 0.0.1): https://semver.org/", csv.Name))
}
} else {
warns = append(warns, fmt.Errorf("csv.metadata.Name %v is not following the versioning "+
"convention (MAJOR.MINOR.PATCH e.g 0.0.1): https://semver.org/", csv.Name))
}

// Check if its following the name convention
if len(strings.Split(csv.Name, ".v")) != 2 {
warns = append(warns, fmt.Errorf("csv.metadata.Name %v is not following the recommended "+
"naming convention: <operator-name>.v<semver> e.g. memcached-operator.v0.0.1", csv.Name))
}

return warns
}

// validateHubChannels will check the channels. The motivation for the following check is to ensure that operators
// authors knows if their operator bundles are or not respecting the Naming Convention Rules.
// However, the operator authors still able to choose the names as please them.
Expand Down
74 changes: 74 additions & 0 deletions pkg/validation/internal/good_practices_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,3 +242,77 @@ func TestValidateRBACForCRDsWith(t *testing.T) {
})
}
}

func TestCheckBundleName(t *testing.T) {
type args struct {
bundleName string
}
tests := []struct {
name string
args args
wantWarning bool
errStrings []string
warnStrings []string
}{
{
name: "should work with valid bundle name",
args: args{bundleName: "memcached-operator.v0.9.2"},
},
{
name: "should return a warning when the bundle name is not following the convention",
args: args{bundleName: "memcached-operator0.9.2"},
wantWarning: true,
warnStrings: []string{"csv.metadata.Name memcached-operator0.9.2 is not following the recommended naming " +
"convention: <operator-name>.v<semver> e.g. memcached-operator.v0.0.1"},
},
{
name: "should return a warning when the bundle name version is not following semver",
args: args{bundleName: "memcached-operator.v1"},
wantWarning: true,
warnStrings: []string{"csv.metadata.Name memcached-operator.v1 is not following the versioning convention " +
"(MAJOR.MINOR.PATCH e.g 0.0.1): https://semver.org/"},
},
{
name: "should return a warning when the bundle name version is not following semver",
args: args{bundleName: "memcached-operator.v1"},
wantWarning: true,
warnStrings: []string{"csv.metadata.Name memcached-operator.v1 is not following the " +
"versioning convention (MAJOR.MINOR.PATCH e.g 0.0.1): https://semver.org/"},
},
{
name: "should return a warning when the bundle name version is not following semver",
args: args{bundleName: "memcached-operator.v1--1.0"},
wantWarning: true,
warnStrings: []string{"csv.metadata.Name memcached-operator.v1--1.0 is not following the " +
"versioning convention (MAJOR.MINOR.PATCH e.g 0.0.1): https://semver.org/"},
},
{
name: "should return a warning when the bundle name version is not following semver",
args: args{bundleName: "memcached-operator.v1.3"},
wantWarning: true,
warnStrings: []string{"csv.metadata.Name memcached-operator.v1.3 is not following the " +
"versioning convention (MAJOR.MINOR.PATCH e.g 0.0.1): https://semver.org/"},
},
{
name: "should not warning for patch releases",
args: args{bundleName: "memcached-operator.v0.9.2+alpha"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

csv := operatorsv1alpha1.ClusterServiceVersion{}
csv.Name = tt.args.bundleName
result := checkBundleName(&csv)

require.Equal(t, tt.wantWarning, len(result) > 0)
if tt.wantWarning {
require.Equal(t, len(tt.warnStrings), len(result))
for _, w := range result {
wString := w.Error()
require.Contains(t, tt.warnStrings, wString)
}
}
})
}
}
34 changes: 0 additions & 34 deletions pkg/validation/internal/operatorhub.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"net/url"
"os"
"path/filepath"
"regexp"
"strings"

"github.com/blang/semver/v4"
Expand Down Expand Up @@ -111,10 +110,6 @@ import (
// - If informed ONLY, check if the value csv.Spec.MinKubeVersion is parsable according to semver (https://semver.org/)
// Also, this validator will raise warnings when:
//
// - The bundle name (CSV.metadata.name) does not follow the naming convention: <operator-name>.v<semver> e.g. memcached-operator.v0.0.1
//
// NOTE: The bundle name must be 63 characters or less because it will be used as k8s ownerref label which only allows max of 63 characters.
//
// - The channel names seems are not following the convention https://olm.operatorframework.io/docs/best-practices/channel-naming/
//
// - The usage of the removed APIs on Kubernetes 1.22 is found. More info: https://kubernetes.io/docs/reference/using-api/deprecation-guide/#v1-22
Expand Down Expand Up @@ -230,7 +225,6 @@ func validateHubCSVSpec(csv v1alpha1.ClusterServiceVersion) CSVChecks {
checks = checkSpecVersion(checks)
checks = checkSpecIcon(checks)
checks = checkSpecMinKubeVersion(checks)
checks = checkBundleName(checks)

return checks
}
Expand All @@ -241,34 +235,6 @@ type CSVChecks struct {
warns []error
}

// checkBundleName will validate the operator bundle name informed via CSV.metadata.name.
// The motivation for the following check is to ensure that operators authors knows that operator bundles names should
// follow a name and versioning convention
func checkBundleName(checks CSVChecks) CSVChecks {

// Check if is following the semver
re := regexp.MustCompile("([0-9]+)\\.([0-9]+)\\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?(?:\\+[0-9A-Za-z-]+)?$")
match := re.FindStringSubmatch(checks.csv.Name)

if len(match) > 0 {
if _, err := semver.Parse(match[0]); err != nil {
checks.warns = append(checks.warns, fmt.Errorf("csv.metadata.Name %v is not following the versioning "+
"convention (MAJOR.MINOR.PATCH e.g 0.0.1): https://semver.org/", checks.csv.Name))
}
} else {
checks.warns = append(checks.warns, fmt.Errorf("csv.metadata.Name %v is not following the versioning "+
"convention (MAJOR.MINOR.PATCH e.g 0.0.1): https://semver.org/", checks.csv.Name))
}

// Check if its following the name convention
if len(strings.Split(checks.csv.Name, ".v")) < 2 {
checks.warns = append(checks.errs, fmt.Errorf("csv.metadata.Name %v is not following the recommended "+
"naming convention: <operator-name>.v<semver> e.g. memcached-operator.v0.0.1", checks.csv.Name))
}

return checks
}

// checkSpecMinKubeVersion will validate the spec minKubeVersion informed via CSV.spec.minKubeVersion
func checkSpecMinKubeVersion(checks CSVChecks) CSVChecks {
if len(strings.TrimSpace(checks.csv.Spec.MinKubeVersion)) == 0 {
Expand Down
81 changes: 0 additions & 81 deletions pkg/validation/internal/operatorhub_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,87 +204,6 @@ func TestCheckSpecIcon(t *testing.T) {
}
}

func TestCheckBundleName(t *testing.T) {
type args struct {
bundleName string
}
tests := []struct {
name string
args args
wantError bool
wantWarning bool
errStrings []string
warnStrings []string
}{
{
name: "should work with valid bundle name",
args: args{bundleName: "memcached-operator.v0.9.2"},
},
{
name: "should return a warning when the bundle name is not following the convention",
args: args{bundleName: "memcached-operator0.9.2"},
wantWarning: true,
warnStrings: []string{"csv.metadata.Name memcached-operator0.9.2 is not following the recommended naming " +
"convention: <operator-name>.v<semver> e.g. memcached-operator.v0.0.1"},
},
{
name: "should return a warning when the bundle name version is not following semver",
args: args{bundleName: "memcached-operator.v1"},
wantWarning: true,
warnStrings: []string{"csv.metadata.Name memcached-operator.v1 is not following the versioning convention " +
"(MAJOR.MINOR.PATCH e.g 0.0.1): https://semver.org/"},
},
{
name: "should return a warning when the bundle name version is not following semver",
args: args{bundleName: "memcached-operator.v1"},
wantWarning: true,
warnStrings: []string{"csv.metadata.Name memcached-operator.v1 is not following the " +
"versioning convention (MAJOR.MINOR.PATCH e.g 0.0.1): https://semver.org/"},
},
{
name: "should return a warning when the bundle name version is not following semver",
args: args{bundleName: "memcached-operator.v1--1.0"},
wantWarning: true,
warnStrings: []string{"csv.metadata.Name memcached-operator.v1--1.0 is not following the " +
"versioning convention (MAJOR.MINOR.PATCH e.g 0.0.1): https://semver.org/"},
},
{
name: "should return a warning when the bundle name version is not following semver",
args: args{bundleName: "memcached-operator.v1.3"},
wantWarning: true,
warnStrings: []string{"csv.metadata.Name memcached-operator.v1.3 is not following the " +
"versioning convention (MAJOR.MINOR.PATCH e.g 0.0.1): https://semver.org/"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

csv := v1alpha1.ClusterServiceVersion{}
csv.Name = tt.args.bundleName
checks := CSVChecks{csv: csv, errs: []error{}, warns: []error{}}
result := checkBundleName(checks)

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

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

func TestCheckSpecMinKubeVersion(t *testing.T) {
type args struct {
minKubeVersion string
Expand Down