docker-api 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.cane +0 -0
- data/.gitignore +3 -0
- data/Gemfile +3 -0
- data/README.md +215 -0
- data/Rakefile +16 -0
- data/docker-api.gemspec +27 -0
- data/lib/docker.rb +60 -0
- data/lib/docker/connection.rb +65 -0
- data/lib/docker/container.rb +54 -0
- data/lib/docker/error.rb +23 -0
- data/lib/docker/image.rb +73 -0
- data/lib/docker/model.rb +88 -0
- data/lib/docker/multipart.rb +30 -0
- data/lib/docker/version.rb +3 -0
- data/spec/docker/connection_spec.rb +67 -0
- data/spec/docker/container_spec.rb +529 -0
- data/spec/docker/image_spec.rb +379 -0
- data/spec/docker_spec.rb +58 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/support/vcr.rb +11 -0
- data/spec/vcr/Docker/_info/returns_the_info_as_a_Hash.yml +31 -0
- data/spec/vcr/Docker/_version/returns_the_version_as_a_Hash.yml +31 -0
- data/spec/vcr/Docker_Container/_all/when_the_HTTP_response_is_a_200/materializes_each_Container_into_a_Docker_Container.yml +333 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- data/spec/vcr/Docker_Image/_all/when_the_HTTP_response_is_a_200/materializes_each_Container_into_a_Docker_Container.yml +59 -0
- data/spec/vcr/Docker_Image/_build/with_a_valid_Dockerfile/builds_an_image.yml +45 -0
- data/spec/vcr/Docker_Image/_build/with_an_invalid_Dockerfile/throws_a_UnexpectedResponseError.yml +38 -0
- 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
- 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
- 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
- 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
- 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
- data/spec/vcr/Docker_Image/_search/when_the_HTTP_response_is_a_200/materializes_each_Container_into_a_Docker_Container.yml +64 -0
- 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
- metadata +281 -0
data/lib/docker/error.rb
ADDED
@@ -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
|
data/lib/docker/image.rb
ADDED
@@ -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
|
data/lib/docker/model.rb
ADDED
@@ -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,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
|