1
1
require "java-properties"
2
2
3
3
module Testcontainers
4
+
4
5
# The DockerContainer class is used to manage Docker containers.
5
6
# It provides an interface to create, start, stop, and manipulate containers
6
7
# using the Docker API.
@@ -21,8 +22,22 @@ module Testcontainers
21
22
# @attr_reader _container [Docker::Container, nil] the underlying Docker::Container object
22
23
# @attr_reader _id [String, nil] the container's ID
23
24
class DockerContainer
25
+ class << self
26
+ def setup_docker
27
+ expanded_path = File . expand_path ( "~/.testcontainers.properties" )
28
+
29
+ properties = File . exist? ( expanded_path ) ? JavaProperties . load ( expanded_path ) : { }
30
+
31
+ tc_host = ENV [ "TESTCONTAINERS_HOST" ] || properties [ :"tc.host" ]
32
+
33
+ if tc_host && !tc_host . empty?
34
+ Docker . url = tc_host
35
+ end
36
+ end
37
+ end
38
+
24
39
attr_accessor :name , :image , :command , :entrypoint , :exposed_ports , :port_bindings , :volumes , :filesystem_binds ,
25
- :env , :labels , :working_dir , :healthcheck , :wait_for
40
+ :env , :labels , :working_dir , :healthcheck , :wait_for , :aliases , :network
26
41
attr_accessor :logger
27
42
attr_reader :_container , :_id
28
43
@@ -39,9 +54,11 @@ class DockerContainer
39
54
# @param env [Array<String>, Hash, nil] an array or a hash of environment variables for the container in the format KEY=VALUE
40
55
# @param labels [Hash, nil] a hash of labels to be applied to the container
41
56
# @param working_dir [String, nil] the working directory for the container
57
+ # @param network [Testcontainers::Network, nil] the network to attach the container to
58
+ # @param aliases [Array<String>, nil] the aliases for the container in the network
42
59
# @param logger [Logger] a logger instance for the container
43
60
def initialize ( image , name : nil , command : nil , entrypoint : nil , exposed_ports : nil , image_create_options : { } , port_bindings : nil , volumes : nil , filesystem_binds : nil ,
44
- env : nil , labels : nil , working_dir : nil , healthcheck : nil , wait_for : nil , logger : Testcontainers . logger )
61
+ env : nil , labels : nil , working_dir : nil , healthcheck : nil , wait_for : nil , network : nil , aliases : nil , logger : Testcontainers . logger )
45
62
46
63
@image = image
47
64
@name = name
@@ -61,6 +78,8 @@ def initialize(image, name: nil, command: nil, entrypoint: nil, exposed_ports: n
61
78
@_container = nil
62
79
@_id = nil
63
80
@_created_at = nil
81
+ @aliases = aliases
82
+ @network = network
64
83
end
65
84
66
85
# Add environment variables to the container configuration.
@@ -87,7 +106,7 @@ def add_exposed_port(port)
87
106
@exposed_ports ||= { }
88
107
@port_bindings ||= { }
89
108
@exposed_ports [ port ] ||= { }
90
- @port_bindings [ port ] ||= [ { "HostPort" => "" } ]
109
+ @port_bindings [ port ] ||= [ { "HostPort" => "" } ]
91
110
@exposed_ports
92
111
end
93
112
@@ -119,7 +138,7 @@ def add_fixed_exposed_port(container_port, host_port = nil)
119
138
@exposed_ports ||= { }
120
139
@port_bindings ||= { }
121
140
@exposed_ports [ container_port ] = { }
122
- @port_bindings [ container_port ] = [ { "HostPort" => host_port . to_s } ]
141
+ @port_bindings [ container_port ] = [ { "HostPort" => host_port . to_s } ]
123
142
@port_bindings
124
143
end
125
144
@@ -229,7 +248,7 @@ def add_healthcheck(options = {})
229
248
test = options [ :test ]
230
249
231
250
if test . nil?
232
- @healthcheck = { "Test" => [ "NONE" ] }
251
+ @healthcheck = { "Test" => [ "NONE" ] }
233
252
return @healthcheck
234
253
end
235
254
@@ -457,6 +476,34 @@ def with_wait_for(method = nil, *args, **kwargs, &block)
457
476
self
458
477
end
459
478
479
+ # Returns the container's ID.
480
+ #
481
+ def id
482
+ @_id
483
+ end
484
+
485
+ # Returns the container's aliases within the network.
486
+ #
487
+ def aliases
488
+ @aliases ||= [ ]
489
+ end
490
+
491
+ # Sets the container's network.
492
+ #
493
+ # @param network [Testcontainers::Network] The network to attach the container to.
494
+ def with_network ( network )
495
+ @network = network
496
+ self
497
+ end
498
+
499
+ # Sets the container's network aliases.
500
+ #
501
+ # @param aliases [Array<String>] The aliases for the container in the network.
502
+ def with_network_aliases ( *aliases )
503
+ self . aliases += aliases &.flatten
504
+ self
505
+ end
506
+
460
507
# Starts the container, yields the container instance to the block, and stops the container.
461
508
#
462
509
# @yield [DockerContainer] The container instance.
@@ -474,19 +521,13 @@ def use
474
521
# @raise [ConnectionError] If the connection to the Docker daemon fails.
475
522
# @raise [NotFoundError] If Docker is unable to find the image.
476
523
def start
477
- expanded_path = File . expand_path ( "~/.testcontainers.properties" )
478
-
479
- properties = File . exist? ( expanded_path ) ? JavaProperties . load ( expanded_path ) : { }
480
-
481
- tc_host = ENV [ "TESTCONTAINERS_HOST" ] || properties [ :"tc.host" ]
482
-
483
- if tc_host && !tc_host . empty?
484
- Docker . url = tc_host
485
- end
524
+ self . class . setup_docker
486
525
487
526
connection = Docker ::Connection . new ( Docker . url , Docker . options )
488
527
489
- Docker ::Image . create ( { "fromImage" => @image } . merge ( @image_create_options ) , connection )
528
+ @network &.create
529
+
530
+ Docker ::Image . create ( { "fromImage" => @image } . merge ( @image_create_options ) , connection )
490
531
491
532
@_container ||= Docker ::Container . create ( _container_create_options )
492
533
@_container . start
@@ -516,6 +557,7 @@ def start
516
557
def stop ( force : false )
517
558
raise ContainerNotStartedError unless @_container
518
559
@_container . stop ( force : force )
560
+ @network &.close
519
561
self
520
562
rescue Excon ::Error ::Socket => e
521
563
raise ConnectionError , e . message
@@ -1026,7 +1068,7 @@ def normalize_port_bindings(port_bindings)
1026
1068
return port_bindings if port_bindings . is_a? ( Hash ) && port_bindings . values . all? { |v | v . is_a? ( Array ) }
1027
1069
1028
1070
port_bindings . each_with_object ( { } ) do |( container_port , host_port ) , hash |
1029
- hash [ normalize_port ( container_port ) ] = [ { "HostPort" => host_port . to_s } ]
1071
+ hash [ normalize_port ( container_port ) ] = [ { "HostPort" => host_port . to_s } ]
1030
1072
end
1031
1073
end
1032
1074
@@ -1126,6 +1168,10 @@ def docker_host
1126
1168
nil
1127
1169
end
1128
1170
1171
+ def network_name
1172
+ @network_name ||= network &.name
1173
+ end
1174
+
1129
1175
def _container_create_options
1130
1176
{
1131
1177
"name" => @name ,
@@ -1139,11 +1185,24 @@ def _container_create_options
1139
1185
"WorkingDir" => @working_dir ,
1140
1186
"Healthcheck" => @healthcheck ,
1141
1187
"HostConfig" => {
1188
+ "NetworkMode" => network_name ,
1142
1189
"PortBindings" => @port_bindings ,
1143
1190
"Binds" => @filesystem_binds
1144
- } . compact
1191
+ } . compact ,
1192
+ "NetworkingConfig" : _networking_config
1145
1193
} . compact
1146
1194
end
1195
+
1196
+ def _networking_config
1197
+ return nil unless network_name && !aliases &.empty?
1198
+ {
1199
+ "EndpointsConfig" => {
1200
+ network_name => {
1201
+ "Aliases" => aliases
1202
+ }
1203
+ }
1204
+ }
1205
+ end
1147
1206
end
1148
1207
1149
1208
# Alias for forward-compatibility
0 commit comments