Skip to content

Commit ad6611d

Browse files
feat: setup script (#16)
* setup script functionality * add bash to image * update regex in error * EOF newlines
1 parent 640792f commit ad6611d

File tree

8 files changed

+258
-48
lines changed

8 files changed

+258
-48
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,7 @@ unit-tests.xml
1010
.env
1111

1212
# binary
13-
release/
13+
release/
14+
15+
# MacOS
16+
.DS_STORE

DOCS.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,11 @@ export const options = {
6767

6868
The following parameters are used to configure the image:
6969

70-
| Name | Description | Required | Default |
71-
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- |
72-
| `script_path` | path to the k6 script file. must be a JavaScript file satisfying the pattern `^([.]{0,2}/)?[a-zA-Z0-9-_/]\*[a-zA-Z0-9]\.js$`. | `true` | `N/A` |
73-
| `output_path` | path to the output file that will be created. directories will be created as necessary. if empty, no output file will be generated. must be a JSON file satisfying the pattern `^([.]{0,2}/)?[a-zA-Z0-9-_/]\*[a-zA-Z0-9]\.json$`. | `false` | `N/A` |
74-
| `fail_on_threshold_breach` | if `false`, the pipeline step will not fail even if thresholds are breached. | `false` | `true` |
75-
| `projektor_compat_mode` | if `true`, output will be generated with the `--summary-output` flag instead of the `--out` flag. this is necessary for results uploaded to a [Projektor](https://projektor.dev/) server. | `false` | `false` |
76-
| `log_progress` | if `true`, k6 progress bar output will print to the Vela pipeline. Not recommended for numerous or long-running tests, as logging becomes excessive. | `false` | `false` |
70+
| Name | Description | Required | Default |
71+
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | ------- |
72+
| `script_path` | path to the k6 script file. must be a JavaScript file satisfying the pattern `^(\./\|(\.\./)+)?[a-zA-Z0-9-_/]*[a-zA-Z0-9]\.js$`. | `true` | `N/A` |
73+
| `output_path` | path to the output file that will be created. directories will be created as necessary. if empty, no output file will be generated. must be a JSON file satisfying the pattern `^(\./\|(\.\./)+)?[a-zA-Z0-9-_/]*[a-zA-Z0-9]\.json$`. | `false` | `N/A` |
74+
| `setup_script_path` | path to an optional setup script file to be run before tests. must be a shell script (sh or bash) with execute permissions matching the pattern `^(\./\|(\.\./)+)?[a-zA-Z0-9-_/]*[a-zA-Z0-9]\.sh$`. | `false` | `N/A` |
75+
| `fail_on_threshold_breach` | if `false`, the pipeline step will not fail even if thresholds are breached. | `false` | `true` |
76+
| `projektor_compat_mode` | if `true`, output will be generated with the `--summary-output` flag instead of the `--out` flag. this is necessary for results uploaded to a [Projektor](https://projektor.dev/) server. | `false` | `false` |
77+
| `log_progress` | if `true`, k6 progress bar output will print to the Vela pipeline. Not recommended for numerous or long-running tests, as logging becomes excessive. | `false` | `false` |

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ FROM docker.io/grafana/k6:0.46.0@sha256:2f40a302ec1e1e3cc96b9a3871bf5d7d4697e9ec
22

33
FROM alpine:3.18@sha256:7144f7bab3d4c2648d7e59409f15ec52a18006a128c733fcff20d3a4a54ba44a as certs
44

5-
RUN apk add --update --no-cache ca-certificates
5+
RUN apk add --update --no-cache ca-certificates bash
66

77
COPY --from=k6-image /usr/bin/k6 /usr/bin/k6
88

Makefile

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,8 @@ clean: clean-all go-tidy ## Clean up the application and test output
4444
.PHONY: build
4545
build: build-all ## Compile the application
4646

47-
.PHONY: build-docker
48-
build-docker: ## Compile the application for testing locally with Docker
49-
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o $(BIN_NAME)
47+
.PHONY: docker
48+
docker: build-linux docker-build ## Build a Docker image for local testing
5049

5150
.PHONY: test
5251
test: test-all ## Run all unit tests
@@ -116,3 +115,23 @@ build-static-ci:
116115
@echo
117116
@echo "### Building CI static release/vela-k6 binary"
118117
go build -a -ldflags '-s -w -extldflags "-static" ${LD_FLAGS}' -o $(BIN_LOCATION) $(BIN_NAME)
118+
119+
# The `build-linux` target is intended to compile
120+
# the Go source code into a linux-compatible binary.
121+
#
122+
# Usage: `make build-linux`
123+
.PHONY: build-linux
124+
build-linux:
125+
@echo
126+
@echo "### Building release/vela-k6 binary for linux"
127+
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -a -ldflags '${LD_FLAGS}' -o $(BIN_LOCATION) $(BIN_NAME)
128+
129+
# The `docker-build` target is intended to build
130+
# the Docker image for the plugin.
131+
#
132+
# Usage: `make docker-build`
133+
.PHONY: docker-build
134+
docker-build: build-linux
135+
@echo
136+
@echo "### Building vela-k6:local image"
137+
@docker build --no-cache -t vela-k6:local .

main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ func main() {
2929
log.Fatalf("FATAL: %s\n", err)
3030
}
3131

32+
err = plugin.RunSetupScript(cfg)
33+
if err != nil {
34+
log.Fatalf("FATAL: %s\n", err)
35+
}
36+
3237
err = plugin.RunPerfTests(cfg)
3338
if err != nil {
3439
log.Fatalf("FATAL: %s\n", err)

plugin/mock.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package plugin
22

33
import (
4-
"fmt"
54
"io"
65
"os/exec"
76
"strings"
@@ -21,11 +20,7 @@ func (m *MockCommand) Wait() error {
2120
}
2221

2322
func (m *MockCommand) String() (str string) {
24-
for _, arg := range m.args {
25-
str = fmt.Sprintf("%s %s", str, arg)
26-
}
27-
28-
return
23+
return ""
2924
}
3025

3126
func (m *MockCommand) StdoutPipe() (io.ReadCloser, error) {

plugin/plugin.go

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ func checkOSStat(path string) error {
4242
}
4343

4444
var (
45-
validFilePattern = regexp.MustCompile(`^(\./|(\.\./)+)?[a-zA-Z0-9-_/]*[a-zA-Z0-9]\.(json|js)$`)
45+
validJSFilePattern = regexp.MustCompile(`^(\./|(\.\./)+)?[a-zA-Z0-9-_/]*[a-zA-Z0-9]\.js$`)
46+
validJSONFilePattern = regexp.MustCompile(`^(\./|(\.\./)+)?[a-zA-Z0-9-_/]*[a-zA-Z0-9]\.json$`)
47+
validShellFilePattern = regexp.MustCompile(`^(\./|(\.\./)+)?[a-zA-Z0-9-_/]*[a-zA-Z0-9]\.sh$`)
4648
// buildCommand can be swapped out for a mock function for unit testing.
4749
buildCommand = buildExecCommand
4850
// verifyFileExists can be swapped out for a mock function for unit testing.
@@ -55,23 +57,36 @@ var (
5557
// output path is invalid, OutputPath is set to "".
5658
func ConfigFromEnv() (*Config, error) {
5759
cfg := &Config{}
58-
cfg.ScriptPath = sanitizeFilePath(os.Getenv("PARAMETER_SCRIPT_PATH"))
59-
cfg.OutputPath = sanitizeFilePath(os.Getenv("PARAMETER_OUTPUT_PATH"))
60+
cfg.ScriptPath = sanitizeScriptPath(os.Getenv("PARAMETER_SCRIPT_PATH"))
61+
cfg.OutputPath = sanitizeOutputPath(os.Getenv("PARAMETER_OUTPUT_PATH"))
62+
cfg.SetupScriptPath = sanitizeSetupPath(os.Getenv("PARAMETER_SETUP_SCRIPT_PATH"))
6063
cfg.FailOnThresholdBreach = !strings.EqualFold(os.Getenv("PARAMETER_FAIL_ON_THRESHOLD_BREACH"), "false")
6164
cfg.ProjektorCompatMode = strings.EqualFold(os.Getenv("PARAMETER_PROJEKTOR_COMPAT_MODE"), "true")
6265
cfg.LogProgress = strings.EqualFold(os.Getenv("PARAMETER_LOG_PROGRESS"), "true")
6366

6467
if cfg.ScriptPath == "" || !strings.HasSuffix(cfg.ScriptPath, ".js") {
65-
return nil, fmt.Errorf("invalid script file. provide the filepath to a JavaScript file in plugin parameter 'script_path' (e.g. 'script_path: \"/k6-test/script.js\"'). the filepath must follow the regular expression `^[a-zA-Z0-9-_/]*[a-zA-Z0-9]+\\.(json|js)$`")
68+
return nil, fmt.Errorf("invalid script file. provide the filepath to a JavaScript file in plugin parameter 'script_path' (e.g. 'script_path: \"/k6-test/script.js\"'). the filepath must follow the regular expression `%s`", validJSFilePattern)
6669
}
6770

6871
return cfg, nil
6972
}
7073

71-
// sanitizeFilePath returns the input string if it satisfies the pattern
72-
// for a valid filepath, and an empty string otherwise.
73-
func sanitizeFilePath(input string) string {
74-
return validFilePattern.FindString(input)
74+
// sanitizeScriptPath returns the input string if it satisfies the pattern
75+
// for a valid JS filepath, and an empty string otherwise.
76+
func sanitizeScriptPath(input string) string {
77+
return validJSFilePattern.FindString(input)
78+
}
79+
80+
// sanitizeOutputPath returns the input string if it satisfies the pattern
81+
// for a valid JSON filepath, and an empty string otherwise.
82+
func sanitizeOutputPath(input string) string {
83+
return validJSONFilePattern.FindString(input)
84+
}
85+
86+
// sanitizeSetupPath returns the input string if it satisfies the pattern
87+
// for a valid .sh filepath, and an empty string otherwise.
88+
func sanitizeSetupPath(input string) string {
89+
return validShellFilePattern.FindString(input)
7590
}
7691

7792
// buildK6Command returns a shellCommand that will execute K6 tests
@@ -103,6 +118,52 @@ func buildK6Command(cfg *Config) (cmd shellCommand, err error) {
103118
return
104119
}
105120

121+
// RunSetupScript runs the setup script located at the cfg.SetupScriptPath
122+
// if the path is not empty.
123+
func RunSetupScript(cfg *Config) error {
124+
if cfg.SetupScriptPath == "" {
125+
log.Println("No setup script specified, skipping.")
126+
return nil
127+
}
128+
129+
err := verifyFileExists(cfg.SetupScriptPath)
130+
if err != nil {
131+
return fmt.Errorf("read setup script file at %s: %w", cfg.SetupScriptPath, err)
132+
}
133+
134+
cmd := buildCommand(cfg.SetupScriptPath)
135+
136+
stdout, err := cmd.StdoutPipe()
137+
if err != nil {
138+
return fmt.Errorf("get stdout pipe: %w", err)
139+
}
140+
141+
stderr, err := cmd.StderrPipe()
142+
if err != nil {
143+
return fmt.Errorf("get stderr pipe: %w", err)
144+
}
145+
146+
if err := cmd.Start(); err != nil {
147+
return fmt.Errorf("start command: %w", err)
148+
}
149+
150+
log.Println("Running setup script...")
151+
152+
wg := sync.WaitGroup{}
153+
wg.Add(2)
154+
155+
go readLinesFromPipe(stdout, &wg)
156+
go readLinesFromPipe(stderr, &wg)
157+
wg.Wait()
158+
159+
err = cmd.Wait()
160+
if err != nil {
161+
return fmt.Errorf("run setup script: %w", err)
162+
}
163+
164+
return nil
165+
}
166+
106167
// RunPerfTests runs the K6 performance test script located at the
107168
// cfg.ScriptPath and saves the output to cfg.OutputPath if it is present
108169
// and a valid filepath.
@@ -184,6 +245,7 @@ func readLinesFromPipe(pipe io.ReadCloser, wg *sync.WaitGroup) {
184245
type Config struct {
185246
ScriptPath string
186247
OutputPath string
248+
SetupScriptPath string
187249
FailOnThresholdBreach bool
188250
ProjektorCompatMode bool
189251
LogProgress bool

0 commit comments

Comments
 (0)