testcontainers-compose 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f2ea689b10ff1396275d2395062aff23e77feb3edffc83ab7cecd7eb0a097d04
4
+ data.tar.gz: c995e21fa6fe265632fb397ad3b8fba2e8c50ecc6c9d667a7f0ac560aee43b3f
5
+ SHA512:
6
+ metadata.gz: 8e97904d1097e1d11ffb9d6436a107e664e826fdf150cd2eb8325478eacebbb41b066b70ab3d26c2cb736481aec95dfe830598769304ff72564d6f3caff47792
7
+ data.tar.gz: f121dfbea69d3e89f26c3593e73b30f85d82a56442b68befe7e148ddd74abbfea0080f1dda3189e5a84b08b7f956430ba6f85ba63d3d96c920dfc75ff02274d3
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [0.2.0] - 2024-02-08
2
+
3
+ ### Added
4
+
5
+ - Initial release of the Docker Compose module
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in testcontainers-compose.gemspec
6
+ gemspec
7
+
8
+ # Use the latest version of testcontainers-core from the local path
9
+ gem "testcontainers-core", path: "../core"
data/Gemfile.lock ADDED
@@ -0,0 +1,80 @@
1
+ PATH
2
+ remote: ../core
3
+ specs:
4
+ testcontainers-core (0.2.0)
5
+ docker-api (~> 2.2)
6
+
7
+ PATH
8
+ remote: .
9
+ specs:
10
+ testcontainers-compose (0.2.0)
11
+ testcontainers-core (~> 0.1)
12
+
13
+ GEM
14
+ remote: https://rubygems.org/
15
+ specs:
16
+ ast (2.4.2)
17
+ docker-api (2.2.0)
18
+ excon (>= 0.47.0)
19
+ multi_json
20
+ excon (0.100.0)
21
+ json (2.6.3)
22
+ language_server-protocol (3.17.0.3)
23
+ lint_roller (1.0.0)
24
+ minitest (5.18.0)
25
+ minitest-hooks (1.5.0)
26
+ minitest (> 5.3)
27
+ multi_json (1.15.0)
28
+ parallel (1.23.0)
29
+ parser (3.2.2.3)
30
+ ast (~> 2.4.1)
31
+ racc
32
+ racc (1.7.0)
33
+ rainbow (3.1.1)
34
+ rake (13.0.6)
35
+ regexp_parser (2.8.1)
36
+ rexml (3.2.5)
37
+ rubocop (1.52.1)
38
+ json (~> 2.3)
39
+ parallel (~> 1.10)
40
+ parser (>= 3.2.2.3)
41
+ rainbow (>= 2.2.2, < 4.0)
42
+ regexp_parser (>= 1.8, < 3.0)
43
+ rexml (>= 3.2.5, < 4.0)
44
+ rubocop-ast (>= 1.28.0, < 2.0)
45
+ ruby-progressbar (~> 1.7)
46
+ unicode-display_width (>= 2.4.0, < 3.0)
47
+ rubocop-ast (1.29.0)
48
+ parser (>= 3.2.1.0)
49
+ rubocop-performance (1.18.0)
50
+ rubocop (>= 1.7.0, < 2.0)
51
+ rubocop-ast (>= 0.4.0)
52
+ ruby-progressbar (1.13.0)
53
+ standard (1.29.0)
54
+ language_server-protocol (~> 3.17.0.2)
55
+ lint_roller (~> 1.0)
56
+ rubocop (~> 1.52.0)
57
+ standard-custom (~> 1.0.0)
58
+ standard-performance (~> 1.1.0)
59
+ standard-custom (1.0.1)
60
+ lint_roller (~> 1.0)
61
+ standard-performance (1.1.0)
62
+ lint_roller (~> 1.0)
63
+ rubocop-performance (~> 1.18.0)
64
+ unicode-display_width (2.4.2)
65
+
66
+ PLATFORMS
67
+ arm64-darwin-21
68
+ ruby
69
+ x86_64-linux
70
+
71
+ DEPENDENCIES
72
+ minitest (~> 5.0)
73
+ minitest-hooks (~> 1.5)
74
+ rake (~> 13.0)
75
+ standard (~> 1.3)
76
+ testcontainers-compose!
77
+ testcontainers-core!
78
+
79
+ BUNDLED WITH
80
+ 2.4.13
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Guillermo Iguaran
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # Testcontainers module for Docker Compose
2
+
3
+ ## Installation
4
+
5
+ Add the library to the test section in your application's Gemfile:
6
+
7
+ ```ruby
8
+ group :test do
9
+ gem 'testcontainers-compose'
10
+ end
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```bash
16
+ $ bundle install
17
+ ```
18
+
19
+ Or install it yourself as:
20
+
21
+ ```bash
22
+ $ gem install testcontainers-compose
23
+ ```
24
+
25
+ ## Usage
26
+ To use the library, you first need to require it:
27
+
28
+ ```ruby
29
+ require "testcontainers/compose"
30
+ ```
31
+
32
+ ### Creating a Compose Container
33
+ Create a new instance of the `Testcontainers::ComposeContainer` class:
34
+
35
+ ``` ruby
36
+ compose = Testcontainer::ComposeContainer.new(filepath: Dir.getwd)
37
+ ```
38
+
39
+ The instance creates a set of containers defined on the .yml file, the 'compose.start' wakes up all containers as service
40
+
41
+ Start the services on the compose file.
42
+
43
+ ```ruby
44
+ compose.start
45
+ ```
46
+
47
+ Stop the services:
48
+
49
+ ```ruby
50
+ compose.stop
51
+ ```
52
+
53
+ ### Connecting to services
54
+
55
+ Once the service is running, you can obtain the mapped port to connect to it:
56
+
57
+ ```ruby
58
+ compose.service_port(service: "hub", port: 4444)
59
+ ```
60
+
61
+ You can inspect the logs of for the services also:
62
+
63
+ ```ruby
64
+ puts compose.logs
65
+ ```
66
+
67
+ You can use `wait_for_logs`, `wait_for_http` and `wait_for_tcp_port` to wait for the services to start:
68
+
69
+ ```ruby
70
+ compose.wait_for_logs(/Service started/)
71
+ compose.wait_for_http(url: "http://localhost:4444/hub")
72
+ compose.wait_for_tcp_port(host: "localhost", port: 3306)
73
+ ```
74
+
75
+ ### Configuration of services
76
+
77
+ This example initialize docker compose with two services described in the YML file located in the current directory:
78
+
79
+ ```ruby
80
+ services = ["hub","firefox"]
81
+ compose = Testcontainers::ComposeContainer.new(filepath: Dir.getwd, services: services)
82
+ ```
83
+
84
+ You can specify the name of different docker-compose files also:
85
+
86
+ ```ruby
87
+ compose_filenames = ["docker-compose.dbs.yml", "docker-compose.web.yml"]
88
+ compose = Testcontainer::ComposeContainer.new(filepath: Dir.getwd, compose_filenames: compose_filenames)
89
+ compose.start
90
+ ```
91
+
92
+ An env file can be specified when starting the services:
93
+
94
+ ```ruby
95
+ compose_filename = ["docker-compose.test.yml"]
96
+ compose = Testcontainers::ComposeContainer.new(filepath: Dir.getwd, env_file: ".env.test")
97
+ ```
98
+
99
+ ### Executing commands in the container for a service
100
+
101
+ ```ruby
102
+ compose.exec(service_name: "hub", command: "echo test")
103
+ ```
104
+
105
+
106
+ ## Contributing
107
+
108
+ 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).
109
+
110
+ ## License
111
+
112
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
113
+
114
+
115
+ ## Code of Conduct
116
+
117
+ 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).
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ end
11
+
12
+ require "standard/rake"
13
+
14
+ task default: %i[test standard]
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Testcontainers
4
+ module Compose
5
+ VERSION = "0.2.0"
6
+ end
7
+ end
@@ -0,0 +1,242 @@
1
+ require_relative "compose/version"
2
+ require "testcontainers"
3
+ require "open3"
4
+
5
+ module Testcontainers
6
+ # ComposeContainer class is used to manage a large number of containers in a synchronous environment
7
+ #
8
+ # @attr_accesor [String] filepath used by the container
9
+ # @attr_accesor [String,List] compose_filename used by the container
10
+ # @attr_accesor [Boolean] pull used by the container
11
+ # @attr_accesor [Boolean] build used by the container
12
+ # @attr_accesor [List] services used by the container
13
+ class ComposeContainer
14
+ # Default image used by the container
15
+
16
+ attr_accessor :filepath, :compose_filenames, :pull, :build, :env_file, :services
17
+
18
+ # Initializes a new instance of ComposeContainer
19
+ #
20
+ # @param image [String] the image to use
21
+ # @param filepath [String] the filepath of the configuration files for the configuration of docker compose
22
+ # @param compose_filename [String, List] the names of the files with yml extencion for custom configuration
23
+ # @param pull [Boolean] is the option for decide if there should be a pull request to generate the image for the containers
24
+ # @param build [Boolean] is the option for decide if there have to use a build command for the images used for the containers
25
+ # @param env_file [String] is the name of the envieroment configuration
26
+ # @param services [List] are the names of the services that gonna use in the images of the containers
27
+ def initialize(command: ["docker compose"], filepath: ".", compose_filenames: ["docker-compose.yml"],
28
+ pull: false, build: false, env_file: nil, services: nil)
29
+
30
+ @command = command
31
+ @filepath = filepath
32
+ @compose_filenames = compose_filenames
33
+ @pull = pull
34
+ @build = build
35
+ @services = services
36
+ @env_file = env_file
37
+ @_container_started = false
38
+ end
39
+
40
+ # Run the containers using the `docker-compose up` command
41
+ #
42
+ # @return [ComposeContainer] self
43
+ def start
44
+ return self if @_container_started
45
+
46
+ if @pull
47
+ pull_cmd = compose_command("pull")
48
+ exec_command(pull_cmd)
49
+ end
50
+
51
+ args = ["up", "-d"]
52
+ args << "--build" if @build
53
+ args << @services.join(" ") if @services
54
+ up_cmd = compose_command(args)
55
+ _, _, status = exec_command(up_cmd)
56
+ @_container_started = true if status.success?
57
+
58
+ self
59
+ end
60
+
61
+ # Stop the containers using the `docker-compose down` command
62
+ #
63
+ # @return [ComposeContainer] self
64
+ def stop
65
+ raise ContainerNotStartedError unless @_container_started
66
+
67
+ down_cmd = compose_command("down -v")
68
+ _, _, status = exec_command(down_cmd)
69
+ @_container_started = false if status.success?
70
+
71
+ self
72
+ end
73
+
74
+ def running?
75
+ @_container_started
76
+ end
77
+
78
+ def exited?
79
+ !running?
80
+ end
81
+
82
+ # Return the logs of the containers using the `docker-compose logs` command
83
+ #
84
+ # @return [String] logs
85
+ def logs
86
+ raise ContainerNotStartedError unless @_container_started
87
+
88
+ logs_command = compose_command("logs")
89
+ stdout, _, _ = exec_command(logs_command)
90
+ stdout
91
+ end
92
+
93
+ # Execute a command in the given service using the `docker-compose exec` command
94
+ #
95
+ # @param service_name [String]
96
+ # @param command [String]
97
+ def exec(service_name:, command:)
98
+ raise ContainerNotStartedError unless @_container_started
99
+
100
+ exec_cmd = compose_command(["exec", "-T", service_name, command])
101
+ exec_command(exec_cmd)
102
+ end
103
+
104
+ # Return the mapped port for a given service and port using the `docker-compose port` command
105
+ #
106
+ # @param service [String]
107
+ # @return port [int]
108
+ def service_port(service: nil, port: 0)
109
+ raise ContainerNotStartedError unless @_container_started
110
+
111
+ _service_info(service: service, port: port)["port"]
112
+ end
113
+
114
+ # Return the host for a given service and port using the `docker-compose port` command
115
+ #
116
+ # @param service [String]
117
+ # @return host [String]
118
+ def service_host(service: nil, port: 0)
119
+ raise ContainerNotStartedError unless @_container_started
120
+
121
+ _service_info(service: service, port: port)["host"]
122
+ end
123
+
124
+ # Waits for the container logs to match the given regex.
125
+ #
126
+ # @param matcher [Regexp] The regex to match.
127
+ # @param timeout [Integer] The number of seconds to wait for the logs to match.
128
+ # @param interval [Float] The number of seconds to wait between checks.
129
+ # @return [Boolean] Whether the logs matched the regex.
130
+ # @raise [ContainerNotStartedError] If the container has not been started.
131
+ # @raise [TimeoutError] If the timeout is reached.
132
+ # @raise [ConnectionError] If the connection to the Docker daemon fails.
133
+ def wait_for_logs(matcher:, timeout: 60, interval: 0.1)
134
+ raise ContainerNotStartedError unless @_container_started
135
+
136
+ Timeout.timeout(timeout) do
137
+ loop do
138
+ return true if logs&.match?(matcher)
139
+
140
+ sleep interval
141
+ end
142
+ end
143
+ rescue Timeout::Error
144
+ raise TimeoutError, "Timed out waiting for logs to match #{matcher}"
145
+ end
146
+
147
+ # Waits for the container to open the given port.
148
+ #
149
+ # @param port [Integer] The port to wait for.
150
+ # @param timeout [Integer] The number of seconds to wait for the port to open.
151
+ # @param interval [Float] The number of seconds to wait between checks.
152
+ # @return [Boolean] Whether the port is open.
153
+ # @raise [ContainerNotStartedError] If the container has not been started.
154
+ # @raise [TimeoutError] If the timeout is reached.
155
+ # @raise [ConnectionError] If the connection to the Docker daemon fails.
156
+ # @raise [PortNotMappedError] If the port is not mapped.
157
+ def wait_for_tcp_port(host:, port:, timeout: 60, interval: 0.1)
158
+ raise ContainerNotStartedError unless @_container_started
159
+
160
+ Timeout.timeout(timeout) do
161
+ loop do
162
+ Timeout.timeout(interval) do
163
+ TCPSocket.new(host, port).close
164
+ return true
165
+ end
166
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Timeout::Error
167
+ sleep interval
168
+ end
169
+ end
170
+ rescue Timeout::Error
171
+ raise TimeoutError, "Timed out waiting for port #{port} to open"
172
+ end
173
+
174
+ # Waits for the container to respond to HTTP requests.
175
+ #
176
+ # @param service [String] The name of the service.
177
+ # @param timeout [Integer] The number of seconds to wait for the TCP connection to be established.
178
+ # @param interval [Float] The number of seconds to wait between checks.
179
+ # @param path [String] The path to request.
180
+ # @param container_port [Integer] The container port to request.
181
+ # @param https [Boolean] Whether to use TLS.
182
+ # @param status [Integer] The expected HTTP status code.
183
+ # @return [Boolean] Whether the container is responding to HTTP requests.
184
+ # @raise [ContainerNotStartedError] If the container has not been started.
185
+ # @raise [TimeoutError] If the timeout is reached.
186
+ # @raise [PortNotMappedError] If the port is not mapped.
187
+ def wait_for_http(url:, timeout: 60, interval: 0.1, status: 200)
188
+ raise ContainerNotStartedError unless @_container_started
189
+
190
+ Timeout.timeout(timeout) do
191
+ loop do
192
+ begin
193
+ response = Excon.get(url)
194
+ return true if response.status == status
195
+ rescue Excon::Error::Socket
196
+ # The container may not be ready to accept connections yet
197
+ end
198
+
199
+ sleep interval
200
+ end
201
+ end
202
+ rescue Timeout::Error
203
+ raise TimeoutError, "Timed out waiting for HTTP status #{status} on #{path}"
204
+ end
205
+
206
+ private
207
+
208
+ def exec_command(cmd)
209
+ stdout, stderr, status = Open3.capture3(cmd, chdir: @filepath)
210
+
211
+ [stdout, stderr, status]
212
+ end
213
+
214
+ def compose_command(args)
215
+ # Prepare the args and command
216
+ args = args.split(" ") if args.is_a?(String)
217
+ compose_command = @command.dup
218
+ compose_command = compose_comand.split(" ") if compose_command.is_a?(String)
219
+
220
+ # Add the compose files
221
+ file_args = @compose_filenames.map { |filename| "-f #{filename}" }
222
+ compose_command += file_args
223
+
224
+ # Add the env file
225
+ compose_command.push("--env-file #{env_file}") if env_file
226
+
227
+ # Add the args
228
+ compose_command += args
229
+
230
+ # Return the command
231
+ compose_command.join(" ")
232
+ end
233
+
234
+ # Return the host and the mapped port for the given service and port
235
+ def _service_info(service: nil, port: 0)
236
+ port_command = compose_command(["port", service, port])
237
+ stdout, _, _ = exec_command(port_command)
238
+ host, port = stdout.strip.split(":")
239
+ {"host" => host, "port" => port}
240
+ end
241
+ end
242
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: testcontainers-compose
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Guillermo Iguaran
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-02-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: testcontainers-core
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest-hooks
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.5'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.5'
69
+ - !ruby/object:Gem::Dependency
70
+ name: standard
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.3'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.3'
83
+ description: Testcontainers makes it easy to create and clean up container-based dependencies
84
+ for automated tests.
85
+ email:
86
+ - guilleiguaran@gmail.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - CHANGELOG.md
92
+ - Gemfile
93
+ - Gemfile.lock
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - lib/testcontainers/compose.rb
98
+ - lib/testcontainers/compose/version.rb
99
+ homepage: https://github.com/testcontainers/testcontainers-ruby
100
+ licenses:
101
+ - MIT
102
+ metadata:
103
+ homepage_uri: https://github.com/testcontainers/testcontainers-ruby/blob/main/compose
104
+ source_code_uri: https://github.com/testcontainers/testcontainers-ruby/blob/main/compose
105
+ changelog_uri: https://github.com/testcontainers/testcontainers-ruby/blob/main/compose/CHANGELOG.md
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: 2.6.0
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ requirements: []
121
+ rubygems_version: 3.4.14
122
+ signing_key:
123
+ specification_version: 4
124
+ summary: 'Testcontainers for Ruby: Compose module'
125
+ test_files: []