docker-compose-api 1.0.5 → 1.1.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.
- 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
|