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

Adding imagerunner services #839

Merged
merged 3 commits into from
Oct 4, 2023
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
12 changes: 12 additions & 0 deletions .sauce/imagerunner.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,15 @@ suites:
dst: "hello.txt"
env: # Arbitrary Key-Value pairs set as environment variables inside the container.
MY_FOO: bar
services:
- name: "service1"
image: "busybox:1.35.0"
imagePullAuth: # Credentials used to pull the container image
user: $DOCKER_USERNAME
token: $DOCKER_PASSWORD
entrypoint: "cat hello.txt" # What command to start the container with
files: # Which files should be uploaded and mounted within the container
- src: "tests/e2e/imagerunner/hello.txt"
dst: "hello.txt"
env: # Arbitrary Key-Value pairs set as environment variables inside the container.
MY_FOO: bar
72 changes: 72 additions & 0 deletions api/saucectl.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2763,12 +2763,84 @@
"metadata": {
"description": "Supply additional metadata to your runner.",
"type": "object"
},
"services": {
"description": "List of services to run with the suite.",
"type": "array",
"items": {
"$ref": "#/allOf/9/then/definitions/service"
}
}
},
"required": [
"name",
"workload"
]
},
"service": {
"description": "The set of properties providing details about how to run the service container.",
"type": "object",
"properties": {
"name": {
"description": "The name of the service.",
"type": "string"
},
"image": {
"description": "The name of the service image.",
"type": "string"
},
"imagePullAuth": {
"description": "Container registry credentials for accessing the service image.",
"type": "object",
"properties": {
"user": {
"description": "The username.",
"type": "string"
},
"token": {
"description": "The access token.",
"type": "string"
}
}
},
"entrypoint": {
"description": "The command line arguments to launch the service image with.",
"type": "string"
},
"files": {
"description": "List of files that you'd like saucectl to upload and mount within the service container.",
"type": "array",
"items": {
"type": "object",
"properties": {
"src": {
"description": "Path to the local file.",
"type": "string"
},
"dst": {
"description": "Path within the container that the file should be mounted at.",
"type": "string"
}
}
}
},
"env": {
"description": "Set one or more environment variables for the service.",
"type": "object"
},
"resourceProfile": {
"description": "Sets the CPU/memory limits of the service container. Format is <CPU><level><mem><level>. Default to c1m1.",
"enum": [
"",
"c1m1",
"c2m2",
"c3m3"
]
}
},
"required": [
"name"
]
}
},
"properties": {
Expand Down
72 changes: 72 additions & 0 deletions api/v1alpha/framework/imagerunner.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,84 @@
"metadata": {
"description": "Supply additional metadata to your runner.",
"type": "object"
},
"services": {
"description": "List of services to run with the suite.",
"type": "array",
"items": {
"$ref": "#/definitions/service"
}
}
},
"required": [
"name",
"workload"
]
},
"service": {
"description": "The set of properties providing details about how to run the service container.",
"type": "object",
"properties": {
"name": {
"description": "The name of the service.",
"type": "string"
},
"image": {
"description": "The name of the service image.",
"type": "string"
},
"imagePullAuth": {
"description": "Container registry credentials for accessing the service image.",
"type": "object",
"properties": {
"user": {
"description": "The username.",
"type": "string"
},
"token": {
"description": "The access token.",
"type": "string"
}
}
},
"entrypoint": {
"description": "The command line arguments to launch the service image with.",
"type": "string"
},
"files": {
"description": "List of files that you'd like saucectl to upload and mount within the service container.",
"type": "array",
"items": {
"type": "object",
"properties": {
"src": {
"description": "Path to the local file.",
"type": "string"
},
"dst": {
"description": "Path within the container that the file should be mounted at.",
"type": "string"
}
}
}
},
"env": {
"description": "Set one or more environment variables for the service.",
"type": "object"
},
"resourceProfile": {
"description": "Sets the CPU/memory limits of the service container. Format is <CPU><level><mem><level>. Default to c1m1.",
"enum": [
"",
"c1m1",
"c2m2",
"c3m3"
]
}
},
"required": [
"name"
]
}
},
"properties": {
Expand Down
46 changes: 46 additions & 0 deletions internal/imagerunner/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ type Suite struct {
Workload string `yaml:"workload,omitempty" json:"workload,omitempty"`
ResourceProfile string `yaml:"resourceProfile,omitempty" json:"resourceProfile,omitempty"`
Metadata map[string]string `yaml:"metadata,omitempty" json:"metadata,omitempty"`
Services []SuiteService `yaml:"services,omitempty" json:"services,omitempty"`
}

type SuiteService struct {
Name string `yaml:"name,omitempty" json:"name"`
Image string `yaml:"image,omitempty" json:"image"`
ImagePullAuth ImagePullAuth `yaml:"imagePullAuth,omitempty" json:"imagePullAuth"`
EntryPoint string `yaml:"entrypoint,omitempty" json:"entrypoint"`
Files []File `yaml:"files,omitempty" json:"files"`
Env map[string]string `yaml:"env,omitempty" json:"env"`
ResourceProfile string `yaml:"resourceProfile,omitempty" json:"resourceProfile,omitempty"`
}

type ImagePullAuth struct {
Expand Down Expand Up @@ -130,6 +141,23 @@ func SetDefaults(p *Project) {
suite.Env[k] = v
}
}

for j := range suite.Services {
service := &suite.Services[j]
if service.ResourceProfile == "" {
service.ResourceProfile = "c1m1"
}
suite.Metadata[fmt.Sprintf("resourceProfile-%s", GetCanonicalServiceName(service.Name))] = suite.ResourceProfile
if service.Env == nil {
service.Env = make(map[string]string)
}
// Precedence: --env flag > root-level env vars > default env vars > service env vars.
for _, env := range []map[string]string{p.Defaults.Env, p.Env, p.EnvFlag} {
for k, v := range env {
service.Env[k] = v
}
}
}
}
}

Expand Down Expand Up @@ -159,6 +187,24 @@ func Validate(p Project) error {
if suite.ResourceProfile != "" && !ValidResourceProfilesValidator.MatchString(suite.ResourceProfile) {
return fmt.Errorf(msg.InvalidResourceProfile, suite.Name, ValidResourceProfilesFormat)
}
if err := ValidateServices(suite.Services, suite.Name); err != nil {
return err
}
}
return nil
}

func ValidateServices(service []SuiteService, suiteName string) error {
for _, service := range service {
if service.Name == "" {
return fmt.Errorf(msg.MissingServiceName, suiteName)
}
if service.Image == "" {
return fmt.Errorf(msg.MissingServiceImage, service.Name, suiteName)
}
if service.ResourceProfile != "" && !ValidResourceProfilesValidator.MatchString(service.ResourceProfile) {
return fmt.Errorf(msg.InvalidServiceResourceProfile, service.Name, suiteName, ValidResourceProfilesFormat)
}
}
return nil
}
Expand Down
71 changes: 71 additions & 0 deletions internal/imagerunner/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,77 @@ func TestValidate(t *testing.T) {
},
wantErr: `invalid resourceProfile for suite: Main Suite, resourceProfile should be of format cXmX`,
},
{
name: "Invalid serviceName",
args: args{
p: Project{
Sauce: config.SauceConfig{
Region: region.USWest1.String(),
},
Suites: []Suite{
{
Name: "Main Suite",
Image: "dummy/image",
Workload: "other",
Services: []SuiteService{
{
Image: "dummy/image",
},
},
},
},
},
},
wantErr: `missing "name" for service in suite: Main Suite`,
},
{
name: "Invalid serviceImage",
args: args{
p: Project{
Sauce: config.SauceConfig{
Region: region.USWest1.String(),
},
Suites: []Suite{
{
Name: "Main Suite",
Image: "dummy/image",
Workload: "other",
Services: []SuiteService{
{
Name: "myservice",
},
},
},
},
},
},
wantErr: `missing "image" for service: myservice in suite: Main Suite`,
},
{
name: "Invalid serviceResourceProfile",
args: args{
p: Project{
Sauce: config.SauceConfig{
Region: region.USWest1.String(),
},
Suites: []Suite{
{
Name: "Main Suite",
Image: "dummy/image",
Workload: "other",
Services: []SuiteService{
{
Name: "myservice",
Image: "dummy/image",
ResourceProfile: "test",
},
},
},
},
},
},
wantErr: `invalid resourceProfile for service: myservice in suite: Main Suite, resourceProfile should be of format cXmX`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
9 changes: 9 additions & 0 deletions internal/imagerunner/imagerunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ type RunnerSpec struct {
Artifacts []string `json:"artifacts,omitempty"`
WorkloadType string `json:"workload_type,omitempty"`
Tunnel *Tunnel `json:"tunnel,omitempty"`
Services []Service `json:"services,omitempty"`
}

type Service struct {
Name string `json:"name,omitempty"`
Container Container `json:"container,omitempty"`
EntryPoint string `json:"entrypoint,omitempty"`
Env []EnvItem `json:"env,omitempty"`
Files []FileData `json:"files,omitempty"`
}

type Tunnel struct {
Expand Down
17 changes: 17 additions & 0 deletions internal/imagerunner/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package imagerunner

import (
"regexp"
"strings"
)

func GetCanonicalServiceName(serviceName string) string {
if serviceName == "" {
return ""
}
// make sure the service name has only lowercase letters, numbers, and underscores
canonicalName := strings.ToLower(regexp.MustCompile("[^a-z0-9-]").ReplaceAllString(serviceName, "-"))
// remove successives dashes
canonicalName = regexp.MustCompile("-+").ReplaceAllString(canonicalName, "-")
return canonicalName
}
6 changes: 6 additions & 0 deletions internal/msg/errormsg.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,12 @@ const (
ImageRunnerMaxConcurrency = "Maximum concurrency for imagerunner is 5. Replacing %d with 5."
// InvalidResourceProfile indicates the resourceProfile is not valid
InvalidResourceProfile = "invalid resourceProfile for suite: %s, resourceProfile should be of format %v"
// MissingServiceName indicates no service name provided
MissingServiceName = `missing "name" for service in suite: %s`
// MissingServiceImage indicates no docker image provided
MissingServiceImage = `missing "image" for service: %s in suite: %s`
// InvalidServiceResourceProfile indicates the service resourceProfile is not valid
InvalidServiceResourceProfile = "invalid resourceProfile for service: %s in suite: %s, resourceProfile should be of format %v"
)

// testcafe config settings
Expand Down
Loading