From 55ca42a26776f8412bb1d34f736b954364ba5d5b Mon Sep 17 00:00:00 2001 From: Barrett Strausser Date: Wed, 3 Apr 2024 12:50:39 -0400 Subject: [PATCH] feat(k6):Add remote test scripts (#2350) * Add remote test scripts * Add basic auth * Simplify error handling * Use container reader API * Use Typed URI + linting/formatting * Add failing remote test + update docs/comments * Rename struct + Docs * Fix doc typo --------- Co-authored-by: bstrausser --- docs/modules/k6.md | 16 ++++++++- modules/k6/k6.go | 81 ++++++++++++++++++++++++++++++++++++++++--- modules/k6/k6_test.go | 34 +++++++++++++++--- 3 files changed, 122 insertions(+), 9 deletions(-) diff --git a/docs/modules/k6.md b/docs/modules/k6.md index d0b9a8b8d8..ce81d31c1c 100644 --- a/docs/modules/k6.md +++ b/docs/modules/k6.md @@ -83,12 +83,26 @@ k6.RunContainer(ctx, WithCmdOptions("--vus=10", "--duration=30s"), k6.WithTestSc #### WithTestScript -Use the `WithTestScript` option to specify the test script to run. The path to the script must be an absolute path. This option copies the script file to the container and pass it to k6's `run` command. At least one `WithTestScript` option must be specified. +Use the `WithTestScript` option to specify the test script to run. The path to the script must be an absolute path. This option copies the script file to the container and pass it to k6's `run` command. At least one `WithTestScript` or `WithRemoteTestScript` option must be specified. ```golang k6.RunContainer(ctx, k6.WithTestScript("/tests/test.js")) ``` +#### WithRemoteTestScript + +Use the `WithRemoteTestScript` option to specify the remote test script to run. The path to the remote script must be a http or https url. Basic authentication is supported. This option performs a HTTP `GET` to copy the remote file locally then copies the script file to the container and pass it to k6's `run` command. The default timeout for the `GET` is 60 seconds. Only javascript, or more specifically `Content-Type:text/javascript` is supported. At least one `WithTestScript` or `WithRemoteTestScript` option must be specified. + +```golang +scriptUrl:="https://raw.githubusercontent.com/testcontainers/testcontainers-go/main/modules/k6/scripts/pass.js" + +uri, _ := url.Parse(scriptUrl) +desc := k6.DownloadableFile{Uri: *uri , DownloadDir: t.TempDir()} +options := k6.WithRemoteTestScript(desc) + +k6.RunContainer(ctx, k6.WithCache(), options) +``` + ### Container Methods The K6 container does not expose any method. diff --git a/modules/k6/k6.go b/modules/k6/k6.go index 82976b7ea3..664f7e0695 100644 --- a/modules/k6/k6.go +++ b/modules/k6/k6.go @@ -3,8 +3,13 @@ package k6 import ( "context" "fmt" + "io" + "net/http" + "net/url" "os" + "path" "path/filepath" + "time" "github.com/docker/docker/api/types/mount" @@ -17,17 +22,73 @@ type K6Container struct { testcontainers.Container } +type DownloadableFile struct { + Uri url.URL + DownloadDir string + User string + Password string +} + +func (d *DownloadableFile) getDownloadPath() string { + baseName := path.Base(d.Uri.Path) + return path.Join(d.DownloadDir, baseName) + +} + +func downloadFileFromDescription(d DownloadableFile) error { + + client := http.Client{Timeout: time.Second * 60} + req, err := http.NewRequest(http.MethodGet, d.Uri.String(), nil) + if err != nil { + return err + } + + req.Header.Set("Content-Type", "text/javascript") + // Set up HTTPS request with basic authorization. + if d.User != "" && d.Password != "" { + req.SetBasicAuth(d.User, d.Password) + } + + resp, err := client.Do(req) + if err != nil { + return err + } + + downloadedFile, err := os.Create(d.getDownloadPath()) + if err != nil { + return err + } + defer downloadedFile.Close() + + _, err = io.Copy(downloadedFile, resp.Body) + return err + +} + // WithTestScript mounts the given script into the ./test directory in the container // and passes it to k6 as the test to run. // The path to the script must be an absolute path func WithTestScript(scriptPath string) testcontainers.CustomizeRequestOption { - return func(req *testcontainers.GenericContainerRequest) { - script := filepath.Base(scriptPath) - target := "/home/k6x/" + script + + scriptBaseName := filepath.Base(scriptPath) + f, err := os.Open(scriptPath) + if err != nil { + panic("Cannot create reader for test file ") + } + return WithTestScriptReader(f, scriptBaseName) + +} + +// WithTestScriptReader copies files into the Container using the Reader API +// The script base name is not a path, neither absolute or relative and should +// be just the file name of the script +func WithTestScriptReader(reader io.Reader, scriptBaseName string) testcontainers.CustomizeRequestOption { + opt := func(req *testcontainers.GenericContainerRequest) { + target := "/home/k6x/" + scriptBaseName req.Files = append( req.Files, testcontainers.ContainerFile{ - HostFilePath: scriptPath, + Reader: reader, ContainerFilePath: target, FileMode: 0o644, }, @@ -36,6 +97,18 @@ func WithTestScript(scriptPath string) testcontainers.CustomizeRequestOption { // add script to the k6 run command req.Cmd = append(req.Cmd, target) } + return opt +} + +// WithRemoteTestScript takes a RemoteTestFileDescription and copies to container +func WithRemoteTestScript(d DownloadableFile) testcontainers.CustomizeRequestOption { + + err := downloadFileFromDescription(d) + if err != nil { + panic("Not able to download required test script") + } + + return WithTestScript(d.getDownloadPath()) } // WithCmdOptions pass the given options to the k6 run command diff --git a/modules/k6/k6_test.go b/modules/k6/k6_test.go index f90b24ab29..29a7c2bcce 100644 --- a/modules/k6/k6_test.go +++ b/modules/k6/k6_test.go @@ -2,9 +2,12 @@ package k6_test import ( "context" + "net/url" "path/filepath" + "strings" "testing" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/k6" ) @@ -24,6 +27,16 @@ func TestK6(t *testing.T) { script: "fail.js", expect: 108, }, + { + title: "Passing remote test", + script: "https://raw.githubusercontent.com/testcontainers/testcontainers-go/main/modules/k6/scripts/pass.js", + expect: 0, + }, + { + title: "Failing remote test", + script: "https://raw.githubusercontent.com/testcontainers/testcontainers-go/main/modules/k6/scripts/fail.js", + expect: 108, + }, } for _, tc := range testCases { @@ -31,12 +44,25 @@ func TestK6(t *testing.T) { t.Run(tc.title, func(t *testing.T) { ctx := context.Background() - absPath, err := filepath.Abs(filepath.Join("scripts", tc.script)) - if err != nil { - t.Fatal(err) + var options testcontainers.CustomizeRequestOption + if !strings.HasPrefix(tc.script, "http") { + absPath, err := filepath.Abs(filepath.Join("scripts", tc.script)) + if err != nil { + t.Fatal(err) + } + options = k6.WithTestScript(absPath) + } else { + + uri, err := url.Parse(tc.script) + if err != nil { + t.Fatal(err) + } + + desc := k6.DownloadableFile{Uri: *uri, DownloadDir: t.TempDir()} + options = k6.WithRemoteTestScript(desc) } - container, err := k6.RunContainer(ctx, k6.WithCache(), k6.WithTestScript(absPath)) + container, err := k6.RunContainer(ctx, k6.WithCache(), options) if err != nil { t.Fatal(err) }