docker-compose-api 1.0.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.
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/.travis.yml +17 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +69 -0
- data/docker-compose-api.gemspec +25 -0
- data/lib/docker-compose.rb +53 -0
- data/lib/docker-compose/models/compose.rb +86 -0
- data/lib/docker-compose/models/compose_container.rb +175 -0
- data/lib/docker-compose/models/compose_port.rb +9 -0
- data/lib/docker-compose/utils/compose_utils.rb +60 -0
- data/lib/version.rb +5 -0
- data/spec/docker-compose/docker_compose_spec.rb +167 -0
- data/spec/docker-compose/fixtures/sample1.yaml +16 -0
- data/spec/docker-compose/models/compose_container_spec.rb +79 -0
- data/spec/docker-compose/utils/compose_utils_spec.rb +54 -0
- data/spec/spec_helper.rb +6 -0
- metadata +117 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Mauricio S. Klein
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
[](https://travis-ci.org/mauricioklein/docker-compose-api)
|
2
|
+
[](https://codeclimate.com/github/mauricioklein/docker-compose-api)
|
3
|
+
[](https://codeclimate.com/github/mauricioklein/docker-compose-api/coverage)
|
4
|
+
|
5
|
+
# Docker Compose Api
|
6
|
+
|
7
|
+
Docker Compose API provides an easy way to parse docker compose files and lift the whole environment.
|
8
|
+
|
9
|
+
## Instalation
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
# Add the line below on your Gemfile...
|
13
|
+
gem 'docker-compose-api', git: 'https://github.com/mauricioklein/docker-compose-api'
|
14
|
+
|
15
|
+
# ... and run bundle install
|
16
|
+
bundle install
|
17
|
+
```
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
require 'docker-compose'
|
23
|
+
|
24
|
+
# Docker compose is simply a layer running over Docker client (https://github.com/swipely/docker-api).
|
25
|
+
# So, all Docker specific configurations, such URL, authentication, SSL, etc, must be made directly on
|
26
|
+
# Docker client.
|
27
|
+
#
|
28
|
+
# Docker compose provides an easy way to access this client:
|
29
|
+
DockerCompose.docker_client
|
30
|
+
|
31
|
+
# Gem version
|
32
|
+
DockerCompose.version
|
33
|
+
|
34
|
+
# Loading a compose file
|
35
|
+
compose = DockerCompose.load('[path to docker compose file]')
|
36
|
+
|
37
|
+
# Accessing containers
|
38
|
+
compose.containers # access all containers
|
39
|
+
compose.containers['[container name]'] # access a specific container
|
40
|
+
|
41
|
+
# Starting containers (and their dependencies)
|
42
|
+
compose.start # start all containers
|
43
|
+
compose.start(['container1', 'container2', ...]) # start a list of specific containers
|
44
|
+
|
45
|
+
# Stopping containers
|
46
|
+
# (ps: container dependencies will keep running)
|
47
|
+
compose.stop # stop all containers
|
48
|
+
compose.stop(['container1', 'container2', ...]) # stop a list of specific containers
|
49
|
+
|
50
|
+
# Killing containers
|
51
|
+
# (ps: container dependencies will keep running)
|
52
|
+
compose.kill # kill all containers
|
53
|
+
compose.kill(['container1', 'container2', ...]) # kill a list of specific containers
|
54
|
+
|
55
|
+
# Checking if a container is running or not
|
56
|
+
a_container = compose.containers['a_container']
|
57
|
+
a_container.running?
|
58
|
+
|
59
|
+
# Accessing container informations
|
60
|
+
a_container.stats
|
61
|
+
```
|
62
|
+
|
63
|
+
## Contributing
|
64
|
+
|
65
|
+
1. Fork it ( https://github.com/mauricioklein/docker-compose-api/fork )
|
66
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
67
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
68
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
69
|
+
5. Create a new Pull Request
|
@@ -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 'version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "docker-compose-api"
|
8
|
+
spec.version = DockerCompose.version
|
9
|
+
spec.authors = ["Mauricio S. Klein"]
|
10
|
+
spec.email = ["mauricio.klein.msk@gmail.com"]
|
11
|
+
spec.summary = %q{A simple ruby client for docker-compose api}
|
12
|
+
spec.description = %q{A simple ruby client for docker-compose api}
|
13
|
+
spec.homepage = "https://github.com/mauricioklein/docker-compose-api"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "docker-api", "~> 1.22.2"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler"
|
24
|
+
spec.add_development_dependency "rspec"
|
25
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require_relative 'docker-compose/models/compose'
|
2
|
+
require_relative 'docker-compose/models/compose_container'
|
3
|
+
require_relative 'version'
|
4
|
+
|
5
|
+
require 'yaml'
|
6
|
+
require 'docker'
|
7
|
+
|
8
|
+
module DockerCompose
|
9
|
+
#
|
10
|
+
# Get Docker client object
|
11
|
+
#
|
12
|
+
def self.docker_client
|
13
|
+
Docker
|
14
|
+
end
|
15
|
+
|
16
|
+
#
|
17
|
+
# Load a given docker-compose file.
|
18
|
+
# Returns a new Compose object
|
19
|
+
#
|
20
|
+
def self.load(filepath)
|
21
|
+
unless File.exist?(filepath)
|
22
|
+
puts("Compose file doesn't exists")
|
23
|
+
raise ENOENT
|
24
|
+
end
|
25
|
+
|
26
|
+
compose = Compose.new
|
27
|
+
|
28
|
+
_compose_entries = YAML.load_file(filepath)
|
29
|
+
_compose_entries.each do |entry|
|
30
|
+
compose.add_container(create_container(entry))
|
31
|
+
end
|
32
|
+
|
33
|
+
# Perform containers linkage
|
34
|
+
compose.link_containers
|
35
|
+
|
36
|
+
compose
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.create_container(attributes)
|
40
|
+
ComposeContainer.new({
|
41
|
+
label: attributes[0],
|
42
|
+
image: attributes[1]['image'],
|
43
|
+
build: attributes[1]['build'],
|
44
|
+
links: attributes[1]['links'],
|
45
|
+
ports: attributes[1]['ports'],
|
46
|
+
volumes: attributes[1]['volumes'],
|
47
|
+
command: attributes[1]['command'],
|
48
|
+
environment: attributes[1]['environment']
|
49
|
+
})
|
50
|
+
end
|
51
|
+
|
52
|
+
private_class_method :create_container
|
53
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'docker'
|
2
|
+
require_relative 'compose_port'
|
3
|
+
require_relative '../utils/compose_utils'
|
4
|
+
|
5
|
+
class Compose
|
6
|
+
attr_reader :containers
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@containers = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
public
|
13
|
+
|
14
|
+
#
|
15
|
+
# Add a new container to compose
|
16
|
+
#
|
17
|
+
def add_container(container)
|
18
|
+
@containers[container.attributes[:label]] = container
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# Create link relations among containers
|
24
|
+
#
|
25
|
+
def link_containers
|
26
|
+
@containers.each_value do |container|
|
27
|
+
links = container.attributes[:links]
|
28
|
+
|
29
|
+
next if links.nil?
|
30
|
+
|
31
|
+
links.each do |link|
|
32
|
+
dependency_container = @containers[link]
|
33
|
+
container.dependencies << dependency_container
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# Start a container
|
40
|
+
#
|
41
|
+
# This method accepts an array of labels.
|
42
|
+
# If labels is informed, only those containers with label present in array will be started.
|
43
|
+
# Otherwise, all containers are started
|
44
|
+
#
|
45
|
+
def start(labels = [])
|
46
|
+
call_container_method(:start, labels)
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# Stop a container
|
51
|
+
#
|
52
|
+
# This method accepts an array of labels.
|
53
|
+
# If labels is informed, only those containers with label present in array will be stopped.
|
54
|
+
# Otherwise, all containers are stopped
|
55
|
+
#
|
56
|
+
def stop(labels = [])
|
57
|
+
call_container_method(:stop, labels)
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# Stop a container
|
62
|
+
#
|
63
|
+
# This method accepts an array of labels.
|
64
|
+
# If labels is informed, only those containers with label present in array will be stopped.
|
65
|
+
# Otherwise, all containers are stopped
|
66
|
+
#
|
67
|
+
def kill(labels = [])
|
68
|
+
call_container_method(:kill, labels)
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def call_container_method(method, labels = [])
|
74
|
+
labels = @containers.keys if labels.empty?
|
75
|
+
|
76
|
+
containers = @containers.select { |key, value|
|
77
|
+
labels.include?(key)
|
78
|
+
}
|
79
|
+
|
80
|
+
containers.values.each do |entry|
|
81
|
+
entry.send(method)
|
82
|
+
end
|
83
|
+
|
84
|
+
true
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
require 'docker'
|
2
|
+
require_relative 'compose_port'
|
3
|
+
require_relative '../utils/compose_utils'
|
4
|
+
|
5
|
+
class ComposeContainer
|
6
|
+
attr_reader :attributes, :dependencies
|
7
|
+
|
8
|
+
def initialize(hash_attributes)
|
9
|
+
@attributes = {
|
10
|
+
label: hash_attributes[:label],
|
11
|
+
image: ComposeUtils.format_image(hash_attributes[:image]),
|
12
|
+
build: hash_attributes[:build],
|
13
|
+
links: hash_attributes[:links],
|
14
|
+
ports: prepare_ports(hash_attributes[:ports]),
|
15
|
+
volumes: hash_attributes[:volumes],
|
16
|
+
command: ComposeUtils.format_command(hash_attributes[:command]),
|
17
|
+
environment: hash_attributes[:environment]
|
18
|
+
}.reject{ |key, value| value.nil? }
|
19
|
+
|
20
|
+
# Docker client variables
|
21
|
+
@container = nil
|
22
|
+
@dependencies = []
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
#
|
28
|
+
# Download or build an image
|
29
|
+
#
|
30
|
+
def prepare_image
|
31
|
+
has_image_or_build_arg = @attributes.key?(:image) || @attributes.key?(:build)
|
32
|
+
|
33
|
+
raise ArgumentError.new('No Image or Build command provided') unless has_image_or_build_arg
|
34
|
+
|
35
|
+
# Build or pull image
|
36
|
+
if @attributes.key?(:image)
|
37
|
+
if image_exists
|
38
|
+
Docker::Image.get(@attributes[:image])
|
39
|
+
else
|
40
|
+
Docker::Image.create('fromImage' => @attributes[:image])
|
41
|
+
end
|
42
|
+
elsif @attributes.key?(:build)
|
43
|
+
Docker::Image.build_from_dir(@attributes[:build])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Start a new container with parameters informed in object construction
|
49
|
+
# (TODO: start container from a Dockerfile)
|
50
|
+
#
|
51
|
+
def prepare_container
|
52
|
+
exposed_ports = {}
|
53
|
+
port_bindings = {}
|
54
|
+
links = []
|
55
|
+
|
56
|
+
# Build expose and port binding parameters
|
57
|
+
if !@attributes[:ports].nil?
|
58
|
+
@attributes[:ports].each do |port|
|
59
|
+
exposed_ports["#{port.container_port}/tcp"] = {}
|
60
|
+
port_bindings["#{port.container_port}/tcp"] = [{
|
61
|
+
"HostIp" => port.host_ip || '',
|
62
|
+
"HostPort" => port.host_port || ''
|
63
|
+
}]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Build link parameters
|
68
|
+
@dependencies.each do |dependency|
|
69
|
+
links << "#{dependency.stats['Id']}:#{dependency.attributes[:label]}"
|
70
|
+
end
|
71
|
+
|
72
|
+
container_config = {
|
73
|
+
Image: @attributes[:image],
|
74
|
+
Cmd: @attributes[:command],
|
75
|
+
Env: @attributes[:environment],
|
76
|
+
Volumes: @attributes[:volumes],
|
77
|
+
ExposedPorts: exposed_ports,
|
78
|
+
HostConfig: {
|
79
|
+
Links: links,
|
80
|
+
PortBindings: port_bindings
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
@container = Docker::Container.create(container_config)
|
85
|
+
end
|
86
|
+
|
87
|
+
#
|
88
|
+
# Process each port entry in docker compose file and
|
89
|
+
# create structure recognized by docker client
|
90
|
+
#
|
91
|
+
def prepare_ports(port_entries)
|
92
|
+
ports = []
|
93
|
+
|
94
|
+
if port_entries.nil?
|
95
|
+
return nil
|
96
|
+
end
|
97
|
+
|
98
|
+
port_entries.each do |port_entry|
|
99
|
+
ports.push(ComposeUtils.format_port(port_entry))
|
100
|
+
end
|
101
|
+
|
102
|
+
ports
|
103
|
+
end
|
104
|
+
|
105
|
+
#
|
106
|
+
# Check if a given image already exists in host
|
107
|
+
#
|
108
|
+
def image_exists
|
109
|
+
Docker::Image.exist?(@attributes[:image])
|
110
|
+
end
|
111
|
+
|
112
|
+
public
|
113
|
+
|
114
|
+
#
|
115
|
+
# Start the container and its dependencies
|
116
|
+
#
|
117
|
+
def start
|
118
|
+
# Start dependencies
|
119
|
+
@dependencies.each do |dependency|
|
120
|
+
dependency.start unless dependency.running?
|
121
|
+
end
|
122
|
+
|
123
|
+
# Create a container object
|
124
|
+
if @container.nil?
|
125
|
+
prepare_image
|
126
|
+
prepare_container
|
127
|
+
end
|
128
|
+
|
129
|
+
@container.start unless @container.nil?
|
130
|
+
end
|
131
|
+
|
132
|
+
#
|
133
|
+
# Stop the container
|
134
|
+
#
|
135
|
+
def stop
|
136
|
+
@container.kill unless @container.nil?
|
137
|
+
end
|
138
|
+
|
139
|
+
#
|
140
|
+
# Kill the container
|
141
|
+
#
|
142
|
+
def kill
|
143
|
+
@container.kill unless @container.nil?
|
144
|
+
end
|
145
|
+
|
146
|
+
#
|
147
|
+
# Delete the container
|
148
|
+
#
|
149
|
+
def delete
|
150
|
+
@container.delete(:force => true) unless @container.nil?
|
151
|
+
@container = nil
|
152
|
+
end
|
153
|
+
|
154
|
+
#
|
155
|
+
# Add a dependency to this container
|
156
|
+
# (i.e. a container that must be started before this one)
|
157
|
+
#
|
158
|
+
def add_dependency(dependency)
|
159
|
+
@dependencies << dependency
|
160
|
+
end
|
161
|
+
|
162
|
+
#
|
163
|
+
# Return container statistics
|
164
|
+
#
|
165
|
+
def stats
|
166
|
+
@container.json
|
167
|
+
end
|
168
|
+
|
169
|
+
#
|
170
|
+
# Check if a container is already running or not
|
171
|
+
#
|
172
|
+
def running?
|
173
|
+
@container.nil? ? false : self.stats['State']['Running']
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module ComposeUtils
|
2
|
+
#
|
3
|
+
# Format a given docker image in a complete structure (base image + tag)
|
4
|
+
#
|
5
|
+
def self.format_image(image)
|
6
|
+
base_image = nil
|
7
|
+
tag = nil
|
8
|
+
|
9
|
+
if image.nil?
|
10
|
+
return nil
|
11
|
+
end
|
12
|
+
|
13
|
+
if image.index(':').nil?
|
14
|
+
base_image = image
|
15
|
+
tag = 'latest'
|
16
|
+
else
|
17
|
+
image_split = image.split(':')
|
18
|
+
base_image = image_split[0]
|
19
|
+
tag = image_split[1]
|
20
|
+
end
|
21
|
+
|
22
|
+
"#{base_image}:#{tag}"
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# Transform docker command from string to an array of commands
|
27
|
+
#
|
28
|
+
def self.format_command(command)
|
29
|
+
command.nil? ? nil : command.split(' ')
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# Read a port specification in string format
|
34
|
+
# and create a compose port structure
|
35
|
+
#
|
36
|
+
def self.format_port(port_entry)
|
37
|
+
compose_port = nil
|
38
|
+
container_port = nil
|
39
|
+
host_port = nil
|
40
|
+
host_ip = nil
|
41
|
+
|
42
|
+
port_parts = port_entry.split(':')
|
43
|
+
|
44
|
+
case port_parts.length
|
45
|
+
# [container port]
|
46
|
+
when 1
|
47
|
+
compose_port = ComposePort.new(port_parts[0])
|
48
|
+
|
49
|
+
# [host port]:[container port]
|
50
|
+
when 2
|
51
|
+
compose_port = ComposePort.new(port_parts[1], port_parts[0])
|
52
|
+
|
53
|
+
# [host ip]:[host port]:[container port]
|
54
|
+
when 3
|
55
|
+
compose_port = ComposePort.new(port_parts[2], port_parts[1], port_parts[0])
|
56
|
+
end
|
57
|
+
|
58
|
+
compose_port
|
59
|
+
end
|
60
|
+
end
|
data/lib/version.rb
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DockerCompose do
|
4
|
+
before(:all) do
|
5
|
+
@compose = DockerCompose.load(File.expand_path('spec/docker-compose/fixtures/sample1.yaml'))
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'should be able to access gem version' do
|
9
|
+
expect(DockerCompose.version).to_not be_nil
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should be able to access Docker client' do
|
13
|
+
expect(DockerCompose.docker_client).to_not be_nil
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should read a YAML file correctly' do
|
17
|
+
expect(@compose.containers.length).to eq(2)
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'All containers' do
|
21
|
+
it 'should start/stop all containers' do
|
22
|
+
# Start containers to test Stop
|
23
|
+
@compose.start
|
24
|
+
@compose.containers.values.each do |container|
|
25
|
+
expect(container.running?).to be true
|
26
|
+
end
|
27
|
+
|
28
|
+
# Stop containers
|
29
|
+
@compose.stop
|
30
|
+
@compose.containers.values.each do |container|
|
31
|
+
expect(container.running?).to be false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should start/kill all containers' do
|
36
|
+
# Start containers to test Kill
|
37
|
+
@compose.start
|
38
|
+
@compose.containers.values.each do |container|
|
39
|
+
expect(container.running?).to be true
|
40
|
+
end
|
41
|
+
|
42
|
+
# Kill containers
|
43
|
+
@compose.kill
|
44
|
+
@compose.containers.values.each do |container|
|
45
|
+
expect(container.running?).to be false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'Single container' do
|
51
|
+
context 'Without dependencies' do
|
52
|
+
it 'should start/stop a single container' do
|
53
|
+
ubuntu = @compose.containers.values.first.attributes[:label]
|
54
|
+
redis = @compose.containers.values.last.attributes[:label]
|
55
|
+
|
56
|
+
# Should start Redis only, since it hasn't dependencies
|
57
|
+
@compose.start([redis])
|
58
|
+
expect(@compose.containers[ubuntu].running?).to be false
|
59
|
+
expect(@compose.containers[redis].running?).to be true
|
60
|
+
|
61
|
+
# Stop Redis
|
62
|
+
@compose.stop([redis])
|
63
|
+
expect(@compose.containers[ubuntu].running?).to be false
|
64
|
+
expect(@compose.containers[redis].running?).to be false
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should start/kill a single container' do
|
68
|
+
ubuntu = @compose.containers.values.first.attributes[:label]
|
69
|
+
redis = @compose.containers.values.last.attributes[:label]
|
70
|
+
|
71
|
+
# Should start Redis only, since it hasn't dependencies
|
72
|
+
@compose.start([redis])
|
73
|
+
expect(@compose.containers[ubuntu].running?).to be false
|
74
|
+
expect(@compose.containers[redis].running?).to be true
|
75
|
+
|
76
|
+
# Stop Redis
|
77
|
+
@compose.kill([redis])
|
78
|
+
expect(@compose.containers[ubuntu].running?).to be false
|
79
|
+
expect(@compose.containers[redis].running?).to be false
|
80
|
+
end
|
81
|
+
end # context 'Without dependencies'
|
82
|
+
|
83
|
+
context 'With dependencies' do
|
84
|
+
it 'should start/stop a single container' do
|
85
|
+
ubuntu = @compose.containers.values.first.attributes[:label]
|
86
|
+
redis = @compose.containers.values.last.attributes[:label]
|
87
|
+
|
88
|
+
# Should start Ubuntu and Redis, since Ubuntu depends on Redis
|
89
|
+
@compose.start([ubuntu])
|
90
|
+
expect(@compose.containers[ubuntu].running?).to be true
|
91
|
+
expect(@compose.containers[redis].running?).to be true
|
92
|
+
|
93
|
+
# Stop Ubuntu (Redis keeps running)
|
94
|
+
@compose.stop([ubuntu])
|
95
|
+
expect(@compose.containers[ubuntu].running?).to be false
|
96
|
+
expect(@compose.containers[redis].running?).to be true
|
97
|
+
|
98
|
+
# Stop Redis
|
99
|
+
@compose.stop([redis])
|
100
|
+
expect(@compose.containers[redis].running?).to be false
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should start/kill a single container' do
|
104
|
+
ubuntu = @compose.containers.values.first.attributes[:label]
|
105
|
+
redis = @compose.containers.values.last.attributes[:label]
|
106
|
+
|
107
|
+
# Should start Ubuntu and Redis, since Ubuntu depends on Redis
|
108
|
+
@compose.start([ubuntu])
|
109
|
+
expect(@compose.containers[ubuntu].running?).to be true
|
110
|
+
expect(@compose.containers[redis].running?).to be true
|
111
|
+
|
112
|
+
# Kill Ubuntu (Redis keeps running)
|
113
|
+
@compose.kill([ubuntu])
|
114
|
+
expect(@compose.containers[ubuntu].running?).to be false
|
115
|
+
expect(@compose.containers[redis].running?).to be true
|
116
|
+
|
117
|
+
# Kill Redis
|
118
|
+
@compose.kill([redis])
|
119
|
+
expect(@compose.containers[redis].running?).to be false
|
120
|
+
end
|
121
|
+
end # context 'with dependencies'
|
122
|
+
end # context 'Single container'
|
123
|
+
|
124
|
+
it 'should assign ports' do
|
125
|
+
ubuntu = @compose.containers.values.first
|
126
|
+
|
127
|
+
# Start container
|
128
|
+
ubuntu.start
|
129
|
+
|
130
|
+
port_bindings = ubuntu.stats['HostConfig']['PortBindings']
|
131
|
+
exposed_ports = ubuntu.stats['Config']['ExposedPorts']
|
132
|
+
|
133
|
+
# Check port bindings
|
134
|
+
expect(port_bindings.length).to eq(3)
|
135
|
+
expect(port_bindings.key?('3000/tcp')).to be true
|
136
|
+
expect(port_bindings.key?('8000/tcp')).to be true
|
137
|
+
expect(port_bindings.key?('8001/tcp')).to be true
|
138
|
+
|
139
|
+
# Check exposed ports
|
140
|
+
expect(exposed_ports.key?('3000/tcp')).to be true
|
141
|
+
expect(exposed_ports.key?('8000/tcp')).to be true
|
142
|
+
expect(exposed_ports.key?('8001/tcp')).to be true
|
143
|
+
|
144
|
+
# Stop container
|
145
|
+
ubuntu.stop
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'should link containers' do
|
149
|
+
ubuntu = @compose.containers.values.first
|
150
|
+
|
151
|
+
# Start container
|
152
|
+
ubuntu.start
|
153
|
+
|
154
|
+
# Ubuntu should be linked to Redis
|
155
|
+
links = ubuntu.stats['HostConfig']['Links']
|
156
|
+
expect(links.length).to eq(1)
|
157
|
+
|
158
|
+
# Stop container
|
159
|
+
ubuntu.stop
|
160
|
+
end
|
161
|
+
|
162
|
+
after(:all) do
|
163
|
+
@compose.containers.values.each do |entry|
|
164
|
+
entry.delete
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ComposeContainer do
|
4
|
+
context 'Object creation' do
|
5
|
+
it 'should prepare the attributes correctly' do
|
6
|
+
attributes = {
|
7
|
+
image: 'ubuntu:latest',
|
8
|
+
links: ['links:links'],
|
9
|
+
ports: ['3000', '8000:8000', '127.0.0.1:8001:8001'],
|
10
|
+
volumes: {'/tmp' => {}},
|
11
|
+
command: 'ps aux',
|
12
|
+
environment: ['ENVIRONMENT']
|
13
|
+
}
|
14
|
+
|
15
|
+
entry = ComposeContainer.new(attributes)
|
16
|
+
|
17
|
+
expect(entry.attributes[:image]).to eq(attributes[:image])
|
18
|
+
expect(entry.attributes[:links]).to eq(attributes[:links])
|
19
|
+
expect(entry.attributes[:volumes]).to eq(attributes[:volumes])
|
20
|
+
expect(entry.attributes[:command]).to eq(attributes[:command].split(' '))
|
21
|
+
expect(entry.attributes[:environment]).to eq(attributes[:environment])
|
22
|
+
|
23
|
+
# Check ports structure
|
24
|
+
expect(entry.attributes[:ports].length).to eq(attributes[:ports].length)
|
25
|
+
|
26
|
+
# Port 1: '3000'
|
27
|
+
port_entry = entry.attributes[:ports][0]
|
28
|
+
expect(port_entry.container_port).to eq('3000')
|
29
|
+
expect(port_entry.host_ip).to eq(nil)
|
30
|
+
expect(port_entry.host_port).to eq(nil)
|
31
|
+
|
32
|
+
# Port 2: '8000:8000'
|
33
|
+
port_entry = entry.attributes[:ports][1]
|
34
|
+
expect(port_entry.container_port).to eq('8000')
|
35
|
+
expect(port_entry.host_ip).to eq(nil)
|
36
|
+
expect(port_entry.host_port).to eq('8000')
|
37
|
+
|
38
|
+
# Port 3: '127.0.0.1:8001:8001'
|
39
|
+
port_entry = entry.attributes[:ports][2]
|
40
|
+
expect(port_entry.container_port).to eq('8001')
|
41
|
+
expect(port_entry.host_ip).to eq('127.0.0.1')
|
42
|
+
expect(port_entry.host_port).to eq('8001')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'Start container' do
|
47
|
+
it 'should start/stop a container' do
|
48
|
+
attributes = {
|
49
|
+
image: 'ubuntu:latest',
|
50
|
+
links: ['links:links'],
|
51
|
+
volumes: {'/tmp' => {}},
|
52
|
+
command: 'ps aux',
|
53
|
+
environment: ['ENVIRONMENT']
|
54
|
+
}
|
55
|
+
|
56
|
+
entry = ComposeContainer.new(attributes)
|
57
|
+
|
58
|
+
#Start container
|
59
|
+
entry.start
|
60
|
+
expect(entry.running?).to be true
|
61
|
+
|
62
|
+
# Stop container
|
63
|
+
entry.stop
|
64
|
+
expect(entry.running?).to be false
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should not start a container without either image and build commands' do
|
68
|
+
attributes = {
|
69
|
+
links: ['links:links'],
|
70
|
+
volumes: {'/tmp' => {}},
|
71
|
+
command: 'ps aux',
|
72
|
+
environment: ['ENVIRONMENT']
|
73
|
+
}
|
74
|
+
|
75
|
+
entry = ComposeContainer.new(attributes)
|
76
|
+
expect{entry.start}.to raise_error(ArgumentError)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ComposeUtils do
|
4
|
+
context 'Format image' do
|
5
|
+
it 'should return nil when image is nil' do
|
6
|
+
expect(ComposeUtils.format_image(nil)).to be_nil
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should assign latest tag when no tag is provided' do
|
10
|
+
expect(ComposeUtils.format_image('ubuntu')).to eq('ubuntu:latest')
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should assign base image and tag when both are provided' do
|
14
|
+
expect(ComposeUtils.format_image('ubuntu:11')).to eq('ubuntu:11')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'Format command' do
|
19
|
+
it 'should return nil when command is nil' do
|
20
|
+
expect(ComposeUtils.format_command(nil)).to be_nil
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should return original command as array when command has no whitespaces' do
|
24
|
+
expect(ComposeUtils.format_command('top')).to eq(['top'])
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should split command in array on whitespaces' do
|
28
|
+
expect(ComposeUtils.format_command('ls -lh')).to eq(['ls', '-lh'])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'Format port' do
|
33
|
+
it 'should recognize pattern "[container port]"' do
|
34
|
+
compose_port = ComposeUtils.format_port('8080')
|
35
|
+
expect(compose_port.container_port).to eq('8080')
|
36
|
+
expect(compose_port.host_port).to eq(nil)
|
37
|
+
expect(compose_port.host_ip).to eq(nil)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should recognize pattern "[host port]:[container port]"' do
|
41
|
+
compose_port = ComposeUtils.format_port('8080:7777')
|
42
|
+
expect(compose_port.container_port).to eq('7777')
|
43
|
+
expect(compose_port.host_port).to eq('8080')
|
44
|
+
expect(compose_port.host_ip).to eq(nil)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should recognize pattern "[host ip]:[host port]:[container port]' do
|
48
|
+
compose_port = ComposeUtils.format_port('127.0.0.1:8080:7777')
|
49
|
+
expect(compose_port.container_port).to eq('7777')
|
50
|
+
expect(compose_port.host_port).to eq('8080')
|
51
|
+
expect(compose_port.host_ip).to eq('127.0.0.1')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: docker-compose-api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Mauricio S. Klein
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2015-10-23 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: docker-api
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.22.2
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.22.2
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: bundler
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: A simple ruby client for docker-compose api
|
63
|
+
email:
|
64
|
+
- mauricio.klein.msk@gmail.com
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- .rspec
|
71
|
+
- .travis.yml
|
72
|
+
- Gemfile
|
73
|
+
- LICENSE.txt
|
74
|
+
- README.md
|
75
|
+
- docker-compose-api.gemspec
|
76
|
+
- lib/docker-compose.rb
|
77
|
+
- lib/docker-compose/models/compose.rb
|
78
|
+
- lib/docker-compose/models/compose_container.rb
|
79
|
+
- lib/docker-compose/models/compose_port.rb
|
80
|
+
- lib/docker-compose/utils/compose_utils.rb
|
81
|
+
- lib/version.rb
|
82
|
+
- spec/docker-compose/docker_compose_spec.rb
|
83
|
+
- spec/docker-compose/fixtures/sample1.yaml
|
84
|
+
- spec/docker-compose/models/compose_container_spec.rb
|
85
|
+
- spec/docker-compose/utils/compose_utils_spec.rb
|
86
|
+
- spec/spec_helper.rb
|
87
|
+
homepage: https://github.com/mauricioklein/docker-compose-api
|
88
|
+
licenses:
|
89
|
+
- MIT
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options: []
|
92
|
+
require_paths:
|
93
|
+
- lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ! '>='
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ! '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 1.8.23
|
109
|
+
signing_key:
|
110
|
+
specification_version: 3
|
111
|
+
summary: A simple ruby client for docker-compose api
|
112
|
+
test_files:
|
113
|
+
- spec/docker-compose/docker_compose_spec.rb
|
114
|
+
- spec/docker-compose/fixtures/sample1.yaml
|
115
|
+
- spec/docker-compose/models/compose_container_spec.rb
|
116
|
+
- spec/docker-compose/utils/compose_utils_spec.rb
|
117
|
+
- spec/spec_helper.rb
|