testcontainers-compose 0.2.0

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