docker-api 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +2 -0
- data/lib/docker.rb +12 -2
- data/lib/docker/connection.rb +42 -49
- data/lib/docker/container.rb +12 -20
- data/lib/docker/error.rb +3 -0
- data/lib/docker/image.rb +21 -47
- data/lib/docker/model.rb +17 -14
- data/lib/docker/util.rb +12 -0
- data/lib/docker/version.rb +5 -1
- data/spec/docker/connection_spec.rb +56 -30
- data/spec/docker/container_spec.rb +81 -201
- data/spec/docker/image_spec.rb +48 -157
- data/spec/docker/util_spec.rb +43 -0
- data/spec/docker_spec.rb +15 -0
- data/spec/vcr/Docker/_info/returns_the_info_as_a_Hash.yml +10 -8
- data/spec/vcr/Docker/_validate_version/when_nothing_is_raised/validate_version_/.yml +33 -0
- data/spec/vcr/Docker/_version/returns_the_version_as_a_Hash.yml +10 -8
- data/spec/vcr/Docker_Container/_all/when_the_HTTP_response_is_a_200/materializes_each_Container_into_a_Docker_Container.yml +857 -17
- data/spec/vcr/Docker_Container/_attach/{when_the_HTTP_response_status_is_200/yields_each_chunk.yml → yields_each_chunk.yml} +9 -9
- data/spec/vcr/Docker_Container/_changes/{when_the_HTTP_response_status_is_200/returns_the_changes_as_an_array.yml → returns_the_changes_as_an_array.yml} +13 -13
- data/spec/vcr/Docker_Container/_commit/{when_the_HTTP_response_status_is_200/creates_a_new_Image_from_the_Container_s_changes.yml → creates_a_new_Image_from_the_Container_s_changes.yml} +12 -12
- 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 +4 -4
- data/spec/vcr/Docker_Container/_export/{when_the_HTTP_response_status_is_200/yields_each_chunk.yml → yields_each_chunk.yml} +11 -11
- data/spec/vcr/Docker_Container/_json/{when_the_HTTP_response_status_is_200/returns_the_description_as_a_Hash.yml → returns_the_description_as_a_Hash.yml} +8 -8
- data/spec/vcr/Docker_Container/_kill/{when_the_HTTP_response_status_is_204/kills_the_container.yml → kills_the_container.yml} +436 -16
- data/spec/vcr/Docker_Container/_restart/{when_the_HTTP_response_status_is_204/restarts_the_container.yml → restarts_the_container.yml} +29 -29
- data/spec/vcr/Docker_Container/_start/{when_the_HTTP_response_status_is_200/starts_the_container.yml → starts_the_container.yml} +12 -12
- data/spec/vcr/Docker_Container/_stop/{when_the_HTTP_response_status_is_204/stops_the_container.yml → stops_the_container.yml} +440 -20
- data/spec/vcr/Docker_Container/_wait/{when_the_HTTP_response_status_is_200/waits_for_the_command_to_finish.yml → waits_for_the_command_to_finish.yml} +10 -10
- data/spec/vcr/Docker_Image/_all/materializes_each_Image_into_a_Docker_Image.yml +65 -0
- data/spec/vcr/Docker_Image/_build/with_a_valid_Dockerfile/builds_an_image.yml +9 -5
- data/spec/vcr/Docker_Image/_build/with_an_invalid_Dockerfile/throws_a_UnexpectedResponseError.yml +9 -5
- data/spec/vcr/Docker_Image/_build_from_dir/with_a_valid_Dockerfile/builds_the_image.yml +17 -15
- 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 → _create/when_the_Image_does_not_yet_exist_and_the_body_is_a_Hash}/sets_the_id.yml +4 -2
- 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 → returns_the_history_of_the_Image.yml} +7 -5
- data/spec/vcr/Docker_Image/_import/when_the_file_does_exist/creates_the_Image.yml +6 -6
- data/spec/vcr/Docker_Image/_insert/{when_the_Image_has_been_created/when_the_HTTP_response_status_is_200/inserts_the_url_s_file_into_a_new_Image.yml → inserts_the_url_s_file_into_a_new_Image.yml} +30 -29
- 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 → returns_additional_information_about_image_image.yml} +7 -5
- data/spec/vcr/Docker_Image/_remove/removes_the_Image.yml +95 -0
- data/spec/vcr/Docker_Image/_run/when_the_argument_is_a_String/splits_the_String_by_spaces_and_creates_a_new_Container.yml +14 -12
- data/spec/vcr/Docker_Image/_run/when_the_argument_is_an_Array/creates_a_new_Container.yml +14 -12
- data/spec/vcr/Docker_Image/_search/{when_the_HTTP_response_is_a_200/materializes_each_Image_into_a_Docker_Image.yml → materializes_each_Image_into_a_Docker_Image.yml} +4 -34
- 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 → tags_the_image_with_the_repo_name.yml} +7 -5
- metadata +43 -56
- data/spec/vcr/Docker_Image/_all/when_the_HTTP_response_is_a_200/materializes_each_Image_into_a_Docker_Image.yml +0 -63
- 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 +0 -33
- data/spec/vcr/Docker_Image/_history/when_the_HTTP_response_status_is_200/returns_the_history_of_the_Image.yml +0 -63
- data/spec/vcr/Docker_Image/_insert/when_the_HTTP_response_status_is_200/inserts_the_url_s_file_into_a_new_Image.yml +0 -220
- data/spec/vcr/Docker_Image/_json/when_the_HTTP_response_status_is_200/returns_additional_information_about_image_image.yml +0 -63
- data/spec/vcr/Docker_Image/_remove/when_the_HTTP_response_status_is_204/removes_the_Image.yml +0 -93
- data/spec/vcr/Docker_Image/_remove/when_the_Image_has_been_created/when_the_HTTP_response_status_is_204/nils_out_the_id.yml +0 -63
- data/spec/vcr/Docker_Image/_remove/when_the_Image_has_been_created/when_the_HTTP_response_status_is_204/removes_the_Image.yml +0 -63
- data/spec/vcr/Docker_Image/_run/when_the_Image_has_been_created/when_the_argument_is_a_String/splits_the_String_by_spaces_and_creates_a_new_Container.yml +0 -119
- data/spec/vcr/Docker_Image/_run/when_the_Image_has_been_created/when_the_argument_is_an_Array/creates_a_new_Container.yml +0 -119
- data/spec/vcr/Docker_Image/_tag/when_the_HTTP_response_status_is_200/tags_the_image_with_the_repo_name.yml +0 -63
data/README.md
CHANGED
@@ -51,6 +51,8 @@ Docker.options = { :port => 5422 }
|
|
51
51
|
|
52
52
|
Two things to note here. The first is that this gem uses [excon](http://www.github.com/geemus/excon), so any of the options that are valid for `Excon.new` are alse valid for `Docker.options`. Second, by default Docker runs on port 4243. The gem will assume you want to connnect to port 4243 unless you specify otherwise.
|
53
53
|
|
54
|
+
Before doing anything else, ensure you have the correct version of the Docker API. To do this, run `Docker.validate_version!`. If your installed version is not supported, a `Docker::Error::VersionError` is raised.
|
55
|
+
|
54
56
|
## Global calls
|
55
57
|
|
56
58
|
All of the following examples require a connection to a Docker server. See the <a href="#starting-up">Starting up</a> section above for more information.
|
data/lib/docker.rb
CHANGED
@@ -39,12 +39,12 @@ module Docker
|
|
39
39
|
|
40
40
|
# Get the version of Go, Docker, and optionally the Git commit.
|
41
41
|
def version
|
42
|
-
connection.
|
42
|
+
Util.parse_json(connection.request(:get, '/version'))
|
43
43
|
end
|
44
44
|
|
45
45
|
# Get more information about the Docker server.
|
46
46
|
def info
|
47
|
-
connection.
|
47
|
+
Util.parse_json(connection.request(:get, '/info'))
|
48
48
|
end
|
49
49
|
|
50
50
|
# Login to the Docker registry.
|
@@ -53,10 +53,20 @@ module Docker
|
|
53
53
|
connection.post(:path => '/auth', :body => @creds)
|
54
54
|
true
|
55
55
|
end
|
56
|
+
|
57
|
+
# When the correct version of Docker is installed, returns true. Otherwise,
|
58
|
+
# raises a VersionError.
|
59
|
+
def validate_version!
|
60
|
+
Docker.info
|
61
|
+
true
|
62
|
+
rescue Docker::Error::DockerError
|
63
|
+
raise Docker::Error::VersionError, "Expected API Version: #{API_VERSION}"
|
64
|
+
end
|
56
65
|
end
|
57
66
|
|
58
67
|
require 'docker/version'
|
59
68
|
require 'docker/error'
|
69
|
+
require 'docker/util'
|
60
70
|
require 'docker/connection'
|
61
71
|
require 'docker/model'
|
62
72
|
require 'docker/container'
|
data/lib/docker/connection.rb
CHANGED
@@ -5,69 +5,62 @@ class Docker::Connection
|
|
5
5
|
|
6
6
|
attr_reader :url, :options
|
7
7
|
|
8
|
-
# Create a new Connection.
|
9
|
-
#
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
# Create a new Connection. This method takes a url (String) and options
|
9
|
+
# (Hash). These are passed to Excon, so any options valid for `Excon.new`
|
10
|
+
# can be passed here.
|
11
|
+
def initialize(url, opts)
|
12
|
+
case
|
13
|
+
when !url.is_a?(String)
|
14
|
+
raise ArgumentError, "Expected a String, got: '#{url}'"
|
15
|
+
when !opts.is_a?(Hash)
|
16
|
+
raise ArgumentError, "Expected a Hash, got: '#{opts}'"
|
17
|
+
else
|
18
|
+
@url, @options = url, opts
|
13
19
|
end
|
14
|
-
self.url = url
|
15
|
-
self.options = { :port => 4243 }.merge(options)
|
16
20
|
end
|
17
21
|
|
18
|
-
# The actual client that sends HTTP methods to the
|
22
|
+
# The actual client that sends HTTP methods to the Docker server. This value
|
23
|
+
# is not cached, since doing so may cause socket errors after bad requests.
|
19
24
|
def resource
|
20
|
-
|
25
|
+
Excon.new(url, options)
|
21
26
|
end
|
27
|
+
private :resource
|
22
28
|
|
23
|
-
#
|
24
|
-
|
25
|
-
|
26
|
-
|
29
|
+
# Send a request to the server with the `
|
30
|
+
def request(*args, &block)
|
31
|
+
resource.request(compile_request_params(*args, &block)).body
|
32
|
+
rescue Excon::Errors::BadRequest => ex
|
33
|
+
raise ClientError, ex.message
|
34
|
+
rescue Excon::Errors::InternalServerError => ex
|
35
|
+
raise ServerError, ex.message
|
27
36
|
end
|
28
37
|
|
29
|
-
# Delegate all HTTP methods to the
|
30
|
-
[:get, :put, :post, :delete
|
31
|
-
define_method(method)
|
32
|
-
begin
|
33
|
-
self.reset!
|
34
|
-
self.resource.public_send(method, *args, &block)
|
35
|
-
rescue Excon::Errors::BadRequest => ex
|
36
|
-
raise ClientError, ex.message
|
37
|
-
rescue Excon::Errors::InternalServerError => ex
|
38
|
-
raise ServerError, ex.message
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
# Send a request to the server and then parse it into a Hash.
|
44
|
-
def json_request(method, path, query = {}, &block)
|
45
|
-
params = compile_request_params(method, path, query, &block)
|
46
|
-
body = self.request(params).body
|
47
|
-
JSON.parse(body) unless body.nil? || body.empty? || (body == 'null')
|
48
|
-
rescue JSON::ParserError => ex
|
49
|
-
raise UnexpectedResponseError, ex.message
|
38
|
+
# Delegate all HTTP methods to the #request.
|
39
|
+
[:get, :put, :post, :delete].each do |method|
|
40
|
+
define_method(method) { |*args, &block| request(method, *args, &block) }
|
50
41
|
end
|
51
42
|
|
52
43
|
def to_s
|
53
|
-
"Docker::Connection { :url => #{
|
44
|
+
"Docker::Connection { :url => #{url}, :options => #{options} }"
|
54
45
|
end
|
55
46
|
|
56
47
|
private
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
48
|
+
# Given an HTTP method, path, optional query, extra options, and block,
|
49
|
+
# compiles a request.
|
50
|
+
def compile_request_params(http_method, path, query = nil, opts = nil, &block)
|
51
|
+
query ||= {}
|
52
|
+
opts ||= {}
|
53
|
+
headers = opts.delete(:headers) || {}
|
62
54
|
{
|
63
|
-
:method
|
64
|
-
:path
|
65
|
-
:query
|
66
|
-
:headers
|
67
|
-
|
68
|
-
|
69
|
-
:
|
70
|
-
:
|
71
|
-
|
55
|
+
:method => http_method,
|
56
|
+
:path => "/v#{Docker::API_VERSION}#{path}",
|
57
|
+
:query => query,
|
58
|
+
:headers => { 'Content-Type' => 'text/plain',
|
59
|
+
'User-Agent' => 'Docker-Client/0.4.6'
|
60
|
+
}.merge(headers),
|
61
|
+
:expects => (200..204),
|
62
|
+
:idempotent => http_method == :get,
|
63
|
+
:request_block => block
|
64
|
+
}.merge(opts).reject { |_, v| v.nil? }
|
72
65
|
end
|
73
66
|
end
|
data/lib/docker/container.rb
CHANGED
@@ -3,22 +3,14 @@
|
|
3
3
|
class Docker::Container
|
4
4
|
include Docker::Model
|
5
5
|
|
6
|
-
|
6
|
+
set_resource_prefix '/containers'
|
7
7
|
|
8
|
-
|
9
|
-
response =
|
10
|
-
|
11
|
-
:headers => { 'Content-Type' => 'text/plain',
|
12
|
-
'User-Agent' => "Docker-Client/0.4.6" },
|
13
|
-
:body => body.to_json,
|
14
|
-
:expects => (200..204)
|
15
|
-
)
|
16
|
-
@id = JSON.parse(response.body)['Id']
|
8
|
+
set_create_request do |body|
|
9
|
+
response = connection.post('/containers/create', nil, :body => body.to_json)
|
10
|
+
@id = Docker::Util.parse_json(response)['Id']
|
17
11
|
self
|
18
12
|
end
|
19
13
|
|
20
|
-
# Export the Container as a .tgz.
|
21
|
-
get :export
|
22
14
|
# Get more information about the Container.
|
23
15
|
get :json
|
24
16
|
# Wait for the current command to finish executing.
|
@@ -34,22 +26,22 @@ class Docker::Container
|
|
34
26
|
# Restart the Container
|
35
27
|
post :restart
|
36
28
|
|
29
|
+
# Export the Container as a tar.
|
30
|
+
def export(&block)
|
31
|
+
connection.get("/containers/#{id}/export", nil, :response_block => block)
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
37
35
|
# Attach to a container's standard streams / logs.
|
38
36
|
def attach(options = {})
|
39
37
|
options = { :stream => true, :stdout => true }.merge(options)
|
40
|
-
|
41
|
-
:path => "/containers/#{self.id}/attach",
|
42
|
-
:headers => { 'Content-Type' => 'text/plain',
|
43
|
-
'User-Agent' => "Docker-Client/0.4.6" },
|
44
|
-
:query => options,
|
45
|
-
:expects => (200..204)
|
46
|
-
).body
|
38
|
+
connection.post("/containers/#{id}/attach", options)
|
47
39
|
end
|
48
40
|
|
49
41
|
# Create an Image from a Container's change.s
|
50
42
|
def commit(options = {})
|
51
43
|
options.merge!('container' => self.id[0..7])
|
52
|
-
hash =
|
44
|
+
hash = JSON.parse(connection.request(:post, '/commit', options))
|
53
45
|
Docker::Image.send(:new, :id => hash['Id'], :connection => self.connection)
|
54
46
|
end
|
55
47
|
end
|
data/lib/docker/error.rb
CHANGED
data/lib/docker/image.rb
CHANGED
@@ -3,16 +3,11 @@ class Docker::Image
|
|
3
3
|
include Docker::Model
|
4
4
|
include Docker::Error
|
5
5
|
|
6
|
-
|
6
|
+
set_resource_prefix '/images'
|
7
7
|
|
8
|
-
|
9
|
-
body =
|
10
|
-
|
11
|
-
:headers => { 'User-Agent' => 'Docker-Client/0.4.6' },
|
12
|
-
:query => options,
|
13
|
-
:expects => (200..204)
|
14
|
-
).body
|
15
|
-
@id = JSON.parse(body)['status'] rescue nil
|
8
|
+
set_create_request do |options|
|
9
|
+
body = connection.post('/images/create', options)
|
10
|
+
@id = Docker::Util.parse_json(body)['status'] rescue nil
|
16
11
|
@id ||= options['fromImage']
|
17
12
|
@id ||= "#{options['repo']}/#{options['tag']}"
|
18
13
|
self
|
@@ -30,33 +25,19 @@ class Docker::Image
|
|
30
25
|
# to run the Image.
|
31
26
|
def run(cmd)
|
32
27
|
cmd = cmd.split(/\s+/) if cmd.is_a?(String)
|
33
|
-
Docker::Container.create({ 'Image' => self.id, 'Cmd' => cmd },
|
34
|
-
self.connection)
|
28
|
+
Docker::Container.create({ 'Image' => self.id, 'Cmd' => cmd }, connection)
|
35
29
|
.tap(&:start)
|
36
30
|
end
|
37
31
|
|
38
32
|
# Push the Image to the Docker registry.
|
39
33
|
def push(options = {})
|
40
|
-
|
41
|
-
:path => "/images/#{self.id}/push",
|
42
|
-
:headers => { 'Content-Type' => 'text/plain',
|
43
|
-
'User-Agent' => 'Docker-Client/0.4.6' },
|
44
|
-
:query => options,
|
45
|
-
:body => Docker.creds,
|
46
|
-
:expects => (200..204)
|
47
|
-
)
|
34
|
+
connection.post("/images/#{self.id}/push", options, :body => Docker.creds)
|
48
35
|
true
|
49
36
|
end
|
50
37
|
|
51
38
|
# Insert a file into the Image, returns a new Image that has that file.
|
52
39
|
def insert(query = {})
|
53
|
-
body =
|
54
|
-
:path => "/images/#{self.id}/insert",
|
55
|
-
:headers => { 'Content-Type' => 'text/plain',
|
56
|
-
'User-Agent' => "Docker-Client/0.4.6" },
|
57
|
-
:query => query,
|
58
|
-
:expects => (200..204)
|
59
|
-
).body
|
40
|
+
body = connection.post("/images/#{self.id}/insert", query)
|
60
41
|
if (id = body.match(/{"Id":"([a-f0-9]+)"}\z/)).nil? || id[1].empty?
|
61
42
|
raise UnexpectedResponseError, "Could not find Id in '#{body}'"
|
62
43
|
else
|
@@ -66,7 +47,7 @@ class Docker::Image
|
|
66
47
|
|
67
48
|
# Remove the Image from the server.
|
68
49
|
def remove
|
69
|
-
|
50
|
+
connection.request(:delete, "/images/#{self.id}", nil)
|
70
51
|
end
|
71
52
|
|
72
53
|
class << self
|
@@ -75,7 +56,8 @@ class Docker::Image
|
|
75
56
|
# Given a query like `{ :term => 'sshd' }`, queries the Docker Registry for
|
76
57
|
# a corresponiding Image.
|
77
58
|
def search(query = {}, connection = Docker.connection)
|
78
|
-
|
59
|
+
body = connection.request(:get, '/images/search', query)
|
60
|
+
hashes = Docker::Util.parse_json(body) || []
|
79
61
|
hashes.map { |hash| new(:id => hash['Name'], :connection => connection) }
|
80
62
|
end
|
81
63
|
|
@@ -83,24 +65,18 @@ class Docker::Image
|
|
83
65
|
def import(file, options = {}, connection = Docker.connection)
|
84
66
|
File.open(file, 'r') do |io|
|
85
67
|
body = connection.post(
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
).body
|
93
|
-
new(:id => JSON.parse(body)['status'], :connection => connection)
|
68
|
+
'/images/create',
|
69
|
+
options.merge('fromSrc' => '-'),
|
70
|
+
:headers => { 'Transfer-Encoding' => 'chunked' }
|
71
|
+
) { io.read(Excon.defaults[:chunk_size]).to_s }
|
72
|
+
new(:id => Docker::Util.parse_json(body)['status'],
|
73
|
+
:connection => connection)
|
94
74
|
end
|
95
75
|
end
|
96
76
|
|
97
77
|
# Given a Dockerfile as a string, builds an Image.
|
98
78
|
def build(commands, connection = Docker.connection)
|
99
|
-
body = connection.post(
|
100
|
-
:path => '/build',
|
101
|
-
:body => create_tar(commands),
|
102
|
-
:expects => (200..204)
|
103
|
-
).body
|
79
|
+
body = connection.post('/build', {}, :body => create_tar(commands))
|
104
80
|
new(:id => extract_id(body), :connection => connection)
|
105
81
|
end
|
106
82
|
|
@@ -108,12 +84,10 @@ class Docker::Image
|
|
108
84
|
def build_from_dir(dir, connection = Docker.connection)
|
109
85
|
tar = create_dir_tar(dir)
|
110
86
|
body = connection.post(
|
111
|
-
|
112
|
-
:headers
|
113
|
-
|
114
|
-
|
115
|
-
:expects => (200..204),
|
116
|
-
).body
|
87
|
+
'/build', {},
|
88
|
+
:headers => { 'Content-Type' => 'application/tar',
|
89
|
+
'Transfer-Encoding' => 'chunked' }
|
90
|
+
) { tar.read(Excon.defaults[:chunk_size]).to_s }
|
117
91
|
new(:id => extract_id(body), :connection => connection)
|
118
92
|
ensure
|
119
93
|
tar.close
|
data/lib/docker/model.rb
CHANGED
@@ -9,7 +9,7 @@ module Docker::Model
|
|
9
9
|
base.class_eval do
|
10
10
|
extend ClassMethods
|
11
11
|
private_class_method :new, :request, :get, :put, :post, :delete,
|
12
|
-
:
|
12
|
+
:set_create_request, :set_resource_prefix
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
@@ -17,12 +17,10 @@ module Docker::Model
|
|
17
17
|
# is specified and it is not a Docker::Connection, a
|
18
18
|
# Docker::Error::ArgumentError is raised.
|
19
19
|
def initialize(options = {})
|
20
|
-
options[:connection] ||= Docker.connection
|
21
|
-
|
22
|
-
raise ArgumentError, 'Expected a Docker::Connection.'
|
20
|
+
if (options[:connection] ||= Docker.connection).is_a?(Docker::Connection)
|
21
|
+
@id, @connection = options[:id], options[:connection]
|
23
22
|
else
|
24
|
-
|
25
|
-
@connection = options[:connection]
|
23
|
+
raise ArgumentError, 'Expected a Docker::Connection.'
|
26
24
|
end
|
27
25
|
end
|
28
26
|
|
@@ -33,24 +31,29 @@ module Docker::Model
|
|
33
31
|
# This defines the DSL for the including Classes.
|
34
32
|
module ClassMethods
|
35
33
|
include Docker::Error
|
34
|
+
attr_reader :resource_prefix, :create_request
|
36
35
|
|
37
36
|
# Define the Model's prefix for all requests.
|
38
|
-
def
|
39
|
-
|
37
|
+
def set_resource_prefix(val)
|
38
|
+
@resource_prefix = val
|
40
39
|
end
|
41
40
|
|
42
41
|
# Define how the Model should send a create request to the server.
|
43
|
-
def
|
44
|
-
|
42
|
+
def set_create_request(&block)
|
43
|
+
@create_request = block
|
45
44
|
end
|
46
45
|
|
47
46
|
# Define a method named `action` that sends an http `method` request to the
|
48
47
|
# Docker Server.
|
49
48
|
def request(method, action, opts = {}, &outer_block)
|
50
49
|
define_method(action) do |query = nil, &block|
|
51
|
-
|
52
|
-
|
53
|
-
|
50
|
+
new_opts = {
|
51
|
+
:path => "#{self.class.resource_prefix}/#{self.id}/#{action}",
|
52
|
+
:json => true
|
53
|
+
}.merge(opts)
|
54
|
+
body = connection.request(method, new_opts[:path], query,
|
55
|
+
new_opts[:excon], &block)
|
56
|
+
body = Docker::Util.parse_json(body) if new_opts[:json]
|
54
57
|
outer_block.nil? ? body : instance_exec(body, &outer_block)
|
55
58
|
end
|
56
59
|
end
|
@@ -71,7 +74,7 @@ module Docker::Model
|
|
71
74
|
# Retrieve every Instance of a model for the given server.
|
72
75
|
def all(options = {}, connection = Docker.connection)
|
73
76
|
path = "#{resource_prefix}/json"
|
74
|
-
hashes = connection.
|
77
|
+
hashes = Docker::Util.parse_json(connection.get(path, options)) || []
|
75
78
|
hashes.map { |hash| new(:id => hash['Id'], :connection => connection) }
|
76
79
|
end
|
77
80
|
end
|
data/lib/docker/util.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# This module holds shared logic that doesn't really belong anywhere else in the
|
2
|
+
# gem.
|
3
|
+
module Docker::Util
|
4
|
+
extend self
|
5
|
+
include Docker::Error
|
6
|
+
|
7
|
+
def parse_json(body)
|
8
|
+
JSON.parse(body) unless body.nil? || body.empty? || (body == 'null')
|
9
|
+
rescue JSON::ParserError => ex
|
10
|
+
raise UnexpectedResponseError, ex.message
|
11
|
+
end
|
12
|
+
end
|
data/lib/docker/version.rb
CHANGED
@@ -1,38 +1,34 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Docker::Connection do
|
4
|
+
subject { described_class.new('http://localhost', :port => 4243) }
|
5
|
+
|
4
6
|
describe '#initialize' do
|
5
|
-
|
7
|
+
let(:url) { 'http://localhost' }
|
8
|
+
let(:options) { { :port => 4243 } }
|
9
|
+
subject { described_class.new(url, options) }
|
6
10
|
|
7
|
-
context '
|
8
|
-
|
9
|
-
subject.new.options.should == { :port => 4243 }
|
10
|
-
end
|
11
|
+
context 'when the first argument is not a String' do
|
12
|
+
let(:url) { :lol_not_a_string }
|
11
13
|
|
12
|
-
it '
|
13
|
-
subject.
|
14
|
+
it 'raises an error' do
|
15
|
+
expect { subject }.to raise_error(Docker::Error::ArgumentError)
|
14
16
|
end
|
15
17
|
end
|
16
18
|
|
17
|
-
context '
|
18
|
-
context '
|
19
|
-
|
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 } }
|
19
|
+
context 'when the first argument is a String' do
|
20
|
+
context 'but the second argument is not a Hash' do
|
21
|
+
let(:options) { :lol_not_a_hash }
|
29
22
|
|
30
|
-
it '
|
31
|
-
subject.
|
23
|
+
it 'raises an error' do
|
24
|
+
expect { subject }.to raise_error(Docker::Error::ArgumentError)
|
32
25
|
end
|
26
|
+
end
|
33
27
|
|
34
|
-
|
35
|
-
|
28
|
+
context 'and the second argument is a Hash' do
|
29
|
+
it 'sets the url and options' do
|
30
|
+
subject.url.should == url
|
31
|
+
subject.options.should == options
|
36
32
|
end
|
37
33
|
end
|
38
34
|
end
|
@@ -42,12 +38,42 @@ describe Docker::Connection do
|
|
42
38
|
its(:resource) { should be_a Excon::Connection }
|
43
39
|
end
|
44
40
|
|
41
|
+
describe '#request' do
|
42
|
+
let(:method) { :get }
|
43
|
+
let(:path) { '/test' }
|
44
|
+
let(:query) { { :all => true } }
|
45
|
+
let(:options) { { :expects => 201, :lol => true } }
|
46
|
+
let(:body) { rand(10000000) }
|
47
|
+
let(:resource) { mock(:resource) }
|
48
|
+
let(:response) { mock(:response, :body => body) }
|
49
|
+
let(:expected_hash) {
|
50
|
+
{
|
51
|
+
:method => method,
|
52
|
+
:path => "/v#{Docker::API_VERSION}#{path}",
|
53
|
+
:query => query,
|
54
|
+
:headers => { 'Content-Type' => 'text/plain',
|
55
|
+
'User-Agent' => 'Docker-Client/0.4.6' },
|
56
|
+
:expects => 201,
|
57
|
+
:idempotent => true,
|
58
|
+
:lol => true
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
before do
|
63
|
+
subject.stub(:resource).and_return(resource)
|
64
|
+
resource.should_receive(:request).with(expected_hash).and_return(response)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'sends #request to #resource with the compiled params' do
|
68
|
+
subject.request(method, path, query, options).should == body
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
45
72
|
[:get, :put, :post, :delete].each do |method|
|
46
73
|
describe "##{method}" do
|
47
|
-
it 'is delegated to #
|
48
|
-
subject.should_receive(:
|
49
|
-
subject.
|
50
|
-
subject.public_send(method).should == :lol
|
74
|
+
it 'is delegated to #request' do
|
75
|
+
subject.should_receive(:request).with(method)
|
76
|
+
subject.public_send(method)
|
51
77
|
end
|
52
78
|
end
|
53
79
|
end
|
@@ -56,12 +82,12 @@ describe Docker::Connection do
|
|
56
82
|
let(:url) { 'google.com' }
|
57
83
|
let(:port) { 4000 }
|
58
84
|
let(:options) { { :port => port } }
|
59
|
-
let(:expected_string)
|
85
|
+
let(:expected_string) {
|
60
86
|
"Docker::Connection { :url => #{url}, :options => #{options} }"
|
61
|
-
|
87
|
+
}
|
62
88
|
subject { described_class.new(url, options) }
|
63
89
|
|
64
|
-
it 'returns a pretty
|
90
|
+
it 'returns a pretty version with the url and port' do
|
65
91
|
subject.to_s.should == expected_string
|
66
92
|
end
|
67
93
|
end
|