docker-compose 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +154 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/docker-compose.gemspec +25 -0
- data/lib/docker/compose.rb +14 -0
- data/lib/docker/compose/future/session.rb +90 -0
- data/lib/docker/compose/mapper.rb +95 -0
- data/lib/docker/compose/net_info.rb +88 -0
- data/lib/docker/compose/rake_tasks.rb +158 -0
- data/lib/docker/compose/session.rb +113 -0
- data/lib/docker/compose/shell.rb +165 -0
- data/lib/docker/compose/version.rb +5 -0
- metadata +107 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a06d21989982bb658ca42414f0a20e6e511263f6
|
4
|
+
data.tar.gz: 94d12b911c12197da022fe880548d68f80225182
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 400d618d335ebb064edccbb21f074c66d1b5e82ba4b6f5a93b69ac1227cab19438df7e23ea94529f40f5598405fcd4834234881ffdc011a7c00f0d00d0467919
|
7
|
+
data.tar.gz: 180aac4998d6a1ed559f006a617a987e0ea61aa0904c9030de8ba9698f23f254b08ee907aa613b12ca79f1863a61e761668388f1e4f6d8efa816bf6b13d7b6d9
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.2.2
|
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
4
|
+
|
5
|
+
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
|
6
|
+
|
7
|
+
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
8
|
+
|
9
|
+
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
|
10
|
+
|
11
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
|
12
|
+
|
13
|
+
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Tony Spataro
|
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,154 @@
|
|
1
|
+
# Docker::Compose
|
2
|
+
|
3
|
+
This is a Ruby OOP wrapper for the [docker-compose](https://github.com/docker/compose)
|
4
|
+
container orchestration tool from Docker Inc. It also contains some features that
|
5
|
+
layer nicely on top of docker-compose to enhance your productivity.
|
6
|
+
|
7
|
+
Distinctive features of this gem:
|
8
|
+
|
9
|
+
1) Simulates environment-variable substitution in the style of docker-compose
|
10
|
+
1.5; sequences such as ${FOO} in your YML will be replaced with the
|
11
|
+
corresponding environment variable. (This feature will go away once 1.5
|
12
|
+
is released.)
|
13
|
+
|
14
|
+
2) Environment-variable mapping that allows you to export environment variables
|
15
|
+
into your _host_ that point to network services published by containers.
|
16
|
+
|
17
|
+
Throughout this documentation we will refer to this gem as `Docker::Compose`
|
18
|
+
as opposed to the `docker-compose` tool that this gem wraps.
|
19
|
+
|
20
|
+
## Installation
|
21
|
+
|
22
|
+
Add this line to your application's Gemfile:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
gem 'docker-compose'
|
26
|
+
```
|
27
|
+
|
28
|
+
And then execute:
|
29
|
+
|
30
|
+
$ bundle
|
31
|
+
|
32
|
+
Or install it yourself as:
|
33
|
+
|
34
|
+
$ gem install docker-compose
|
35
|
+
|
36
|
+
## Usage
|
37
|
+
|
38
|
+
### Invoking from Ruby code
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
require 'docker/compose'
|
42
|
+
|
43
|
+
# Create a new session in Dir.pwd using the file "docker-compose.yml"
|
44
|
+
# for fine-grained control over options, see Docker::Compose::Session#new
|
45
|
+
compose = Docker::Compose.new
|
46
|
+
|
47
|
+
compose.version
|
48
|
+
|
49
|
+
compose.up(detached:true)
|
50
|
+
```
|
51
|
+
|
52
|
+
### Invoking from Rake
|
53
|
+
|
54
|
+
Open your Rakefile and add the Docker::Compose tasks.
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
require 'docker/compose/rake_tasks'
|
58
|
+
|
59
|
+
Docker::Compose::RakeTasks.new do |tasks|
|
60
|
+
# customize by calling setter methods of tasks;
|
61
|
+
# see the class documentation for details
|
62
|
+
end
|
63
|
+
|
64
|
+
```
|
65
|
+
|
66
|
+
Notice that `rake -T` now has a few additional tasks for invoking gem
|
67
|
+
functionality. You can `docker:compose:env` to print bash export statements
|
68
|
+
for host-to-container environment mapping; you can `docker:compose:up` or
|
69
|
+
`docker:compose:stop` to start and stop containers.
|
70
|
+
|
71
|
+
The `docker-compose` command is a perfectly valid way to start
|
72
|
+
and stop containers, but the gem provides some env-substitution functionality
|
73
|
+
for your YML files that will be built into docker-compose 1.5 but is not
|
74
|
+
released yet. If your YML contains `${ENV}` references, i.e. in order to
|
75
|
+
point your containers at network services running on the host, then you must
|
76
|
+
invoke docker-compose through Rake in order to peform the substitution.
|
77
|
+
|
78
|
+
### Mapping container IPs and ports
|
79
|
+
|
80
|
+
Assuming that your app accepts its configuration in the form of environment
|
81
|
+
variables, you can use the `docker:compose:env` to export environment values
|
82
|
+
into your bash shell that point to services running inside containers. This
|
83
|
+
allows you to run the app on your host (for easier debugging and code editing)
|
84
|
+
but let it communicate with services running inside containers.
|
85
|
+
|
86
|
+
Docker::Compose uses a heuristic to figure out which IP your services
|
87
|
+
are actually reachable at; the heuristic works regardless whether you are
|
88
|
+
running "bare" docker daemon on localhost, communicating with a docker-machine
|
89
|
+
instance, or even using a cloud-hosted docker machine!
|
90
|
+
|
91
|
+
As a trivial example, let's say that your `docker-compose.yml` contains one
|
92
|
+
service, the database that your app needs in order to run.
|
93
|
+
|
94
|
+
```yaml
|
95
|
+
db:
|
96
|
+
image: mysql:latest
|
97
|
+
environment:
|
98
|
+
MYSQL_DATABASE: myapp_development
|
99
|
+
MYSQL_ROOT_PASSWORD: opensesame
|
100
|
+
ports:
|
101
|
+
- "3306"
|
102
|
+
|
103
|
+
```
|
104
|
+
|
105
|
+
Your app needs two inputs, `DATABASE_HOST` and `DATABASE_PORT`. You can specify
|
106
|
+
this in the env section of the Rake task:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
Docker::Compose::RakeTasks.new do |tasks|
|
110
|
+
tasks.env = {
|
111
|
+
'DATABASE_HOST' => 'db:[3306]'
|
112
|
+
'DATABASE_PORT' => '[db]:3306'
|
113
|
+
}
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
(If I had a `DATABASE_URL` input, I could provide a URL such as
|
118
|
+
`mysql://db/myapp_development`; Docker::Compose would parse the URL and replace
|
119
|
+
the hostname and port appropriately.)
|
120
|
+
|
121
|
+
Now, I can run my services, ask Docker::Compose to map the environment values
|
122
|
+
to the actual IP and port that `db` has been published to, and run my app:
|
123
|
+
|
124
|
+
```bash
|
125
|
+
user@machine$ docker-compose up -d
|
126
|
+
|
127
|
+
# This prints bash code resembling the following:
|
128
|
+
# export DATABASE_HOST=127.0.0.1
|
129
|
+
# export DATABASE_PORT=34387
|
130
|
+
# We eval it, which makes the variables available to our shell and to all
|
131
|
+
# subprocesses.
|
132
|
+
user@machine$ eval "$(bundle exec rake docker:compose:env)"
|
133
|
+
|
134
|
+
user@machine$ bundle exec rackup
|
135
|
+
```
|
136
|
+
|
137
|
+
To learn more about mapping, read the class documentation for
|
138
|
+
`Docker::Compose::Mapper`.
|
139
|
+
|
140
|
+
## Development
|
141
|
+
|
142
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
143
|
+
|
144
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
145
|
+
|
146
|
+
## Contributing
|
147
|
+
|
148
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/xeger/docker-compose. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
|
149
|
+
|
150
|
+
|
151
|
+
## License
|
152
|
+
|
153
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
154
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "docker/compose"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'docker/compose/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "docker-compose"
|
8
|
+
spec.version = Docker::Compose::VERSION
|
9
|
+
spec.authors = ["Tony Spataro"]
|
10
|
+
spec.email = ["xeger@xeger.net"]
|
11
|
+
|
12
|
+
spec.summary = %q{Wrapper docker-compose with added Rake smarts.}
|
13
|
+
spec.description = %q{Provides an OOP interface to docker-compose and facilitates container-to-host and host-to-container networking.}
|
14
|
+
spec.homepage = "https://github.com/xeger/docker-compose"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
spec.add_development_dependency "rspec"
|
25
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require_relative 'compose/version'
|
2
|
+
require_relative 'compose/shell'
|
3
|
+
require_relative 'compose/session'
|
4
|
+
require_relative 'compose/net_info'
|
5
|
+
require_relative 'compose/mapper'
|
6
|
+
|
7
|
+
module Docker
|
8
|
+
module Compose
|
9
|
+
# Create a new session.
|
10
|
+
def self.new
|
11
|
+
Session.new
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Docker::Compose::Future
|
4
|
+
module Session
|
5
|
+
# Pattern that matches an environment substitution in a docker-compose YML
|
6
|
+
# file.
|
7
|
+
# @see #substitute
|
8
|
+
SUBSTITUTION = /\$\{([A-Z0-9:_-]+)\}/
|
9
|
+
|
10
|
+
# Hook in env-var substitution by aliasing a method chain for run!
|
11
|
+
def self.included(host)
|
12
|
+
done = host.instance_methods.include?(:run_without_substitution!)
|
13
|
+
host.instance_eval do
|
14
|
+
alias_method :run_without_substitution!, :run!
|
15
|
+
alias_method :run!, :run_with_substitution!
|
16
|
+
end unless done
|
17
|
+
end
|
18
|
+
|
19
|
+
# Read docker-compose YML; perform environment variable substitution;
|
20
|
+
# write a temp file; invoke run! with the new file; delete the temp
|
21
|
+
# file afterward.
|
22
|
+
#
|
23
|
+
# This is a complete reimplementation of run! and we only alias the original
|
24
|
+
# to be good citizens.
|
25
|
+
def run_with_substitution!(*cmd)
|
26
|
+
temp = nil
|
27
|
+
project = File.basename(@dir)
|
28
|
+
|
29
|
+
# Find and purge the 'file' flag if it exists; otherwise assume we will
|
30
|
+
# substitute our default (session) file.
|
31
|
+
fn = nil
|
32
|
+
cmd.each do |item|
|
33
|
+
fn ||= item.delete(:file) if item.is_a?(Hash)
|
34
|
+
end
|
35
|
+
fn ||= @file
|
36
|
+
|
37
|
+
# Rewrite YML if the file exists and the file:false "flag" wasn't
|
38
|
+
# explicitly passed to us.
|
39
|
+
Dir.chdir(@dir) do
|
40
|
+
yml = YAML.load(File.read(fn))
|
41
|
+
yml = substitute(yml)
|
42
|
+
temp = Tempfile.new(fn, @dir)
|
43
|
+
temp.write(YAML.dump(yml))
|
44
|
+
temp.close
|
45
|
+
|
46
|
+
project_opts = {
|
47
|
+
file: temp.path,
|
48
|
+
project: File.basename(@dir)
|
49
|
+
}
|
50
|
+
|
51
|
+
result, output =
|
52
|
+
@shell.command('docker-compose', project_opts, *cmd)
|
53
|
+
(result == 0) || raise(RuntimeError,
|
54
|
+
"#{cmd.first} failed with status #{result}")
|
55
|
+
output
|
56
|
+
end
|
57
|
+
ensure
|
58
|
+
temp.unlink if temp
|
59
|
+
end
|
60
|
+
|
61
|
+
# Simulate the behavior of docker-compose 1.5: replace "${VAR}" sequences
|
62
|
+
# with the values of environment variables. Perform this recursively if
|
63
|
+
# data is a Hash or Array.
|
64
|
+
#
|
65
|
+
#
|
66
|
+
# @param [Hash,Array,String,Object] data
|
67
|
+
# @return [Hash,Array,String,Object] data with all ${ENV} references substituted
|
68
|
+
private def substitute(data)
|
69
|
+
case data
|
70
|
+
when Hash
|
71
|
+
result = {}
|
72
|
+
data.each_pair { |k, v| result[k] = substitute(v) }
|
73
|
+
when Array
|
74
|
+
result = []
|
75
|
+
data.each { |v| result << substitute(v) }
|
76
|
+
when String
|
77
|
+
result = data
|
78
|
+
while (match = SUBSTITUTION.match(result))
|
79
|
+
var = match[1]
|
80
|
+
repl = format("${%s}", var)
|
81
|
+
result.gsub!(repl, ENV[var])
|
82
|
+
end
|
83
|
+
else
|
84
|
+
result = data
|
85
|
+
end
|
86
|
+
|
87
|
+
result
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Docker::Compose
|
2
|
+
# Uses a Session to discover information about services' IP addresses and
|
3
|
+
# ports as reachable from the host, then
|
4
|
+
class Mapper
|
5
|
+
# Pattern that matches an "elided" host or port that should be omitted from
|
6
|
+
# output, but is needed to identify a specific container and port.
|
7
|
+
ELIDED = /^\[.+\]$/.freeze
|
8
|
+
|
9
|
+
# Regexp that can be used with gsub to strip elision marks
|
10
|
+
REMOVE_ELIDED = /[\[\]]/.freeze
|
11
|
+
|
12
|
+
BadSubstitution = Class.new(StandardError)
|
13
|
+
NoService = Class.new(RuntimeError)
|
14
|
+
|
15
|
+
# Create an instance of Mapper
|
16
|
+
# @param [Docker::Compose::Session] session
|
17
|
+
# @param [String] host_ip IPv4 address of the host that is publishing
|
18
|
+
# Docker services (i.e. the `DOCKER_HOST` hostname or IP if you are using
|
19
|
+
# a non-clustered Docker environment)
|
20
|
+
# @param [Boolean] strict if true, raise BadSubstitution when unrecognized
|
21
|
+
# syntax is passed to #map; if false, simply return the value without
|
22
|
+
# substituting anything
|
23
|
+
def initialize(session, host_ip, strict:true)
|
24
|
+
@session = session
|
25
|
+
@host_ip = host_ip
|
26
|
+
@strict = strict
|
27
|
+
end
|
28
|
+
|
29
|
+
# Substitute service hostnames and ports that appear in a URL or a
|
30
|
+
# host:port string. If either component of a host:port string is
|
31
|
+
# surrounded by square brackets, "elide" that component, removing it
|
32
|
+
# from the result but using it to find the correct service and port.
|
33
|
+
#
|
34
|
+
# @example map MySQL on local docker host with 3306 published to 13847
|
35
|
+
# map("tcp://db:3306") # => "tcp://127.0.0.1:13847"
|
36
|
+
#
|
37
|
+
# @example map just the hostname of MySQL on local docker host
|
38
|
+
# map("db:[3306]") # => "127.0.0.1"
|
39
|
+
#
|
40
|
+
# @example map just the port of MySQL on local docker host
|
41
|
+
# map("[db]:3306") # => "13847"
|
42
|
+
#
|
43
|
+
# @param [String] value a URI or a host:port pair
|
44
|
+
#
|
45
|
+
# @raise [BadSubstitution] if a substitution string can't be parsed
|
46
|
+
# @raise [NoService] if service is not up or does not publish port
|
47
|
+
def map(value)
|
48
|
+
uri = URI.parse(value) rescue nil
|
49
|
+
pair = value.split(':')
|
50
|
+
|
51
|
+
if uri && uri.scheme && uri.host
|
52
|
+
# absolute URI with scheme, authority, etc
|
53
|
+
uri.port = published_port(uri.host, uri.port)
|
54
|
+
uri.host = @host_ip
|
55
|
+
return uri.to_s
|
56
|
+
elsif pair.size == 2
|
57
|
+
# "host:port" pair; three sub-cases...
|
58
|
+
if pair.first =~ ELIDED
|
59
|
+
# output only the port
|
60
|
+
service = pair.first.gsub(REMOVE_ELIDED, '')
|
61
|
+
port = published_port(service, pair.last)
|
62
|
+
return port.to_s
|
63
|
+
elsif pair.last =~ ELIDED
|
64
|
+
# output only the hostname; resolve the port anyway to ensure that
|
65
|
+
# the service is running.
|
66
|
+
service = pair.first
|
67
|
+
port = pair.last.gsub(REMOVE_ELIDED, '')
|
68
|
+
published_port(service, port)
|
69
|
+
return @host_ip
|
70
|
+
else
|
71
|
+
# output port:hostname pair
|
72
|
+
port = published_port(pair.first, pair.last)
|
73
|
+
return "#{@host_ip}:#{port}"
|
74
|
+
end
|
75
|
+
elsif @strict
|
76
|
+
raise BadSubstitution, "Can't understand '#{value}'"
|
77
|
+
else
|
78
|
+
return value
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Figure out which host port a given service's port has been published to,
|
83
|
+
# and/or whether that service is running. Cannot distinguish between the
|
84
|
+
# "service not running" case and the "container port not published" case!
|
85
|
+
#
|
86
|
+
# @raise [NoService] if service is not up or does not publish port
|
87
|
+
# @return [Integer] host port number, or nil if port not published
|
88
|
+
def published_port(service, port)
|
89
|
+
result = @session.port(service, port)
|
90
|
+
Integer(result.split(':').last.gsub("\n", ""))
|
91
|
+
rescue RuntimeError
|
92
|
+
raise NoService, "Service '#{service}' not running, or does not publish port '#{port}'"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Docker::Compose
|
2
|
+
# Utility that gathers information about the relationship between the host
|
3
|
+
# on which the Ruby VM is running and the docker host, then makes an
|
4
|
+
# guess about the mutually routable IP addresses of each.
|
5
|
+
#
|
6
|
+
# This information can be used to tell containers how to connect to ports on
|
7
|
+
# the local host, or conversely to tell the local host how to connect to ports
|
8
|
+
# published by containers running on the docker host.
|
9
|
+
#
|
10
|
+
# The heuristic works for most cases encountered in the wild, including:
|
11
|
+
# - DOCKER_HOST is unset (assume daemon listening on 127.0.0.1)
|
12
|
+
# - DOCKER_HOST points to a socket (assume 127.0.0.1)
|
13
|
+
# - DOCKER_HOST points to a tcp, http or https address
|
14
|
+
class NetInfo
|
15
|
+
# Determine IP addresses of the local host's network interfaces.
|
16
|
+
#
|
17
|
+
# @return [Array] list of String dotted-quad IPv4 addresses
|
18
|
+
def self.ipv4_interfaces
|
19
|
+
Socket.getifaddrs
|
20
|
+
.map { |i| i.addr.ip_address if i.addr && i.addr.ipv4? }.compact
|
21
|
+
end
|
22
|
+
|
23
|
+
# Create a new instance of this class.
|
24
|
+
# @param [String] docker_host a URI pointing to the docker host
|
25
|
+
# @param [Array] list of String dotted-quad IPv4 addresses of local host
|
26
|
+
def initialize(docker_host=ENV['DOCKER_HOST'],
|
27
|
+
my_ips=self.class.ipv4_interfaces)
|
28
|
+
docker_host ||= 'unix:/var/run/docker.sock'
|
29
|
+
@docker_url = URI.parse(docker_host)
|
30
|
+
@my_ips = my_ips
|
31
|
+
end
|
32
|
+
|
33
|
+
# Examine local host's network interfaces; figure out which one is most
|
34
|
+
# likely to share a route with the given IP address. If no IP address
|
35
|
+
# is specified, figure out which IP the Docker daemon is reachable on
|
36
|
+
# and use that as the target IP.
|
37
|
+
#
|
38
|
+
# @param [String] target_ip IPv4 address of target
|
39
|
+
#
|
40
|
+
# @return [String] IPv4 address of host machine that _may_ be reachable from
|
41
|
+
# Docker machine
|
42
|
+
def host_routable_ip(target_ip=docker_routable_ip)
|
43
|
+
best_match = ''
|
44
|
+
best_prefix = 0
|
45
|
+
|
46
|
+
target_cps = target_ip.codepoints
|
47
|
+
|
48
|
+
@my_ips.each do |my_ip|
|
49
|
+
ip_cps = my_ip.codepoints
|
50
|
+
prefix = 0
|
51
|
+
ip_cps.each_with_index do |cp, i|
|
52
|
+
break unless target_cps[i] == cp
|
53
|
+
prefix = i
|
54
|
+
end
|
55
|
+
|
56
|
+
if prefix > best_prefix
|
57
|
+
best_match = my_ip
|
58
|
+
best_prefix = prefix
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
best_match
|
63
|
+
end
|
64
|
+
|
65
|
+
# Figure out the likely IP address of the host pointed to by
|
66
|
+
# self.docker_url.
|
67
|
+
#
|
68
|
+
# @return [String] host-reachable IPv4 address of docker host
|
69
|
+
def docker_routable_ip
|
70
|
+
case @docker_url.scheme
|
71
|
+
when 'tcp', 'http', 'https'
|
72
|
+
docker_dns = @docker_url.host
|
73
|
+
docker_port = @docker_url.port || 2376
|
74
|
+
else
|
75
|
+
# Cheap trick: for unix or other protocols, assume docker daemon
|
76
|
+
# is listening on 127.0.0.1:2376
|
77
|
+
docker_dns = 'localhost'
|
78
|
+
docker_port = 2376
|
79
|
+
end
|
80
|
+
|
81
|
+
addr = Addrinfo.getaddrinfo(
|
82
|
+
docker_dns, docker_port,
|
83
|
+
Socket::AF_INET, Socket::SOCK_STREAM).first
|
84
|
+
|
85
|
+
addr && addr.ip_address
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'rake/tasklib'
|
2
|
+
|
3
|
+
# In case this file is required directly
|
4
|
+
require 'docker/compose'
|
5
|
+
|
6
|
+
module Docker::Compose
|
7
|
+
class RakeTasks < Rake::TaskLib
|
8
|
+
# Set the directory in which docker-compose commands will be run. Default
|
9
|
+
# is the directory in which Rakefile is located.
|
10
|
+
#
|
11
|
+
# @return [String]
|
12
|
+
attr_accessor :dir
|
13
|
+
|
14
|
+
# Set the name of the docker-compose file. Default is`docker-compose.yml`.
|
15
|
+
# @return [String]
|
16
|
+
attr_accessor :file
|
17
|
+
|
18
|
+
# Provide a mapping of environment variables that should be set in the
|
19
|
+
# _host_ shell for docker:compose:env or docker:compose:server.
|
20
|
+
# The values of the environment variables can refer to names of services
|
21
|
+
# and ports defined in the docker-compose file, and this gem will query
|
22
|
+
# docker-compose to find out which host IP and port the services are
|
23
|
+
# reachable on. This allows components running on the host to connect to
|
24
|
+
# services running inside containers.
|
25
|
+
#
|
26
|
+
# @see Docker::Compose::Mapper for information about the substitution syntax
|
27
|
+
attr_accessor :env
|
28
|
+
|
29
|
+
# Extra environment variables that should be set before invoking the command
|
30
|
+
# specified for docker:compose:server. These are set _in addition_ to env
|
31
|
+
# (and should be disjoint from env), and do not necessarily need to map the
|
32
|
+
# location of a container; they can be simple extra env values that are
|
33
|
+
# useful to change the server's behavior when it runs in cooperation
|
34
|
+
# with containers.
|
35
|
+
#
|
36
|
+
# If there is overlap between env and server_env, then keys of server_env
|
37
|
+
# will "win"; they are set last.
|
38
|
+
attr_accessor :server_env
|
39
|
+
|
40
|
+
# Command to exec on the _host_ when someone invokes docker:compose:server.
|
41
|
+
# This is used to start up all containers and then run a server that
|
42
|
+
# depends on them and is properly linked to them.
|
43
|
+
attr_accessor :server
|
44
|
+
|
45
|
+
# Construct Rake wrapper tasks for docker-compose. If a block is given,
|
46
|
+
# yield self to the block before defining any tasks so their behavior
|
47
|
+
# can be configured by calling #env=, #file= and so forth.
|
48
|
+
def initialize
|
49
|
+
self.dir = Rake.application.original_dir
|
50
|
+
self.file = 'docker-compose.yml'
|
51
|
+
self.env = {}
|
52
|
+
self.server_env = {}
|
53
|
+
yield self if block_given?
|
54
|
+
|
55
|
+
@shell = Docker::Compose::Shell.new
|
56
|
+
@session = Docker::Compose::Session.new(@shell, dir:dir, file:file)
|
57
|
+
@net_info = Docker::Compose::NetInfo.new
|
58
|
+
|
59
|
+
define
|
60
|
+
end
|
61
|
+
|
62
|
+
private def define
|
63
|
+
namespace :docker do
|
64
|
+
namespace :compose do
|
65
|
+
desc 'Print bash exports with IP/ports of running services'
|
66
|
+
task :env do
|
67
|
+
@shell.interactive = false # suppress useless 'port' output
|
68
|
+
|
69
|
+
if Rake.application.top_level_tasks.include? 'docker:compose:env'
|
70
|
+
# This task is being run as top-level; print some bash export
|
71
|
+
# statements or usage information depending on whether STDOUT
|
72
|
+
# is a tty.
|
73
|
+
if STDOUT.tty?
|
74
|
+
print_usage
|
75
|
+
else
|
76
|
+
export_env(print:true)
|
77
|
+
end
|
78
|
+
else
|
79
|
+
# This task is a dependency of something else; just export the
|
80
|
+
# environment variables for use in-process by other Rake tasks.
|
81
|
+
export_env(print:false)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
desc 'Launch services needed to run this application'
|
86
|
+
task :up do
|
87
|
+
@shell.interactive = true # let user see what's happening
|
88
|
+
@session.up(detached:true)
|
89
|
+
end
|
90
|
+
|
91
|
+
desc 'Tail logs of all running services'
|
92
|
+
task :logs do
|
93
|
+
@session.logs
|
94
|
+
end
|
95
|
+
|
96
|
+
desc 'Stop services needed to run this application'
|
97
|
+
task :stop do
|
98
|
+
@session.stop
|
99
|
+
end
|
100
|
+
|
101
|
+
desc 'Run application on the host, linked to services in containers'
|
102
|
+
task :server => ['docker:compose:up', 'docker:compose:env'] do
|
103
|
+
exec(self.server)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Substitute and set environment variables that point to network ports
|
110
|
+
# published by docker-compose services. Optionally also print bash export
|
111
|
+
# statements so this information can be made available to a user's shell.
|
112
|
+
private def export_env(print:)
|
113
|
+
# First, do env substitutions in strict mode; don't catch BadSubstitution
|
114
|
+
# so the caller knows when he has a bogus value
|
115
|
+
mapper = Docker::Compose::Mapper.new(@session,
|
116
|
+
@net_info.docker_routable_ip)
|
117
|
+
self.env.each_pair do |k, v|
|
118
|
+
begin
|
119
|
+
v = mapper.map(v)
|
120
|
+
ENV[k] = v
|
121
|
+
print_env(k, v) if print
|
122
|
+
rescue Docker::Compose::Mapper::NoService
|
123
|
+
ENV[k] = nil
|
124
|
+
print_env(k, nil) if print
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Next, do server substitutions in non-strict mode since server_env
|
129
|
+
# can contain arbitrary values.
|
130
|
+
mapper = Docker::Compose::Mapper.new(@session,
|
131
|
+
@net_info.docker_routable_ip,
|
132
|
+
strict:false)
|
133
|
+
self.server_env.each_pair do |k, v|
|
134
|
+
v = mapper.map(v)
|
135
|
+
ENV[k] = v
|
136
|
+
print_env(k, v) if print
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Print a bash export or unset statement
|
141
|
+
private def print_env(k, v)
|
142
|
+
if v
|
143
|
+
puts format('export %s=%s', k, v)
|
144
|
+
else
|
145
|
+
puts format('unset %s # service not running', k)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
private def print_usage
|
150
|
+
be = 'bundle exec ' if defined?(Bundler)
|
151
|
+
puts "# To export container network locations to your environment:"
|
152
|
+
puts %Q{eval "$(#{be}rake docker:compose:env)"}
|
153
|
+
puts
|
154
|
+
puts '# To learn which environment variables we will export:'
|
155
|
+
puts %Q{echo "$(#{be}rake docker:compose:env)"}
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'docker/compose/future/session'
|
2
|
+
|
3
|
+
module Docker::Compose
|
4
|
+
# A Ruby OOP interface to a docker-compose session. A session is bound to
|
5
|
+
# a particular directory and docker-compose file (which are set at initialize
|
6
|
+
# time) and invokes whichever docker-compose command is resident in $PATH.
|
7
|
+
#
|
8
|
+
# Run docker-compose commands by calling instance methods of this class and
|
9
|
+
# passing kwargs that are equivalent to the CLI options you would pass to
|
10
|
+
# the command-line tool.
|
11
|
+
#
|
12
|
+
# Note that the Ruby command methods usually expose a _subset_ of the options
|
13
|
+
# allowed by the docker-compose CLI, and that options are sometimes renamed
|
14
|
+
# for clarity, e.g. the "-d" flag always becomes the "detached:" kwarg.
|
15
|
+
class Session
|
16
|
+
def initialize(shell=Docker::Compose::Shell.new,
|
17
|
+
dir:Dir.pwd, file:'docker-compose.yml')
|
18
|
+
@shell = shell
|
19
|
+
@dir = dir
|
20
|
+
@file = file
|
21
|
+
end
|
22
|
+
|
23
|
+
# Monitor the logs of one or more containers.
|
24
|
+
# @param [Array] services list of String service names to show logs for
|
25
|
+
# @return [true] always returns true
|
26
|
+
# @raise [RuntimeError] if command fails
|
27
|
+
def logs(*services)
|
28
|
+
run!('logs', services)
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
# Idempotently run services in the project,
|
33
|
+
# @param [Array] services list of String service names to run
|
34
|
+
# @param [Boolean] detached if true, to start services in the background;
|
35
|
+
# otherwise, monitor logs in the foreground and shutdown on Ctrl+C
|
36
|
+
# @param [Integer] timeout how long to wait for each service to stostart
|
37
|
+
# @param [Boolean] no_build if true, to skip building images for services
|
38
|
+
# that have a `build:` instruction in the docker-compose file
|
39
|
+
# @param [Boolean] no_deps if true, just run specified services without
|
40
|
+
# running the services that they depend on
|
41
|
+
# @return [true] always returns true
|
42
|
+
# @raise [RuntimeError] if command fails
|
43
|
+
def up(*services,
|
44
|
+
detached:false, timeout:10, no_build:false, no_deps:false)
|
45
|
+
run!('up',
|
46
|
+
{d:detached, timeout:timeout, no_build:no_build, no_deps:no_deps},
|
47
|
+
services)
|
48
|
+
true
|
49
|
+
end
|
50
|
+
|
51
|
+
# Stop running services.
|
52
|
+
# @param [Array] services list of String service names to stop
|
53
|
+
# @param [Integer] timeout how long to wait for each service to stop
|
54
|
+
def stop(*services, timeout:10)
|
55
|
+
run!('stop', {timeout:timeout}, services)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Figure out which host a port a given service port has been published to.
|
59
|
+
# @param [String] service name of service from docker-compose.yml
|
60
|
+
# @param [Integer] port number of port
|
61
|
+
# @param [String] protocol 'tcp' or 'udp'
|
62
|
+
# @param [Integer] index of container (if multiple instances running)
|
63
|
+
def port(service, port, protocol:'tcp', index:1)
|
64
|
+
run!('port', {protocol:protocol, index:index}, service, port)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Determine the installed version of docker-compose.
|
68
|
+
# @param [Boolean] short whether to return terse version information
|
69
|
+
# @return [String, Hash] if short==true, returns a version string;
|
70
|
+
# otherwise, returns a Hash of component names to version strings
|
71
|
+
# @raise [RuntimeError] if command fails
|
72
|
+
def version(short:false)
|
73
|
+
result = run!('version', short:short, file:false, dir:false)
|
74
|
+
|
75
|
+
if short
|
76
|
+
result.strip
|
77
|
+
else
|
78
|
+
lines = result.split("\n")
|
79
|
+
lines.inject({}) do |h, line|
|
80
|
+
kv = line.split(/: +/, 2)
|
81
|
+
h[kv.first] = kv.last
|
82
|
+
h
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Run a docker-compose command without validating that the CLI parameters
|
88
|
+
# make sense. Prepend project and file options if suitable.
|
89
|
+
#
|
90
|
+
# @see Docker::Compose::Shell#command
|
91
|
+
#
|
92
|
+
# @param [Array] cmd subcommand words and options in the format accepted by
|
93
|
+
# Shell#command
|
94
|
+
# @return [String] output of the command
|
95
|
+
# @raise [RuntimeError] if command fails
|
96
|
+
def run!(*cmd)
|
97
|
+
project_opts = {
|
98
|
+
file: @file
|
99
|
+
}
|
100
|
+
|
101
|
+
Dir.chdir(@dir) do
|
102
|
+
result, output =
|
103
|
+
@shell.command('docker-compose', project_opts, *cmd)
|
104
|
+
(result == 0) || raise(RuntimeError,
|
105
|
+
"#{cmd.first} failed with status #{result}")
|
106
|
+
output
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Simulate behaviors from Docker 1.5
|
111
|
+
include Docker::Compose::Future::Session
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
module Docker::Compose
|
4
|
+
# An easy-to-use interface for invoking commands and capturing their output.
|
5
|
+
# Instances of Shell can be interactive, which prints the command's output
|
6
|
+
# to the terminal and also allows the user to interact with the command.
|
7
|
+
class Shell
|
8
|
+
# If true, commands run in the shell will have their stdio streams tied
|
9
|
+
# to the parent process so the user can view their output and send input
|
10
|
+
# to them. Commands' stdout is still captured normally when they are
|
11
|
+
# interactive.
|
12
|
+
#
|
13
|
+
# Note that interactivity doesn't work very well because we use popen,
|
14
|
+
# which uses pipes to communicate with the child process and pipes have
|
15
|
+
# a fixed buffer size; the displayed output tends to "lag" behind the
|
16
|
+
# actual program, and bytes sent to stdin may not arrive until you send
|
17
|
+
# a lot of them!
|
18
|
+
#
|
19
|
+
# TODO: solve pipe buffering issues, perhaps with a pty...
|
20
|
+
#
|
21
|
+
# @return [Boolean]
|
22
|
+
attr_accessor :interactive
|
23
|
+
|
24
|
+
# Convert Ruby keyword arguments into CLI parameters that are compatible
|
25
|
+
# with the syntax of golang's flags package.
|
26
|
+
#
|
27
|
+
# Options are translated to CLI parameters using the following convention:
|
28
|
+
# 1) Snake-case symbols are hyphenated, e.g. :no_foo => "--no-foo"
|
29
|
+
# 2) boolean values indicate a CLI flag; true includes the flag, false or nil omits it
|
30
|
+
# 3) other values indicate a CLI option that has a value.
|
31
|
+
# 4) single character values are passed as short options e.g. "-X V"
|
32
|
+
# 5) multi-character values are passed as long options e.g. "--XXX=V"
|
33
|
+
#
|
34
|
+
def self.options(**opts)
|
35
|
+
flags = []
|
36
|
+
|
37
|
+
# Transform opts into golang flags-style command line parameters;
|
38
|
+
# append them to the command.
|
39
|
+
opts.each do |kw, arg|
|
40
|
+
if kw.length == 1
|
41
|
+
if arg == true
|
42
|
+
# true: boolean flag
|
43
|
+
flags << "-#{kw}"
|
44
|
+
elsif arg
|
45
|
+
# truthey: option that has a value
|
46
|
+
flags << "-#{kw}" << arg.to_s
|
47
|
+
else
|
48
|
+
# falsey: omit boolean flag
|
49
|
+
end
|
50
|
+
else
|
51
|
+
kw = kw.to_s.gsub('_','-')
|
52
|
+
if arg == true
|
53
|
+
# true: boolean flag
|
54
|
+
flags << "--#{kw}"
|
55
|
+
elsif arg
|
56
|
+
# truthey: option that has a value
|
57
|
+
flags << "--#{kw}=#{arg}"
|
58
|
+
else
|
59
|
+
# falsey: omit boolean flag
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
flags
|
65
|
+
end
|
66
|
+
|
67
|
+
# Create an instance of Shell.
|
68
|
+
def initialize
|
69
|
+
@interactive = false
|
70
|
+
end
|
71
|
+
|
72
|
+
# Run a shell command whose arguments and flags are expressed using some
|
73
|
+
# Rubyish sugar. This method accepts an arbitrary number of positional
|
74
|
+
# parameters; each parameter can be a Hash, an array, or a simple Object.
|
75
|
+
# Arrays and simple objects are appended to argv as "bare" words; Hashes
|
76
|
+
# are translated to golang flags and then appended to argv.
|
77
|
+
#
|
78
|
+
# @example Run docker-compose with complex parameters
|
79
|
+
# command('docker-compose', {file: 'joe.yml'}, 'up', {d:true}, 'mysvc')
|
80
|
+
#
|
81
|
+
# @see #options for information on Hash-to-flag translation
|
82
|
+
def command(*cmd)
|
83
|
+
argv = []
|
84
|
+
|
85
|
+
cmd.each do |item|
|
86
|
+
case item
|
87
|
+
when Array
|
88
|
+
# list of words to append to argv
|
89
|
+
argv.concat(item.map { |e| e.to_s })
|
90
|
+
when Hash
|
91
|
+
# list of options to convert to CLI parameters
|
92
|
+
argv.concat(self.class.options(item))
|
93
|
+
else
|
94
|
+
# single word to append to argv
|
95
|
+
argv << item.to_s
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
run(argv)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Run a shell command. Perform no translation or substitution. Return
|
103
|
+
# the program's exit status and stdout.
|
104
|
+
#
|
105
|
+
# @param [Array] argv command to run; argv[0] is program name and the
|
106
|
+
# remaining elements are parameters and flags
|
107
|
+
# @return [Array] a pair of Integer exitstatus and String output
|
108
|
+
private def run(argv)
|
109
|
+
stdin, stdout, stderr, thr = Open3.popen3(*argv)
|
110
|
+
|
111
|
+
streams = [stdout, stderr]
|
112
|
+
|
113
|
+
if @interactive
|
114
|
+
streams << STDIN
|
115
|
+
else
|
116
|
+
stdin.close
|
117
|
+
end
|
118
|
+
|
119
|
+
output = String.new.force_encoding(Encoding::BINARY)
|
120
|
+
|
121
|
+
until streams.empty? || (streams.length == 1 && streams.first == STDIN)
|
122
|
+
ready, _, _ = IO.select(streams, [], [], 1)
|
123
|
+
|
124
|
+
if ready && ready.include?(STDIN)
|
125
|
+
input = STDIN.readpartial(1_024) rescue nil
|
126
|
+
if input
|
127
|
+
stdin.write(input)
|
128
|
+
else
|
129
|
+
# our own STDIN got closed; proxy to child's stdin
|
130
|
+
stdin.close
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
if ready && ready.include?(stderr)
|
135
|
+
data = stderr.readpartial(1_024) rescue nil
|
136
|
+
if data
|
137
|
+
STDERR.write(data) if @interactive
|
138
|
+
else
|
139
|
+
streams.delete(stderr)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
if ready && ready.include?(stdout)
|
144
|
+
data = stdout.readpartial(1_024) rescue nil
|
145
|
+
if data
|
146
|
+
output << data
|
147
|
+
STDOUT.write(data) if @interactive
|
148
|
+
else
|
149
|
+
streams.delete(stdout)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# This blocks until the process exits (which probably already happened,
|
155
|
+
# given that we have received EOF on its output streams).
|
156
|
+
status = thr.value.exitstatus
|
157
|
+
|
158
|
+
[status, output]
|
159
|
+
rescue Interrupt
|
160
|
+
# Proxy Ctrl+C to our child process
|
161
|
+
Process.kill('INT', thr.pid) rescue nil
|
162
|
+
raise
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: docker-compose
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tony Spataro
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-10-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.10'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Provides an OOP interface to docker-compose and facilitates container-to-host
|
56
|
+
and host-to-container networking.
|
57
|
+
email:
|
58
|
+
- xeger@xeger.net
|
59
|
+
executables: []
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- ".gitignore"
|
64
|
+
- ".rspec"
|
65
|
+
- ".ruby-version"
|
66
|
+
- ".travis.yml"
|
67
|
+
- CODE_OF_CONDUCT.md
|
68
|
+
- Gemfile
|
69
|
+
- LICENSE.txt
|
70
|
+
- README.md
|
71
|
+
- Rakefile
|
72
|
+
- bin/console
|
73
|
+
- bin/setup
|
74
|
+
- docker-compose.gemspec
|
75
|
+
- lib/docker/compose.rb
|
76
|
+
- lib/docker/compose/future/session.rb
|
77
|
+
- lib/docker/compose/mapper.rb
|
78
|
+
- lib/docker/compose/net_info.rb
|
79
|
+
- lib/docker/compose/rake_tasks.rb
|
80
|
+
- lib/docker/compose/session.rb
|
81
|
+
- lib/docker/compose/shell.rb
|
82
|
+
- lib/docker/compose/version.rb
|
83
|
+
homepage: https://github.com/xeger/docker-compose
|
84
|
+
licenses:
|
85
|
+
- MIT
|
86
|
+
metadata: {}
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
requirements: []
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 2.4.5
|
104
|
+
signing_key:
|
105
|
+
specification_version: 4
|
106
|
+
summary: Wrapper docker-compose with added Rake smarts.
|
107
|
+
test_files: []
|