Skip to content

Commit d8de40f

Browse files
Automatically retry with correct content-type
The podman socket will return a note about the correct content-type that should be used for a given type of message if it is incorrect. This is a workaround to automatically correct the content-type and resubmit the call. A full correction would be to update all of the different connection entries but this appears to work correctly in the short term. Updated the spec tests to use debian:stable instead of wheezy since wheezy is no longer widely supported Added podman-specific skips to spec tests for running on podman systems. Added GitHub Actions since Travis is flaky Explicitly use URI.open in image pulls to work around potential issues. Fixes #568
1 parent 1e9b9cc commit d8de40f

File tree

16 files changed

+193
-83
lines changed

16 files changed

+193
-83
lines changed

.cane

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
--abc-max 19
1+
--abc-max 23
22
--style-measure 100

.github/workflows/unit_test.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: Unit Tests
2+
on:
3+
push:
4+
branches:
5+
# A test branch for seeing if your tests will pass in your personal fork
6+
- test_me_github
7+
pull_request:
8+
branches:
9+
- main
10+
- master
11+
jobs:
12+
rspec:
13+
runs-on:
14+
- ubuntu-latest
15+
strategy:
16+
matrix:
17+
ruby:
18+
- 2.7
19+
- 2.6
20+
- 2.5
21+
- 2.4
22+
docker_version:
23+
- 5:19.03.8~3-0~ubuntu-bionic
24+
- 5:18.09.9~3-0~ubuntu-bionic
25+
- 18.06.3~ce~3-0~ubuntu
26+
fail-fast: true
27+
steps:
28+
- uses: actions/checkout@v2
29+
- uses: actions/setup-ruby@v1
30+
with:
31+
ruby-version: ${{ matrix.ruby }}
32+
- name: install bundler
33+
run: |
34+
gem install bundler -v '~> 1.17.3'
35+
bundle update
36+
- name: install docker
37+
env:
38+
DOCKER_VERSION: ${{ matrix.docker_version }}
39+
run: sudo ./script/install_docker.sh ${DOCKER_VERSION} ${DOCKER_CE}
40+
- name: spec tests
41+
run: bundle exec rake

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ Usage
3434

3535
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.
3636

37+
At this time, basic `podman` support has been added via the podman docker-compatible API socket.
38+
3739
## Starting up
3840

3941
Follow the [installation instructions](https://docs.docker.com/install/), and then run:

Rakefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ end
2020

2121
desc 'Download the necessary base images'
2222
task :unpack do
23-
%w( swipely/base registry busybox tianon/true debian:wheezy ).each do |image|
23+
%w( swipely/base registry busybox tianon/true debian:stable ).each do |image|
2424
system "docker pull #{image}"
2525
end
2626
end

lib/docker.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,15 @@ def ping(connection = self.connection)
119119
connection.get('/_ping')
120120
end
121121

122+
# Determine if the server is podman or docker.
123+
def podman?(connection = self.connection)
124+
not (
125+
Array(version(connection)['Components']).find do |component|
126+
component['Name'] && component['Name'].include?('Podman')
127+
end
128+
).nil?
129+
end
130+
122131
# Login to the Docker registry.
123132
def authenticate!(options = {}, connection = self.connection)
124133
creds = MultiJson.dump(options)
@@ -132,5 +141,5 @@ def authenticate!(options = {}, connection = self.connection)
132141
module_function :default_socket_url, :env_url, :url, :url=, :env_options,
133142
:options, :options=, :creds, :creds=, :logger, :logger=,
134143
:connection, :reset!, :reset_connection!, :version, :info,
135-
:ping, :authenticate!, :ssl_options
144+
:ping, :podman?, :authenticate!, :ssl_options
136145
end

lib/docker/connection.rb

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,51 @@ def resource
3535

3636
# Send a request to the server with the `
3737
def request(*args, &block)
38+
retries ||= 0
3839
request = compile_request_params(*args, &block)
3940
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
41+
begin
42+
resource.request(request).body
43+
rescue Excon::Errors::BadRequest => ex
44+
if retries < 2
45+
response_cause = ''
46+
begin
47+
response_cause = JSON.parse(ex.response.body)['cause']
48+
rescue JSON::ParserError
49+
#noop
50+
end
51+
52+
if response_cause.is_a?(String)
53+
# The error message will tell the application type given and then the
54+
# application type that the message should be
55+
#
56+
# This is not perfect since it relies on processing a message that
57+
# could change in the future. However, it should be a good stop-gap
58+
# until all methods are updated to pass in the appropriate content
59+
# type.
60+
#
61+
# A current example message is:
62+
# * 'Content-Type: application/json is not supported. Should be "application/x-tar"'
63+
matches = response_cause.delete('"\'').scan(%r{(application/\S+)})
64+
unless matches.count < 2
65+
request[:headers]['Content-Type'] = matches.last.first
66+
retries += 1
67+
retry
68+
end
69+
end
70+
end
71+
raise ClientError, ex.response.body
72+
rescue Excon::Errors::Unauthorized => ex
73+
raise UnauthorizedError, ex.response.body
74+
rescue Excon::Errors::NotFound => ex
75+
raise NotFoundError, ex.response.body
76+
rescue Excon::Errors::Conflict => ex
77+
raise ConflictError, ex.response.body
78+
rescue Excon::Errors::InternalServerError => ex
79+
raise ServerError, ex.response.body
80+
rescue Excon::Errors::Timeout => ex
81+
raise TimeoutError, ex.message
82+
end
5383
end
5484

5585
def log_request(request)

lib/docker/image.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,16 @@ def search(query = {}, connection = Docker.connection, creds = nil)
227227
# Import an Image from the output of Docker::Container#export. The first
228228
# argument may either be a File or URI.
229229
def import(imp, opts = {}, conn = Docker.connection)
230-
open(imp) do |io|
230+
require 'open-uri'
231+
232+
# This differs after Ruby 2.4
233+
if URI.public_methods.include?(:open)
234+
munged_open = URI.method(:open)
235+
else
236+
munged_open = self.method(:open)
237+
end
238+
239+
munged_open.call(imp) do |io|
231240
import_stream(opts, conn) do
232241
io.read(Excon.defaults[:chunk_size]).to_s
233242
end

spec/docker/connection_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@
2020

2121
context 'when the first argument is a String' do
2222
context 'and the url is a unix socket' do
23-
let(:url) { 'unix:///var/run/docker.sock' }
23+
let(:url) { ::Docker.env_url || ::Docker.default_socket_url }
2424

2525
it 'sets the socket path in the options' do
2626
expect(subject.url).to eq('unix:///')
27-
expect(subject.options).to include(:socket => '/var/run/docker.sock')
27+
expect(subject.options).to include(:socket => url.split('//').last)
2828
end
2929
end
3030

0 commit comments

Comments
 (0)