docker-compose-api 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/mauricioklein/docker-compose-api.svg?branch=develop)](https://travis-ci.org/mauricioklein/docker-compose-api)
|
2
|
+
[![Code Climate](https://codeclimate.com/github/mauricioklein/docker-compose-api/badges/gpa.svg)](https://codeclimate.com/github/mauricioklein/docker-compose-api)
|
3
|
+
[![Test Coverage](https://codeclimate.com/github/mauricioklein/docker-compose-api/badges/coverage.svg)](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
|