docker-api 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/.cane +0 -0
  2. data/.gitignore +3 -0
  3. data/Gemfile +3 -0
  4. data/README.md +215 -0
  5. data/Rakefile +16 -0
  6. data/docker-api.gemspec +27 -0
  7. data/lib/docker.rb +60 -0
  8. data/lib/docker/connection.rb +65 -0
  9. data/lib/docker/container.rb +54 -0
  10. data/lib/docker/error.rb +23 -0
  11. data/lib/docker/image.rb +73 -0
  12. data/lib/docker/model.rb +88 -0
  13. data/lib/docker/multipart.rb +30 -0
  14. data/lib/docker/version.rb +3 -0
  15. data/spec/docker/connection_spec.rb +67 -0
  16. data/spec/docker/container_spec.rb +529 -0
  17. data/spec/docker/image_spec.rb +379 -0
  18. data/spec/docker_spec.rb +58 -0
  19. data/spec/spec_helper.rb +14 -0
  20. data/spec/support/vcr.rb +11 -0
  21. data/spec/vcr/Docker/_info/returns_the_info_as_a_Hash.yml +31 -0
  22. data/spec/vcr/Docker/_version/returns_the_version_as_a_Hash.yml +31 -0
  23. data/spec/vcr/Docker_Container/_all/when_the_HTTP_response_is_a_200/materializes_each_Container_into_a_Docker_Container.yml +333 -0
  24. data/spec/vcr/Docker_Container/_attach/when_the_Container_has_been_created/when_the_HTTP_response_status_is_200/yields_each_chunk.yml +81 -0
  25. data/spec/vcr/Docker_Container/_changes/when_the_Container_has_been_created/when_the_HTTP_response_status_is_200/returns_the_changes_as_an_array.yml +115 -0
  26. data/spec/vcr/Docker_Container/_commit/when_the_Container_has_been_created/when_the_HTTP_response_status_is_200/creates_a_new_Image_from_the_Container_s_changes.yml +87 -0
  27. data/spec/vcr/Docker_Container/_create_/when_the_Container_does_not_yet_exist_and_the_body_is_a_Hash/when_the_HTTP_request_returns_a_200/sets_the_id.yml +31 -0
  28. data/spec/vcr/Docker_Container/_export/when_the_Container_has_been_created/when_the_HTTP_response_status_is_200/yields_each_chunk.yml +97 -0
  29. data/spec/vcr/Docker_Container/_json/when_the_Container_has_been_created/when_the_HTTP_response_status_is_200/returns_the_description_as_a_Hash.yml +59 -0
  30. data/spec/vcr/Docker_Container/_kill/when_the_Container_has_been_created/when_the_HTTP_response_status_is_204/kills_the_container.yml +232 -0
  31. data/spec/vcr/Docker_Container/_restart/when_the_Container_has_been_created/when_the_HTTP_response_status_is_204/restarts_the_container.yml +201 -0
  32. data/spec/vcr/Docker_Container/_start/when_the_Container_has_been_created/when_the_HTTP_response_status_is_200/starts_the_container.yml +89 -0
  33. data/spec/vcr/Docker_Container/_stop/when_the_Container_has_been_created/when_the_HTTP_response_status_is_204/stops_the_container.yml +259 -0
  34. data/spec/vcr/Docker_Container/_wait/when_the_Container_has_been_created/when_the_HTTP_response_status_is_200/waits_for_the_command_to_finish.yml +87 -0
  35. data/spec/vcr/Docker_Image/_all/when_the_HTTP_response_is_a_200/materializes_each_Container_into_a_Docker_Container.yml +59 -0
  36. data/spec/vcr/Docker_Image/_build/with_a_valid_Dockerfile/builds_an_image.yml +45 -0
  37. data/spec/vcr/Docker_Image/_build/with_an_invalid_Dockerfile/throws_a_UnexpectedResponseError.yml +38 -0
  38. data/spec/vcr/Docker_Image/_create_/when_the_Image_does_not_yet_exist_and_the_body_is_a_Hash/when_the_HTTP_request_returns_a_200/sets_the_id.yml +31 -0
  39. data/spec/vcr/Docker_Image/_history/when_the_Image_has_been_created/when_the_HTTP_response_status_is_200/returns_the_history_of_the_Image.yml +59 -0
  40. data/spec/vcr/Docker_Image/_insert/when_the_Image_has_been_created/when_the_HTTP_response_status_is_200/inserts_the_file.yml +59 -0
  41. data/spec/vcr/Docker_Image/_json/when_the_Image_has_been_created/when_the_HTTP_response_status_is_200/returns_additional_information_about_image_image.yml +60 -0
  42. data/spec/vcr/Docker_Image/_remove/when_the_Image_has_been_created/when_the_HTTP_response_status_is_204/nils_out_the_id.yml +59 -0
  43. data/spec/vcr/Docker_Image/_search/when_the_HTTP_response_is_a_200/materializes_each_Container_into_a_Docker_Container.yml +64 -0
  44. data/spec/vcr/Docker_Image/_tag/when_the_Image_has_been_created/when_the_HTTP_response_status_is_200/tags_the_image_with_the_repo_name.yml +59 -0
  45. metadata +281 -0
@@ -0,0 +1,23 @@
1
+ # This module holds the Errors for the gem.
2
+ module Docker::Error
3
+
4
+ # The default error. It's never actually raised, but can be used to catch all
5
+ # gem-specific errors that are thrown as they all subclass from this.
6
+ class DockerError < StandardError; end
7
+
8
+ # Raised when invalid arguments are passed to a method.
9
+ class ArgumentError < DockerError; end
10
+
11
+ # Raised when a method requires a Model to be in a certain state (typically
12
+ # created or not created), but the Model is not in that state.
13
+ class StateError < DockerError; end
14
+
15
+ # Raised when a request returns a 400.
16
+ class ClientError < DockerError; end
17
+
18
+ # Raised when a request returns a 500.
19
+ class ServerError < DockerError; end
20
+
21
+ # Raised when there is an unexpected response code / body.
22
+ class UnexpectedResponseError < DockerError; end
23
+ end
@@ -0,0 +1,73 @@
1
+ # This class represents a Docker Image.
2
+ class Docker::Image
3
+ include Docker::Model
4
+ resource_prefix '/images'
5
+
6
+ create_request do |options|
7
+ body = self.connection.post(
8
+ :path => '/images/create',
9
+ :headers => { 'Content-Type' => 'application/x-www-form-urlencoded' },
10
+ :body => hash_to_params(options),
11
+ :expects => (200..204)
12
+ ).body
13
+ self.id = JSON.parse(body)['status']
14
+ self
15
+ end
16
+
17
+ # Tag the Image.
18
+ docker_request :tag, :post
19
+ # Get more information about the Image.
20
+ docker_request :json, :get
21
+ # Push the Image to the Docker registry.
22
+ docker_request :push, :post
23
+ # Insert a file into the Image.
24
+ docker_request :insert, :post
25
+ # Get the history of the Image.
26
+ docker_request :history, :get
27
+
28
+ # Remove the Image from the server.
29
+ def remove
30
+ ensure_created!
31
+ self.connection.json_request(:delete, "/images/#{self.id}", nil)
32
+ self.id = nil
33
+ true
34
+ end
35
+
36
+ # Create a query string from a Hash.
37
+ def hash_to_params(hash)
38
+ hash.map { |k, v| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}" }.join('&')
39
+ end
40
+ private :hash_to_params
41
+
42
+ class << self
43
+ include Docker::Error
44
+ include Docker::Multipart
45
+
46
+ # Given a query like `{ :term => 'sshd' }`, queries the Docker Registry for
47
+ # a corresponiding Image.
48
+ def search(query = {}, connection = Docker.connection)
49
+ hashes = connection.json_request(:get, '/images/search', query) || []
50
+ hashes.map { |hash| new(:id => hash['Name'], :connection => connection) }
51
+ end
52
+
53
+ # Given a Dockerfile as a string, builds an Image.
54
+ def build(commands, connection = Docker.connection)
55
+ body = multipart_request(
56
+ '/build',
57
+ 'Dockerfile',
58
+ StringIO.new("#{commands}\n"),
59
+ connection
60
+ )
61
+ new(:id => extract_id(body), :connection => connection)
62
+ end
63
+
64
+ private
65
+ def extract_id(body)
66
+ if match = body.lines.to_a[-1].match(/^===> ([a-f0-9]+)$/)
67
+ match[1]
68
+ else
69
+ raise UnexpectedResponseError, "Couldn't find id: #{body}"
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,88 @@
1
+ # This module is intended to be used as a Mixin for all objects exposed by the
2
+ # Remote API. Currently, these are limited to Containers and Images.
3
+ module Docker::Model
4
+ attr_reader :id, :connection
5
+
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ # Creates a new Model with the specified id and Connection. If a Connection
11
+ # is specified and it is not a Docker::Connection, a
12
+ # Docker::Error::ArgumentError is raised.
13
+ def initialize(options = {})
14
+ options[:connection] ||= Docker.connection
15
+ unless options[:connection].is_a?(Docker::Connection)
16
+ raise Docker::Error::ArgumentError, "Expected a Docker::Connection."
17
+ end
18
+ self.id = options[:id]
19
+ self.connection = options[:connection]
20
+ end
21
+
22
+ # Create a Model with the specified body. Raises A Docker::Error::StateError
23
+ # if the model already exists, and a Docker::Error::ArgumentError if the
24
+ # argument is not a Hash. Otherwise, instances exec the Class's
25
+ # #create_request method with the single argument.
26
+ def create!(options = {})
27
+ case
28
+ when self.created?
29
+ raise Docker::Error::StateError, "This #{self.class.name} already exists!"
30
+ when !options.is_a?(Hash)
31
+ raise Docker::Error::ArgumentError, 'Expected a Hash'
32
+ else
33
+ instance_exec(options, &self.class.create_request)
34
+ end
35
+ end
36
+
37
+ # Returns true if the Container has been created, false otherwise.
38
+ def created?
39
+ !!self.id
40
+ end
41
+
42
+ def to_s
43
+ "#{self.class.name} { :id => #{id}, :connection => #{connection} }"
44
+ end
45
+
46
+ # This defines the DSL for the including Classes.
47
+ module ClassMethods
48
+ # Define the Model's prefix for all requests.
49
+ def resource_prefix(val = nil)
50
+ val.nil? ? @resource_prefix : (@resource_prefix = val)
51
+ end
52
+
53
+ # Define how the Model should send a create request to the server.
54
+ def create_request(&block)
55
+ block.nil? ? @create_request : (@create_request = block)
56
+ end
57
+
58
+ # Define a method named `action` that sends an http `method` request to the
59
+ # Docker Server.
60
+ def docker_request(action, method, &outer_block)
61
+ define_method(action) do |query = nil, &block|
62
+ ensure_created!
63
+ path = "#{self.class.resource_prefix}/#{self.id}/#{action}"
64
+ body = self.connection.json_request(method, path, query, &block)
65
+ outer_block.nil? ? body : instance_exec(body, &outer_block)
66
+ end
67
+ end
68
+
69
+ # Retrieve every Instance of a model for the given server.
70
+ def all(options = {}, connection = Docker.connection)
71
+ path = "#{self.resource_prefix}/json"
72
+ hashes = connection.json_request(:get, path, options) || []
73
+ hashes.map { |hash| new(:id => hash['Id'], :connection => connection) }
74
+ end
75
+
76
+ private
77
+ end
78
+
79
+ private
80
+ attr_writer :id, :connection
81
+
82
+ # Raises an error unless the Model is created.
83
+ def ensure_created!
84
+ unless created?
85
+ raise Docker::Error::StateError, "This #{self.class.name} is not created."
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,30 @@
1
+ # This Mixin provides the ability to do multipart post requests.
2
+ module Docker::Multipart
3
+ include Docker::Error
4
+
5
+ # Given a path, resource name, io, and Connection sends a multipart request.
6
+ def multipart_request(path, name, io, connection)
7
+ host, port = host_and_port(connection)
8
+ res = Net::HTTP.start(host, port) { |http|
9
+ req = build_multipart_post(path, io, 'application/octet-stream', name)
10
+ http.request(req)
11
+ }
12
+ if (200..204).include?(res.code.to_i)
13
+ res.body
14
+ else
15
+ raise UnexpectedResponseError, "Got status #{res.code}"
16
+ end
17
+ end
18
+
19
+ private
20
+ # Return the host and port from a Connection.
21
+ def host_and_port(connection)
22
+ [URI.parse(connection.url).host, connection.options[:port]]
23
+ end
24
+
25
+ # Build the multipart post request.
26
+ def build_multipart_post(path, inner_io, content_type, file_name)
27
+ io = UploadIO.new(inner_io, content_type, file_name)
28
+ Net::HTTP::Post::Multipart.new(path, file_name => io)
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ module Docker
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe Docker::Connection do
4
+ describe '#initialize' do
5
+ subject { described_class }
6
+
7
+ context 'with no arguments' do
8
+ it 'defaults to port 4243' do
9
+ subject.new.options.should == { :port => 4243 }
10
+ end
11
+
12
+ it 'defaults to \'http://localhost\' for the url' do
13
+ subject.new.url.should == 'http://localhost'
14
+ end
15
+ end
16
+
17
+ context 'with an argument' do
18
+ context 'when the second argument is not a Hash' do
19
+ it 'raises a Docker::Error::ArgumentError' do
20
+ expect { subject.new('http://localhost', :lol) }
21
+ .to raise_error Docker::Error::ArgumentError
22
+ end
23
+ end
24
+
25
+ context 'when the argument is a Hash' do
26
+ let(:url) { 'google.com' }
27
+ let(:port) { 80 }
28
+ let(:options) { { :port => port } }
29
+
30
+ it 'sets the specified url' do
31
+ subject.new(url, options).url.should == url
32
+ end
33
+
34
+ it 'sets the specified port' do
35
+ subject.new(url, options).options[:port].should == port
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ describe '#resource' do
42
+ its(:resource) { should be_a Excon::Connection }
43
+ end
44
+
45
+ [:get, :put, :post, :delete].each do |method|
46
+ describe "##{method}" do
47
+ it 'is delegated to #resource' do
48
+ subject.resource.should_receive(method)
49
+ subject.public_send(method)
50
+ end
51
+ end
52
+ end
53
+
54
+ describe '#to_s' do
55
+ let(:url) { 'google.com' }
56
+ let(:port) { 4000 }
57
+ let(:options) { { :port => port } }
58
+ let(:expected_string) do
59
+ "Docker::Connection { :url => #{url}, :options => #{options} }"
60
+ end
61
+ subject { described_class.new(url, options) }
62
+
63
+ it 'returns a pretty printed version with the url and port' do
64
+ subject.to_s.should == expected_string
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,529 @@
1
+ require 'spec_helper'
2
+
3
+ # WARNING if you're re-recording any of these VCRs, you must be running the
4
+ # Docker daemon and have the base Image pulled.
5
+ describe Docker::Container do
6
+ describe '#initialize' do
7
+ subject { described_class }
8
+
9
+ context 'with no argument' do
10
+ let(:container) { subject.new }
11
+
12
+ it 'sets the id to nil' do
13
+ container.id.should be_nil
14
+ end
15
+
16
+ it 'keeps the default Connection' do
17
+ container.connection.should == Docker.connection
18
+ end
19
+ end
20
+
21
+ context 'with an argument' do
22
+ let(:id) { 'a7c2ee4' }
23
+ let(:container) { subject.new(:id => id) }
24
+
25
+ it 'sets the id to the argument' do
26
+ container.id.should == id
27
+ end
28
+
29
+ it 'keeps the default Connection' do
30
+ container.connection.should == Docker.connection
31
+ end
32
+ end
33
+
34
+ context 'with two arguments' do
35
+ context 'when the second argument is not a Docker::Connection' do
36
+ let(:id) { 'abc123f' }
37
+ let(:connection) { :not_a_connection }
38
+ let(:container) { subject.new(:id => id, :connection => connection) }
39
+
40
+ it 'raises an error' do
41
+ expect { container }.to raise_error(Docker::Error::ArgumentError)
42
+ end
43
+ end
44
+
45
+ context 'when the second argument is a Docker::Connection' do
46
+ let(:id) { 'cb3f14a' }
47
+ let(:connection) { Docker::Connection.new }
48
+ let(:container) { subject.new(:id => id, :connection => connection) }
49
+
50
+ it 'initializes the Container' do
51
+ container.id.should == id
52
+ container.connection.should == connection
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ describe '#to_s' do
59
+ let(:id) { 'bf119e2' }
60
+ let(:connection) { Docker::Connection.new }
61
+ let(:expected_string) do
62
+ "Docker::Container { :id => #{id}, :connection => #{connection} }"
63
+ end
64
+ subject { described_class.new(:id => id, :connection => connection) }
65
+
66
+ its(:to_s) { should == expected_string }
67
+ end
68
+
69
+ describe '#created?' do
70
+ context 'when the id is nil' do
71
+ its(:created?) { should be_false }
72
+ end
73
+
74
+ context 'when the id is present' do
75
+ subject { described_class.new(:id => 'a732ebf') }
76
+
77
+ its(:created?) { should be_true }
78
+ end
79
+ end
80
+
81
+ describe '#create!' do
82
+ context 'when the Container has already been created' do
83
+ subject { described_class.new(:id => '5e88b2a') }
84
+
85
+ it 'raises an error' do
86
+ expect { subject.create! }
87
+ .to raise_error(Docker::Error::StateError)
88
+ end
89
+ end
90
+
91
+ context 'when the body is not a Hash' do
92
+ it 'raises an error' do
93
+ expect { subject.create!(:not_a_hash) }
94
+ .to raise_error(Docker::Error::ArgumentError)
95
+ end
96
+ end
97
+
98
+ context 'when the Container does not yet exist and the body is a Hash' do
99
+ context 'when the HTTP request does not return a 200' do
100
+ before { Excon.stub({ :method => :post }, { :status => 400 }) }
101
+ after { Excon.stubs.shift }
102
+
103
+ it 'raises an error' do
104
+ expect { subject.create! }.to raise_error(Docker::Error::ClientError)
105
+ end
106
+ end
107
+
108
+ context 'when the HTTP request returns a 200' do
109
+ let(:options) do
110
+ {
111
+ "Hostname" => "",
112
+ "User" => "",
113
+ "Memory" => 0,
114
+ "MemorySwap" => 0,
115
+ "AttachStdin" => false,
116
+ "AttachStdout" => false,
117
+ "AttachStderr" => false,
118
+ "PortSpecs" => nil,
119
+ "Tty" => false,
120
+ "OpenStdin" => false,
121
+ "StdinOnce" => false,
122
+ "Env" => nil,
123
+ "Cmd" => ["date"],
124
+ "Dns" => nil,
125
+ "Image" => "base",
126
+ "Volumes" => {},
127
+ "VolumesFrom" => ""
128
+ }
129
+ end
130
+
131
+ it 'sets the id', :vcr do
132
+ expect { subject.create!(options) }
133
+ .to change { subject.id }
134
+ .from(nil)
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ describe '#json' do
141
+ context 'when the Container has not been created' do
142
+ it 'raises an error' do
143
+ expect { subject.json }.to raise_error Docker::Error::StateError
144
+ end
145
+ end
146
+
147
+ context 'when the Container has been created' do
148
+ context 'when the HTTP response status is not 200' do
149
+ before do
150
+ subject.stub(:created?).and_return(true)
151
+ Excon.stub({ :method => :get }, { :status => 500 })
152
+ end
153
+ after { Excon.stubs.shift }
154
+
155
+ it 'raises an error' do
156
+ expect { subject.json }
157
+ .to raise_error(Docker::Error::ServerError)
158
+ end
159
+ end
160
+
161
+ context 'when the HTTP response status is 200' do
162
+ let(:description) { subject.json }
163
+ before { subject.create!('Cmd' => ['ls'], 'Image' => 'base') }
164
+
165
+ it 'returns the description as a Hash', :vcr do
166
+ description.should be_a(Hash)
167
+ description['Id'].should start_with(subject.id)
168
+ end
169
+ end
170
+ end
171
+ end
172
+
173
+ describe '#changes' do
174
+ context 'when the Container has not been created' do
175
+ it 'raises an error' do
176
+ expect { subject.changes }
177
+ .to raise_error Docker::Error::StateError
178
+ end
179
+ end
180
+
181
+ context 'when the Container has been created' do
182
+ context 'when the HTTP response status is not 200' do
183
+ before do
184
+ subject.stub(:created?).and_return(true)
185
+ Excon.stub({ :method => :get }, { :status => 500 })
186
+ end
187
+ after { Excon.stubs.shift }
188
+
189
+ it 'raises an error' do
190
+ expect { subject.changes }
191
+ .to raise_error(Docker::Error::ServerError)
192
+ end
193
+ end
194
+
195
+ context 'when the HTTP response status is 200' do
196
+ let(:changes) { subject.changes }
197
+ before do
198
+ subject.create!('Cmd' => ['ls'], 'Image' => 'base')
199
+ subject.start
200
+ subject.wait
201
+ end
202
+
203
+ it 'returns the changes as an array', :vcr do
204
+ changes.should be_a Array
205
+ changes.should be_all { |change| change.is_a?(Hash) }
206
+ changes.length.should_not be_zero
207
+ end
208
+ end
209
+ end
210
+ end
211
+
212
+ describe '#export' do
213
+ context 'when the Container has not been created' do
214
+ it 'raises an error' do
215
+ expect { subject.export { } }
216
+ .to raise_error Docker::Error::StateError
217
+ end
218
+ end
219
+
220
+ context 'when the Container has been created' do
221
+ context 'when the HTTP response status is not 200' do
222
+ before do
223
+ subject.stub(:created?).and_return(true)
224
+ Excon.stub({ :method => :get }, { :status => 500 })
225
+ end
226
+ after { Excon.stubs.shift }
227
+
228
+ it 'raises an error' do
229
+ expect { subject.export { } }
230
+ .to raise_error(Docker::Error::ServerError)
231
+ end
232
+ end
233
+
234
+ context 'when the HTTP response status is 200' do
235
+ before do
236
+ subject.create!('Cmd' => %w[rm -rf / --no-preserve-root],
237
+ 'Image' => 'base')
238
+ subject.start
239
+ end
240
+
241
+ it 'yields each chunk', :vcr do
242
+ first = nil
243
+ subject.export do |chunk|
244
+ first = chunk
245
+ break
246
+ end
247
+ first[257..262].should == "ustar\000" # Make sure the export is a tar.
248
+ end
249
+ end
250
+ end
251
+ end
252
+
253
+ describe '#attach' do
254
+ context 'when the Container has not been created' do
255
+ it 'raises an error' do
256
+ expect { subject.attach { } }
257
+ .to raise_error Docker::Error::StateError
258
+ end
259
+ end
260
+
261
+ context 'when the Container has been created' do
262
+ context 'when the HTTP response status is not 200' do
263
+ before do
264
+ subject.stub(:created?).and_return(true)
265
+ Excon.stub({ :method => :post }, { :status => 500 })
266
+ end
267
+ after { Excon.stubs.shift }
268
+
269
+ it 'raises an error' do
270
+ expect { subject.attach { } }
271
+ .to raise_error(Docker::Error::ServerError)
272
+ end
273
+ end
274
+
275
+ context 'when the HTTP response status is 200' do
276
+ before { subject.create!('Cmd' => %w[uname -r], 'Image' => 'base') }
277
+
278
+ it 'yields each chunk', :vcr do
279
+ subject.tap(&:start).attach { |chunk|
280
+ chunk.should == "3.8.0-25-generic\n"
281
+ }
282
+ end
283
+ end
284
+ end
285
+ end
286
+
287
+ describe '#start' do
288
+ context 'when the Container has not been created' do
289
+ it 'raises an error' do
290
+ expect { subject.start }.to raise_error Docker::Error::StateError
291
+ end
292
+ end
293
+
294
+ context 'when the Container has been created' do
295
+ context 'when the HTTP response status is not 200' do
296
+ before do
297
+ subject.stub(:created?).and_return(true)
298
+ Excon.stub({ :method => :post }, { :status => 500 })
299
+ end
300
+ after { Excon.stubs.shift }
301
+
302
+ it 'raises an error' do
303
+ expect { subject.start }
304
+ .to raise_error(Docker::Error::ServerError)
305
+ end
306
+ end
307
+
308
+ context 'when the HTTP response status is 200' do
309
+ before { subject.create!('Cmd' => ['/usr/bin/sleep 10'],
310
+ 'Image' => 'base') }
311
+
312
+ it 'starts the container', :vcr do
313
+ subject.start
314
+ described_class.all.map(&:id).should be_any { |id|
315
+ id.start_with?(subject.id)
316
+ }
317
+ end
318
+ end
319
+ end
320
+ end
321
+
322
+ describe '#stop' do
323
+ context 'when the Container has not been created' do
324
+ it 'raises an error' do
325
+ expect { subject.stop }.to raise_error Docker::Error::StateError
326
+ end
327
+ end
328
+
329
+ context 'when the Container has been created' do
330
+ context 'when the HTTP response status is not 204' do
331
+ before do
332
+ subject.stub(:created?).and_return(true)
333
+ Excon.stub({ :method => :post }, { :status => 500 })
334
+ end
335
+ after { Excon.stubs.shift }
336
+
337
+ it 'raises an error' do
338
+ expect { subject.stop }
339
+ .to raise_error(Docker::Error::ServerError)
340
+ end
341
+ end
342
+
343
+ context 'when the HTTP response status is 204' do
344
+ before { subject.create!('Cmd' => ['ls'], 'Image' => 'base') }
345
+
346
+ it 'stops the container', :vcr do
347
+ subject.tap(&:start).stop
348
+ described_class.all(:all => true).map(&:id).should be_any { |id|
349
+ id.start_with?(subject.id)
350
+ }
351
+ described_class.all.map(&:id).should be_none { |id|
352
+ id.start_with?(subject.id)
353
+ }
354
+ end
355
+ end
356
+ end
357
+ end
358
+
359
+ describe '#kill' do
360
+ context 'when the Container has not been created' do
361
+ it 'raises an error' do
362
+ expect { subject.kill }.to raise_error Docker::Error::StateError
363
+ end
364
+ end
365
+
366
+ context 'when the Container has been created' do
367
+ context 'when the HTTP response status is not 204' do
368
+ before do
369
+ subject.stub(:created?).and_return(true)
370
+ Excon.stub({ :method => :post }, { :status => 500 })
371
+ end
372
+ after { Excon.stubs.shift }
373
+
374
+ it 'raises an error' do
375
+ expect { subject.kill }
376
+ .to raise_error(Docker::Error::ServerError)
377
+ end
378
+ end
379
+
380
+ context 'when the HTTP response status is 204' do
381
+ before { subject.create!('Cmd' => ['ls'], 'Image' => 'base') }
382
+
383
+ it 'kills the container', :vcr do
384
+ subject.kill
385
+ described_class.all.map(&:id).should be_none { |id|
386
+ id.start_with?(subject.id)
387
+ }
388
+ described_class.all(:all => true).map(&:id).should be_any { |id|
389
+ id.start_with?(subject.id)
390
+ }
391
+ end
392
+ end
393
+ end
394
+ end
395
+
396
+ describe '#restart' do
397
+ context 'when the Container has not been created' do
398
+ it 'raises an error' do
399
+ expect { subject.restart }.to raise_error Docker::Error::StateError
400
+ end
401
+ end
402
+
403
+ context 'when the Container has been created' do
404
+ context 'when the HTTP response status is not 204' do
405
+ before do
406
+ subject.stub(:created?).and_return(true)
407
+ Excon.stub({ :method => :post }, { :status => 500 })
408
+ end
409
+ after { Excon.stubs.shift }
410
+
411
+ it 'raises an error' do
412
+ expect { subject.restart }
413
+ .to raise_error(Docker::Error::ServerError)
414
+ end
415
+ end
416
+
417
+ context 'when the HTTP response status is 204' do
418
+ before { subject.create!('Cmd' => ['/usr/bin/sleep 50'],
419
+ 'Image' => 'base') }
420
+
421
+ it 'restarts the container', :vcr do
422
+ subject.start
423
+ described_class.all.map(&:id).should be_any { |id|
424
+ id.start_with?(subject.id)
425
+ }
426
+ subject.stop
427
+ described_class.all.map(&:id).should be_none { |id|
428
+ id.start_with?(subject.id)
429
+ }
430
+ subject.restart
431
+ described_class.all.map(&:id).should be_any { |id|
432
+ id.start_with?(subject.id)
433
+ }
434
+ end
435
+ end
436
+ end
437
+ end
438
+
439
+ describe '#wait' do
440
+ context 'when the Container has not been created' do
441
+ it 'raises an error' do
442
+ expect { subject.wait }.to raise_error Docker::Error::StateError
443
+ end
444
+ end
445
+
446
+ context 'when the Container has been created' do
447
+ context 'when the HTTP response status is not 200' do
448
+ before do
449
+ subject.stub(:created?).and_return(true)
450
+ Excon.stub({ :method => :post }, { :status => 500 })
451
+ end
452
+ after { Excon.stubs.shift }
453
+
454
+ it 'raises an error' do
455
+ expect { subject.wait }
456
+ .to raise_error(Docker::Error::ServerError)
457
+ end
458
+ end
459
+
460
+ context 'when the HTTP response status is 200' do
461
+ before { subject.create!('Cmd' => %w[tar nonsense],
462
+ 'Image' => 'base') }
463
+
464
+ it 'waits for the command to finish', :vcr do
465
+ subject.start
466
+ subject.wait['StatusCode'].should == 64
467
+ end
468
+ end
469
+ end
470
+ end
471
+
472
+ describe '#commit' do
473
+ context 'when the Container has not been created' do
474
+ it 'raises an error' do
475
+ expect { subject.commit }.to raise_error Docker::Error::StateError
476
+ end
477
+ end
478
+
479
+ context 'when the Container has been created' do
480
+ context 'when the HTTP response status is not 200' do
481
+ before do
482
+ subject.stub(:id).and_return('abc')
483
+ Excon.stub({ :method => :post }, { :status => 500 })
484
+ end
485
+ after { Excon.stubs.shift }
486
+
487
+ it 'raises an error' do
488
+ expect { subject.commit }
489
+ .to raise_error(Docker::Error::ServerError)
490
+ end
491
+ end
492
+
493
+ context 'when the HTTP response status is 200' do
494
+ let(:image) { subject.commit }
495
+ before { subject.create!('Cmd' => %w[ls], 'Image' => 'base') }
496
+
497
+ it 'creates a new Image from the Container\'s changes', :vcr do
498
+ subject.start
499
+ image.should be_a Docker::Image
500
+ image.id.should_not be_nil
501
+ end
502
+ end
503
+ end
504
+ end
505
+
506
+ describe '.all' do
507
+ subject { described_class }
508
+
509
+ context 'when the HTTP response is not a 200' do
510
+ before { Excon.stub({ :method => :get }, { :status => 500 }) }
511
+ after { Excon.stubs.shift }
512
+
513
+ it 'raises an error' do
514
+ expect { subject.all }
515
+ .to raise_error(Docker::Error::ServerError)
516
+ end
517
+ end
518
+
519
+ context 'when the HTTP response is a 200' do
520
+ it 'materializes each Container into a Docker::Container', :vcr do
521
+ subject.new.create!('Cmd' => ['ls'], 'Image' => 'base')
522
+ subject.all(:all => true).should be_all { |container|
523
+ container.is_a?(Docker::Container)
524
+ }
525
+ subject.all(:all => true).length.should_not be_zero
526
+ end
527
+ end
528
+ end
529
+ end