docker-api 0.0.2

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