docker-api 2.0.0 → 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
2
  SHA256:
3
- metadata.gz: 7c8d229ece5b5b347a925c62c3f199a940393e58de0b02049aed295090739b04
4
- data.tar.gz: 619467ed3697f6b9221f89bab145ba298bd47f5f26209e86df89347b6e83fe08
3
+ metadata.gz: fc1628fd247f468eade6265726036edfe62cf330a3f0be35f24359af395dc4b4
4
+ data.tar.gz: 29a503b4ac556125534508256dbea71f6acc1a3612b24c1ae204cbd93a83ba04
5
5
  SHA512:
6
- metadata.gz: 7a1a3a2a995c4a172217fc80eb573501502f7f05a337080bf4723d90bb2ba9053cde786aaa994993bd2841ebeeb18cc8a5e4dfa342792bb558fb84a3f4688b89
7
- data.tar.gz: c9bbb7ae229ba1784859ddb0e3bc6e7df1651718d552779baac4e2ad49d34096aba760e6ce99fa41dfd14ef6ea0f3a5ddfc9178cf1c7c7f286a8385be1c59385
6
+ metadata.gz: 0e0478393e9624e3b206c4578a1a2449843da15bcbceb70520a708ce4a9e8a493a13dd5c6d0e8d3b2b71e1256a4770eb3cb1568471985fd3f52fb703e03ac20e
7
+ data.tar.gz: 14022d379e3e2dade67ffcd6ce12e56f9e12b65eb00e1906e579561262963fde36181b955526bfbd24c47baba3b4dfee15475c980e7f5bda4bff64ecf8e36859
data/README.md CHANGED
@@ -34,6 +34,8 @@ 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
41
  Follow the [installation instructions](https://docs.docker.com/install/), and then run:
@@ -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.
@@ -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
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
@@ -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
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,4 +1,4 @@
1
1
  module Docker
2
2
  # The version of the docker-api gem.
3
- VERSION = '2.0.0'
3
+ VERSION = '2.2.0'
4
4
  end
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.
@@ -132,5 +142,5 @@ module Docker
132
142
  module_function :default_socket_url, :env_url, :url, :url=, :env_options,
133
143
  :options, :options=, :creds, :creds=, :logger, :logger=,
134
144
  :connection, :reset!, :reset_connection!, :version, :info,
135
- :ping, :authenticate!, :ssl_options
145
+ :ping, :podman?, :rootless?, :authenticate!, :ssl_options
136
146
  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: 2.0.0
4
+ version: 2.2.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: 2020-09-11 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
@@ -194,7 +194,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
194
194
  - !ruby/object:Gem::Version
195
195
  version: '0'
196
196
  requirements: []
197
- rubygems_version: 3.1.2
197
+ rubygems_version: 3.0.6
198
198
  signing_key:
199
199
  specification_version: 4
200
200
  summary: A simple REST client for the Docker Remote API