-
Notifications
You must be signed in to change notification settings - Fork 78
feat: add new validator to check/warning when the resource request is not defined for a container used/shipped in the bundle #197
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package internal | ||
|
||
import ( | ||
goerrors "errors" | ||
"fmt" | ||
|
||
"github.com/operator-framework/api/pkg/manifests" | ||
operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" | ||
"github.com/operator-framework/api/pkg/validation/errors" | ||
interfaces "github.com/operator-framework/api/pkg/validation/interfaces" | ||
) | ||
|
||
// GoodPracticesValidator validates the bundle against criteria and suggestions defined as | ||
// good practices for bundles under the operator-framework solutions. (You might give a | ||
// look at https://sdk.operatorframework.io/docs/best-practices/) | ||
// | ||
// This validator will raise an WARNING when: | ||
// | ||
// - The resources request for CPU and/or Memory are not defined for any of the containers found in the CSV | ||
var GoodPracticesValidator interfaces.Validator = interfaces.ValidatorFunc(goodPracticesValidator) | ||
|
||
func goodPracticesValidator(objs ...interface{}) (results []errors.ManifestResult) { | ||
for _, obj := range objs { | ||
switch v := obj.(type) { | ||
case *manifests.Bundle: | ||
results = append(results, validateGoodPracticesFrom(v)) | ||
} | ||
} | ||
return results | ||
} | ||
|
||
func validateGoodPracticesFrom(bundle *manifests.Bundle) errors.ManifestResult { | ||
result := errors.ManifestResult{} | ||
if bundle == nil { | ||
result.Add(errors.ErrInvalidBundle("Bundle is nil", nil)) | ||
return result | ||
} | ||
|
||
result.Name = bundle.Name | ||
|
||
if bundle.CSV == nil { | ||
result.Add(errors.ErrInvalidBundle("Bundle csv is nil", bundle.Name)) | ||
return result | ||
} | ||
|
||
errs, warns := validateResourceRequests(bundle.CSV) | ||
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 | ||
} | ||
|
||
// validateResourceRequests will return a WARN when the resource request is not set | ||
func validateResourceRequests(csv *operatorsv1alpha1.ClusterServiceVersion) (errs, warns []error) { | ||
if csv.Spec.InstallStrategy.StrategySpec.DeploymentSpecs == nil { | ||
errs = append(errs, goerrors.New("unable to find a deployment to install in the CSV")) | ||
return errs, warns | ||
} | ||
deploymentSpec := csv.Spec.InstallStrategy.StrategySpec.DeploymentSpecs | ||
|
||
for _, dSpec := range deploymentSpec { | ||
for _, c := range dSpec.Spec.Template.Spec.Containers { | ||
if c.Resources.Requests == nil || !(len(c.Resources.Requests.Cpu().String()) != 0 && len(c.Resources.Requests.Memory().String()) != 0) { | ||
msg := fmt.Errorf("unable to find the resource requests for the container %s. It is recommended "+ | ||
"to ensure the resource request for CPU and Memory. Be aware that for some clusters configurations "+ | ||
"it is required to specify requests or limits for those values. Otherwise, the system or quota may "+ | ||
"reject Pod creation. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/", c.Name) | ||
warns = append(warns, msg) | ||
} | ||
} | ||
} | ||
return errs, warns | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package internal | ||
|
||
import ( | ||
"github.com/operator-framework/api/pkg/manifests" | ||
"github.com/stretchr/testify/require" | ||
"testing" | ||
) | ||
|
||
func Test_ValidateGoodPractices(t *testing.T) { | ||
bundleWithDeploymentSpecEmpty, _ := manifests.GetBundleFromDir("./testdata/valid_bundle") | ||
bundleWithDeploymentSpecEmpty.CSV.Spec.InstallStrategy.StrategySpec.DeploymentSpecs = nil | ||
|
||
type args struct { | ||
bundleDir string | ||
bundle *manifests.Bundle | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
wantError bool | ||
wantWarning bool | ||
errStrings []string | ||
warnStrings []string | ||
}{ | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it th exercising the other error paths as well, e.g. when the bundle is nil, or the bundle.CSV is nil, etc. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also when There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideally yes, I agree. |
||
name: "should pass successfully when the resource request is set for " + | ||
"all containers defined in the bundle", | ||
args: args{ | ||
bundleDir: "./testdata/valid_bundle", | ||
}, | ||
}, | ||
{ | ||
name: "should raise an waring when the resource request is NOT set for any of the containers defined in the bundle", | ||
wantWarning: true, | ||
warnStrings: []string{"Warning: Value memcached-operator.v0.0.1: unable to find the resource requests for the container kube-rbac-proxy. It is recommended to ensure the resource request for CPU and Memory. Be aware that for some clusters configurations it is required to specify requests or limits for those values. Otherwise, the system or quota may reject Pod creation. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/", | ||
"Warning: Value memcached-operator.v0.0.1: unable to find the resource requests for the container manager. It is recommended to ensure the resource request for CPU and Memory. Be aware that for some clusters configurations it is required to specify requests or limits for those values. Otherwise, the system or quota may reject Pod creation. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/"}, | ||
args: args{ | ||
bundleDir: "./testdata/valid_bundle_v1", | ||
}, | ||
}, | ||
{ | ||
name: "should fail when the bundle is nil", | ||
wantError: true, | ||
args: args{ | ||
bundle: nil, | ||
}, | ||
errStrings: []string{"Error: : Bundle is nil"}, | ||
}, | ||
{ | ||
name: "should fail when the bundle csv is nil", | ||
wantError: true, | ||
args: args{ | ||
bundle: &manifests.Bundle{CSV: nil, Name: "test"}, | ||
}, | ||
errStrings: []string{"Error: Value test: Bundle csv is nil"}, | ||
}, | ||
{ | ||
name: "should fail when the csv.Spec.InstallStrategy.StrategySpec.DeploymentSpecs is nil", | ||
wantError: true, | ||
args: args{ | ||
bundle: bundleWithDeploymentSpecEmpty, | ||
}, | ||
errStrings: []string{"Error: Value etcdoperator.v0.9.4: unable to find a deployment to install in the CSV"}, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
var err error | ||
if len(tt.args.bundleDir) > 0 { | ||
tt.args.bundle, err = manifests.GetBundleFromDir(tt.args.bundleDir) | ||
require.NoError(t, err) | ||
} | ||
results := validateGoodPracticesFrom(tt.args.bundle) | ||
require.Equal(t, tt.wantWarning, len(results.Warnings) > 0) | ||
if tt.wantWarning { | ||
require.Equal(t, len(tt.warnStrings), len(results.Warnings)) | ||
for _, w := range results.Warnings { | ||
wString := w.Error() | ||
require.Contains(t, tt.warnStrings, wString) | ||
} | ||
} | ||
|
||
require.Equal(t, tt.wantError, len(results.Errors) > 0) | ||
if tt.wantError { | ||
require.Equal(t, len(tt.errStrings), len(results.Errors)) | ||
for _, err := range results.Errors { | ||
errString := err.Error() | ||
require.Contains(t, tt.errStrings, errString) | ||
} | ||
} | ||
}) | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.