From f52be4dc7c034949d5cb449e5156cad43ebd1dc9 Mon Sep 17 00:00:00 2001 From: Daniel Schmidt Date: Thu, 24 Apr 2025 15:32:43 -0700 Subject: [PATCH 1/3] Buildfix: pin rspec-its; simplify podman install (resolves #597) - Pins rspec-its to 1.x - Removes unnecessary setup steps for Ubuntu 20.10+ from ./scripts/install_podman.sh --- docker-api.gemspec | 2 +- script/install_podman.sh | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/docker-api.gemspec b/docker-api.gemspec index 1a131c76..3d652824 100644 --- a/docker-api.gemspec +++ b/docker-api.gemspec @@ -16,7 +16,7 @@ Gem::Specification.new do |gem| gem.add_dependency 'multi_json' gem.add_development_dependency 'rake' gem.add_development_dependency 'rspec', '~> 3.0' - gem.add_development_dependency 'rspec-its' + gem.add_development_dependency 'rspec-its', '~> 1' gem.add_development_dependency 'pry' gem.add_development_dependency 'single_cov' gem.add_development_dependency 'webmock' diff --git a/script/install_podman.sh b/script/install_podman.sh index f5d48789..1e6d8543 100755 --- a/script/install_podman.sh +++ b/script/install_podman.sh @@ -1,12 +1,5 @@ #!/bin/sh set -ex -. /etc/os-release - -curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/Release.key | sudo apt-key add - - -echo "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/ /" > /etc/apt/sources.list.d/podman.list - apt-get update - apt-get install -y podman From d5436e3cc9aafea98d32ad3bd8826537125caa8d Mon Sep 17 00:00:00 2001 From: Daniel Schmidt Date: Thu, 24 Apr 2025 15:13:08 -0700 Subject: [PATCH 2/3] Send :read_timeout when streaming events (#584) - Corrects a bug in `Docker::Connection.compile_request_params` that incorrectly stripped out `read_timeout` when it was nil. - Sets `Docker::Event.stream` to skip timeouts by default. - Sets `Docker::Event.stream` to avoid automatically retrying timeout-related events by default. - Adds timeout-related event streaming tests. A particularly slow (60s+) test can be enabled by setting `RUN_SLOW_TESTS=1` in the testing environment. --- lib/docker/connection.rb | 32 ++++++++++++++++---------------- lib/docker/event.rb | 18 ++++++++++++++---- spec/docker/event_spec.rb | 38 ++++++++++++++++++++++++++++++++++++++ spec/spec_helper.rb | 4 ++++ 4 files changed, 72 insertions(+), 20 deletions(-) diff --git a/lib/docker/connection.rb b/lib/docker/connection.rb index d678d243..7894f3ca 100644 --- a/lib/docker/connection.rb +++ b/lib/docker/connection.rb @@ -137,24 +137,24 @@ def version end private - # Given an HTTP method, path, optional query, extra options, and block, - # compiles a request. + def compile_request_params(http_method, path, query = nil, opts = nil, &block) - query ||= {} opts ||= {} - headers = opts.delete(:headers) || {} - content_type = opts[:body].nil? ? 'text/plain' : 'application/json' - user_agent = "Swipely/Docker-API #{Docker::VERSION}" + query ||= opts.delete(:query) || {} + + default_headers = { + 'Content-Type' => opts[:body].nil? ? 'text/plain' : 'application/json', + 'User-Agent' => "Swipely/Docker-API #{Docker::VERSION}", + } + headers = default_headers.merge(opts.delete(:headers) || {}) + { - :method => http_method, - :path => path, - :query => query, - :headers => { 'Content-Type' => content_type, - 'User-Agent' => user_agent, - }.merge(headers), - :expects => (200..204).to_a << 301 << 304, - :idempotent => http_method == :get, - :request_block => block, - }.merge(opts).reject { |_, v| v.nil? } + method: http_method, + path: path, + headers:, + query:, + expects: (200..204).to_a << 301 << 304, + idempotent: http_method == :get, + }.merge(opts).tap { |params| params[:request_block] = block if block } end end diff --git a/lib/docker/event.rb b/lib/docker/event.rb index 4945bca4..2d2cb41f 100644 --- a/lib/docker/event.rb +++ b/lib/docker/event.rb @@ -30,11 +30,21 @@ class << self include Docker::Error def stream(opts = {}, conn = Docker.connection, &block) - conn.get('/events', opts, :response_block => lambda { |b, r, t| - b.each_line do |line| - block.call(new_event(line, r, t)) + # Disable timeouts by default + opts[:read_timeout] = nil unless opts.key? :read_timeout + + # By default, avoid retrying timeout errors. Set opts[:retry_errors] to override this. + opts[:retry_errors] ||= Excon::DEFAULT_RETRY_ERRORS.reject do |cls| + cls == Excon::Error::Timeout + end + + opts[:response_block] = lambda do |chunk, remaining, total| + chunk.each_line do |event_json| + block.call(new_event(event_json, remaining, total)) end - }) + end + + conn.get('/events', opts.delete(:query), opts) end def since(since, opts = {}, conn = Docker.connection, &block) diff --git a/spec/docker/event_spec.rb b/spec/docker/event_spec.rb index 0ba6effe..b8c130a6 100644 --- a/spec/docker/event_spec.rb +++ b/spec/docker/event_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'spec_helper' +require 'securerandom' SingleCov.covered! uncovered: 5 @@ -87,6 +88,43 @@ container.remove end + + context 'with timeouts' do + # @see https://github.com/upserve/docker-api/issues/584 + + it 'does not (seem to) time out by default' do + # @note Excon passes timeout-related arguments directly to IO.select, which in turn makes a system call, + # so it's not possible to mock this (e.g. w/Timecop). + skip_slow_test + expect { Timeout.timeout(65) { stream_events_with_timeout } } + .to raise_error Timeout::Error + end + + it 'times out immediately if read_timeout < Timeout.timeout' do + expect { Timeout.timeout(1) { stream_events_with_timeout(0) } } + .to raise_error Docker::Error::TimeoutError + end + + it 'times out after timeout(1) if read_timeout=2' do + expect { Timeout.timeout(1) { stream_events_with_timeout(2) } } + .to raise_error Timeout::Error + end + + private + + def stream_events_with_timeout(read_timeout = []) + opts = { + # Filter to avoid unexpected Docker events interfering with timeout behavior + query: { filters: { container: [SecureRandom.uuid] }.to_json }, + # Use [] to differentiate between explicit nil and not providing an arg (falling back to the default) + read_timeout:, + }.reject { |_, v| v.empty? rescue false } + + Docker::Event.stream(opts) do |event| + raise "Unexpected event interfered with timeout test: #{event}" + end + end + end end describe ".since" do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8be80cab..117b91b9 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -24,6 +24,10 @@ def project_dir end module SpecHelpers + def skip_slow_test + skip "Disabled because ENV['RUN_SLOW_TESTS'] not set" unless ENV['RUN_SLOW_TESTS'] + end + def skip_without_auth skip "Disabled because of missing auth" if ENV['DOCKER_API_USER'] == 'debbie_docker' end From 87daf35f37d7afc7d47f192b334819e60d5fb3d3 Mon Sep 17 00:00:00 2001 From: Daniel Schmidt Date: Fri, 25 Apr 2025 10:16:56 -0700 Subject: [PATCH 3/3] Bugfix: Support Ruby < 3.1 hash syntax --- lib/docker/connection.rb | 4 ++-- spec/docker/event_spec.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/docker/connection.rb b/lib/docker/connection.rb index 7894f3ca..c41f26e4 100644 --- a/lib/docker/connection.rb +++ b/lib/docker/connection.rb @@ -151,8 +151,8 @@ def compile_request_params(http_method, path, query = nil, opts = nil, &block) { method: http_method, path: path, - headers:, - query:, + headers: headers, + query: query, expects: (200..204).to_a << 301 << 304, idempotent: http_method == :get, }.merge(opts).tap { |params| params[:request_block] = block if block } diff --git a/spec/docker/event_spec.rb b/spec/docker/event_spec.rb index b8c130a6..b289aa71 100644 --- a/spec/docker/event_spec.rb +++ b/spec/docker/event_spec.rb @@ -117,7 +117,7 @@ def stream_events_with_timeout(read_timeout = []) # Filter to avoid unexpected Docker events interfering with timeout behavior query: { filters: { container: [SecureRandom.uuid] }.to_json }, # Use [] to differentiate between explicit nil and not providing an arg (falling back to the default) - read_timeout:, + read_timeout: read_timeout, }.reject { |_, v| v.empty? rescue false } Docker::Event.stream(opts) do |event|