From 1ef2f846a7c6731fe765be85084477884d0fcd63 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sun, 3 Aug 2025 05:08:37 +0200
Subject: [PATCH 01/54] feat(kafka_native): new module
---
.github/dependabot.yml | 1 +
docs/modules/kafka_native.md | 76 ++++++
mkdocs.yml | 1 +
modules/kafka_native/Makefile | 5 +
modules/kafka_native/consumer_test.go | 56 +++++
modules/kafka_native/examples_test.go | 43 ++++
modules/kafka_native/go.mod | 85 +++++++
modules/kafka_native/go.sum | 260 +++++++++++++++++++++
modules/kafka_native/kafka.go | 171 ++++++++++++++
modules/kafka_native/kafka_helpers_test.go | 63 +++++
modules/kafka_native/kafka_test.go | 82 +++++++
11 files changed, 843 insertions(+)
create mode 100644 docs/modules/kafka_native.md
create mode 100644 modules/kafka_native/Makefile
create mode 100644 modules/kafka_native/consumer_test.go
create mode 100644 modules/kafka_native/examples_test.go
create mode 100644 modules/kafka_native/go.mod
create mode 100644 modules/kafka_native/go.sum
create mode 100644 modules/kafka_native/kafka.go
create mode 100644 modules/kafka_native/kafka_helpers_test.go
create mode 100644 modules/kafka_native/kafka_test.go
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 9525d30327..40036db57a 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -42,6 +42,7 @@ updates:
- /modules/k3s
- /modules/k6
- /modules/kafka
+ - /modules/kafka_native
- /modules/localstack
- /modules/mariadb
- /modules/meilisearch
diff --git a/docs/modules/kafka_native.md b/docs/modules/kafka_native.md
new file mode 100644
index 0000000000..845ede8c0a
--- /dev/null
+++ b/docs/modules/kafka_native.md
@@ -0,0 +1,76 @@
+# Kafka Native
+
+Since :material-tag: v0.39.0
+
+## Introduction
+
+The Testcontainers module for [Apache Kafka Native](https://hub.docker.com/r/apache/kafka-native).
+
+## Adding this module to your project dependencies
+
+Please run the following command to add the Kafka module to your Go dependencies:
+
+```
+go get github.com/testcontainers/testcontainers-go/modules/kafka_native
+```
+
+## Usage example
+
+
+[Creating a Kafka container](../../modules/kafka_native/examples_test.go) inside_block:runKafkaContainer
+
+
+## Module Reference
+
+### Run function
+
+The Kafka module exposes one entrypoint function to create the Kafka container, and this function receives three parameters:
+
+```golang
+func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*KafkaContainer, error)
+```
+
+- `context.Context`, the Go context.
+- `string`, the Docker image to use.
+- `testcontainers.ContainerCustomizer`, a variadic argument for passing options.
+
+#### Image
+
+Use the second argument in the `Run` function to set a valid Docker image.
+In example: `Run(context.Background(), "apache/kafka-native:3.9.1")`.
+
+#### Environment variables
+
+The environment variables that are already set by default are:
+
+
+[Environment variables](../../modules/kafka_native/kafka.go) inside_block:envVars
+
+
+And also KAFKA_ADVERTISED_LISTENERS that is defined dynamically based on the container's hostname.
+
+#### Init script
+
+The Kafka container will be started using a custom shell script:
+
+
+[Init script](../../modules/kafka_native/kafka.go) inside_block:starterScriptContentText
+
+
+### Container Options
+
+When starting the Kafka container, you can pass options in a variadic way to configure it.
+
+{% include "../features/common_functional_options_list.md" %}
+
+### Container Methods
+
+The Kafka container exposes the following methods:
+
+#### Brokers
+
+The `Brokers(ctx)` method returns the Kafka brokers as a string slice, containing the host and the random port defined by Kafka's public port (`9093/tcp`).
+
+
+[Get Kafka brokers](../../modules/kafka_native/kafka_test.go) inside_block:getBrokers
+
diff --git a/mkdocs.yml b/mkdocs.yml
index 625acbf673..2be9695c53 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -93,6 +93,7 @@ nav:
- modules/k3s.md
- modules/k6.md
- modules/kafka.md
+ - modules/kafka_native.md
- modules/localstack.md
- modules/mariadb.md
- modules/meilisearch.md
diff --git a/modules/kafka_native/Makefile b/modules/kafka_native/Makefile
new file mode 100644
index 0000000000..cb486dc6a2
--- /dev/null
+++ b/modules/kafka_native/Makefile
@@ -0,0 +1,5 @@
+include ../../commons-test.mk
+
+.PHONY: test
+test:
+ $(MAKE) test-kafka-native
diff --git a/modules/kafka_native/consumer_test.go b/modules/kafka_native/consumer_test.go
new file mode 100644
index 0000000000..3397ff8e75
--- /dev/null
+++ b/modules/kafka_native/consumer_test.go
@@ -0,0 +1,56 @@
+package kafka_native_test
+
+import (
+ "testing"
+
+ "github.com/IBM/sarama"
+)
+
+// TestKafkaConsumer is a test consumer for Kafka
+type TestKafkaConsumer struct {
+ t *testing.T
+ ready chan bool
+ done chan bool
+ cancel chan bool
+ message *sarama.ConsumerMessage
+}
+
+func NewTestKafkaConsumer(t *testing.T) (*TestKafkaConsumer, <-chan bool, <-chan bool, func()) {
+ t.Helper()
+ kc := &TestKafkaConsumer{
+ t: t,
+ ready: make(chan bool, 1),
+ done: make(chan bool, 1),
+ cancel: make(chan bool, 1),
+ }
+ return kc, kc.ready, kc.done, func() {
+ kc.cancel <- true
+ }
+}
+
+func (k *TestKafkaConsumer) Setup(_ sarama.ConsumerGroupSession) error {
+ return nil
+}
+
+func (k *TestKafkaConsumer) Cleanup(_ sarama.ConsumerGroupSession) error {
+ return nil
+}
+
+// ConsumeClaim is called by the Kafka client library when a message is received
+func (k *TestKafkaConsumer) ConsumeClaim(session sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error {
+ k.ready <- true
+ for {
+ select {
+ case message := <-claim.Messages():
+ k.message = message
+ session.MarkMessage(message, "")
+ k.done <- true
+
+ case <-k.cancel:
+ return nil
+
+ case <-session.Context().Done():
+ return nil
+ }
+ }
+}
diff --git a/modules/kafka_native/examples_test.go b/modules/kafka_native/examples_test.go
new file mode 100644
index 0000000000..15403c132b
--- /dev/null
+++ b/modules/kafka_native/examples_test.go
@@ -0,0 +1,43 @@
+package kafka_native_test
+
+import (
+ "context"
+ "fmt"
+ "log"
+
+ "github.com/testcontainers/testcontainers-go"
+ "github.com/testcontainers/testcontainers-go/modules/kafka_native"
+)
+
+func ExampleRun() {
+ // runKafkaContainer {
+ ctx := context.Background()
+
+ kafkaContainer, err := kafka_native.Run(ctx,
+ "apache/kafka-native:3.9.1",
+ kafka_native.WithClusterID("test-cluster"),
+ )
+ defer func() {
+ if err := testcontainers.TerminateContainer(kafkaContainer); err != nil {
+ log.Printf("failed to terminate container: %s", err)
+ }
+ }()
+ if err != nil {
+ log.Printf("failed to start container: %s", err)
+ return
+ }
+ // }
+
+ state, err := kafkaContainer.State(ctx)
+ if err != nil {
+ log.Printf("failed to get container state: %s", err)
+ return
+ }
+
+ fmt.Println(kafkaContainer.ClusterID)
+ fmt.Println(state.Running)
+
+ // Output:
+ // test-cluster
+ // true
+}
diff --git a/modules/kafka_native/go.mod b/modules/kafka_native/go.mod
new file mode 100644
index 0000000000..d2cc3ce24e
--- /dev/null
+++ b/modules/kafka_native/go.mod
@@ -0,0 +1,85 @@
+module github.com/testcontainers/testcontainers-go/modules/kafka_native
+
+go 1.23.0
+
+toolchain go1.23.6
+
+require (
+ github.com/IBM/sarama v1.42.1
+ github.com/docker/go-connections v0.5.0
+ github.com/stretchr/testify v1.10.0
+ github.com/testcontainers/testcontainers-go v0.38.0
+ golang.org/x/mod v0.16.0
+)
+
+require (
+ dario.cat/mergo v1.0.1 // indirect
+ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
+ github.com/Microsoft/go-winio v0.6.2 // indirect
+ github.com/cenkalti/backoff/v4 v4.2.1 // indirect
+ github.com/containerd/errdefs v1.0.0 // indirect
+ github.com/containerd/errdefs/pkg v0.3.0 // indirect
+ github.com/containerd/log v0.1.0 // indirect
+ github.com/containerd/platforms v0.2.1 // indirect
+ github.com/cpuguy83/dockercfg v0.3.2 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/distribution/reference v0.6.0 // indirect
+ github.com/docker/docker v28.2.2+incompatible // indirect
+ github.com/docker/go-units v0.5.0 // indirect
+ github.com/eapache/go-resiliency v1.4.0 // indirect
+ github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect
+ github.com/eapache/queue v1.1.0 // indirect
+ github.com/ebitengine/purego v0.8.4 // indirect
+ github.com/felixge/httpsnoop v1.0.4 // indirect
+ github.com/go-logr/logr v1.4.2 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/go-ole/go-ole v1.2.6 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/snappy v0.0.4 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/hashicorp/errwrap v1.1.0 // indirect
+ github.com/hashicorp/go-multierror v1.1.1 // indirect
+ github.com/hashicorp/go-uuid v1.0.3 // indirect
+ github.com/jcmturner/aescts/v2 v2.0.0 // indirect
+ github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
+ github.com/jcmturner/gofork v1.7.6 // indirect
+ github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
+ github.com/jcmturner/rpc/v2 v2.0.3 // indirect
+ github.com/klauspost/compress v1.18.0 // indirect
+ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
+ github.com/magiconair/properties v1.8.10 // indirect
+ github.com/moby/docker-image-spec v1.3.1 // indirect
+ github.com/moby/go-archive v0.1.0 // indirect
+ github.com/moby/patternmatcher v0.6.0 // indirect
+ github.com/moby/sys/sequential v0.6.0 // indirect
+ github.com/moby/sys/user v0.4.0 // indirect
+ github.com/moby/sys/userns v0.1.0 // indirect
+ github.com/moby/term v0.5.0 // indirect
+ github.com/morikuni/aec v1.0.0 // indirect
+ github.com/opencontainers/go-digest v1.0.0 // indirect
+ github.com/opencontainers/image-spec v1.1.1 // indirect
+ github.com/pierrec/lz4/v4 v4.1.18 // indirect
+ github.com/pkg/errors v0.9.1 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
+ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
+ github.com/shirou/gopsutil/v4 v4.25.5 // indirect
+ github.com/sirupsen/logrus v1.9.3 // indirect
+ github.com/tklauser/go-sysconf v0.3.12 // indirect
+ github.com/tklauser/numcpus v0.6.1 // indirect
+ github.com/yusufpapurcu/wmi v1.2.4 // indirect
+ go.opentelemetry.io/auto/sdk v1.1.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
+ go.opentelemetry.io/otel v1.35.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect
+ go.opentelemetry.io/otel/metric v1.35.0 // indirect
+ go.opentelemetry.io/otel/trace v1.35.0 // indirect
+ go.opentelemetry.io/proto/otlp v1.0.0 // indirect
+ golang.org/x/crypto v0.37.0 // indirect
+ golang.org/x/net v0.38.0 // indirect
+ golang.org/x/sync v0.8.0 // indirect
+ golang.org/x/sys v0.32.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
+
+replace github.com/testcontainers/testcontainers-go => ../..
diff --git a/modules/kafka_native/go.sum b/modules/kafka_native/go.sum
new file mode 100644
index 0000000000..695a2f434a
--- /dev/null
+++ b/modules/kafka_native/go.sum
@@ -0,0 +1,260 @@
+dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
+dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
+github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
+github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
+github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
+github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
+github.com/IBM/sarama v1.42.1 h1:wugyWa15TDEHh2kvq2gAy1IHLjEjuYOYgXz/ruC/OSQ=
+github.com/IBM/sarama v1.42.1/go.mod h1:Xxho9HkHd4K/MDUo/T/sOqwtX/17D33++E9Wib6hUdQ=
+github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
+github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
+github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
+github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
+github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
+github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
+github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
+github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
+github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
+github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
+github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
+github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
+github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
+github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
+github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
+github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
+github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
+github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw=
+github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
+github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
+github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
+github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/eapache/go-resiliency v1.4.0 h1:3OK9bWpPk5q6pbFAaYSEwD9CLUSHG8bnZuqX2yMt3B0=
+github.com/eapache/go-resiliency v1.4.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho=
+github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws=
+github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0=
+github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
+github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
+github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
+github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
+github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
+github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
+github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
+github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
+github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
+github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
+github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
+github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
+github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
+github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
+github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
+github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
+github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
+github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
+github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
+github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
+github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
+github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
+github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
+github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
+github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
+github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
+github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
+github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
+github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
+github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
+github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
+github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
+github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
+github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
+github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
+github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
+github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
+github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
+github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
+github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
+github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
+github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
+github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
+github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
+github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
+github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
+github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
+github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
+github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
+github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
+github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
+github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc=
+github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
+github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
+github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
+github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
+github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
+github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
+github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
+go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
+go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
+go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
+go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
+go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
+go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
+go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
+go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
+go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
+go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
+go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
+go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
+golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
+golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
+golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
+golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
+golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
+golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
+golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
+golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
+golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8=
+google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
+google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw=
+google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
+google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
+gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
diff --git a/modules/kafka_native/kafka.go b/modules/kafka_native/kafka.go
new file mode 100644
index 0000000000..944bbe7ea5
--- /dev/null
+++ b/modules/kafka_native/kafka.go
@@ -0,0 +1,171 @@
+package kafka_native
+
+import (
+ "context"
+ "fmt"
+ "math"
+ "strconv"
+
+ "github.com/docker/go-connections/nat"
+ "github.com/testcontainers/testcontainers-go"
+ "github.com/testcontainers/testcontainers-go/wait"
+)
+
+const publicPort = nat.Port("9093/tcp")
+const (
+ starterScript = "/usr/sbin/testcontainers_start.sh"
+
+ // starterScriptContentText {
+ starterScriptContent = `#!/bin/bash
+export KAFKA_ADVERTISED_LISTENERS=%s,BROKER://%s:9092
+echo Starting Kafka Native
+exec /etc/kafka/docker/run`
+ // }
+)
+
+// KafkaContainer represents the Kafka container type used in the module
+type KafkaContainer struct {
+ testcontainers.Container
+ ClusterID string
+}
+
+// Run creates an instance of the Kafka container type
+func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*KafkaContainer, error) {
+ req := testcontainers.ContainerRequest{
+ Image: img,
+ ExposedPorts: []string{string(publicPort)},
+ Env: map[string]string{
+ // envVars {
+ "KAFKA_LISTENERS": "PLAINTEXT://0.0.0.0:9093,BROKER://0.0.0.0:9092,CONTROLLER://0.0.0.0:9094",
+ "KAFKA_REST_BOOTSTRAP_SERVERS": "PLAINTEXT://0.0.0.0:9093,BROKER://0.0.0.0:9092,CONTROLLER://0.0.0.0:9094",
+ "KAFKA_LISTENER_SECURITY_PROTOCOL_MAP": "BROKER:PLAINTEXT,PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT",
+ "KAFKA_INTER_BROKER_LISTENER_NAME": "BROKER",
+ "KAFKA_BROKER_ID": "1",
+ "KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR": "1",
+ "KAFKA_OFFSETS_TOPIC_NUM_PARTITIONS": "1",
+ "KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR": "1",
+ "KAFKA_TRANSACTION_STATE_LOG_MIN_ISR": "1",
+ "KAFKA_LOG_FLUSH_INTERVAL_MESSAGES": strconv.Itoa(math.MaxInt),
+ "KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS": "0",
+ "KAFKA_NODE_ID": "1",
+ "KAFKA_PROCESS_ROLES": "broker,controller",
+ "KAFKA_CONTROLLER_LISTENER_NAMES": "CONTROLLER",
+ // }
+ },
+ Entrypoint: []string{"sh"},
+ // this CMD will wait for the starter script to be copied into the container and then execute it
+ Cmd: []string{"-c", "while [ ! -f " + starterScript + " ]; do sleep 0.1; done; bash " + starterScript},
+ LifecycleHooks: []testcontainers.ContainerLifecycleHooks{
+ {
+ PostStarts: []testcontainers.ContainerHook{
+ // Use a single hook to copy the starter script and wait for
+ // the Kafka server to be ready. This prevents the wait running
+ // if the starter script fails to copy.
+ func(ctx context.Context, c testcontainers.Container) error {
+ // 1. copy the starter script into the container
+ if err := copyStarterScript(ctx, c); err != nil {
+ return fmt.Errorf("copy starter script: %w", err)
+ }
+
+ // 2. wait for the Kafka server to be ready
+ return wait.ForLog(".*Transitioning from RECOVERY to RUNNING.*").AsRegexp().WaitUntilReady(ctx, c)
+ },
+ },
+ },
+ },
+ }
+
+ genericContainerReq := testcontainers.GenericContainerRequest{
+ ContainerRequest: req,
+ Started: true,
+ }
+
+ for _, opt := range opts {
+ if err := opt.Customize(&genericContainerReq); err != nil {
+ return nil, err
+ }
+ }
+
+ configureControllerQuorumVoters(&genericContainerReq)
+
+ container, err := testcontainers.GenericContainer(ctx, genericContainerReq)
+ var c *KafkaContainer
+ if container != nil {
+ c = &KafkaContainer{Container: container, ClusterID: genericContainerReq.Env["CLUSTER_ID"]}
+ }
+
+ if err != nil {
+ return c, fmt.Errorf("generic container: %w", err)
+ }
+
+ return c, nil
+}
+
+// copyStarterScript copies the starter script into the container.
+func copyStarterScript(ctx context.Context, c testcontainers.Container) error {
+ if err := wait.ForMappedPort(publicPort).
+ WaitUntilReady(ctx, c); err != nil {
+ return fmt.Errorf("wait for mapped port: %w", err)
+ }
+
+ endpoint, err := c.PortEndpoint(ctx, publicPort, "PLAINTEXT")
+ if err != nil {
+ return fmt.Errorf("port endpoint: %w", err)
+ }
+
+ inspect, err := c.Inspect(ctx)
+ if err != nil {
+ return fmt.Errorf("inspect: %w", err)
+ }
+
+ hostname := inspect.Config.Hostname
+
+ scriptContent := fmt.Sprintf(starterScriptContent, endpoint, hostname)
+
+ if err := c.CopyToContainer(ctx, []byte(scriptContent), starterScript, 0o755); err != nil {
+ return fmt.Errorf("copy to container: %w", err)
+ }
+
+ return nil
+}
+
+func WithClusterID(clusterID string) testcontainers.CustomizeRequestOption {
+ return func(req *testcontainers.GenericContainerRequest) error {
+ req.Env["CLUSTER_ID"] = clusterID
+
+ return nil
+ }
+}
+
+// Brokers retrieves the broker connection strings from Kafka with only one entry,
+// defined by the exposed public port.
+func (kc *KafkaContainer) Brokers(ctx context.Context) ([]string, error) {
+ endpoint, err := kc.PortEndpoint(ctx, publicPort, "")
+ if err != nil {
+ return nil, err
+ }
+
+ return []string{endpoint}, nil
+}
+
+// configureControllerQuorumVoters sets the quorum voters for the controller. For that, it will
+// check if there are any network aliases defined for the container and use the first alias in the
+// first network. Else, it will use localhost.
+func configureControllerQuorumVoters(req *testcontainers.GenericContainerRequest) {
+ if req.Env == nil {
+ req.Env = map[string]string{}
+ }
+
+ if req.Env["KAFKA_CONTROLLER_QUORUM_VOTERS"] == "" {
+ host := "localhost"
+ if len(req.Networks) > 0 {
+ nw := req.Networks[0]
+ if len(req.NetworkAliases[nw]) > 0 {
+ host = req.NetworkAliases[nw][0]
+ }
+ }
+
+ req.Env["KAFKA_CONTROLLER_QUORUM_VOTERS"] = fmt.Sprintf("1@%s:9094", host)
+ }
+ // }
+}
diff --git a/modules/kafka_native/kafka_helpers_test.go b/modules/kafka_native/kafka_helpers_test.go
new file mode 100644
index 0000000000..c9227fcc40
--- /dev/null
+++ b/modules/kafka_native/kafka_helpers_test.go
@@ -0,0 +1,63 @@
+package kafka_native
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ "github.com/testcontainers/testcontainers-go"
+)
+
+func TestConfigureQuorumVoters(t *testing.T) {
+ tests := []struct {
+ name string
+ req *testcontainers.GenericContainerRequest
+ expectedVoters string
+ }{
+ {
+ name: "voters on localhost",
+ req: &testcontainers.GenericContainerRequest{
+ ContainerRequest: testcontainers.ContainerRequest{
+ Env: map[string]string{},
+ },
+ },
+ expectedVoters: "1@localhost:9094",
+ },
+ {
+ name: "voters on first network alias of the first network",
+ req: &testcontainers.GenericContainerRequest{
+ ContainerRequest: testcontainers.ContainerRequest{
+ Env: map[string]string{},
+ Networks: []string{"foo", "bar", "baaz"},
+ NetworkAliases: map[string][]string{
+ "foo": {"foo0", "foo1", "foo2", "foo3"},
+ "bar": {"bar0", "bar1", "bar2", "bar3"},
+ "baaz": {"baaz0", "baaz1", "baaz2", "baaz3"},
+ },
+ },
+ },
+ expectedVoters: "1@foo0:9094",
+ },
+ {
+ name: "voters on localhost if alias but no networks",
+ req: &testcontainers.GenericContainerRequest{
+ ContainerRequest: testcontainers.ContainerRequest{
+ NetworkAliases: map[string][]string{
+ "foo": {"foo0", "foo1", "foo2", "foo3"},
+ "bar": {"bar0", "bar1", "bar2", "bar3"},
+ "baaz": {"baaz0", "baaz1", "baaz2", "baaz3"},
+ },
+ },
+ },
+ expectedVoters: "1@localhost:9094",
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ configureControllerQuorumVoters(test.req)
+
+ require.Equalf(t, test.expectedVoters, test.req.Env["KAFKA_CONTROLLER_QUORUM_VOTERS"], "expected KAFKA_CONTROLLER_QUORUM_VOTERS to be %s, got %s", test.expectedVoters, test.req.Env["KAFKA_CONTROLLER_QUORUM_VOTERS"])
+ })
+ }
+}
diff --git a/modules/kafka_native/kafka_test.go b/modules/kafka_native/kafka_test.go
new file mode 100644
index 0000000000..1a15393b6c
--- /dev/null
+++ b/modules/kafka_native/kafka_test.go
@@ -0,0 +1,82 @@
+package kafka_native_test
+
+import (
+ "context"
+ "strings"
+ "testing"
+
+ "github.com/IBM/sarama"
+ "github.com/stretchr/testify/require"
+
+ "github.com/testcontainers/testcontainers-go"
+ "github.com/testcontainers/testcontainers-go/modules/kafka_native"
+)
+
+func TestKafka(t *testing.T) {
+ topic := "some-topic"
+ ctx := context.Background()
+
+ kafkaContainer, err := kafka_native.Run(ctx, "apache/kafka-native:3.9.1", kafka_native.WithClusterID("kraftCluster"))
+ testcontainers.CleanupContainer(t, kafkaContainer)
+ require.NoError(t, err)
+
+ assertAdvertisedListeners(t, kafkaContainer)
+
+ require.Truef(t, strings.EqualFold(kafkaContainer.ClusterID, "kraftCluster"), "expected clusterID to be %s, got %s", "kraftCluster", kafkaContainer.ClusterID)
+
+ // getBrokers {
+ brokers, err := kafkaContainer.Brokers(ctx)
+ // }
+ require.NoError(t, err)
+
+ config := sarama.NewConfig()
+ client, err := sarama.NewConsumerGroup(brokers, "groupName", config)
+ require.NoError(t, err)
+
+ consumer, ready, done, cancel := NewTestKafkaConsumer(t)
+ defer cancel()
+ go func() {
+ if err := client.Consume(context.Background(), []string{topic}, consumer); err != nil {
+ cancel()
+ }
+ }()
+
+ // wait for the consumer to be ready
+ <-ready
+
+ // perform assertions
+
+ // set config to true because successfully delivered messages will be returned on the Successes channel
+ config.Producer.Return.Successes = true
+
+ producer, err := sarama.NewSyncProducer(brokers, config)
+ require.NoError(t, err)
+
+ _, _, err = producer.SendMessage(&sarama.ProducerMessage{
+ Topic: topic,
+ Key: sarama.StringEncoder("key"),
+ Value: sarama.StringEncoder("value"),
+ })
+ require.NoError(t, err)
+
+ <-done
+
+ require.Truef(t, strings.EqualFold(string(consumer.message.Key), "key"), "expected key to be %s, got %s", "key", string(consumer.message.Key))
+ require.Truef(t, strings.EqualFold(string(consumer.message.Value), "value"), "expected value to be %s, got %s", "value", string(consumer.message.Value))
+}
+
+// assertAdvertisedListeners checks that the advertised listeners are set correctly:
+// - The BROKER:// protocol is using the hostname of the Kafka container
+func assertAdvertisedListeners(t *testing.T, container testcontainers.Container) {
+ t.Helper()
+ inspect, err := container.Inspect(context.Background())
+ require.NoError(t, err)
+
+ brokerURL := "BROKER://" + inspect.Config.Hostname + ":9092"
+
+ ctx := context.Background()
+
+ bs := testcontainers.RequireContainerExec(ctx, t, container, []string{"cat", "/usr/sbin/testcontainers_start.sh"})
+
+ require.Containsf(t, bs, brokerURL, "expected advertised listeners to contain %s, got %s", brokerURL, bs)
+}
From 4ebe2d1e9cb530d7666f64d182d9648f0c607492 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sun, 3 Aug 2025 05:08:37 +0200
Subject: [PATCH 02/54] feat(kafka_native): new module
---
docs/modules/kafka_native.md | 76 ++++++
mkdocs.yml | 1 +
modules/kafka_native/Makefile | 5 +
modules/kafka_native/consumer_test.go | 56 +++++
modules/kafka_native/examples_test.go | 43 ++++
modules/kafka_native/go.mod | 85 +++++++
modules/kafka_native/go.sum | 260 +++++++++++++++++++++
modules/kafka_native/kafka.go | 172 ++++++++++++++
modules/kafka_native/kafka_helpers_test.go | 63 +++++
modules/kafka_native/kafka_test.go | 82 +++++++
10 files changed, 843 insertions(+)
create mode 100644 docs/modules/kafka_native.md
create mode 100644 modules/kafka_native/Makefile
create mode 100644 modules/kafka_native/consumer_test.go
create mode 100644 modules/kafka_native/examples_test.go
create mode 100644 modules/kafka_native/go.mod
create mode 100644 modules/kafka_native/go.sum
create mode 100644 modules/kafka_native/kafka.go
create mode 100644 modules/kafka_native/kafka_helpers_test.go
create mode 100644 modules/kafka_native/kafka_test.go
diff --git a/docs/modules/kafka_native.md b/docs/modules/kafka_native.md
new file mode 100644
index 0000000000..6188adcdeb
--- /dev/null
+++ b/docs/modules/kafka_native.md
@@ -0,0 +1,76 @@
+# Kafka Native
+
+Since :material-tag: v0.39.0
+
+## Introduction
+
+The Testcontainers module for [Apache Kafka Native](https://hub.docker.com/r/apache/kafka-native).
+
+## Adding this module to your project dependencies
+
+Please run the following command to add the Kafka module to your Go dependencies:
+
+```
+go get github.com/testcontainers/testcontainers-go/modules/kafka_native
+```
+
+## Usage example
+
+
+[Creating a Kafka container](../../modules/kafka_native/examples_test.go) inside_block:runKafkaContainer
+
+
+## Module Reference
+
+### Run function
+
+The Kafka module exposes one entrypoint function to create the Kafka container, and this function receives three parameters:
+
+```golang
+func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*KafkaContainer, error)
+```
+
+- `context.Context`, the Go context.
+- `string`, the Docker image to use.
+- `testcontainers.ContainerCustomizer`, a variadic argument for passing options.
+
+#### Image
+
+Use the second argument in the `Run` function to set a valid Docker image.
+In example: `Run(context.Background(), "apache/kafka-native:3.9.1")`.
+
+#### Environment variables
+
+The environment variables that are already set by default are:
+
+
+[Environment variables](../../modules/kafka_native/kafka.go) inside_block:envVars
+
+
+And also KAFKA_ADVERTISED_LISTENERS that is defined dynamically based on the container's hostname.
+
+#### Init script
+
+The Kafka container will be started using a custom shell script:
+
+
+[Init script](../../modules/kafka_native/kafka.go) inside_block:starterScript
+
+
+### Container Options
+
+When starting the Kafka container, you can pass options in a variadic way to configure it.
+
+{% include "../features/common_functional_options_list.md" %}
+
+### Container Methods
+
+The Kafka container exposes the following methods:
+
+#### Brokers
+
+The `Brokers(ctx)` method returns the Kafka brokers as a string slice, containing the host and the random port defined by Kafka's public port (`9093/tcp`).
+
+
+[Get Kafka brokers](../../modules/kafka_native/kafka_test.go) inside_block:getBrokers
+
diff --git a/mkdocs.yml b/mkdocs.yml
index 8d6ee9f469..192927c63a 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -93,6 +93,7 @@ nav:
- modules/k3s.md
- modules/k6.md
- modules/kafka.md
+ - modules/kafka_native.md
- modules/localstack.md
- modules/mariadb.md
- modules/meilisearch.md
diff --git a/modules/kafka_native/Makefile b/modules/kafka_native/Makefile
new file mode 100644
index 0000000000..cb486dc6a2
--- /dev/null
+++ b/modules/kafka_native/Makefile
@@ -0,0 +1,5 @@
+include ../../commons-test.mk
+
+.PHONY: test
+test:
+ $(MAKE) test-kafka-native
diff --git a/modules/kafka_native/consumer_test.go b/modules/kafka_native/consumer_test.go
new file mode 100644
index 0000000000..3397ff8e75
--- /dev/null
+++ b/modules/kafka_native/consumer_test.go
@@ -0,0 +1,56 @@
+package kafka_native_test
+
+import (
+ "testing"
+
+ "github.com/IBM/sarama"
+)
+
+// TestKafkaConsumer is a test consumer for Kafka
+type TestKafkaConsumer struct {
+ t *testing.T
+ ready chan bool
+ done chan bool
+ cancel chan bool
+ message *sarama.ConsumerMessage
+}
+
+func NewTestKafkaConsumer(t *testing.T) (*TestKafkaConsumer, <-chan bool, <-chan bool, func()) {
+ t.Helper()
+ kc := &TestKafkaConsumer{
+ t: t,
+ ready: make(chan bool, 1),
+ done: make(chan bool, 1),
+ cancel: make(chan bool, 1),
+ }
+ return kc, kc.ready, kc.done, func() {
+ kc.cancel <- true
+ }
+}
+
+func (k *TestKafkaConsumer) Setup(_ sarama.ConsumerGroupSession) error {
+ return nil
+}
+
+func (k *TestKafkaConsumer) Cleanup(_ sarama.ConsumerGroupSession) error {
+ return nil
+}
+
+// ConsumeClaim is called by the Kafka client library when a message is received
+func (k *TestKafkaConsumer) ConsumeClaim(session sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error {
+ k.ready <- true
+ for {
+ select {
+ case message := <-claim.Messages():
+ k.message = message
+ session.MarkMessage(message, "")
+ k.done <- true
+
+ case <-k.cancel:
+ return nil
+
+ case <-session.Context().Done():
+ return nil
+ }
+ }
+}
diff --git a/modules/kafka_native/examples_test.go b/modules/kafka_native/examples_test.go
new file mode 100644
index 0000000000..fd7b862c37
--- /dev/null
+++ b/modules/kafka_native/examples_test.go
@@ -0,0 +1,43 @@
+package kafka_native_test
+
+import (
+ "context"
+ "fmt"
+ "log"
+
+ "github.com/testcontainers/testcontainers-go"
+ kafka "github.com/testcontainers/testcontainers-go/modules/kafka_native"
+)
+
+func ExampleRun() {
+ // runKafkaContainer {
+ ctx := context.Background()
+
+ kafkaContainer, err := kafka.Run(ctx,
+ "apache/kafka-native:3.9.1",
+ kafka.WithClusterID("test-cluster"),
+ )
+ defer func() {
+ if err := testcontainers.TerminateContainer(kafkaContainer); err != nil {
+ log.Printf("failed to terminate container: %s", err)
+ }
+ }()
+ if err != nil {
+ log.Printf("failed to start container: %s", err)
+ return
+ }
+ // }
+
+ state, err := kafkaContainer.State(ctx)
+ if err != nil {
+ log.Printf("failed to get container state: %s", err)
+ return
+ }
+
+ fmt.Println(kafkaContainer.ClusterID)
+ fmt.Println(state.Running)
+
+ // Output:
+ // test-cluster
+ // true
+}
diff --git a/modules/kafka_native/go.mod b/modules/kafka_native/go.mod
new file mode 100644
index 0000000000..d2cc3ce24e
--- /dev/null
+++ b/modules/kafka_native/go.mod
@@ -0,0 +1,85 @@
+module github.com/testcontainers/testcontainers-go/modules/kafka_native
+
+go 1.23.0
+
+toolchain go1.23.6
+
+require (
+ github.com/IBM/sarama v1.42.1
+ github.com/docker/go-connections v0.5.0
+ github.com/stretchr/testify v1.10.0
+ github.com/testcontainers/testcontainers-go v0.38.0
+ golang.org/x/mod v0.16.0
+)
+
+require (
+ dario.cat/mergo v1.0.1 // indirect
+ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
+ github.com/Microsoft/go-winio v0.6.2 // indirect
+ github.com/cenkalti/backoff/v4 v4.2.1 // indirect
+ github.com/containerd/errdefs v1.0.0 // indirect
+ github.com/containerd/errdefs/pkg v0.3.0 // indirect
+ github.com/containerd/log v0.1.0 // indirect
+ github.com/containerd/platforms v0.2.1 // indirect
+ github.com/cpuguy83/dockercfg v0.3.2 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/distribution/reference v0.6.0 // indirect
+ github.com/docker/docker v28.2.2+incompatible // indirect
+ github.com/docker/go-units v0.5.0 // indirect
+ github.com/eapache/go-resiliency v1.4.0 // indirect
+ github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect
+ github.com/eapache/queue v1.1.0 // indirect
+ github.com/ebitengine/purego v0.8.4 // indirect
+ github.com/felixge/httpsnoop v1.0.4 // indirect
+ github.com/go-logr/logr v1.4.2 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/go-ole/go-ole v1.2.6 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/snappy v0.0.4 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/hashicorp/errwrap v1.1.0 // indirect
+ github.com/hashicorp/go-multierror v1.1.1 // indirect
+ github.com/hashicorp/go-uuid v1.0.3 // indirect
+ github.com/jcmturner/aescts/v2 v2.0.0 // indirect
+ github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
+ github.com/jcmturner/gofork v1.7.6 // indirect
+ github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
+ github.com/jcmturner/rpc/v2 v2.0.3 // indirect
+ github.com/klauspost/compress v1.18.0 // indirect
+ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
+ github.com/magiconair/properties v1.8.10 // indirect
+ github.com/moby/docker-image-spec v1.3.1 // indirect
+ github.com/moby/go-archive v0.1.0 // indirect
+ github.com/moby/patternmatcher v0.6.0 // indirect
+ github.com/moby/sys/sequential v0.6.0 // indirect
+ github.com/moby/sys/user v0.4.0 // indirect
+ github.com/moby/sys/userns v0.1.0 // indirect
+ github.com/moby/term v0.5.0 // indirect
+ github.com/morikuni/aec v1.0.0 // indirect
+ github.com/opencontainers/go-digest v1.0.0 // indirect
+ github.com/opencontainers/image-spec v1.1.1 // indirect
+ github.com/pierrec/lz4/v4 v4.1.18 // indirect
+ github.com/pkg/errors v0.9.1 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
+ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
+ github.com/shirou/gopsutil/v4 v4.25.5 // indirect
+ github.com/sirupsen/logrus v1.9.3 // indirect
+ github.com/tklauser/go-sysconf v0.3.12 // indirect
+ github.com/tklauser/numcpus v0.6.1 // indirect
+ github.com/yusufpapurcu/wmi v1.2.4 // indirect
+ go.opentelemetry.io/auto/sdk v1.1.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
+ go.opentelemetry.io/otel v1.35.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect
+ go.opentelemetry.io/otel/metric v1.35.0 // indirect
+ go.opentelemetry.io/otel/trace v1.35.0 // indirect
+ go.opentelemetry.io/proto/otlp v1.0.0 // indirect
+ golang.org/x/crypto v0.37.0 // indirect
+ golang.org/x/net v0.38.0 // indirect
+ golang.org/x/sync v0.8.0 // indirect
+ golang.org/x/sys v0.32.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
+
+replace github.com/testcontainers/testcontainers-go => ../..
diff --git a/modules/kafka_native/go.sum b/modules/kafka_native/go.sum
new file mode 100644
index 0000000000..695a2f434a
--- /dev/null
+++ b/modules/kafka_native/go.sum
@@ -0,0 +1,260 @@
+dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
+dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
+github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
+github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
+github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
+github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
+github.com/IBM/sarama v1.42.1 h1:wugyWa15TDEHh2kvq2gAy1IHLjEjuYOYgXz/ruC/OSQ=
+github.com/IBM/sarama v1.42.1/go.mod h1:Xxho9HkHd4K/MDUo/T/sOqwtX/17D33++E9Wib6hUdQ=
+github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
+github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
+github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
+github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
+github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
+github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
+github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
+github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
+github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
+github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
+github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
+github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
+github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
+github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
+github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
+github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
+github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
+github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw=
+github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
+github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
+github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
+github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/eapache/go-resiliency v1.4.0 h1:3OK9bWpPk5q6pbFAaYSEwD9CLUSHG8bnZuqX2yMt3B0=
+github.com/eapache/go-resiliency v1.4.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho=
+github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws=
+github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0=
+github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
+github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
+github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
+github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
+github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
+github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
+github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
+github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
+github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
+github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
+github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
+github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
+github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
+github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
+github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
+github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
+github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
+github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
+github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
+github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
+github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
+github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
+github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
+github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
+github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
+github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
+github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
+github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
+github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
+github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
+github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
+github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
+github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
+github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
+github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
+github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
+github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
+github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
+github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
+github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
+github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
+github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
+github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
+github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
+github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
+github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
+github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
+github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
+github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
+github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
+github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
+github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc=
+github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
+github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
+github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
+github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
+github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
+github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
+github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
+go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
+go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
+go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
+go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
+go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
+go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
+go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
+go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
+go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
+go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
+go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
+go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
+golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
+golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
+golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
+golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
+golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
+golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
+golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
+golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
+golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8=
+google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
+google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw=
+google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
+google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
+gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
diff --git a/modules/kafka_native/kafka.go b/modules/kafka_native/kafka.go
new file mode 100644
index 0000000000..8f680f8150
--- /dev/null
+++ b/modules/kafka_native/kafka.go
@@ -0,0 +1,172 @@
+package kafka_native
+
+import (
+ "context"
+ "fmt"
+ "math"
+ "strconv"
+
+ "github.com/docker/go-connections/nat"
+ "github.com/testcontainers/testcontainers-go"
+ "github.com/testcontainers/testcontainers-go/wait"
+)
+
+const publicPort = nat.Port("9093/tcp")
+const (
+ starterScript = "/usr/sbin/testcontainers_start.sh"
+
+ // starterScript {
+ starterScriptContent = `#!/bin/bash
+export KAFKA_ADVERTISED_LISTENERS=%s,BROKER://%s:9092
+echo Starting Kafka Native
+exec /etc/kafka/docker/run
+`
+ // }
+)
+
+// KafkaContainer represents the Kafka container type used in the module
+type KafkaContainer struct {
+ testcontainers.Container
+ ClusterID string
+}
+
+// Run creates an instance of the Kafka container type
+func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*KafkaContainer, error) {
+ req := testcontainers.ContainerRequest{
+ Image: img,
+ ExposedPorts: []string{string(publicPort)},
+ Env: map[string]string{
+ // envVars {
+ "KAFKA_LISTENERS": "PLAINTEXT://0.0.0.0:9093,BROKER://0.0.0.0:9092,CONTROLLER://0.0.0.0:9094",
+ "KAFKA_REST_BOOTSTRAP_SERVERS": "PLAINTEXT://0.0.0.0:9093,BROKER://0.0.0.0:9092,CONTROLLER://0.0.0.0:9094",
+ "KAFKA_LISTENER_SECURITY_PROTOCOL_MAP": "BROKER:PLAINTEXT,PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT",
+ "KAFKA_INTER_BROKER_LISTENER_NAME": "BROKER",
+ "KAFKA_BROKER_ID": "1",
+ "KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR": "1",
+ "KAFKA_OFFSETS_TOPIC_NUM_PARTITIONS": "1",
+ "KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR": "1",
+ "KAFKA_TRANSACTION_STATE_LOG_MIN_ISR": "1",
+ "KAFKA_LOG_FLUSH_INTERVAL_MESSAGES": strconv.Itoa(math.MaxInt),
+ "KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS": "0",
+ "KAFKA_NODE_ID": "1",
+ "KAFKA_PROCESS_ROLES": "broker,controller",
+ "KAFKA_CONTROLLER_LISTENER_NAMES": "CONTROLLER",
+ // }
+ },
+ Entrypoint: []string{"sh"},
+ // this CMD will wait for the starter script to be copied into the container and then execute it
+ Cmd: []string{"-c", "while [ ! -f " + starterScript + " ]; do sleep 0.1; done; bash " + starterScript},
+ LifecycleHooks: []testcontainers.ContainerLifecycleHooks{
+ {
+ PostStarts: []testcontainers.ContainerHook{
+ // Use a single hook to copy the starter script and wait for
+ // the Kafka server to be ready. This prevents the wait running
+ // if the starter script fails to copy.
+ func(ctx context.Context, c testcontainers.Container) error {
+ // 1. copy the starter script into the container
+ if err := copyStarterScript(ctx, c); err != nil {
+ return fmt.Errorf("copy starter script: %w", err)
+ }
+
+ // 2. wait for the Kafka server to be ready
+ return wait.ForLog(".*Transitioning from RECOVERY to RUNNING.*").AsRegexp().WaitUntilReady(ctx, c)
+ },
+ },
+ },
+ },
+ }
+
+ genericContainerReq := testcontainers.GenericContainerRequest{
+ ContainerRequest: req,
+ Started: true,
+ }
+
+ for _, opt := range opts {
+ if err := opt.Customize(&genericContainerReq); err != nil {
+ return nil, err
+ }
+ }
+
+ configureControllerQuorumVoters(&genericContainerReq)
+
+ container, err := testcontainers.GenericContainer(ctx, genericContainerReq)
+ var c *KafkaContainer
+ if container != nil {
+ c = &KafkaContainer{Container: container, ClusterID: genericContainerReq.Env["CLUSTER_ID"]}
+ }
+
+ if err != nil {
+ return c, fmt.Errorf("generic container: %w", err)
+ }
+
+ return c, nil
+}
+
+// copyStarterScript copies the starter script into the container.
+func copyStarterScript(ctx context.Context, c testcontainers.Container) error {
+ if err := wait.ForMappedPort(publicPort).
+ WaitUntilReady(ctx, c); err != nil {
+ return fmt.Errorf("wait for mapped port: %w", err)
+ }
+
+ endpoint, err := c.PortEndpoint(ctx, publicPort, "PLAINTEXT")
+ if err != nil {
+ return fmt.Errorf("port endpoint: %w", err)
+ }
+
+ inspect, err := c.Inspect(ctx)
+ if err != nil {
+ return fmt.Errorf("inspect: %w", err)
+ }
+
+ hostname := inspect.Config.Hostname
+
+ scriptContent := fmt.Sprintf(starterScriptContent, endpoint, hostname)
+
+ if err := c.CopyToContainer(ctx, []byte(scriptContent), starterScript, 0o755); err != nil {
+ return fmt.Errorf("copy to container: %w", err)
+ }
+
+ return nil
+}
+
+func WithClusterID(clusterID string) testcontainers.CustomizeRequestOption {
+ return func(req *testcontainers.GenericContainerRequest) error {
+ req.Env["CLUSTER_ID"] = clusterID
+
+ return nil
+ }
+}
+
+// Brokers retrieves the broker connection strings from Kafka with only one entry,
+// defined by the exposed public port.
+func (kc *KafkaContainer) Brokers(ctx context.Context) ([]string, error) {
+ endpoint, err := kc.PortEndpoint(ctx, publicPort, "")
+ if err != nil {
+ return nil, err
+ }
+
+ return []string{endpoint}, nil
+}
+
+// configureControllerQuorumVoters sets the quorum voters for the controller. For that, it will
+// check if there are any network aliases defined for the container and use the first alias in the
+// first network. Else, it will use localhost.
+func configureControllerQuorumVoters(req *testcontainers.GenericContainerRequest) {
+ if req.Env == nil {
+ req.Env = map[string]string{}
+ }
+
+ if req.Env["KAFKA_CONTROLLER_QUORUM_VOTERS"] == "" {
+ host := "localhost"
+ if len(req.Networks) > 0 {
+ nw := req.Networks[0]
+ if len(req.NetworkAliases[nw]) > 0 {
+ host = req.NetworkAliases[nw][0]
+ }
+ }
+
+ req.Env["KAFKA_CONTROLLER_QUORUM_VOTERS"] = fmt.Sprintf("1@%s:9094", host)
+ }
+ // }
+}
diff --git a/modules/kafka_native/kafka_helpers_test.go b/modules/kafka_native/kafka_helpers_test.go
new file mode 100644
index 0000000000..c9227fcc40
--- /dev/null
+++ b/modules/kafka_native/kafka_helpers_test.go
@@ -0,0 +1,63 @@
+package kafka_native
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ "github.com/testcontainers/testcontainers-go"
+)
+
+func TestConfigureQuorumVoters(t *testing.T) {
+ tests := []struct {
+ name string
+ req *testcontainers.GenericContainerRequest
+ expectedVoters string
+ }{
+ {
+ name: "voters on localhost",
+ req: &testcontainers.GenericContainerRequest{
+ ContainerRequest: testcontainers.ContainerRequest{
+ Env: map[string]string{},
+ },
+ },
+ expectedVoters: "1@localhost:9094",
+ },
+ {
+ name: "voters on first network alias of the first network",
+ req: &testcontainers.GenericContainerRequest{
+ ContainerRequest: testcontainers.ContainerRequest{
+ Env: map[string]string{},
+ Networks: []string{"foo", "bar", "baaz"},
+ NetworkAliases: map[string][]string{
+ "foo": {"foo0", "foo1", "foo2", "foo3"},
+ "bar": {"bar0", "bar1", "bar2", "bar3"},
+ "baaz": {"baaz0", "baaz1", "baaz2", "baaz3"},
+ },
+ },
+ },
+ expectedVoters: "1@foo0:9094",
+ },
+ {
+ name: "voters on localhost if alias but no networks",
+ req: &testcontainers.GenericContainerRequest{
+ ContainerRequest: testcontainers.ContainerRequest{
+ NetworkAliases: map[string][]string{
+ "foo": {"foo0", "foo1", "foo2", "foo3"},
+ "bar": {"bar0", "bar1", "bar2", "bar3"},
+ "baaz": {"baaz0", "baaz1", "baaz2", "baaz3"},
+ },
+ },
+ },
+ expectedVoters: "1@localhost:9094",
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ configureControllerQuorumVoters(test.req)
+
+ require.Equalf(t, test.expectedVoters, test.req.Env["KAFKA_CONTROLLER_QUORUM_VOTERS"], "expected KAFKA_CONTROLLER_QUORUM_VOTERS to be %s, got %s", test.expectedVoters, test.req.Env["KAFKA_CONTROLLER_QUORUM_VOTERS"])
+ })
+ }
+}
diff --git a/modules/kafka_native/kafka_test.go b/modules/kafka_native/kafka_test.go
new file mode 100644
index 0000000000..d1835fe89a
--- /dev/null
+++ b/modules/kafka_native/kafka_test.go
@@ -0,0 +1,82 @@
+package kafka_native_test
+
+import (
+ "context"
+ "strings"
+ "testing"
+
+ "github.com/IBM/sarama"
+ "github.com/stretchr/testify/require"
+
+ "github.com/testcontainers/testcontainers-go"
+ kafka "github.com/testcontainers/testcontainers-go/modules/kafka_native"
+)
+
+func TestKafka(t *testing.T) {
+ topic := "some-topic"
+ ctx := context.Background()
+
+ kafkaContainer, err := kafka.Run(ctx, "apache/kafka-native:3.9.1", kafka.WithClusterID("kraftCluster"))
+ testcontainers.CleanupContainer(t, kafkaContainer)
+ require.NoError(t, err)
+
+ assertAdvertisedListeners(t, kafkaContainer)
+
+ require.Truef(t, strings.EqualFold(kafkaContainer.ClusterID, "kraftCluster"), "expected clusterID to be %s, got %s", "kraftCluster", kafkaContainer.ClusterID)
+
+ // getBrokers {
+ brokers, err := kafkaContainer.Brokers(ctx)
+ // }
+ require.NoError(t, err)
+
+ config := sarama.NewConfig()
+ client, err := sarama.NewConsumerGroup(brokers, "groupName", config)
+ require.NoError(t, err)
+
+ consumer, ready, done, cancel := NewTestKafkaConsumer(t)
+ defer cancel()
+ go func() {
+ if err := client.Consume(context.Background(), []string{topic}, consumer); err != nil {
+ cancel()
+ }
+ }()
+
+ // wait for the consumer to be ready
+ <-ready
+
+ // perform assertions
+
+ // set config to true because successfully delivered messages will be returned on the Successes channel
+ config.Producer.Return.Successes = true
+
+ producer, err := sarama.NewSyncProducer(brokers, config)
+ require.NoError(t, err)
+
+ _, _, err = producer.SendMessage(&sarama.ProducerMessage{
+ Topic: topic,
+ Key: sarama.StringEncoder("key"),
+ Value: sarama.StringEncoder("value"),
+ })
+ require.NoError(t, err)
+
+ <-done
+
+ require.Truef(t, strings.EqualFold(string(consumer.message.Key), "key"), "expected key to be %s, got %s", "key", string(consumer.message.Key))
+ require.Truef(t, strings.EqualFold(string(consumer.message.Value), "value"), "expected value to be %s, got %s", "value", string(consumer.message.Value))
+}
+
+// assertAdvertisedListeners checks that the advertised listeners are set correctly:
+// - The BROKER:// protocol is using the hostname of the Kafka container
+func assertAdvertisedListeners(t *testing.T, container testcontainers.Container) {
+ t.Helper()
+ inspect, err := container.Inspect(context.Background())
+ require.NoError(t, err)
+
+ brokerURL := "BROKER://" + inspect.Config.Hostname + ":9092"
+
+ ctx := context.Background()
+
+ bs := testcontainers.RequireContainerExec(ctx, t, container, []string{"cat", "/usr/sbin/testcontainers_start.sh"})
+
+ require.Containsf(t, bs, brokerURL, "expected advertised listeners to contain %s, got %s", brokerURL, bs)
+}
From 0a80e8cf98808e4c779198d665c2185a377144e9 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 8 Nov 2025 22:24:42 +0100
Subject: [PATCH 03/54] feat: merge kafka native into kafka module
This should make it easier for users to start.
The change attempts to steer new users to
start with native image, which shows significant
performance improvement especially if many
containers need to be started during entire
test, due to GraalVM optimizations.
While new users should prefer using Kafka
Native, these changes are made in a non-breaking
fashion such that if image was not detected as
being from Apache, it is assumed to be from
Confluent, so that even custom images that
used to work before should continue to work,
in case if they were based on confluentinc images
---
.github/dependabot.yml | 1 -
docs/modules/kafka.md | 39 +++-
docs/modules/kafka_native.md | 76 ------
mkdocs.yml | 1 -
modules/kafka/examples_test.go | 70 +++++-
modules/kafka/kafka.go | 19 +-
modules/kafka/kafka_test.go | 30 ++-
modules/kafka/version.go | 29 +++
modules/kafka/version_test.go | 171 ++++++++++++++
modules/kafka_native/Makefile | 5 -
modules/kafka_native/consumer_test.go | 56 -----
modules/kafka_native/examples_test.go | 43 ----
modules/kafka_native/go.mod | 85 -------
modules/kafka_native/go.sum | 260 ---------------------
modules/kafka_native/kafka.go | 171 --------------
modules/kafka_native/kafka_helpers_test.go | 63 -----
modules/kafka_native/kafka_test.go | 82 -------
17 files changed, 339 insertions(+), 862 deletions(-)
delete mode 100644 docs/modules/kafka_native.md
create mode 100644 modules/kafka/version.go
create mode 100644 modules/kafka/version_test.go
delete mode 100644 modules/kafka_native/Makefile
delete mode 100644 modules/kafka_native/consumer_test.go
delete mode 100644 modules/kafka_native/examples_test.go
delete mode 100644 modules/kafka_native/go.mod
delete mode 100644 modules/kafka_native/go.sum
delete mode 100644 modules/kafka_native/kafka.go
delete mode 100644 modules/kafka_native/kafka_helpers_test.go
delete mode 100644 modules/kafka_native/kafka_test.go
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 40036db57a..9525d30327 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -42,7 +42,6 @@ updates:
- /modules/k3s
- /modules/k6
- /modules/kafka
- - /modules/kafka_native
- /modules/localstack
- /modules/mariadb
- /modules/meilisearch
diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md
index 87c0020b04..e1e9b6f04f 100644
--- a/docs/modules/kafka.md
+++ b/docs/modules/kafka.md
@@ -1,10 +1,12 @@
-# Kafka (KRaft)
+# Kafka
Since :material-tag: v0.24.0
## Introduction
-The Testcontainers module for KRaft: [Apache Kafka Without ZooKeeper](https://developer.confluent.io/learn/kraft).
+The Testcontainers module for Kafka.
+
+This module would run Kafka in Kraft mode: [Apache Kafka Without ZooKeeper](https://developer.confluent.io/learn/kraft/) and it supports both [Apache Kafka](https://kafka.apache.org/) and [Confluent](https://docs.confluent.io/kafka/overview.html) images.
## Adding this module to your project dependencies
@@ -17,9 +19,19 @@ go get github.com/testcontainers/testcontainers-go/modules/kafka
## Usage example
-[Creating a Kafka container](../../modules/kafka/examples_test.go) inside_block:runKafkaContainer
+[Apache Native Kafka](../../modules/kafka/examples_test.go) inside_block:runKafkaContainerApacheNative
+
+
+
+[Apache Kafka](../../modules/kafka/examples_test.go) inside_block:runKafkaContainerApacheNotNative
+
+
+
+[Confluent Kafka](../../modules/kafka/examples_test.go) inside_block:runKafkaContainerConfluent
+The native container ([apache/kafka-native](https://hub.docker.com/r/apache/kafka-native/)) is based on GraalVM and typically starts several seconds faster than alternatives.
+
## Module Reference
### Run function
@@ -42,12 +54,12 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom
#### Image
Use the second argument in the `Run` function to set a valid Docker image.
-In example: `Run(context.Background(), "confluentinc/confluent-local:7.5.0")`.
+In example: `Run(context.Background(), "apache/kafka-native:4.0.1")`.
!!! warning
- The minimal required version of Kafka for KRaft mode is `confluentinc/confluent-local:7.4.0`. If you are using an image that
- is different from the official one, please make sure that it's compatible with KRaft mode, as the module won't check
- the version for you.
+ Module expects that the image in use supports Kraft mode (Kafka without ZooKeeper).
+ The minimal required version of Confluent images for KRaft mode is `confluentinc/confluent-local:7.4.0`.
+ All Apache images support Kraft mode.
#### Environment variables
@@ -59,10 +71,19 @@ The environment variables that are already set by default are:
#### Init script
-The Kafka container will be started using a custom shell script:
+The Kafka container will be started using a custom shell script.
+
+Module would vary the starter script depending on the image in use, using following logic:
+
+- image starts with `apache/kafka`: use Apache Kafka starter script.
+- image starts with `confluentinc/`: use Confluent starter script.
+
+
+[Apache Kafka starter script](../../modules/kafka/kafka.go) inside_block:starterScriptApache
+
-[Init script](../../modules/kafka/kafka.go) inside_block:starterScript
+[Confluent starter script](../../modules/kafka/kafka.go) inside_block:starterScriptConfluentinc
### Container Options
diff --git a/docs/modules/kafka_native.md b/docs/modules/kafka_native.md
deleted file mode 100644
index 845ede8c0a..0000000000
--- a/docs/modules/kafka_native.md
+++ /dev/null
@@ -1,76 +0,0 @@
-# Kafka Native
-
-Since :material-tag: v0.39.0
-
-## Introduction
-
-The Testcontainers module for [Apache Kafka Native](https://hub.docker.com/r/apache/kafka-native).
-
-## Adding this module to your project dependencies
-
-Please run the following command to add the Kafka module to your Go dependencies:
-
-```
-go get github.com/testcontainers/testcontainers-go/modules/kafka_native
-```
-
-## Usage example
-
-
-[Creating a Kafka container](../../modules/kafka_native/examples_test.go) inside_block:runKafkaContainer
-
-
-## Module Reference
-
-### Run function
-
-The Kafka module exposes one entrypoint function to create the Kafka container, and this function receives three parameters:
-
-```golang
-func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*KafkaContainer, error)
-```
-
-- `context.Context`, the Go context.
-- `string`, the Docker image to use.
-- `testcontainers.ContainerCustomizer`, a variadic argument for passing options.
-
-#### Image
-
-Use the second argument in the `Run` function to set a valid Docker image.
-In example: `Run(context.Background(), "apache/kafka-native:3.9.1")`.
-
-#### Environment variables
-
-The environment variables that are already set by default are:
-
-
-[Environment variables](../../modules/kafka_native/kafka.go) inside_block:envVars
-
-
-And also KAFKA_ADVERTISED_LISTENERS that is defined dynamically based on the container's hostname.
-
-#### Init script
-
-The Kafka container will be started using a custom shell script:
-
-
-[Init script](../../modules/kafka_native/kafka.go) inside_block:starterScriptContentText
-
-
-### Container Options
-
-When starting the Kafka container, you can pass options in a variadic way to configure it.
-
-{% include "../features/common_functional_options_list.md" %}
-
-### Container Methods
-
-The Kafka container exposes the following methods:
-
-#### Brokers
-
-The `Brokers(ctx)` method returns the Kafka brokers as a string slice, containing the host and the random port defined by Kafka's public port (`9093/tcp`).
-
-
-[Get Kafka brokers](../../modules/kafka_native/kafka_test.go) inside_block:getBrokers
-
diff --git a/mkdocs.yml b/mkdocs.yml
index 2be9695c53..625acbf673 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -93,7 +93,6 @@ nav:
- modules/k3s.md
- modules/k6.md
- modules/kafka.md
- - modules/kafka_native.md
- modules/localstack.md
- modules/mariadb.md
- modules/meilisearch.md
diff --git a/modules/kafka/examples_test.go b/modules/kafka/examples_test.go
index c275924ecc..ea5330534a 100644
--- a/modules/kafka/examples_test.go
+++ b/modules/kafka/examples_test.go
@@ -9,8 +9,8 @@ import (
"github.com/testcontainers/testcontainers-go/modules/kafka"
)
-func ExampleRun() {
- // runKafkaContainer {
+func ExampleRun_confluentinc() {
+ // runKafkaContainerConfluentinc {
ctx := context.Background()
kafkaContainer, err := kafka.Run(ctx,
@@ -41,3 +41,69 @@ func ExampleRun() {
// test-cluster
// true
}
+
+func ExampleRun_apacheNative() {
+ // runKafkaContainerApacheNative {
+ ctx := context.Background()
+
+ kafkaContainer, err := kafka.Run(ctx,
+ "apache/kafka-native:4.0.1",
+ kafka.WithClusterID("test-cluster"),
+ )
+ defer func() {
+ if err := testcontainers.TerminateContainer(kafkaContainer); err != nil {
+ log.Printf("failed to terminate container: %s", err)
+ }
+ }()
+ if err != nil {
+ log.Printf("failed to start container: %s", err)
+ return
+ }
+ // }
+
+ state, err := kafkaContainer.State(ctx)
+ if err != nil {
+ log.Printf("failed to get container state: %s", err)
+ return
+ }
+
+ fmt.Println(kafkaContainer.ClusterID)
+ fmt.Println(state.Running)
+
+ // Output:
+ // test-cluster
+ // true
+}
+
+func ExampleRun_apacheNotNative() {
+ // runKafkaContainerApacheNotNative {
+ ctx := context.Background()
+
+ kafkaContainer, err := kafka.Run(ctx,
+ "apache/kafka:4.0.1",
+ kafka.WithClusterID("test-cluster"),
+ )
+ defer func() {
+ if err := testcontainers.TerminateContainer(kafkaContainer); err != nil {
+ log.Printf("failed to terminate container: %s", err)
+ }
+ }()
+ if err != nil {
+ log.Printf("failed to start container: %s", err)
+ return
+ }
+ // }
+
+ state, err := kafkaContainer.State(ctx)
+ if err != nil {
+ log.Printf("failed to get container state: %s", err)
+ return
+ }
+
+ fmt.Println(kafkaContainer.ClusterID)
+ fmt.Println(state.Running)
+
+ // Output:
+ // test-cluster
+ // true
+}
diff --git a/modules/kafka/kafka.go b/modules/kafka/kafka.go
index b1342de98f..591e6e8429 100644
--- a/modules/kafka/kafka.go
+++ b/modules/kafka/kafka.go
@@ -19,8 +19,8 @@ const publicPort = nat.Port("9093/tcp")
const (
starterScript = "/usr/sbin/testcontainers_start.sh"
- // starterScript {
- starterScriptContent = `#!/bin/bash
+ // starterScriptConfluentinc {
+ confluentincStarterScriptContent = `#!/bin/bash
source /etc/confluent/docker/bash-config
export KAFKA_ADVERTISED_LISTENERS=%s,BROKER://%s:9092
echo Starting Kafka KRaft mode
@@ -30,6 +30,13 @@ echo '' > /etc/confluent/docker/ensure
/etc/confluent/docker/configure
/etc/confluent/docker/launch`
// }
+
+ // starterScriptApache {
+ apacheStarterScriptContent = `#!/bin/bash
+export KAFKA_ADVERTISED_LISTENERS=%s,BROKER://%s:9092
+echo Starting Apache Kafka
+exec /etc/kafka/docker/run`
+ // }
)
// KafkaContainer represents the Kafka container type used in the module
@@ -78,7 +85,7 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom
// if the starter script fails to copy.
func(ctx context.Context, c testcontainers.Container) error {
// 1. copy the starter script into the container
- if err := copyStarterScript(ctx, c); err != nil {
+ if err := copyStarterScript(ctx, img, c); err != nil {
return fmt.Errorf("copy starter script: %w", err)
}
@@ -122,7 +129,7 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom
}
// copyStarterScript copies the starter script into the container.
-func copyStarterScript(ctx context.Context, c testcontainers.Container) error {
+func copyStarterScript(ctx context.Context, img string, c testcontainers.Container) error {
if err := wait.ForMappedPort(publicPort).
WaitUntilReady(ctx, c); err != nil {
return fmt.Errorf("wait for mapped port: %w", err)
@@ -140,7 +147,7 @@ func copyStarterScript(ctx context.Context, c testcontainers.Container) error {
hostname := inspect.Config.Hostname
- scriptContent := fmt.Sprintf(starterScriptContent, endpoint, hostname)
+ scriptContent := fmt.Sprintf(getStarterScriptContent(img), endpoint, hostname)
if err := c.CopyToContainer(ctx, []byte(scriptContent), starterScript, 0o755); err != nil {
return fmt.Errorf("copy to container: %w", err)
@@ -200,7 +207,7 @@ func validateKRaftVersion(fqName string) error {
image := fqName[:strings.LastIndex(fqName, ":")]
version := fqName[strings.LastIndex(fqName, ":")+1:]
- if !strings.EqualFold(image, "confluentinc/confluent-local") {
+ if !isConfluentinc(image) {
// do not validate if the image is not the official one.
// not raising an error here, letting the image start and
// eventually evaluate an error if it exists.
diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go
index af858f849f..dbd4d94511 100644
--- a/modules/kafka/kafka_test.go
+++ b/modules/kafka/kafka_test.go
@@ -12,12 +12,12 @@ import (
"github.com/testcontainers/testcontainers-go/modules/kafka"
)
-func TestKafka(t *testing.T) {
+func testFor(image string, t *testing.T) {
topic := "some-topic"
ctx := context.Background()
- kafkaContainer, err := kafka.Run(ctx, "confluentinc/confluent-local:7.5.0", kafka.WithClusterID("kraftCluster"))
+ kafkaContainer, err := kafka.Run(ctx, image, kafka.WithClusterID("kraftCluster"))
testcontainers.CleanupContainer(t, kafkaContainer)
require.NoError(t, err)
@@ -66,6 +66,32 @@ func TestKafka(t *testing.T) {
require.Truef(t, strings.EqualFold(string(consumer.message.Value), "value"), "expected value to be %s, got %s", "value", string(consumer.message.Value))
}
+func TestKafka(t *testing.T) {
+ testCases := []struct {
+ name string
+ image string
+ }{
+ {
+ name: "confluentinc",
+ image: "confluentinc/confluent-local:7.5.0",
+ },
+ {
+ name: "apache native",
+ image: "apache/kafka-native:4.0.1",
+ },
+ {
+ name: "apache not-native",
+ image: "apache/kafka:4.0.1",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ testFor(tc.image, t)
+ })
+ }
+}
+
func TestKafka_invalidVersion(t *testing.T) {
ctx := context.Background()
diff --git a/modules/kafka/version.go b/modules/kafka/version.go
new file mode 100644
index 0000000000..7f252995ee
--- /dev/null
+++ b/modules/kafka/version.go
@@ -0,0 +1,29 @@
+package kafka
+
+import "strings"
+
+const (
+ apacheKafkaImagePrefix = "apache/kafka"
+ confluentincImagePrefix = "confluentinc/"
+ dockerIoPrefix = "docker.io/"
+)
+
+func isApache(image string) bool {
+ return strings.HasPrefix(image, apacheKafkaImagePrefix) || strings.HasPrefix(image, dockerIoPrefix+apacheKafkaImagePrefix)
+}
+
+func isConfluentinc(image string) bool {
+ return strings.HasPrefix(image, confluentincImagePrefix) || strings.HasPrefix(image, dockerIoPrefix+confluentincImagePrefix)
+}
+
+func getStarterScriptContent(image string) string {
+ if isApache(image) {
+ return apacheStarterScriptContent
+ } else if isConfluentinc(image) {
+ return confluentincStarterScriptContent
+ } else {
+ // Default to confluentinc for backward compatibility
+ // in situations when image was custom specified based on confluentinc
+ return confluentincStarterScriptContent
+ }
+}
diff --git a/modules/kafka/version_test.go b/modules/kafka/version_test.go
new file mode 100644
index 0000000000..e4b3220b2e
--- /dev/null
+++ b/modules/kafka/version_test.go
@@ -0,0 +1,171 @@
+package kafka
+
+import "testing"
+
+func Test_isNative(t *testing.T) {
+ tests := []struct {
+ name string
+ image string
+ want bool
+ }{
+ {
+ name: "apache native image - no tag",
+ image: "apache/kafka-native",
+ want: true,
+ },
+ {
+ name: "apache native image - latest",
+ image: "apache/kafka-native:latest",
+ want: true,
+ },
+ {
+ name: "apache native image - specific version",
+ image: "apache/kafka-native:4.0.1",
+ want: true,
+ },
+ {
+ name: "apache native image - specific version with docker.io prefix",
+ image: "docker.io/apache/kafka-native:4.0.1",
+ want: true,
+ },
+ {
+ name: "apache not-native image - no tag",
+ image: "apache/kafka",
+ want: true,
+ },
+ {
+ name: "apache not-native image - latest",
+ image: "apache/kafka:latest",
+ want: true,
+ },
+ {
+ name: "apache not-native image - specific version",
+ image: "apache/kafka:4.0.1",
+ want: true,
+ },
+ {
+ name: "apache not-native image - specific version with docker.io prefix",
+ image: "docker.io/apache/kafka:4.0.1",
+ want: true,
+ },
+ {
+ name: "confluentinc image",
+ image: "confluentinc/cp-kafka:latest",
+ want: false,
+ },
+ {
+ name: "custom image",
+ image: "custom/kafka:latest",
+ want: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := isApache(tt.image); got != tt.want {
+ t.Errorf("isNative() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func Test_isConfluentinc(t *testing.T) {
+ tests := []struct {
+ name string
+ image string
+ want bool
+ }{
+ {
+ name: "confluentinc image - no tag",
+ image: "confluentinc/cp-kafka",
+ want: true,
+ },
+ {
+ name: "confluentinc image - latest",
+ image: "confluentinc/cp-kafka:latest",
+ want: true,
+ },
+ {
+ name: "confluentinc image - specific version",
+ image: "confluentinc/cp-kafka:8.1.0",
+ want: true,
+ },
+ {
+ name: "confluentinc image - specific version with docker.io prefix",
+ image: "docker.io/confluentinc/cp-kafka:8.1.0",
+ want: true,
+ },
+ {
+ name: "apache native image",
+ image: "apache/kafka-native:latest",
+ want: false,
+ },
+ {
+ name: "custom image",
+ image: "custom/kafka:latest",
+ want: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := isConfluentinc(tt.image); got != tt.want {
+ t.Errorf("isConfluentinc() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func Test_getStarterScriptContent(t *testing.T) {
+ tests := []struct {
+ name string
+ image string
+ want string
+ }{
+ {
+ name: "apache native image - latest",
+ image: "apache/kafka-native:latest",
+ want: apacheStarterScriptContent,
+ },
+ {
+ name: "apache native image - specific version",
+ image: "apache/kafka-native:4.0.1",
+ want: apacheStarterScriptContent,
+ },
+ {
+ name: "apache native image - specific version with docker.io prefix",
+ image: "docker.io/apache/kafka-native:4.0.1",
+ want: apacheStarterScriptContent,
+ },
+ {
+ name: "confluentinc image - latest",
+ image: "confluentinc/cp-kafka:latest",
+ want: confluentincStarterScriptContent,
+ },
+ {
+ name: "confluentinc image - no tag",
+ image: "confluentinc/cp-kafka",
+ want: confluentincStarterScriptContent,
+ },
+ {
+ name: "confluentinc image - specific version",
+ image: "confluentinc/cp-kafka:8.1.0",
+ want: confluentincStarterScriptContent,
+ },
+ {
+ name: "confluentinc image - specific version with docker.io prefix",
+ image: "docker.io/confluentinc/cp-kafka:8.1.0",
+ want: confluentincStarterScriptContent,
+ },
+ {
+ name: "custom image",
+ image: "custom/kafka:latest",
+ want: confluentincStarterScriptContent,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := getStarterScriptContent(tt.image); got != tt.want {
+ t.Errorf("getStarterScriptContent() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/modules/kafka_native/Makefile b/modules/kafka_native/Makefile
deleted file mode 100644
index cb486dc6a2..0000000000
--- a/modules/kafka_native/Makefile
+++ /dev/null
@@ -1,5 +0,0 @@
-include ../../commons-test.mk
-
-.PHONY: test
-test:
- $(MAKE) test-kafka-native
diff --git a/modules/kafka_native/consumer_test.go b/modules/kafka_native/consumer_test.go
deleted file mode 100644
index 3397ff8e75..0000000000
--- a/modules/kafka_native/consumer_test.go
+++ /dev/null
@@ -1,56 +0,0 @@
-package kafka_native_test
-
-import (
- "testing"
-
- "github.com/IBM/sarama"
-)
-
-// TestKafkaConsumer is a test consumer for Kafka
-type TestKafkaConsumer struct {
- t *testing.T
- ready chan bool
- done chan bool
- cancel chan bool
- message *sarama.ConsumerMessage
-}
-
-func NewTestKafkaConsumer(t *testing.T) (*TestKafkaConsumer, <-chan bool, <-chan bool, func()) {
- t.Helper()
- kc := &TestKafkaConsumer{
- t: t,
- ready: make(chan bool, 1),
- done: make(chan bool, 1),
- cancel: make(chan bool, 1),
- }
- return kc, kc.ready, kc.done, func() {
- kc.cancel <- true
- }
-}
-
-func (k *TestKafkaConsumer) Setup(_ sarama.ConsumerGroupSession) error {
- return nil
-}
-
-func (k *TestKafkaConsumer) Cleanup(_ sarama.ConsumerGroupSession) error {
- return nil
-}
-
-// ConsumeClaim is called by the Kafka client library when a message is received
-func (k *TestKafkaConsumer) ConsumeClaim(session sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error {
- k.ready <- true
- for {
- select {
- case message := <-claim.Messages():
- k.message = message
- session.MarkMessage(message, "")
- k.done <- true
-
- case <-k.cancel:
- return nil
-
- case <-session.Context().Done():
- return nil
- }
- }
-}
diff --git a/modules/kafka_native/examples_test.go b/modules/kafka_native/examples_test.go
deleted file mode 100644
index 15403c132b..0000000000
--- a/modules/kafka_native/examples_test.go
+++ /dev/null
@@ -1,43 +0,0 @@
-package kafka_native_test
-
-import (
- "context"
- "fmt"
- "log"
-
- "github.com/testcontainers/testcontainers-go"
- "github.com/testcontainers/testcontainers-go/modules/kafka_native"
-)
-
-func ExampleRun() {
- // runKafkaContainer {
- ctx := context.Background()
-
- kafkaContainer, err := kafka_native.Run(ctx,
- "apache/kafka-native:3.9.1",
- kafka_native.WithClusterID("test-cluster"),
- )
- defer func() {
- if err := testcontainers.TerminateContainer(kafkaContainer); err != nil {
- log.Printf("failed to terminate container: %s", err)
- }
- }()
- if err != nil {
- log.Printf("failed to start container: %s", err)
- return
- }
- // }
-
- state, err := kafkaContainer.State(ctx)
- if err != nil {
- log.Printf("failed to get container state: %s", err)
- return
- }
-
- fmt.Println(kafkaContainer.ClusterID)
- fmt.Println(state.Running)
-
- // Output:
- // test-cluster
- // true
-}
diff --git a/modules/kafka_native/go.mod b/modules/kafka_native/go.mod
deleted file mode 100644
index d2cc3ce24e..0000000000
--- a/modules/kafka_native/go.mod
+++ /dev/null
@@ -1,85 +0,0 @@
-module github.com/testcontainers/testcontainers-go/modules/kafka_native
-
-go 1.23.0
-
-toolchain go1.23.6
-
-require (
- github.com/IBM/sarama v1.42.1
- github.com/docker/go-connections v0.5.0
- github.com/stretchr/testify v1.10.0
- github.com/testcontainers/testcontainers-go v0.38.0
- golang.org/x/mod v0.16.0
-)
-
-require (
- dario.cat/mergo v1.0.1 // indirect
- github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
- github.com/Microsoft/go-winio v0.6.2 // indirect
- github.com/cenkalti/backoff/v4 v4.2.1 // indirect
- github.com/containerd/errdefs v1.0.0 // indirect
- github.com/containerd/errdefs/pkg v0.3.0 // indirect
- github.com/containerd/log v0.1.0 // indirect
- github.com/containerd/platforms v0.2.1 // indirect
- github.com/cpuguy83/dockercfg v0.3.2 // indirect
- github.com/davecgh/go-spew v1.1.1 // indirect
- github.com/distribution/reference v0.6.0 // indirect
- github.com/docker/docker v28.2.2+incompatible // indirect
- github.com/docker/go-units v0.5.0 // indirect
- github.com/eapache/go-resiliency v1.4.0 // indirect
- github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect
- github.com/eapache/queue v1.1.0 // indirect
- github.com/ebitengine/purego v0.8.4 // indirect
- github.com/felixge/httpsnoop v1.0.4 // indirect
- github.com/go-logr/logr v1.4.2 // indirect
- github.com/go-logr/stdr v1.2.2 // indirect
- github.com/go-ole/go-ole v1.2.6 // indirect
- github.com/gogo/protobuf v1.3.2 // indirect
- github.com/golang/snappy v0.0.4 // indirect
- github.com/google/uuid v1.6.0 // indirect
- github.com/hashicorp/errwrap v1.1.0 // indirect
- github.com/hashicorp/go-multierror v1.1.1 // indirect
- github.com/hashicorp/go-uuid v1.0.3 // indirect
- github.com/jcmturner/aescts/v2 v2.0.0 // indirect
- github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
- github.com/jcmturner/gofork v1.7.6 // indirect
- github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
- github.com/jcmturner/rpc/v2 v2.0.3 // indirect
- github.com/klauspost/compress v1.18.0 // indirect
- github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
- github.com/magiconair/properties v1.8.10 // indirect
- github.com/moby/docker-image-spec v1.3.1 // indirect
- github.com/moby/go-archive v0.1.0 // indirect
- github.com/moby/patternmatcher v0.6.0 // indirect
- github.com/moby/sys/sequential v0.6.0 // indirect
- github.com/moby/sys/user v0.4.0 // indirect
- github.com/moby/sys/userns v0.1.0 // indirect
- github.com/moby/term v0.5.0 // indirect
- github.com/morikuni/aec v1.0.0 // indirect
- github.com/opencontainers/go-digest v1.0.0 // indirect
- github.com/opencontainers/image-spec v1.1.1 // indirect
- github.com/pierrec/lz4/v4 v4.1.18 // indirect
- github.com/pkg/errors v0.9.1 // indirect
- github.com/pmezard/go-difflib v1.0.0 // indirect
- github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
- github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
- github.com/shirou/gopsutil/v4 v4.25.5 // indirect
- github.com/sirupsen/logrus v1.9.3 // indirect
- github.com/tklauser/go-sysconf v0.3.12 // indirect
- github.com/tklauser/numcpus v0.6.1 // indirect
- github.com/yusufpapurcu/wmi v1.2.4 // indirect
- go.opentelemetry.io/auto/sdk v1.1.0 // indirect
- go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
- go.opentelemetry.io/otel v1.35.0 // indirect
- go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect
- go.opentelemetry.io/otel/metric v1.35.0 // indirect
- go.opentelemetry.io/otel/trace v1.35.0 // indirect
- go.opentelemetry.io/proto/otlp v1.0.0 // indirect
- golang.org/x/crypto v0.37.0 // indirect
- golang.org/x/net v0.38.0 // indirect
- golang.org/x/sync v0.8.0 // indirect
- golang.org/x/sys v0.32.0 // indirect
- gopkg.in/yaml.v3 v3.0.1 // indirect
-)
-
-replace github.com/testcontainers/testcontainers-go => ../..
diff --git a/modules/kafka_native/go.sum b/modules/kafka_native/go.sum
deleted file mode 100644
index 695a2f434a..0000000000
--- a/modules/kafka_native/go.sum
+++ /dev/null
@@ -1,260 +0,0 @@
-dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
-dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
-github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
-github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
-github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
-github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
-github.com/IBM/sarama v1.42.1 h1:wugyWa15TDEHh2kvq2gAy1IHLjEjuYOYgXz/ruC/OSQ=
-github.com/IBM/sarama v1.42.1/go.mod h1:Xxho9HkHd4K/MDUo/T/sOqwtX/17D33++E9Wib6hUdQ=
-github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
-github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
-github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
-github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
-github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
-github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
-github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
-github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
-github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
-github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
-github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
-github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
-github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
-github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
-github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
-github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
-github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
-github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw=
-github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
-github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
-github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
-github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
-github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
-github.com/eapache/go-resiliency v1.4.0 h1:3OK9bWpPk5q6pbFAaYSEwD9CLUSHG8bnZuqX2yMt3B0=
-github.com/eapache/go-resiliency v1.4.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho=
-github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws=
-github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0=
-github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
-github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
-github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
-github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
-github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
-github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
-github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
-github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
-github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
-github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
-github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
-github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
-github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
-github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
-github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
-github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
-github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
-github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
-github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
-github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
-github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
-github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
-github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
-github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
-github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
-github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
-github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
-github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
-github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
-github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
-github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
-github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
-github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
-github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
-github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
-github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
-github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
-github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
-github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
-github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
-github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
-github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
-github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
-github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
-github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
-github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
-github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
-github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
-github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
-github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
-github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
-github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
-github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
-github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
-github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
-github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
-github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
-github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
-github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
-github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
-github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
-github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
-github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
-github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
-github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
-github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
-github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
-github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
-github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
-github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
-github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
-github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
-github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
-github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
-github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
-github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
-github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
-github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc=
-github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
-github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
-github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
-github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
-github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
-github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
-github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
-github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
-github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
-github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
-go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
-go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
-go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
-go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
-go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
-go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
-go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
-go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
-go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
-go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
-go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
-go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
-golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
-golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
-golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
-golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
-golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
-golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
-golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
-golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
-golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
-golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
-golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8=
-google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
-google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw=
-google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
-google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
-google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
-gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
diff --git a/modules/kafka_native/kafka.go b/modules/kafka_native/kafka.go
deleted file mode 100644
index 944bbe7ea5..0000000000
--- a/modules/kafka_native/kafka.go
+++ /dev/null
@@ -1,171 +0,0 @@
-package kafka_native
-
-import (
- "context"
- "fmt"
- "math"
- "strconv"
-
- "github.com/docker/go-connections/nat"
- "github.com/testcontainers/testcontainers-go"
- "github.com/testcontainers/testcontainers-go/wait"
-)
-
-const publicPort = nat.Port("9093/tcp")
-const (
- starterScript = "/usr/sbin/testcontainers_start.sh"
-
- // starterScriptContentText {
- starterScriptContent = `#!/bin/bash
-export KAFKA_ADVERTISED_LISTENERS=%s,BROKER://%s:9092
-echo Starting Kafka Native
-exec /etc/kafka/docker/run`
- // }
-)
-
-// KafkaContainer represents the Kafka container type used in the module
-type KafkaContainer struct {
- testcontainers.Container
- ClusterID string
-}
-
-// Run creates an instance of the Kafka container type
-func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*KafkaContainer, error) {
- req := testcontainers.ContainerRequest{
- Image: img,
- ExposedPorts: []string{string(publicPort)},
- Env: map[string]string{
- // envVars {
- "KAFKA_LISTENERS": "PLAINTEXT://0.0.0.0:9093,BROKER://0.0.0.0:9092,CONTROLLER://0.0.0.0:9094",
- "KAFKA_REST_BOOTSTRAP_SERVERS": "PLAINTEXT://0.0.0.0:9093,BROKER://0.0.0.0:9092,CONTROLLER://0.0.0.0:9094",
- "KAFKA_LISTENER_SECURITY_PROTOCOL_MAP": "BROKER:PLAINTEXT,PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT",
- "KAFKA_INTER_BROKER_LISTENER_NAME": "BROKER",
- "KAFKA_BROKER_ID": "1",
- "KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR": "1",
- "KAFKA_OFFSETS_TOPIC_NUM_PARTITIONS": "1",
- "KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR": "1",
- "KAFKA_TRANSACTION_STATE_LOG_MIN_ISR": "1",
- "KAFKA_LOG_FLUSH_INTERVAL_MESSAGES": strconv.Itoa(math.MaxInt),
- "KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS": "0",
- "KAFKA_NODE_ID": "1",
- "KAFKA_PROCESS_ROLES": "broker,controller",
- "KAFKA_CONTROLLER_LISTENER_NAMES": "CONTROLLER",
- // }
- },
- Entrypoint: []string{"sh"},
- // this CMD will wait for the starter script to be copied into the container and then execute it
- Cmd: []string{"-c", "while [ ! -f " + starterScript + " ]; do sleep 0.1; done; bash " + starterScript},
- LifecycleHooks: []testcontainers.ContainerLifecycleHooks{
- {
- PostStarts: []testcontainers.ContainerHook{
- // Use a single hook to copy the starter script and wait for
- // the Kafka server to be ready. This prevents the wait running
- // if the starter script fails to copy.
- func(ctx context.Context, c testcontainers.Container) error {
- // 1. copy the starter script into the container
- if err := copyStarterScript(ctx, c); err != nil {
- return fmt.Errorf("copy starter script: %w", err)
- }
-
- // 2. wait for the Kafka server to be ready
- return wait.ForLog(".*Transitioning from RECOVERY to RUNNING.*").AsRegexp().WaitUntilReady(ctx, c)
- },
- },
- },
- },
- }
-
- genericContainerReq := testcontainers.GenericContainerRequest{
- ContainerRequest: req,
- Started: true,
- }
-
- for _, opt := range opts {
- if err := opt.Customize(&genericContainerReq); err != nil {
- return nil, err
- }
- }
-
- configureControllerQuorumVoters(&genericContainerReq)
-
- container, err := testcontainers.GenericContainer(ctx, genericContainerReq)
- var c *KafkaContainer
- if container != nil {
- c = &KafkaContainer{Container: container, ClusterID: genericContainerReq.Env["CLUSTER_ID"]}
- }
-
- if err != nil {
- return c, fmt.Errorf("generic container: %w", err)
- }
-
- return c, nil
-}
-
-// copyStarterScript copies the starter script into the container.
-func copyStarterScript(ctx context.Context, c testcontainers.Container) error {
- if err := wait.ForMappedPort(publicPort).
- WaitUntilReady(ctx, c); err != nil {
- return fmt.Errorf("wait for mapped port: %w", err)
- }
-
- endpoint, err := c.PortEndpoint(ctx, publicPort, "PLAINTEXT")
- if err != nil {
- return fmt.Errorf("port endpoint: %w", err)
- }
-
- inspect, err := c.Inspect(ctx)
- if err != nil {
- return fmt.Errorf("inspect: %w", err)
- }
-
- hostname := inspect.Config.Hostname
-
- scriptContent := fmt.Sprintf(starterScriptContent, endpoint, hostname)
-
- if err := c.CopyToContainer(ctx, []byte(scriptContent), starterScript, 0o755); err != nil {
- return fmt.Errorf("copy to container: %w", err)
- }
-
- return nil
-}
-
-func WithClusterID(clusterID string) testcontainers.CustomizeRequestOption {
- return func(req *testcontainers.GenericContainerRequest) error {
- req.Env["CLUSTER_ID"] = clusterID
-
- return nil
- }
-}
-
-// Brokers retrieves the broker connection strings from Kafka with only one entry,
-// defined by the exposed public port.
-func (kc *KafkaContainer) Brokers(ctx context.Context) ([]string, error) {
- endpoint, err := kc.PortEndpoint(ctx, publicPort, "")
- if err != nil {
- return nil, err
- }
-
- return []string{endpoint}, nil
-}
-
-// configureControllerQuorumVoters sets the quorum voters for the controller. For that, it will
-// check if there are any network aliases defined for the container and use the first alias in the
-// first network. Else, it will use localhost.
-func configureControllerQuorumVoters(req *testcontainers.GenericContainerRequest) {
- if req.Env == nil {
- req.Env = map[string]string{}
- }
-
- if req.Env["KAFKA_CONTROLLER_QUORUM_VOTERS"] == "" {
- host := "localhost"
- if len(req.Networks) > 0 {
- nw := req.Networks[0]
- if len(req.NetworkAliases[nw]) > 0 {
- host = req.NetworkAliases[nw][0]
- }
- }
-
- req.Env["KAFKA_CONTROLLER_QUORUM_VOTERS"] = fmt.Sprintf("1@%s:9094", host)
- }
- // }
-}
diff --git a/modules/kafka_native/kafka_helpers_test.go b/modules/kafka_native/kafka_helpers_test.go
deleted file mode 100644
index c9227fcc40..0000000000
--- a/modules/kafka_native/kafka_helpers_test.go
+++ /dev/null
@@ -1,63 +0,0 @@
-package kafka_native
-
-import (
- "testing"
-
- "github.com/stretchr/testify/require"
-
- "github.com/testcontainers/testcontainers-go"
-)
-
-func TestConfigureQuorumVoters(t *testing.T) {
- tests := []struct {
- name string
- req *testcontainers.GenericContainerRequest
- expectedVoters string
- }{
- {
- name: "voters on localhost",
- req: &testcontainers.GenericContainerRequest{
- ContainerRequest: testcontainers.ContainerRequest{
- Env: map[string]string{},
- },
- },
- expectedVoters: "1@localhost:9094",
- },
- {
- name: "voters on first network alias of the first network",
- req: &testcontainers.GenericContainerRequest{
- ContainerRequest: testcontainers.ContainerRequest{
- Env: map[string]string{},
- Networks: []string{"foo", "bar", "baaz"},
- NetworkAliases: map[string][]string{
- "foo": {"foo0", "foo1", "foo2", "foo3"},
- "bar": {"bar0", "bar1", "bar2", "bar3"},
- "baaz": {"baaz0", "baaz1", "baaz2", "baaz3"},
- },
- },
- },
- expectedVoters: "1@foo0:9094",
- },
- {
- name: "voters on localhost if alias but no networks",
- req: &testcontainers.GenericContainerRequest{
- ContainerRequest: testcontainers.ContainerRequest{
- NetworkAliases: map[string][]string{
- "foo": {"foo0", "foo1", "foo2", "foo3"},
- "bar": {"bar0", "bar1", "bar2", "bar3"},
- "baaz": {"baaz0", "baaz1", "baaz2", "baaz3"},
- },
- },
- },
- expectedVoters: "1@localhost:9094",
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- configureControllerQuorumVoters(test.req)
-
- require.Equalf(t, test.expectedVoters, test.req.Env["KAFKA_CONTROLLER_QUORUM_VOTERS"], "expected KAFKA_CONTROLLER_QUORUM_VOTERS to be %s, got %s", test.expectedVoters, test.req.Env["KAFKA_CONTROLLER_QUORUM_VOTERS"])
- })
- }
-}
diff --git a/modules/kafka_native/kafka_test.go b/modules/kafka_native/kafka_test.go
deleted file mode 100644
index 1a15393b6c..0000000000
--- a/modules/kafka_native/kafka_test.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package kafka_native_test
-
-import (
- "context"
- "strings"
- "testing"
-
- "github.com/IBM/sarama"
- "github.com/stretchr/testify/require"
-
- "github.com/testcontainers/testcontainers-go"
- "github.com/testcontainers/testcontainers-go/modules/kafka_native"
-)
-
-func TestKafka(t *testing.T) {
- topic := "some-topic"
- ctx := context.Background()
-
- kafkaContainer, err := kafka_native.Run(ctx, "apache/kafka-native:3.9.1", kafka_native.WithClusterID("kraftCluster"))
- testcontainers.CleanupContainer(t, kafkaContainer)
- require.NoError(t, err)
-
- assertAdvertisedListeners(t, kafkaContainer)
-
- require.Truef(t, strings.EqualFold(kafkaContainer.ClusterID, "kraftCluster"), "expected clusterID to be %s, got %s", "kraftCluster", kafkaContainer.ClusterID)
-
- // getBrokers {
- brokers, err := kafkaContainer.Brokers(ctx)
- // }
- require.NoError(t, err)
-
- config := sarama.NewConfig()
- client, err := sarama.NewConsumerGroup(brokers, "groupName", config)
- require.NoError(t, err)
-
- consumer, ready, done, cancel := NewTestKafkaConsumer(t)
- defer cancel()
- go func() {
- if err := client.Consume(context.Background(), []string{topic}, consumer); err != nil {
- cancel()
- }
- }()
-
- // wait for the consumer to be ready
- <-ready
-
- // perform assertions
-
- // set config to true because successfully delivered messages will be returned on the Successes channel
- config.Producer.Return.Successes = true
-
- producer, err := sarama.NewSyncProducer(brokers, config)
- require.NoError(t, err)
-
- _, _, err = producer.SendMessage(&sarama.ProducerMessage{
- Topic: topic,
- Key: sarama.StringEncoder("key"),
- Value: sarama.StringEncoder("value"),
- })
- require.NoError(t, err)
-
- <-done
-
- require.Truef(t, strings.EqualFold(string(consumer.message.Key), "key"), "expected key to be %s, got %s", "key", string(consumer.message.Key))
- require.Truef(t, strings.EqualFold(string(consumer.message.Value), "value"), "expected value to be %s, got %s", "value", string(consumer.message.Value))
-}
-
-// assertAdvertisedListeners checks that the advertised listeners are set correctly:
-// - The BROKER:// protocol is using the hostname of the Kafka container
-func assertAdvertisedListeners(t *testing.T, container testcontainers.Container) {
- t.Helper()
- inspect, err := container.Inspect(context.Background())
- require.NoError(t, err)
-
- brokerURL := "BROKER://" + inspect.Config.Hostname + ":9092"
-
- ctx := context.Background()
-
- bs := testcontainers.RequireContainerExec(ctx, t, container, []string{"cat", "/usr/sbin/testcontainers_start.sh"})
-
- require.Containsf(t, bs, brokerURL, "expected advertised listeners to contain %s, got %s", brokerURL, bs)
-}
From 0e2dfab763edd55528081b0a94cd51dd1a3ddcb2 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 8 Nov 2025 22:44:33 +0100
Subject: [PATCH 04/54] chore: correct unit test naming
---
modules/kafka/version_test.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/modules/kafka/version_test.go b/modules/kafka/version_test.go
index e4b3220b2e..569a221507 100644
--- a/modules/kafka/version_test.go
+++ b/modules/kafka/version_test.go
@@ -2,7 +2,7 @@ package kafka
import "testing"
-func Test_isNative(t *testing.T) {
+func Test_isApache(t *testing.T) {
tests := []struct {
name string
image string
@@ -62,7 +62,7 @@ func Test_isNative(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isApache(tt.image); got != tt.want {
- t.Errorf("isNative() = %v, want %v", got, tt.want)
+ t.Errorf("isApache() = %v, want %v", got, tt.want)
}
})
}
From 57f0cd961203626259c5a52498335610bee633e0 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 8 Nov 2025 22:50:06 +0100
Subject: [PATCH 05/54] chore: remove kafka_native doc
Module is removed in favor of including
relevant functionality in existing module
---
docs/modules/kafka_native.md | 76 ------------------------------------
1 file changed, 76 deletions(-)
delete mode 100644 docs/modules/kafka_native.md
diff --git a/docs/modules/kafka_native.md b/docs/modules/kafka_native.md
deleted file mode 100644
index 6188adcdeb..0000000000
--- a/docs/modules/kafka_native.md
+++ /dev/null
@@ -1,76 +0,0 @@
-# Kafka Native
-
-Since :material-tag: v0.39.0
-
-## Introduction
-
-The Testcontainers module for [Apache Kafka Native](https://hub.docker.com/r/apache/kafka-native).
-
-## Adding this module to your project dependencies
-
-Please run the following command to add the Kafka module to your Go dependencies:
-
-```
-go get github.com/testcontainers/testcontainers-go/modules/kafka_native
-```
-
-## Usage example
-
-
-[Creating a Kafka container](../../modules/kafka_native/examples_test.go) inside_block:runKafkaContainer
-
-
-## Module Reference
-
-### Run function
-
-The Kafka module exposes one entrypoint function to create the Kafka container, and this function receives three parameters:
-
-```golang
-func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*KafkaContainer, error)
-```
-
-- `context.Context`, the Go context.
-- `string`, the Docker image to use.
-- `testcontainers.ContainerCustomizer`, a variadic argument for passing options.
-
-#### Image
-
-Use the second argument in the `Run` function to set a valid Docker image.
-In example: `Run(context.Background(), "apache/kafka-native:3.9.1")`.
-
-#### Environment variables
-
-The environment variables that are already set by default are:
-
-
-[Environment variables](../../modules/kafka_native/kafka.go) inside_block:envVars
-
-
-And also KAFKA_ADVERTISED_LISTENERS that is defined dynamically based on the container's hostname.
-
-#### Init script
-
-The Kafka container will be started using a custom shell script:
-
-
-[Init script](../../modules/kafka_native/kafka.go) inside_block:starterScript
-
-
-### Container Options
-
-When starting the Kafka container, you can pass options in a variadic way to configure it.
-
-{% include "../features/common_functional_options_list.md" %}
-
-### Container Methods
-
-The Kafka container exposes the following methods:
-
-#### Brokers
-
-The `Brokers(ctx)` method returns the Kafka brokers as a string slice, containing the host and the random port defined by Kafka's public port (`9093/tcp`).
-
-
-[Get Kafka brokers](../../modules/kafka_native/kafka_test.go) inside_block:getBrokers
-
From cb668834367c895f7683c9498eeafbb809b313fe Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 8 Nov 2025 22:53:11 +0100
Subject: [PATCH 06/54] chore: small doc correction
---
docs/modules/kafka.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md
index e1e9b6f04f..fda0796019 100644
--- a/docs/modules/kafka.md
+++ b/docs/modules/kafka.md
@@ -19,7 +19,7 @@ go get github.com/testcontainers/testcontainers-go/modules/kafka
## Usage example
-[Apache Native Kafka](../../modules/kafka/examples_test.go) inside_block:runKafkaContainerApacheNative
+[Apache Kafka Native](../../modules/kafka/examples_test.go) inside_block:runKafkaContainerApacheNative
From 246fdae37a09b1195637059385ef8a4d4ec19475 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 8 Nov 2025 22:54:27 +0100
Subject: [PATCH 07/54] chore: remove all kafka native folder
---
modules/kafka_native/Makefile | 5 -
modules/kafka_native/consumer_test.go | 56 -----
modules/kafka_native/examples_test.go | 43 ----
modules/kafka_native/go.mod | 85 -------
modules/kafka_native/go.sum | 260 ---------------------
modules/kafka_native/kafka.go | 172 --------------
modules/kafka_native/kafka_helpers_test.go | 63 -----
modules/kafka_native/kafka_test.go | 82 -------
8 files changed, 766 deletions(-)
delete mode 100644 modules/kafka_native/Makefile
delete mode 100644 modules/kafka_native/consumer_test.go
delete mode 100644 modules/kafka_native/examples_test.go
delete mode 100644 modules/kafka_native/go.mod
delete mode 100644 modules/kafka_native/go.sum
delete mode 100644 modules/kafka_native/kafka.go
delete mode 100644 modules/kafka_native/kafka_helpers_test.go
delete mode 100644 modules/kafka_native/kafka_test.go
diff --git a/modules/kafka_native/Makefile b/modules/kafka_native/Makefile
deleted file mode 100644
index cb486dc6a2..0000000000
--- a/modules/kafka_native/Makefile
+++ /dev/null
@@ -1,5 +0,0 @@
-include ../../commons-test.mk
-
-.PHONY: test
-test:
- $(MAKE) test-kafka-native
diff --git a/modules/kafka_native/consumer_test.go b/modules/kafka_native/consumer_test.go
deleted file mode 100644
index 3397ff8e75..0000000000
--- a/modules/kafka_native/consumer_test.go
+++ /dev/null
@@ -1,56 +0,0 @@
-package kafka_native_test
-
-import (
- "testing"
-
- "github.com/IBM/sarama"
-)
-
-// TestKafkaConsumer is a test consumer for Kafka
-type TestKafkaConsumer struct {
- t *testing.T
- ready chan bool
- done chan bool
- cancel chan bool
- message *sarama.ConsumerMessage
-}
-
-func NewTestKafkaConsumer(t *testing.T) (*TestKafkaConsumer, <-chan bool, <-chan bool, func()) {
- t.Helper()
- kc := &TestKafkaConsumer{
- t: t,
- ready: make(chan bool, 1),
- done: make(chan bool, 1),
- cancel: make(chan bool, 1),
- }
- return kc, kc.ready, kc.done, func() {
- kc.cancel <- true
- }
-}
-
-func (k *TestKafkaConsumer) Setup(_ sarama.ConsumerGroupSession) error {
- return nil
-}
-
-func (k *TestKafkaConsumer) Cleanup(_ sarama.ConsumerGroupSession) error {
- return nil
-}
-
-// ConsumeClaim is called by the Kafka client library when a message is received
-func (k *TestKafkaConsumer) ConsumeClaim(session sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error {
- k.ready <- true
- for {
- select {
- case message := <-claim.Messages():
- k.message = message
- session.MarkMessage(message, "")
- k.done <- true
-
- case <-k.cancel:
- return nil
-
- case <-session.Context().Done():
- return nil
- }
- }
-}
diff --git a/modules/kafka_native/examples_test.go b/modules/kafka_native/examples_test.go
deleted file mode 100644
index fd7b862c37..0000000000
--- a/modules/kafka_native/examples_test.go
+++ /dev/null
@@ -1,43 +0,0 @@
-package kafka_native_test
-
-import (
- "context"
- "fmt"
- "log"
-
- "github.com/testcontainers/testcontainers-go"
- kafka "github.com/testcontainers/testcontainers-go/modules/kafka_native"
-)
-
-func ExampleRun() {
- // runKafkaContainer {
- ctx := context.Background()
-
- kafkaContainer, err := kafka.Run(ctx,
- "apache/kafka-native:3.9.1",
- kafka.WithClusterID("test-cluster"),
- )
- defer func() {
- if err := testcontainers.TerminateContainer(kafkaContainer); err != nil {
- log.Printf("failed to terminate container: %s", err)
- }
- }()
- if err != nil {
- log.Printf("failed to start container: %s", err)
- return
- }
- // }
-
- state, err := kafkaContainer.State(ctx)
- if err != nil {
- log.Printf("failed to get container state: %s", err)
- return
- }
-
- fmt.Println(kafkaContainer.ClusterID)
- fmt.Println(state.Running)
-
- // Output:
- // test-cluster
- // true
-}
diff --git a/modules/kafka_native/go.mod b/modules/kafka_native/go.mod
deleted file mode 100644
index d2cc3ce24e..0000000000
--- a/modules/kafka_native/go.mod
+++ /dev/null
@@ -1,85 +0,0 @@
-module github.com/testcontainers/testcontainers-go/modules/kafka_native
-
-go 1.23.0
-
-toolchain go1.23.6
-
-require (
- github.com/IBM/sarama v1.42.1
- github.com/docker/go-connections v0.5.0
- github.com/stretchr/testify v1.10.0
- github.com/testcontainers/testcontainers-go v0.38.0
- golang.org/x/mod v0.16.0
-)
-
-require (
- dario.cat/mergo v1.0.1 // indirect
- github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
- github.com/Microsoft/go-winio v0.6.2 // indirect
- github.com/cenkalti/backoff/v4 v4.2.1 // indirect
- github.com/containerd/errdefs v1.0.0 // indirect
- github.com/containerd/errdefs/pkg v0.3.0 // indirect
- github.com/containerd/log v0.1.0 // indirect
- github.com/containerd/platforms v0.2.1 // indirect
- github.com/cpuguy83/dockercfg v0.3.2 // indirect
- github.com/davecgh/go-spew v1.1.1 // indirect
- github.com/distribution/reference v0.6.0 // indirect
- github.com/docker/docker v28.2.2+incompatible // indirect
- github.com/docker/go-units v0.5.0 // indirect
- github.com/eapache/go-resiliency v1.4.0 // indirect
- github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect
- github.com/eapache/queue v1.1.0 // indirect
- github.com/ebitengine/purego v0.8.4 // indirect
- github.com/felixge/httpsnoop v1.0.4 // indirect
- github.com/go-logr/logr v1.4.2 // indirect
- github.com/go-logr/stdr v1.2.2 // indirect
- github.com/go-ole/go-ole v1.2.6 // indirect
- github.com/gogo/protobuf v1.3.2 // indirect
- github.com/golang/snappy v0.0.4 // indirect
- github.com/google/uuid v1.6.0 // indirect
- github.com/hashicorp/errwrap v1.1.0 // indirect
- github.com/hashicorp/go-multierror v1.1.1 // indirect
- github.com/hashicorp/go-uuid v1.0.3 // indirect
- github.com/jcmturner/aescts/v2 v2.0.0 // indirect
- github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
- github.com/jcmturner/gofork v1.7.6 // indirect
- github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
- github.com/jcmturner/rpc/v2 v2.0.3 // indirect
- github.com/klauspost/compress v1.18.0 // indirect
- github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
- github.com/magiconair/properties v1.8.10 // indirect
- github.com/moby/docker-image-spec v1.3.1 // indirect
- github.com/moby/go-archive v0.1.0 // indirect
- github.com/moby/patternmatcher v0.6.0 // indirect
- github.com/moby/sys/sequential v0.6.0 // indirect
- github.com/moby/sys/user v0.4.0 // indirect
- github.com/moby/sys/userns v0.1.0 // indirect
- github.com/moby/term v0.5.0 // indirect
- github.com/morikuni/aec v1.0.0 // indirect
- github.com/opencontainers/go-digest v1.0.0 // indirect
- github.com/opencontainers/image-spec v1.1.1 // indirect
- github.com/pierrec/lz4/v4 v4.1.18 // indirect
- github.com/pkg/errors v0.9.1 // indirect
- github.com/pmezard/go-difflib v1.0.0 // indirect
- github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
- github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
- github.com/shirou/gopsutil/v4 v4.25.5 // indirect
- github.com/sirupsen/logrus v1.9.3 // indirect
- github.com/tklauser/go-sysconf v0.3.12 // indirect
- github.com/tklauser/numcpus v0.6.1 // indirect
- github.com/yusufpapurcu/wmi v1.2.4 // indirect
- go.opentelemetry.io/auto/sdk v1.1.0 // indirect
- go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
- go.opentelemetry.io/otel v1.35.0 // indirect
- go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect
- go.opentelemetry.io/otel/metric v1.35.0 // indirect
- go.opentelemetry.io/otel/trace v1.35.0 // indirect
- go.opentelemetry.io/proto/otlp v1.0.0 // indirect
- golang.org/x/crypto v0.37.0 // indirect
- golang.org/x/net v0.38.0 // indirect
- golang.org/x/sync v0.8.0 // indirect
- golang.org/x/sys v0.32.0 // indirect
- gopkg.in/yaml.v3 v3.0.1 // indirect
-)
-
-replace github.com/testcontainers/testcontainers-go => ../..
diff --git a/modules/kafka_native/go.sum b/modules/kafka_native/go.sum
deleted file mode 100644
index 695a2f434a..0000000000
--- a/modules/kafka_native/go.sum
+++ /dev/null
@@ -1,260 +0,0 @@
-dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
-dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
-github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
-github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
-github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
-github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
-github.com/IBM/sarama v1.42.1 h1:wugyWa15TDEHh2kvq2gAy1IHLjEjuYOYgXz/ruC/OSQ=
-github.com/IBM/sarama v1.42.1/go.mod h1:Xxho9HkHd4K/MDUo/T/sOqwtX/17D33++E9Wib6hUdQ=
-github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
-github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
-github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
-github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
-github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
-github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
-github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
-github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
-github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
-github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
-github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
-github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
-github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
-github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
-github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
-github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
-github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
-github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw=
-github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
-github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
-github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
-github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
-github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
-github.com/eapache/go-resiliency v1.4.0 h1:3OK9bWpPk5q6pbFAaYSEwD9CLUSHG8bnZuqX2yMt3B0=
-github.com/eapache/go-resiliency v1.4.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho=
-github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws=
-github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0=
-github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
-github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
-github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
-github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
-github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
-github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
-github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
-github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
-github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
-github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
-github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
-github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
-github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
-github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
-github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
-github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
-github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
-github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
-github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
-github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
-github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
-github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
-github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
-github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
-github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
-github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
-github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
-github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
-github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
-github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
-github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
-github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
-github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
-github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
-github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
-github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
-github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
-github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
-github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
-github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
-github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
-github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
-github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
-github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
-github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
-github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
-github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
-github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
-github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
-github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
-github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
-github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
-github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
-github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
-github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
-github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
-github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
-github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
-github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
-github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
-github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
-github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
-github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
-github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
-github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
-github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
-github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
-github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
-github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
-github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
-github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
-github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
-github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
-github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
-github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
-github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
-github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
-github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc=
-github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
-github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
-github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
-github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
-github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
-github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
-github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
-github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
-github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
-github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
-go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
-go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
-go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
-go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
-go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
-go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
-go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
-go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
-go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
-go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
-go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
-go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
-golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
-golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
-golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
-golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
-golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
-golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
-golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
-golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
-golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
-golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
-golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8=
-google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
-google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw=
-google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
-google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
-google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
-gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
diff --git a/modules/kafka_native/kafka.go b/modules/kafka_native/kafka.go
deleted file mode 100644
index 8f680f8150..0000000000
--- a/modules/kafka_native/kafka.go
+++ /dev/null
@@ -1,172 +0,0 @@
-package kafka_native
-
-import (
- "context"
- "fmt"
- "math"
- "strconv"
-
- "github.com/docker/go-connections/nat"
- "github.com/testcontainers/testcontainers-go"
- "github.com/testcontainers/testcontainers-go/wait"
-)
-
-const publicPort = nat.Port("9093/tcp")
-const (
- starterScript = "/usr/sbin/testcontainers_start.sh"
-
- // starterScript {
- starterScriptContent = `#!/bin/bash
-export KAFKA_ADVERTISED_LISTENERS=%s,BROKER://%s:9092
-echo Starting Kafka Native
-exec /etc/kafka/docker/run
-`
- // }
-)
-
-// KafkaContainer represents the Kafka container type used in the module
-type KafkaContainer struct {
- testcontainers.Container
- ClusterID string
-}
-
-// Run creates an instance of the Kafka container type
-func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*KafkaContainer, error) {
- req := testcontainers.ContainerRequest{
- Image: img,
- ExposedPorts: []string{string(publicPort)},
- Env: map[string]string{
- // envVars {
- "KAFKA_LISTENERS": "PLAINTEXT://0.0.0.0:9093,BROKER://0.0.0.0:9092,CONTROLLER://0.0.0.0:9094",
- "KAFKA_REST_BOOTSTRAP_SERVERS": "PLAINTEXT://0.0.0.0:9093,BROKER://0.0.0.0:9092,CONTROLLER://0.0.0.0:9094",
- "KAFKA_LISTENER_SECURITY_PROTOCOL_MAP": "BROKER:PLAINTEXT,PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT",
- "KAFKA_INTER_BROKER_LISTENER_NAME": "BROKER",
- "KAFKA_BROKER_ID": "1",
- "KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR": "1",
- "KAFKA_OFFSETS_TOPIC_NUM_PARTITIONS": "1",
- "KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR": "1",
- "KAFKA_TRANSACTION_STATE_LOG_MIN_ISR": "1",
- "KAFKA_LOG_FLUSH_INTERVAL_MESSAGES": strconv.Itoa(math.MaxInt),
- "KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS": "0",
- "KAFKA_NODE_ID": "1",
- "KAFKA_PROCESS_ROLES": "broker,controller",
- "KAFKA_CONTROLLER_LISTENER_NAMES": "CONTROLLER",
- // }
- },
- Entrypoint: []string{"sh"},
- // this CMD will wait for the starter script to be copied into the container and then execute it
- Cmd: []string{"-c", "while [ ! -f " + starterScript + " ]; do sleep 0.1; done; bash " + starterScript},
- LifecycleHooks: []testcontainers.ContainerLifecycleHooks{
- {
- PostStarts: []testcontainers.ContainerHook{
- // Use a single hook to copy the starter script and wait for
- // the Kafka server to be ready. This prevents the wait running
- // if the starter script fails to copy.
- func(ctx context.Context, c testcontainers.Container) error {
- // 1. copy the starter script into the container
- if err := copyStarterScript(ctx, c); err != nil {
- return fmt.Errorf("copy starter script: %w", err)
- }
-
- // 2. wait for the Kafka server to be ready
- return wait.ForLog(".*Transitioning from RECOVERY to RUNNING.*").AsRegexp().WaitUntilReady(ctx, c)
- },
- },
- },
- },
- }
-
- genericContainerReq := testcontainers.GenericContainerRequest{
- ContainerRequest: req,
- Started: true,
- }
-
- for _, opt := range opts {
- if err := opt.Customize(&genericContainerReq); err != nil {
- return nil, err
- }
- }
-
- configureControllerQuorumVoters(&genericContainerReq)
-
- container, err := testcontainers.GenericContainer(ctx, genericContainerReq)
- var c *KafkaContainer
- if container != nil {
- c = &KafkaContainer{Container: container, ClusterID: genericContainerReq.Env["CLUSTER_ID"]}
- }
-
- if err != nil {
- return c, fmt.Errorf("generic container: %w", err)
- }
-
- return c, nil
-}
-
-// copyStarterScript copies the starter script into the container.
-func copyStarterScript(ctx context.Context, c testcontainers.Container) error {
- if err := wait.ForMappedPort(publicPort).
- WaitUntilReady(ctx, c); err != nil {
- return fmt.Errorf("wait for mapped port: %w", err)
- }
-
- endpoint, err := c.PortEndpoint(ctx, publicPort, "PLAINTEXT")
- if err != nil {
- return fmt.Errorf("port endpoint: %w", err)
- }
-
- inspect, err := c.Inspect(ctx)
- if err != nil {
- return fmt.Errorf("inspect: %w", err)
- }
-
- hostname := inspect.Config.Hostname
-
- scriptContent := fmt.Sprintf(starterScriptContent, endpoint, hostname)
-
- if err := c.CopyToContainer(ctx, []byte(scriptContent), starterScript, 0o755); err != nil {
- return fmt.Errorf("copy to container: %w", err)
- }
-
- return nil
-}
-
-func WithClusterID(clusterID string) testcontainers.CustomizeRequestOption {
- return func(req *testcontainers.GenericContainerRequest) error {
- req.Env["CLUSTER_ID"] = clusterID
-
- return nil
- }
-}
-
-// Brokers retrieves the broker connection strings from Kafka with only one entry,
-// defined by the exposed public port.
-func (kc *KafkaContainer) Brokers(ctx context.Context) ([]string, error) {
- endpoint, err := kc.PortEndpoint(ctx, publicPort, "")
- if err != nil {
- return nil, err
- }
-
- return []string{endpoint}, nil
-}
-
-// configureControllerQuorumVoters sets the quorum voters for the controller. For that, it will
-// check if there are any network aliases defined for the container and use the first alias in the
-// first network. Else, it will use localhost.
-func configureControllerQuorumVoters(req *testcontainers.GenericContainerRequest) {
- if req.Env == nil {
- req.Env = map[string]string{}
- }
-
- if req.Env["KAFKA_CONTROLLER_QUORUM_VOTERS"] == "" {
- host := "localhost"
- if len(req.Networks) > 0 {
- nw := req.Networks[0]
- if len(req.NetworkAliases[nw]) > 0 {
- host = req.NetworkAliases[nw][0]
- }
- }
-
- req.Env["KAFKA_CONTROLLER_QUORUM_VOTERS"] = fmt.Sprintf("1@%s:9094", host)
- }
- // }
-}
diff --git a/modules/kafka_native/kafka_helpers_test.go b/modules/kafka_native/kafka_helpers_test.go
deleted file mode 100644
index c9227fcc40..0000000000
--- a/modules/kafka_native/kafka_helpers_test.go
+++ /dev/null
@@ -1,63 +0,0 @@
-package kafka_native
-
-import (
- "testing"
-
- "github.com/stretchr/testify/require"
-
- "github.com/testcontainers/testcontainers-go"
-)
-
-func TestConfigureQuorumVoters(t *testing.T) {
- tests := []struct {
- name string
- req *testcontainers.GenericContainerRequest
- expectedVoters string
- }{
- {
- name: "voters on localhost",
- req: &testcontainers.GenericContainerRequest{
- ContainerRequest: testcontainers.ContainerRequest{
- Env: map[string]string{},
- },
- },
- expectedVoters: "1@localhost:9094",
- },
- {
- name: "voters on first network alias of the first network",
- req: &testcontainers.GenericContainerRequest{
- ContainerRequest: testcontainers.ContainerRequest{
- Env: map[string]string{},
- Networks: []string{"foo", "bar", "baaz"},
- NetworkAliases: map[string][]string{
- "foo": {"foo0", "foo1", "foo2", "foo3"},
- "bar": {"bar0", "bar1", "bar2", "bar3"},
- "baaz": {"baaz0", "baaz1", "baaz2", "baaz3"},
- },
- },
- },
- expectedVoters: "1@foo0:9094",
- },
- {
- name: "voters on localhost if alias but no networks",
- req: &testcontainers.GenericContainerRequest{
- ContainerRequest: testcontainers.ContainerRequest{
- NetworkAliases: map[string][]string{
- "foo": {"foo0", "foo1", "foo2", "foo3"},
- "bar": {"bar0", "bar1", "bar2", "bar3"},
- "baaz": {"baaz0", "baaz1", "baaz2", "baaz3"},
- },
- },
- },
- expectedVoters: "1@localhost:9094",
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- configureControllerQuorumVoters(test.req)
-
- require.Equalf(t, test.expectedVoters, test.req.Env["KAFKA_CONTROLLER_QUORUM_VOTERS"], "expected KAFKA_CONTROLLER_QUORUM_VOTERS to be %s, got %s", test.expectedVoters, test.req.Env["KAFKA_CONTROLLER_QUORUM_VOTERS"])
- })
- }
-}
diff --git a/modules/kafka_native/kafka_test.go b/modules/kafka_native/kafka_test.go
deleted file mode 100644
index d1835fe89a..0000000000
--- a/modules/kafka_native/kafka_test.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package kafka_native_test
-
-import (
- "context"
- "strings"
- "testing"
-
- "github.com/IBM/sarama"
- "github.com/stretchr/testify/require"
-
- "github.com/testcontainers/testcontainers-go"
- kafka "github.com/testcontainers/testcontainers-go/modules/kafka_native"
-)
-
-func TestKafka(t *testing.T) {
- topic := "some-topic"
- ctx := context.Background()
-
- kafkaContainer, err := kafka.Run(ctx, "apache/kafka-native:3.9.1", kafka.WithClusterID("kraftCluster"))
- testcontainers.CleanupContainer(t, kafkaContainer)
- require.NoError(t, err)
-
- assertAdvertisedListeners(t, kafkaContainer)
-
- require.Truef(t, strings.EqualFold(kafkaContainer.ClusterID, "kraftCluster"), "expected clusterID to be %s, got %s", "kraftCluster", kafkaContainer.ClusterID)
-
- // getBrokers {
- brokers, err := kafkaContainer.Brokers(ctx)
- // }
- require.NoError(t, err)
-
- config := sarama.NewConfig()
- client, err := sarama.NewConsumerGroup(brokers, "groupName", config)
- require.NoError(t, err)
-
- consumer, ready, done, cancel := NewTestKafkaConsumer(t)
- defer cancel()
- go func() {
- if err := client.Consume(context.Background(), []string{topic}, consumer); err != nil {
- cancel()
- }
- }()
-
- // wait for the consumer to be ready
- <-ready
-
- // perform assertions
-
- // set config to true because successfully delivered messages will be returned on the Successes channel
- config.Producer.Return.Successes = true
-
- producer, err := sarama.NewSyncProducer(brokers, config)
- require.NoError(t, err)
-
- _, _, err = producer.SendMessage(&sarama.ProducerMessage{
- Topic: topic,
- Key: sarama.StringEncoder("key"),
- Value: sarama.StringEncoder("value"),
- })
- require.NoError(t, err)
-
- <-done
-
- require.Truef(t, strings.EqualFold(string(consumer.message.Key), "key"), "expected key to be %s, got %s", "key", string(consumer.message.Key))
- require.Truef(t, strings.EqualFold(string(consumer.message.Value), "value"), "expected value to be %s, got %s", "value", string(consumer.message.Value))
-}
-
-// assertAdvertisedListeners checks that the advertised listeners are set correctly:
-// - The BROKER:// protocol is using the hostname of the Kafka container
-func assertAdvertisedListeners(t *testing.T, container testcontainers.Container) {
- t.Helper()
- inspect, err := container.Inspect(context.Background())
- require.NoError(t, err)
-
- brokerURL := "BROKER://" + inspect.Config.Hostname + ":9092"
-
- ctx := context.Background()
-
- bs := testcontainers.RequireContainerExec(ctx, t, container, []string{"cat", "/usr/sbin/testcontainers_start.sh"})
-
- require.Containsf(t, bs, brokerURL, "expected advertised listeners to contain %s, got %s", brokerURL, bs)
-}
From b131199065e21fa95af731db2147c9420c191a1b Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 8 Nov 2025 22:59:01 +0100
Subject: [PATCH 08/54] chore: remove kafka_native from mkdocs
---
mkdocs.yml | 1 -
1 file changed, 1 deletion(-)
diff --git a/mkdocs.yml b/mkdocs.yml
index 490c639d7d..ac77b5b3bc 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -93,7 +93,6 @@ nav:
- modules/k3s.md
- modules/k6.md
- modules/kafka.md
- - modules/kafka_native.md
- modules/localstack.md
- modules/mariadb.md
- modules/meilisearch.md
From 13e9773ce0d45c6918d775039dbffc9aadd45af1 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 8 Nov 2025 23:03:29 +0100
Subject: [PATCH 09/54] docs: explain default behavior
---
docs/modules/kafka.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md
index fda0796019..c9ced3eaaf 100644
--- a/docs/modules/kafka.md
+++ b/docs/modules/kafka.md
@@ -77,6 +77,7 @@ Module would vary the starter script depending on the image in use, using follow
- image starts with `apache/kafka`: use Apache Kafka starter script.
- image starts with `confluentinc/`: use Confluent starter script.
+- otherwise: use Confluent starter script (for backward compatibility).
[Apache Kafka starter script](../../modules/kafka/kafka.go) inside_block:starterScriptApache
From 5da08a19fc6288fbfc45642b1662466497963101 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 8 Nov 2025 23:10:40 +0100
Subject: [PATCH 10/54] chore: refactor to make linter happy
---
modules/kafka/kafka_test.go | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go
index dbd4d94511..1ef8ca6e14 100644
--- a/modules/kafka/kafka_test.go
+++ b/modules/kafka/kafka_test.go
@@ -12,7 +12,9 @@ import (
"github.com/testcontainers/testcontainers-go/modules/kafka"
)
-func testFor(image string, t *testing.T) {
+func testFor(t *testing.T, image string) {
+ t.Helper()
+
topic := "some-topic"
ctx := context.Background()
@@ -87,7 +89,7 @@ func TestKafka(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
- testFor(tc.image, t)
+ testFor(t, tc.image)
})
}
}
From d641100ce835ade659de3b98989041760ebb3dc3 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 8 Nov 2025 23:11:49 +0100
Subject: [PATCH 11/54] chore: simplify helper function
---
modules/kafka/version.go | 55 +++---
modules/kafka/version_test.go | 342 +++++++++++++++++-----------------
2 files changed, 197 insertions(+), 200 deletions(-)
diff --git a/modules/kafka/version.go b/modules/kafka/version.go
index 7f252995ee..81d212f9e2 100644
--- a/modules/kafka/version.go
+++ b/modules/kafka/version.go
@@ -1,29 +1,26 @@
-package kafka
-
-import "strings"
-
-const (
- apacheKafkaImagePrefix = "apache/kafka"
- confluentincImagePrefix = "confluentinc/"
- dockerIoPrefix = "docker.io/"
-)
-
-func isApache(image string) bool {
- return strings.HasPrefix(image, apacheKafkaImagePrefix) || strings.HasPrefix(image, dockerIoPrefix+apacheKafkaImagePrefix)
-}
-
-func isConfluentinc(image string) bool {
- return strings.HasPrefix(image, confluentincImagePrefix) || strings.HasPrefix(image, dockerIoPrefix+confluentincImagePrefix)
-}
-
-func getStarterScriptContent(image string) string {
- if isApache(image) {
- return apacheStarterScriptContent
- } else if isConfluentinc(image) {
- return confluentincStarterScriptContent
- } else {
- // Default to confluentinc for backward compatibility
- // in situations when image was custom specified based on confluentinc
- return confluentincStarterScriptContent
- }
-}
+package kafka
+
+import "strings"
+
+const (
+ apacheKafkaImagePrefix = "apache/kafka"
+ confluentincImagePrefix = "confluentinc/"
+ dockerIoPrefix = "docker.io/"
+)
+
+func isApache(image string) bool {
+ return strings.HasPrefix(image, apacheKafkaImagePrefix) || strings.HasPrefix(image, dockerIoPrefix+apacheKafkaImagePrefix)
+}
+
+func isConfluentinc(image string) bool {
+ return strings.HasPrefix(image, confluentincImagePrefix) || strings.HasPrefix(image, dockerIoPrefix+confluentincImagePrefix)
+}
+
+func getStarterScriptContent(image string) string {
+ if isApache(image) {
+ return apacheStarterScriptContent
+ }
+ // Default to confluentinc for backward compatibility
+ // in situations when image was custom specified based on confluentinc
+ return confluentincStarterScriptContent
+}
diff --git a/modules/kafka/version_test.go b/modules/kafka/version_test.go
index 569a221507..b1cfa30b40 100644
--- a/modules/kafka/version_test.go
+++ b/modules/kafka/version_test.go
@@ -1,171 +1,171 @@
-package kafka
-
-import "testing"
-
-func Test_isApache(t *testing.T) {
- tests := []struct {
- name string
- image string
- want bool
- }{
- {
- name: "apache native image - no tag",
- image: "apache/kafka-native",
- want: true,
- },
- {
- name: "apache native image - latest",
- image: "apache/kafka-native:latest",
- want: true,
- },
- {
- name: "apache native image - specific version",
- image: "apache/kafka-native:4.0.1",
- want: true,
- },
- {
- name: "apache native image - specific version with docker.io prefix",
- image: "docker.io/apache/kafka-native:4.0.1",
- want: true,
- },
- {
- name: "apache not-native image - no tag",
- image: "apache/kafka",
- want: true,
- },
- {
- name: "apache not-native image - latest",
- image: "apache/kafka:latest",
- want: true,
- },
- {
- name: "apache not-native image - specific version",
- image: "apache/kafka:4.0.1",
- want: true,
- },
- {
- name: "apache not-native image - specific version with docker.io prefix",
- image: "docker.io/apache/kafka:4.0.1",
- want: true,
- },
- {
- name: "confluentinc image",
- image: "confluentinc/cp-kafka:latest",
- want: false,
- },
- {
- name: "custom image",
- image: "custom/kafka:latest",
- want: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if got := isApache(tt.image); got != tt.want {
- t.Errorf("isApache() = %v, want %v", got, tt.want)
- }
- })
- }
-}
-
-func Test_isConfluentinc(t *testing.T) {
- tests := []struct {
- name string
- image string
- want bool
- }{
- {
- name: "confluentinc image - no tag",
- image: "confluentinc/cp-kafka",
- want: true,
- },
- {
- name: "confluentinc image - latest",
- image: "confluentinc/cp-kafka:latest",
- want: true,
- },
- {
- name: "confluentinc image - specific version",
- image: "confluentinc/cp-kafka:8.1.0",
- want: true,
- },
- {
- name: "confluentinc image - specific version with docker.io prefix",
- image: "docker.io/confluentinc/cp-kafka:8.1.0",
- want: true,
- },
- {
- name: "apache native image",
- image: "apache/kafka-native:latest",
- want: false,
- },
- {
- name: "custom image",
- image: "custom/kafka:latest",
- want: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if got := isConfluentinc(tt.image); got != tt.want {
- t.Errorf("isConfluentinc() = %v, want %v", got, tt.want)
- }
- })
- }
-}
-
-func Test_getStarterScriptContent(t *testing.T) {
- tests := []struct {
- name string
- image string
- want string
- }{
- {
- name: "apache native image - latest",
- image: "apache/kafka-native:latest",
- want: apacheStarterScriptContent,
- },
- {
- name: "apache native image - specific version",
- image: "apache/kafka-native:4.0.1",
- want: apacheStarterScriptContent,
- },
- {
- name: "apache native image - specific version with docker.io prefix",
- image: "docker.io/apache/kafka-native:4.0.1",
- want: apacheStarterScriptContent,
- },
- {
- name: "confluentinc image - latest",
- image: "confluentinc/cp-kafka:latest",
- want: confluentincStarterScriptContent,
- },
- {
- name: "confluentinc image - no tag",
- image: "confluentinc/cp-kafka",
- want: confluentincStarterScriptContent,
- },
- {
- name: "confluentinc image - specific version",
- image: "confluentinc/cp-kafka:8.1.0",
- want: confluentincStarterScriptContent,
- },
- {
- name: "confluentinc image - specific version with docker.io prefix",
- image: "docker.io/confluentinc/cp-kafka:8.1.0",
- want: confluentincStarterScriptContent,
- },
- {
- name: "custom image",
- image: "custom/kafka:latest",
- want: confluentincStarterScriptContent,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if got := getStarterScriptContent(tt.image); got != tt.want {
- t.Errorf("getStarterScriptContent() = %v, want %v", got, tt.want)
- }
- })
- }
-}
+package kafka
+
+import "testing"
+
+func Test_isApache(t *testing.T) {
+ tests := []struct {
+ name string
+ image string
+ want bool
+ }{
+ {
+ name: "apache native image - no tag",
+ image: "apache/kafka-native",
+ want: true,
+ },
+ {
+ name: "apache native image - latest",
+ image: "apache/kafka-native:latest",
+ want: true,
+ },
+ {
+ name: "apache native image - specific version",
+ image: "apache/kafka-native:4.0.1",
+ want: true,
+ },
+ {
+ name: "apache native image - specific version with docker.io prefix",
+ image: "docker.io/apache/kafka-native:4.0.1",
+ want: true,
+ },
+ {
+ name: "apache not-native image - no tag",
+ image: "apache/kafka",
+ want: true,
+ },
+ {
+ name: "apache not-native image - latest",
+ image: "apache/kafka:latest",
+ want: true,
+ },
+ {
+ name: "apache not-native image - specific version",
+ image: "apache/kafka:4.0.1",
+ want: true,
+ },
+ {
+ name: "apache not-native image - specific version with docker.io prefix",
+ image: "docker.io/apache/kafka:4.0.1",
+ want: true,
+ },
+ {
+ name: "confluentinc image",
+ image: "confluentinc/cp-kafka:latest",
+ want: false,
+ },
+ {
+ name: "custom image",
+ image: "custom/kafka:latest",
+ want: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := isApache(tt.image); got != tt.want {
+ t.Errorf("isApache() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func Test_isConfluentinc(t *testing.T) {
+ tests := []struct {
+ name string
+ image string
+ want bool
+ }{
+ {
+ name: "confluentinc image - no tag",
+ image: "confluentinc/cp-kafka",
+ want: true,
+ },
+ {
+ name: "confluentinc image - latest",
+ image: "confluentinc/cp-kafka:latest",
+ want: true,
+ },
+ {
+ name: "confluentinc image - specific version",
+ image: "confluentinc/cp-kafka:8.1.0",
+ want: true,
+ },
+ {
+ name: "confluentinc image - specific version with docker.io prefix",
+ image: "docker.io/confluentinc/cp-kafka:8.1.0",
+ want: true,
+ },
+ {
+ name: "apache native image",
+ image: "apache/kafka-native:latest",
+ want: false,
+ },
+ {
+ name: "custom image",
+ image: "custom/kafka:latest",
+ want: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := isConfluentinc(tt.image); got != tt.want {
+ t.Errorf("isConfluentinc() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func Test_getStarterScriptContent(t *testing.T) {
+ tests := []struct {
+ name string
+ image string
+ want string
+ }{
+ {
+ name: "apache native image - latest",
+ image: "apache/kafka-native:latest",
+ want: apacheStarterScriptContent,
+ },
+ {
+ name: "apache native image - specific version",
+ image: "apache/kafka-native:4.0.1",
+ want: apacheStarterScriptContent,
+ },
+ {
+ name: "apache native image - specific version with docker.io prefix",
+ image: "docker.io/apache/kafka-native:4.0.1",
+ want: apacheStarterScriptContent,
+ },
+ {
+ name: "confluentinc image - latest",
+ image: "confluentinc/cp-kafka:latest",
+ want: confluentincStarterScriptContent,
+ },
+ {
+ name: "confluentinc image - no tag",
+ image: "confluentinc/cp-kafka",
+ want: confluentincStarterScriptContent,
+ },
+ {
+ name: "confluentinc image - specific version",
+ image: "confluentinc/cp-kafka:8.1.0",
+ want: confluentincStarterScriptContent,
+ },
+ {
+ name: "confluentinc image - specific version with docker.io prefix",
+ image: "docker.io/confluentinc/cp-kafka:8.1.0",
+ want: confluentincStarterScriptContent,
+ },
+ {
+ name: "custom image",
+ image: "custom/kafka:latest",
+ want: confluentincStarterScriptContent,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := getStarterScriptContent(tt.image); got != tt.want {
+ t.Errorf("getStarterScriptContent() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
From 5a57c72dc0a7fd1e85f19eacb6f6a5492bfeb726 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 8 Nov 2025 23:21:58 +0100
Subject: [PATCH 12/54] chore: test graceful shutdown for apache images
Note: for confluent graceful shutdown does not work,
only for Apache images
---
modules/kafka/kafka_test.go | 38 +++++++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)
diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go
index 1ef8ca6e14..ddfc637494 100644
--- a/modules/kafka/kafka_test.go
+++ b/modules/kafka/kafka_test.go
@@ -4,6 +4,7 @@ import (
"context"
"strings"
"testing"
+ "time"
"github.com/IBM/sarama"
"github.com/stretchr/testify/require"
@@ -117,3 +118,40 @@ func assertAdvertisedListeners(t *testing.T, container testcontainers.Container)
require.Containsf(t, bs, brokerURL, "expected advertised listeners to contain %s, got %s", brokerURL, bs)
}
+
+func TestKafkaGracefulShutdown(t *testing.T) {
+ testCases := []struct {
+ name string
+ image string
+ }{
+ {
+ name: "apache native",
+ image: "apache/kafka-native:4.0.1",
+ },
+ {
+ name: "apache not-native",
+ image: "apache/kafka:4.0.1",
+ },
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ ctx := context.Background()
+ kafkaContainer, err := kafka.Run(ctx, tc.image)
+ testcontainers.CleanupContainer(t, kafkaContainer, testcontainers.StopTimeout(0))
+ require.NoError(t, err)
+
+ done := make(chan struct{})
+ go func() {
+ stopTimeout := 120 * time.Second
+ _ = kafkaContainer.Stop(ctx, &stopTimeout)
+ close(done)
+ }()
+ gracefulShutdownTimeout := 60 * time.Second
+ select {
+ case <-done:
+ case <-time.After(gracefulShutdownTimeout):
+ require.Failf(t, "Kafka did not gracefully exit", "Kafka did not gracefully exit in %v", gracefulShutdownTimeout)
+ }
+ })
+ }
+}
From ea0521247a8c5d51be93eaff20ffa80c4e1a165e Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 8 Nov 2025 23:35:36 +0100
Subject: [PATCH 13/54] chore: test for more versions
---
modules/kafka/kafka_test.go | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go
index ddfc637494..ecb4b8d4ca 100644
--- a/modules/kafka/kafka_test.go
+++ b/modules/kafka/kafka_test.go
@@ -74,18 +74,30 @@ func TestKafka(t *testing.T) {
name string
image string
}{
+ {
+ name: "confluentinc",
+ image: "confluentinc/confluent-local:7.4.0",
+ },
{
name: "confluentinc",
image: "confluentinc/confluent-local:7.5.0",
},
{
- name: "apache native",
+ name: "apache native 4",
image: "apache/kafka-native:4.0.1",
},
{
- name: "apache not-native",
+ name: "apache not-native 4",
image: "apache/kafka:4.0.1",
},
+ {
+ name: "apache native 3.9",
+ image: "apache/kafka-native:3.9.1",
+ },
+ {
+ name: "apache not-native 3.9",
+ image: "apache/kafka:3.9.1",
+ },
}
for _, tc := range testCases {
From b55f3fd4ea79c2483c801755452f88395f5db404 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sun, 9 Nov 2025 00:05:17 +0100
Subject: [PATCH 14/54] chore: rename tests
---
modules/kafka/kafka_test.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go
index ecb4b8d4ca..c4b9ac9688 100644
--- a/modules/kafka/kafka_test.go
+++ b/modules/kafka/kafka_test.go
@@ -75,11 +75,11 @@ func TestKafka(t *testing.T) {
image string
}{
{
- name: "confluentinc",
+ name: "confluentinc 7.4.0",
image: "confluentinc/confluent-local:7.4.0",
},
{
- name: "confluentinc",
+ name: "confluentinc 7.5.0",
image: "confluentinc/confluent-local:7.5.0",
},
{
From 5cbb28d170bee92239ecc3647fae71f89c9edf4e Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sun, 9 Nov 2025 00:23:10 +0100
Subject: [PATCH 15/54] chore: graceful shutdown should not give error
---
modules/kafka/kafka_test.go | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go
index c4b9ac9688..bdf5d01978 100644
--- a/modules/kafka/kafka_test.go
+++ b/modules/kafka/kafka_test.go
@@ -153,14 +153,16 @@ func TestKafkaGracefulShutdown(t *testing.T) {
require.NoError(t, err)
done := make(chan struct{})
+ var stopErr error
go func() {
stopTimeout := 120 * time.Second
- _ = kafkaContainer.Stop(ctx, &stopTimeout)
+ stopErr = kafkaContainer.Stop(ctx, &stopTimeout)
close(done)
}()
gracefulShutdownTimeout := 60 * time.Second
select {
case <-done:
+ require.NoError(t, stopErr)
case <-time.After(gracefulShutdownTimeout):
require.Failf(t, "Kafka did not gracefully exit", "Kafka did not gracefully exit in %v", gracefulShutdownTimeout)
}
From 5946d3956742e8ed3f18886ba125a72087b2716f Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sun, 9 Nov 2025 00:24:44 +0100
Subject: [PATCH 16/54] chore: test already calls stop, drop extra timeout
---
modules/kafka/kafka_test.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go
index bdf5d01978..1ae2ca0188 100644
--- a/modules/kafka/kafka_test.go
+++ b/modules/kafka/kafka_test.go
@@ -149,7 +149,7 @@ func TestKafkaGracefulShutdown(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
ctx := context.Background()
kafkaContainer, err := kafka.Run(ctx, tc.image)
- testcontainers.CleanupContainer(t, kafkaContainer, testcontainers.StopTimeout(0))
+ testcontainers.CleanupContainer(t, kafkaContainer)
require.NoError(t, err)
done := make(chan struct{})
From 9c948d2661d69a5f186f4ca95e354f866f0bf1f7 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sun, 9 Nov 2025 00:57:58 +0100
Subject: [PATCH 17/54] docs: give more guidance to pick kafka image
---
docs/modules/kafka.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md
index c9ced3eaaf..8581d392e3 100644
--- a/docs/modules/kafka.md
+++ b/docs/modules/kafka.md
@@ -32,6 +32,10 @@ go get github.com/testcontainers/testcontainers-go/modules/kafka
The native container ([apache/kafka-native](https://hub.docker.com/r/apache/kafka-native/)) is based on GraalVM and typically starts several seconds faster than alternatives.
+It is recommended to prefer Apache Kafka images over Confluent images, as Confluent has [unresolved issue with graceful shutdown](https://github.com/testcontainers/testcontainers-go/issues/2206).
+
+Apache Kafka Native images are also smallest (under 150Mb), with standard Apache about 400Mb and Confluent close to 600Mb.
+
## Module Reference
### Run function
From 9ae246b45c92457c2a6665ae8642846a18907702 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sun, 9 Nov 2025 01:03:48 +0100
Subject: [PATCH 18/54] docs: correct snippet ref
---
docs/modules/kafka.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md
index 8581d392e3..cb4c4fa5ed 100644
--- a/docs/modules/kafka.md
+++ b/docs/modules/kafka.md
@@ -27,7 +27,7 @@ go get github.com/testcontainers/testcontainers-go/modules/kafka
-[Confluent Kafka](../../modules/kafka/examples_test.go) inside_block:runKafkaContainerConfluent
+[Confluent Kafka](../../modules/kafka/examples_test.go) inside_block:runKafkaContainerConfluentinc
The native container ([apache/kafka-native](https://hub.docker.com/r/apache/kafka-native/)) is based on GraalVM and typically starts several seconds faster than alternatives.
From 12a1ae97b025b3d321345396a84bb05db1015f6c Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sun, 9 Nov 2025 01:09:25 +0100
Subject: [PATCH 19/54] docs: wording adjustment
---
docs/modules/kafka.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md
index cb4c4fa5ed..673c040e1f 100644
--- a/docs/modules/kafka.md
+++ b/docs/modules/kafka.md
@@ -6,7 +6,7 @@ Since
-#### Init script
+#### Starter script
The Kafka container will be started using a custom shell script.
@@ -83,6 +86,8 @@ Module would vary the starter script depending on the image in use, using follow
- image starts with `confluentinc/`: use Confluent starter script.
- otherwise: use Confluent starter script (for backward compatibility).
+This behavior can be overridden using the `kafka.WithStarterScript` option.
+
[Apache Kafka starter script](../../modules/kafka/kafka.go) inside_block:starterScriptApache
@@ -91,6 +96,10 @@ Module would vary the starter script depending on the image in use, using follow
[Confluent starter script](../../modules/kafka/kafka.go) inside_block:starterScriptConfluentinc
+
+[Overriding starter script](../../modules/kafka/examples_test.go) inside_block:runKafkaContainerWithOverrideScript
+
+
### Container Options
When starting the Kafka container, you can pass options in a variadic way to configure it.
diff --git a/modules/kafka/examples_test.go b/modules/kafka/examples_test.go
index ea5330534a..c5cb668add 100644
--- a/modules/kafka/examples_test.go
+++ b/modules/kafka/examples_test.go
@@ -107,3 +107,43 @@ func ExampleRun_apacheNotNative() {
// test-cluster
// true
}
+
+func ExampleRun_apacheNative_withOverrideScript() {
+ // runKafkaContainerWithOverrideScript {
+ ctx := context.Background()
+
+ kafkaContainer, err := kafka.Run(ctx,
+ // the image might be different, for example
+ // custom-registry/apache/kafka-native:4.0.1,
+ // in which case the starter script would not
+ // be correctly inferred, and should be overridden
+ "apache/kafka-native:4.0.1",
+ kafka.WithClusterID("test-cluster"),
+ // this explicitly sets the starter script to use
+ // the one compatible with Apache images
+ kafka.WithStarterScript(kafka.ApacheStarterScript),
+ )
+ defer func() {
+ if err := testcontainers.TerminateContainer(kafkaContainer); err != nil {
+ log.Printf("failed to terminate container: %s", err)
+ }
+ }()
+ if err != nil {
+ log.Printf("failed to start container: %s", err)
+ return
+ }
+ // }
+
+ state, err := kafkaContainer.State(ctx)
+ if err != nil {
+ log.Printf("failed to get container state: %s", err)
+ return
+ }
+
+ fmt.Println(kafkaContainer.ClusterID)
+ fmt.Println(state.Running)
+
+ // Output:
+ // test-cluster
+ // true
+}
diff --git a/modules/kafka/kafka.go b/modules/kafka/kafka.go
index d1bb61fc14..3572c70181 100644
--- a/modules/kafka/kafka.go
+++ b/modules/kafka/kafka.go
@@ -17,10 +17,10 @@ import (
const publicPort = nat.Port("9093/tcp")
const (
- starterScript = "/usr/sbin/testcontainers_start.sh"
+ starterScriptPath = "/usr/sbin/testcontainers_start.sh"
// starterScriptConfluentinc {
- confluentincStarterScriptContent = `#!/bin/bash
+ ConfluentStarterScript = `#!/bin/bash
source /etc/confluent/docker/bash-config
export KAFKA_ADVERTISED_LISTENERS=%s,BROKER://%s:9092
echo Starting Kafka KRaft mode
@@ -32,7 +32,7 @@ echo '' > /etc/confluent/docker/ensure
// }
// starterScriptApache {
- apacheStarterScriptContent = `#!/bin/bash
+ ApacheStarterScript = `#!/bin/bash
export KAFKA_ADVERTISED_LISTENERS=%s,BROKER://%s:9092
echo Starting Apache Kafka
exec /etc/kafka/docker/run`
@@ -42,6 +42,7 @@ exec /etc/kafka/docker/run`
// KafkaContainer represents the Kafka container type used in the module
type KafkaContainer struct {
testcontainers.Container
+ options *runOptions
ClusterID string
}
@@ -57,6 +58,17 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom
return nil, err
}
+ runOptions := runOptions{
+ image: img,
+ }
+ for _, opt := range opts {
+ if apply, ok := opt.(Option); ok {
+ if err := apply(&runOptions); err != nil {
+ return nil, fmt.Errorf("apply option: %w", err)
+ }
+ }
+ }
+
moduleOpts := []testcontainers.ContainerCustomizer{
testcontainers.WithExposedPorts(string(publicPort)),
testcontainers.WithEnv(map[string]string{
@@ -79,7 +91,7 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom
}),
testcontainers.WithEntrypoint("sh"),
// this CMD will wait for the starter script to be copied into the container and then execute it
- testcontainers.WithCmd("-c", "while [ ! -f "+starterScript+" ]; do sleep 0.1; done; bash "+starterScript),
+ testcontainers.WithCmd("-c", "while [ ! -f "+starterScriptPath+" ]; do sleep 0.1; done; bash "+starterScriptPath),
testcontainers.WithLifecycleHooks(testcontainers.ContainerLifecycleHooks{
PostStarts: []testcontainers.ContainerHook{
// Use a single hook to copy the starter script and wait for
@@ -87,7 +99,7 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom
// if the starter script fails to copy.
func(ctx context.Context, c testcontainers.Container) error {
// 1. copy the starter script into the container
- if err := copyStarterScript(ctx, img, c); err != nil {
+ if err := copyStarterScript(ctx, &runOptions, c); err != nil {
return fmt.Errorf("copy starter script: %w", err)
}
@@ -129,7 +141,7 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom
}
// copyStarterScript copies the starter script into the container.
-func copyStarterScript(ctx context.Context, img string, c testcontainers.Container) error {
+func copyStarterScript(ctx context.Context, opts *runOptions, c testcontainers.Container) error {
if err := wait.ForMappedPort(publicPort).
WaitUntilReady(ctx, c); err != nil {
return fmt.Errorf("wait for mapped port: %w", err)
@@ -147,9 +159,9 @@ func copyStarterScript(ctx context.Context, img string, c testcontainers.Contain
hostname := inspect.Config.Hostname
- scriptContent := fmt.Sprintf(getStarterScriptContent(img), endpoint, hostname)
+ scriptContent := fmt.Sprintf(opts.getStarterScriptContent(), endpoint, hostname)
- if err := c.CopyToContainer(ctx, []byte(scriptContent), starterScript, 0o755); err != nil {
+ if err := c.CopyToContainer(ctx, []byte(scriptContent), starterScriptPath, 0o755); err != nil {
return fmt.Errorf("copy to container: %w", err)
}
diff --git a/modules/kafka/options.go b/modules/kafka/options.go
new file mode 100644
index 0000000000..4e669da932
--- /dev/null
+++ b/modules/kafka/options.go
@@ -0,0 +1,35 @@
+package kafka
+
+import "github.com/testcontainers/testcontainers-go"
+
+type runOptions struct {
+ image string
+ starterScript string
+}
+
+type Option func(*runOptions) error
+
+var _ testcontainers.ContainerCustomizer = (Option)(nil)
+
+func (o Option) Customize(req *testcontainers.GenericContainerRequest) error {
+ return nil
+}
+
+func WithStarterScript(content string) Option {
+ return func(o *runOptions) error {
+ o.starterScript = content
+ return nil
+ }
+}
+
+func (o *runOptions) getStarterScriptContent() string {
+ if o.starterScript == "" {
+ if isApache(o.image) {
+ return ApacheStarterScript
+ }
+ // Default to confluentinc for backward compatibility
+ // in situations when image was custom specified based on confluentinc
+ return ConfluentStarterScript
+ }
+ return o.starterScript
+}
diff --git a/modules/kafka/options_test.go b/modules/kafka/options_test.go
new file mode 100644
index 0000000000..1aff788c79
--- /dev/null
+++ b/modules/kafka/options_test.go
@@ -0,0 +1,67 @@
+package kafka
+
+import "testing"
+
+func Test_runOptions_getStarterScriptContent(t *testing.T) {
+ tests := []struct {
+ name string
+ image string
+ want string
+ }{
+ {
+ name: "apache native image - latest",
+ image: "apache/kafka-native:latest",
+ want: ApacheStarterScript,
+ },
+ {
+ name: "apache native image - specific version",
+ image: "apache/kafka-native:4.0.1",
+ want: ApacheStarterScript,
+ },
+ {
+ name: "apache native image - specific version with docker.io prefix",
+ image: "docker.io/apache/kafka-native:4.0.1",
+ want: ApacheStarterScript,
+ },
+ {
+ name: "confluentinc image - latest",
+ image: "confluentinc/cp-kafka:latest",
+ want: ConfluentStarterScript,
+ },
+ {
+ name: "confluentinc image - no tag",
+ image: "confluentinc/cp-kafka",
+ want: ConfluentStarterScript,
+ },
+ {
+ name: "confluentinc image - specific version",
+ image: "confluentinc/cp-kafka:8.1.0",
+ want: ConfluentStarterScript,
+ },
+ {
+ name: "confluentinc image - specific version with docker.io prefix",
+ image: "docker.io/confluentinc/cp-kafka:8.1.0",
+ want: ConfluentStarterScript,
+ },
+ {
+ name: "custom image",
+ image: "custom/kafka:latest",
+ want: ConfluentStarterScript,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ opts := &runOptions{
+ image: tt.image,
+ }
+ if got := opts.getStarterScriptContent(); got != tt.want {
+ t.Errorf("getStarterScriptContent() = %v, want %v", got, tt.want)
+ }
+
+ WithStarterScript("mytestsript")(opts)
+ if got := opts.getStarterScriptContent(); got != "mytestsript" {
+ t.Errorf("getStarterScriptContent() with explicit setting = %v, want %v", got, "mytestsript")
+ }
+ })
+ }
+}
diff --git a/modules/kafka/version.go b/modules/kafka/version.go
index 81d212f9e2..3c2c05558d 100644
--- a/modules/kafka/version.go
+++ b/modules/kafka/version.go
@@ -15,12 +15,3 @@ func isApache(image string) bool {
func isConfluentinc(image string) bool {
return strings.HasPrefix(image, confluentincImagePrefix) || strings.HasPrefix(image, dockerIoPrefix+confluentincImagePrefix)
}
-
-func getStarterScriptContent(image string) string {
- if isApache(image) {
- return apacheStarterScriptContent
- }
- // Default to confluentinc for backward compatibility
- // in situations when image was custom specified based on confluentinc
- return confluentincStarterScriptContent
-}
diff --git a/modules/kafka/version_test.go b/modules/kafka/version_test.go
index b1cfa30b40..43155211fc 100644
--- a/modules/kafka/version_test.go
+++ b/modules/kafka/version_test.go
@@ -113,59 +113,3 @@ func Test_isConfluentinc(t *testing.T) {
})
}
}
-
-func Test_getStarterScriptContent(t *testing.T) {
- tests := []struct {
- name string
- image string
- want string
- }{
- {
- name: "apache native image - latest",
- image: "apache/kafka-native:latest",
- want: apacheStarterScriptContent,
- },
- {
- name: "apache native image - specific version",
- image: "apache/kafka-native:4.0.1",
- want: apacheStarterScriptContent,
- },
- {
- name: "apache native image - specific version with docker.io prefix",
- image: "docker.io/apache/kafka-native:4.0.1",
- want: apacheStarterScriptContent,
- },
- {
- name: "confluentinc image - latest",
- image: "confluentinc/cp-kafka:latest",
- want: confluentincStarterScriptContent,
- },
- {
- name: "confluentinc image - no tag",
- image: "confluentinc/cp-kafka",
- want: confluentincStarterScriptContent,
- },
- {
- name: "confluentinc image - specific version",
- image: "confluentinc/cp-kafka:8.1.0",
- want: confluentincStarterScriptContent,
- },
- {
- name: "confluentinc image - specific version with docker.io prefix",
- image: "docker.io/confluentinc/cp-kafka:8.1.0",
- want: confluentincStarterScriptContent,
- },
- {
- name: "custom image",
- image: "custom/kafka:latest",
- want: confluentincStarterScriptContent,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if got := getStarterScriptContent(tt.image); got != tt.want {
- t.Errorf("getStarterScriptContent() = %v, want %v", got, tt.want)
- }
- })
- }
-}
From b51a3413b90f419047be9e59efd93d62735d140a Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sun, 9 Nov 2025 02:08:21 +0100
Subject: [PATCH 21/54] chore: clean, doc and linter
---
modules/kafka/kafka.go | 3 +-
modules/kafka/options.go | 76 ++++++++++---------
modules/kafka/options_test.go | 138 +++++++++++++++++-----------------
3 files changed, 113 insertions(+), 104 deletions(-)
diff --git a/modules/kafka/kafka.go b/modules/kafka/kafka.go
index 3572c70181..a14255e359 100644
--- a/modules/kafka/kafka.go
+++ b/modules/kafka/kafka.go
@@ -42,7 +42,6 @@ exec /etc/kafka/docker/run`
// KafkaContainer represents the Kafka container type used in the module
type KafkaContainer struct {
testcontainers.Container
- options *runOptions
ClusterID string
}
@@ -62,7 +61,7 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom
image: img,
}
for _, opt := range opts {
- if apply, ok := opt.(Option); ok {
+ if apply, ok := opt.(RunOption); ok {
if err := apply(&runOptions); err != nil {
return nil, fmt.Errorf("apply option: %w", err)
}
diff --git a/modules/kafka/options.go b/modules/kafka/options.go
index 4e669da932..0f59db523e 100644
--- a/modules/kafka/options.go
+++ b/modules/kafka/options.go
@@ -1,35 +1,41 @@
-package kafka
-
-import "github.com/testcontainers/testcontainers-go"
-
-type runOptions struct {
- image string
- starterScript string
-}
-
-type Option func(*runOptions) error
-
-var _ testcontainers.ContainerCustomizer = (Option)(nil)
-
-func (o Option) Customize(req *testcontainers.GenericContainerRequest) error {
- return nil
-}
-
-func WithStarterScript(content string) Option {
- return func(o *runOptions) error {
- o.starterScript = content
- return nil
- }
-}
-
-func (o *runOptions) getStarterScriptContent() string {
- if o.starterScript == "" {
- if isApache(o.image) {
- return ApacheStarterScript
- }
- // Default to confluentinc for backward compatibility
- // in situations when image was custom specified based on confluentinc
- return ConfluentStarterScript
- }
- return o.starterScript
-}
+package kafka
+
+import "github.com/testcontainers/testcontainers-go"
+
+type runOptions struct {
+ image string
+ starterScript string
+}
+
+// RunOption is an option that configures how Kafka container is started.
+type RunOption func(*runOptions) error
+
+var _ testcontainers.ContainerCustomizer = (RunOption)(nil)
+
+func (o RunOption) Customize(_req *testcontainers.GenericContainerRequest) error {
+ return nil
+}
+
+// WithStarterScript is an option to set a custom starter script content for the Kafka container.
+//
+// You would typically use this option when the image you are using is different from
+// the standard ones or the image is in your custom registry and automatic inference
+// of the starter script does not work as expected.
+func WithStarterScript(content string) RunOption {
+ return func(o *runOptions) error {
+ o.starterScript = content
+ return nil
+ }
+}
+
+func (o *runOptions) getStarterScriptContent() string {
+ if o.starterScript == "" {
+ if isApache(o.image) {
+ return ApacheStarterScript
+ }
+ // Default to confluentinc for backward compatibility
+ // in situations when image was custom specified based on confluentinc
+ return ConfluentStarterScript
+ }
+ return o.starterScript
+}
diff --git a/modules/kafka/options_test.go b/modules/kafka/options_test.go
index 1aff788c79..8420886b0e 100644
--- a/modules/kafka/options_test.go
+++ b/modules/kafka/options_test.go
@@ -1,67 +1,71 @@
-package kafka
-
-import "testing"
-
-func Test_runOptions_getStarterScriptContent(t *testing.T) {
- tests := []struct {
- name string
- image string
- want string
- }{
- {
- name: "apache native image - latest",
- image: "apache/kafka-native:latest",
- want: ApacheStarterScript,
- },
- {
- name: "apache native image - specific version",
- image: "apache/kafka-native:4.0.1",
- want: ApacheStarterScript,
- },
- {
- name: "apache native image - specific version with docker.io prefix",
- image: "docker.io/apache/kafka-native:4.0.1",
- want: ApacheStarterScript,
- },
- {
- name: "confluentinc image - latest",
- image: "confluentinc/cp-kafka:latest",
- want: ConfluentStarterScript,
- },
- {
- name: "confluentinc image - no tag",
- image: "confluentinc/cp-kafka",
- want: ConfluentStarterScript,
- },
- {
- name: "confluentinc image - specific version",
- image: "confluentinc/cp-kafka:8.1.0",
- want: ConfluentStarterScript,
- },
- {
- name: "confluentinc image - specific version with docker.io prefix",
- image: "docker.io/confluentinc/cp-kafka:8.1.0",
- want: ConfluentStarterScript,
- },
- {
- name: "custom image",
- image: "custom/kafka:latest",
- want: ConfluentStarterScript,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- opts := &runOptions{
- image: tt.image,
- }
- if got := opts.getStarterScriptContent(); got != tt.want {
- t.Errorf("getStarterScriptContent() = %v, want %v", got, tt.want)
- }
-
- WithStarterScript("mytestsript")(opts)
- if got := opts.getStarterScriptContent(); got != "mytestsript" {
- t.Errorf("getStarterScriptContent() with explicit setting = %v, want %v", got, "mytestsript")
- }
- })
- }
-}
+package kafka
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func Test_runOptions_getStarterScriptContent(t *testing.T) {
+ tests := []struct {
+ name string
+ image string
+ want string
+ }{
+ {
+ name: "apache native image - latest",
+ image: "apache/kafka-native:latest",
+ want: ApacheStarterScript,
+ },
+ {
+ name: "apache native image - specific version",
+ image: "apache/kafka-native:4.0.1",
+ want: ApacheStarterScript,
+ },
+ {
+ name: "apache native image - specific version with docker.io prefix",
+ image: "docker.io/apache/kafka-native:4.0.1",
+ want: ApacheStarterScript,
+ },
+ {
+ name: "confluentinc image - latest",
+ image: "confluentinc/cp-kafka:latest",
+ want: ConfluentStarterScript,
+ },
+ {
+ name: "confluentinc image - no tag",
+ image: "confluentinc/cp-kafka",
+ want: ConfluentStarterScript,
+ },
+ {
+ name: "confluentinc image - specific version",
+ image: "confluentinc/cp-kafka:8.1.0",
+ want: ConfluentStarterScript,
+ },
+ {
+ name: "confluentinc image - specific version with docker.io prefix",
+ image: "docker.io/confluentinc/cp-kafka:8.1.0",
+ want: ConfluentStarterScript,
+ },
+ {
+ name: "custom image",
+ image: "custom/kafka:latest",
+ want: ConfluentStarterScript,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ opts := &runOptions{
+ image: tt.image,
+ }
+ if got := opts.getStarterScriptContent(); got != tt.want {
+ t.Errorf("getStarterScriptContent() = %v, want %v", got, tt.want)
+ }
+
+ assert.NoError(t, WithStarterScript("mytestsript")(opts))
+ if got := opts.getStarterScriptContent(); got != "mytestsript" {
+ t.Errorf("getStarterScriptContent() with explicit setting = %v, want %v", got, "mytestsript")
+ }
+ })
+ }
+}
From a2eb5e372825b079d8806ec4f651cea6b2600144 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sun, 9 Nov 2025 02:09:50 +0100
Subject: [PATCH 22/54] chore: fix linter issues
---
modules/kafka/options.go | 2 +-
modules/kafka/options_test.go | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/modules/kafka/options.go b/modules/kafka/options.go
index 0f59db523e..ed35e59a09 100644
--- a/modules/kafka/options.go
+++ b/modules/kafka/options.go
@@ -12,7 +12,7 @@ type RunOption func(*runOptions) error
var _ testcontainers.ContainerCustomizer = (RunOption)(nil)
-func (o RunOption) Customize(_req *testcontainers.GenericContainerRequest) error {
+func (o RunOption) Customize(_ *testcontainers.GenericContainerRequest) error {
return nil
}
diff --git a/modules/kafka/options_test.go b/modules/kafka/options_test.go
index 8420886b0e..e9dfd262f2 100644
--- a/modules/kafka/options_test.go
+++ b/modules/kafka/options_test.go
@@ -3,7 +3,7 @@ package kafka
import (
"testing"
- "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func Test_runOptions_getStarterScriptContent(t *testing.T) {
@@ -62,7 +62,7 @@ func Test_runOptions_getStarterScriptContent(t *testing.T) {
t.Errorf("getStarterScriptContent() = %v, want %v", got, tt.want)
}
- assert.NoError(t, WithStarterScript("mytestsript")(opts))
+ require.NoError(t, WithStarterScript("mytestsript")(opts))
if got := opts.getStarterScriptContent(); got != "mytestsript" {
t.Errorf("getStarterScriptContent() with explicit setting = %v, want %v", got, "mytestsript")
}
From b21194e6ed5b8d899f17362e7a0dc1c153c4ae78 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sun, 9 Nov 2025 02:37:11 +0100
Subject: [PATCH 23/54] feat: add localhost listener to both flavors
---
modules/kafka/kafka.go | 10 ++++-----
modules/kafka/kafka_test.go | 41 +++++++++++++++++++++++++++++++++++++
2 files changed, 46 insertions(+), 5 deletions(-)
diff --git a/modules/kafka/kafka.go b/modules/kafka/kafka.go
index a14255e359..7a8b91b895 100644
--- a/modules/kafka/kafka.go
+++ b/modules/kafka/kafka.go
@@ -22,7 +22,7 @@ const (
// starterScriptConfluentinc {
ConfluentStarterScript = `#!/bin/bash
source /etc/confluent/docker/bash-config
-export KAFKA_ADVERTISED_LISTENERS=%s,BROKER://%s:9092
+export KAFKA_ADVERTISED_LISTENERS=%s,BROKER://%s:9092,LOCALHOST://localhost:9095
echo Starting Kafka KRaft mode
sed -i '/KAFKA_ZOOKEEPER_CONNECT/d' /etc/confluent/docker/configure
echo 'kafka-storage format --ignore-formatted -t "$(kafka-storage random-uuid)" -c /etc/kafka/kafka.properties' >> /etc/confluent/docker/configure
@@ -33,7 +33,7 @@ echo '' > /etc/confluent/docker/ensure
// starterScriptApache {
ApacheStarterScript = `#!/bin/bash
-export KAFKA_ADVERTISED_LISTENERS=%s,BROKER://%s:9092
+export KAFKA_ADVERTISED_LISTENERS=%s,BROKER://%s:9092,LOCALHOST://localhost:9095
echo Starting Apache Kafka
exec /etc/kafka/docker/run`
// }
@@ -72,9 +72,9 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom
testcontainers.WithExposedPorts(string(publicPort)),
testcontainers.WithEnv(map[string]string{
// envVars {
- "KAFKA_LISTENERS": "PLAINTEXT://0.0.0.0:9093,BROKER://0.0.0.0:9092,CONTROLLER://0.0.0.0:9094",
- "KAFKA_REST_BOOTSTRAP_SERVERS": "PLAINTEXT://0.0.0.0:9093,BROKER://0.0.0.0:9092,CONTROLLER://0.0.0.0:9094",
- "KAFKA_LISTENER_SECURITY_PROTOCOL_MAP": "BROKER:PLAINTEXT,PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT",
+ "KAFKA_LISTENERS": "PLAINTEXT://0.0.0.0:9093,BROKER://0.0.0.0:9092,CONTROLLER://0.0.0.0:9094,LOCALHOST://localhost:9095",
+ "KAFKA_REST_BOOTSTRAP_SERVERS": "PLAINTEXT://0.0.0.0:9093,BROKER://0.0.0.0:9092,CONTROLLER://0.0.0.0:9094,LOCALHOST://localhost:9095",
+ "KAFKA_LISTENER_SECURITY_PROTOCOL_MAP": "BROKER:PLAINTEXT,PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT,LOCALHOST:PLAINTEXT",
"KAFKA_INTER_BROKER_LISTENER_NAME": "BROKER",
"KAFKA_BROKER_ID": "1",
"KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR": "1",
diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go
index 1ae2ca0188..991c0a8cee 100644
--- a/modules/kafka/kafka_test.go
+++ b/modules/kafka/kafka_test.go
@@ -11,6 +11,7 @@ import (
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/kafka"
+ "github.com/testcontainers/testcontainers-go/wait"
)
func testFor(t *testing.T, image string) {
@@ -169,3 +170,43 @@ func TestKafkaGracefulShutdown(t *testing.T) {
})
}
}
+
+func TestKafkaLocalhostListener(t *testing.T) {
+ testCases := []struct {
+ name string
+ image string
+ topicsExecPath string
+ }{
+ {
+ name: "confluentinc 7.5.0",
+ image: "confluentinc/confluent-local:7.5.0",
+ topicsExecPath: "/bin/kafka-topics",
+ },
+ {
+ name: "apache 4",
+ image: "apache/kafka:4.0.1",
+ topicsExecPath: "/opt/kafka/bin/kafka-topics.sh",
+ },
+ // Note: this will not work for native images, because they do not include command line tools
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ ctx := context.Background()
+
+ kafkaContainer, err := kafka.Run(ctx, tc.image,
+ testcontainers.WithWaitStrategy(
+ wait.NewExecStrategy([]string{
+ tc.topicsExecPath,
+ "--bootstrap-server",
+ "localhost:9095",
+ "--list",
+ }).
+ WithExitCode(0).
+ WithPollInterval(2*time.Second).
+ WithStartupTimeout(120*time.Second)))
+ testcontainers.CleanupContainer(t, kafkaContainer, testcontainers.StopTimeout(0))
+ require.NoError(t, err)
+ })
+ }
+}
From 4b7ebda4c89d0c745eb15328fb6a5eb3f353be22 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 15 Nov 2025 04:07:01 +0100
Subject: [PATCH 24/54] provide With..Flavor options
---
docs/modules/kafka.md | 4 +++-
modules/kafka/examples_test.go | 2 +-
modules/kafka/kafka.go | 10 ++------
modules/kafka/kafka_test.go | 11 +++++++++
modules/kafka/options.go | 42 +++++++++++++++++++++++++++++++---
modules/kafka/options_test.go | 16 ++++++-------
6 files changed, 64 insertions(+), 21 deletions(-)
diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md
index edcc3bdd81..769253a016 100644
--- a/docs/modules/kafka.md
+++ b/docs/modules/kafka.md
@@ -86,7 +86,9 @@ Module would vary the starter script depending on the image in use, using follow
- image starts with `confluentinc/`: use Confluent starter script.
- otherwise: use Confluent starter script (for backward compatibility).
-This behavior can be overridden using the `kafka.WithStarterScript` option.
+This behavior can be overridden using the `kafka.WithApacheFlavor` or `kafka.WithConfluentFlavor` options.
+
+You can also provide a completely custom starter script using the `kafka.WithStarterScript` option, however note that if your script would become incompatible with the image in use, the container might fail to start.
[Apache Kafka starter script](../../modules/kafka/kafka.go) inside_block:starterScriptApache
diff --git a/modules/kafka/examples_test.go b/modules/kafka/examples_test.go
index c5cb668add..e63eefd930 100644
--- a/modules/kafka/examples_test.go
+++ b/modules/kafka/examples_test.go
@@ -121,7 +121,7 @@ func ExampleRun_apacheNative_withOverrideScript() {
kafka.WithClusterID("test-cluster"),
// this explicitly sets the starter script to use
// the one compatible with Apache images
- kafka.WithStarterScript(kafka.ApacheStarterScript),
+ kafka.WithApacheFlavor(),
)
defer func() {
if err := testcontainers.TerminateContainer(kafkaContainer); err != nil {
diff --git a/modules/kafka/kafka.go b/modules/kafka/kafka.go
index 7a8b91b895..253ca201d4 100644
--- a/modules/kafka/kafka.go
+++ b/modules/kafka/kafka.go
@@ -20,7 +20,7 @@ const (
starterScriptPath = "/usr/sbin/testcontainers_start.sh"
// starterScriptConfluentinc {
- ConfluentStarterScript = `#!/bin/bash
+ confluentStarterScript = `#!/bin/bash
source /etc/confluent/docker/bash-config
export KAFKA_ADVERTISED_LISTENERS=%s,BROKER://%s:9092,LOCALHOST://localhost:9095
echo Starting Kafka KRaft mode
@@ -32,7 +32,7 @@ echo '' > /etc/confluent/docker/ensure
// }
// starterScriptApache {
- ApacheStarterScript = `#!/bin/bash
+ apacheStarterScript = `#!/bin/bash
export KAFKA_ADVERTISED_LISTENERS=%s,BROKER://%s:9092,LOCALHOST://localhost:9095
echo Starting Apache Kafka
exec /etc/kafka/docker/run`
@@ -167,12 +167,6 @@ func copyStarterScript(ctx context.Context, opts *runOptions, c testcontainers.C
return nil
}
-func WithClusterID(clusterID string) testcontainers.CustomizeRequestOption {
- return testcontainers.WithEnv(map[string]string{
- "CLUSTER_ID": clusterID,
- })
-}
-
// Brokers retrieves the broker connection strings from Kafka with only one entry,
// defined by the exposed public port.
func (kc *KafkaContainer) Brokers(ctx context.Context) ([]string, error) {
diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go
index 991c0a8cee..5922eb4222 100644
--- a/modules/kafka/kafka_test.go
+++ b/modules/kafka/kafka_test.go
@@ -210,3 +210,14 @@ func TestKafkaLocalhostListener(t *testing.T) {
})
}
}
+
+func TestFailOnBothFlavors(t *testing.T) {
+ ctx := context.Background()
+
+ _, err := kafka.Run(ctx, "apache/kafka-native:4.0.1",
+ kafka.WithApacheFlavor(),
+ kafka.WithConfluentFlavor(),
+ )
+ require.Error(t, err)
+ require.EqualError(t, err, "apply option: flavor was already set, provide only one of WithApacheFlavor or WithConfluentFlavor")
+}
diff --git a/modules/kafka/options.go b/modules/kafka/options.go
index ed35e59a09..865482f2b8 100644
--- a/modules/kafka/options.go
+++ b/modules/kafka/options.go
@@ -1,10 +1,15 @@
package kafka
-import "github.com/testcontainers/testcontainers-go"
+import (
+ "errors"
+
+ "github.com/testcontainers/testcontainers-go"
+)
type runOptions struct {
image string
starterScript string
+ flavorWasSet bool
}
// RunOption is an option that configures how Kafka container is started.
@@ -31,11 +36,42 @@ func WithStarterScript(content string) RunOption {
func (o *runOptions) getStarterScriptContent() string {
if o.starterScript == "" {
if isApache(o.image) {
- return ApacheStarterScript
+ return apacheStarterScript
}
// Default to confluentinc for backward compatibility
// in situations when image was custom specified based on confluentinc
- return ConfluentStarterScript
+ return confluentStarterScript
}
return o.starterScript
}
+
+// WithClusterID sets the CLUSTER_ID environment variable for the Kafka container.
+func WithClusterID(clusterID string) testcontainers.CustomizeRequestOption {
+ return testcontainers.WithEnv(map[string]string{
+ "CLUSTER_ID": clusterID,
+ })
+}
+
+var errFlavorAlreadySet = errors.New("flavor was already set, provide only one of WithApacheFlavor or WithConfluentFlavor")
+
+func WithApacheFlavor() RunOption {
+ return func(o *runOptions) error {
+ o.starterScript = apacheStarterScript
+ if o.flavorWasSet {
+ return errFlavorAlreadySet
+ }
+ o.flavorWasSet = true
+ return nil
+ }
+}
+
+func WithConfluentFlavor() RunOption {
+ return func(o *runOptions) error {
+ o.starterScript = confluentStarterScript
+ if o.flavorWasSet {
+ return errFlavorAlreadySet
+ }
+ o.flavorWasSet = true
+ return nil
+ }
+}
diff --git a/modules/kafka/options_test.go b/modules/kafka/options_test.go
index e9dfd262f2..da5e820e9d 100644
--- a/modules/kafka/options_test.go
+++ b/modules/kafka/options_test.go
@@ -15,42 +15,42 @@ func Test_runOptions_getStarterScriptContent(t *testing.T) {
{
name: "apache native image - latest",
image: "apache/kafka-native:latest",
- want: ApacheStarterScript,
+ want: apacheStarterScript,
},
{
name: "apache native image - specific version",
image: "apache/kafka-native:4.0.1",
- want: ApacheStarterScript,
+ want: apacheStarterScript,
},
{
name: "apache native image - specific version with docker.io prefix",
image: "docker.io/apache/kafka-native:4.0.1",
- want: ApacheStarterScript,
+ want: apacheStarterScript,
},
{
name: "confluentinc image - latest",
image: "confluentinc/cp-kafka:latest",
- want: ConfluentStarterScript,
+ want: confluentStarterScript,
},
{
name: "confluentinc image - no tag",
image: "confluentinc/cp-kafka",
- want: ConfluentStarterScript,
+ want: confluentStarterScript,
},
{
name: "confluentinc image - specific version",
image: "confluentinc/cp-kafka:8.1.0",
- want: ConfluentStarterScript,
+ want: confluentStarterScript,
},
{
name: "confluentinc image - specific version with docker.io prefix",
image: "docker.io/confluentinc/cp-kafka:8.1.0",
- want: ConfluentStarterScript,
+ want: confluentStarterScript,
},
{
name: "custom image",
image: "custom/kafka:latest",
- want: ConfluentStarterScript,
+ want: confluentStarterScript,
},
}
for _, tt := range tests {
From 4d5a29971cde85d2bd8561b02be8312e7dc0f6c6 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 15 Nov 2025 04:11:04 +0100
Subject: [PATCH 25/54] go doc for options
---
modules/kafka/options.go | 2 ++
1 file changed, 2 insertions(+)
diff --git a/modules/kafka/options.go b/modules/kafka/options.go
index 865482f2b8..3246a069bd 100644
--- a/modules/kafka/options.go
+++ b/modules/kafka/options.go
@@ -54,6 +54,7 @@ func WithClusterID(clusterID string) testcontainers.CustomizeRequestOption {
var errFlavorAlreadySet = errors.New("flavor was already set, provide only one of WithApacheFlavor or WithConfluentFlavor")
+// WithApacheFlavor sets the starter script to the one compatible with Apache Kafka images.
func WithApacheFlavor() RunOption {
return func(o *runOptions) error {
o.starterScript = apacheStarterScript
@@ -65,6 +66,7 @@ func WithApacheFlavor() RunOption {
}
}
+// WithConfluentFlavor sets the starter script to the one compatible with Confluent Kafka images.
func WithConfluentFlavor() RunOption {
return func(o *runOptions) error {
o.starterScript = confluentStarterScript
From 8c1a7abf130fe7d91c3e17a907ce51b8bae2792e Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 15 Nov 2025 04:12:02 +0100
Subject: [PATCH 26/54] update option go doc
---
modules/kafka/options.go | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/modules/kafka/options.go b/modules/kafka/options.go
index 3246a069bd..7ae6384ad0 100644
--- a/modules/kafka/options.go
+++ b/modules/kafka/options.go
@@ -24,8 +24,7 @@ func (o RunOption) Customize(_ *testcontainers.GenericContainerRequest) error {
// WithStarterScript is an option to set a custom starter script content for the Kafka container.
//
// You would typically use this option when the image you are using is different from
-// the standard ones or the image is in your custom registry and automatic inference
-// of the starter script does not work as expected.
+// the standard ones and the default starter script does not work as expected.
func WithStarterScript(content string) RunOption {
return func(o *runOptions) error {
o.starterScript = content
From 9adbde3dafe448600e9001a777a706f30d3b2279 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 15 Nov 2025 04:15:52 +0100
Subject: [PATCH 27/54] update doc about flavor option
---
docs/modules/kafka.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md
index 769253a016..ae38bd7f5d 100644
--- a/docs/modules/kafka.md
+++ b/docs/modules/kafka.md
@@ -86,7 +86,7 @@ Module would vary the starter script depending on the image in use, using follow
- image starts with `confluentinc/`: use Confluent starter script.
- otherwise: use Confluent starter script (for backward compatibility).
-This behavior can be overridden using the `kafka.WithApacheFlavor` or `kafka.WithConfluentFlavor` options.
+This behavior can be overridden using the `kafka.WithApacheFlavor` or `kafka.WithConfluentFlavor` options. You can only provide one of these two options, otherwise an error would be returned when starting the container.
You can also provide a completely custom starter script using the `kafka.WithStarterScript` option, however note that if your script would become incompatible with the image in use, the container might fail to start.
From fcfee34dc6abc0611832b21871ebc653ba96aaef Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 15 Nov 2025 04:22:54 +0100
Subject: [PATCH 28/54] fix to not have side effect when returning error
---
modules/kafka/options.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/modules/kafka/options.go b/modules/kafka/options.go
index 7ae6384ad0..c5dfb1622e 100644
--- a/modules/kafka/options.go
+++ b/modules/kafka/options.go
@@ -56,11 +56,11 @@ var errFlavorAlreadySet = errors.New("flavor was already set, provide only one o
// WithApacheFlavor sets the starter script to the one compatible with Apache Kafka images.
func WithApacheFlavor() RunOption {
return func(o *runOptions) error {
- o.starterScript = apacheStarterScript
if o.flavorWasSet {
return errFlavorAlreadySet
}
o.flavorWasSet = true
+ o.starterScript = apacheStarterScript
return nil
}
}
@@ -68,11 +68,11 @@ func WithApacheFlavor() RunOption {
// WithConfluentFlavor sets the starter script to the one compatible with Confluent Kafka images.
func WithConfluentFlavor() RunOption {
return func(o *runOptions) error {
- o.starterScript = confluentStarterScript
if o.flavorWasSet {
return errFlavorAlreadySet
}
o.flavorWasSet = true
+ o.starterScript = confluentStarterScript
return nil
}
}
From f227e38778758db0bcc799d2834b5c0098815d9d Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 15 Nov 2025 04:25:28 +0100
Subject: [PATCH 29/54] update go doc for options
---
modules/kafka/options.go | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/modules/kafka/options.go b/modules/kafka/options.go
index c5dfb1622e..fe3736eb36 100644
--- a/modules/kafka/options.go
+++ b/modules/kafka/options.go
@@ -25,6 +25,8 @@ func (o RunOption) Customize(_ *testcontainers.GenericContainerRequest) error {
//
// You would typically use this option when the image you are using is different from
// the standard ones and the default starter script does not work as expected.
+// This option conflicts with WithApacheFlavor and WithConfluentFlavor options,
+// and the last one provided takes precedence.
func WithStarterScript(content string) RunOption {
return func(o *runOptions) error {
o.starterScript = content
@@ -54,6 +56,10 @@ func WithClusterID(clusterID string) testcontainers.CustomizeRequestOption {
var errFlavorAlreadySet = errors.New("flavor was already set, provide only one of WithApacheFlavor or WithConfluentFlavor")
// WithApacheFlavor sets the starter script to the one compatible with Apache Kafka images.
+//
+// Note: this option conflicts with WithConfluentFlavor option, and the error is returned
+// if both are provided. The option also conflicts with WithStarterScript option,
+// but in that case the last one provided takes precedence.
func WithApacheFlavor() RunOption {
return func(o *runOptions) error {
if o.flavorWasSet {
@@ -66,6 +72,10 @@ func WithApacheFlavor() RunOption {
}
// WithConfluentFlavor sets the starter script to the one compatible with Confluent Kafka images.
+//
+// Note: this option conflicts with WithApacheFlavor option, and the error is returned
+// if both are provided. The option also conflicts with WithStarterScript option,
+// but in that case the last one provided takes precedence.
func WithConfluentFlavor() RunOption {
return func(o *runOptions) error {
if o.flavorWasSet {
From 164ff1b50267c74100fceac972c8d3c4e18dd218 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 15 Nov 2025 04:39:30 +0100
Subject: [PATCH 30/54] document images difference
---
docs/modules/kafka.md | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md
index ae38bd7f5d..2da03abfc7 100644
--- a/docs/modules/kafka.md
+++ b/docs/modules/kafka.md
@@ -34,7 +34,13 @@ The native container ([apache/kafka-native](https://hub.docker.com/r/apache/kafk
It is recommended to prefer Apache Kafka images over Confluent images, as Confluent has [unresolved issue with graceful shutdown](https://github.com/testcontainers/testcontainers-go/issues/2206).
-Apache Kafka Native images are also smallest (under 150Mb), with standard Apache about 400Mb and Confluent close to 600Mb.
+Apache Kafka Native images are also smallest, however they do not include CLI tools such as `kafka-topics.sh`.
+
+| Docker Image | Size | Startup time | Notes |
+|---------------------|--------------------------|-----------------|-------------------------|
+| Apache Kafka Native | 137MB (4.0.1 linux amd) | ~1-3 seconds | Does not have CLI tools |
+| Apache Kafka | 393MB (4.0.1 linux amd) | ~4-5 seconds | |
+| Confluent Kafka | 649MB (7.5.0 linux amd) | ~13-15 seconds | Shutdown issues |
!!!info
If you use image from custom registry, you might need to override starter script, see "Starter script" section below.
From 31f4579b05f42aca28a7ccc4eb606640f80e85ad Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 15 Nov 2025 04:51:48 +0100
Subject: [PATCH 31/54] document options better
---
docs/modules/kafka.md | 18 +++++++++++++-
modules/kafka/examples_test.go | 44 ++++++++++++++++++++++++++++++++--
2 files changed, 59 insertions(+), 3 deletions(-)
diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md
index 2da03abfc7..94de7b546e 100644
--- a/docs/modules/kafka.md
+++ b/docs/modules/kafka.md
@@ -105,13 +105,29 @@ You can also provide a completely custom starter script using the `kafka.WithSta
-[Overriding starter script](../../modules/kafka/examples_test.go) inside_block:runKafkaContainerWithOverrideScript
+[Overriding starter script](../../modules/kafka/examples_test.go) inside_block:runKafkaContainerWithApacheFlavor
### Container Options
When starting the Kafka container, you can pass options in a variadic way to configure it.
+#### WithApacheFlavor/WithConfluentFlavor
+
+You can manually specify which flavor of starter script to use with the following options:
+
+
+[With Apache Flavor](../../modules/kafka/examples_test.go) inside_block:runKafkaContainerWithApacheFlavor
+
+
+
+[With Confluent Flavor](../../modules/kafka/examples_test.go) inside_block:runKafkaContainerWithConfluentFlavor
+
+
+#### WithStarterScript
+
+This allows to provide a completely custom starter script for the Kafka container. Be careful when using this option, as compatibility with any image and module version cannot be guaranteed.
+
{% include "../features/common_functional_options_list.md" %}
### Container Methods
diff --git a/modules/kafka/examples_test.go b/modules/kafka/examples_test.go
index e63eefd930..1743e5aee5 100644
--- a/modules/kafka/examples_test.go
+++ b/modules/kafka/examples_test.go
@@ -108,8 +108,8 @@ func ExampleRun_apacheNotNative() {
// true
}
-func ExampleRun_apacheNative_withOverrideScript() {
- // runKafkaContainerWithOverrideScript {
+func ExampleRun_apacheNative_withApacheFlavor() {
+ // runKafkaContainerWithApacheFlavor {
ctx := context.Background()
kafkaContainer, err := kafka.Run(ctx,
@@ -123,6 +123,7 @@ func ExampleRun_apacheNative_withOverrideScript() {
// the one compatible with Apache images
kafka.WithApacheFlavor(),
)
+ // }
defer func() {
if err := testcontainers.TerminateContainer(kafkaContainer); err != nil {
log.Printf("failed to terminate container: %s", err)
@@ -132,7 +133,46 @@ func ExampleRun_apacheNative_withOverrideScript() {
log.Printf("failed to start container: %s", err)
return
}
+
+ state, err := kafkaContainer.State(ctx)
+ if err != nil {
+ log.Printf("failed to get container state: %s", err)
+ return
+ }
+
+ fmt.Println(kafkaContainer.ClusterID)
+ fmt.Println(state.Running)
+
+ // Output:
+ // test-cluster
+ // true
+}
+
+func ExampleRun_confluentinc_withConfluentFlavor() {
+ // runKafkaContainerWithConfluentFlavor {
+ ctx := context.Background()
+
+ kafkaContainer, err := kafka.Run(ctx,
+ // the image might be different, for example
+ // custom-registry/confluentinc/confluent-local:7.5.0,
+ // in which case the starter script might not
+ // be correctly inferred, and should be overridden
+ "confluentinc/confluent-local:7.5.0",
+ kafka.WithClusterID("test-cluster"),
+ // this explicitly sets the starter script to use
+ // the one compatible with Confluent images
+ kafka.WithConfluentFlavor(),
+ )
// }
+ defer func() {
+ if err := testcontainers.TerminateContainer(kafkaContainer); err != nil {
+ log.Printf("failed to terminate container: %s", err)
+ }
+ }()
+ if err != nil {
+ log.Printf("failed to start container: %s", err)
+ return
+ }
state, err := kafkaContainer.State(ctx)
if err != nil {
From f6f75b2e918be850b357a5ba2fe496c26f19ec4f Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 15 Nov 2025 05:27:20 +0100
Subject: [PATCH 32/54] document localhost listener
---
docs/modules/kafka.md | 12 +++++++++
modules/kafka/examples_test.go | 45 ++++++++++++++++++++++++++++++++++
2 files changed, 57 insertions(+)
diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md
index 94de7b546e..1289cb2e74 100644
--- a/docs/modules/kafka.md
+++ b/docs/modules/kafka.md
@@ -45,6 +45,18 @@ Apache Kafka Native images are also smallest, however they do not include CLI to
!!!info
If you use image from custom registry, you might need to override starter script, see "Starter script" section below.
+### Localhost listener
+
+Kafka container would by default be configured with `localhost:9095` as one of advertised listeners. This can be used when you need to run CLI commands inside the container, for example with custom wait strategies or to prepare test data.
+
+Here is an example that uses custom wait strategy that checks if listing topics works:
+
+
+[Custom wait strategy](../../modules/kafka/examples_test.go) inside_block:runKafkaContainerAndUseLocalhostListener
+
+
+Note: this will not work with `apache/kafka-native` images, as they do not include CLI tools.
+
## Module Reference
### Run function
diff --git a/modules/kafka/examples_test.go b/modules/kafka/examples_test.go
index 1743e5aee5..cfd7f86343 100644
--- a/modules/kafka/examples_test.go
+++ b/modules/kafka/examples_test.go
@@ -4,9 +4,11 @@ import (
"context"
"fmt"
"log"
+ "time"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/kafka"
+ "github.com/testcontainers/testcontainers-go/wait"
)
func ExampleRun_confluentinc() {
@@ -187,3 +189,46 @@ func ExampleRun_confluentinc_withConfluentFlavor() {
// test-cluster
// true
}
+
+func ExampleRun_usingLocalhostListener() {
+ ctx := context.Background()
+
+ // runKafkaContainerAndUseLocalhostListener {
+ kafkaContainer, err := kafka.Run(ctx, "apache/kafka:4.0.1",
+ testcontainers.WithWaitStrategy(
+ wait.NewExecStrategy([]string{
+ "/opt/kafka/bin/kafka-topics.sh",
+ "--bootstrap-server",
+ "localhost:9095",
+ "--list",
+ }).
+ WithExitCode(0).
+ WithPollInterval(2*time.Second).
+ WithStartupTimeout(120*time.Second),
+ ),
+ kafka.WithClusterID("test-cluster"),
+ )
+ // }
+ defer func() {
+ if err := testcontainers.TerminateContainer(kafkaContainer); err != nil {
+ log.Printf("failed to terminate container: %s", err)
+ }
+ }()
+ if err != nil {
+ log.Printf("failed to start container: %s", err)
+ return
+ }
+
+ state, err := kafkaContainer.State(ctx)
+ if err != nil {
+ log.Printf("failed to get container state: %s", err)
+ return
+ }
+
+ fmt.Println(kafkaContainer.ClusterID)
+ fmt.Println(state.Running)
+
+ // Output:
+ // test-cluster
+ // true
+}
From 1e916ee53c572b407e9a755f61935202764ff0b3 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 15 Nov 2025 06:00:46 +0100
Subject: [PATCH 33/54] move localhost doc into separate section
---
docs/modules/kafka.md | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md
index 1289cb2e74..8146722e2e 100644
--- a/docs/modules/kafka.md
+++ b/docs/modules/kafka.md
@@ -45,18 +45,6 @@ Apache Kafka Native images are also smallest, however they do not include CLI to
!!!info
If you use image from custom registry, you might need to override starter script, see "Starter script" section below.
-### Localhost listener
-
-Kafka container would by default be configured with `localhost:9095` as one of advertised listeners. This can be used when you need to run CLI commands inside the container, for example with custom wait strategies or to prepare test data.
-
-Here is an example that uses custom wait strategy that checks if listing topics works:
-
-
-[Custom wait strategy](../../modules/kafka/examples_test.go) inside_block:runKafkaContainerAndUseLocalhostListener
-
-
-Note: this will not work with `apache/kafka-native` images, as they do not include CLI tools.
-
## Module Reference
### Run function
@@ -155,3 +143,15 @@ The `Brokers(ctx)` method returns the Kafka brokers as a string slice, containin
[Get Kafka brokers](../../modules/kafka/kafka_test.go) inside_block:getBrokers
+
+## Localhost listener
+
+Kafka container would by default be configured with `localhost:9095` as one of advertised listeners. This can be used when you need to run CLI commands inside the container, for example with custom wait strategies or to prepare test data.
+
+Here is an example that uses custom wait strategy that checks if listing topics works:
+
+
+[Custom wait strategy](../../modules/kafka/examples_test.go) inside_block:runKafkaContainerAndUseLocalhostListener
+
+
+Note: this will not work with `apache/kafka-native` images, as they do not include CLI tools.
\ No newline at end of file
From 9a005157743fdfc2c3f370e105c823214807619f Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 15 Nov 2025 06:30:33 +0100
Subject: [PATCH 34/54] add benchmarks and correct time docs
---
docs/modules/kafka.md | 6 +++---
modules/kafka/benchmark_test.go | 38 +++++++++++++++++++++++++++++++++
2 files changed, 41 insertions(+), 3 deletions(-)
create mode 100644 modules/kafka/benchmark_test.go
diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md
index 8146722e2e..0e7f72daf8 100644
--- a/docs/modules/kafka.md
+++ b/docs/modules/kafka.md
@@ -36,10 +36,10 @@ It is recommended to prefer Apache Kafka images over Confluent images, as Conflu
Apache Kafka Native images are also smallest, however they do not include CLI tools such as `kafka-topics.sh`.
-| Docker Image | Size | Startup time | Notes |
+| Docker Image | Size | Start/stop time | Notes |
|---------------------|--------------------------|-----------------|-------------------------|
-| Apache Kafka Native | 137MB (4.0.1 linux amd) | ~1-3 seconds | Does not have CLI tools |
-| Apache Kafka | 393MB (4.0.1 linux amd) | ~4-5 seconds | |
+| Apache Kafka Native | 137MB (4.0.1 linux amd) | <1 seconds | Does not have CLI tools |
+| Apache Kafka | 393MB (4.0.1 linux amd) | ~3-4 seconds | |
| Confluent Kafka | 649MB (7.5.0 linux amd) | ~13-15 seconds | Shutdown issues |
!!!info
diff --git a/modules/kafka/benchmark_test.go b/modules/kafka/benchmark_test.go
new file mode 100644
index 0000000000..b78ef3de69
--- /dev/null
+++ b/modules/kafka/benchmark_test.go
@@ -0,0 +1,38 @@
+package kafka_test
+
+import (
+ "context"
+ "log"
+ "testing"
+
+ "github.com/testcontainers/testcontainers-go"
+ "github.com/testcontainers/testcontainers-go/modules/kafka"
+)
+
+func startStopBenchmark(b *testing.B, image string) {
+ for b.Loop() {
+ kafkaContainer, err := kafka.Run(context.Background(),
+ image,
+ )
+ if err != nil {
+ log.Printf("failed to start container: %s", err)
+ panic(err)
+ }
+
+ if err := testcontainers.TerminateContainer(kafkaContainer); err != nil {
+ log.Printf("failed to terminate container: %s", err)
+ }
+ }
+}
+
+func BenchmarkConfluentStartStop(b *testing.B) {
+ startStopBenchmark(b, "confluentinc/confluent-local:7.5.0")
+}
+
+func BenchmarkApacheNativeStartStop(b *testing.B) {
+ startStopBenchmark(b, "apache/kafka-native:4.0.1")
+}
+
+func BenchmarkApacheStartStop(b *testing.B) {
+ startStopBenchmark(b, "apache/kafka:4.0.1")
+}
From ad56cd27352f6be9b9a5a38058d6a1b3e27cfbf5 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 15 Nov 2025 06:34:16 +0100
Subject: [PATCH 35/54] fix doc typo
---
docs/modules/kafka.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md
index 0e7f72daf8..ef65078832 100644
--- a/docs/modules/kafka.md
+++ b/docs/modules/kafka.md
@@ -38,7 +38,7 @@ Apache Kafka Native images are also smallest, however they do not include CLI to
| Docker Image | Size | Start/stop time | Notes |
|---------------------|--------------------------|-----------------|-------------------------|
-| Apache Kafka Native | 137MB (4.0.1 linux amd) | <1 seconds | Does not have CLI tools |
+| Apache Kafka Native | 137MB (4.0.1 linux amd) | <1 second | Does not have CLI tools |
| Apache Kafka | 393MB (4.0.1 linux amd) | ~3-4 seconds | |
| Confluent Kafka | 649MB (7.5.0 linux amd) | ~13-15 seconds | Shutdown issues |
From a3a5cb728404c33019395bc0a963a8616f082609 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 15 Nov 2025 06:41:01 +0100
Subject: [PATCH 36/54] update images pick table
---
docs/modules/kafka.md | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md
index ef65078832..ab7afe9d18 100644
--- a/docs/modules/kafka.md
+++ b/docs/modules/kafka.md
@@ -36,11 +36,11 @@ It is recommended to prefer Apache Kafka images over Confluent images, as Conflu
Apache Kafka Native images are also smallest, however they do not include CLI tools such as `kafka-topics.sh`.
-| Docker Image | Size | Start/stop time | Notes |
-|---------------------|--------------------------|-----------------|-------------------------|
-| Apache Kafka Native | 137MB (4.0.1 linux amd) | <1 second | Does not have CLI tools |
-| Apache Kafka | 393MB (4.0.1 linux amd) | ~3-4 seconds | |
-| Confluent Kafka | 649MB (7.5.0 linux amd) | ~13-15 seconds | Shutdown issues |
+| Docker Image | Size | Start/stop time | CLI Tools | Graceful Shutdown |
+|---------------------|--------------------------|-----------------|-----------|-------------------|
+| Apache Kafka Native | 137MB (4.0.1 linux amd) | <1 second | No | OK |
+| Apache Kafka | 393MB (4.0.1 linux amd) | ~3-4 seconds | Yes | OK |
+| Confluent Kafka | 649MB (7.5.0 linux amd) | ~13-14 seconds | Yes | [issue](https://github.com/testcontainers/testcontainers-go/issues/2206) |
!!!info
If you use image from custom registry, you might need to override starter script, see "Starter script" section below.
From fb876de90c7c52d4cdd839e7bb6fb645785584bf Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 15 Nov 2025 06:41:30 +0100
Subject: [PATCH 37/54] ideomatic benchmark error handling
---
modules/kafka/benchmark_test.go | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/modules/kafka/benchmark_test.go b/modules/kafka/benchmark_test.go
index b78ef3de69..304b8847b9 100644
--- a/modules/kafka/benchmark_test.go
+++ b/modules/kafka/benchmark_test.go
@@ -2,7 +2,6 @@ package kafka_test
import (
"context"
- "log"
"testing"
"github.com/testcontainers/testcontainers-go"
@@ -15,12 +14,11 @@ func startStopBenchmark(b *testing.B, image string) {
image,
)
if err != nil {
- log.Printf("failed to start container: %s", err)
- panic(err)
+ b.Fatalf("failed to start container: %s", err)
}
if err := testcontainers.TerminateContainer(kafkaContainer); err != nil {
- log.Printf("failed to terminate container: %s", err)
+ b.Errorf("failed to terminate container: %s", err)
}
}
}
From 57b8a7c92b54881200d6f067c5ba5e085c115c39 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 15 Nov 2025 06:43:31 +0100
Subject: [PATCH 38/54] fix lint issue in benchmark
---
modules/kafka/benchmark_test.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/modules/kafka/benchmark_test.go b/modules/kafka/benchmark_test.go
index 304b8847b9..b7530e1b50 100644
--- a/modules/kafka/benchmark_test.go
+++ b/modules/kafka/benchmark_test.go
@@ -9,6 +9,7 @@ import (
)
func startStopBenchmark(b *testing.B, image string) {
+ b.Helper()
for b.Loop() {
kafkaContainer, err := kafka.Run(context.Background(),
image,
From 5fe72049ecfc549731f1376d1423468bbfb261c0 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 15 Nov 2025 06:51:21 +0100
Subject: [PATCH 39/54] docs: add since version markers
---
docs/modules/kafka.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md
index ab7afe9d18..5bbe3d4a1f 100644
--- a/docs/modules/kafka.md
+++ b/docs/modules/kafka.md
@@ -114,6 +114,8 @@ When starting the Kafka container, you can pass options in a variadic way to con
#### WithApacheFlavor/WithConfluentFlavor
+- Not available until the next release :material-tag: main
+
You can manually specify which flavor of starter script to use with the following options:
@@ -126,6 +128,8 @@ You can manually specify which flavor of starter script to use with the followin
#### WithStarterScript
+- Not available until the next release :material-tag: main
+
This allows to provide a completely custom starter script for the Kafka container. Be careful when using this option, as compatibility with any image and module version cannot be guaranteed.
{% include "../features/common_functional_options_list.md" %}
From 0e923e02bce7cdf384efabc5a3160e8f9264ec71 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 15 Nov 2025 07:01:31 +0100
Subject: [PATCH 40/54] docs: clarify when apache images are available
---
docs/modules/kafka.md | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md
index 5bbe3d4a1f..ed803d3354 100644
--- a/docs/modules/kafka.md
+++ b/docs/modules/kafka.md
@@ -6,7 +6,7 @@ Since :material-tag: main
+
+Images `apache/kafka`, `apache/kafka-native` ([Apache Kafka](https://kafka.apache.org/)) are supported by this module in addition to `confluentinc/confluent-local` ([Confluent](https://docs.confluent.io/kafka/overview.html)).
+
## Usage example
From f83d07d725753a26b4d81e9d2ef375c63219664f Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 15 Nov 2025 07:07:56 +0100
Subject: [PATCH 41/54] docs: separate image pick section from usage
---
docs/modules/kafka.md | 28 ++++++++++++++--------------
1 file changed, 14 insertions(+), 14 deletions(-)
diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md
index ed803d3354..fc9afea995 100644
--- a/docs/modules/kafka.md
+++ b/docs/modules/kafka.md
@@ -22,20 +22,6 @@ go get github.com/testcontainers/testcontainers-go/modules/kafka
Images `apache/kafka`, `apache/kafka-native` ([Apache Kafka](https://kafka.apache.org/)) are supported by this module in addition to `confluentinc/confluent-local` ([Confluent](https://docs.confluent.io/kafka/overview.html)).
-## Usage example
-
-
-[Apache Kafka Native](../../modules/kafka/examples_test.go) inside_block:runKafkaContainerApacheNative
-
-
-
-[Apache Kafka](../../modules/kafka/examples_test.go) inside_block:runKafkaContainerApacheNotNative
-
-
-
-[Confluent Kafka](../../modules/kafka/examples_test.go) inside_block:runKafkaContainerConfluentinc
-
-
The native container ([apache/kafka-native](https://hub.docker.com/r/apache/kafka-native/)) is based on GraalVM and typically starts several seconds faster than alternatives.
It is recommended to prefer Apache Kafka images over Confluent images, as Confluent has [unresolved issue with graceful shutdown](https://github.com/testcontainers/testcontainers-go/issues/2206).
@@ -51,6 +37,20 @@ Apache Kafka Native images are also smallest, however they do not include CLI to
!!!info
If you use image from custom registry, you might need to override starter script, see "Starter script" section below.
+## Usage example
+
+
+[Apache Kafka Native](../../modules/kafka/examples_test.go) inside_block:runKafkaContainerApacheNative
+
+
+
+[Apache Kafka](../../modules/kafka/examples_test.go) inside_block:runKafkaContainerApacheNotNative
+
+
+
+[Confluent Kafka](../../modules/kafka/examples_test.go) inside_block:runKafkaContainerConfluentinc
+
+
## Module Reference
### Run function
From b65ba065377299d12c089f29d216566506891814 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 15 Nov 2025 07:13:05 +0100
Subject: [PATCH 42/54] docs: simplify doc with reference to detailed option
---
docs/modules/kafka.md | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md
index fc9afea995..758c1992dd 100644
--- a/docs/modules/kafka.md
+++ b/docs/modules/kafka.md
@@ -98,9 +98,7 @@ Module would vary the starter script depending on the image in use, using follow
- image starts with `confluentinc/`: use Confluent starter script.
- otherwise: use Confluent starter script (for backward compatibility).
-This behavior can be overridden using the `kafka.WithApacheFlavor` or `kafka.WithConfluentFlavor` options. You can only provide one of these two options, otherwise an error would be returned when starting the container.
-
-You can also provide a completely custom starter script using the `kafka.WithStarterScript` option, however note that if your script would become incompatible with the image in use, the container might fail to start.
+See also [WithApacheFlavor/WithConfluentFlavor](#withapacheflavorwithconfluentflavor) and [WithStarterScript](#withstarterscript) options to override this behavior.
[Apache Kafka starter script](../../modules/kafka/kafka.go) inside_block:starterScriptApache
From 4f02648ea5848afc35329c3a9e560eaf376ef108 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 15 Nov 2025 07:13:56 +0100
Subject: [PATCH 43/54] docs: link starter script section
---
docs/modules/kafka.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md
index 758c1992dd..da4e7f9809 100644
--- a/docs/modules/kafka.md
+++ b/docs/modules/kafka.md
@@ -35,7 +35,7 @@ Apache Kafka Native images are also smallest, however they do not include CLI to
| Confluent Kafka | 649MB (7.5.0 linux amd) | ~13-14 seconds | Yes | [issue](https://github.com/testcontainers/testcontainers-go/issues/2206) |
!!!info
- If you use image from custom registry, you might need to override starter script, see "Starter script" section below.
+ If you use image from custom registry, you might need to override starter script, see [Starter script](#starter-script) section below.
## Usage example
From 87060535be73151f8209a4a1a30514fa4c8e4350 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Sat, 15 Nov 2025 07:22:40 +0100
Subject: [PATCH 44/54] docs: add since version marker to localhost listener
---
docs/modules/kafka.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md
index da4e7f9809..abadbbc5d0 100644
--- a/docs/modules/kafka.md
+++ b/docs/modules/kafka.md
@@ -154,6 +154,8 @@ The `Brokers(ctx)` method returns the Kafka brokers as a string slice, containin
## Localhost listener
+- Not available until the next release :material-tag: main
+
Kafka container would by default be configured with `localhost:9095` as one of advertised listeners. This can be used when you need to run CLI commands inside the container, for example with custom wait strategies or to prepare test data.
Here is an example that uses custom wait strategy that checks if listing topics works:
From 5f5e8fb066927285c012c1136a8c2da171ad65a8 Mon Sep 17 00:00:00 2001
From: strowk
Date: Thu, 20 Nov 2025 05:30:59 +0700
Subject: [PATCH 45/54] docs: align section header
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/kafka.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md
index abadbbc5d0..9df6d48ab3 100644
--- a/docs/modules/kafka.md
+++ b/docs/modules/kafka.md
@@ -152,7 +152,7 @@ The `Brokers(ctx)` method returns the Kafka brokers as a string slice, containin
[Get Kafka brokers](../../modules/kafka/kafka_test.go) inside_block:getBrokers
-## Localhost listener
+#### Localhost listener
- Not available until the next release :material-tag: main
From b8de9b1e19386e32c1a170ef335f9d992f8a8a66 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Thu, 20 Nov 2025 11:44:17 +0100
Subject: [PATCH 46/54] chore: simplify test assertion
---
modules/kafka/kafka_test.go | 1 -
1 file changed, 1 deletion(-)
diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go
index 5922eb4222..308c2a600b 100644
--- a/modules/kafka/kafka_test.go
+++ b/modules/kafka/kafka_test.go
@@ -219,5 +219,4 @@ func TestFailOnBothFlavors(t *testing.T) {
kafka.WithConfluentFlavor(),
)
require.Error(t, err)
- require.EqualError(t, err, "apply option: flavor was already set, provide only one of WithApacheFlavor or WithConfluentFlavor")
}
From c6a2b1dc65f3bd3f3718b7ef467c931f384699aa Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Thu, 20 Nov 2025 11:44:41 +0100
Subject: [PATCH 47/54] chore: simplify benchmark assertion
---
modules/kafka/benchmark_test.go | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/modules/kafka/benchmark_test.go b/modules/kafka/benchmark_test.go
index b7530e1b50..ec2f498570 100644
--- a/modules/kafka/benchmark_test.go
+++ b/modules/kafka/benchmark_test.go
@@ -4,6 +4,7 @@ import (
"context"
"testing"
+ "github.com/stretchr/testify/require"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/kafka"
)
@@ -14,13 +15,10 @@ func startStopBenchmark(b *testing.B, image string) {
kafkaContainer, err := kafka.Run(context.Background(),
image,
)
- if err != nil {
- b.Fatalf("failed to start container: %s", err)
- }
+ require.NoError(b, err)
- if err := testcontainers.TerminateContainer(kafkaContainer); err != nil {
- b.Errorf("failed to terminate container: %s", err)
- }
+ err = testcontainers.TerminateContainer(kafkaContainer)
+ require.NoError(b, err)
}
}
From 81b106567a6e87c9bbc6f103565cfd8fb46f5ebe Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Thu, 20 Nov 2025 11:58:17 +0100
Subject: [PATCH 48/54] fix: return error if conflicting options are given
---
modules/kafka/kafka_test.go | 12 ++++++++++++
modules/kafka/options.go | 18 ++++++++++--------
2 files changed, 22 insertions(+), 8 deletions(-)
diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go
index 308c2a600b..9ba8acbe6c 100644
--- a/modules/kafka/kafka_test.go
+++ b/modules/kafka/kafka_test.go
@@ -219,4 +219,16 @@ func TestFailOnBothFlavors(t *testing.T) {
kafka.WithConfluentFlavor(),
)
require.Error(t, err)
+
+ _, err = kafka.Run(ctx, "apache/kafka-native:4.0.1",
+ kafka.WithApacheFlavor(),
+ kafka.WithStarterScript("testscript"),
+ )
+ require.Error(t, err)
+
+ _, err = kafka.Run(ctx, "apache/kafka-native:4.0.1",
+ kafka.WithConfluentFlavor(),
+ kafka.WithStarterScript("testscript"),
+ )
+ require.Error(t, err)
}
diff --git a/modules/kafka/options.go b/modules/kafka/options.go
index fe3736eb36..39f0573b9d 100644
--- a/modules/kafka/options.go
+++ b/modules/kafka/options.go
@@ -26,9 +26,13 @@ func (o RunOption) Customize(_ *testcontainers.GenericContainerRequest) error {
// You would typically use this option when the image you are using is different from
// the standard ones and the default starter script does not work as expected.
// This option conflicts with WithApacheFlavor and WithConfluentFlavor options,
-// and the last one provided takes precedence.
+// and the error is returned if several are provided.
func WithStarterScript(content string) RunOption {
return func(o *runOptions) error {
+ if o.flavorWasSet {
+ return errFlavorAlreadySet
+ }
+ o.flavorWasSet = true
o.starterScript = content
return nil
}
@@ -53,13 +57,12 @@ func WithClusterID(clusterID string) testcontainers.CustomizeRequestOption {
})
}
-var errFlavorAlreadySet = errors.New("flavor was already set, provide only one of WithApacheFlavor or WithConfluentFlavor")
+var errFlavorAlreadySet = errors.New("flavor was already set, provide only one of WithApacheFlavor, WithConfluentFlavor or WithStarterScript")
// WithApacheFlavor sets the starter script to the one compatible with Apache Kafka images.
//
-// Note: this option conflicts with WithConfluentFlavor option, and the error is returned
-// if both are provided. The option also conflicts with WithStarterScript option,
-// but in that case the last one provided takes precedence.
+// Note: this option conflicts with WithConfluentFlavor and WithStarterScript options,
+// and the error is returned if several are provided.
func WithApacheFlavor() RunOption {
return func(o *runOptions) error {
if o.flavorWasSet {
@@ -73,9 +76,8 @@ func WithApacheFlavor() RunOption {
// WithConfluentFlavor sets the starter script to the one compatible with Confluent Kafka images.
//
-// Note: this option conflicts with WithApacheFlavor option, and the error is returned
-// if both are provided. The option also conflicts with WithStarterScript option,
-// but in that case the last one provided takes precedence.
+// Note: this option conflicts with WithApacheFlavor and WithStarterScript options,
+// and the error is returned if several are provided.
func WithConfluentFlavor() RunOption {
return func(o *runOptions) error {
if o.flavorWasSet {
From 34242dfbba5f91d19497d47a79f13fa0a69926a2 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Thu, 20 Nov 2025 11:58:30 +0100
Subject: [PATCH 49/54] chore: formatting from linter
---
modules/kafka/benchmark_test.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/modules/kafka/benchmark_test.go b/modules/kafka/benchmark_test.go
index ec2f498570..29913c127f 100644
--- a/modules/kafka/benchmark_test.go
+++ b/modules/kafka/benchmark_test.go
@@ -5,6 +5,7 @@ import (
"testing"
"github.com/stretchr/testify/require"
+
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/kafka"
)
From 907ce01cd5e2c98e01085f98dacc949f0d77f5c6 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Thu, 20 Nov 2025 12:09:23 +0100
Subject: [PATCH 50/54] docs: explain conflicting options
---
docs/modules/kafka.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md
index 9df6d48ab3..aa86088799 100644
--- a/docs/modules/kafka.md
+++ b/docs/modules/kafka.md
@@ -130,12 +130,16 @@ You can manually specify which flavor of starter script to use with the followin
[With Confluent Flavor](../../modules/kafka/examples_test.go) inside_block:runKafkaContainerWithConfluentFlavor
+Note that both `WithApacheFlavor` and `WithConfluentFlavor` conflict with each other and with `WithStarterScript` option. An error will be returned if several of those options are provided.
+
#### WithStarterScript
- Not available until the next release :material-tag: main
This allows to provide a completely custom starter script for the Kafka container. Be careful when using this option, as compatibility with any image and module version cannot be guaranteed.
+Note that `WithStarterScript` conflicts with `WithApacheFlavor` and `WithConfluentFlavor` options. An error will be returned if several of those options are provided.
+
{% include "../features/common_functional_options_list.md" %}
### Container Methods
From 4627d1b86eadc82f431a49eb9a8a2004047ea97c Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Thu, 20 Nov 2025 12:12:35 +0100
Subject: [PATCH 51/54] chore: simplify test assertions
---
modules/kafka/options_test.go | 9 ++-------
modules/kafka/version_test.go | 14 +++++++-------
2 files changed, 9 insertions(+), 14 deletions(-)
diff --git a/modules/kafka/options_test.go b/modules/kafka/options_test.go
index da5e820e9d..339c6ea184 100644
--- a/modules/kafka/options_test.go
+++ b/modules/kafka/options_test.go
@@ -58,14 +58,9 @@ func Test_runOptions_getStarterScriptContent(t *testing.T) {
opts := &runOptions{
image: tt.image,
}
- if got := opts.getStarterScriptContent(); got != tt.want {
- t.Errorf("getStarterScriptContent() = %v, want %v", got, tt.want)
- }
-
+ require.Equal(t, tt.want, opts.getStarterScriptContent())
require.NoError(t, WithStarterScript("mytestsript")(opts))
- if got := opts.getStarterScriptContent(); got != "mytestsript" {
- t.Errorf("getStarterScriptContent() with explicit setting = %v, want %v", got, "mytestsript")
- }
+ require.Equal(t, "mytestsript", opts.getStarterScriptContent())
})
}
}
diff --git a/modules/kafka/version_test.go b/modules/kafka/version_test.go
index 43155211fc..c91961426a 100644
--- a/modules/kafka/version_test.go
+++ b/modules/kafka/version_test.go
@@ -1,6 +1,10 @@
package kafka
-import "testing"
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
func Test_isApache(t *testing.T) {
tests := []struct {
@@ -61,9 +65,7 @@ func Test_isApache(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- if got := isApache(tt.image); got != tt.want {
- t.Errorf("isApache() = %v, want %v", got, tt.want)
- }
+ require.Equal(t, tt.want, isApache(tt.image))
})
}
}
@@ -107,9 +109,7 @@ func Test_isConfluentinc(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- if got := isConfluentinc(tt.image); got != tt.want {
- t.Errorf("isConfluentinc() = %v, want %v", got, tt.want)
- }
+ require.Equal(t, tt.want, isConfluentinc(tt.image))
})
}
}
From 1fc9b85fa1edbfa10461e3cb3b5381302e863df0 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Thu, 20 Nov 2025 12:27:44 +0100
Subject: [PATCH 52/54] chore: correct typo in test
---
modules/kafka/options_test.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/modules/kafka/options_test.go b/modules/kafka/options_test.go
index 339c6ea184..e1f7edd271 100644
--- a/modules/kafka/options_test.go
+++ b/modules/kafka/options_test.go
@@ -59,8 +59,8 @@ func Test_runOptions_getStarterScriptContent(t *testing.T) {
image: tt.image,
}
require.Equal(t, tt.want, opts.getStarterScriptContent())
- require.NoError(t, WithStarterScript("mytestsript")(opts))
- require.Equal(t, "mytestsript", opts.getStarterScriptContent())
+ require.NoError(t, WithStarterScript("mytestcript")(opts))
+ require.Equal(t, "mytestcript", opts.getStarterScriptContent())
})
}
}
From 68f2f3febee7205a2f07105bde2df9fbd82b3851 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Thu, 20 Nov 2025 12:29:58 +0100
Subject: [PATCH 53/54] chore: add apache/kafka test cases
---
modules/kafka/options_test.go | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/modules/kafka/options_test.go b/modules/kafka/options_test.go
index e1f7edd271..6ecd7b7623 100644
--- a/modules/kafka/options_test.go
+++ b/modules/kafka/options_test.go
@@ -27,6 +27,21 @@ func Test_runOptions_getStarterScriptContent(t *testing.T) {
image: "docker.io/apache/kafka-native:4.0.1",
want: apacheStarterScript,
},
+ {
+ name: "apache non-native image - latest",
+ image: "apache/kafka:latest",
+ want: apacheStarterScript,
+ },
+ {
+ name: "apache non-native image - specific version",
+ image: "apache/kafka:4.0.0",
+ want: apacheStarterScript,
+ },
+ {
+ name: "apache non-native image - with docker.io prefix",
+ image: "docker.io/apache/kafka:4.0.0",
+ want: apacheStarterScript,
+ },
{
name: "confluentinc image - latest",
image: "confluentinc/cp-kafka:latest",
From 3034eea0e4763f58318c6736d790244cced5e488 Mon Sep 17 00:00:00 2001
From: Timur Sultanaev <25692644+strowk@users.noreply.github.com>
Date: Thu, 20 Nov 2025 12:31:21 +0100
Subject: [PATCH 54/54] docs: better describe image detection
---
docs/modules/kafka.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md
index aa86088799..8896c73648 100644
--- a/docs/modules/kafka.md
+++ b/docs/modules/kafka.md
@@ -94,8 +94,8 @@ The Kafka container will be started using a custom shell script.
Module would vary the starter script depending on the image in use, using following logic:
-- image starts with `apache/kafka`: use Apache Kafka starter script.
-- image starts with `confluentinc/`: use Confluent starter script.
+- image starts with `apache/kafka` or `docker.io/apache/kafka`: use Apache Kafka starter script.
+- image starts with `confluentinc/` or `docker.io/confluentinc/`: use Confluent starter script.
- otherwise: use Confluent starter script (for backward compatibility).
See also [WithApacheFlavor/WithConfluentFlavor](#withapacheflavorwithconfluentflavor) and [WithStarterScript](#withstarterscript) options to override this behavior.