docker-api 1.0.1 → 1.1.0

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 (55) hide show
  1. data/README.md +2 -0
  2. data/lib/docker.rb +12 -2
  3. data/lib/docker/connection.rb +42 -49
  4. data/lib/docker/container.rb +12 -20
  5. data/lib/docker/error.rb +3 -0
  6. data/lib/docker/image.rb +21 -47
  7. data/lib/docker/model.rb +17 -14
  8. data/lib/docker/util.rb +12 -0
  9. data/lib/docker/version.rb +5 -1
  10. data/spec/docker/connection_spec.rb +56 -30
  11. data/spec/docker/container_spec.rb +81 -201
  12. data/spec/docker/image_spec.rb +48 -157
  13. data/spec/docker/util_spec.rb +43 -0
  14. data/spec/docker_spec.rb +15 -0
  15. data/spec/vcr/Docker/_info/returns_the_info_as_a_Hash.yml +10 -8
  16. data/spec/vcr/Docker/_validate_version/when_nothing_is_raised/validate_version_/.yml +33 -0
  17. data/spec/vcr/Docker/_version/returns_the_version_as_a_Hash.yml +10 -8
  18. data/spec/vcr/Docker_Container/_all/when_the_HTTP_response_is_a_200/materializes_each_Container_into_a_Docker_Container.yml +857 -17
  19. data/spec/vcr/Docker_Container/_attach/{when_the_HTTP_response_status_is_200/yields_each_chunk.yml → yields_each_chunk.yml} +9 -9
  20. 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
  21. 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
  22. 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
  23. data/spec/vcr/Docker_Container/_export/{when_the_HTTP_response_status_is_200/yields_each_chunk.yml → yields_each_chunk.yml} +11 -11
  24. 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
  25. data/spec/vcr/Docker_Container/_kill/{when_the_HTTP_response_status_is_204/kills_the_container.yml → kills_the_container.yml} +436 -16
  26. data/spec/vcr/Docker_Container/_restart/{when_the_HTTP_response_status_is_204/restarts_the_container.yml → restarts_the_container.yml} +29 -29
  27. data/spec/vcr/Docker_Container/_start/{when_the_HTTP_response_status_is_200/starts_the_container.yml → starts_the_container.yml} +12 -12
  28. data/spec/vcr/Docker_Container/_stop/{when_the_HTTP_response_status_is_204/stops_the_container.yml → stops_the_container.yml} +440 -20
  29. 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
  30. data/spec/vcr/Docker_Image/_all/materializes_each_Image_into_a_Docker_Image.yml +65 -0
  31. data/spec/vcr/Docker_Image/_build/with_a_valid_Dockerfile/builds_an_image.yml +9 -5
  32. data/spec/vcr/Docker_Image/_build/with_an_invalid_Dockerfile/throws_a_UnexpectedResponseError.yml +9 -5
  33. data/spec/vcr/Docker_Image/_build_from_dir/with_a_valid_Dockerfile/builds_the_image.yml +17 -15
  34. 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
  35. 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
  36. data/spec/vcr/Docker_Image/_import/when_the_file_does_exist/creates_the_Image.yml +6 -6
  37. 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
  38. 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
  39. data/spec/vcr/Docker_Image/_remove/removes_the_Image.yml +95 -0
  40. 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
  41. data/spec/vcr/Docker_Image/_run/when_the_argument_is_an_Array/creates_a_new_Container.yml +14 -12
  42. 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
  43. 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
  44. metadata +43 -56
  45. data/spec/vcr/Docker_Image/_all/when_the_HTTP_response_is_a_200/materializes_each_Image_into_a_Docker_Image.yml +0 -63
  46. 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
  47. data/spec/vcr/Docker_Image/_history/when_the_HTTP_response_status_is_200/returns_the_history_of_the_Image.yml +0 -63
  48. 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
  49. data/spec/vcr/Docker_Image/_json/when_the_HTTP_response_status_is_200/returns_additional_information_about_image_image.yml +0 -63
  50. data/spec/vcr/Docker_Image/_remove/when_the_HTTP_response_status_is_204/removes_the_Image.yml +0 -93
  51. 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
  52. 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
  53. 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
  54. 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
  55. 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.
@@ -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.json_request(:get, '/version')
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.json_request(:get, '/info')
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'
@@ -5,69 +5,62 @@ class Docker::Connection
5
5
 
6
6
  attr_reader :url, :options
7
7
 
8
- # Create a new Connection. By default, the Connection points to localhost at
9
- # port 4243, but this can be changed via an options Hash.
10
- def initialize(url = 'http://localhost', options = {})
11
- unless options.is_a?(Hash)
12
- raise Docker::Error::ArgumentError, "Expected a Hash, got: #{options}"
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 docker server.
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
- @resource ||= Excon.new(self.url, self.options)
25
+ Excon.new(url, options)
21
26
  end
27
+ private :resource
22
28
 
23
- # Nil out the connection. This now happens on every request to prevent socket
24
- # errors.
25
- def reset!
26
- @resource = nil
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 resource.
30
- [:get, :put, :post, :delete, :request].each do |method|
31
- define_method(method) do |*args, &block|
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 => #{self.url}, :options => #{self.options} }"
44
+ "Docker::Connection { :url => #{url}, :options => #{options} }"
54
45
  end
55
46
 
56
47
  private
57
- attr_writer :url, :options
58
-
59
- # Given an http_method, path, query, and optional block, returns the
60
- # corresponding request parameters.
61
- def compile_request_params(http_method, path, query, &block)
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 => http_method,
64
- :path => path,
65
- :query => query,
66
- :headers => { 'Content-Type' => 'text/plain',
67
- 'User-Agent' => "Docker-Client/0.4.6" },
68
- :expects => (200..204),
69
- :idempotent => http_method == :get,
70
- :response_block => block
71
- }.reject { |_, v| v.nil? }
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
@@ -3,22 +3,14 @@
3
3
  class Docker::Container
4
4
  include Docker::Model
5
5
 
6
- resource_prefix '/containers'
6
+ set_resource_prefix '/containers'
7
7
 
8
- create_request do |body|
9
- response = self.connection.post(
10
- :path => '/containers/create',
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
- self.connection.post(
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 = self.connection.json_request(:post, '/commit', options)
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
@@ -16,4 +16,7 @@ module Docker::Error
16
16
 
17
17
  # Raised when there is an unexpected response code / body.
18
18
  class UnexpectedResponseError < DockerError; end
19
+
20
+ # Raised when there is an incompatible version of Docker.
21
+ class VersionError < DockerError; end
19
22
  end
@@ -3,16 +3,11 @@ class Docker::Image
3
3
  include Docker::Model
4
4
  include Docker::Error
5
5
 
6
- resource_prefix '/images'
6
+ set_resource_prefix '/images'
7
7
 
8
- create_request do |options|
9
- body = self.connection.post(
10
- :path => '/images/create',
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
- self.connection.post(
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 = self.connection.post(
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
- self.connection.json_request(:delete, "/images/#{self.id}", nil)
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
- hashes = connection.json_request(:get, '/images/search', query) || []
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
- :path => '/images/create',
87
- :headers => { 'User-Agent' => 'Docker-Client/0.4.6',
88
- 'Transfer-Encoding' => 'chunked' },
89
- :query => options.merge('fromSrc' => '-'),
90
- :request_block => proc { io.read(Excon.defaults[:chunk_size]).to_s },
91
- :expects => (200..204)
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
- :path => '/build',
112
- :headers => { 'Content-Type' => 'application/tar',
113
- 'Transfer-Encoding' => 'chunked' },
114
- :request_block => proc { tar.read(Excon.defaults[:chunk_size]).to_s },
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
@@ -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
- :create_request, :resource_prefix
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
- if !options[:connection].is_a?(Docker::Connection)
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
- @id = options[:id]
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 resource_prefix(val = nil)
39
- val.nil? ? @resource_prefix : (@resource_prefix = val)
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 create_request(&block)
44
- block.nil? ? @create_request : (@create_request = block)
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
- path = opts[:path]
52
- path ||= "#{self.class.send(:resource_prefix)}/#{self.id}/#{action}"
53
- body = self.connection.json_request(method, path, query, &block)
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.json_request(:get, path, options) || []
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
@@ -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
@@ -1,3 +1,7 @@
1
1
  module Docker
2
- VERSION = '1.0.1'
2
+ # The version of the docker-api gem.
3
+ VERSION = '1.1.0'
4
+
5
+ # The version of the compatible Docker remote API.
6
+ API_VERSION = '1.3'
3
7
  end
@@ -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
- subject { described_class }
7
+ let(:url) { 'http://localhost' }
8
+ let(:options) { { :port => 4243 } }
9
+ subject { described_class.new(url, options) }
6
10
 
7
- context 'with no arguments' do
8
- it 'defaults to port 4243' do
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 'defaults to \'http://localhost\' for the url' do
13
- subject.new.url.should == 'http://localhost'
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 '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 } }
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 'sets the specified url' do
31
- subject.new(url, options).url.should == url
23
+ it 'raises an error' do
24
+ expect { subject }.to raise_error(Docker::Error::ArgumentError)
32
25
  end
26
+ end
33
27
 
34
- it 'sets the specified port' do
35
- subject.new(url, options).options[:port].should == port
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 #resource' do
48
- subject.should_receive(:reset!)
49
- subject.stub_chain(:resource, :public_send).and_return(:lol)
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) do
85
+ let(:expected_string) {
60
86
  "Docker::Connection { :url => #{url}, :options => #{options} }"
61
- end
87
+ }
62
88
  subject { described_class.new(url, options) }
63
89
 
64
- it 'returns a pretty printed version with the url and port' do
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