docker-api 1.34.1 → 2.2.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: f4316523d43ba5a3e24f42f5eae2efab140f472f
4
- data.tar.gz: 74b8a64ced780ee23a735ae916c6b141ed9cf07b
2
+ SHA256:
3
+ metadata.gz: fc1628fd247f468eade6265726036edfe62cf330a3f0be35f24359af395dc4b4
4
+ data.tar.gz: 29a503b4ac556125534508256dbea71f6acc1a3612b24c1ae204cbd93a83ba04
5
5
  SHA512:
6
- metadata.gz: 24713b6480cb4e1a7c948b86a898181e83f6be5719959925d873f09b4b4c4672b47aa0e1eb8f987ba962be8c23c56e55ebfa8943cd716e65be64704fe5233df9
7
- data.tar.gz: 9051f10166d00078703559fb92624ceb1cba960b2b41c6939e791616057dc6105e10f7e3656e13fad2ad37ba166d5d3b0d79c652187fbc80e5ec3710c6f8fef1
6
+ metadata.gz: 0e0478393e9624e3b206c4578a1a2449843da15bcbceb70520a708ce4a9e8a493a13dd5c6d0e8d3b2b71e1256a4770eb3cb1568471985fd3f52fb703e03ac20e
7
+ data.tar.gz: 14022d379e3e2dade67ffcd6ce12e56f9e12b65eb00e1906e579561262963fde36181b955526bfbd24c47baba3b4dfee15475c980e7f5bda4bff64ecf8e36859
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) [![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) [![Dependency Status](https://gemnasium.com/swipely/docker-api.svg)](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
 
@@ -34,9 +34,11 @@ Usage
34
34
 
35
35
  docker-api is designed to be very lightweight. Almost no state is cached (aside from id's which are immutable) to ensure that each method call's information is up to date. As such, just about every external method represents an API call.
36
36
 
37
+ At this time, basic `podman` support has been added via the podman docker-compatible API socket.
38
+
37
39
  ## Starting up
38
40
 
39
- Follow the [installation instructions](https://docs.docker.com/installation/#installation), and then run:
41
+ Follow the [installation instructions](https://docs.docker.com/install/), and then run:
40
42
 
41
43
  ```shell
42
44
  $ sudo docker -d
@@ -52,7 +54,7 @@ If you're running Docker locally as a socket, there is no setup to do in Ruby. I
52
54
  Docker.url = 'tcp://example.com:5422'
53
55
  ```
54
56
 
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.
57
+ 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
58
 
57
59
  Also, you may set the above variables via `ENV` variables. For example:
58
60
 
@@ -76,8 +78,6 @@ irb(main):004:0> Docker.options
76
78
  => {}
77
79
  ```
78
80
 
79
- Before doing anything else, ensure you have the correct version of the Docker API. To do this, run `Docker.validate_version!`. If your installed version is not supported, a `Docker::Error::VersionError` is raised.
80
-
81
81
  ### SSL
82
82
 
83
83
  When running docker using SSL, setting the DOCKER_CERT_PATH will configure docker-api to use SSL.
data/lib/docker.rb CHANGED
@@ -106,17 +106,27 @@ module Docker
106
106
 
107
107
  # Get the version of Go, Docker, and optionally the Git commit.
108
108
  def version(connection = self.connection)
109
- Util.parse_json(connection.get('/version'))
109
+ connection.version
110
110
  end
111
111
 
112
112
  # Get more information about the Docker server.
113
113
  def info(connection = self.connection)
114
- Util.parse_json(connection.get('/info'))
114
+ connection.info
115
115
  end
116
116
 
117
117
  # Ping the Docker server.
118
118
  def ping(connection = self.connection)
119
- connection.get('/_ping')
119
+ connection.ping
120
+ end
121
+
122
+ # Determine if the server is podman or docker.
123
+ def podman?(connection = self.connection)
124
+ connection.podman?
125
+ end
126
+
127
+ # Determine if the session is rootless.
128
+ def rootless?(connection = self.connection)
129
+ connection.rootless?
120
130
  end
121
131
 
122
132
  # Login to the Docker registry.
@@ -129,19 +139,8 @@ module Docker
129
139
  raise Docker::Error::AuthenticationError
130
140
  end
131
141
 
132
- # When the correct version of Docker is installed, returns true. Otherwise,
133
- # raises a VersionError.
134
- def validate_version!
135
- Docker.info
136
- true
137
- rescue Docker::Error::TimeoutError
138
- raise
139
- rescue Docker::Error::DockerError
140
- raise Docker::Error::VersionError, "Expected API Version: #{API_VERSION}"
141
- end
142
-
143
142
  module_function :default_socket_url, :env_url, :url, :url=, :env_options,
144
143
  :options, :options=, :creds, :creds=, :logger, :logger=,
145
144
  :connection, :reset!, :reset_connection!, :version, :info,
146
- :ping, :authenticate!, :validate_version!, :ssl_options
145
+ :ping, :podman?, :rootless?, :authenticate!, :ssl_options
147
146
  end
@@ -1,6 +1,9 @@
1
1
  # This class represents a Connection to a Docker server. The Connection is
2
2
  # immutable in that once the url and options is set they cannot be changed.
3
3
  class Docker::Connection
4
+ require 'docker/util'
5
+ require 'docker/error'
6
+
4
7
  include Docker::Error
5
8
 
6
9
  attr_reader :url, :options
@@ -35,21 +38,58 @@ class Docker::Connection
35
38
 
36
39
  # Send a request to the server with the `
37
40
  def request(*args, &block)
41
+ retries ||= 0
38
42
  request = compile_request_params(*args, &block)
39
43
  log_request(request)
40
- resource.request(request).body
41
- rescue Excon::Errors::BadRequest => ex
42
- raise ClientError, ex.response.body
43
- rescue Excon::Errors::Unauthorized => ex
44
- raise UnauthorizedError, ex.response.body
45
- rescue Excon::Errors::NotFound => ex
46
- raise NotFoundError, ex.response.body
47
- rescue Excon::Errors::Conflict => ex
48
- raise ConflictError, ex.response.body
49
- rescue Excon::Errors::InternalServerError => ex
50
- raise ServerError, ex.response.body
51
- rescue Excon::Errors::Timeout => ex
52
- raise TimeoutError, ex.message
44
+ begin
45
+ resource.request(request).body
46
+ rescue Excon::Errors::BadRequest => ex
47
+ if retries < 2
48
+ response_cause = ''
49
+ begin
50
+ response_cause = JSON.parse(ex.response.body)['cause']
51
+ rescue JSON::ParserError
52
+ #noop
53
+ end
54
+
55
+ if response_cause.is_a?(String)
56
+ # The error message will tell the application type given and then the
57
+ # application type that the message should be
58
+ #
59
+ # This is not perfect since it relies on processing a message that
60
+ # could change in the future. However, it should be a good stop-gap
61
+ # until all methods are updated to pass in the appropriate content
62
+ # type.
63
+ #
64
+ # A current example message is:
65
+ # * 'Content-Type: application/json is not supported. Should be "application/x-tar"'
66
+ matches = response_cause.delete('"\'').scan(%r{(application/\S+)})
67
+ unless matches.count < 2
68
+ Docker.logger.warn(
69
+ <<~RETRY_WARNING
70
+ Automatically retrying with content type '#{response_cause}'
71
+ Original Error: #{ex}
72
+ RETRY_WARNING
73
+ ) if Docker.logger
74
+
75
+ request[:headers]['Content-Type'] = matches.last.first
76
+ retries += 1
77
+ retry
78
+ end
79
+ end
80
+ end
81
+ raise ClientError, ex.response.body
82
+ rescue Excon::Errors::Unauthorized => ex
83
+ raise UnauthorizedError, ex.response.body
84
+ rescue Excon::Errors::NotFound => ex
85
+ raise NotFoundError, ex.response.body
86
+ rescue Excon::Errors::Conflict => ex
87
+ raise ConflictError, ex.response.body
88
+ rescue Excon::Errors::InternalServerError => ex
89
+ raise ServerError, ex.response.body
90
+ rescue Excon::Errors::Timeout => ex
91
+ raise TimeoutError, ex.message
92
+ end
53
93
  end
54
94
 
55
95
  def log_request(request)
@@ -60,13 +100,38 @@ class Docker::Connection
60
100
  end
61
101
  end
62
102
 
103
+ def to_s
104
+ "Docker::Connection { :url => #{url}, :options => #{options} }"
105
+ end
106
+
63
107
  # Delegate all HTTP methods to the #request.
64
108
  [:get, :put, :post, :delete].each do |method|
65
109
  define_method(method) { |*args, &block| request(method, *args, &block) }
66
110
  end
67
111
 
68
- def to_s
69
- "Docker::Connection { :url => #{url}, :options => #{options} }"
112
+ # Common attribute requests
113
+ def info
114
+ Docker::Util.parse_json(get('/info'))
115
+ end
116
+
117
+ def ping
118
+ get('/_ping')
119
+ end
120
+
121
+ def podman?
122
+ @podman ||= !(
123
+ Array(version['Components']).find do |component|
124
+ component['Name'].include?('Podman')
125
+ end
126
+ ).nil?
127
+ end
128
+
129
+ def rootless?
130
+ @rootless ||= (info['Rootless'] == true)
131
+ end
132
+
133
+ def version
134
+ @version ||= Docker::Util.parse_json(get('/version'))
70
135
  end
71
136
 
72
137
  private
@@ -80,7 +145,7 @@ private
80
145
  user_agent = "Swipely/Docker-API #{Docker::VERSION}"
81
146
  {
82
147
  :method => http_method,
83
- :path => "/v#{Docker::API_VERSION}#{path}",
148
+ :path => path,
84
149
  :query => query,
85
150
  :headers => { 'Content-Type' => content_type,
86
151
  'User-Agent' => user_agent,
@@ -189,10 +189,10 @@ class Docker::Container
189
189
  end
190
190
 
191
191
  def streaming_logs(opts = {}, &block)
192
- stack_size = opts.delete('stack_size') || -1
192
+ stack_size = opts.delete('stack_size') || opts.delete(:stack_size) || -1
193
193
  tty = opts.delete('tty') || opts.delete(:tty) || false
194
194
  msgs = Docker::MessagesStack.new(stack_size)
195
- 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}
196
196
 
197
197
  connection.get(path_for(:logs), opts, excon_params)
198
198
  msgs.messages.join
@@ -266,16 +266,6 @@ class Docker::Container
266
266
  end
267
267
  end
268
268
 
269
- def copy(path, &block)
270
- connection.post(
271
- path_for(:copy),
272
- {},
273
- body: MultiJson.dump('Resource' => path),
274
- response_block: block
275
- )
276
- self
277
- end
278
-
279
269
  def archive_out(path, &block)
280
270
  connection.get(
281
271
  path_for(:archive),
@@ -343,7 +333,7 @@ class Docker::Container
343
333
 
344
334
  # Return the container with specified ID
345
335
  def self.get(id, opts = {}, conn = Docker.connection)
346
- container_json = conn.get("/containers/#{URI.encode(id)}/json", opts)
336
+ container_json = conn.get("/containers/#{id}/json", opts)
347
337
  hash = Docker::Util.parse_json(container_json) || {}
348
338
  new(conn, hash)
349
339
  end
data/lib/docker/event.rb CHANGED
@@ -29,7 +29,9 @@ class Docker::Event
29
29
 
30
30
  def stream(opts = {}, conn = Docker.connection, &block)
31
31
  conn.get('/events', opts, :response_block => lambda { |b, r, t|
32
- block.call(new_event(b, r, t))
32
+ b.each_line do |line|
33
+ block.call(new_event(line, r, t))
34
+ end
33
35
  })
34
36
  end
35
37
 
data/lib/docker/exec.rb CHANGED
@@ -19,6 +19,13 @@ class Docker::Exec
19
19
  # @return [Docker::Exec] self
20
20
  def self.create(options = {}, conn = Docker.connection)
21
21
  container = options.delete('Container')
22
+
23
+ # Podman does not attach these by default but does require them to be attached
24
+ if ::Docker.podman?(conn)
25
+ options['AttachStderr'] = true if options['AttachStderr'].nil?
26
+ options['AttachStdout'] = true if options['AttachStdout'].nil?
27
+ end
28
+
22
29
  resp = conn.post("/containers/#{container}/exec", {},
23
30
  body: MultiJson.dump(options))
24
31
  hash = Docker::Util.parse_json(resp) || {}
data/lib/docker/image.rb CHANGED
@@ -65,7 +65,16 @@ class Docker::Image
65
65
 
66
66
  # Remove the Image from the server.
67
67
  def remove(opts = {})
68
- name = opts.delete(:name) || self.id
68
+ name = opts.delete(:name)
69
+
70
+ unless name
71
+ if ::Docker.podman?
72
+ name = self.id.split(':').last
73
+ else
74
+ name = self.id
75
+ end
76
+ end
77
+
69
78
  connection.delete("/images/#{name}", opts)
70
79
  end
71
80
  alias_method :delete, :remove
@@ -126,7 +135,7 @@ class Docker::Image
126
135
 
127
136
  # Return a specific image.
128
137
  def get(id, opts = {}, conn = Docker.connection)
129
- image_json = conn.get("/images/#{URI.encode(id)}/json", opts)
138
+ image_json = conn.get("/images/#{id}/json", opts)
130
139
  hash = Docker::Util.parse_json(image_json) || {}
131
140
  new(conn, hash)
132
141
  end
@@ -174,7 +183,7 @@ class Docker::Image
174
183
  # By using compare_by_identity we can create a Hash that has
175
184
  # the same key multiple times.
176
185
  query = {}.tap(&:compare_by_identity)
177
- Array(names).each { |name| query['names'.dup] = URI.encode(name) }
186
+ Array(names).each { |name| query['names'.dup] = name }
178
187
  conn.get(
179
188
  '/images/get',
180
189
  query,
@@ -227,7 +236,16 @@ class Docker::Image
227
236
  # Import an Image from the output of Docker::Container#export. The first
228
237
  # argument may either be a File or URI.
229
238
  def import(imp, opts = {}, conn = Docker.connection)
230
- open(imp) do |io|
239
+ require 'open-uri'
240
+
241
+ # This differs after Ruby 2.4
242
+ if URI.public_methods.include?(:open)
243
+ munged_open = URI.method(:open)
244
+ else
245
+ munged_open = self.method(:open)
246
+ end
247
+
248
+ munged_open.call(imp) do |io|
231
249
  import_stream(opts, conn) do
232
250
  io.read(Excon.defaults[:chunk_size]).to_s
233
251
  end
@@ -34,7 +34,7 @@ class Docker::Network
34
34
  end
35
35
 
36
36
  def reload
37
- network_json = @connection.get("/networks/#{URI.encode(@id)}")
37
+ network_json = @connection.get("/networks/#{@id}")
38
38
  hash = Docker::Util.parse_json(network_json) || {}
39
39
  @info = hash
40
40
  end
@@ -51,7 +51,7 @@ class Docker::Network
51
51
  end
52
52
 
53
53
  def get(id, opts = {}, conn = Docker.connection)
54
- network_json = conn.get("/networks/#{URI.encode(id)}", opts)
54
+ network_json = conn.get("/networks/#{id}", opts)
55
55
  hash = Docker::Util.parse_json(network_json) || {}
56
56
  new(conn, hash)
57
57
  end
@@ -62,7 +62,7 @@ class Docker::Network
62
62
  end
63
63
 
64
64
  def remove(id, opts = {}, conn = Docker.connection)
65
- conn.delete("/networks/#{URI.encode(id)}", opts)
65
+ conn.delete("/networks/#{id}", opts)
66
66
  nil
67
67
  end
68
68
  alias_method :delete, :remove
data/lib/docker/util.rb CHANGED
@@ -1,8 +1,10 @@
1
+ require 'set'
2
+
1
3
  # This module holds shared logic that doesn't really belong anywhere else in the
2
4
  # gem.
3
5
  module Docker::Util
4
6
  # http://www.tldp.org/LDP/GNU-Linux-Tools-Summary/html/x11655.htm#STANDARD-WILDCARDS
5
- GLOB_WILDCARDS = /[\?\*\[\{]/
7
+ GLOB_WILDCARDS = /[\?\*\[\{\]\}]/
6
8
 
7
9
  include Docker::Error
8
10
 
@@ -142,10 +144,35 @@ module Docker::Util
142
144
  File.new(tempfile.path, 'r')
143
145
  end
144
146
 
147
+
148
+ # return the set of files that form the docker context
149
+ # implement this logic https://docs.docker.com/engine/reference/builder/#dockerignore-file
150
+ def docker_context(directory)
151
+ all_files = glob_all_files(File.join(directory, "**/*"))
152
+ dockerignore = File.join(directory, '.dockerignore')
153
+ return all_files unless all_files.include?(dockerignore)
154
+
155
+ # Iterate over valid lines, starting with the initial glob as working set
156
+ File
157
+ .read(dockerignore) # https://docs.docker.com/engine/reference/builder/#dockerignore-file
158
+ .each_line # "a newline-separated list of patterns"
159
+ .map(&:strip) # "A preprocessing step removes leading and trailing whitespace"
160
+ .reject(&:empty?) # "Lines that are blank after preprocessing are ignored"
161
+ .reject { |p| p.start_with?('#') } # "if [a line starts with `#`], then this line is considered as a comment"
162
+ .each_with_object(Set.new(all_files)) do |p, working_set|
163
+ # determine the pattern (p) and whether it is to be added or removed from context
164
+ add = p.start_with?("!")
165
+ # strip leading "!" from pattern p, then prepend the base directory
166
+ matches = dockerignore_compatible_glob(File.join(directory, add ? p[1..-1] : p))
167
+ # add or remove the matched items as indicated in the ignore file
168
+ add ? working_set.merge(matches) : working_set.replace(working_set.difference(matches))
169
+ end
170
+ .to_a
171
+ end
172
+
145
173
  def create_relative_dir_tar(directory, output)
146
174
  Gem::Package::TarWriter.new(output) do |tar|
147
- files = glob_all_files(File.join(directory, "**/*"))
148
- remove_ignored_files!(directory, files)
175
+ files = docker_context(directory)
149
176
 
150
177
  files.each do |prefixed_file_name|
151
178
  stat = File.stat(prefixed_file_name)
@@ -259,21 +286,23 @@ module Docker::Util
259
286
  }
260
287
  end
261
288
 
262
- def glob_all_files(pattern)
263
- Dir.glob(pattern, File::FNM_DOTMATCH) - ['..', '.']
289
+ # do a directory glob that matches .dockerignore behavior
290
+ # specifically: matched directories are considered a recursive match
291
+ def dockerignore_compatible_glob(pattern)
292
+ begin
293
+ some_dirs, some_files = glob_all_files(pattern).partition { |f| File.directory?(f) }
294
+ # since all directories will be re-processed with a /**/* glob, we can preemptively
295
+ # eliminate any whose parent directory is already in this set. This saves significant time.
296
+ some_files + some_dirs.reject { |d| some_dirs.any? { |pd| d.start_with?(pd) && d != pd } }
297
+ end.each_with_object(Set.new) do |f, acc|
298
+ # expand any directories by globbing; flatten results
299
+ acc.merge(File.directory?(f) ? glob_all_files("#{f}/**/*") : [f])
300
+ end
264
301
  end
265
302
 
266
- def remove_ignored_files!(directory, files)
267
- ignore = File.join(directory, '.dockerignore')
268
- return unless files.include?(ignore)
269
- ignored_files(directory, ignore).each { |f| files.delete(f) }
303
+ def glob_all_files(pattern)
304
+ # globs of "a_dir/**/*" can return "a_dir/.", so explicitly reject those
305
+ (Dir.glob(pattern, File::FNM_DOTMATCH) - ['..', '.']).reject { |p| p.end_with?("/.") }
270
306
  end
271
307
 
272
- def ignored_files(directory, ignore_file)
273
- patterns = File.read(ignore_file).split("\n").each(&:strip!)
274
- patterns.reject! { |p| p.empty? || p.start_with?('#') }
275
- patterns.map! { |p| File.join(directory, p) }
276
- patterns.map! { |p| File.directory?(p) ? "#{p}/**/*" : p }
277
- patterns.flat_map { |p| p =~ GLOB_WILDCARDS ? glob_all_files(p) : p }
278
- end
279
308
  end
@@ -1,7 +1,4 @@
1
1
  module Docker
2
2
  # The version of the docker-api gem.
3
- VERSION = '1.34.1'
4
-
5
- # The version of the compatible Docker remote API.
6
- API_VERSION = '1.16'
3
+ VERSION = '2.2.0'
7
4
  end
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: 1.34.1
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Swipely, Inc.
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-03-02 00:00:00.000000000 Z
11
+ date: 2021-07-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: excon
@@ -179,7 +179,7 @@ homepage: https://github.com/swipely/docker-api
179
179
  licenses:
180
180
  - MIT
181
181
  metadata: {}
182
- post_install_message:
182
+ post_install_message:
183
183
  rdoc_options: []
184
184
  require_paths:
185
185
  - lib
@@ -194,9 +194,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
194
194
  - !ruby/object:Gem::Version
195
195
  version: '0'
196
196
  requirements: []
197
- rubyforge_project:
198
- rubygems_version: 2.5.1
199
- signing_key:
197
+ rubygems_version: 3.0.6
198
+ signing_key:
200
199
  specification_version: 4
201
200
  summary: A simple REST client for the Docker Remote API
202
201
  test_files: []