testcontainers-core 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 584f5aabce655fd2f113af37af6ce59cc8b3e0f6aa4b276bff52378e1d3c5e9d
4
- data.tar.gz: 0a3c73c6a6ba495c61b3ded68092d4a58a06099e1ff543d7ac2a45e9503082a6
3
+ metadata.gz: a5422789f7d26555fc9198f71b189ec3f75beba0dbaed0b66f541ef5513a9819
4
+ data.tar.gz: abd82854569df36e3581edc1eb6460af76f2cb3b570ec711131f24f351568f56
5
5
  SHA512:
6
- metadata.gz: 18f526847480a5cec71aa4abf2b10393fa7a08ccf5c4f70a07c18667624a8fb49ca3f0d9d241f0f5dd9e32404f3e0e06fc2cb67465059703d5e9eec143c04f4e
7
- data.tar.gz: fcc6793d0bbdf773de7e0cb5933364f45840c034c8821dd12ab99c17d08a05c23967d2b992d4152b2c7298eae12d75d751eaff8e83e21563a060647cdcbd1b67
6
+ metadata.gz: 7eebf9df18f1599e5a13928f163a5269b5b0e78297447ff04396db804bfe7f0ff2515b16da13180e504905bf81d533d9528d76bae8f52d2d55d4823d69aed209
7
+ data.tar.gz: 97461a668a41d5efb9c3c88cac46d2f4f308a5975bff34042bb78a7edf41b66a2f2a3426ffa8a39baa0efe6b70bc8949306e8372d40dc35a80e4ecd12b2e0574
data/CHANGELOG.md CHANGED
@@ -1,6 +1,35 @@
1
+ ## [0.2.0] - 2024-02-08
2
+
3
+ ### Added
4
+
5
+ - DockerContainer#new now accepts optional keyword argument `image_create_options` which accepts a hash. Passes the options to `Docker::Image.create`. See the [Docker ImageCreate api](https://docs.docker.com/engine/api/v1.43/#tag/Image/operation/ImageCreate) for available parameters.
6
+
7
+ - DockerContainer#remove now accepts an optional options hash. See the [Docker ContainerDelete api](https://docs.docker.com/engine/api/v1.43/#tag/Container/operation/ContainerDelete) for available parameters.
8
+
9
+ ## [0.1.3] - 2023-06-10
10
+
11
+ ### Added
12
+
13
+ - Support for entrypoint customization and the DockerContainer#with_entrypoint method
14
+
15
+ - Methods to read/write strings from and to containers: read_file, store_file
16
+
17
+ - Methods to copy files from and to containers: copy_file_from_container, copy_file_to_container
18
+
19
+ - Support for waiting strategies on start
20
+
21
+ - DockerContainer#with_exposed_port (singular) for convenience
22
+
23
+ - GenericContainer as alias for DockerContainer
24
+
25
+ ### Fixed
26
+
27
+ - DockerContainer#add_exposed_ports don't override PortBinding settings added by #add_fixed_exposed_port
28
+
29
+
1
30
  ## [0.1.2] - 2023-05-13
2
31
 
3
- ## Added
32
+ ### Added
4
33
 
5
34
  - DockerContainer#first_mapped_port method returns the first of the
6
35
  mapped ports for convenience.
@@ -13,11 +42,11 @@
13
42
 
14
43
  redis_container.with_healthcheck(test: ["redis-cli ping"], interval: 30, timeout: 30, retries: 3)
15
44
 
16
- ## Changed
45
+ ### Changed
17
46
 
18
47
  - DockerContainer#mapped_port(port) method now returns an Integer instead of a String.
19
48
 
20
- ## Fixed
49
+ ### Fixed
21
50
 
22
51
  - Links to the GitHub project on the README.md file are fixed.
23
52
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- testcontainers-core (0.1.1)
4
+ testcontainers-core (0.2.0)
5
5
  docker-api (~> 2.2)
6
6
 
7
7
  GEM
@@ -57,6 +57,7 @@ GEM
57
57
 
58
58
  PLATFORMS
59
59
  arm64-darwin-21
60
+ ruby
60
61
  x86_64-linux
61
62
 
62
63
  DEPENDENCIES
data/README.md CHANGED
@@ -28,7 +28,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
28
28
 
29
29
  ## Contributing
30
30
 
31
- Bug reports and pull requests are welcome on GitHub at https://github.com/guilleiguaran/testcontainers. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/guilleiguaran/testcontainers/blob/main/CODE_OF_CONDUCT.md).
31
+ Bug reports and pull requests are welcome on GitHub at https://github.com/testcontainers/testcontainers-ruby. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/testcontainers/testcontainers-ruby/blob/main/CODE_OF_CONDUCT.md).
32
32
 
33
33
  ## License
34
34
 
@@ -36,4 +36,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
36
36
 
37
37
  ## Code of Conduct
38
38
 
39
- Everyone interacting in the Testcontainers project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/guilleiguaran/testcontainers/blob/main/CODE_OF_CONDUCT.md).
39
+ Everyone interacting in the Testcontainers project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/testcontainers/testcontainers-ruby/blob/main/CODE_OF_CONDUCT.md).
@@ -6,6 +6,7 @@ module Testcontainers
6
6
  # @attr name [String] the container's name
7
7
  # @attr image [String] the container's image name
8
8
  # @attr command [Array<String>, nil] the command to run in the container
9
+ # @attr entrypoint [Array<String>, nil] the entrypoint to run in the container
9
10
  # @attr exposed_ports [Hash, nil] a hash mapping exposed container ports to an empty hash (used for Docker API compatibility)
10
11
  # @attr port_bindings [Hash, nil] a hash mapping container ports to host port bindings (used for Docker API compatibility)
11
12
  # @attr volumes [Hash, nil] a hash mapping volume paths in the container to an empty hash (used for Docker API compatibility)
@@ -18,7 +19,8 @@ module Testcontainers
18
19
  # @attr_reader _container [Docker::Container, nil] the underlying Docker::Container object
19
20
  # @attr_reader _id [String, nil] the container's ID
20
21
  class DockerContainer
21
- attr_accessor :name, :image, :command, :exposed_ports, :port_bindings, :volumes, :filesystem_binds, :env, :labels, :working_dir, :healthcheck
22
+ attr_accessor :name, :image, :command, :entrypoint, :exposed_ports, :port_bindings, :volumes, :filesystem_binds,
23
+ :env, :labels, :working_dir, :healthcheck, :wait_for
22
24
  attr_accessor :logger
23
25
  attr_reader :_container, :_id
24
26
 
@@ -28,6 +30,7 @@ module Testcontainers
28
30
  # @param command [Array<String>, nil] the command to run in the container
29
31
  # @param name [String, nil] the container's name
30
32
  # @param exposed_ports [Hash, Array<String>, nil] a hash or an array of exposed container ports
33
+ # @param image_create_options [Hash] a hash of options to pass to Docker::Image.create.
31
34
  # @param port_bindings [Hash, Array<String>, nil] a hash or an array of container ports to host port bindings
32
35
  # @param volumes [Hash, Array<String>, nil] a hash or an array of volume paths in the container
33
36
  # @param filesystem_binds [Array<String>, Hash, nil] an array of strings or a hash representing bind mounts from the host to the container
@@ -35,13 +38,15 @@ module Testcontainers
35
38
  # @param labels [Hash, nil] a hash of labels to be applied to the container
36
39
  # @param working_dir [String, nil] the working directory for the container
37
40
  # @param logger [Logger] a logger instance for the container
38
- def initialize(image, command: nil, name: nil, exposed_ports: nil, port_bindings: nil, volumes: nil, filesystem_binds: nil, env: nil,
39
- labels: nil, working_dir: nil, healthcheck: nil, logger: Testcontainers.logger)
41
+ def initialize(image, name: nil, command: nil, entrypoint: nil, exposed_ports: nil, image_create_options: {}, port_bindings: nil, volumes: nil, filesystem_binds: nil,
42
+ env: nil, labels: nil, working_dir: nil, healthcheck: nil, wait_for: nil, logger: Testcontainers.logger)
40
43
 
41
44
  @image = image
42
- @command = command
43
45
  @name = name
46
+ @command = command
47
+ @entrypoint = entrypoint
44
48
  @exposed_ports = add_exposed_ports(exposed_ports) if exposed_ports
49
+ @image_create_options = image_create_options
45
50
  @port_bindings = add_fixed_exposed_ports(port_bindings) if port_bindings
46
51
  @volumes = add_volumes(volumes) if volumes
47
52
  @env = add_env(env) if env
@@ -49,6 +54,7 @@ module Testcontainers
49
54
  @labels = add_labels(labels) if labels
50
55
  @working_dir = working_dir
51
56
  @healthcheck = add_healthcheck(healthcheck) if healthcheck
57
+ @wait_for = add_wait_for(wait_for)
52
58
  @logger = logger
53
59
  @_container = nil
54
60
  @_id = nil
@@ -78,8 +84,8 @@ module Testcontainers
78
84
  port = normalize_port(port)
79
85
  @exposed_ports ||= {}
80
86
  @port_bindings ||= {}
81
- @exposed_ports[port] = {}
82
- @port_bindings[port] = [{"HostPort" => ""}]
87
+ @exposed_ports[port] ||= {}
88
+ @port_bindings[port] ||= [{"HostPort" => ""}]
83
89
  @exposed_ports
84
90
  end
85
91
 
@@ -242,6 +248,51 @@ module Testcontainers
242
248
  }
243
249
  end
244
250
 
251
+ # Add a wait_for strategy to the container configuration.
252
+ #
253
+ # @param method [Symbol, String, Proc, Array] The method to call on the container to wait for it to be ready.
254
+ # @param args [Array] The arguments to pass to the method if it is a symbol or string.
255
+ # @param kwargs [Hash] The keyword arguments to pass to the method if it is a symbol or string.
256
+ # @param block [Proc] The block to call on the container to wait for it to be ready.
257
+ # @return [Proc] The wait_for strategy.
258
+ def add_wait_for(method = nil, *args, **kwargs, &block)
259
+ if method.nil?
260
+ if block
261
+ if block.arity == 1
262
+ @wait_for = block
263
+ else
264
+ raise ArgumentError, "Invalid wait_for block: #{block}"
265
+ end
266
+ elsif @exposed_ports && !@exposed_ports.empty?
267
+ port = @exposed_ports.keys.first.split("/").first
268
+ @wait_for = ->(container) { container.wait_for_tcp_port(port) }
269
+ end
270
+ elsif method.is_a?(Proc)
271
+ if method.arity == 1
272
+ @wait_for = method
273
+ else
274
+ raise ArgumentError, "Invalid wait_for method: #{method}"
275
+ end
276
+ elsif method.is_a?(Array)
277
+ method_name = "wait_for_#{method[0]}".to_sym
278
+ args = method[1] || []
279
+ kwargs = method[2] || {}
280
+ if respond_to?(method_name)
281
+ @wait_for = ->(container) { container.send(method_name, *args, **kwargs) }
282
+ else
283
+ raise ArgumentError, "Invalid wait_for method: #{method_name}"
284
+ end
285
+ else
286
+ method_name = "wait_for_#{method}".to_sym
287
+ if respond_to?(method_name)
288
+ @wait_for = ->(container) { container.send(method_name, *args, **kwargs) }
289
+ else
290
+ raise ArgumentError, "Invalid wait_for method: #{method_name}"
291
+ end
292
+ end
293
+ @wait_for
294
+ end
295
+
245
296
  # Set options for the container configuration using "with_" methods.
246
297
  #
247
298
  # @param options [Hash] A hash of options where keys correspond to "with_" methods and values are the arguments for those methods.
@@ -269,6 +320,16 @@ module Testcontainers
269
320
  self
270
321
  end
271
322
 
323
+ # Set the entrypoint for the container.
324
+ #
325
+ # @param parts [Array<String>] The entry point for the container as an array of strings.
326
+ # @return [DockerContainer] The updated DockerContainer instance.
327
+ def with_entrypoint(*parts)
328
+ @entrypoint = parts.first.is_a?(Array) ? parts.first : parts
329
+
330
+ self
331
+ end
332
+
272
333
  # Set the name of the container.
273
334
  #
274
335
  # @param name [String] The name of the container.
@@ -309,6 +370,15 @@ module Testcontainers
309
370
  self
310
371
  end
311
372
 
373
+ # Adds a single exposed port to the container.
374
+ #
375
+ # @param port [String, Integer] The port to expose.
376
+ # @return [DockerContainer] The updated DockerContainer instance.
377
+ def with_exposed_port(port)
378
+ add_exposed_ports(port)
379
+ self
380
+ end
381
+
312
382
  # Adds a fixed exposed port to the container.
313
383
  #
314
384
  # @param container_port [String, Integer, Hash] The container port in the format "port/protocol" or as an integer.
@@ -373,6 +443,18 @@ module Testcontainers
373
443
  self
374
444
  end
375
445
 
446
+ # Add a wait_for strategy to the container configuration.
447
+ #
448
+ # @param method [Symbol, String, Proc, Array] The method to call on the container to wait for it to be ready.
449
+ # @param args [Array] The arguments to pass to the method if it is a symbol or string.
450
+ # @param kwargs [Hash] The keyword arguments to pass to the method if it is a symbol or string.
451
+ # @param block [Proc] The block to call on the container to wait for it to be ready.
452
+ # @return [DockerContainer] The updated DockerContainer instance.
453
+ def with_wait_for(method = nil, *args, **kwargs, &block)
454
+ add_wait_for(method, *args, **kwargs, &block)
455
+ self
456
+ end
457
+
376
458
  # Starts the container, yields the container instance to the block, and stops the container.
377
459
  #
378
460
  # @yield [DockerContainer] The container instance.
@@ -388,19 +470,23 @@ module Testcontainers
388
470
  #
389
471
  # @return [DockerContainer] The DockerContainer instance.
390
472
  # @raise [ConnectionError] If the connection to the Docker daemon fails.
473
+ # @raise [NotFoundError] If Docker is unable to find the image.
391
474
  def start
392
- Docker::Image.create("fromImage" => @image)
475
+ Docker::Image.create({"fromImage" => @image}.merge(@image_create_options))
393
476
 
394
477
  @_container ||= Docker::Container.create(_container_create_options)
395
478
  @_container.start
396
479
 
397
480
  @_id = @_container.id
398
481
  json = @_container.json
399
-
400
482
  @name = json["Name"]
401
483
  @_created_at = json["Created"]
402
484
 
485
+ @wait_for&.call(self)
486
+
403
487
  self
488
+ rescue Docker::Error::NotFoundError => e
489
+ raise NotFoundError, e.message
404
490
  rescue Excon::Error::Socket => e
405
491
  raise ConnectionError, e.message
406
492
  end
@@ -449,11 +535,12 @@ module Testcontainers
449
535
 
450
536
  # Removes the container.
451
537
  #
538
+ # @param options [Hash] Additional options to send to the container remove command.
452
539
  # @return [DockerContainer] The DockerContainer instance.
453
540
  # @return [nil] If the container does not exist.
454
541
  # @raise [ConnectionError] If the connection to the Docker daemon fails.
455
- def remove
456
- @_container&.remove
542
+ def remove(options = {})
543
+ @_container&.remove(options)
457
544
  @_container = nil
458
545
  self
459
546
  rescue Excon::Error::Socket => e
@@ -651,6 +738,24 @@ module Testcontainers
651
738
  container_ports.map { |port| mapped_port(port) }.first
652
739
  end
653
740
 
741
+ # Returns the container's mounts.
742
+ #
743
+ # @return [Array<Hash>] An array of the container's mounts.
744
+ # @raise [ConnectionError] If the connection to the Docker daemon fails.
745
+ # @raise [ContainerNotStartedError] If the container has not been started.
746
+ def mounts
747
+ info["Mounts"]
748
+ end
749
+
750
+ # Returns the container's mount names.
751
+ #
752
+ # @return [Array<String>] The container's mount names.
753
+ # @raise [ConnectionError] If the connection to the Docker daemon fails.
754
+ # @raise [ContainerNotStartedError] If the container has not been started.
755
+ def mount_names
756
+ mounts.map { |mount| mount["Name"] }
757
+ end
758
+
654
759
  # Returns the value for the given environment variable.
655
760
  #
656
761
  # @param key [String] The environment variable's key.
@@ -810,6 +915,81 @@ module Testcontainers
810
915
  File.exist?("/.dockerenv")
811
916
  end
812
917
 
918
+ # Copies a IO object or a file from the host to the container.
919
+ #
920
+ # @param container_path [String] The path to the file inside the container.
921
+ # @param host_path_or_io [String, IO] The path to the file on the host or a IO object.
922
+ # @raise [ContainerNotStartedError] If the container has not been started.
923
+ # @raise [ConnectionError] If the connection to the Docker daemon fails.
924
+ # @return [self]
925
+ def copy_file_to_container(container_path, host_path_or_io)
926
+ raise ContainerNotStartedError, "Container has not been started" unless running?
927
+ raise ArgumentError, "Container path must be a non-empty string" if container_path.to_s.empty?
928
+
929
+ begin
930
+ io = host_path_or_io.is_a?(String) ? File.open(host_path_or_io) : host_path_or_io
931
+ io.rewind if io.pos != 0
932
+ store_file(container_path, io.read)
933
+ io.rewind
934
+ rescue => e
935
+ puts "Error while copying file to container: #{e.message}"
936
+ return false
937
+ ensure
938
+ io.close if io.respond_to?(:close)
939
+ end
940
+
941
+ true
942
+ end
943
+
944
+ # Copies a file from the container to the host.
945
+ #
946
+ # @param container_path [String] The path to the file inside the container.
947
+ # @param host_path_or_io [String, IO] The path to the file on the host or a IO object.
948
+ # @raise [ContainerNotStartedError] If the container has not been started.
949
+ # @raise [ConnectionError] If the connection to the Docker daemon fails.
950
+ # @return [String] The contents of the file inside the container.
951
+ def copy_file_from_container(container_path, host_path_or_io)
952
+ raise ContainerNotStartedError, "Container has not been started" unless running?
953
+ raise ArgumentError, "Container path must be a non-empty string" if container_path.to_s.empty?
954
+
955
+ begin
956
+ io = host_path_or_io.is_a?(String) ? File.open(host_path_or_io, "w") : host_path_or_io
957
+ io.rewind if io.pos != 0
958
+ content = read_file(container_path)
959
+ io.write(content)
960
+ io.rewind
961
+ rescue => e
962
+ puts "Error while copying file from container: #{e.message}"
963
+ raise e # Optionally re-raise the exception or handle it according to your needs
964
+ ensure
965
+ io.close if io.respond_to?(:close)
966
+ end
967
+
968
+ content
969
+ end
970
+
971
+ # Reads the contents of a file inside the container.
972
+ #
973
+ # @param path [String] The path to the file.
974
+ # @return [String] The contents of the file.
975
+ def read_file(path)
976
+ raise ContainerNotStartedError unless @_container
977
+
978
+ @_container.read_file(path)
979
+ end
980
+
981
+ # Writes the contents of a file inside the container.
982
+ #
983
+ # @param path [String] The path to the file.
984
+ # @param contents [String] The contents of the file.
985
+ # @raise [ContainerNotStartedError] If the container has not been started.
986
+ # @raise [ConnectionError] If the connection to the Docker daemon fails.
987
+ def store_file(path, contents)
988
+ raise ContainerNotStartedError unless @_container
989
+
990
+ @_container.store_file(path, contents)
991
+ end
992
+
813
993
  private
814
994
 
815
995
  def normalize_ports(ports)
@@ -937,6 +1117,7 @@ module Testcontainers
937
1117
  "name" => @name,
938
1118
  "Image" => @image,
939
1119
  "Cmd" => @command,
1120
+ "Entrypoint" => @entrypoint,
940
1121
  "ExposedPorts" => @exposed_ports,
941
1122
  "Volumes" => @volumes,
942
1123
  "Env" => @env,
@@ -950,4 +1131,7 @@ module Testcontainers
950
1131
  }.compact
951
1132
  end
952
1133
  end
1134
+
1135
+ # Alias for forward-compatibility
1136
+ GenericContainer = DockerContainer
953
1137
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Testcontainers
4
- VERSION = "0.1.2"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -12,6 +12,8 @@ module Testcontainers
12
12
 
13
13
  class ConnectionError < Error; end
14
14
 
15
+ class NotFoundError < Error; end
16
+
15
17
  class TimeoutError < Error; end
16
18
 
17
19
  class ContainerNotStartedError < Error; end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: testcontainers-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Guillermo Iguaran
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-05-13 00:00:00.000000000 Z
11
+ date: 2024-02-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: docker-api
@@ -98,13 +98,13 @@ files:
98
98
  - lib/testcontainers.rb
99
99
  - lib/testcontainers/docker_container.rb
100
100
  - lib/testcontainers/version.rb
101
- homepage: https://github.com/guilleiguaran/testcontainers-ruby
101
+ homepage: https://github.com/testcontainers/testcontainers-ruby
102
102
  licenses:
103
103
  - MIT
104
104
  metadata:
105
- homepage_uri: https://github.com/guilleiguaran/testcontainers-ruby
106
- source_code_uri: https://github.com/guilleiguaran/testcontainers-ruby
107
- changelog_uri: https://github.com/guilleiguaran/testcontainers-ruby/blob/main/core/CHANGELOG.md
105
+ homepage_uri: https://github.com/testcontainers/testcontainers-ruby
106
+ source_code_uri: https://github.com/testcontainers/testcontainers-ruby
107
+ changelog_uri: https://github.com/testcontainers/testcontainers-ruby/blob/main/core/CHANGELOG.md
108
108
  post_install_message:
109
109
  rdoc_options: []
110
110
  require_paths:
@@ -120,7 +120,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
120
120
  - !ruby/object:Gem::Version
121
121
  version: '0'
122
122
  requirements: []
123
- rubygems_version: 3.4.1
123
+ rubygems_version: 3.4.14
124
124
  signing_key:
125
125
  specification_version: 4
126
126
  summary: Testcontainers for Ruby.