docker-compose-api 1.0.5 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +15 -0
- data/docker-compose-api.gemspec +1 -1
- data/lib/docker-compose.rb +39 -5
- data/lib/docker-compose/models/compose.rb +15 -0
- data/lib/docker-compose/models/compose_container.rb +15 -5
- data/lib/docker-compose/utils/compose_utils.rb +64 -0
- data/lib/version.rb +1 -1
- data/spec/docker-compose/docker_compose_spec.rb +264 -218
- data/spec/docker-compose/fixtures/compose_1.yaml +4 -0
- data/spec/docker-compose/fixtures/empty_compose.yml +0 -0
- data/spec/docker-compose/models/compose_container_spec.rb +30 -5
- data/spec/docker-compose/models/compose_spec.rb +13 -0
- data/spec/docker-compose/utils/compose_utils_spec.rb +37 -0
- metadata +8 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cf18202df3237d79dc3ef72446eec4f226d12ec0
|
4
|
+
data.tar.gz: 1810eb1c15212070d375d6bf626cd8b94ab7bd81
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4108355ffb6c39c9424b98ce271b83f1e992d3bfd9bbc067c5d570ba1996ec946af63de1babecb8640dfa84452eb930654060c551ecee2e23025a7bfdd6cbb75
|
7
|
+
data.tar.gz: da6d88fe21804f589edcbdb5287518c0b8d9f8a9e7cbec06c701b9c43c4347a6c999ec979086bfd169b4cd5cefc0c85b622c0a04dd3269f8e7fa2829e320d8ab
|
data/README.md
CHANGED
@@ -43,11 +43,26 @@ DockerCompose.version
|
|
43
43
|
# Loading a compose file
|
44
44
|
compose = DockerCompose.load('[path to docker compose file]')
|
45
45
|
|
46
|
+
# 'Load' method accepts a second argument, telling to do load or not
|
47
|
+
# containers started previously by this compose file.
|
48
|
+
#
|
49
|
+
# So, loading a compose file + containers started by this compose previously
|
50
|
+
compose = DockerCompose.load('[path to docker compose file]', true)
|
51
|
+
|
46
52
|
# Accessing containers
|
47
53
|
compose.containers # access all containers
|
48
54
|
compose.containers['container_label'] # access a container by its label (DEPRECATED)
|
49
55
|
compose.get_containers_by(label: 'foo', name: 'bar') # Returns an array of all containers with label = 'foo' and name = bar
|
50
56
|
|
57
|
+
# Containers names are generated using the pattern below:
|
58
|
+
# [Directory name]_[Container label]_[Sequential ID]
|
59
|
+
#
|
60
|
+
# So, you can access a container by its full name...
|
61
|
+
compose.get_containers_by(name: 'myawessomedir_foobar_1')
|
62
|
+
|
63
|
+
# ... or by its given name (ignores both prefix and suffix)
|
64
|
+
compose.get_containers_by_given_name('foobar')
|
65
|
+
|
51
66
|
# Starting containers (and their dependencies)
|
52
67
|
compose.start # start all containers
|
53
68
|
compose.start(['container1', 'container2', ...]) # start a list of specific containers
|
data/docker-compose-api.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_dependency "docker-api", "1.22
|
21
|
+
spec.add_dependency "docker-api", "~> 1.22"
|
22
22
|
|
23
23
|
spec.add_development_dependency "bundler", "~> 1.7"
|
24
24
|
spec.add_development_dependency "rspec", "~> 3.3"
|
data/lib/docker-compose.rb
CHANGED
@@ -17,16 +17,30 @@ module DockerCompose
|
|
17
17
|
# Load a given docker-compose file.
|
18
18
|
# Returns a new Compose object
|
19
19
|
#
|
20
|
-
def self.load(filepath)
|
20
|
+
def self.load(filepath, do_load_running_containers = false)
|
21
21
|
unless File.exist?(filepath)
|
22
22
|
raise ArgumentError, 'Compose file doesn\'t exists'
|
23
23
|
end
|
24
24
|
|
25
25
|
compose = Compose.new
|
26
26
|
|
27
|
+
# Create containers from compose file
|
27
28
|
_compose_entries = YAML.load_file(filepath)
|
28
|
-
|
29
|
-
|
29
|
+
|
30
|
+
if _compose_entries
|
31
|
+
_compose_entries.each do |entry|
|
32
|
+
compose.add_container(create_container(entry))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Load running containers
|
37
|
+
if do_load_running_containers
|
38
|
+
Docker::Container
|
39
|
+
.all(all: true)
|
40
|
+
.select {|c| c.info['Names'].last.match(/\A\/#{ComposeUtils.dir_name}\w*/) }
|
41
|
+
.each do |container|
|
42
|
+
compose.add_container(load_running_container(container))
|
43
|
+
end
|
30
44
|
end
|
31
45
|
|
32
46
|
# Perform containers linkage
|
@@ -45,9 +59,29 @@ module DockerCompose
|
|
45
59
|
ports: attributes[1]['ports'],
|
46
60
|
volumes: attributes[1]['volumes'],
|
47
61
|
command: attributes[1]['command'],
|
48
|
-
environment: attributes[1]['environment']
|
62
|
+
environment: attributes[1]['environment'],
|
63
|
+
labels: attributes[1]['labels']
|
49
64
|
})
|
50
65
|
end
|
51
66
|
|
52
|
-
|
67
|
+
def self.load_running_container(container)
|
68
|
+
info = container.json
|
69
|
+
|
70
|
+
container_args = {
|
71
|
+
label: info['Name'].split(/_/)[1] || '',
|
72
|
+
full_name: info['Name'],
|
73
|
+
image: info['Image'],
|
74
|
+
build: nil,
|
75
|
+
links: info['HostConfig']['Links'],
|
76
|
+
ports: ComposeUtils.format_ports_from_running_container(info['NetworkSettings']['Ports']),
|
77
|
+
volumes: info['Config']['Volumes'],
|
78
|
+
command: info['Config']['Cmd'].join(' '),
|
79
|
+
environment: info['Config']['Env'],
|
80
|
+
labels: info['Config']['Labels']
|
81
|
+
}
|
82
|
+
|
83
|
+
ComposeContainer.new(container_args, container)
|
84
|
+
end
|
85
|
+
|
86
|
+
private_class_method :create_container, :load_running_container
|
53
87
|
end
|
@@ -15,6 +15,11 @@ class Compose
|
|
15
15
|
# Add a new container to compose
|
16
16
|
#
|
17
17
|
def add_container(container)
|
18
|
+
# Avoid duplicated labels on compose
|
19
|
+
while @containers.has_key?(container.attributes[:label]) do
|
20
|
+
container.attributes[:label].succ!
|
21
|
+
end
|
22
|
+
|
18
23
|
@containers[container.attributes[:label]] = container
|
19
24
|
true
|
20
25
|
end
|
@@ -28,6 +33,16 @@ class Compose
|
|
28
33
|
end
|
29
34
|
end
|
30
35
|
|
36
|
+
#
|
37
|
+
# Select containers based on its given name
|
38
|
+
# (ignore basename)
|
39
|
+
#
|
40
|
+
def get_containers_by_given_name(given_name)
|
41
|
+
@containers.select { |label, container|
|
42
|
+
container.attributes[:name].match(/#{ComposeUtils.dir_name}_#{given_name}_\d+/)
|
43
|
+
}.values
|
44
|
+
end
|
45
|
+
|
31
46
|
#
|
32
47
|
# Create link relations among containers
|
33
48
|
#
|
@@ -4,24 +4,25 @@ require_relative 'compose_port'
|
|
4
4
|
require_relative '../utils/compose_utils'
|
5
5
|
|
6
6
|
class ComposeContainer
|
7
|
-
attr_reader :attributes, :container, :dependencies
|
7
|
+
attr_reader :attributes, :internal_image, :container, :dependencies
|
8
8
|
|
9
|
-
def initialize(hash_attributes)
|
9
|
+
def initialize(hash_attributes, docker_container = nil)
|
10
10
|
@attributes = {
|
11
11
|
label: hash_attributes[:label],
|
12
|
-
name: hash_attributes[:
|
12
|
+
name: hash_attributes[:full_name] || ComposeUtils.generate_container_name(hash_attributes[:name], hash_attributes[:label]),
|
13
13
|
image: ComposeUtils.format_image(hash_attributes[:image]),
|
14
14
|
build: hash_attributes[:build],
|
15
15
|
links: ComposeUtils.format_links(hash_attributes[:links]),
|
16
16
|
ports: prepare_ports(hash_attributes[:ports]),
|
17
17
|
volumes: hash_attributes[:volumes],
|
18
18
|
command: ComposeUtils.format_command(hash_attributes[:command]),
|
19
|
-
environment: prepare_environment(hash_attributes[:environment])
|
19
|
+
environment: prepare_environment(hash_attributes[:environment]),
|
20
|
+
labels: prepare_labels(hash_attributes[:labels])
|
20
21
|
}.reject{ |key, value| value.nil? }
|
21
22
|
|
22
23
|
# Docker client variables
|
23
24
|
@internal_image = nil
|
24
|
-
@container =
|
25
|
+
@container = docker_container
|
25
26
|
@dependencies = []
|
26
27
|
end
|
27
28
|
|
@@ -68,6 +69,7 @@ class ComposeContainer
|
|
68
69
|
Env: @attributes[:environment],
|
69
70
|
Volumes: volumes,
|
70
71
|
ExposedPorts: exposed_ports,
|
72
|
+
Labels: @attributes[:labels],
|
71
73
|
HostConfig: {
|
72
74
|
Binds: volume_binds,
|
73
75
|
Links: links,
|
@@ -163,6 +165,14 @@ class ComposeContainer
|
|
163
165
|
env_entries.to_a.map { |x| x.join('=') }
|
164
166
|
end
|
165
167
|
|
168
|
+
#
|
169
|
+
# Forces the labels structure to use the hash format.
|
170
|
+
#
|
171
|
+
def prepare_labels(labels)
|
172
|
+
return labels unless labels.is_a?(Array)
|
173
|
+
Hash[labels.map { |label| label.split('=') }]
|
174
|
+
end
|
175
|
+
|
166
176
|
#
|
167
177
|
# Check if a given image already exists in host
|
168
178
|
#
|
@@ -1,4 +1,29 @@
|
|
1
1
|
module ComposeUtils
|
2
|
+
@dir_name = File.split(Dir.pwd).last.gsub(/[-_]/, '')
|
3
|
+
@current_container_id = nil
|
4
|
+
|
5
|
+
#
|
6
|
+
# Returns the directory name where compose
|
7
|
+
# file is saved (used in container naming)
|
8
|
+
#
|
9
|
+
def self.dir_name
|
10
|
+
@dir_name
|
11
|
+
end
|
12
|
+
|
13
|
+
#
|
14
|
+
# Provides the next available ID
|
15
|
+
# to container names
|
16
|
+
#
|
17
|
+
def self.next_available_id
|
18
|
+
if @current_container_id.nil?
|
19
|
+
# Discovery the max id used by already running containers
|
20
|
+
# (default to '0' if no container is running)
|
21
|
+
@current_container_id = Docker::Container.all(all: true).map {|c| c.info['Names'].last.split(/_/).last.to_i}.flatten.max || 0
|
22
|
+
end
|
23
|
+
|
24
|
+
@current_container_id += 1
|
25
|
+
end
|
26
|
+
|
2
27
|
#
|
3
28
|
# Format a given docker image in a complete structure (base image + tag)
|
4
29
|
#
|
@@ -58,6 +83,30 @@ module ComposeUtils
|
|
58
83
|
compose_port
|
59
84
|
end
|
60
85
|
|
86
|
+
#
|
87
|
+
# Format ports from running container
|
88
|
+
#
|
89
|
+
def self.format_ports_from_running_container(port_entry)
|
90
|
+
entries = []
|
91
|
+
container_port = nil
|
92
|
+
host_ip = nil
|
93
|
+
host_port = nil
|
94
|
+
|
95
|
+
if port_entry.nil?
|
96
|
+
return entries
|
97
|
+
end
|
98
|
+
|
99
|
+
port_entry.each do |key, value|
|
100
|
+
container_port = key.gsub(/\D/, '').to_i
|
101
|
+
host_ip = value.first['HostIp']
|
102
|
+
host_port = value.first['HostPort']
|
103
|
+
|
104
|
+
entries << "#{container_port}:#{host_ip}:#{host_port}"
|
105
|
+
end
|
106
|
+
|
107
|
+
entries
|
108
|
+
end
|
109
|
+
|
61
110
|
#
|
62
111
|
# Generate a pair key:hash with
|
63
112
|
# format {service:label}
|
@@ -83,4 +132,19 @@ module ComposeUtils
|
|
83
132
|
|
84
133
|
links
|
85
134
|
end
|
135
|
+
|
136
|
+
#
|
137
|
+
# Generate a container name, based on:
|
138
|
+
# - directory where the compose file is saved;
|
139
|
+
# - container name (or label, if name isn't provided);
|
140
|
+
# - a sequential index;
|
141
|
+
#
|
142
|
+
def self.generate_container_name(container_name, container_label)
|
143
|
+
label = container_name.nil? ? container_label : container_name
|
144
|
+
index = next_available_id
|
145
|
+
|
146
|
+
"#{@dir_name}_#{label}_#{index}"
|
147
|
+
end
|
148
|
+
|
149
|
+
private_class_method :next_available_id
|
86
150
|
end
|
data/lib/version.rb
CHANGED
@@ -1,286 +1,332 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe DockerCompose do
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
context 'Without memory' do
|
5
|
+
before(:all) do
|
6
|
+
@compose = DockerCompose.load(File.expand_path('spec/docker-compose/fixtures/compose_1.yaml'))
|
7
|
+
end
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
9
|
+
it 'should be able to access gem version' do
|
10
|
+
expect(DockerCompose.version).to_not be_nil
|
11
|
+
end
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
it 'should be able to access Docker client' do
|
14
|
+
expect(DockerCompose.docker_client).to_not be_nil
|
15
|
+
end
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
17
|
+
it 'should read a YAML file correctly' do
|
18
|
+
expect(@compose.containers.length).to eq(3)
|
19
|
+
end
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
21
|
+
it 'should raise error when reading an invalid YAML file' do
|
22
|
+
expect{DockerCompose.load('')}.to raise_error(ArgumentError)
|
23
|
+
end
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
25
|
+
context 'All containers' do
|
26
|
+
it 'should start/stop all containers' do
|
27
|
+
# Start containers to test Stop
|
28
|
+
@compose.start
|
29
|
+
@compose.containers.values.each do |container|
|
30
|
+
expect(container.running?).to be true
|
31
|
+
end
|
32
|
+
|
33
|
+
# Stop containers
|
34
|
+
@compose.stop
|
35
|
+
@compose.containers.values.each do |container|
|
36
|
+
expect(container.running?).to be false
|
37
|
+
end
|
30
38
|
end
|
31
39
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
40
|
+
it 'should start/kill all containers' do
|
41
|
+
# Start containers to test Kill
|
42
|
+
@compose.start
|
43
|
+
@compose.containers.values.each do |container|
|
44
|
+
expect(container.running?).to be true
|
45
|
+
end
|
46
|
+
|
47
|
+
# Kill containers
|
48
|
+
@compose.kill
|
49
|
+
@compose.containers.values.each do |container|
|
50
|
+
expect(container.running?).to be false
|
51
|
+
end
|
44
52
|
end
|
45
53
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
54
|
+
it 'should start/delete all containers' do
|
55
|
+
# Start containers to test Delete
|
56
|
+
@compose.start
|
57
|
+
@compose.containers.values.each do |container|
|
58
|
+
expect(container.running?).to be true
|
59
|
+
end
|
60
|
+
|
61
|
+
# Delete containers
|
62
|
+
@compose.delete
|
63
|
+
@compose.containers.values.each do |container|
|
64
|
+
expect(container.exist?).to be false
|
65
|
+
end
|
50
66
|
end
|
51
67
|
end
|
52
68
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
69
|
+
context 'Single container' do
|
70
|
+
context 'Without dependencies' do
|
71
|
+
it 'should start/stop a single container' do
|
72
|
+
container1 = @compose.containers.values.first.attributes[:label]
|
73
|
+
container2 = @compose.containers.values[1].attributes[:label]
|
74
|
+
|
75
|
+
@compose.start([container2])
|
76
|
+
expect(@compose.containers[container1].running?).to be false
|
77
|
+
expect(@compose.containers[container2].running?).to be true
|
78
|
+
|
79
|
+
@compose.stop([container2])
|
80
|
+
expect(@compose.containers[container1].running?).to be false
|
81
|
+
expect(@compose.containers[container2].running?).to be false
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'should start/kill a single container' do
|
85
|
+
container1 = @compose.containers.values.first.attributes[:label]
|
86
|
+
container2 = @compose.containers.values[1].attributes[:label]
|
87
|
+
|
88
|
+
@compose.start([container2])
|
89
|
+
expect(@compose.containers[container1].running?).to be false
|
90
|
+
expect(@compose.containers[container2].running?).to be true
|
91
|
+
|
92
|
+
@compose.kill([container2])
|
93
|
+
expect(@compose.containers[container1].running?).to be false
|
94
|
+
expect(@compose.containers[container2].running?).to be false
|
95
|
+
end
|
58
96
|
end
|
59
97
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
98
|
+
context 'With dependencies' do
|
99
|
+
it 'should start/stop a single container' do
|
100
|
+
container1 = @compose.containers.values.first.attributes[:label]
|
101
|
+
container2 = @compose.containers.values[1].attributes[:label]
|
102
|
+
|
103
|
+
@compose.start([container1])
|
104
|
+
expect(@compose.containers[container1].running?).to be true
|
105
|
+
expect(@compose.containers[container2].running?).to be true
|
106
|
+
|
107
|
+
@compose.stop([container1])
|
108
|
+
expect(@compose.containers[container1].running?).to be false
|
109
|
+
expect(@compose.containers[container2].running?).to be true
|
110
|
+
|
111
|
+
@compose.stop([container2])
|
112
|
+
expect(@compose.containers[container2].running?).to be false
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'should start/kill a single container' do
|
116
|
+
container1 = @compose.containers.values.first.attributes[:label]
|
117
|
+
container2 = @compose.containers.values[1].attributes[:label]
|
118
|
+
|
119
|
+
@compose.start([container1])
|
120
|
+
expect(@compose.containers[container1].running?).to be true
|
121
|
+
expect(@compose.containers[container2].running?).to be true
|
122
|
+
|
123
|
+
@compose.kill([container1])
|
124
|
+
expect(@compose.containers[container1].running?).to be false
|
125
|
+
expect(@compose.containers[container2].running?).to be true
|
126
|
+
|
127
|
+
@compose.kill([container2])
|
128
|
+
expect(@compose.containers[container2].running?).to be false
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'should be able to ping a dependent container' do
|
132
|
+
container1 = @compose.containers.values.first.attributes[:label]
|
133
|
+
container2 = @compose.containers.values[1].attributes[:label]
|
134
|
+
|
135
|
+
# Start all containers
|
136
|
+
@compose.start
|
137
|
+
expect(@compose.containers[container1].running?).to be true
|
138
|
+
expect(@compose.containers[container2].running?).to be true
|
139
|
+
|
140
|
+
# Ping container2 from container1
|
141
|
+
ping_response = @compose.containers[container1].container.exec(['ping', '-c', '3', 'busybox2'])
|
142
|
+
expect(ping_response[2]).to eq(0) # Status 0 = OK
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'should be able to ping a dependent aliased container' do
|
146
|
+
container2 = @compose.containers.values[1].attributes[:label]
|
147
|
+
container3 = @compose.containers.values[2].attributes[:label]
|
148
|
+
|
149
|
+
# Start all containers
|
150
|
+
@compose.start
|
151
|
+
expect(@compose.containers[container2].running?).to be true
|
152
|
+
expect(@compose.containers[container3].running?).to be true
|
153
|
+
|
154
|
+
# Ping container3 from container1
|
155
|
+
ping_response = @compose.containers[container3].container.exec(['ping', '-c', '3', 'bb2'])
|
156
|
+
expect(ping_response[2]).to eq(0) # Status 0 = OK
|
157
|
+
end
|
64
158
|
end
|
65
159
|
end
|
66
|
-
end
|
67
160
|
|
68
|
-
|
69
|
-
|
70
|
-
it 'should start/stop a single container' do
|
71
|
-
container1 = @compose.containers.values.first.attributes[:label]
|
72
|
-
container2 = @compose.containers.values[1].attributes[:label]
|
73
|
-
|
74
|
-
# Should start Redis only, since it hasn't dependencies
|
75
|
-
@compose.start([container2])
|
76
|
-
expect(@compose.containers[container1].running?).to be false
|
77
|
-
expect(@compose.containers[container2].running?).to be true
|
78
|
-
|
79
|
-
# Stop Redis
|
80
|
-
@compose.stop([container2])
|
81
|
-
expect(@compose.containers[container1].running?).to be false
|
82
|
-
expect(@compose.containers[container2].running?).to be false
|
83
|
-
end
|
161
|
+
it 'should assign ports' do
|
162
|
+
container = @compose.get_containers_by(label: 'busybox1').first
|
84
163
|
|
85
|
-
|
86
|
-
|
87
|
-
container2 = @compose.containers.values[1].attributes[:label]
|
164
|
+
# Start container
|
165
|
+
container.start
|
88
166
|
|
89
|
-
|
90
|
-
|
91
|
-
expect(@compose.containers[container1].running?).to be false
|
92
|
-
expect(@compose.containers[container2].running?).to be true
|
167
|
+
port_bindings = container.stats['HostConfig']['PortBindings']
|
168
|
+
exposed_ports = container.stats['Config']['ExposedPorts']
|
93
169
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
end # context 'Without dependencies'
|
100
|
-
|
101
|
-
context 'With dependencies' do
|
102
|
-
it 'should start/stop a single container' do
|
103
|
-
container1 = @compose.containers.values.first.attributes[:label]
|
104
|
-
container2 = @compose.containers.values[1].attributes[:label]
|
105
|
-
|
106
|
-
# Should start Ubuntu and Redis, since Ubuntu depends on Redis
|
107
|
-
@compose.start([container1])
|
108
|
-
expect(@compose.containers[container1].running?).to be true
|
109
|
-
expect(@compose.containers[container2].running?).to be true
|
110
|
-
|
111
|
-
# Stop Ubuntu (Redis keeps running)
|
112
|
-
@compose.stop([container1])
|
113
|
-
expect(@compose.containers[container1].running?).to be false
|
114
|
-
expect(@compose.containers[container2].running?).to be true
|
115
|
-
|
116
|
-
# Stop Redis
|
117
|
-
@compose.stop([container2])
|
118
|
-
expect(@compose.containers[container2].running?).to be false
|
119
|
-
end
|
170
|
+
# Check port bindings
|
171
|
+
expect(port_bindings.length).to eq(3)
|
172
|
+
expect(port_bindings.key?('3000/tcp')).to be true
|
173
|
+
expect(port_bindings.key?('8000/tcp')).to be true
|
174
|
+
expect(port_bindings.key?('8001/tcp')).to be true
|
120
175
|
|
121
|
-
|
122
|
-
|
123
|
-
|
176
|
+
# Check exposed ports
|
177
|
+
expect(exposed_ports.key?('3000/tcp')).to be true
|
178
|
+
expect(exposed_ports.key?('8000/tcp')).to be true
|
179
|
+
expect(exposed_ports.key?('8001/tcp')).to be true
|
124
180
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
expect(@compose.containers[container2].running?).to be true
|
181
|
+
# Stop container
|
182
|
+
container.stop
|
183
|
+
end
|
129
184
|
|
130
|
-
|
131
|
-
|
132
|
-
expect(@compose.containers[container1].running?).to be false
|
133
|
-
expect(@compose.containers[container2].running?).to be true
|
185
|
+
it 'should link containers' do
|
186
|
+
container = @compose.get_containers_by(label: 'busybox1').first
|
134
187
|
|
135
|
-
|
136
|
-
|
137
|
-
expect(@compose.containers[container2].running?).to be false
|
138
|
-
end
|
188
|
+
# Start container
|
189
|
+
container.start
|
139
190
|
|
140
|
-
|
141
|
-
|
142
|
-
|
191
|
+
# Ubuntu should be linked to Redis
|
192
|
+
links = container.stats['HostConfig']['Links']
|
193
|
+
expect(links.length).to eq(1)
|
143
194
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
expect(@compose.containers[container2].running?).to be true
|
195
|
+
# Stop container
|
196
|
+
container.stop
|
197
|
+
end
|
148
198
|
|
149
|
-
|
150
|
-
|
151
|
-
expect(ping_response[2]).to eq(0) # Status 0 = OK
|
152
|
-
end
|
199
|
+
it 'binds volumes' do
|
200
|
+
container = @compose.get_containers_by(label: 'busybox1').first
|
153
201
|
|
154
|
-
|
155
|
-
|
156
|
-
container3 = @compose.containers.values[2].attributes[:label]
|
202
|
+
# Start container
|
203
|
+
container.start
|
157
204
|
|
158
|
-
|
159
|
-
|
160
|
-
expect(@compose.containers[container2].running?).to be true
|
161
|
-
expect(@compose.containers[container3].running?).to be true
|
205
|
+
volumes = container.stats['HostConfig']['Binds']
|
206
|
+
expect(volumes).to match_array(['/tmp/test:/tmp:ro'])
|
162
207
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
end
|
167
|
-
end # context 'with dependencies'
|
168
|
-
end # context 'Single container'
|
208
|
+
# Stop container
|
209
|
+
container.stop
|
210
|
+
end
|
169
211
|
|
170
|
-
|
171
|
-
|
212
|
+
it 'supports setting environment as array' do
|
213
|
+
container = @compose.get_containers_by(label: 'busybox1').first
|
172
214
|
|
173
|
-
|
174
|
-
|
215
|
+
# Start container
|
216
|
+
container.start
|
175
217
|
|
176
|
-
|
177
|
-
|
218
|
+
env = container.stats['Config']['Env']
|
219
|
+
expect(env).to eq(%w(MYENV1=variable1))
|
178
220
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
expect(port_bindings.key?('8000/tcp')).to be true
|
183
|
-
expect(port_bindings.key?('8001/tcp')).to be true
|
221
|
+
# Stop container
|
222
|
+
container.stop
|
223
|
+
end
|
184
224
|
|
185
|
-
|
186
|
-
|
187
|
-
expect(exposed_ports.key?('8000/tcp')).to be true
|
188
|
-
expect(exposed_ports.key?('8001/tcp')).to be true
|
225
|
+
it 'supports setting environment as hash' do
|
226
|
+
container = @compose.get_containers_by(label: 'busybox2').first
|
189
227
|
|
190
|
-
|
191
|
-
|
192
|
-
end
|
228
|
+
# Start container
|
229
|
+
container.start
|
193
230
|
|
194
|
-
|
195
|
-
|
231
|
+
env = container.stats['Config']['Env']
|
232
|
+
expect(env).to eq(%w(MYENV2=variable2))
|
196
233
|
|
197
|
-
|
198
|
-
|
234
|
+
# Stop container
|
235
|
+
container.stop
|
236
|
+
end
|
199
237
|
|
200
|
-
|
201
|
-
|
202
|
-
expect(links.length).to eq(1)
|
238
|
+
it 'supports setting labels as an array' do
|
239
|
+
container = @compose.get_containers_by(label: 'busybox1').first
|
203
240
|
|
204
|
-
|
205
|
-
|
206
|
-
end
|
241
|
+
# Start container
|
242
|
+
container.start
|
207
243
|
|
208
|
-
|
209
|
-
|
244
|
+
env = container.stats['Config']['Labels']
|
245
|
+
expect(env).to eq({ 'com.example.foo' => 'bar' })
|
210
246
|
|
211
|
-
|
212
|
-
|
247
|
+
# Stop container
|
248
|
+
container.stop
|
249
|
+
end
|
213
250
|
|
214
|
-
|
215
|
-
|
251
|
+
it 'supports setting labels as a hash' do
|
252
|
+
container = @compose.get_containers_by(label: 'busybox2').first
|
216
253
|
|
217
|
-
|
218
|
-
|
219
|
-
end
|
254
|
+
# Start container
|
255
|
+
container.start
|
220
256
|
|
221
|
-
|
222
|
-
|
257
|
+
env = container.stats['Config']['Labels']
|
258
|
+
expect(env).to eq({ 'com.example.foo' => 'bar' })
|
223
259
|
|
224
|
-
|
225
|
-
|
260
|
+
# Stop container
|
261
|
+
container.stop
|
262
|
+
end
|
226
263
|
|
227
|
-
|
228
|
-
|
264
|
+
it 'should assing given name to container' do
|
265
|
+
container = @compose.get_containers_by(label: 'busybox1').first
|
229
266
|
|
230
|
-
|
231
|
-
|
232
|
-
end
|
267
|
+
# Start container
|
268
|
+
container.start
|
233
269
|
|
234
|
-
|
235
|
-
|
270
|
+
container_name = container.stats['Name']
|
271
|
+
expect(container_name).to match(/\/#{ComposeUtils.dir_name}_busybox-container_\d+/)
|
236
272
|
|
237
|
-
|
238
|
-
|
273
|
+
# Stop container
|
274
|
+
container.stop
|
275
|
+
end
|
239
276
|
|
240
|
-
|
241
|
-
|
277
|
+
it 'should assing a random name to container when name is not given' do
|
278
|
+
container = @compose.get_containers_by(label: 'busybox2').first
|
242
279
|
|
243
|
-
|
244
|
-
|
245
|
-
end
|
280
|
+
# Start container
|
281
|
+
container.start
|
246
282
|
|
247
|
-
|
248
|
-
|
283
|
+
container_name = container.stats['Name']
|
284
|
+
expect(container_name).to_not be_nil
|
249
285
|
|
250
|
-
|
251
|
-
|
286
|
+
# Stop container
|
287
|
+
container.stop
|
288
|
+
end
|
252
289
|
|
253
|
-
|
254
|
-
|
290
|
+
it 'should filter containers by its attributes' do
|
291
|
+
expect(@compose.get_containers_by(label: 'busybox2')).to eq([@compose.containers['busybox2']])
|
292
|
+
expect(@compose.get_containers_by(name: @compose.containers['busybox1'].attributes[:name])).to eq([@compose.containers['busybox1']])
|
293
|
+
expect(@compose.get_containers_by_given_name('busybox-container')).to eq([@compose.containers['busybox1']])
|
294
|
+
expect(@compose.get_containers_by(image: 'busybox:latest')).to eq([
|
295
|
+
@compose.containers['busybox1'],
|
296
|
+
@compose.containers['busybox2'],
|
297
|
+
@compose.containers['busybox3']
|
298
|
+
])
|
299
|
+
end
|
255
300
|
|
256
|
-
|
257
|
-
|
301
|
+
after(:all) do
|
302
|
+
@compose.delete
|
303
|
+
end
|
258
304
|
end
|
259
305
|
|
260
|
-
|
261
|
-
|
306
|
+
context 'With memory' do
|
307
|
+
before(:all) do
|
308
|
+
@compose1 = DockerCompose.load(File.expand_path('spec/docker-compose/fixtures/compose_1.yaml'), false)
|
309
|
+
@compose1.start
|
262
310
|
|
263
|
-
|
264
|
-
|
311
|
+
@compose2 = DockerCompose.load(File.expand_path('spec/docker-compose/fixtures/empty_compose.yml'), true)
|
312
|
+
end
|
265
313
|
|
266
|
-
|
267
|
-
|
314
|
+
it 'should load all running containers from this directory' do
|
315
|
+
expect(@compose2.containers.length).to eq(@compose1.containers.length)
|
316
|
+
end
|
268
317
|
|
269
|
-
|
270
|
-
|
271
|
-
|
318
|
+
it '@compose2 should have the same containers of @compose1' do
|
319
|
+
docker_containers_compose1 = @compose1.containers.values.select { |c| c.container }
|
320
|
+
docker_containers_compose2 = @compose2.containers.values.select { |c| c.container }
|
272
321
|
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
@compose.containers['busybox2'],
|
279
|
-
@compose.containers['busybox3']
|
280
|
-
])
|
281
|
-
end
|
322
|
+
# Check that both composes have the same containers (based on its names)
|
323
|
+
docker_containers_compose2.each_index do |index|
|
324
|
+
expect(docker_containers_compose2[index].attributes['Name']).to eq(docker_containers_compose1[index].attributes['Name'])
|
325
|
+
end
|
326
|
+
end
|
282
327
|
|
283
|
-
|
284
|
-
|
328
|
+
after(:all) do
|
329
|
+
@compose1.delete
|
330
|
+
end
|
285
331
|
end
|
286
332
|
end
|
@@ -14,6 +14,8 @@ busybox1:
|
|
14
14
|
- MYENV1=variable1
|
15
15
|
volumes:
|
16
16
|
- "/tmp/test:/tmp:ro"
|
17
|
+
labels:
|
18
|
+
- com.example.foo=bar
|
17
19
|
|
18
20
|
busybox2:
|
19
21
|
image: busybox
|
@@ -22,6 +24,8 @@ busybox2:
|
|
22
24
|
command: ping localhost
|
23
25
|
environment:
|
24
26
|
MYENV2: variable2
|
27
|
+
labels:
|
28
|
+
com.example.foo: bar
|
25
29
|
|
26
30
|
busybox3:
|
27
31
|
image: busybox
|
File without changes
|
@@ -11,7 +11,8 @@ describe ComposeContainer do
|
|
11
11
|
ports: ['3000', '8000:8000', '127.0.0.1:8001:8001'],
|
12
12
|
volumes: ['/tmp'],
|
13
13
|
command: 'ping -c 3 localhost',
|
14
|
-
environment: ['ENVIRONMENT']
|
14
|
+
environment: ['ENVIRONMENT'],
|
15
|
+
labels: { 'com.example.foo' => 'bar' }
|
15
16
|
}
|
16
17
|
|
17
18
|
@entry = ComposeContainer.new(@attributes)
|
@@ -19,12 +20,13 @@ describe ComposeContainer do
|
|
19
20
|
|
20
21
|
it 'should prepare attributes correctly' do
|
21
22
|
expect(@entry.attributes[:image]).to eq(@attributes[:image])
|
22
|
-
expect(@entry.attributes[:name]).to
|
23
|
+
expect(@entry.attributes[:name]).to match(/#{ComposeUtils.dir_name}_#{@attributes[:name]}_\d+/)
|
23
24
|
expect(@entry.attributes[:links])
|
24
25
|
.to eq({'service1' => 'label', 'service2' => 'service2'})
|
25
26
|
expect(@entry.attributes[:volumes]).to eq(@attributes[:volumes])
|
26
27
|
expect(@entry.attributes[:command]).to eq(@attributes[:command].split(' '))
|
27
28
|
expect(@entry.attributes[:environment]).to eq(@attributes[:environment])
|
29
|
+
expect(@entry.attributes[:labels]).to eq(@attributes[:labels])
|
28
30
|
end
|
29
31
|
|
30
32
|
it 'should map ports' do
|
@@ -49,6 +51,10 @@ describe ComposeContainer do
|
|
49
51
|
expect(port_entry.host_ip).to eq('127.0.0.1')
|
50
52
|
expect(port_entry.host_port).to eq('8001')
|
51
53
|
end
|
54
|
+
|
55
|
+
after(:all) do
|
56
|
+
@entry.delete
|
57
|
+
end
|
52
58
|
end
|
53
59
|
|
54
60
|
context 'From image' do
|
@@ -93,7 +99,7 @@ describe ComposeContainer do
|
|
93
99
|
#Start container
|
94
100
|
@entry.start
|
95
101
|
|
96
|
-
expect(@entry.stats['Name']).to
|
102
|
+
expect(@entry.stats['Name']).to match(/#{ComposeUtils.dir_name}_#{@attributes[:name]}_\d+/)
|
97
103
|
|
98
104
|
# Stop container
|
99
105
|
@entry.stop
|
@@ -103,16 +109,22 @@ describe ComposeContainer do
|
|
103
109
|
#Start container
|
104
110
|
@entry_autogen_name.start
|
105
111
|
|
106
|
-
expect(@entry_autogen_name.stats['Name']).to
|
112
|
+
expect(@entry_autogen_name.stats['Name']).to match(/#{ComposeUtils.dir_name}_#{@entry_autogen_name.attributes[:label]}_\d+/)
|
107
113
|
|
108
114
|
# Stop container
|
109
115
|
@entry_autogen_name.stop
|
110
116
|
end
|
117
|
+
|
118
|
+
after(:all) do
|
119
|
+
@entry.delete
|
120
|
+
@entry_autogen_name.delete
|
121
|
+
end
|
111
122
|
end
|
112
123
|
|
113
124
|
context 'From Dockerfile' do
|
114
125
|
before(:all) do
|
115
126
|
attributes = {
|
127
|
+
label: 'foobar',
|
116
128
|
build: File.expand_path('spec/docker-compose/fixtures/'),
|
117
129
|
links: ['links:links'],
|
118
130
|
volumes: ['/tmp']
|
@@ -142,6 +154,11 @@ describe ComposeContainer do
|
|
142
154
|
@entry.stop
|
143
155
|
expect(@entry.running?).to be false
|
144
156
|
end
|
157
|
+
|
158
|
+
after(:all) do
|
159
|
+
Docker::Image.get(@entry.internal_image).remove(force: true)
|
160
|
+
@entry.delete
|
161
|
+
end
|
145
162
|
end
|
146
163
|
|
147
164
|
context 'Without image or Dockerfile' do
|
@@ -159,6 +176,10 @@ describe ComposeContainer do
|
|
159
176
|
it 'should not start a container' do
|
160
177
|
expect{@entry.start}.to raise_error(ArgumentError)
|
161
178
|
end
|
179
|
+
|
180
|
+
after(:all) do
|
181
|
+
@entry.delete
|
182
|
+
end
|
162
183
|
end
|
163
184
|
|
164
185
|
context 'With environment as a hash' do
|
@@ -175,9 +196,13 @@ describe ComposeContainer do
|
|
175
196
|
it 'should prepare environment attribute correctly' do
|
176
197
|
expect(@entry.attributes[:environment]).to eq(%w(ENVIRONMENT=VALUE))
|
177
198
|
end
|
199
|
+
|
200
|
+
after(:all) do
|
201
|
+
@entry.delete
|
202
|
+
end
|
178
203
|
end
|
179
204
|
|
180
|
-
describe '
|
205
|
+
describe 'prepare_volumes' do
|
181
206
|
let(:attributes) do
|
182
207
|
{ image: 'busybox:latest' }
|
183
208
|
end
|
@@ -72,5 +72,18 @@ describe Compose do
|
|
72
72
|
expect(container3.dependencies.empty?).to be true
|
73
73
|
end
|
74
74
|
end
|
75
|
+
|
76
|
+
it 'should increment label when already exists' do
|
77
|
+
@compose = Compose.new
|
78
|
+
|
79
|
+
# Add the same container twice
|
80
|
+
@compose.add_container(ComposeContainer.new(@attributes_container1))
|
81
|
+
@compose.add_container(ComposeContainer.new(@attributes_container1))
|
82
|
+
|
83
|
+
label_first_container = @compose.containers.keys[0]
|
84
|
+
label_second_container = @compose.containers.keys[1]
|
85
|
+
|
86
|
+
expect(label_first_container).to_not eq(label_second_container)
|
87
|
+
end
|
75
88
|
end
|
76
89
|
end
|
@@ -52,6 +52,26 @@ describe ComposeUtils do
|
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
|
+
context 'Format ports from running containers' do
|
56
|
+
before(:all) do
|
57
|
+
@hash_attr = {
|
58
|
+
'8000/tcp' => [{
|
59
|
+
'HostIp' => '0.0.0.0',
|
60
|
+
'HostPort' => '4444'
|
61
|
+
}]
|
62
|
+
}
|
63
|
+
@expected_format = ['8000:0.0.0.0:4444']
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should format ports correctly' do
|
67
|
+
expect(ComposeUtils.format_ports_from_running_container(@hash_attr)).to eq(@expected_format)
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should return an empty array when ports are nil' do
|
71
|
+
expect(ComposeUtils.format_ports_from_running_container(nil)).to eq([])
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
55
75
|
context 'Format links' do
|
56
76
|
it 'should recognize pattern "[service]"' do
|
57
77
|
links = ComposeUtils.format_links(['service'])
|
@@ -65,4 +85,21 @@ describe ComposeUtils do
|
|
65
85
|
expect(links['service']).to eq('label')
|
66
86
|
end
|
67
87
|
end
|
88
|
+
|
89
|
+
context 'Generate container name' do
|
90
|
+
before(:all) do
|
91
|
+
@name = 'foo'
|
92
|
+
@label = 'bar'
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'should generate name with given name' do
|
96
|
+
name = ComposeUtils.generate_container_name(@name, @label)
|
97
|
+
expect(name).to match(/#{ComposeUtils.dir_name}_#{@name}_\d+/)
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'should generate name with label' do
|
101
|
+
name = ComposeUtils.generate_container_name(nil, @label)
|
102
|
+
expect(name).to match(/#{ComposeUtils.dir_name}_#{@label}_\d+/)
|
103
|
+
end
|
104
|
+
end
|
68
105
|
end
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: docker-compose-api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mauricio S. Klein
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-02-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: docker-api
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 1.22
|
19
|
+
version: '1.22'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 1.22
|
26
|
+
version: '1.22'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -89,6 +89,7 @@ files:
|
|
89
89
|
- spec/docker-compose/docker_compose_spec.rb
|
90
90
|
- spec/docker-compose/fixtures/Dockerfile
|
91
91
|
- spec/docker-compose/fixtures/compose_1.yaml
|
92
|
+
- spec/docker-compose/fixtures/empty_compose.yml
|
92
93
|
- spec/docker-compose/models/compose_container_spec.rb
|
93
94
|
- spec/docker-compose/models/compose_spec.rb
|
94
95
|
- spec/docker-compose/utils/compose_utils_spec.rb
|
@@ -121,6 +122,7 @@ test_files:
|
|
121
122
|
- spec/docker-compose/docker_compose_spec.rb
|
122
123
|
- spec/docker-compose/fixtures/Dockerfile
|
123
124
|
- spec/docker-compose/fixtures/compose_1.yaml
|
125
|
+
- spec/docker-compose/fixtures/empty_compose.yml
|
124
126
|
- spec/docker-compose/models/compose_container_spec.rb
|
125
127
|
- spec/docker-compose/models/compose_spec.rb
|
126
128
|
- spec/docker-compose/utils/compose_utils_spec.rb
|