From 767d698ba25ec77821a740978042b14d5aad85f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Wed, 15 Nov 2023 11:15:44 +0100 Subject: [PATCH] break: update Executable interface to accept ProcessOptions --- docs/features/common_functional_options.md | 5 +++- modules/cassandra/executable.go | 3 +++ modules/rabbitmq/examples_test.go | 5 +++- modules/rabbitmq/rabbitmq_test.go | 2 +- modules/rabbitmq/types_test.go | 19 ++++++++++++-- options.go | 30 +++++++++++++++++++--- options_test.go | 12 +-------- 7 files changed, 57 insertions(+), 19 deletions(-) diff --git a/docs/features/common_functional_options.md b/docs/features/common_functional_options.md index 1eedfbd8ea..085cf3cb61 100644 --- a/docs/features/common_functional_options.md +++ b/docs/features/common_functional_options.md @@ -24,7 +24,10 @@ Testcontainers exposes the `WithStartupCommand(e ...Executable)` option to run a !!!info To better understand how this feature works, please read the [Create containers: Lifecycle Hooks](/features/creating_container/#lifecycle-hooks) documentation. -It also exports an `Executable` interface, defining one single method: `AsCommand()`, which returns a slice of strings to represent the command and positional arguments to be executed in the container. +It also exports an `Executable` interface, defining the following methods: + +- `AsCommand()`, which returns a slice of strings to represent the command and positional arguments to be executed in the container; +- `Options()` to set the command options, such as the working directory, environment variables, user executing the command, etc. It returns a slice of functional options to configure the command. You could use this feature to run a custom script, or to run a command that is not supported by the module right after the container is started. diff --git a/modules/cassandra/executable.go b/modules/cassandra/executable.go index 7645eb22ba..1a85829e80 100644 --- a/modules/cassandra/executable.go +++ b/modules/cassandra/executable.go @@ -2,9 +2,12 @@ package cassandra import ( "strings" + + "github.com/testcontainers/testcontainers-go" ) type initScript struct { + testcontainers.ExecOptions File string } diff --git a/modules/rabbitmq/examples_test.go b/modules/rabbitmq/examples_test.go index d239bc1f3a..3e9ae1451d 100644 --- a/modules/rabbitmq/examples_test.go +++ b/modules/rabbitmq/examples_test.go @@ -130,7 +130,10 @@ func ExampleRunContainer_withPlugins() { testcontainers.WithImage("rabbitmq:3.7.25-management-alpine"), // Multiple test implementations of the Executable interface, specific to RabbitMQ, exist in the types_test.go file. // Please refer to them for more examples. - testcontainers.WithStartupCommand(testcontainers.RawCommand{"rabbitmq_shovel"}, testcontainers.RawCommand{"rabbitmq_random_exchange"}), + testcontainers.WithStartupCommand( + testcontainers.NewRawCommand([]string{"rabbitmq_shovel"}), + testcontainers.NewRawCommand([]string{"rabbitmq_random_exchange"}), + ), ) if err != nil { panic(err) diff --git a/modules/rabbitmq/rabbitmq_test.go b/modules/rabbitmq/rabbitmq_test.go index 64fe27b1f6..5fb338ca75 100644 --- a/modules/rabbitmq/rabbitmq_test.go +++ b/modules/rabbitmq/rabbitmq_test.go @@ -124,7 +124,7 @@ func TestRunContainer_withAllSettings(t *testing.T) { }), // } // enablePlugins { - testcontainers.WithStartupCommand(Plugin("rabbitmq_shovel"), Plugin("rabbitmq_random_exchange")), + testcontainers.WithStartupCommand(Plugin{Name: "rabbitmq_shovel"}, Plugin{Name: "rabbitmq_random_exchange"}), // } ) if err != nil { diff --git a/modules/rabbitmq/types_test.go b/modules/rabbitmq/types_test.go index ca9fed49d4..7ed92f3805 100644 --- a/modules/rabbitmq/types_test.go +++ b/modules/rabbitmq/types_test.go @@ -4,6 +4,8 @@ import ( "encoding/json" "fmt" "strings" + + "github.com/testcontainers/testcontainers-go" ) // The following structs are added as a demonstration for the RabbitMQ management API therefore, @@ -15,6 +17,7 @@ import ( // --------- Bindings --------- type Binding struct { + testcontainers.ConfigurableExec VHost string Source string Destination string @@ -72,6 +75,7 @@ func (b Binding) AsCommand() []string { // --------- Exchange --------- type Exchange struct { + testcontainers.ConfigurableExec Name string VHost string Type string @@ -117,6 +121,7 @@ func (e Exchange) AsCommand() []string { // --------- OperatorPolicy --------- type OperatorPolicy struct { + testcontainers.ConfigurableExec Name string Pattern string Definition map[string]interface{} @@ -151,6 +156,7 @@ func (op OperatorPolicy) AsCommand() []string { // --------- Parameter --------- type Parameter struct { + testcontainers.ConfigurableExec Component string Name string Value string @@ -176,6 +182,7 @@ func (p Parameter) AsCommand() []string { // --------- Permission --------- type Permission struct { + testcontainers.ConfigurableExec VHost string User string Configure string @@ -205,10 +212,13 @@ func (p Permission) AsCommand() []string { // --------- Plugin --------- -type Plugin string +type Plugin struct { + testcontainers.ConfigurableExec + Name string +} func (p Plugin) AsCommand() []string { - return []string{"rabbitmq-plugins", "enable", string(p)} + return []string{"rabbitmq-plugins", "enable", p.Name} } // --------- Plugin --------- @@ -216,6 +226,7 @@ func (p Plugin) AsCommand() []string { // --------- Policy --------- type Policy struct { + testcontainers.ConfigurableExec VHost string Name string Pattern string @@ -257,6 +268,7 @@ func (p Policy) AsCommand() []string { // --------- Queue --------- type Queue struct { + testcontainers.ConfigurableExec Name string VHost string AutoDelete bool @@ -297,6 +309,7 @@ func (q Queue) AsCommand() []string { // --------- User --------- type User struct { + testcontainers.ConfigurableExec Name string Password string Tags []string @@ -325,6 +338,7 @@ func (u User) AsCommand() []string { // --------- Virtual Hosts -------- type VirtualHost struct { + testcontainers.ConfigurableExec Name string Tracing bool } @@ -340,6 +354,7 @@ func (v VirtualHost) AsCommand() []string { } type VirtualHostLimit struct { + testcontainers.ConfigurableExec VHost string Name string Value int diff --git a/options.go b/options.go index 0e15fc9080..43c0b42b77 100644 --- a/options.go +++ b/options.go @@ -10,6 +10,7 @@ import ( "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" + tcexec "github.com/testcontainers/testcontainers-go/exec" "github.com/testcontainers/testcontainers-go/wait" ) @@ -118,14 +119,37 @@ func WithNetwork(networkName string, alias string) CustomizeRequestOption { // as part of the PostStart lifecycle hook. type Executable interface { AsCommand() []string + Options() []tcexec.ProcessOption +} + +// ExecOptions is a struct that provides a default implementation for the Options method +// of the Executable interface. +type ExecOptions struct { + opts []tcexec.ProcessOption +} + +func (ce ExecOptions) Options() []tcexec.ProcessOption { + return ce.opts } // RawCommand is a type that implements Executable and represents a command to be sent to a container -type RawCommand []string +type RawCommand struct { + ExecOptions + cmds []string +} + +func NewRawCommand(cmds []string) RawCommand { + return RawCommand{ + cmds: cmds, + ExecOptions: ExecOptions{ + opts: []tcexec.ProcessOption{}, + }, + } +} // AsCommand returns the command as a slice of strings func (r RawCommand) AsCommand() []string { - return r + return r.cmds } // WithStartupCommand will execute the command representation of each Executable into the container. @@ -139,7 +163,7 @@ func WithStartupCommand(execs ...Executable) CustomizeRequestOption { for _, exec := range execs { execFn := func(ctx context.Context, c Container) error { - _, _, err := c.Exec(ctx, exec.AsCommand()) + _, _, err := c.Exec(ctx, exec.AsCommand(), exec.Options()...) return err } diff --git a/options_test.go b/options_test.go index 4d61c18a8e..c29b930be1 100644 --- a/options_test.go +++ b/options_test.go @@ -120,14 +120,6 @@ func TestWithNetworkMultipleCallsWithSameNameReuseTheNetwork(t *testing.T) { assert.Equal(t, "new-network", resources[0].Name) } -type testExecutable struct { - cmds []string -} - -func (t testExecutable) AsCommand() []string { - return t.cmds -} - func TestWithStartupCommand(t *testing.T) { req := testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ @@ -137,9 +129,7 @@ func TestWithStartupCommand(t *testing.T) { Started: true, } - testExec := testExecutable{ - cmds: []string{"touch", "/tmp/.testcontainers"}, - } + testExec := testcontainers.NewRawCommand([]string{"touch", "/tmp/.testcontainers"}) testcontainers.WithStartupCommand(testExec)(&req)