testcontainers-core 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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.