docker-api 1.28.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 208227d2b48722c6c994b7182065a13ee1e478ce
4
- data.tar.gz: 7fe1de106942c93a80884c0b5ca65a06bf237e81
2
+ SHA256:
3
+ metadata.gz: 7c8d229ece5b5b347a925c62c3f199a940393e58de0b02049aed295090739b04
4
+ data.tar.gz: 619467ed3697f6b9221f89bab145ba298bd47f5f26209e86df89347b6e83fe08
5
5
  SHA512:
6
- metadata.gz: 8420bbc610fa614cd590c9ff449a98e237c9bdaa20c91b03954c2c9d797849cb48f97e2048ee4589b095f0448fe9ab9e46685dcbe884d232cb3c9339313c47e3
7
- data.tar.gz: 64af52f1ed2c4a9b25c69759c06d77745e8ce7843db8675247b6225318b809754b4c336f50ebae01fbfcd20cc3b4adde22042c2eeb9fb96e7a03aba85a7b2a8d
6
+ metadata.gz: 7a1a3a2a995c4a172217fc80eb573501502f7f05a337080bf4723d90bb2ba9053cde786aaa994993bd2841ebeeb18cc8a5e4dfa342792bb558fb84a3f4688b89
7
+ data.tar.gz: c9bbb7ae229ba1784859ddb0e3bc6e7df1651718d552779baac4e2ad49d34096aba760e6ce99fa41dfd14ef6ea0f3a5ddfc9178cf1c7c7f286a8385be1c59385
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  docker-api
2
2
  ==========
3
- [![Gem Version](https://badge.fury.io/rb/docker-api.png)](https://badge.fury.io/rb/docker-api) [![travis-ci](https://travis-ci.org/swipely/docker-api.png?branch=master)](https://travis-ci.org/swipely/docker-api) [![Code Climate](https://codeclimate.com/github/swipely/docker-api.png)](https://codeclimate.com/github/swipely/docker-api) [![Dependency Status](https://gemnasium.com/swipely/docker-api.png)](https://gemnasium.com/swipely/docker-api)
3
+ [![Gem Version](https://badge.fury.io/rb/docker-api.svg)](https://badge.fury.io/rb/docker-api) [![travis-ci](https://travis-ci.org/swipely/docker-api.svg?branch=master)](https://travis-ci.org/swipely/docker-api) [![Code Climate](https://codeclimate.com/github/swipely/docker-api.svg)](https://codeclimate.com/github/swipely/docker-api)
4
4
 
5
- This gem provides an object-oriented interface to the [Docker Remote API](https://docs.docker.com/reference/api/docker_remote_api/). Every method listed there is implemented. At the time of this writing, docker-api is meant to interface with Docker version 1.3.*
5
+ This gem provides an object-oriented interface to the [Docker Engine API](https://docs.docker.com/develop/sdk/). Every method listed there is implemented. At the time of this writing, docker-api is meant to interface with Docker version 1.4.*
6
6
 
7
7
  If you're interested in using Docker to package your apps, we recommend the [dockly](https://github.com/swipely/dockly) gem. Dockly provides a simple DSL for describing Docker containers that install as Debian packages and are controlled by upstart scripts.
8
8
 
@@ -36,7 +36,7 @@ docker-api is designed to be very lightweight. Almost no state is cached (aside
36
36
 
37
37
  ## Starting up
38
38
 
39
- Follow the [installation instructions](https://docs.docker.com/installation/#installation), and then run:
39
+ Follow the [installation instructions](https://docs.docker.com/install/), and then run:
40
40
 
41
41
  ```shell
42
42
  $ sudo docker -d
@@ -52,7 +52,7 @@ If you're running Docker locally as a socket, there is no setup to do in Ruby. I
52
52
  Docker.url = 'tcp://example.com:5422'
53
53
  ```
54
54
 
55
- 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 also valid for `Docker.options`. Second, by default Docker runs on a socket. The gem will assume you want to connect to the socket unless you specify otherwise.
55
+ Two things to note here. The first is that this gem uses [excon](https://github.com/excon/excon), so any of the options that are valid for `Excon.new` are also valid for `Docker.options`. Second, by default Docker runs on a socket. The gem will assume you want to connect to the socket unless you specify otherwise.
56
56
 
57
57
  Also, you may set the above variables via `ENV` variables. For example:
58
58
 
@@ -94,6 +94,21 @@ Docker.options = {
94
94
  }
95
95
  ```
96
96
 
97
+ If you want to load the cert files from a variable, e.g. you want to load them from ENV as needed on Heroku:
98
+
99
+ ```
100
+ cert_store = OpenSSL::X509::Store.new
101
+ certificate = OpenSSL::X509::Certificate.new ENV["DOCKER_CA"]
102
+ cert_store.add_cert certificate
103
+
104
+ Docker.options = {
105
+ client_cert_data: ENV["DOCKER_CERT"],
106
+ client_key_data: ENV["DOCKER_KEY"],
107
+ ssl_cert_store: cert_store,
108
+ scheme: 'https'
109
+ }
110
+ ```
111
+
97
112
  If you need to disable SSL verification, set the DOCKER_SSL_VERIFY variable to 'false'.
98
113
 
99
114
  ## Global calls
@@ -115,6 +130,10 @@ Docker.info
115
130
  # docker command for reference: docker login
116
131
  Docker.authenticate!('username' => 'docker-fan-boi', 'password' => 'i<3docker', 'email' => 'dockerboy22@aol.com')
117
132
  # => true
133
+
134
+ # docker command for reference: docker login registry.gitlab.com
135
+ Docker.authenticate!('username' => 'docker-fan-boi', 'password' => 'i<3docker', 'email' => 'dockerboy22@aol.com', 'serveraddress' => 'https://registry.gitlab.com/v1/')
136
+ # => true
118
137
  ```
119
138
 
120
139
  ## Images
@@ -324,9 +343,77 @@ container.kill(:signal => "SIGHUP")
324
343
  container.top
325
344
  # => [{"PID"=>"4851", "TTY"=>"pts/0", "TIME"=>"00:00:00", "CMD"=>"lxc-start"}]
326
345
 
346
+ # Same as above, but uses the original format
347
+ container.top(format: :hash)
348
+ # => {
349
+ # "Titles" => ["PID", "TTY", "TIME", "CMD"],
350
+ # "Processes" => [["4851", "pts/0", "00:00:00", "lxc-start"]]
351
+ # }
352
+
353
+ # To expose 1234 to bridge
354
+ # In Dockerfile: EXPOSE 1234/tcp
355
+ # docker run resulting-image-name
356
+ Docker::Container.create(
357
+ 'Image' => 'image-name',
358
+ 'HostConfig' => {
359
+ 'PortBindings' => {
360
+ '1234/tcp' => [{}]
361
+ }
362
+ }
363
+ )
364
+
365
+ # To expose 1234 to host with any port
366
+ # docker run -p 1234 image-name
367
+ Docker::Container.create(
368
+ 'Image' => 'image-name',
369
+ 'ExposedPorts' => { '1234/tcp' => {} },
370
+ 'HostConfig' => {
371
+ 'PortBindings' => {
372
+ '1234/tcp' => [{}]
373
+ }
374
+ }
375
+ )
376
+
377
+ # To expose 1234 to host with a specified host port
378
+ # docker run -p 1234:1234 image-name
379
+ Docker::Container.create(
380
+ 'Image' => 'image-name',
381
+ 'ExposedPorts' => { '1234/tcp' => {} },
382
+ 'HostConfig' => {
383
+ 'PortBindings' => {
384
+ '1234/tcp' => [{ 'HostPort' => '1234' }]
385
+ }
386
+ }
387
+ )
388
+
389
+ # To expose 1234 to host with a specified host port and host IP
390
+ # docker run -p 192.168.99.100:1234:1234 image-name
391
+ Docker::Container.create(
392
+ 'Image' => 'image-name',
393
+ 'ExposedPorts' => { '1234/tcp' => {} },
394
+ 'HostConfig' => {
395
+ 'PortBindings' => {
396
+ '1234/tcp' => [{ 'HostPort' => '1234', 'HostIp' => '192.168.99.100' }]
397
+ }
398
+ }
399
+ )
400
+
401
+ # To set container name pass `name` key to options
402
+ Docker::Container.create(
403
+ 'name' => 'my-new-container',
404
+ 'Image' => 'image-name'
405
+ )
406
+
407
+ # Stores a file with the given content in the container
408
+ container.store_file("/test", "Hello world")
409
+
410
+ # Reads a file from the container
411
+ container.read_file("/test")
412
+ # => "Hello world"
413
+
327
414
  # Export a Container. Since an export is typically at least 300M, chunks of the
328
415
  # export are yielded instead of just returning the whole thing.
329
- File.open('export.tar', 'w') do |f|
416
+ File.open('export.tar', 'w') do |file|
330
417
  container.export { |chunk| file.write(chunk) }
331
418
  end
332
419
  # => nil
@@ -401,6 +488,10 @@ container = Docker::Container.create('Image' => 'ubuntu', 'Cmd' => command, 'Tty
401
488
  container.tap(&:start).attach(:tty => true)
402
489
  # => [["I'm a TTY!"], []]
403
490
 
491
+ # Obtaining the current statistics of a container
492
+ container.stats
493
+ # => {"read"=>"2016-02-29T20:47:05.221608695Z", "precpu_stats"=>{"cpu_usage"=> ... }
494
+
404
495
  # Create an Image from a Container's changes.
405
496
  container.commit
406
497
  # => Docker::Image { :id => eaeb8d00efdf, :connection => Docker::Connection { :url => tcp://localhost, :options => {:port=>2375} } }
@@ -446,6 +537,9 @@ container.exec(command, wait: 120)
446
537
  container.delete(:force => true)
447
538
  # => nil
448
539
 
540
+ # Update the container.
541
+ container.update("CpuShares" => 50000")
542
+
449
543
  # Request a Container by ID or name.
450
544
  Docker::Container.get('500f53b25e6e')
451
545
  # => Docker::Container { :id => , :connection => Docker::Connection { :url => tcp://localhost, :options => {:port=>2375} } }
@@ -528,10 +622,6 @@ image 'repo:new_tag' => 'repo:tag' do
528
622
  end
529
623
  ```
530
624
 
531
- ## Known issues
532
-
533
- * If the docker daemon is always responding to your requests with a 400 Bad Request when using UNIX sockets, verify you're running Excon version 0.46.0 or greater. [Link](https://github.com/swipely/docker-api/issues/381)
534
-
535
625
  ## Not supported (yet)
536
626
 
537
627
  * Generating a tarball of images and metadata for a repository specified by a name: https://docs.docker.com/engine/reference/api/docker_remote_api_v1.14/#get-a-tarball-containing-all-images-and-tags-in-a-repository
@@ -1,5 +1,5 @@
1
1
  require 'cgi'
2
- require 'json'
2
+ require 'multi_json'
3
3
  require 'excon'
4
4
  require 'tempfile'
5
5
  require 'base64'
@@ -14,7 +14,9 @@ require 'open-uri'
14
14
  require 'excon/middlewares/hijack'
15
15
  Excon.defaults[:middlewares].unshift Excon::Middleware::Hijack
16
16
 
17
- # The top-level module for this gem. It's purpose is to hold global
17
+ Excon.defaults[:middlewares] << Excon::Middleware::RedirectFollower
18
+
19
+ # The top-level module for this gem. Its purpose is to hold global
18
20
  # configuration variables that are used as defaults in other classes.
19
21
  module Docker
20
22
  attr_accessor :creds, :logger
@@ -119,25 +121,16 @@ module Docker
119
121
 
120
122
  # Login to the Docker registry.
121
123
  def authenticate!(options = {}, connection = self.connection)
122
- creds = options.to_json
123
- connection.post('/auth', {}, :body => creds)
124
+ creds = MultiJson.dump(options)
125
+ connection.post('/auth', {}, body: creds)
124
126
  @creds = creds
125
127
  true
126
128
  rescue Docker::Error::ServerError, Docker::Error::UnauthorizedError
127
129
  raise Docker::Error::AuthenticationError
128
130
  end
129
131
 
130
- # When the correct version of Docker is installed, returns true. Otherwise,
131
- # raises a VersionError.
132
- def validate_version!
133
- Docker.info
134
- true
135
- rescue Docker::Error::DockerError
136
- raise Docker::Error::VersionError, "Expected API Version: #{API_VERSION}"
137
- end
138
-
139
132
  module_function :default_socket_url, :env_url, :url, :url=, :env_options,
140
133
  :options, :options=, :creds, :creds=, :logger, :logger=,
141
134
  :connection, :reset!, :reset_connection!, :version, :info,
142
- :ping, :authenticate!, :validate_version!, :ssl_options
135
+ :ping, :authenticate!, :ssl_options
143
136
  end
@@ -80,14 +80,14 @@ private
80
80
  user_agent = "Swipely/Docker-API #{Docker::VERSION}"
81
81
  {
82
82
  :method => http_method,
83
- :path => "/v#{Docker::API_VERSION}#{path}",
83
+ :path => path,
84
84
  :query => query,
85
85
  :headers => { 'Content-Type' => content_type,
86
86
  'User-Agent' => user_agent,
87
87
  }.merge(headers),
88
- :expects => (200..204).to_a << 304,
88
+ :expects => (200..204).to_a << 301 << 304,
89
89
  :idempotent => http_method == :get,
90
- :request_block => block
90
+ :request_block => block,
91
91
  }.merge(opts).reject { |_, v| v.nil? }
92
92
  end
93
93
  end
@@ -11,17 +11,18 @@ class Docker::Container
11
11
  }
12
12
 
13
13
  info.merge!(self.json)
14
- other && info.merge!(other.info)
14
+ other && info.merge!(other.info) { |key, info_value, other_value| info_value }
15
15
  self
16
16
  end
17
17
 
18
18
  # Return a List of Hashes that represents the top running processes.
19
19
  def top(opts = {})
20
+ format = opts.delete(:format) { :array }
20
21
  resp = Docker::Util.parse_json(connection.get(path_for(:top), opts))
21
22
  if resp['Processes'].nil?
22
- []
23
+ format == :array ? [] : {}
23
24
  else
24
- resp['Processes'].map { |ary| Hash[resp['Titles'].zip(ary)] }
25
+ format == :array ? resp['Processes'].map { |ary| Hash[resp['Titles'].zip(ary)] } : resp
25
26
  end
26
27
  end
27
28
 
@@ -51,33 +52,37 @@ class Docker::Container
51
52
  # @param options [Hash] The options to pass to Docker::Exec
52
53
  #
53
54
  # @return [Docker::Exec] The Exec instance
54
- def exec(command, opts = {}, &block)
55
+ def exec(command, options = {}, &block)
55
56
  # Establish values
56
- tty = opts.delete(:tty) || false
57
- detach = opts.delete(:detach) || false
58
- user = opts.delete(:user)
59
- stdin = opts.delete(:stdin)
60
- stdout = opts.delete(:stdout) || !detach
61
- stderr = opts.delete(:stderr) || !detach
57
+ tty = options.delete(:tty) || false
58
+ detach = options.delete(:detach) || false
59
+ user = options.delete(:user)
60
+ stdin = options.delete(:stdin)
61
+ stdout = options.delete(:stdout) || !detach
62
+ stderr = options.delete(:stderr) || !detach
63
+ wait = options.delete(:wait)
64
+
65
+ opts = {
66
+ 'Container' => self.id,
67
+ 'User' => user,
68
+ 'AttachStdin' => !!stdin,
69
+ 'AttachStdout' => stdout,
70
+ 'AttachStderr' => stderr,
71
+ 'Tty' => tty,
72
+ 'Cmd' => command
73
+ }.merge(options)
62
74
 
63
75
  # Create Exec Instance
64
76
  instance = Docker::Exec.create(
65
- {
66
- 'Container' => self.id,
67
- 'User' => user,
68
- 'AttachStdin' => !!stdin,
69
- 'AttachStdout' => stdout,
70
- 'AttachStderr' => stderr,
71
- 'Tty' => tty,
72
- 'Cmd' => command
73
- },
77
+ opts,
74
78
  self.connection
75
79
  )
76
80
 
77
81
  start_opts = {
78
82
  :tty => tty,
79
83
  :stdin => stdin,
80
- :detach => detach
84
+ :detach => detach,
85
+ :wait => wait
81
86
  }
82
87
 
83
88
  if detach
@@ -95,7 +100,7 @@ class Docker::Container
95
100
  end
96
101
 
97
102
  # Attach to a container's standard streams / logs.
98
- def attach(options = {}, &block)
103
+ def attach(options = {}, excon_params = {}, &block)
99
104
  stdin = options.delete(:stdin)
100
105
  tty = options.delete(:tty)
101
106
 
@@ -105,8 +110,6 @@ class Docker::Container
105
110
  # Creates list to store stdout and stderr messages
106
111
  msgs = Docker::Messages.new
107
112
 
108
- excon_params = {}
109
-
110
113
  if stdin
111
114
  # If attaching to stdin, we must hijack the underlying TCP connection
112
115
  # so we can stream stdin to the remote Docker process
@@ -132,10 +135,10 @@ class Docker::Container
132
135
  # Based on the link, the config passed as run, needs to be passed as the
133
136
  # body of the post so capture it, remove from the options, and pass it via
134
137
  # the post body
135
- config = options.delete('run')
136
- hash = Docker::Util.parse_json(connection.post('/commit',
137
- options,
138
- :body => config.to_json))
138
+ config = MultiJson.dump(options.delete('run'))
139
+ hash = Docker::Util.parse_json(
140
+ connection.post('/commit', options, body: config)
141
+ )
139
142
  Docker::Image.send(:new, self.connection, hash)
140
143
  end
141
144
 
@@ -156,24 +159,47 @@ class Docker::Container
156
159
  connection.get(path_for(:logs), opts)
157
160
  end
158
161
 
162
+ def stats(options = {})
163
+ if block_given?
164
+ options[:read_timeout] ||= 10
165
+ options[:idempotent] ||= false
166
+ parser = lambda do |chunk, remaining_bytes, total_bytes|
167
+ yield Docker::Util.parse_json(chunk)
168
+ end
169
+ begin
170
+ connection.get(path_for(:stats), nil, {response_block: parser}.merge(options))
171
+ rescue Docker::Error::TimeoutError
172
+ # If the container stops, the docker daemon will hold the connection
173
+ # open forever, but stop sending events.
174
+ # So this Timeout indicates the stream is over.
175
+ end
176
+ else
177
+ Docker::Util.parse_json(connection.get(path_for(:stats), {stream: 0}.merge(options)))
178
+ end
179
+ end
180
+
159
181
  def rename(new_name)
160
182
  query = {}
161
183
  query['name'] = new_name
162
184
  connection.post(path_for(:rename), query)
163
185
  end
164
186
 
187
+ def update(opts)
188
+ connection.post(path_for(:update), {}, body: MultiJson.dump(opts))
189
+ end
190
+
165
191
  def streaming_logs(opts = {}, &block)
166
- stack_size = opts.delete('stack_size') || -1
192
+ stack_size = opts.delete('stack_size') || opts.delete(:stack_size) || -1
167
193
  tty = opts.delete('tty') || opts.delete(:tty) || false
168
194
  msgs = Docker::MessagesStack.new(stack_size)
169
- excon_params = {response_block: Docker::Util.attach_for(block, msgs, tty)}
195
+ excon_params = {response_block: Docker::Util.attach_for(block, msgs, tty), idempotent: false}
170
196
 
171
197
  connection.get(path_for(:logs), opts, excon_params)
172
198
  msgs.messages.join
173
199
  end
174
200
 
175
201
  def start!(opts = {})
176
- connection.post(path_for(:start), {}, :body => opts.to_json)
202
+ connection.post(path_for(:start), {}, body: MultiJson.dump(opts))
177
203
  self
178
204
  end
179
205
 
@@ -198,8 +224,18 @@ class Docker::Container
198
224
  define_method(:"#{method}!") do |opts = {}|
199
225
  timeout = opts.delete('timeout')
200
226
  query = {}
201
- query['t'] = timeout if timeout
202
- connection.post(path_for(method), query, :body => opts.to_json)
227
+ request_options = {
228
+ :body => MultiJson.dump(opts)
229
+ }
230
+ if timeout
231
+ query['t'] = timeout
232
+ # Ensure request does not timeout before Docker timeout
233
+ request_options.merge!(
234
+ read_timeout: timeout.to_i + 5,
235
+ write_timeout: timeout.to_i + 5
236
+ )
237
+ end
238
+ connection.post(path_for(method), query, request_options)
203
239
  self
204
240
  end
205
241
 
@@ -230,14 +266,6 @@ class Docker::Container
230
266
  end
231
267
  end
232
268
 
233
- def copy(path, &block)
234
- connection.post(path_for(:copy), {},
235
- :body => { "Resource" => path }.to_json,
236
- :response_block => block
237
- )
238
- self
239
- end
240
-
241
269
  def archive_out(path, &block)
242
270
  connection.get(
243
271
  path_for(:archive),
@@ -269,19 +297,43 @@ class Docker::Container
269
297
  self
270
298
  end
271
299
 
300
+ def read_file(path)
301
+ content = StringIO.new
302
+ archive_out(path) do |chunk|
303
+ content.write chunk
304
+ end
305
+
306
+ content.rewind
307
+
308
+ Gem::Package::TarReader.new(content) do |tar|
309
+ tar.each do |tarfile|
310
+ return tarfile.read
311
+ end
312
+ end
313
+ end
314
+
315
+ def store_file(path, file_content)
316
+ output_io = StringIO.new(
317
+ Docker::Util.create_tar(
318
+ path => file_content
319
+ )
320
+ )
321
+
322
+ archive_in_stream("/", overwrite: true) { output_io.read }
323
+ end
324
+
272
325
  # Create a new Container.
273
326
  def self.create(opts = {}, conn = Docker.connection)
274
- name = opts.delete('name')
275
- query = {}
276
- query['name'] = name if name
277
- resp = conn.post('/containers/create', query, :body => opts.to_json)
327
+ query = opts.select {|key| ['name', :name].include?(key) }
328
+ clean_opts = opts.reject {|key| ['name', :name].include?(key) }
329
+ resp = conn.post('/containers/create', query, :body => MultiJson.dump(clean_opts))
278
330
  hash = Docker::Util.parse_json(resp) || {}
279
331
  new(conn, hash)
280
332
  end
281
333
 
282
334
  # Return the container with specified ID
283
335
  def self.get(id, opts = {}, conn = Docker.connection)
284
- container_json = conn.get("/containers/#{URI.encode(id)}/json", opts)
336
+ container_json = conn.get("/containers/#{id}/json", opts)
285
337
  hash = Docker::Util.parse_json(container_json) || {}
286
338
  new(conn, hash)
287
339
  end
@@ -292,6 +344,12 @@ class Docker::Container
292
344
  hashes.map { |hash| new(conn, hash) }
293
345
  end
294
346
 
347
+ # Prune images
348
+ def self.prune(conn = Docker.connection)
349
+ conn.post("/containers/prune", {})
350
+ nil
351
+ end
352
+
295
353
  # Convenience method to return the path for a particular resource.
296
354
  def path_for(resource)
297
355
  "/containers/#{self.id}/#{resource}"