docker-api 2.4.0 → 2.5.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
2
  SHA256:
3
- metadata.gz: c8c844030bda8d245234ddd5d977ad5202b9a2f0a2e34175579790d79e6f584b
4
- data.tar.gz: 5b793f7689d091a8a765853511e4f0f8f495338250cdec62bce30fc7a83d5117
3
+ metadata.gz: 8bd06b9a57651660c10d8e279df9ee588c3eaed258e4ced980f4090a5728a0b3
4
+ data.tar.gz: 07f5419a9f3e02af5db30e41fa91fd12afb01cfed84bae20d19fda55de27819b
5
5
  SHA512:
6
- metadata.gz: d5ce08a8dfbe1e5377ceb62d8186e4973ca6f7fa8d166bd9177f8f4a3338dc2dfeb3d735d3c54e31b15373f4124cc87d509d369a34e0f1598291105a4593947f
7
- data.tar.gz: d27a38af366bbb20febcc3699238a56f5c52d24394780c25d3caf72bc6f9314fb8bae6dd9c7f53cd5ff30558f5bbc353d22fc9f236913a2d8786488c9a702aea
6
+ metadata.gz: e778d8fbb81217c9ad405e0879b8e5754e17e79f6bdb072e717a2ac2e7ab044e3a62a8aab0c3d58fc1094a578ef4480451b478a4161e611a1ce10bbb980564cc
7
+ data.tar.gz: aa0670fd34bd8a2ee4aa16422af84a462451bde78807fe9d9ce2401bc4006f432e79b06d38a86aa0d2ee6d2c2a3cc2ca35b2c67e513565b91bd1ee351df75466
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  docker-api
2
2
  ==========
3
- [![Gem Version](https://badge.fury.io/rb/docker-api.svg)](https://badge.fury.io/rb/docker-api) [![Code Climate](https://codeclimate.com/github/upserve/docker-api.svg)](https://codeclimate.com/github/upserve/docker-api)
3
+ [![Gem Version](https://badge.fury.io/rb/docker-api.svg)](https://badge.fury.io/rb/docker-api)
4
4
 
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.*
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. The gem does not pin an API version, so requests use the daemon's default. It is tested against Docker 28 and 29.
6
6
 
7
7
  If you're interested in using Docker to package your apps, we recommend the [dockly](https://github.com/upserve/dockly) gem. Dockly provides a simple DSL for describing Docker containers that install as Debian packages and are controlled by upstart scripts.
8
8
 
@@ -41,7 +41,7 @@ At this time, basic `podman` support has been added via the podman docker-compat
41
41
  Follow the [installation instructions](https://docs.docker.com/install/), and then run:
42
42
 
43
43
  ```shell
44
- $ sudo docker -d
44
+ $ sudo dockerd
45
45
  ```
46
46
 
47
47
  This will daemonize Docker so that it can be used for the remote API calls.
@@ -138,15 +138,15 @@ Docker.authenticate!('username' => 'docker-fan-boi', 'password' => 'i<3docker',
138
138
 
139
139
  ## Images
140
140
 
141
- Just about every method here has a one-to-one mapping with the [Images](https://docs.docker.com/engine/reference/api/docker_remote_api_v1.14/#2-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`, `build_from_tar`, or `.import` to make an instance.
141
+ Just about every method here has a one-to-one mapping with the [Images](https://docs.docker.com/reference/api/engine/) 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`, `build_from_tar`, or `.import` to make an instance.
142
142
 
143
143
  ```ruby
144
144
  require 'docker'
145
145
  # => true
146
146
 
147
147
  # Pull an Image.
148
- # docker command for reference: docker pull ubuntu:14.04
149
- image = Docker::Image.create('fromImage' => 'ubuntu:14.04')
148
+ # docker command for reference: docker pull ubuntu:24.04
149
+ image = Docker::Image.create('fromImage' => 'ubuntu:24.04')
150
150
  # => Docker::Image { :id => ae7ffbcd1, :connection => Docker::Connection { :url => tcp://localhost, :options => {:port=>2375} } }
151
151
 
152
152
  # Insert a local file into an Image.
@@ -194,12 +194,12 @@ image.run('ls -l')
194
194
  # Remove the Image from the server.
195
195
  # docker command for reference: docker rmi -f <IMAGE.ID>
196
196
  image.remove(:force => true)
197
- # => true
197
+ # => "[{\"Untagged\":\"base2:latest\"}]\n"
198
198
 
199
199
  # Export a single Docker Image to a file
200
200
  # docker command for reference: docker save <IMAGE.ID> my_export.tar
201
201
  image.save('my_export.tar')
202
- # => Docker::Image { :id => 66b712aef, :connection => Docker::Connection { :url => tcp://localhost, :options => {:port=>2375} } }
202
+ # => nil
203
203
 
204
204
  # Return the raw image binary data
205
205
  image.save
@@ -225,7 +225,7 @@ File.open('my-export.tar') do |file|
225
225
  end
226
226
 
227
227
  # Create an Image from a Dockerfile as a String.
228
- Docker::Image.build("from base\nrun touch /test")
228
+ Docker::Image.build("FROM debian:stable\nRUN touch /test")
229
229
  # => Docker::Image { :id => b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc, :connection => Docker::Connection { :url => tcp://localhost, :options => {:port=>2375} } }
230
230
 
231
231
  # Create an Image from a Dockerfile.
@@ -256,7 +256,7 @@ Docker::Image.all
256
256
  # => [Docker::Image { :id => b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc, :connection => Docker::Connection { :url => tcp://localhost, :options => {:port=>2375} } }, Docker::Image { :id => 8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c, :connection => Docker::Connection { :url => tcp://localhost, :options => {:port=>2375} } }]
257
257
 
258
258
  # Get Image from the server, with id
259
- # docker command for reference: docker images <IMAGE.ID>
259
+ # docker command for reference: docker inspect <IMAGE.ID>
260
260
  Docker::Image.get('df4f1bdecf40')
261
261
  # => Docker::Image { :id => eb693ec80, :connection => Docker::Connection { :url => tcp://localhost, :options => {:port=>2375} } }
262
262
 
@@ -298,13 +298,13 @@ Docker::Image.search('term' => 'sshd')
298
298
 
299
299
  ## Containers
300
300
 
301
- Much like the Images, this object also has a one-to-one mapping with the [Containers](https://docs.docker.com/engine/reference/api/docker_remote_api_v1.14/#2-1-containers) section of the API. Also like Images, `.new` is a private method, so you must use `.create` to make an instance.
301
+ Much like the Images, this object also has a one-to-one mapping with the [Containers](https://docs.docker.com/reference/api/engine/) section of the API. Also like Images, `.new` is a private method, so you must use `.create` to make an instance.
302
302
 
303
303
  ```ruby
304
304
  require 'docker'
305
305
 
306
306
  # Create a Container.
307
- container = Docker::Container.create('Cmd' => ['ls'], 'Image' => 'base')
307
+ container = Docker::Container.create('Cmd' => ['ls'], 'Image' => 'debian:stable')
308
308
  # => Docker::Container { :id => 492510dd38e4, :connection => Docker::Connection { :url => tcp://localhost, :options => {:port=>2375} } }
309
309
 
310
310
  # Get more information about the Container.
@@ -423,15 +423,15 @@ container.changes
423
423
  # => [{'Path'=>'/dev', 'Kind'=>0}, {'Path'=>'/dev/kmsg', 'Kind'=>1}]
424
424
 
425
425
  # Copy files/directories from the Container. Note that these are exported as tars.
426
- container.copy('/etc/hosts') { |chunk| puts chunk }
427
-
428
- hosts0000644000000000000000000000023412100405636007023 0ustar
429
- 127.0.0.1 localhost
430
- ::1 localhost ip6-localhost ip6-loopback
431
- fe00::0 ip6-localnet
432
- ff00::0 ip6-mcastprefix
433
- ff02::1 ip6-allnodes
434
- ff02::2 ip6-allrouters
426
+ container.archive_out('/etc/hosts') { |chunk| puts chunk }
427
+
428
+ # hosts0000644000000000000000000000023412100405636007023 0ustar
429
+ # 127.0.0.1 localhost
430
+ # ::1 localhost ip6-localhost ip6-loopback
431
+ # fe00::0 ip6-localnet
432
+ # ff00::0 ip6-mcastprefix
433
+ # ff02::1 ip6-allnodes
434
+ # ff02::2 ip6-allrouters
435
435
  # => Docker::Container { :id => a1759f3e2873, :connection => Docker::Connection { :url => tcp://localhost, :options => {:port=>2375} } }
436
436
 
437
437
  # Wait for the current command to finish executing. If an argument is given,
@@ -445,13 +445,13 @@ container.attach(:stream => true, :stdin => nil, :stdout => true, :stderr => tru
445
445
  # => [["bin\nboot\ndev\netc\nhome\nlib\nlib64\nmedia\nmnt\nopt\nproc\nroot\nrun\nsbin\nselinux\nsrv\nsys\ntmp\nusr\nvar", []]
446
446
 
447
447
  # If you wish to stream the attach method, a block may be supplied.
448
- container = Docker::Container.create('Image' => 'base', 'Cmd' => ['find / -name *'])
448
+ container = Docker::Container.create('Image' => 'debian:stable', 'Cmd' => ['find / -name *'])
449
449
  container.tap(&:start).attach { |stream, chunk| puts "#{stream}: #{chunk}" }
450
- stderr: 2013/10/30 17:16:24 Unable to locate find / -name *
450
+ # stderr: 2013/10/30 17:16:24 Unable to locate find / -name *
451
451
  # => [[], ["2013/10/30 17:16:24 Unable to locate find / -name *\n"]]
452
452
 
453
453
  # If you want to attach to stdin of the container, supply an IO-like object:
454
- container = Docker::Container.create('Image' => 'base', 'Cmd' => ['cat'], 'OpenStdin' => true, 'StdinOnce' => true)
454
+ container = Docker::Container.create('Image' => 'debian:stable', 'Cmd' => ['cat'], 'OpenStdin' => true, 'StdinOnce' => true)
455
455
  container.tap(&:start).attach(stdin: StringIO.new("foo\nbar\n"))
456
456
  # => [["foo\nbar\n"], []]
457
457
 
@@ -470,16 +470,16 @@ container.logs(stdout: true)
470
470
 
471
471
  # Streaming logs from non-TTY container removing multiplex prefix with a block printing out each line (block not possible with Container#logs)
472
472
  container.streaming_logs(stdout: true) { |stream, chunk| puts "#{stream}: #{chunk}" }
473
- stdout: 1
474
- stdout: 2
475
- stdout: 3
476
- stdout: 4
477
- stdout: 5
478
- stdout: 6
479
- stdout: 7
480
- stdout: 8
481
- stdout: 9
482
- stdout: 10
473
+ # stdout: 1
474
+ # stdout: 2
475
+ # stdout: 3
476
+ # stdout: 4
477
+ # stdout: 5
478
+ # stdout: 6
479
+ # stdout: 7
480
+ # stdout: 8
481
+ # stdout: 9
482
+ # stdout: 10
483
483
  # => "1\n\n2\n\n3\n\n4\n\n5\n\n6\n\n7\n\n8\n\n9\n\n10\n"
484
484
 
485
485
  # If the container has TTY enabled, set `tty => true` to get the raw stream:
@@ -499,7 +499,7 @@ container.commit
499
499
  # Commit the Container and run a new command. The second argument is the number
500
500
  # of seconds the Container should wait before stopping its current command.
501
501
  container.run('pwd', 10)
502
- # => Docker::Image { :id => 4427be4199ac, :connection => Docker::Connection { :url => tcp://localhost, :options => {:port=>2375} } }
502
+ # => Docker::Container { :id => 4427be4199ac, :connection => Docker::Connection { :url => tcp://localhost, :options => {:port=>2375} } }
503
503
 
504
504
  # Run an Exec instance inside the container and capture its output and exit status
505
505
  container.exec(['date'])
@@ -511,12 +511,13 @@ container.exec(['./my_service'], detach: true)
511
511
 
512
512
  # Parse the output of an Exec instance
513
513
  container.exec(['find', '/', '-name *']) { |stream, chunk| puts "#{stream}: #{chunk}" }
514
- stderr: 2013/10/30 17:16:24 Unable to locate find / -name *
514
+ # stderr: 2013/10/30 17:16:24 Unable to locate find / -name *
515
515
  # => [[], ["2013/10/30 17:16:24 Unable to locate find / -name *\n"], 1]
516
516
 
517
- # Run an Exec instance by grab only the STDOUT output
518
- container.exec(['date'], stderr: false)
519
- # => [["Wed Nov 26 11:10:30 CST 2014\n"], [], 0]
517
+ # The return value is [stdout_lines, stderr_lines, exit_code], so destructure
518
+ # it if you only care about one stream.
519
+ stdout, _stderr, _exit_code = container.exec(['date'])
520
+ # stdout => ["Wed Nov 26 11:10:30 CST 2014\n"]
520
521
 
521
522
  # Pass input to an Exec instance command via Stdin
522
523
  container.exec(['cat'], stdin: StringIO.new("foo\nbar\n"))
@@ -529,7 +530,7 @@ container.exec(command, tty: true)
529
530
 
530
531
  # Wait for the current command to finish executing. If an argument is given,
531
532
  # will timeout after that number of seconds. The default is one minute.
532
- command = ["bash", "-c", "if [ -t 1 ]; then echo -n \"Set max seconds for exec!!\"; fi"]
533
+ command = ["bash", "-c", "if [ -t 1 ]; then echo -n \"Set max seconds for exec!\"; fi"]
533
534
  container.exec(command, wait: 120)
534
535
  # => [["Set max seconds for exec!"], [], 0]
535
536
 
@@ -538,7 +539,7 @@ container.delete(:force => true)
538
539
  # => nil
539
540
 
540
541
  # Update the container.
541
- container.update("CpuShares" => 50000")
542
+ container.update("CpuShares" => 50000)
542
543
 
543
544
  # Request a Container by ID or name.
544
545
  Docker::Container.get('500f53b25e6e')
@@ -578,17 +579,37 @@ require 'docker'
578
579
 
579
580
  # Action on a stream of events as they come in
580
581
  Docker::Event.stream { |event| puts event; break }
581
- Docker::Event { :status => create, :id => aeb8b55726df63bdd69d41e1b2650131d7ce32ca0d2fa5cbc75f24d0df34c7b0, :from => base:latest, :time => 1416958554 }
582
+ # Docker::Event { :status => create, :id => aeb8b55726df63bdd69d41e1b2650131d7ce32ca0d2fa5cbc75f24d0df34c7b0, :from => debian:stable, :time => 1416958554 }
582
583
  # => nil
583
584
 
584
585
  # Action on all events after a given time (will execute the block for all events up till the current time, and wait to execute on any new events after)
585
586
  Docker::Event.since(1416958763) { |event| puts event; puts Time.now.to_i; break }
586
- Docker::Event { :status => die, :id => 663005cdeb56f50177c395a817dbc8bdcfbdfbdaef329043b409ecb97fb68d7e, :from => base:latest, :time => 1416958764 }
587
- 1416959041
587
+ # Docker::Event { :status => die, :id => 663005cdeb56f50177c395a817dbc8bdcfbdfbdaef329043b409ecb97fb68d7e, :from => debian:stable, :time => 1416958764 }
588
+ # 1416959041
588
589
  # => nil
589
590
  ```
590
591
 
591
- These methods are prone to read timeouts. `Docker.options[:read_timeout]` will need to be made higher than 60 seconds if expecting a long time between events.
592
+ These methods are prone to read timeouts. Note that the first argument to
593
+ `Docker::Event.stream` and `Docker::Event.since` is a hash of **query parameters**
594
+ for the `/events` endpoint, not Excon options. Passing `read_timeout` there sends
595
+ it to Docker as a query parameter and has no effect on the timeout. Set it on the
596
+ connection instead:
597
+
598
+ ```ruby
599
+ # Disable timeouts completely, for this call only
600
+ conn = Docker::Connection.new(Docker.url, Docker.options.merge(read_timeout: 0))
601
+ Docker::Event.stream({}, conn) { |event| puts event }
602
+
603
+ # Timeout if no events are received in 24h
604
+ conn = Docker::Connection.new(Docker.url, Docker.options.merge(read_timeout: 60 * 60 * 24))
605
+ Docker::Event.stream({}, conn) { |event| puts event }
606
+
607
+ # Or set a high timeout globally. Be warned that you probably don't want this
608
+ # for other methods. This has to happen before the default connection is first
609
+ # used, since `Docker.connection` is memoized.
610
+ Docker.options[:read_timeout] = 60 * 60 * 24
611
+ Docker::Event.stream { |event| puts event }
612
+ ```
592
613
 
593
614
  ## Connecting to Multiple Servers
594
615
 
@@ -624,8 +645,8 @@ end
624
645
 
625
646
  ## Not supported (yet)
626
647
 
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
628
- * Load a tarball generated from docker that contains all the images and metadata of a repository: https://docs.docker.com/engine/reference/api/docker_remote_api_v1.14/#load-a-tarball-with-a-set-of-images-and-tags-into-docker
648
+ * Generating a tarball of images and metadata for a repository specified by a name
649
+ * Load a tarball generated from docker that contains all the images and metadata of a repository
629
650
 
630
651
  License
631
652
  -----
@@ -129,7 +129,12 @@ class Docker::Connection
129
129
  end
130
130
 
131
131
  def rootless?
132
- @rootless ||= (info['Rootless'] == true)
132
+ # `||=` cannot cache a false result, so this has to test for the ivar
133
+ # itself. #info is not memoized, so re-evaluating means another /info
134
+ # request on every call against a non-rootless daemon.
135
+ return @rootless if defined?(@rootless)
136
+
137
+ @rootless = (info['Rootless'] == true)
133
138
  end
134
139
 
135
140
  def version
@@ -60,8 +60,11 @@ class Docker::Container
60
60
  detach = options.delete(:detach) || false
61
61
  user = options.delete(:user)
62
62
  stdin = options.delete(:stdin)
63
- stdout = options.delete(:stdout) || !detach
64
- stderr = options.delete(:stderr) || !detach
63
+ # `options.delete(:stdout) || !detach` turns an explicit false back into
64
+ # true, so these have to be tested for presence rather than truthiness.
65
+ # They default to the inverse of :detach when the caller says nothing.
66
+ stdout = options.key?(:stdout) ? options.delete(:stdout) : !detach
67
+ stderr = options.key?(:stderr) ? options.delete(:stderr) : !detach
65
68
  wait = options.delete(:wait)
66
69
 
67
70
  opts = {
@@ -118,6 +121,16 @@ class Docker::Container
118
121
  opts[:stdin] = true
119
122
  excon_params[:hijack_block] = Docker::Util.hijack_for(stdin, block,
120
123
  msgs, tty)
124
+ # Hijacking means asking the daemon to upgrade the connection. Without
125
+ # these headers Docker answers 200 and leaves the payload on Excon's
126
+ # buffered socket, which the raw hijacked read never looks at, so the
127
+ # attach blocks forever. With them the daemon replies 101 and hands the
128
+ # socket over, so 101 has to be an accepted status.
129
+ excon_params[:headers] = {
130
+ 'Connection' => 'Upgrade',
131
+ 'Upgrade' => 'tcp'
132
+ }.merge(excon_params[:headers] || {})
133
+ excon_params[:expects] ||= (200..204).to_a + [101, 301, 304]
121
134
  else
122
135
  excon_params[:response_block] = Docker::Util.attach_for(block, msgs, tty)
123
136
  end
@@ -137,7 +150,7 @@ class Docker::Container
137
150
  # Based on the link, the config passed as run, needs to be passed as the
138
151
  # body of the post so capture it, remove from the options, and pass it via
139
152
  # the post body
140
- config = MultiJson.dump(options.delete('run'))
153
+ config = JSON.generate(options.delete('run'))
141
154
  hash = Docker::Util.parse_json(
142
155
  connection.post('/commit', options, body: config)
143
156
  )
@@ -187,7 +200,7 @@ class Docker::Container
187
200
  end
188
201
 
189
202
  def update(opts)
190
- connection.post(path_for(:update), {}, body: MultiJson.dump(opts))
203
+ connection.post(path_for(:update), {}, body: JSON.generate(opts))
191
204
  end
192
205
 
193
206
  def streaming_logs(opts = {}, &block)
@@ -201,7 +214,7 @@ class Docker::Container
201
214
  end
202
215
 
203
216
  def start!(opts = {})
204
- connection.post(path_for(:start), {}, body: MultiJson.dump(opts))
217
+ connection.post(path_for(:start), {}, body: JSON.generate(opts))
205
218
  self
206
219
  end
207
220
 
@@ -227,7 +240,7 @@ class Docker::Container
227
240
  timeout = opts.delete('timeout')
228
241
  query = {}
229
242
  request_options = {
230
- :body => MultiJson.dump(opts)
243
+ :body => JSON.generate(opts)
231
244
  }
232
245
  if timeout
233
246
  query['t'] = timeout
@@ -324,11 +337,16 @@ class Docker::Container
324
337
  archive_in_stream("/", overwrite: true) { output_io.read }
325
338
  end
326
339
 
340
+ # Keys that /containers/create takes as query parameters rather than in the
341
+ # request body. The daemon silently ignores them if they are sent in the
342
+ # body, so a misplaced key means the option has no effect at all.
343
+ CREATE_QUERY_KEYS = %w[name platform].freeze
344
+
327
345
  # Create a new Container.
328
346
  def self.create(opts = {}, conn = Docker.connection)
329
- query = opts.select {|key| ['name', :name].include?(key) }
330
- clean_opts = opts.reject {|key| ['name', :name].include?(key) }
331
- resp = conn.post('/containers/create', query, :body => MultiJson.dump(clean_opts))
347
+ query = opts.select { |key| CREATE_QUERY_KEYS.include?(key.to_s) }
348
+ clean_opts = opts.reject { |key| CREATE_QUERY_KEYS.include?(key.to_s) }
349
+ resp = conn.post('/containers/create', query, :body => JSON.generate(clean_opts))
332
350
  hash = Docker::Util.parse_json(resp) || {}
333
351
  new(conn, hash)
334
352
  end
data/lib/docker/exec.rb CHANGED
@@ -29,7 +29,7 @@ class Docker::Exec
29
29
  end
30
30
 
31
31
  resp = conn.post("/containers/#{container}/exec", {},
32
- body: MultiJson.dump(options))
32
+ body: JSON.generate(options))
33
33
  hash = Docker::Util.parse_json(resp) || {}
34
34
  new(conn, hash)
35
35
  end
@@ -60,7 +60,7 @@ class Docker::Exec
60
60
  read_timeout = options[:wait]
61
61
 
62
62
  # Create API Request Body
63
- body = MultiJson.dump(
63
+ body = JSON.generate(
64
64
  'Tty' => tty,
65
65
  'Detach' => detached
66
66
  )
data/lib/docker/image.rb CHANGED
@@ -119,7 +119,7 @@ class Docker::Image
119
119
 
120
120
  # Create a new Image.
121
121
  def create(opts = {}, creds = nil, conn = Docker.connection, &block)
122
- credentials = creds.nil? ? Docker.creds : MultiJson.dump(creds)
122
+ credentials = creds.nil? ? Docker.creds : JSON.generate(creds)
123
123
  headers = credentials && Docker::Util.build_auth_header(credentials) || {}
124
124
  body = +''
125
125
  conn.post(
@@ -5,7 +5,7 @@ class Docker::Network
5
5
  include Docker::Base
6
6
 
7
7
  def connect(container, opts = {}, body_opts = {})
8
- body = MultiJson.dump({ container: container }.merge(body_opts))
8
+ body = JSON.generate({ container: container }.merge(body_opts))
9
9
  Docker::Util.parse_json(
10
10
  connection.post(path_for('connect'), opts, body: body)
11
11
  )
@@ -13,7 +13,7 @@ class Docker::Network
13
13
  end
14
14
 
15
15
  def disconnect(container, opts = {})
16
- body = MultiJson.dump(container: container)
16
+ body = JSON.generate(container: container)
17
17
  Docker::Util.parse_json(
18
18
  connection.post(path_for('disconnect'), opts, body: body)
19
19
  )
@@ -43,7 +43,7 @@ class Docker::Network
43
43
 
44
44
  class << self
45
45
  def create(name, opts = {}, conn = Docker.connection)
46
- default_opts = MultiJson.dump({
46
+ default_opts = JSON.generate({
47
47
  'Name' => name,
48
48
  'CheckDuplicate' => true
49
49
  }.merge(opts))
data/lib/docker/util.rb CHANGED
@@ -106,8 +106,8 @@ module Docker::Util
106
106
  end
107
107
 
108
108
  def parse_json(body)
109
- MultiJson.load(body) unless body.nil? || body.empty? || (body == 'null')
110
- rescue MultiJson::ParseError => ex
109
+ JSON.parse(body) unless body.nil? || body.empty? || (body == 'null')
110
+ rescue JSON::ParserError => ex
111
111
  raise UnexpectedResponseError, ex.message
112
112
  end
113
113
 
@@ -173,14 +173,25 @@ module Docker::Util
173
173
  end
174
174
 
175
175
  def create_relative_dir_tar(directory, output)
176
+ root = File.realpath(directory)
177
+
176
178
  Gem::Package::TarWriter.new(output) do |tar|
177
179
  files = docker_context(directory)
178
180
 
179
181
  files.each do |prefixed_file_name|
180
- stat = File.stat(prefixed_file_name)
182
+ stat = File.lstat(prefixed_file_name)
183
+ unprefixed_file_name = prefixed_file_name[directory.length..-1]
184
+
185
+ if stat.symlink?
186
+ tar.add_symlink(
187
+ unprefixed_file_name, File.readlink(prefixed_file_name), stat.mode
188
+ )
189
+ next
190
+ end
181
191
  next unless stat.file?
182
192
 
183
- unprefixed_file_name = prefixed_file_name[directory.length..-1]
193
+ next unless contained?(prefixed_file_name, root)
194
+
184
195
  add_file_to_tar(
185
196
  tar, unprefixed_file_name, stat.mode, stat.size, stat.mtime
186
197
  ) do |tar_file|
@@ -190,6 +201,14 @@ module Docker::Util
190
201
  end
191
202
  end
192
203
 
204
+ # True when path resolves, with every symlink and '..' expanded, to
205
+ # something underneath root.
206
+ def contained?(path, root)
207
+ File.realpath(path).start_with?(root + File::SEPARATOR)
208
+ rescue SystemCallError
209
+ false
210
+ end
211
+
193
212
  def add_file_to_tar(tar, name, mode, size, mtime)
194
213
  tar.check_closed
195
214
 
@@ -261,7 +280,7 @@ module Docker::Util
261
280
  end
262
281
 
263
282
  def build_auth_header(credentials)
264
- credentials = MultiJson.dump(credentials) if credentials.is_a?(Hash)
283
+ credentials = JSON.generate(credentials) if credentials.is_a?(Hash)
265
284
  encoded_creds = Base64.urlsafe_encode64(credentials)
266
285
  {
267
286
  'X-Registry-Auth' => encoded_creds
@@ -270,10 +289,10 @@ module Docker::Util
270
289
 
271
290
  def build_config_header(credentials)
272
291
  if credentials.is_a?(String)
273
- credentials = MultiJson.load(credentials, symbolize_keys: true)
292
+ credentials = JSON.parse(credentials, symbolize_names: true)
274
293
  end
275
294
 
276
- header = MultiJson.dump(
295
+ header = JSON.generate(
277
296
  credentials[:serveraddress].to_s => {
278
297
  'username' => credentials[:username].to_s,
279
298
  'password' => credentials[:password].to_s,
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Docker
4
4
  # The version of the docker-api gem.
5
- VERSION = '2.4.0'
5
+ VERSION = '2.5.0'
6
6
  end
data/lib/docker/volume.rb CHANGED
@@ -33,7 +33,7 @@ class Docker::Volume
33
33
  # creates a volume with an arbitrary name
34
34
  def create(name, opts = {}, conn = Docker.connection)
35
35
  opts['Name'] = name
36
- resp = conn.post('/volumes/create', {}, body: MultiJson.dump(opts))
36
+ resp = conn.post('/volumes/create', {}, body: JSON.generate(opts))
37
37
  hash = Docker::Util.parse_json(resp) || {}
38
38
  new(conn, hash)
39
39
  end
data/lib/docker.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'cgi'
4
- require 'multi_json'
4
+ require 'json'
5
5
  require 'excon'
6
6
  require 'tempfile'
7
7
  require 'base64'
@@ -133,7 +133,7 @@ module Docker
133
133
 
134
134
  # Login to the Docker registry.
135
135
  def authenticate!(options = {}, connection = self.connection)
136
- creds = MultiJson.dump(options)
136
+ creds = JSON.generate(options)
137
137
  connection.post('/auth', {}, body: creds)
138
138
  @creds = creds
139
139
  true
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: docker-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.0
4
+ version: 2.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Swipely, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-30 00:00:00.000000000 Z
11
+ date: 2026-10-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: excon
@@ -25,7 +25,7 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.64.0
27
27
  - !ruby/object:Gem::Dependency
28
- name: multi_json
28
+ name: base64
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
@@ -164,7 +164,10 @@ files:
164
164
  homepage: https://github.com/upserve/docker-api
165
165
  licenses:
166
166
  - MIT
167
- metadata: {}
167
+ metadata:
168
+ bug_tracker_uri: https://github.com/upserve/docker-api/issues
169
+ source_code_uri: https://github.com/upserve/docker-api
170
+ rubygems_mfa_required: 'true'
168
171
  post_install_message:
169
172
  rdoc_options: []
170
173
  require_paths:
@@ -180,7 +183,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
180
183
  - !ruby/object:Gem::Version
181
184
  version: '0'
182
185
  requirements: []
183
- rubygems_version: 3.1.6
186
+ rubygems_version: 3.2.34
184
187
  signing_key:
185
188
  specification_version: 4
186
189
  summary: A simple REST client for the Docker Remote API