docker-api 1.27.0 → 2.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: a402451b60cf3ee27d968b6c406b763ba02fd064
4
- data.tar.gz: 239c7b7261f471873819ade511325c785e632d0f
2
+ SHA256:
3
+ metadata.gz: 7c8d229ece5b5b347a925c62c3f199a940393e58de0b02049aed295090739b04
4
+ data.tar.gz: 619467ed3697f6b9221f89bab145ba298bd47f5f26209e86df89347b6e83fe08
5
5
  SHA512:
6
- metadata.gz: 68228f7e53d244b754ce9c26c3a2e412c4162595ae4fc069afea00aa5b89a499f205bcd2be5e055a274ce6e1f5ef2a9188dec364c2086505817176c4bb66a737
7
- data.tar.gz: 128d69692f073e103aeef72af0743d5ab3db5020b770242f8df4964da3b0ec6c88c70e587e25822e0a66c80cd3c6bebfdf05f7fa90d501850960b71272c29df9
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
@@ -108,13 +123,17 @@ require 'docker'
108
123
  Docker.version
109
124
  # => { 'Version' => '0.5.2', 'GoVersion' => 'go1.1' }
110
125
 
111
- # docker command for reference: docker info
126
+ # docker command for reference: docker info
112
127
  Docker.info
113
128
  # => { "Debug" => false, "Containers" => 187, "Images" => 196, "NFd" => 10, "NGoroutines" => 9, "MemoryLimit" => true }
114
129
 
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
@@ -245,6 +264,16 @@ Docker::Image.get('df4f1bdecf40')
245
264
  Docker::Image.exist?('ef723dcdac09')
246
265
  # => true
247
266
 
267
+ # Load an image from the file system
268
+ Docker::Image.load('./my-image.tar')
269
+ # => ""
270
+
271
+ # An IO object may also be specified for loading
272
+ File.open('./my-image.tar', 'rb') do |file|
273
+ Docker::Image.load(file)
274
+ end
275
+ # => ""
276
+
248
277
  # Export multiple images to a single tarball
249
278
  # docker command for reference: docker save my_image1 my_image2:not_latest > my_export.tar
250
279
  names = %w( my_image1 my_image2:not_latest )
@@ -314,9 +343,77 @@ container.kill(:signal => "SIGHUP")
314
343
  container.top
315
344
  # => [{"PID"=>"4851", "TTY"=>"pts/0", "TIME"=>"00:00:00", "CMD"=>"lxc-start"}]
316
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
+
317
414
  # Export a Container. Since an export is typically at least 300M, chunks of the
318
415
  # export are yielded instead of just returning the whole thing.
319
- File.open('export.tar', 'w') do |f|
416
+ File.open('export.tar', 'w') do |file|
320
417
  container.export { |chunk| file.write(chunk) }
321
418
  end
322
419
  # => nil
@@ -391,6 +488,10 @@ container = Docker::Container.create('Image' => 'ubuntu', 'Cmd' => command, 'Tty
391
488
  container.tap(&:start).attach(:tty => true)
392
489
  # => [["I'm a TTY!"], []]
393
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
+
394
495
  # Create an Image from a Container's changes.
395
496
  container.commit
396
497
  # => Docker::Image { :id => eaeb8d00efdf, :connection => Docker::Connection { :url => tcp://localhost, :options => {:port=>2375} } }
@@ -436,6 +537,9 @@ container.exec(command, wait: 120)
436
537
  container.delete(:force => true)
437
538
  # => nil
438
539
 
540
+ # Update the container.
541
+ container.update("CpuShares" => 50000")
542
+
439
543
  # Request a Container by ID or name.
440
544
  Docker::Container.get('500f53b25e6e')
441
545
  # => Docker::Container { :id => , :connection => Docker::Connection { :url => tcp://localhost, :options => {:port=>2375} } }
@@ -518,10 +622,6 @@ image 'repo:new_tag' => 'repo:tag' do
518
622
  end
519
623
  ```
520
624
 
521
- ## Known issues
522
-
523
- * 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)
524
-
525
625
  ## Not supported (yet)
526
626
 
527
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}"