From d24a7ba1db15b7587b50635d399fe2200ab73a4d Mon Sep 17 00:00:00 2001 From: Michal Vyskocil Date: Sun, 1 Dec 2024 21:40:28 +0100 Subject: [PATCH 01/11] feat: WithConfigFile - pass a configuration file to nats server --- modules/nats/nats.go | 13 ++++++++++++ modules/nats/nats_test.go | 44 +++++++++++++++++++++++++++++++++++++++ modules/nats/options.go | 15 +++++++++++-- 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/modules/nats/nats.go b/modules/nats/nats.go index cd040c09e2..41db26b28c 100644 --- a/modules/nats/nats.go +++ b/modules/nats/nats.go @@ -58,6 +58,19 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom genericContainerReq.Cmd = append(genericContainerReq.Cmd, []string{"--" + k, v}...) } + // Pass a configuration file if provided + if settings.ConfigFile != nil { + genericContainerReq.Cmd = append(genericContainerReq.Cmd, "-config", "/etc/nats.conf") + genericContainerReq.Files = append( + genericContainerReq.Files, + testcontainers.ContainerFile{ + Reader: settings.ConfigFile, + ContainerFilePath: "/etc/nats.conf", + FileMode: 0o644, + }, + ) + } + container, err := testcontainers.GenericContainer(ctx, genericContainerReq) var c *NATSContainer if container != nil { diff --git a/modules/nats/nats_test.go b/modules/nats/nats_test.go index 660473c2e5..be47970dd7 100644 --- a/modules/nats/nats_test.go +++ b/modules/nats/nats_test.go @@ -1,7 +1,9 @@ package nats_test import ( + "bufio" "context" + "strings" "testing" "github.com/nats-io/nats.go" @@ -57,3 +59,45 @@ func TestNATS(t *testing.T) { require.Equal(t, "hello", string(msg.Data)) } + +func TestNATSWithConfigFile(t *testing.T) { + const natsConf = ` +listen: 0.0.0.0:4222 +authorization { + token: "s3cr3t" +} +` + ctx := context.Background() + + // createNATSContainer { + ctr, err := tcnats.Run(ctx, "nats:2.9", tcnats.WithConfigFile(strings.NewReader(natsConf))) + // } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + // connectionString { + uri, err := ctr.ConnectionString(ctx) + // } + require.NoError(t, err) + + // connect via token { + nc, err := nats.Connect(uri, nats.Name("API Token Test"), nats.Token("s3cr3t")) + // } + require.NoError(t, err) + t.Cleanup(nc.Close) + + // validate /etc/nats.conf mentioned in logs { + const expected = "Using configuration file: /etc/nats.conf" + logs, err := ctr.Logs(ctx) + require.NoError(t, err) + sc := bufio.NewScanner(logs) + found := false + for sc.Scan() { + if strings.Contains(sc.Text(), expected) { + found = true + break + } + } + // } + require.Truef(t, found, "expected log line not found: %s", expected) +} diff --git a/modules/nats/options.go b/modules/nats/options.go index 38856d68a9..c562d844c0 100644 --- a/modules/nats/options.go +++ b/modules/nats/options.go @@ -1,18 +1,21 @@ package nats import ( + "io" "strings" "github.com/testcontainers/testcontainers-go" ) type options struct { - CmdArgs map[string]string + CmdArgs map[string]string + ConfigFile io.Reader } func defaultOptions() options { return options{ - CmdArgs: make(map[string]string, 0), + CmdArgs: make(map[string]string, 0), + ConfigFile: nil, } } @@ -49,3 +52,11 @@ func WithArgument(flag string, value string) CmdOption { o.CmdArgs[flag] = value } } + +// WithConfigFile pass io.Reader to the NATS container as /etc/nats.conf +// Changes of a connectivity (listen address, or ports) may break a testcontainer +func WithConfigFile(config io.Reader) CmdOption { + return func(o *options) { + o.ConfigFile = config + } +} From deaadb3b9ec5e8a8441339470423892cacd3702a Mon Sep 17 00:00:00 2001 From: Michal Vyskocil Date: Sun, 1 Dec 2024 22:01:46 +0100 Subject: [PATCH 02/11] add a testcase with a incorrect token --- modules/nats/nats_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/nats/nats_test.go b/modules/nats/nats_test.go index be47970dd7..13bbfcfa48 100644 --- a/modules/nats/nats_test.go +++ b/modules/nats/nats_test.go @@ -80,6 +80,13 @@ authorization { // } require.NoError(t, err) + // connect without token { + mallory, err := nats.Connect(uri, nats.Name("Mallory"), nats.Token("secret")) + // } + require.Error(t, err) + require.ErrorIs(t, err, nats.ErrAuthorization) + t.Cleanup(mallory.Close) + // connect via token { nc, err := nats.Connect(uri, nats.Name("API Token Test"), nats.Token("s3cr3t")) // } From 5da61731f5dfb3f77ab224f22ba36e3149307042 Mon Sep 17 00:00:00 2001 From: Michal Vyskocil Date: Mon, 2 Dec 2024 09:34:01 +0100 Subject: [PATCH 03/11] use testcontainers.ContainerCustomizer as suggested during CR Add ConfigGile struct, which implements the interface. --- modules/nats/nats.go | 13 ------------- modules/nats/options.go | 38 +++++++++++++++++++++++++++++--------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/modules/nats/nats.go b/modules/nats/nats.go index 41db26b28c..cd040c09e2 100644 --- a/modules/nats/nats.go +++ b/modules/nats/nats.go @@ -58,19 +58,6 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom genericContainerReq.Cmd = append(genericContainerReq.Cmd, []string{"--" + k, v}...) } - // Pass a configuration file if provided - if settings.ConfigFile != nil { - genericContainerReq.Cmd = append(genericContainerReq.Cmd, "-config", "/etc/nats.conf") - genericContainerReq.Files = append( - genericContainerReq.Files, - testcontainers.ContainerFile{ - Reader: settings.ConfigFile, - ContainerFilePath: "/etc/nats.conf", - FileMode: 0o644, - }, - ) - } - container, err := testcontainers.GenericContainer(ctx, genericContainerReq) var c *NATSContainer if container != nil { diff --git a/modules/nats/options.go b/modules/nats/options.go index c562d844c0..1172160445 100644 --- a/modules/nats/options.go +++ b/modules/nats/options.go @@ -8,23 +8,30 @@ import ( ) type options struct { - CmdArgs map[string]string - ConfigFile io.Reader + CmdArgs map[string]string } func defaultOptions() options { return options{ - CmdArgs: make(map[string]string, 0), - ConfigFile: nil, + CmdArgs: make(map[string]string, 0), } } -// Compiler check to ensure that Option implements the testcontainers.ContainerCustomizer interface. -var _ testcontainers.ContainerCustomizer = (*CmdOption)(nil) +// Compiler check to ensure that CmdOption and ConfigFile implements the +// testcontainers.ContainerCustomizer interface. +var ( + _ testcontainers.ContainerCustomizer = (*CmdOption)(nil) + _ testcontainers.ContainerCustomizer = (*ConfigFile)(nil) +) // CmdOption is an option for the NATS container. type CmdOption func(opts *options) +// ConfigFile optionally pass a configuration file into NATS container. +type ConfigFile struct { + reader io.Reader +} + // Customize is a NOOP. It's defined to satisfy the testcontainers.ContainerCustomizer interface. func (o CmdOption) Customize(req *testcontainers.GenericContainerRequest) error { // NOOP to satisfy interface. @@ -55,8 +62,21 @@ func WithArgument(flag string, value string) CmdOption { // WithConfigFile pass io.Reader to the NATS container as /etc/nats.conf // Changes of a connectivity (listen address, or ports) may break a testcontainer -func WithConfigFile(config io.Reader) CmdOption { - return func(o *options) { - o.ConfigFile = config +func WithConfigFile(config io.Reader) ConfigFile { + return ConfigFile{reader: config} +} + +func (c ConfigFile) Customize(req *testcontainers.GenericContainerRequest) error { + if c.reader != nil { + req.Cmd = append(req.Cmd, "-config", "/etc/nats.conf") + req.Files = append( + req.Files, + testcontainers.ContainerFile{ + Reader: c.reader, + ContainerFilePath: "/etc/nats.conf", + FileMode: 0o644, + }, + ) } + return nil } From a3636bbb3b0946ea20f2270d704a419faf20b7cb Mon Sep 17 00:00:00 2001 From: Michal Vyskocil Date: Mon, 2 Dec 2024 11:08:36 +0100 Subject: [PATCH 04/11] drop a custom struct, return and implement an interface directly --- modules/nats/options.go | 43 +++++++++++++++-------------------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/modules/nats/options.go b/modules/nats/options.go index 1172160445..4526fa7fdd 100644 --- a/modules/nats/options.go +++ b/modules/nats/options.go @@ -17,21 +17,12 @@ func defaultOptions() options { } } -// Compiler check to ensure that CmdOption and ConfigFile implements the -// testcontainers.ContainerCustomizer interface. -var ( - _ testcontainers.ContainerCustomizer = (*CmdOption)(nil) - _ testcontainers.ContainerCustomizer = (*ConfigFile)(nil) -) +// Compiler check to ensure that CmdOption implements the testcontainers.ContainerCustomizer interface. +var _ testcontainers.ContainerCustomizer = (*CmdOption)(nil) // CmdOption is an option for the NATS container. type CmdOption func(opts *options) -// ConfigFile optionally pass a configuration file into NATS container. -type ConfigFile struct { - reader io.Reader -} - // Customize is a NOOP. It's defined to satisfy the testcontainers.ContainerCustomizer interface. func (o CmdOption) Customize(req *testcontainers.GenericContainerRequest) error { // NOOP to satisfy interface. @@ -62,21 +53,19 @@ func WithArgument(flag string, value string) CmdOption { // WithConfigFile pass io.Reader to the NATS container as /etc/nats.conf // Changes of a connectivity (listen address, or ports) may break a testcontainer -func WithConfigFile(config io.Reader) ConfigFile { - return ConfigFile{reader: config} -} - -func (c ConfigFile) Customize(req *testcontainers.GenericContainerRequest) error { - if c.reader != nil { - req.Cmd = append(req.Cmd, "-config", "/etc/nats.conf") - req.Files = append( - req.Files, - testcontainers.ContainerFile{ - Reader: c.reader, - ContainerFilePath: "/etc/nats.conf", - FileMode: 0o644, - }, - ) +func WithConfigFile(config io.Reader) testcontainers.CustomizeRequestOption { + return func(req *testcontainers.GenericContainerRequest) error { + if config != nil { + req.Cmd = append(req.Cmd, "-config", "/etc/nats.conf") + req.Files = append( + req.Files, + testcontainers.ContainerFile{ + Reader: config, + ContainerFilePath: "/etc/nats.conf", + FileMode: 0o644, + }, + ) + } + return nil } - return nil } From 9a9225f150a516d4e49da0703bd7831ce19ae66a Mon Sep 17 00:00:00 2001 From: Michal Vyskocil Date: Mon, 2 Dec 2024 11:09:06 +0100 Subject: [PATCH 05/11] docs: document nats.WithConfigFile --- docs/modules/nats.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/modules/nats.md b/docs/modules/nats.md index 21419bdc76..3f8a8a0640 100644 --- a/docs/modules/nats.md +++ b/docs/modules/nats.md @@ -75,6 +75,13 @@ These arguments are passed to the NATS server when it starts, as part of the com [Passing arguments](../../modules/nats/examples_test.go) inside_block:withArguments +#### Custom configuration file + +It's possible to pass a custom config file to NATS container using `nats.WithConfigFile(strings.NewReader(config))`. The `io.Reader` is passed as a `-config /etc/nats.conf` arguments to an entrypoint. + +!!! note + Changing a connectivity (listen address or ports) can break the container setup. So configuration must be done with a care. + ### Container Methods The NATS container exposes the following methods: @@ -102,4 +109,4 @@ Exactly like `ConnectionString`, but it panics if an error occurs, returning jus [NATS Cluster](../../modules/nats/examples_test.go) inside_block:cluster - \ No newline at end of file + From 51efb82af923551562ef50bf7a0249d0b52189b5 Mon Sep 17 00:00:00 2001 From: Michal Vyskocil Date: Mon, 2 Dec 2024 11:16:42 +0100 Subject: [PATCH 06/11] Update modules/nats/options.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Manuel de la Peña --- modules/nats/options.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/nats/options.go b/modules/nats/options.go index 4526fa7fdd..5b4abee9c2 100644 --- a/modules/nats/options.go +++ b/modules/nats/options.go @@ -52,7 +52,7 @@ func WithArgument(flag string, value string) CmdOption { } // WithConfigFile pass io.Reader to the NATS container as /etc/nats.conf -// Changes of a connectivity (listen address, or ports) may break a testcontainer +// Changing the connectivity (listen address or ports) can break the container setup. func WithConfigFile(config io.Reader) testcontainers.CustomizeRequestOption { return func(req *testcontainers.GenericContainerRequest) error { if config != nil { From 9247184a826744fcbec4137f5dda5dfb1ca2359b Mon Sep 17 00:00:00 2001 From: Michal Vyskocil Date: Mon, 2 Dec 2024 11:16:58 +0100 Subject: [PATCH 07/11] Update docs/modules/nats.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Manuel de la Peña --- docs/modules/nats.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/nats.md b/docs/modules/nats.md index 3f8a8a0640..adb519d679 100644 --- a/docs/modules/nats.md +++ b/docs/modules/nats.md @@ -80,7 +80,7 @@ These arguments are passed to the NATS server when it starts, as part of the com It's possible to pass a custom config file to NATS container using `nats.WithConfigFile(strings.NewReader(config))`. The `io.Reader` is passed as a `-config /etc/nats.conf` arguments to an entrypoint. !!! note - Changing a connectivity (listen address or ports) can break the container setup. So configuration must be done with a care. + Changing the connectivity (listen address or ports) can break the container setup. So configuration must be done with care. ### Container Methods From 03a0d0912d13226a12099211c0cf6bf097ee30f4 Mon Sep 17 00:00:00 2001 From: Michal Vyskocil Date: Mon, 2 Dec 2024 11:19:33 +0100 Subject: [PATCH 08/11] Update docs/modules/nats.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Manuel de la Peña --- docs/modules/nats.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/modules/nats.md b/docs/modules/nats.md index adb519d679..30152bd99c 100644 --- a/docs/modules/nats.md +++ b/docs/modules/nats.md @@ -77,6 +77,8 @@ These arguments are passed to the NATS server when it starts, as part of the com #### Custom configuration file +- Not available until the next release of testcontainers-go :material-tag: main + It's possible to pass a custom config file to NATS container using `nats.WithConfigFile(strings.NewReader(config))`. The `io.Reader` is passed as a `-config /etc/nats.conf` arguments to an entrypoint. !!! note From 554f949ca217ceb59479e4dca08a1c7b17cde7f1 Mon Sep 17 00:00:00 2001 From: Michal Vyskocil Date: Mon, 2 Dec 2024 16:42:07 +0100 Subject: [PATCH 09/11] remove document markers from a unit test --- modules/nats/nats_test.go | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/modules/nats/nats_test.go b/modules/nats/nats_test.go index 13bbfcfa48..8da8b9363e 100644 --- a/modules/nats/nats_test.go +++ b/modules/nats/nats_test.go @@ -69,31 +69,25 @@ authorization { ` ctx := context.Background() - // createNATSContainer { ctr, err := tcnats.Run(ctx, "nats:2.9", tcnats.WithConfigFile(strings.NewReader(natsConf))) - // } testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - // connectionString { uri, err := ctr.ConnectionString(ctx) - // } require.NoError(t, err) - // connect without token { + // connect without a correct token must fail mallory, err := nats.Connect(uri, nats.Name("Mallory"), nats.Token("secret")) - // } require.Error(t, err) require.ErrorIs(t, err, nats.ErrAuthorization) t.Cleanup(mallory.Close) - // connect via token { + // connect with a correct token must succeed nc, err := nats.Connect(uri, nats.Name("API Token Test"), nats.Token("s3cr3t")) - // } require.NoError(t, err) t.Cleanup(nc.Close) - // validate /etc/nats.conf mentioned in logs { + // validate /etc/nats.conf mentioned in logs const expected = "Using configuration file: /etc/nats.conf" logs, err := ctr.Logs(ctx) require.NoError(t, err) @@ -105,6 +99,5 @@ authorization { break } } - // } require.Truef(t, found, "expected log line not found: %s", expected) } From ff17ba124cb6cf9c72bfd04206630fd4afb6fb8a Mon Sep 17 00:00:00 2001 From: Michal Vyskocil Date: Tue, 3 Dec 2024 14:44:59 +0100 Subject: [PATCH 10/11] address CR comments --- docs/modules/nats.md | 2 +- modules/nats/nats_test.go | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/modules/nats.md b/docs/modules/nats.md index 30152bd99c..8a47c962fd 100644 --- a/docs/modules/nats.md +++ b/docs/modules/nats.md @@ -79,7 +79,7 @@ These arguments are passed to the NATS server when it starts, as part of the com - Not available until the next release of testcontainers-go :material-tag: main -It's possible to pass a custom config file to NATS container using `nats.WithConfigFile(strings.NewReader(config))`. The `io.Reader` is passed as a `-config /etc/nats.conf` arguments to an entrypoint. +It's possible to pass a custom config file to NATS container using `nats.WithConfigFile(strings.NewReader(config))`. The content of `io.Reader` is passed as a `-config /etc/nats.conf` arguments to an entrypoint. !!! note Changing the connectivity (listen address or ports) can break the container setup. So configuration must be done with care. diff --git a/modules/nats/nats_test.go b/modules/nats/nats_test.go index 8da8b9363e..ad777c9556 100644 --- a/modules/nats/nats_test.go +++ b/modules/nats/nats_test.go @@ -5,6 +5,7 @@ import ( "context" "strings" "testing" + "time" "github.com/nats-io/nats.go" "github.com/stretchr/testify/require" @@ -78,14 +79,13 @@ authorization { // connect without a correct token must fail mallory, err := nats.Connect(uri, nats.Name("Mallory"), nats.Token("secret")) - require.Error(t, err) - require.ErrorIs(t, err, nats.ErrAuthorization) t.Cleanup(mallory.Close) + require.EqualError(t, err, "nats: Authorization Violation") // connect with a correct token must succeed nc, err := nats.Connect(uri, nats.Name("API Token Test"), nats.Token("s3cr3t")) - require.NoError(t, err) t.Cleanup(nc.Close) + require.NoError(t, err) // validate /etc/nats.conf mentioned in logs const expected = "Using configuration file: /etc/nats.conf" @@ -93,6 +93,9 @@ authorization { require.NoError(t, err) sc := bufio.NewScanner(logs) found := false + time.AfterFunc(5*time.Second, func() { + require.Truef(t, found, "expected log line not found after 5 seconds: %s", expected) + }) for sc.Scan() { if strings.Contains(sc.Text(), expected) { found = true From 89b884bee52defc668f3bfaef5a0bd88880074ac Mon Sep 17 00:00:00 2001 From: Michal Vyskocil Date: Tue, 3 Dec 2024 14:47:15 +0100 Subject: [PATCH 11/11] fix a comment --- modules/nats/options.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/nats/options.go b/modules/nats/options.go index 5b4abee9c2..60abd10056 100644 --- a/modules/nats/options.go +++ b/modules/nats/options.go @@ -51,7 +51,7 @@ func WithArgument(flag string, value string) CmdOption { } } -// WithConfigFile pass io.Reader to the NATS container as /etc/nats.conf +// WithConfigFile pass a content of io.Reader to the NATS container as /etc/nats.conf // Changing the connectivity (listen address or ports) can break the container setup. func WithConfigFile(config io.Reader) testcontainers.CustomizeRequestOption { return func(req *testcontainers.GenericContainerRequest) error {