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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8cd19119cd22c6ff9c235e71f0f3b7dda2170cd9
4
- data.tar.gz: d8a5b668de10c6fecad255cc96f0359f59c1888c
3
+ metadata.gz: cf18202df3237d79dc3ef72446eec4f226d12ec0
4
+ data.tar.gz: 1810eb1c15212070d375d6bf626cd8b94ab7bd81
5
5
  SHA512:
6
- metadata.gz: 38ccca06b4af322ed96bab86176f2ded9b61da314cd51b57cb485d4dc445a074cca17a800d20450f6731078fc7c3b36ca484f50baf218ec0db7cccba7703daf6
7
- data.tar.gz: 169c08e927c55f7544036e2a4c4572300cfc886bd6ae035e02cb3ab1cd93bf95e9b0dbcbbbfd073a85b3f7fb5a81224d90bca6182a3ce5c56714690fa0292d6c
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
@@ -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.2"
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"
@@ -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
- _compose_entries.each do |entry|
29
- compose.add_container(create_container(entry))
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
- private_class_method :create_container
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[:name].nil? ? hash_attributes[:label] : hash_attributes[:name],
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 = nil
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,5 +1,5 @@
1
1
  module DockerCompose
2
2
  def self.version
3
- "1.0.5"
3
+ "1.1.0"
4
4
  end
5
5
  end
@@ -1,286 +1,332 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe DockerCompose do
4
- before(:all) do
5
- @compose = DockerCompose.load(File.expand_path('spec/docker-compose/fixtures/compose_1.yaml'))
6
- end
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
- it 'should be able to access gem version' do
9
- expect(DockerCompose.version).to_not be_nil
10
- end
9
+ it 'should be able to access gem version' do
10
+ expect(DockerCompose.version).to_not be_nil
11
+ end
11
12
 
12
- it 'should be able to access Docker client' do
13
- expect(DockerCompose.docker_client).to_not be_nil
14
- end
13
+ it 'should be able to access Docker client' do
14
+ expect(DockerCompose.docker_client).to_not be_nil
15
+ end
15
16
 
16
- it 'should read a YAML file correctly' do
17
- expect(@compose.containers.length).to eq(3)
18
- end
17
+ it 'should read a YAML file correctly' do
18
+ expect(@compose.containers.length).to eq(3)
19
+ end
19
20
 
20
- it 'should raise error when reading an invalid YAML file' do
21
- expect{DockerCompose.load('')}.to raise_error(ArgumentError)
22
- end
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
- context 'All containers' do
25
- it 'should start/stop all containers' do
26
- # Start containers to test Stop
27
- @compose.start
28
- @compose.containers.values.each do |container|
29
- expect(container.running?).to be true
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
- # Stop containers
33
- @compose.stop
34
- @compose.containers.values.each do |container|
35
- expect(container.running?).to be false
36
- end
37
- end
38
-
39
- it 'should start/kill all containers' do
40
- # Start containers to test Kill
41
- @compose.start
42
- @compose.containers.values.each do |container|
43
- expect(container.running?).to be true
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
- # Kill containers
47
- @compose.kill
48
- @compose.containers.values.each do |container|
49
- expect(container.running?).to be false
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
- it 'should start/delete all containers' do
54
- # Start containers to test Delete
55
- @compose.start
56
- @compose.containers.values.each do |container|
57
- expect(container.running?).to be true
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
- # Delete containers
61
- @compose.delete
62
- @compose.containers.values.each do |container|
63
- expect(container.exist?).to be false
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
- context 'Single container' do
69
- context 'Without dependencies' do
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
- it 'should start/kill a single container' do
86
- container1 = @compose.containers.values.first.attributes[:label]
87
- container2 = @compose.containers.values[1].attributes[:label]
164
+ # Start container
165
+ container.start
88
166
 
89
- # Should start Redis only, since it hasn't dependencies
90
- @compose.start([container2])
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
- # Stop Redis
95
- @compose.kill([container2])
96
- expect(@compose.containers[container1].running?).to be false
97
- expect(@compose.containers[container2].running?).to be false
98
- end
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
- it 'should start/kill a single container' do
122
- container1 = @compose.containers.values.first.attributes[:label]
123
- container2 = @compose.containers.values[1].attributes[:label]
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
- # Should start Ubuntu and Redis, since Ubuntu depends on Redis
126
- @compose.start([container1])
127
- expect(@compose.containers[container1].running?).to be true
128
- expect(@compose.containers[container2].running?).to be true
181
+ # Stop container
182
+ container.stop
183
+ end
129
184
 
130
- # Kill Ubuntu (Redis keeps running)
131
- @compose.kill([container1])
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
- # Kill Redis
136
- @compose.kill([container2])
137
- expect(@compose.containers[container2].running?).to be false
138
- end
188
+ # Start container
189
+ container.start
139
190
 
140
- it 'should be able to ping a dependent container' do
141
- container1 = @compose.containers.values.first.attributes[:label]
142
- container2 = @compose.containers.values[1].attributes[:label]
191
+ # Ubuntu should be linked to Redis
192
+ links = container.stats['HostConfig']['Links']
193
+ expect(links.length).to eq(1)
143
194
 
144
- # Start all containers
145
- @compose.start
146
- expect(@compose.containers[container1].running?).to be true
147
- expect(@compose.containers[container2].running?).to be true
195
+ # Stop container
196
+ container.stop
197
+ end
148
198
 
149
- # Ping container2 from container1
150
- ping_response = @compose.containers[container1].container.exec(['ping', '-c', '3', 'busybox2'])
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
- it 'should be able to ping a dependent aliased container' do
155
- container2 = @compose.containers.values[1].attributes[:label]
156
- container3 = @compose.containers.values[2].attributes[:label]
202
+ # Start container
203
+ container.start
157
204
 
158
- # Start all containers
159
- @compose.start
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
- # Ping container3 from container1
164
- ping_response = @compose.containers[container3].container.exec(['ping', '-c', '3', 'bb2'])
165
- expect(ping_response[2]).to eq(0) # Status 0 = OK
166
- end
167
- end # context 'with dependencies'
168
- end # context 'Single container'
208
+ # Stop container
209
+ container.stop
210
+ end
169
211
 
170
- it 'should assign ports' do
171
- container1 = @compose.containers.values.first
212
+ it 'supports setting environment as array' do
213
+ container = @compose.get_containers_by(label: 'busybox1').first
172
214
 
173
- # Start container
174
- container1.start
215
+ # Start container
216
+ container.start
175
217
 
176
- port_bindings = container1.stats['HostConfig']['PortBindings']
177
- exposed_ports = container1.stats['Config']['ExposedPorts']
218
+ env = container.stats['Config']['Env']
219
+ expect(env).to eq(%w(MYENV1=variable1))
178
220
 
179
- # Check port bindings
180
- expect(port_bindings.length).to eq(3)
181
- expect(port_bindings.key?('3000/tcp')).to be true
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
- # Check exposed ports
186
- expect(exposed_ports.key?('3000/tcp')).to be true
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
- # Stop container
191
- container1.stop
192
- end
228
+ # Start container
229
+ container.start
193
230
 
194
- it 'should link containers' do
195
- container1 = @compose.containers.values.first
231
+ env = container.stats['Config']['Env']
232
+ expect(env).to eq(%w(MYENV2=variable2))
196
233
 
197
- # Start container
198
- container1.start
234
+ # Stop container
235
+ container.stop
236
+ end
199
237
 
200
- # Ubuntu should be linked to Redis
201
- links = container1.stats['HostConfig']['Links']
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
- # Stop container
205
- container1.stop
206
- end
241
+ # Start container
242
+ container.start
207
243
 
208
- it 'binds volumes' do
209
- container1 = @compose.containers.values.first
244
+ env = container.stats['Config']['Labels']
245
+ expect(env).to eq({ 'com.example.foo' => 'bar' })
210
246
 
211
- # Start container
212
- container1.start
247
+ # Stop container
248
+ container.stop
249
+ end
213
250
 
214
- volumes = container1.stats['HostConfig']['Binds']
215
- expect(volumes).to match_array(['/tmp/test:/tmp:ro'])
251
+ it 'supports setting labels as a hash' do
252
+ container = @compose.get_containers_by(label: 'busybox2').first
216
253
 
217
- # Stop container
218
- container1.stop
219
- end
254
+ # Start container
255
+ container.start
220
256
 
221
- it 'supports setting environment as array' do
222
- container1 = @compose.containers.values.first
257
+ env = container.stats['Config']['Labels']
258
+ expect(env).to eq({ 'com.example.foo' => 'bar' })
223
259
 
224
- # Start container
225
- container1.start
260
+ # Stop container
261
+ container.stop
262
+ end
226
263
 
227
- env = container1.stats['Config']['Env']
228
- expect(env).to eq(%w(MYENV1=variable1))
264
+ it 'should assing given name to container' do
265
+ container = @compose.get_containers_by(label: 'busybox1').first
229
266
 
230
- # Stop container
231
- container1.stop
232
- end
267
+ # Start container
268
+ container.start
233
269
 
234
- it 'supports setting environment as hash' do
235
- container1 = @compose.containers.values[1]
270
+ container_name = container.stats['Name']
271
+ expect(container_name).to match(/\/#{ComposeUtils.dir_name}_busybox-container_\d+/)
236
272
 
237
- # Start container
238
- container1.start
273
+ # Stop container
274
+ container.stop
275
+ end
239
276
 
240
- env = container1.stats['Config']['Env']
241
- expect(env).to eq(%w(MYENV2=variable2))
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
- # Stop container
244
- container1.stop
245
- end
280
+ # Start container
281
+ container.start
246
282
 
247
- it 'should assing given name to container' do
248
- container = @compose.containers.values[0]
283
+ container_name = container.stats['Name']
284
+ expect(container_name).to_not be_nil
249
285
 
250
- # Start container
251
- container.start
286
+ # Stop container
287
+ container.stop
288
+ end
252
289
 
253
- container_name = container.stats['Name']
254
- expect(container_name).to eq('/busybox-container')
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
- # Stop container
257
- container.stop
301
+ after(:all) do
302
+ @compose.delete
303
+ end
258
304
  end
259
305
 
260
- it 'should assing a random name to container when name is not given' do
261
- container = @compose.containers.values[1]
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
- # Start container
264
- container.start
311
+ @compose2 = DockerCompose.load(File.expand_path('spec/docker-compose/fixtures/empty_compose.yml'), true)
312
+ end
265
313
 
266
- container_name = container.stats['Name']
267
- expect(container_name).to_not be_nil
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
- # Stop container
270
- container.stop
271
- end
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
- it 'should filter containers by its attributes' do
274
- expect(@compose.get_containers_by(label: 'busybox2')).to eq([@compose.containers['busybox2']])
275
- expect(@compose.get_containers_by(name: 'busybox-container')).to eq([@compose.containers['busybox1']])
276
- expect(@compose.get_containers_by(image: 'busybox:latest')).to eq([
277
- @compose.containers['busybox1'],
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
- after(:all) do
284
- @compose.delete
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 eq(@attributes[:name])
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 eq("/#{@attributes[:name]}")
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 eq("/#{@entry_autogen_name.attributes[:label]}")
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 '#prepare_volumes' do
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.5
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-01-22 00:00:00.000000000 Z
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.2
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.2
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