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
23 changes: 23 additions & 0 deletions pkg/api/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
105 changes: 103 additions & 2 deletions pkg/api/config_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package api

import (
"errors"
"fmt"
"reflect"
"testing"

"k8s.io/apimachinery/pkg/util/diff"
kerrors "k8s.io/apimachinery/pkg/util/errors"
)

Expand Down Expand Up @@ -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 {
Expand Down
8 changes: 8 additions & 0 deletions pkg/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this is a string and not a PipelineImageReference don't we already allow any pull spec here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two points in the execution where this is used:

  • When configuration is loaded, to determine dependencies between (ci-operator) steps.
  • When the pods are created.

The biggest problem is #1. We know at that point the images that are part of the pipeline, but not those that come from tag_specification.

For #2, we can always use pipeline:src, stable:tests, or even a full spec, which is what I've been using, but since #1 requires us to have that information, that should not be required.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we generally operate on the concept that if an image is named foo and there exists a foo in the images brought in by the tag_specification, the built image overrides that which is pulled in. So, we can accept a simple string here and:

  • if it matches something that is built by the pipeline or the images, create that link in the steps
  • if it does not, expect it to be provided by the import from tag-specification

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what I wanted to hear/read.

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 {
Expand Down