From 540052f0fa617f69f7f5efc57b6d76e475fc1848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Thu, 20 Jun 2024 06:12:43 +0200 Subject: [PATCH] docs: example for NATS cluster (#2591) * chore: add cluster example * docs: include missing methods and options for NATS --- docs/modules/nats.md | 32 +++++++ modules/nats/examples_test.go | 155 ++++++++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+) diff --git a/docs/modules/nats.md b/docs/modules/nats.md index 9d561bd39b..72bd7b4fc5 100644 --- a/docs/modules/nats.md +++ b/docs/modules/nats.md @@ -44,6 +44,8 @@ for NATS. E.g. `testcontainers.WithImage("nats:2.9")`. #### Set username and password +- Since testcontainers-go :material-tag: v0.24.0 + If you need to set different credentials, you can use `WithUsername` and `WithPassword` options. By default, the username, the password are not set. To establish the connection with the NATS container: @@ -51,15 +53,45 @@ options. By default, the username, the password are not set. To establish the co [Connect using the credentials](../../modules/nats/examples_test.go) inside_block:natsConnect +#### Cmd Arguments + +- Since testcontainers-go :material-tag: v0.24.0 + +It's possible to pass extra arguments to the NATS container using the `testcontainers.WithArgument` option. E.g. `nats.WithArgument("cluster_name", "c1")`. +These arguments are passed to the NATS server when it starts, as part of the command line arguments of the entrypoint. + +!!! note + Arguments do not need to be prefixed with `--`: the NATS container will add them automatically. + + +[Passing arguments](../../modules/nats/examples_test.go) inside_block:withArguments + + ### Container Methods The NATS container exposes the following methods: #### ConnectionString +- Since testcontainers-go :material-tag: v0.24.0 + This method returns the connection string to connect to the NATS container, using the default `4222` port. It's possible to pass extra parameters to the connection string, in a variadic way. [Get connection string](../../modules/nats/nats_test.go) inside_block:connectionString + +#### MustConnectionString + +- Since testcontainers-go :material-tag: v0.30.0 + +Exactly like `ConnectionString`, but it panics if an error occurs, returning just a string. + +## Examples + +### NATS Cluster + + +[NATS Cluster](../../modules/nats/examples_test.go) inside_block:cluster + \ No newline at end of file diff --git a/modules/nats/examples_test.go b/modules/nats/examples_test.go index fd95691cc4..63e1c2b956 100644 --- a/modules/nats/examples_test.go +++ b/modules/nats/examples_test.go @@ -4,11 +4,13 @@ import ( "context" "fmt" "log" + "time" natsgo "github.com/nats-io/nats.go" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/nats" + "github.com/testcontainers/testcontainers-go/network" ) func ExampleRunContainer() { @@ -74,3 +76,156 @@ func ExampleRunContainer_connectWithCredentials() { // Output: // true } + +func ExampleRunContainer_cluster() { + ctx := context.Background() + + nwr, err := network.New(ctx) + if err != nil { + log.Fatalf("failed to create network: %s", err) + } + + // withArguments { + natsContainer1, err := nats.RunContainer(ctx, + network.WithNetwork([]string{"nats1"}, nwr), + nats.WithArgument("name", "nats1"), + nats.WithArgument("cluster_name", "c1"), + nats.WithArgument("cluster", "nats://nats1:6222"), + nats.WithArgument("routes", "nats://nats1:6222,nats://nats2:6222,nats://nats3:6222"), + nats.WithArgument("http_port", "8222"), + ) + // } + if err != nil { + log.Fatalf("failed to start container: %s", err) + } + // Clean up the container + defer func() { + if err := natsContainer1.Terminate(ctx); err != nil { + log.Fatalf("failed to terminate container: %s", err) + } + }() + + natsContainer2, err := nats.RunContainer(ctx, + network.WithNetwork([]string{"nats2"}, nwr), + nats.WithArgument("name", "nats2"), + nats.WithArgument("cluster_name", "c1"), + nats.WithArgument("cluster", "nats://nats2:6222"), + nats.WithArgument("routes", "nats://nats1:6222,nats://nats2:6222,nats://nats3:6222"), + nats.WithArgument("http_port", "8222"), + ) + if err != nil { + log.Fatalf("failed to start container: %s", err) // nolint:gocritic + } + // Clean up the container + defer func() { + if err := natsContainer2.Terminate(ctx); err != nil { + log.Fatalf("failed to terminate container: %s", err) + } + }() + + natsContainer3, err := nats.RunContainer(ctx, + network.WithNetwork([]string{"nats3"}, nwr), + nats.WithArgument("name", "nats3"), + nats.WithArgument("cluster_name", "c1"), + nats.WithArgument("cluster", "nats://nats3:6222"), + nats.WithArgument("routes", "nats://nats1:6222,nats://nats2:6222,nats://nats3:6222"), + nats.WithArgument("http_port", "8222"), + ) + if err != nil { + log.Fatalf("failed to start container: %s", err) // nolint:gocritic + } + defer func() { + if err := natsContainer3.Terminate(ctx); err != nil { + log.Fatalf("failed to terminate container: %s", err) + } + }() + + // cluster URL + servers := natsContainer1.MustConnectionString(ctx) + "," + natsContainer2.MustConnectionString(ctx) + "," + natsContainer3.MustConnectionString(ctx) + + nc, err := natsgo.Connect(servers, natsgo.MaxReconnects(5), natsgo.ReconnectWait(2*time.Second)) + if err != nil { + log.Fatalf("connecting to nats container failed:\n\t%v\n", err) // nolint:gocritic + } + + { + // Simple Publisher + err = nc.Publish("foo", []byte("Hello World")) + if err != nil { + log.Fatalf("failed to publish message: %s", err) // nolint:gocritic + } + } + + { + // Channel subscriber + ch := make(chan *natsgo.Msg, 64) + sub, err := nc.ChanSubscribe("channel", ch) + if err != nil { + log.Fatalf("failed to subscribe to message: %s", err) // nolint:gocritic + } + + // Request + err = nc.Publish("channel", []byte("Hello NATS Cluster!")) + if err != nil { + log.Fatalf("failed to publish message: %s", err) // nolint:gocritic + } + + msg := <-ch + fmt.Println(string(msg.Data)) + + err = sub.Unsubscribe() + if err != nil { + log.Fatalf("failed to unsubscribe: %s", err) // nolint:gocritic + } + + err = sub.Drain() + if err != nil { + log.Fatalf("failed to drain: %s", err) // nolint:gocritic + } + } + + { + // Responding to a request message + sub, err := nc.Subscribe("request", func(m *natsgo.Msg) { + err1 := m.Respond([]byte("answer is 42")) + if err1 != nil { + log.Fatalf("failed to respond to message: %s", err1) // nolint:gocritic + } + }) + if err != nil { + log.Fatalf("failed to subscribe to message: %s", err) // nolint:gocritic + } + + // Request + msg, err := nc.Request("request", []byte("what is the answer?"), 1*time.Second) + if err != nil { + log.Fatalf("failed to send request: %s", err) // nolint:gocritic + } + + fmt.Println(string(msg.Data)) + + err = sub.Unsubscribe() + if err != nil { + log.Fatalf("failed to unsubscribe: %s", err) // nolint:gocritic + } + + err = sub.Drain() + if err != nil { + log.Fatalf("failed to drain: %s", err) // nolint:gocritic + } + } + + // Drain connection (Preferred for responders) + // Close() not needed if this is called. + err = nc.Drain() + if err != nil { + log.Fatalf("failed to drain connection: %s", err) // nolint:gocritic + } + + // Close connection + nc.Close() + + // Output: + // Hello NATS Cluster! + // answer is 42 +}