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