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 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