@@ -2,6 +2,8 @@ package elasticsearch
22
33import (
44 "context"
5+ "crypto/tls"
6+ "crypto/x509"
57 "fmt"
68 "io"
79 "os"
@@ -15,6 +17,7 @@ const (
1517 defaultTCPPort = "9300"
1618 defaultPassword = "changeme"
1719 defaultUsername = "elastic"
20+ defaultCaCertPath = "/usr/share/elasticsearch/config/certs/http_ca.crt"
1821 minimalImageVersion = "7.9.2"
1922)
2023
@@ -32,7 +35,7 @@ type ElasticsearchContainer struct {
3235}
3336
3437// Deprecated: use Run instead
35- // RunContainer creates an instance of the Couchbase container type
38+ // RunContainer creates an instance of the Elasticsearch container type
3639func RunContainer (ctx context.Context , opts ... testcontainers.ContainerCustomizer ) (* ElasticsearchContainer , error ) {
3740 return Run (ctx , "docker.elastic.co/elasticsearch/elasticsearch:7.9.2" , opts ... )
3841}
@@ -50,54 +53,41 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom
5053 defaultHTTPPort + "/tcp" ,
5154 defaultTCPPort + "/tcp" ,
5255 },
53- // regex that
54- // matches 8.3 JSON logging with started message and some follow up content within the message field
55- // matches 8.0 JSON logging with no whitespace between message field and content
56- // matches 7.x JSON logging with whitespace between message field and content
57- // matches 6.x text logging with node name in brackets and just a 'started' message till the end of the line
58- WaitingFor : wait .ForLog (`.*("message":\s?"started(\s|")?.*|]\sstarted\n)` ).AsRegexp (),
59- LifecycleHooks : []testcontainers.ContainerLifecycleHooks {
60- {
61- // the container needs a post create hook to set the default JVM options in a file
62- PostCreates : []testcontainers.ContainerHook {},
63- PostReadies : []testcontainers.ContainerHook {},
64- },
65- },
6656 },
6757 Started : true ,
6858 }
6959
7060 // Gather all config options (defaults and then apply provided options)
71- settings := defaultOptions ()
61+ options := defaultOptions ()
7262 for _ , opt := range opts {
7363 if apply , ok := opt .(Option ); ok {
74- apply (settings )
64+ apply (options )
7565 }
7666 if err := opt .Customize (& req ); err != nil {
7767 return nil , err
7868 }
7969 }
8070
81- // Transfer the certificate settings to the container request
82- err := configureCertificate (settings , & req )
83- if err != nil {
84- return nil , err
85- }
86-
8771 // Transfer the password settings to the container request
88- err = configurePassword (settings , & req )
89- if err != nil {
72+ if err := configurePassword (options , & req ); err != nil {
9073 return nil , err
9174 }
9275
9376 if isAtLeastVersion (req .Image , 7 ) {
94- req .LifecycleHooks [0 ].PostCreates = append (req .LifecycleHooks [0 ].PostCreates , configureJvmOpts )
77+ req .LifecycleHooks = append (req .LifecycleHooks ,
78+ testcontainers.ContainerLifecycleHooks {
79+ PostCreates : []testcontainers.ContainerHook {configureJvmOpts },
80+ },
81+ )
9582 }
9683
84+ // Set the default waiting strategy if not already set.
85+ setWaitFor (options , & req .ContainerRequest )
86+
9787 container , err := testcontainers .GenericContainer (ctx , req )
9888 var esContainer * ElasticsearchContainer
9989 if container != nil {
100- esContainer = & ElasticsearchContainer {Container : container , Settings : * settings }
90+ esContainer = & ElasticsearchContainer {Container : container , Settings : * options }
10191 }
10292 if err != nil {
10393 return esContainer , fmt .Errorf ("generic container: %w" , err )
@@ -110,6 +100,61 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom
110100 return esContainer , nil
111101}
112102
103+ // certWriter is a helper that writes the details of a CA cert to options.
104+ type certWriter struct {
105+ options * Options
106+ certPool * x509.CertPool
107+ }
108+
109+ // Read reads the CA cert from the reader and appends it to the options.
110+ func (w * certWriter ) Read (r io.Reader ) error {
111+ buf , err := io .ReadAll (r )
112+ if err != nil {
113+ return fmt .Errorf ("read CA cert: %w" , err )
114+ }
115+
116+ w .options .CACert = buf
117+ w .certPool .AppendCertsFromPEM (w .options .CACert )
118+
119+ return nil
120+ }
121+
122+ // setWaitFor sets the req.WaitingFor strategy based on settings.
123+ func setWaitFor (options * Options , req * testcontainers.ContainerRequest ) {
124+ var strategies []wait.Strategy
125+ if req .WaitingFor != nil {
126+ // Custom waiting strategy, ensure we honour it.
127+ strategies = append (strategies , req .WaitingFor )
128+ }
129+
130+ waitHTTP := wait .ForHTTP ("/" ).WithPort (defaultHTTPPort )
131+ if sslRequired (req ) {
132+ waitHTTP = waitHTTP .WithTLS (true ).WithAllowInsecure (true )
133+ cw := & certWriter {
134+ options : options ,
135+ certPool : x509 .NewCertPool (),
136+ }
137+
138+ waitHTTP = waitHTTP .
139+ WithTLS (true , & tls.Config {RootCAs : cw .certPool })
140+
141+ strategies = append (strategies , wait .ForFile (defaultCaCertPath ).WithMatcher (cw .Read ))
142+ }
143+
144+ if options .Password != "" || options .Username != "" {
145+ waitHTTP = waitHTTP .WithBasicAuth (options .Username , options .Password )
146+ }
147+
148+ strategies = append (strategies , waitHTTP )
149+
150+ if len (strategies ) > 1 {
151+ req .WaitingFor = wait .ForAll (strategies ... )
152+ return
153+ }
154+
155+ req .WaitingFor = strategies [0 ]
156+ }
157+
113158// configureAddress sets the address of the Elasticsearch container.
114159// If the certificate is set, it will use https as protocol, otherwise http.
115160func (c * ElasticsearchContainer ) configureAddress (ctx context.Context ) error {
@@ -133,50 +178,28 @@ func (c *ElasticsearchContainer) configureAddress(ctx context.Context) error {
133178 return nil
134179}
135180
136- // configureCertificate transfers the certificate settings to the container request .
137- // For that, it defines a post start hook that copies the certificate from the container to the host.
138- // The certificate is only available since version 8, and will be located in a well-known location.
139- func configureCertificate ( settings * Options , req * testcontainers. GenericContainerRequest ) error {
140- if isAtLeastVersion ( req . Image , 8 ) {
141- // These configuration keys explicitly disable CA generation.
142- // If any are set we skip the file retrieval .
143- configKeys := [] string {
144- "xpack.security.enabled" ,
145- "xpack.security.http.ssl .enabled" ,
146- "xpack.security.transport .ssl.enabled" ,
147- }
148- for _ , configKey := range configKeys {
149- if value , ok := req . Env [ configKey ]; ok {
150- if value == "false" {
151- return nil
152- }
181+ // sslRequired returns true if the SSL is required, otherwise false .
182+ func sslRequired ( req * testcontainers. ContainerRequest ) bool {
183+ if ! isAtLeastVersion ( req . Image , 8 ) {
184+ return false
185+ }
186+
187+ // These configuration keys explicitly disable CA generation .
188+ // If any are set we skip the file retrieval.
189+ configKeys := [] string {
190+ "xpack.security.enabled" ,
191+ "xpack.security.http .ssl.enabled" ,
192+ "xpack.security.transport.ssl.enabled" ,
193+ }
194+ for _ , configKey := range configKeys {
195+ if value , ok := req . Env [ configKey ]; ok {
196+ if value == "false" {
197+ return false
153198 }
154199 }
155-
156- // The container needs a post ready hook to copy the certificate from the container to the host.
157- // This certificate is only available since version 8
158- req .LifecycleHooks [0 ].PostReadies = append (req .LifecycleHooks [0 ].PostReadies ,
159- func (ctx context.Context , container testcontainers.Container ) error {
160- const defaultCaCertPath = "/usr/share/elasticsearch/config/certs/http_ca.crt"
161-
162- readCloser , err := container .CopyFileFromContainer (ctx , defaultCaCertPath )
163- if err != nil {
164- return err
165- }
166-
167- // receive the bytes from the default location
168- certBytes , err := io .ReadAll (readCloser )
169- if err != nil {
170- return err
171- }
172-
173- settings .CACert = certBytes
174-
175- return nil
176- })
177200 }
178201
179- return nil
202+ return true
180203}
181204
182205// configurePassword transfers the password settings to the container request.
0 commit comments