@@ -14,12 +14,12 @@ import (
1414)
1515
1616const (
17- defaultHTTPPort = "9200"
18- defaultTCPPort = "9300"
19- defaultPassword = "changeme"
20- defaultUsername = "elastic"
21- defaultCaCertPath = "/usr/share/elasticsearch/config/certs/http_ca.crt"
22- minimalImageVersion = "7.9.2 "
17+ defaultHTTPPort = "9200"
18+ defaultTCPPort = "9300"
19+ defaultPassword = "changeme"
20+ defaultUsername = "elastic"
21+ defaultCaCertPath = "/usr/share/elasticsearch/config/certs/http_ca.crt"
22+ envPassword = "ELASTIC_PASSWORD "
2323)
2424
2525const (
@@ -43,52 +43,37 @@ func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomize
4343
4444// Run creates an instance of the Elasticsearch container type
4545func Run (ctx context.Context , img string , opts ... testcontainers.ContainerCustomizer ) (* ElasticsearchContainer , error ) {
46- req := testcontainers.GenericContainerRequest {
47- ContainerRequest : testcontainers.ContainerRequest {
48- Image : img ,
49- Env : map [string ]string {
50- "discovery.type" : "single-node" ,
51- "cluster.routing.allocation.disk.threshold_enabled" : "false" ,
52- },
53- ExposedPorts : []string {
54- defaultHTTPPort + "/tcp" ,
55- defaultTCPPort + "/tcp" ,
56- },
57- },
58- Started : true ,
46+ moduleOpts := []testcontainers.ContainerCustomizer {
47+ testcontainers .WithExposedPorts (defaultHTTPPort + "/tcp" , defaultTCPPort + "/tcp" ),
48+ testcontainers .WithEnv (map [string ]string {
49+ "discovery.type" : "single-node" ,
50+ "cluster.routing.allocation.disk.threshold_enabled" : "false" ,
51+ }),
5952 }
6053
6154 // Gather all config options (defaults and then apply provided options)
6255 options := defaultOptions ()
6356 for _ , opt := range opts {
6457 if apply , ok := opt .(Option ); ok {
65- apply (options )
66- }
67- if err := opt .Customize (& req ); err != nil {
68- return nil , err
58+ if err := apply (options ); err != nil {
59+ return nil , fmt .Errorf ("apply option: %w" , err )
60+ }
6961 }
7062 }
7163
72- // Transfer the password settings to the container request
73- if err := configurePassword (options , & req ); err != nil {
74- return nil , err
75- }
64+ moduleOpts = append (moduleOpts , opts ... )
7665
77- if isAtLeastVersion (req .Image , 7 ) {
78- req .LifecycleHooks = append (req .LifecycleHooks ,
79- testcontainers.ContainerLifecycleHooks {
80- PostCreates : []testcontainers.ContainerHook {configureJvmOpts },
81- },
82- )
83- }
66+ // Transfer the password settings to the container request
67+ moduleOpts = append (moduleOpts , configurePassword (options ))
68+ moduleOpts = append (moduleOpts , configureJvmOpts ())
8469
8570 // Set the default waiting strategy if not already set.
86- setWaitFor ( options , & req . ContainerRequest )
71+ moduleOpts = append ( moduleOpts , configureWaitFor ( options ) )
8772
88- container , err := testcontainers .GenericContainer (ctx , req )
73+ ctr , err := testcontainers .Run (ctx , img , moduleOpts ... )
8974 var esContainer * ElasticsearchContainer
90- if container != nil {
91- esContainer = & ElasticsearchContainer {Container : container , Settings : * options }
75+ if ctr != nil {
76+ esContainer = & ElasticsearchContainer {Container : ctr , Settings : * options }
9277 }
9378 if err != nil {
9479 return esContainer , fmt .Errorf ("generic container: %w" , err )
@@ -120,40 +105,32 @@ func (w *certWriter) Read(r io.Reader) error {
120105 return nil
121106}
122107
123- // setWaitFor sets the req.WaitingFor strategy based on settings.
124- func setWaitFor (options * Options , req * testcontainers.ContainerRequest ) {
125- var strategies []wait.Strategy
126- if req .WaitingFor != nil {
127- // Custom waiting strategy, ensure we honour it.
128- strategies = append (strategies , req .WaitingFor )
129- }
108+ // configureWaitFor sets the req.WaitingFor strategy based on settings.
109+ func configureWaitFor (options * Options ) testcontainers.CustomizeRequestOption {
110+ return func (req * testcontainers.GenericContainerRequest ) error {
111+ var strategies []wait.Strategy
130112
131- waitHTTP := wait .ForHTTP ("/" ).WithPort (defaultHTTPPort )
132- if sslRequired (req ) {
133- waitHTTP = waitHTTP .WithTLS (true ).WithAllowInsecure (true )
134- cw := & certWriter {
135- options : options ,
136- certPool : x509 .NewCertPool (),
137- }
113+ waitHTTP := wait .ForHTTP ("/" ).WithPort (defaultHTTPPort )
114+ if sslRequired (req ) {
115+ cw := & certWriter {
116+ options : options ,
117+ certPool : x509 .NewCertPool (),
118+ }
138119
139- waitHTTP = waitHTTP .
140- WithTLS (true , & tls.Config {RootCAs : cw .certPool })
120+ waitHTTP = waitHTTP .
121+ WithTLS (true , & tls.Config {RootCAs : cw .certPool }). WithAllowInsecure ( true )
141122
142- strategies = append (strategies , wait .ForFile (defaultCaCertPath ).WithMatcher (cw .Read ))
143- }
123+ strategies = append (strategies , wait .ForFile (defaultCaCertPath ).WithMatcher (cw .Read ))
124+ }
144125
145- if options .Password != "" || options .Username != "" {
146- waitHTTP = waitHTTP .WithBasicAuth (options .Username , options .Password )
147- }
126+ if options .Password != "" || options .Username != "" {
127+ waitHTTP = waitHTTP .WithBasicAuth (options .Username , options .Password )
128+ }
148129
149- strategies = append (strategies , waitHTTP )
130+ strategies = append (strategies , waitHTTP )
150131
151- if len (strategies ) > 1 {
152- req .WaitingFor = wait .ForAll (strategies ... )
153- return
132+ return testcontainers .WithAdditionalWaitStrategy (strategies ... )(req )
154133 }
155-
156- req .WaitingFor = strategies [0 ]
157134}
158135
159136// configureAddress sets the address of the Elasticsearch container.
@@ -175,7 +152,7 @@ func (c *ElasticsearchContainer) configureAddress(ctx context.Context) error {
175152}
176153
177154// sslRequired returns true if the SSL is required, otherwise false.
178- func sslRequired (req * testcontainers.ContainerRequest ) bool {
155+ func sslRequired (req * testcontainers.GenericContainerRequest ) bool {
179156 if ! isAtLeastVersion (req .Image , 8 ) {
180157 return false
181158 }
@@ -200,58 +177,84 @@ func sslRequired(req *testcontainers.ContainerRequest) bool {
200177
201178// configurePassword transfers the password settings to the container request.
202179// If the password is not set, it will be set to "changeme" for Elasticsearch 8
203- func configurePassword (settings * Options , req * testcontainers.GenericContainerRequest ) error {
204- // set "changeme" as default password for Elasticsearch 8
205- if isAtLeastVersion (req .Image , 8 ) && settings .Password == "" {
206- WithPassword (defaultPassword )(settings )
207- }
208-
209- if settings .Password != "" {
210- if isOSS (req .Image ) {
211- return errors .New ("it's not possible to activate security on Elastic OSS Image. Please switch to the default distribution" )
180+ func configurePassword (settings * Options ) testcontainers.CustomizeRequestOption {
181+ return func (req * testcontainers.GenericContainerRequest ) error {
182+ // set "changeme" as default password for Elasticsearch 8
183+ if isAtLeastVersion (req .Image , 8 ) && settings .Password == "" {
184+ if err := WithPassword (defaultPassword )(settings ); err != nil {
185+ return fmt .Errorf ("with password: %w" , err )
186+ }
212187 }
213188
214- if _ , ok := req .Env ["ELASTIC_PASSWORD" ]; ! ok {
215- req .Env ["ELASTIC_PASSWORD" ] = settings .Password
216- }
189+ if settings .Password != "" {
190+ if isOSS (req .Image ) {
191+ return errors .New ("it's not possible to activate security on Elastic OSS Image. Please switch to the default distribution" )
192+ }
217193
218- // major version 8 is secure by default and does not need this to enable authentication
219- if ! isAtLeastVersion (req .Image , 8 ) {
220- req .Env ["xpack.security.enabled" ] = "true"
194+ if _ , ok := req .Env [envPassword ]; ! ok {
195+ req .Env [envPassword ] = settings .Password
196+ }
197+
198+ // major version 8 is secure by default and does not need this to enable authentication
199+ if ! isAtLeastVersion (req .Image , 8 ) {
200+ req .Env ["xpack.security.enabled" ] = "true"
201+ }
221202 }
222- }
223203
224- return nil
204+ return nil
205+ }
225206}
226207
227208// configureJvmOpts sets the default memory of the Elasticsearch instance to 2GB.
228209// This functions, which is only available since version 7, is called as a post create hook
229210// for the container request.
230- func configureJvmOpts (ctx context.Context , container testcontainers.Container ) error {
231- // Sets default memory of elasticsearch instance to 2GB
232- defaultJVMOpts := `-Xms2G
211+ func configureJvmOpts () testcontainers.CustomizeRequestOption {
212+ return func (req * testcontainers.GenericContainerRequest ) error {
213+ if ! isAtLeastVersion (req .Image , 7 ) {
214+ return nil
215+ }
216+
217+ return testcontainers .WithAdditionalLifecycleHooks (
218+ testcontainers.ContainerLifecycleHooks {
219+ PostCreates : []testcontainers.ContainerHook {
220+ func (ctx context.Context , container testcontainers.Container ) error {
221+ // Sets default memory of elasticsearch instance to 2GB
222+ defaultJVMOpts := `-Xms2G
233223-Xmx2G
234224-Dingest.geoip.downloader.enabled.default=false
235225`
236226
237- tmpDir := os .TempDir ()
238-
239- tmpFile , err := os .CreateTemp (tmpDir , "elasticsearch-default-memory-vm.options" )
240- if err != nil {
241- return err
242- }
243- defer os .Remove (tmpFile .Name ()) // clean up
244-
245- if _ , err := tmpFile .WriteString (defaultJVMOpts ); err != nil {
246- return err
247- }
248-
249- // Spaces are deliberate to allow user to define additional jvm options as elasticsearch resolves option files lexicographically
250- if err := container .CopyFileToContainer (
251- ctx , tmpFile .Name (),
252- "/usr/share/elasticsearch/config/jvm.options.d/ elasticsearch-default-memory-vm.options" , 0o644 ); err != nil {
253- return err
227+ tmpDir := os .TempDir ()
228+
229+ // The temp file is closed to not leak a file descriptor.
230+ tmpFile , err := os .CreateTemp (tmpDir , "elasticsearch-default-memory-vm.options" )
231+ if err != nil {
232+ return err
233+ }
234+ defer os .Remove (tmpFile .Name ()) // clean up
235+
236+ if _ , err := tmpFile .WriteString (defaultJVMOpts ); err != nil {
237+ if cerr := tmpFile .Close (); cerr != nil {
238+ return errors .Join (err , cerr )
239+ }
240+ return err
241+ }
242+
243+ if err := tmpFile .Close (); err != nil {
244+ return err
245+ }
246+
247+ // Spaces are deliberate to allow user to define additional jvm options as elasticsearch resolves option files lexicographically
248+ if err := container .CopyFileToContainer (
249+ ctx , tmpFile .Name (),
250+ "/usr/share/elasticsearch/config/jvm.options.d/ elasticsearch-default-memory-vm.options" , 0o644 ); err != nil {
251+ return err
252+ }
253+
254+ return nil
255+ },
256+ },
257+ },
258+ )(req )
254259 }
255-
256- return nil
257260}
0 commit comments