Class: Docker::API::Connection

Inherits:
Object
  • Object
show all
Defined in:
lib/docker/api/connection.rb,
sig/docker/api/core.rbs

Overview

Everything between "a socket exists" and "a Ruby value comes back".

A connection owns request building, API version negotiation, error mapping, streaming and hijack. It is immutable once built: there is no setter for the URL or for credentials, which is what lets two connections address two daemons in one process without interfering.

Constant Summary collapse

UNVERSIONED =

Endpoints that must not carry a version prefix, because they are how we discover which version to use.

Returns:

  • (Array[String])
["/_ping"].freeze
VERBS =

HTTP verbs mapped to the Net::HTTP request classes that implement them.

{
  get: Net::HTTP::Get,
  post: Net::HTTP::Post,
  put: Net::HTTP::Put,
  patch: Net::HTTP::Patch,
  delete: Net::HTTP::Delete,
  head: Net::HTTP::Head,
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(transport:, api_version: :negotiate, logger: nil, read_timeout: 60, open_timeout: 10) ⇒ Connection

Returns a new instance of Connection.

Parameters:

  • transport (Docker::API::Transport::Base)

    how to reach the daemon

  • api_version (String, Symbol) (defaults to: :negotiate)

    :negotiate to ask the daemon, a version string such as "1.44" to pin, or :none to send unprefixed paths

  • logger (Logger, nil) (defaults to: nil)

    receives one debug line per request

  • read_timeout (Numeric) (defaults to: 60)

    seconds to wait for response data

  • open_timeout (Numeric) (defaults to: 10)

    seconds to wait for a connection

  • transport: (Object)
  • api_version: (String, Symbol) (defaults to: :negotiate)
  • logger: (Object, nil) (defaults to: nil)
  • read_timeout: (Numeric) (defaults to: 60)
  • open_timeout: (Numeric) (defaults to: 10)


42
43
44
45
46
47
48
49
50
# File 'lib/docker/api/connection.rb', line 42

def initialize(transport:, api_version: :negotiate, logger: nil,
  read_timeout: 60, open_timeout: 10)
  @transport = transport
  @configured_api_version = api_version
  @logger = logger
  @read_timeout = read_timeout
  @open_timeout = open_timeout
  @version_lock = Mutex.new
end

Instance Attribute Details

#loggerLogger? (readonly)

Returns where request metadata is logged, if anywhere.

Returns:

  • (Logger, nil)

    where request metadata is logged, if anywhere



33
34
35
# File 'lib/docker/api/connection.rb', line 33

def logger
  @logger
end

#transportDocker::API::Transport::Base (readonly)

Returns the transport in use.

Returns:



30
31
32
# File 'lib/docker/api/connection.rb', line 30

def transport
  @transport
end

Instance Method Details

#api_versionString?

The API version this connection prefixes its requests with.

Negotiation happens once, lazily, on the first request that needs it.

Returns:

  • (String, nil)

    the version, or nil when versioning is disabled

Raises:



58
59
60
61
62
63
64
65
# File 'lib/docker/api/connection.rb', line 58

def api_version
  return @api_version if defined?(@api_version)

  @version_lock.synchronize do
    @api_version = resolve_api_version unless defined?(@api_version)
  end
  @api_version
end

#hijack(method, path, query: {}, body: nil, headers: {}, operation: nil) ⇒ IO

Take over the socket after an upgrade, for interactive exec and attach.

This deliberately does not go through Net::HTTP. An interactive session needs the socket itself, and Net::BufferedIO may already have read ahead past the response headers. Rather than prise a buffered socket back out of the stdlib -- the problem that produced docker-api's custom Excon middleware -- this writes the request itself and hands back the socket with nothing consumed but the response head.

Parameters:

  • method (Symbol)

    the HTTP verb

  • path (String)

    the path, without a version prefix

  • query (Hash) (defaults to: {})

    query parameters

  • body (Hash, String, nil) (defaults to: nil)

    the request body

  • headers (Hash) (defaults to: {})

    additional headers

  • operation (String, Symbol, nil) (defaults to: nil)

    names this call in errors

Returns:

  • (IO)

    the bidirectional socket, positioned at the stream body

Raises:



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/docker/api/connection.rb', line 119

def hijack(method, path, query: {}, body: nil, headers: {}, operation: nil)
  io = transport.connect
  full_path = build_path(path, query)
  payload, computed_type = encode_body(body)

  write_raw_request(io, method, full_path, payload, headers, computed_type)
  status, response_headers = read_raw_response_head(io)

  unless [101, 200].include?(status)
    error_body = io.read.to_s
    io.close unless io.closed?
    raise Error.for(
      status: status, operation: operation,
      response: Response.new(status: status, headers: response_headers, body: error_body)
    )
  end

  io
rescue Errno::EPIPE, Errno::ECONNRESET, EOFError => e
  io&.close unless io.nil? || io.closed?
  raise ConnectionError.new("the daemon closed the connection during #{operation}: #{e.message}")
end

#pingDocker::API::Response

Ask the daemon whether it is alive. Never version-prefixed.



70
71
72
# File 'lib/docker/api/connection.rb', line 70

def ping
  request(:get, "/_ping", operation: "ping")
end

#request(method, path, query: {}, body: nil, headers: {}, expects: [200], operation: nil, content_type: nil) {|chunk| ... } ⇒ Docker::API::Response

Send a request and interpret the answer.

Parameters:

  • method (Symbol)

    one of :get, :post, :put, :patch, :delete, :head

  • path (String)

    the path, without a version prefix

  • query (Hash) (defaults to: {})

    query parameters in Ruby types

  • body (Hash, Array, String, IO, nil) (defaults to: nil)

    the request body. Hashes and arrays are JSON-encoded; strings and IOs are sent as-is.

  • headers (Hash) (defaults to: {})

    additional request headers

  • expects (Array<Integer>) (defaults to: [200])

    statuses that mean success

  • operation (String, Symbol, nil) (defaults to: nil)

    names this call in errors and logs

  • content_type (String, nil) (defaults to: nil)

    overrides the computed content type

Yield Parameters:

  • chunk (String)

    successive body chunks, when streaming

Returns:

Raises:



88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/docker/api/connection.rb', line 88

def request(method, path, query: {}, body: nil, headers: {}, expects: [200],
  operation: nil, content_type: nil, &block)
  full_path = build_path(path, query)
  payload, computed_type = encode_body(body)
  request_headers = build_headers(headers, content_type || computed_type, payload)

  log(method, full_path, operation)

  response = perform(method, full_path, payload, request_headers, expects, &block)
  return response if expects.include?(response.status)

  raise Error.for(status: response.status, operation: operation, response: response)
end

#to_sString Also known as: inspect

Returns:

  • (String)


143
144
145
146
# File 'lib/docker/api/connection.rb', line 143

def to_s
  "#<Docker::API::Connection transport=#{transport} api_version=#{
    defined?(@api_version) ? @api_version : "unnegotiated"}>"
end