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