diff --git a/pkg/api/config.go b/pkg/api/config.go index 7ba88d90a4c..07ab71708bc 100644 --- a/pkg/api/config.go +++ b/pkg/api/config.go @@ -9,6 +9,7 @@ import ( "strings" "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/util/sets" ) // ValidateAtRuntime validates all the configuration's values without knowledge of config @@ -286,6 +287,28 @@ func validateTestConfigurationType(fieldRoot string, test TestStepConfiguration, return validationErrors } +func validateTestSteps(fieldRoot string, steps []TestStep) (ret []error) { + seen := sets.NewString() + for i, s := range steps { + fieldRootI := fmt.Sprintf("%s[%d]", fieldRoot, i) + if len(s.Name) == 0 { + ret = append(ret, fmt.Errorf("%s: `name` is required", fieldRootI)) + } else if seen.Has(s.Name) { + ret = append(ret, fmt.Errorf("%s: duplicated name %q", fieldRootI, s.Name)) + } else { + seen.Insert(s.Name) + } + if len(s.Image) == 0 { + ret = append(ret, fmt.Errorf("%s: `image` is required", fieldRootI)) + } + if len(s.Commands) == 0 { + ret = append(ret, fmt.Errorf("%s: `commands` is required", fieldRootI)) + } + ret = append(ret, validateResourceRequirements(fieldRootI+".resources", s.Resources)...) + } + return +} + func validateReleaseBuildConfiguration(input *ReleaseBuildConfiguration, org, repo string) []error { var validationErrors []error diff --git a/pkg/api/config_test.go b/pkg/api/config_test.go index bfae3ae89e0..09ce7962f5c 100644 --- a/pkg/api/config_test.go +++ b/pkg/api/config_test.go @@ -1,9 +1,12 @@ package api import ( + "errors" "fmt" + "reflect" "testing" + "k8s.io/apimachinery/pkg/util/diff" kerrors "k8s.io/apimachinery/pkg/util/errors" ) @@ -385,8 +388,106 @@ func TestValidateBaseRpmImages(t *testing.T) { } } -func parseError(id string, err error) error { - return fmt.Errorf("%q expected to be valid, got 'Error(%v)' instead", id, err) +func TestValidateTestSteps(t *testing.T) { + for _, tc := range []struct { + name string + steps []TestStep + errs []error + }{{ + name: "valid step", + steps: []TestStep{{ + Name: "name", + Image: "image", + Commands: "commands", + Resources: ResourceRequirements{ + Requests: ResourceList{"cpu": "1"}, + Limits: ResourceList{"memory": "1m"}, + }, + }}, + }, { + name: "no name", + steps: []TestStep{{ + Image: "image", + Commands: "commands", + Resources: ResourceRequirements{ + Requests: ResourceList{"cpu": "1"}, + Limits: ResourceList{"memory": "1m"}, + }, + }}, + errs: []error{errors.New("test[0]: `name` is required")}, + }, { + name: "duplicated names", + steps: []TestStep{{ + Name: "s0", + Image: "image", + Commands: "commands", + Resources: ResourceRequirements{ + Requests: ResourceList{"cpu": "1"}, + Limits: ResourceList{"memory": "1m"}, + }, + }, { + Name: "s1", + Image: "image", + Commands: "commands", + Resources: ResourceRequirements{ + Requests: ResourceList{"cpu": "1"}, + Limits: ResourceList{"memory": "1m"}, + }, + }, { + Name: "s0", + Image: "image", + Commands: "commands", + Resources: ResourceRequirements{ + Requests: ResourceList{"cpu": "1"}, + Limits: ResourceList{"memory": "1m"}, + }, + }}, + errs: []error{errors.New(`test[2]: duplicated name "s0"`)}, + }, { + name: "no image", + steps: []TestStep{{ + Name: "no_image", + Commands: "commands", + Resources: ResourceRequirements{ + Requests: ResourceList{"cpu": "1"}, + Limits: ResourceList{"memory": "1m"}, + }, + }}, + errs: []error{errors.New("test[0]: `image` is required")}, + }, { + name: "no commands", + steps: []TestStep{{ + Name: "no_commands", + Image: "image", + Resources: ResourceRequirements{ + Requests: ResourceList{"cpu": "1"}, + Limits: ResourceList{"memory": "1m"}, + }, + }}, + errs: []error{errors.New("test[0]: `commands` is required")}, + }, { + name: "invalid resources", + steps: []TestStep{{ + Name: "bad_resources", + Image: "image", + Commands: "commands", + Resources: ResourceRequirements{ + Requests: ResourceList{"cpu": "yes"}, + Limits: ResourceList{"piña_colada": "10dL"}, + }, + }}, + errs: []error{ + errors.New("'test[0].resources.limits' specifies an invalid key piña_colada"), + errors.New("test[0].resources.requests.cpu: invalid quantity: quantities must match the regular expression '^([+-]?[0-9.]+)([eEinumkKMGTP]*[-+]?[0-9]*)$'"), + }, + }} { + t.Run(tc.name, func(t *testing.T) { + ret := validateTestSteps("test", tc.steps) + if !reflect.DeepEqual(ret, tc.errs) { + t.Fatal(diff.ObjectReflectDiff(ret, tc.errs)) + } + }) + } } func parseValidError(id string) error { diff --git a/pkg/api/types.go b/pkg/api/types.go index 97bddd41f55..fe19b9ff471 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -307,6 +307,14 @@ type TestStepConfiguration struct { OpenshiftInstallerCustomTestImageClusterTestConfiguration *OpenshiftInstallerCustomTestImageClusterTestConfiguration `json:"openshift_installer_custom_test_image,omitempty"` } +type TestStep struct { + Name string `json:"name,omitempty"` + Image string `json:"image,omitempty"` + Commands string `json:"commands,omitempty"` + ArtifactDir string `json:"artifact_dir,omitempty"` + Resources ResourceRequirements `json:"resources,omitempty"` +} + // Secret describes a secret to be mounted inside a test // container. type Secret struct {