From 33b9b82fd01285cca8285bd9cb77903005e8a568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20P?= Date: Tue, 18 Apr 2023 22:08:41 +0200 Subject: [PATCH] ImageRunner: Add support for workflow type (#744) * Add support for workflow type --- .sauce/imagerunner.yml | 1 + api/saucectl.schema.json | 10 +- api/v1alpha/framework/imagerunner.schema.json | 10 +- internal/cmd/run/imagerunner.go | 4 + internal/espresso/config_test.go | 2 +- internal/imagerunner/config.go | 77 +++++++++++++ internal/imagerunner/config_test.go | 102 ++++++++++++++++++ internal/imagerunner/imagerunner.go | 13 +-- internal/msg/errormsg.go | 12 ++- internal/saucecloud/imagerunner.go | 11 +- 10 files changed, 227 insertions(+), 15 deletions(-) create mode 100644 internal/imagerunner/config_test.go diff --git a/.sauce/imagerunner.yml b/.sauce/imagerunner.yml index 7cb59c75d..410f56651 100644 --- a/.sauce/imagerunner.yml +++ b/.sauce/imagerunner.yml @@ -5,6 +5,7 @@ sauce: suites: - name: busybox num 1 # Describe your configuration image: busybox:1.35.0 # Name of the container image + workload: other # Kind of workload imagePullAuth: # Credentials used to pull the container image user: $DOCKER_USERNAME token: $DOCKER_PASSWORD diff --git a/api/saucectl.schema.json b/api/saucectl.schema.json index 658d46321..c45058a3d 100644 --- a/api/saucectl.schema.json +++ b/api/saucectl.schema.json @@ -2742,10 +2742,18 @@ "10m", "90s" ] + }, + "workload": { + "description": "Sets the kind of workload that is being executed", + "enum": [ + "webdriver", + "other" + ] } }, "required": [ - "name" + "name", + "workload" ] } }, diff --git a/api/v1alpha/framework/imagerunner.schema.json b/api/v1alpha/framework/imagerunner.schema.json index 18d224656..f3b5893bf 100644 --- a/api/v1alpha/framework/imagerunner.schema.json +++ b/api/v1alpha/framework/imagerunner.schema.json @@ -69,10 +69,18 @@ }, "timeout": { "$ref": "../subschema/common.schema.json#/definitions/timeout" + }, + "workload": { + "description": "Sets the kind of workload that is being executed", + "enum": [ + "webdriver", + "other" + ] } }, "required": [ - "name" + "name", + "workload" ] } }, diff --git a/internal/cmd/run/imagerunner.go b/internal/cmd/run/imagerunner.go index e20bc78f4..852f5154d 100644 --- a/internal/cmd/run/imagerunner.go +++ b/internal/cmd/run/imagerunner.go @@ -21,6 +21,10 @@ func runImageRunner(cmd *cobra.Command) (int, error) { if err != nil { return 1, err } + imagerunner.SetDefaults(&p) + if err := imagerunner.Validate(p); err != nil { + return 1, err + } regio := region.FromString(p.Sauce.Region) imageRunnerClient.URL = regio.APIBaseURL() diff --git a/internal/espresso/config_test.go b/internal/espresso/config_test.go index 902936456..3152a5cb8 100644 --- a/internal/espresso/config_test.go +++ b/internal/espresso/config_test.go @@ -127,7 +127,7 @@ func TestValidateThrowsErrors(t *testing.T) { }, }, }, - expectedErr: errors.New("missing `emulator` in emulator name: Android GoogleApi something. Suite name: no emulator device name. Emulators index: 0"), + expectedErr: errors.New(`missing "emulator" in emulator name: Android GoogleApi something. Suite name: no emulator device name. Emulators index: 0`), }, { name: "validating throws error on missing platform versions", diff --git a/internal/imagerunner/config.go b/internal/imagerunner/config.go index e345c1705..2f449367e 100644 --- a/internal/imagerunner/config.go +++ b/internal/imagerunner/config.go @@ -1,14 +1,23 @@ package imagerunner import ( + "errors" + "fmt" "time" "github.com/saucelabs/saucectl/internal/config" + "github.com/saucelabs/saucectl/internal/msg" + "github.com/saucelabs/saucectl/internal/region" ) var ( Kind = "imagerunner" APIVersion = "v1alpha" + + ValidWorkloadType = []string{ + "webdriver", + "other", + } ) type Project struct { @@ -32,6 +41,7 @@ type Suite struct { Artifacts []string `yaml:"artifacts,omitempty" json:"artifacts"` Env map[string]string `yaml:"env,omitempty" json:"env"` Timeout time.Duration `yaml:"timeout,omitempty" json:"timeout"` + Workload string `yaml:"workload,omitempty" json:"workload,omitempty"` } type ImagePullAuth struct { @@ -53,3 +63,70 @@ func FromFile(cfgPath string) (Project, error) { return p, nil } + +// SetDefaults applies config defaults in case the user has left them blank. +func SetDefaults(p *Project) { + if p.Kind == "" { + p.Kind = Kind + } + + if p.APIVersion == "" { + p.APIVersion = APIVersion + } + + if p.Sauce.Concurrency < 1 { + p.Sauce.Concurrency = 2 + } + + if p.Defaults.Timeout < 0 { + p.Defaults.Timeout = 0 + } + + p.Sauce.Tunnel.SetDefaults() + p.Sauce.Metadata.SetDefaultBuild() + + for i, suite := range p.Suites { + if suite.Timeout <= 0 { + p.Suites[i].Timeout = p.Defaults.Timeout + } + + if suite.Workload == "" { + p.Suites[i].Workload = p.Defaults.Workload + } + } +} + +func Validate(p Project) error { + regio := region.FromString(p.Sauce.Region) + if regio == region.None { + return errors.New(msg.MissingRegion) + } + + if len(p.Suites) == 0 { + return errors.New(msg.EmptySuite) + } + + for _, suite := range p.Suites { + if suite.Workload == "" { + return fmt.Errorf(msg.MissingImageRunnerWorkloadType, suite.Name) + } + + if !sliceContainsString(ValidWorkloadType, suite.Workload) { + return fmt.Errorf(msg.InvalidImageRunnerWorkloadType, suite.Workload, suite.Name) + } + + if suite.Image == "" { + return fmt.Errorf(msg.MissingImageRunnerImage, suite.Name) + } + } + return nil +} + +func sliceContainsString(slice []string, val string) bool { + for _, value := range slice { + if value == val { + return true + } + } + return false +} diff --git a/internal/imagerunner/config_test.go b/internal/imagerunner/config_test.go new file mode 100644 index 000000000..2961f31f5 --- /dev/null +++ b/internal/imagerunner/config_test.go @@ -0,0 +1,102 @@ +package imagerunner + +import ( + "testing" + + "github.com/saucelabs/saucectl/internal/config" + "github.com/saucelabs/saucectl/internal/region" +) + +func TestValidate(t *testing.T) { + type args struct { + p Project + } + tests := []struct { + name string + args args + wantErr string + }{ + { + name: "Passing", + args: args{ + p: Project{ + Sauce: config.SauceConfig{ + Region: region.USWest1.String(), + }, + Suites: []Suite{ + { + Name: "Main Suite", + Workload: "other", + Image: "dummy/image", + }, + }, + }, + }, + wantErr: "", + }, + { + name: "No Image", + args: args{ + p: Project{ + Sauce: config.SauceConfig{ + Region: region.USWest1.String(), + }, + Suites: []Suite{ + { + Name: "Main Suite", + Workload: "other", + }, + }, + }, + }, + wantErr: `missing "image" for suite: Main Suite`, + }, + { + name: "No Workload Type", + args: args{ + p: Project{ + Sauce: config.SauceConfig{ + Region: region.USWest1.String(), + }, + Suites: []Suite{ + { + Name: "Main Suite", + Image: "dummy/image", + }, + }, + }, + }, + wantErr: `missing "workload" value for suite: Main Suite`, + }, + { + name: "Invalid Workload Type", + args: args{ + p: Project{ + Sauce: config.SauceConfig{ + Region: region.USWest1.String(), + }, + Suites: []Suite{ + { + Name: "Main Suite", + Image: "dummy/image", + Workload: "invalid-workload-type", + }, + }, + }, + }, + wantErr: `"invalid-workload-type" is an invalid "workload" value for suite: Main Suite`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := Validate(tt.args.p) + errStr := "" + if err != nil { + errStr = err.Error() + } + if errStr != tt.wantErr { + t.Errorf("Validate() error = %v, wantErr %v", errStr, tt.wantErr) + } + }) + } +} diff --git a/internal/imagerunner/imagerunner.go b/internal/imagerunner/imagerunner.go index 5e4da414c..7d3963881 100644 --- a/internal/imagerunner/imagerunner.go +++ b/internal/imagerunner/imagerunner.go @@ -32,12 +32,13 @@ func Done(status string) bool { var ErrResourceNotFound = errors.New("resource not found") type RunnerSpec struct { - Container Container `json:"container,omitempty"` - EntryPoint string `json:"entrypoint,omitempty"` - Env []EnvItem `json:"env,omitempty"` - Files []FileData `json:"files,omitempty"` - Metadata map[string]string `json:"metadata,omitempty"` - Artifacts []string `json:"artifacts,omitempty"` + Container Container `json:"container,omitempty"` + EntryPoint string `json:"entrypoint,omitempty"` + Env []EnvItem `json:"env,omitempty"` + Files []FileData `json:"files,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` + Artifacts []string `json:"artifacts,omitempty"` + WorkloadType string `json:"workloadType,omitempty"` } type Container struct { diff --git a/internal/msg/errormsg.go b/internal/msg/errormsg.go index 1db04cb57..c0468a4d0 100644 --- a/internal/msg/errormsg.go +++ b/internal/msg/errormsg.go @@ -155,11 +155,21 @@ const ( // MissingEmulatorName indicates empty emulator name MissingEmulatorName = "missing emulator name for suite: %s. Emulators index: %d" // InvalidEmulatorName indicates invalid emulator name - InvalidEmulatorName = "missing `emulator` in emulator name: %s. Suite name: %s. Emulators index: %d" + InvalidEmulatorName = `missing "emulator" in emulator name: %s. Suite name: %s. Emulators index: %d` // MissingEmulatorPlatformVersion indicates no emulator platform version provided MissingEmulatorPlatformVersion = "missing platform versions for emulator: %s. Suite name: %s. Emulators index: %d" ) +// ImageRunner config settings +const ( + // MissingImageRunnerWorkloadType indicates no workload type provided + MissingImageRunnerWorkloadType = `missing "workload" value for suite: %s` + // InvalidImageRunnerWorkloadType indicates invalid workload type provided + InvalidImageRunnerWorkloadType = `%q is an invalid "workload" value for suite: %s` + // MissingImageRunnerImage indicates no docker image provided + MissingImageRunnerImage = `missing "image" for suite: %s` +) + // testcafe config settings const ( // InvalidTestCafeDeviceSetting indicates the unsupported device keyword in the config diff --git a/internal/saucecloud/imagerunner.go b/internal/saucecloud/imagerunner.go index 7320c1f68..6496f0ea1 100644 --- a/internal/saucecloud/imagerunner.go +++ b/internal/saucecloud/imagerunner.go @@ -181,11 +181,12 @@ func (r *ImgRunner) runSuite(suite imagerunner.Suite) (imagerunner.Runner, error Name: suite.Image, Auth: auth, }, - EntryPoint: suite.EntryPoint, - Env: mapEnv(suite.Env), - Files: files, - Artifacts: suite.Artifacts, - Metadata: metadata, + EntryPoint: suite.EntryPoint, + Env: mapEnv(suite.Env), + Files: files, + Artifacts: suite.Artifacts, + Metadata: metadata, + WorkloadType: suite.Workload, }) if errors.Is(err, context.DeadlineExceeded) && ctx.Err() != nil { run.Status = imagerunner.StateCancelled