docker-api 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/README.md +21 -48
  2. data/lib/docker/connection.rb +8 -1
  3. data/lib/docker/container.rb +11 -12
  4. data/lib/docker/error.rb +0 -4
  5. data/lib/docker/image.rb +44 -30
  6. data/lib/docker/model.rb +30 -40
  7. data/lib/docker/version.rb +1 -1
  8. data/spec/docker/connection_spec.rb +3 -2
  9. data/spec/docker/container_spec.rb +206 -377
  10. data/spec/docker/image_spec.rb +123 -237
  11. data/spec/vcr/Docker_Container/_all/when_the_HTTP_response_is_a_200/materializes_each_Container_into_a_Docker_Container.yml +2503 -263
  12. data/spec/vcr/Docker_Container/_attach/{when_the_Container_has_been_created/when_the_HTTP_response_status_is_200 → when_the_HTTP_response_status_is_200}/yields_each_chunk.yml +30 -22
  13. data/spec/vcr/Docker_Container/_changes/{when_the_Container_has_been_created/when_the_HTTP_response_status_is_200 → when_the_HTTP_response_status_is_200}/returns_the_changes_as_an_array.yml +41 -33
  14. data/spec/vcr/Docker_Container/_commit/{when_the_Container_has_been_created/when_the_HTTP_response_status_is_200 → when_the_HTTP_response_status_is_200}/creates_a_new_Image_from_the_Container_s_changes.yml +34 -28
  15. data/spec/vcr/Docker_Container/{_create_ → _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 +11 -9
  16. data/spec/vcr/Docker_Container/_export/{when_the_Container_has_been_created/when_the_HTTP_response_status_is_200 → when_the_HTTP_response_status_is_200}/yields_each_chunk.yml +43 -29
  17. data/spec/vcr/Docker_Container/_json/when_the_HTTP_response_status_is_200/returns_the_description_as_a_Hash.yml +63 -0
  18. data/spec/vcr/Docker_Container/_kill/when_the_HTTP_response_status_is_204/kills_the_container.yml +1358 -0
  19. data/spec/vcr/Docker_Container/_restart/{when_the_Container_has_been_created/when_the_HTTP_response_status_is_204 → when_the_HTTP_response_status_is_204}/restarts_the_container.yml +73 -59
  20. data/spec/vcr/Docker_Container/_start/{when_the_Container_has_been_created/when_the_HTTP_response_status_is_200 → when_the_HTTP_response_status_is_200}/starts_the_container.yml +33 -28
  21. data/spec/vcr/Docker_Container/_stop/when_the_HTTP_response_status_is_204/stops_the_container.yml +1387 -0
  22. data/spec/vcr/Docker_Container/_wait/{when_the_Container_has_been_created/when_the_HTTP_response_status_is_200 → when_the_HTTP_response_status_is_200}/waits_for_the_command_to_finish.yml +31 -25
  23. data/spec/vcr/Docker_Image/_all/when_the_HTTP_response_is_a_200/materializes_each_Image_into_a_Docker_Image.yml +6 -6
  24. data/spec/vcr/Docker_Image/_build/with_a_valid_Dockerfile/builds_an_image.yml +4 -4
  25. data/spec/vcr/Docker_Image/_build/with_an_invalid_Dockerfile/throws_a_UnexpectedResponseError.yml +4 -4
  26. data/spec/vcr/Docker_Image/_build_from_dir/with_a_valid_Dockerfile/builds_the_image.yml +14 -14
  27. 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 +33 -0
  28. data/spec/vcr/Docker_Image/_history/when_the_HTTP_response_status_is_200/returns_the_history_of_the_Image.yml +63 -0
  29. data/spec/vcr/Docker_Image/_import/when_the_file_does_exist/creates_the_Image.yml +11 -9
  30. data/spec/vcr/Docker_Image/_insert/when_the_HTTP_response_status_is_200/inserts_the_url_s_file_into_a_new_Image.yml +220 -0
  31. data/spec/vcr/Docker_Image/_json/when_the_HTTP_response_status_is_200/returns_additional_information_about_image_image.yml +63 -0
  32. data/spec/vcr/Docker_Image/_remove/when_the_HTTP_response_status_is_204/removes_the_Image.yml +93 -0
  33. data/spec/vcr/Docker_Image/_remove/when_the_Image_has_been_created/when_the_HTTP_response_status_is_204/removes_the_Image.yml +63 -0
  34. 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 +119 -0
  35. data/spec/vcr/Docker_Image/_run/when_the_Image_has_been_created/when_the_argument_is_an_Array/creates_a_new_Container.yml +119 -0
  36. data/spec/vcr/Docker_Image/_run/when_the_argument_is_a_String/splits_the_String_by_spaces_and_creates_a_new_Container.yml +119 -0
  37. data/spec/vcr/Docker_Image/_run/when_the_argument_is_an_Array/creates_a_new_Container.yml +119 -0
  38. data/spec/vcr/Docker_Image/_tag/when_the_HTTP_response_status_is_200/tags_the_image_with_the_repo_name.yml +63 -0
  39. metadata +46 -29
  40. 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 +0 -61
  41. data/spec/vcr/Docker_Container/_kill/when_the_Container_has_been_created/when_the_HTTP_response_status_is_204/kills_the_container.yml +0 -232
  42. data/spec/vcr/Docker_Container/_stop/when_the_Container_has_been_created/when_the_HTTP_response_status_is_204/stops_the_container.yml +0 -259
data/README.md CHANGED
@@ -70,27 +70,16 @@ Docker.authenticate!('username' => 'docker-fan-boi', 'password' => 'i<3docker',
70
70
  ```
71
71
 
72
72
  ## Images
73
- Just about every method here has a one-to-one mapping with the [Images](http://docs.docker.io/en/latest/api/docker_remote_api_v1.2/#images) section of the API. If an API call accepts query parameters, these can be passed as an Hash to it's corresponding method.
73
+ Just about every method here has a one-to-one mapping with the [Images](http://docs.docker.io/en/latest/api/docker_remote_api_v1.2/#images) section of the API. If an API call accepts query parameters, these can be passed as an Hash to it's corresponding method. Also, note that `Docker::Image.new` is a private method, so you must use `.create`, `.build`, `.build_from_dir`, or `.import` to make an instance.
74
74
 
75
75
  ```ruby
76
76
  require 'docker'
77
77
  # => true
78
78
 
79
- # Instantiate a new Image. Note that this does NOT create the Image.
80
- image = Docker::Image.new
81
- # => Docker::Image { :id => , :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }
82
-
83
- # Test if the Image is created.
84
- image.created?
85
- # => false
86
-
87
- # Create the Image.
88
- image.create!('fromRepo' => 'base')
79
+ # Create an Image.
80
+ Docker::Image.create('fromRepo' => 'base')
89
81
  # => Docker::Image { :id => ae7ffbcd1, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }
90
82
 
91
- image.created?
92
- # => true
93
-
94
83
  # Insert a file into an Image. Returns a new Image that contains that file.
95
84
  image.insert('path' => '/google', 'url' => 'http://google.com')
96
85
  # => Docker::Image { :id => 11ef6c882, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }
@@ -112,25 +101,18 @@ image.history
112
101
  image.push
113
102
  # => true
114
103
 
104
+ # Given a command, create a new Container to run that command in the Image.
105
+ image.run('ls -l')
106
+ # => Docker::Container { id => aaef712eda, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }
107
+
115
108
  # Remove the Image from the server.
116
109
  image.remove
117
110
  # => true
118
111
 
119
- image.created?
120
- # => false
121
-
122
112
  # Given a Container's export, creates a new Image.
123
- image.create_from_file('some-export.tar')
113
+ Docker::Image.import('some-export.tar')
124
114
  # => Docker::Image { :id => 66b712aef, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }
125
115
 
126
- # Load all Images on your Docker server.
127
- Docker::Image.all
128
- # => [Docker::Image { :id => b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }, Docker::Image { :id => 8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }]
129
-
130
- # Search the Docker registry.
131
- Docker::Image.search('term' => 'sshd')
132
- # => [Docker::Image { :id => cespare/sshd, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }, Docker::Image { :id => johnfuller/sshd, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }, Docker::Image { :id => dhrp/mongodb-sshd, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }, Docker::Image { :id => rayang2004/sshd, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }, Docker::Image { :id => dhrp/sshd, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }, Docker::Image { :id => toorop/daemontools-sshd, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }, Docker::Image { :id => toorop/daemontools-sshd-nginx, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }, Docker::Image { :id => toorop/daemontools-sshd-nginx-php-fpm, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }, Docker::Image { :id => mbkan/lamp, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }, Docker::Image { :id => toorop/golang, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }, Docker::Image { :id => wma55/u1210sshd, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }, Docker::Image { :id => jdswinbank/sshd, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }, Docker::Image { :id => vgauthier/sshd, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }]
133
-
134
116
  # Create an Image from a Dockerfile as a String.
135
117
  Docker::Image.build("from base\nrun touch /test")
136
118
  # => Docker::Image { :id => b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }
@@ -138,29 +120,26 @@ Docker::Image.build("from base\nrun touch /test")
138
120
  # Create an Image from a Dockerfile.
139
121
  Dockerfile::Image.build_from_dir('.')
140
122
  # => Docker::Image { :id => 1266dc19e, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }
123
+
124
+ # Load all Images on your Docker server.
125
+ Docker::Image.all
126
+ # => [Docker::Image { :id => b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }, Docker::Image { :id => 8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }]
127
+
128
+ # Search the Docker registry.
129
+ Docker::Image.search('term' => 'sshd')
130
+ # => [Docker::Image { :id => cespare/sshd, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }, Docker::Image { :id => johnfuller/sshd, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }, Docker::Image { :id => dhrp/mongodb-sshd, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }, Docker::Image { :id => rayang2004/sshd, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }, Docker::Image { :id => dhrp/sshd, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }, Docker::Image { :id => toorop/daemontools-sshd, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }, Docker::Image { :id => toorop/daemontools-sshd-nginx, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }, Docker::Image { :id => toorop/daemontools-sshd-nginx-php-fpm, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }, Docker::Image { :id => mbkan/lamp, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }, Docker::Image { :id => toorop/golang, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }, Docker::Image { :id => wma55/u1210sshd, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }, Docker::Image { :id => jdswinbank/sshd, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }, Docker::Image { :id => vgauthier/sshd, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }]
141
131
  ```
142
132
 
143
133
  ## Containers
144
- Much like the Images, this object also has a one-to-one mapping with the [Containers](http://docs.docker.io/en/latest/api/docker_remote_api_v1.2/#containers) section of the API.
134
+ Much like the Images, this object also has a one-to-one mapping with the [Containers](http://docs.docker.io/en/latest/api/docker_remote_api_v1.2/#containers) section of the API. Also like Images, `.new` is a private method, so you must use `.create` to make an instance.
145
135
 
146
136
  ```ruby
147
137
  require 'docker'
148
138
 
149
- # Instantiate a new Container.
150
- container = Docker::Container.new
151
- # => Docker::Container { :id => , :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }
152
-
153
- # Test if the has been created.
154
- container.created?
155
- # => false
156
-
157
- # Create the Container.
158
- container.create!('Cmd' => ['ls'], 'Image' => 'base')
139
+ # Create a Container.
140
+ Docker::Container.create('Cmd' => ['ls'], 'Image' => 'base')
159
141
  # => Docker::Container { :id => 492510dd38e4, :connection => Docker::Connection { :url => http://localhost, :options => {:port=>4243} } }
160
142
 
161
- container.created?
162
- # => true
163
-
164
143
  # Get more information about the Container.
165
144
  container.json
166
145
  # => {"Id"=>"492510dd38e4da7703f36dfccd013de672b8250f57f59d1555ced647766b5e82", "Created"=>"2013-06-20T10:46:02.897548-04:00", "Path"=>"ls", "Args"=>[], "Config"=>{"Hostname"=>"492510dd38e4", "User"=>"", "Memory"=>0, "MemorySwap"=>0, "CpuShares"=>0, "AttachStdin"=>false, "AttachStdout"=>false, "AttachStderr"=>false, "PortSpecs"=>nil, "Tty"=>false, "OpenStdin"=>false, "StdinOnce"=>false, "Env"=>nil, "Cmd"=>["ls"], "Dns"=>nil, "Image"=>"base", "Volumes"=>nil, "VolumesFrom"=>""}, "State"=>{"Running"=>false, "Pid"=>0, "ExitCode"=>0, "StartedAt"=>"0001-01-01T00:00:00Z", "Ghost"=>false}, "Image"=>"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", "NetworkSettings"=>{"IpAddress"=>"", "IpPrefixLen"=>0, "Gateway"=>"", "Bridge"=>"", "PortMapping"=>nil}, "SysInitPath"=>"/usr/bin/docker", "ResolvConfPath"=>"/etc/resolv.conf", "Volumes"=>nil}
@@ -197,6 +176,7 @@ container.wait
197
176
  # => {'StatusCode'=>0}
198
177
 
199
178
  # Attach to the Container. Currently, the below options are the only valid ones.
179
+ # By default, :stream and :stdout are set.
200
180
  container.attach(:stream => true, :stdout => true, :stderr => true, :logs => true)
201
181
  # => "bin\nboot\ndev\netc\nhome\nlib\nlib64\nmedia\nmnt\nopt\nproc\nroot\nrun\nsbin\nselinux\nsrv\nsys\ntmp\nusr\nvar"
202
182
 
@@ -211,19 +191,12 @@ Docker::Container.all(:all => true)
211
191
 
212
192
  ## Connecting to Multiple Servers
213
193
 
214
- By default, each object connects to the connection specified by `Docker.connection`. If you need to connect to multiple servers, you can do so by specifying the connection on `#new` or in the utilizing class method. Examples:
194
+ By default, each object connects to the connection specified by `Docker.connection`. If you need to connect to multiple servers, you can do so by specifying the connection on `#new` or in the utilizing class method. For example:
215
195
 
216
196
  ```ruby
217
197
  require 'docker'
218
198
 
219
- Docker::Container.new(:connection => Docker::Connection.new(:url => 'http://example.com'))
220
199
  Docker::Container.all({}, Docker::Connection.new(:url => 'http://example.com'))
221
-
222
- Docker::Image.new(:connection => Docker::Connection.new(:url => 'http://example.com'))
223
- Docker::Image.all({}, Docker::Connection.new(:url => 'http://example.com'))
224
- Docker::Image.build('from base', Docker::Connection.new(:url => 'http://example.com'))
225
- Docker::Image.build_from_dir('.', Docker::Connection.new(:url => 'http://example.com'))
226
- Docker::Image.search({ :term => 'sshd' }, Docker::Connection.new(:url => 'http://example.com'))
227
200
  ```
228
201
 
229
202
  ## Known Issues
@@ -20,10 +20,17 @@ class Docker::Connection
20
20
  @resource ||= Excon.new(self.url, self.options)
21
21
  end
22
22
 
23
+ # Nil out the connection. This now happens on every request to prevent socket
24
+ # errors.
25
+ def reset!
26
+ @resource = nil
27
+ end
28
+
23
29
  # Delegate all HTTP methods to the resource.
24
30
  [:get, :put, :post, :delete, :request].each do |method|
25
31
  define_method(method) do |*args, &block|
26
32
  begin
33
+ self.reset!
27
34
  self.resource.public_send(method, *args, &block)
28
35
  rescue Excon::Errors::BadRequest => ex
29
36
  raise ClientError, ex.message
@@ -37,7 +44,7 @@ class Docker::Connection
37
44
  def json_request(method, path, query = {}, &block)
38
45
  params = compile_request_params(method, path, query, &block)
39
46
  body = self.request(params).body
40
- JSON.parse(body) unless (body.nil? || body.empty? || (body == 'null'))
47
+ JSON.parse(body) unless body.nil? || body.empty? || (body == 'null')
41
48
  rescue JSON::ParserError => ex
42
49
  raise UnexpectedResponseError, ex.message
43
50
  end
@@ -13,30 +13,30 @@ class Docker::Container
13
13
  :body => body.to_json,
14
14
  :expects => (200..204)
15
15
  )
16
- self.id = JSON.parse(response.body)['Id']
16
+ @id = JSON.parse(response.body)['Id']
17
17
  self
18
18
  end
19
19
 
20
20
  # Export the Container as a .tgz.
21
- docker_request :export, :get
21
+ get :export
22
22
  # Get more information about the Container.
23
- docker_request :json, :get
23
+ get :json
24
24
  # Wait for the current command to finish executing.
25
- docker_request :wait, :post
25
+ post :wait
26
26
  # Start the Container.
27
- docker_request :start, :post
27
+ post :start
28
28
  # Inspect the Container's changes to the filesysetem
29
- docker_request :changes, :get
29
+ get :changes
30
30
  # Stop the Container.
31
- docker_request :stop, :post
31
+ post :stop
32
32
  # Kill the Container.
33
- docker_request :kill, :post
33
+ post :kill
34
34
  # Restart the Container
35
- docker_request :restart, :post
35
+ post :restart
36
36
 
37
37
  # Attach to a container's standard streams / logs.
38
38
  def attach(options = {})
39
- ensure_created!
39
+ options = { :stream => true, :stdout => true }.merge(options)
40
40
  self.connection.post(
41
41
  :path => "/containers/#{self.id}/attach",
42
42
  :headers => { 'Content-Type' => 'text/plain',
@@ -48,9 +48,8 @@ class Docker::Container
48
48
 
49
49
  # Create an Image from a Container's change.s
50
50
  def commit(options = {})
51
- ensure_created!
52
51
  options.merge!('container' => self.id[0..7])
53
52
  hash = self.connection.json_request(:post, '/commit', options)
54
- Docker::Image.new(:id => hash['Id'], :connection => self.connection)
53
+ Docker::Image.send(:new, :id => hash['Id'], :connection => self.connection)
55
54
  end
56
55
  end
data/lib/docker/error.rb CHANGED
@@ -8,10 +8,6 @@ module Docker::Error
8
8
  # Raised when invalid arguments are passed to a method.
9
9
  class ArgumentError < DockerError; end
10
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
11
  # Raised when a request returns a 400.
16
12
  class ClientError < DockerError; end
17
13
 
data/lib/docker/image.rb CHANGED
@@ -5,13 +5,13 @@ class Docker::Image
5
5
 
6
6
  resource_prefix '/images'
7
7
 
8
- create_request do |options, excon_options|
9
- body = self.connection.post(excon_options.merge(
10
- :path => '/v1.3/images/create',
8
+ create_request do |options|
9
+ body = self.connection.post(
10
+ :path => '/images/create',
11
11
  :headers => { 'User-Agent' => 'Docker-Client/0.4.6' },
12
12
  :query => options,
13
13
  :expects => (200..204)
14
- )).body
14
+ ).body
15
15
  @id = JSON.parse(body)['status'] rescue nil
16
16
  @id ||= options['fromImage']
17
17
  @id ||= "#{options['repo']}/#{options['tag']}"
@@ -19,15 +19,24 @@ class Docker::Image
19
19
  end
20
20
 
21
21
  # Tag the Image.
22
- docker_request :tag, :post
22
+ post :tag
23
23
  # Get more information about the Image.
24
- docker_request :json, :get
24
+ get :json
25
25
  # Get the history of the Image.
26
- docker_request :history, :get
26
+ get :history
27
+
28
+ # Given a command and optional list of streams to attach to, run a command on
29
+ # an Image. This will not modify the Image, but rather create a new Container
30
+ # to run the Image.
31
+ def run(cmd)
32
+ cmd = cmd.split(/\s+/) if cmd.is_a?(String)
33
+ Docker::Container.create({ 'Image' => self.id, 'Cmd' => cmd },
34
+ self.connection)
35
+ .tap(&:start)
36
+ end
27
37
 
28
38
  # Push the Image to the Docker registry.
29
39
  def push(options = {})
30
- ensure_created!
31
40
  self.connection.post(
32
41
  :path => "/images/#{self.id}/push",
33
42
  :headers => { 'Content-Type' => 'text/plain',
@@ -41,7 +50,6 @@ class Docker::Image
41
50
 
42
51
  # Insert a file into the Image, returns a new Image that has that file.
43
52
  def insert(query = {})
44
- ensure_created!
45
53
  body = self.connection.post(
46
54
  :path => "/images/#{self.id}/insert",
47
55
  :headers => { 'Content-Type' => 'text/plain',
@@ -52,16 +60,13 @@ class Docker::Image
52
60
  if (id = body.match(/{"Id":"([a-f0-9]+)"}\z/)).nil? || id[1].empty?
53
61
  raise UnexpectedResponseError, "Could not find Id in '#{body}'"
54
62
  else
55
- Docker::Image.new(:id => id[1], :connection => self.connection)
63
+ self.class.send(:new, :id => id[1], :connection => self.connection)
56
64
  end
57
65
  end
58
66
 
59
67
  # Remove the Image from the server.
60
68
  def remove
61
- ensure_created!
62
69
  self.connection.json_request(:delete, "/images/#{self.id}", nil)
63
- self.id = nil
64
- true
65
70
  end
66
71
 
67
72
  class << self
@@ -74,46 +79,51 @@ class Docker::Image
74
79
  hashes.map { |hash| new(:id => hash['Name'], :connection => connection) }
75
80
  end
76
81
 
82
+ # Import an Image from the output of Docker::Container#export.
77
83
  def import(file, options = {}, connection = Docker.connection)
78
84
  File.open(file, 'r') do |io|
79
- read_chunked = proc { io.read(Excon.defaults[:chunk_size]).to_s }
80
- self.new(:connection => connection)
81
- .create!(options.merge('fromSrc' => '-'),
82
- :request_block => read_chunked)
85
+ 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)
83
94
  end
84
95
  end
85
96
 
86
97
  # Given a Dockerfile as a string, builds an Image.
87
98
  def build(commands, connection = Docker.connection)
88
99
  body = connection.post(
89
- :path => '/v1.3/build',
100
+ :path => '/build',
90
101
  :body => create_tar(commands),
91
102
  :expects => (200..204)
92
103
  ).body
93
104
  new(:id => extract_id(body), :connection => connection)
94
105
  end
95
106
 
107
+ # Given a directory that contains a Dockerfile, builds an Image.
96
108
  def build_from_dir(dir, connection = Docker.connection)
97
- cwd = FileUtils.pwd
98
- FileUtils.cd(dir)
99
- tar = create_dir_tar('.')
109
+ tar = create_dir_tar(dir)
100
110
  body = connection.post(
101
- :path => '/v1.3/build',
102
- :headers => { 'Content-Type' => 'application/tar',
103
- 'Transfer-Encoding' => 'chunked' },
111
+ :path => '/build',
112
+ :headers => { 'Content-Type' => 'application/tar',
113
+ 'Transfer-Encoding' => 'chunked' },
104
114
  :request_block => proc { tar.read(Excon.defaults[:chunk_size]).to_s },
105
- :expects => (200..204),
115
+ :expects => (200..204),
106
116
  ).body
107
117
  new(:id => extract_id(body), :connection => connection)
108
118
  ensure
109
119
  tar.close
110
- FileUtils.cd(cwd)
111
120
  end
112
121
 
113
122
  private
114
123
  def extract_id(body)
115
- if match = body.lines.to_a[-1].match(/^Successfully built ([a-f0-9]+)$/)
116
- match[1]
124
+ line = body.lines.to_a[-1]
125
+ if (id = line.match(/^Successfully built ([a-f0-9]+)$/)) && !id[1].empty?
126
+ id[1]
117
127
  else
118
128
  raise UnexpectedResponseError, "Couldn't find id: #{body}"
119
129
  end
@@ -129,16 +139,20 @@ class Docker::Image
129
139
  file = File.new('Dockerfile', 'w')
130
140
  file.write(input)
131
141
  file.close
132
- Archive::Tar::Minitar.pack_file("#{path}/Dockerfile", tar)
142
+ Archive::Tar::Minitar.pack_file("Dockerfile", tar)
133
143
  FileUtils.cd(cwd)
134
144
  FileUtils.rm_rf(path)
135
145
  string.tap(&:rewind)
136
146
  end
137
147
 
138
148
  def create_dir_tar(directory)
149
+ cwd = FileUtils.pwd
139
150
  tempfile = File.new('/tmp/out', 'w')
140
- Archive::Tar::Minitar.pack(directory, tempfile)
151
+ FileUtils.cd(directory)
152
+ Archive::Tar::Minitar.pack('.', tempfile)
141
153
  File.new('/tmp/out', 'r')
154
+ ensure
155
+ FileUtils.cd(cwd)
142
156
  end
143
157
  end
144
158
  end
data/lib/docker/model.rb CHANGED
@@ -1,10 +1,16 @@
1
1
  # This module is intended to be used as a Mixin for all objects exposed by the
2
2
  # Remote API. Currently, these are limited to Containers and Images.
3
3
  module Docker::Model
4
+ include Docker::Error
5
+
4
6
  attr_reader :id, :connection
5
7
 
6
8
  def self.included(base)
7
- base.extend(ClassMethods)
9
+ base.class_eval do
10
+ extend ClassMethods
11
+ private_class_method :new, :request, :get, :put, :post, :delete,
12
+ :create_request, :resource_prefix
13
+ end
8
14
  end
9
15
 
10
16
  # Creates a new Model with the specified id and Connection. If a Connection
@@ -12,39 +18,22 @@ module Docker::Model
12
18
  # Docker::Error::ArgumentError is raised.
13
19
  def initialize(options = {})
14
20
  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 = {}, excon_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'
21
+ if !options[:connection].is_a?(Docker::Connection)
22
+ raise ArgumentError, 'Expected a Docker::Connection.'
32
23
  else
33
- instance_exec(options, excon_options, &self.class.create_request)
24
+ @id = options[:id]
25
+ @connection = options[:connection]
34
26
  end
35
27
  end
36
28
 
37
- # Returns true if the Container has been created, false otherwise.
38
- def created?
39
- !!self.id
40
- end
41
-
42
29
  def to_s
43
30
  "#{self.class.name} { :id => #{id}, :connection => #{connection} }"
44
31
  end
45
32
 
46
33
  # This defines the DSL for the including Classes.
47
34
  module ClassMethods
35
+ include Docker::Error
36
+
48
37
  # Define the Model's prefix for all requests.
49
38
  def resource_prefix(val = nil)
50
39
  val.nil? ? @resource_prefix : (@resource_prefix = val)
@@ -57,32 +46,33 @@ module Docker::Model
57
46
 
58
47
  # Define a method named `action` that sends an http `method` request to the
59
48
  # Docker Server.
60
- def docker_request(action, method, &outer_block)
49
+ def request(method, action, opts = {}, &outer_block)
61
50
  define_method(action) do |query = nil, &block|
62
- ensure_created!
63
- path = "#{self.class.resource_prefix}/#{self.id}/#{action}"
51
+ path = opts[:path]
52
+ path ||= "#{self.class.send(:resource_prefix)}/#{self.id}/#{action}"
64
53
  body = self.connection.json_request(method, path, query, &block)
65
54
  outer_block.nil? ? body : instance_exec(body, &outer_block)
66
55
  end
67
56
  end
68
57
 
58
+ [:get, :put, :post, :delete].each do |method|
59
+ define_method(method) { |*args, &block| request(method, *args, &block) }
60
+ end
61
+
62
+ # Create a Model with the specified body. Raises a
63
+ # Docker::Error::ArgumentError if the argument is not a Hash. Otherwise,
64
+ # instances execs the Class's #create_request method with the single
65
+ # argument.
66
+ def create(opts = {}, conn = Docker.connection)
67
+ raise Docker::Error::ArgumentError, 'Expected a Hash' if !opts.is_a?(Hash)
68
+ new(:connection => conn).instance_exec(opts, &create_request)
69
+ end
70
+
69
71
  # Retrieve every Instance of a model for the given server.
70
72
  def all(options = {}, connection = Docker.connection)
71
- path = "#{self.resource_prefix}/json"
73
+ path = "#{resource_prefix}/json"
72
74
  hashes = connection.json_request(:get, path, options) || []
73
75
  hashes.map { |hash| new(:id => hash['Id'], :connection => connection) }
74
76
  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
77
  end
88
78
  end