Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docker-api.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
32 changes: 16 additions & 16 deletions lib/docker/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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: headers,
query: query,
expects: (200..204).to_a << 301 << 304,
idempotent: http_method == :get,
}.merge(opts).tap { |params| params[:request_block] = block if block }
end
end
124 changes: 71 additions & 53 deletions lib/docker/event.rb
Original file line number Diff line number Diff line change
@@ -1,40 +1,49 @@
# frozen_string_literal: true

# This class represents a Docker Event.
# @see https://github.com/moby/moby/blob/master/api/types/events/events.go
# @see https://docs.docker.com/reference/api/engine/version/v1.49/#tag/System/operation/SystemEvents
class Docker::Event
include Docker::Error

# Represents the actor object nested within an event
class Actor
attr_accessor :ID, :Attributes
attr_reader :info

def initialize(actor_attributes = {})
[:ID, :Attributes].each do |sym|
value = actor_attributes[sym]
if value.nil?
value = actor_attributes[sym.to_s]
end
send("#{sym}=", value)
end
def initialize(actor_data = {})
@info = actor_data.transform_keys { |k| k.downcase.to_sym }
end

if self.Attributes.nil?
self.Attributes = {}
end
def id
info[:id]
end
alias_method :ID, :id

alias_method :id, :ID
alias_method :attributes, :Attributes
def attributes
info[:attributes] || {}
end
alias_method :Attributes, :attributes
end

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)
Expand All @@ -43,52 +52,61 @@ def since(since, opts = {}, conn = Docker.connection, &block)

def new_event(body, remaining, total)
return if body.nil? || body.empty?
json = Docker::Util.parse_json(body)
Docker::Event.new(json)
info = Docker::Util.parse_json(body)
Docker::Event.new(info)
end
end

attr_accessor :Type, :Action, :time, :timeNano
attr_reader :Actor
# Deprecated interface
attr_accessor :status, :from
attr_reader :info

def initialize(event_attributes = {})
[:Type, :Action, :Actor, :time, :timeNano, :status, :from].each do |sym|
value = event_attributes[sym]
if value.nil?
value = event_attributes[sym.to_s]
end
send("#{sym}=", value)
end
def initialize(event_data = {})
@info = event_data.transform_keys { |k| k.downcase.to_sym }
end

if @Actor.nil?
value = event_attributes[:id]
if value.nil?
value = event_attributes['id']
end
self.Actor = Actor.new(ID: value)
end
def action
info[:action]
end
alias_method :Action, :action

def ID
self.actor.ID
def actor
@actor = Actor.new(info[:actor] || {}) if !defined? @actor
@actor
end
alias_method :Actor, :actor

def Actor=(actor)
return if actor.nil?
if actor.is_a? Actor
@Actor = actor
else
@Actor = Actor.new(actor)
end
def from
# @deprecated Use `actor.attributes['image']` instead
# Only applicable to container events. See Docker docs for details.
info[:from] || actor.attributes['image']
end

def id
# @deprecated Use `actor.id` instead
info[:id] || actor.id
end

def scope
info[:scope]
end

alias_method :type, :Type
alias_method :action, :Action
alias_method :actor, :Actor
alias_method :time_nano, :timeNano
alias_method :id, :ID
def status
# @deprecated Use `action` instead
info[:status] || action
end

def time
info[:time]
end

def time_nano
info[:timenano]
end
alias_method :timeNano, :time_nano

def type
info[:type]
end
alias_method :Type, :type

def to_s
if type.nil? && action.nil?
Expand Down
7 changes: 0 additions & 7 deletions script/install_podman.sh
Original file line number Diff line number Diff line change
@@ -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
40 changes: 40 additions & 0 deletions spec/docker/event_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require 'spec_helper'
require 'securerandom'

SingleCov.covered! uncovered: 5

Expand All @@ -18,6 +19,7 @@
'Type' => 'container',
'from' => 'tianon/true',
'id' => 'bb2c783a32330b726f18d1eb44d80c899ef45771b4f939326e0fefcfc7e05db8',
'scope' => 'local',
'status' => 'start',
'time' => 1461083270,
'timeNano' => 1461083270652069004
Expand Down Expand Up @@ -87,6 +89,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: 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
Expand Down Expand Up @@ -155,6 +194,7 @@
event.actor.id
).to eq('bb2c783a32330b726f18d1eb44d80c899ef45771b4f939326e0fefcfc7e05db8')
expect(event.actor.attributes).to eq('image' => 'tianon/true', 'name' => 'true-dat')
expect(event.scope).to eq('local')
expect(event.time).to eq 1461083270
expect(event.time_nano).to eq 1461083270652069004
end
Expand Down
4 changes: 4 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading