docker-api-ng 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +202 -0
  3. data/NOTICE +12 -0
  4. data/README.md +278 -0
  5. data/docker-api-ng.gemspec +42 -0
  6. data/docs/building-images.md +98 -0
  7. data/docs/connecting.md +111 -0
  8. data/docs/errors.md +89 -0
  9. data/docs/exec.md +100 -0
  10. data/docs/extending.md +109 -0
  11. data/docs/migrating-from-docker-api.md +156 -0
  12. data/docs/streaming.md +91 -0
  13. data/lib/docker/api/auth.rb +154 -0
  14. data/lib/docker/api/body.rb +47 -0
  15. data/lib/docker/api/client.rb +117 -0
  16. data/lib/docker/api/collection.rb +60 -0
  17. data/lib/docker/api/collections/containers.rb +78 -0
  18. data/lib/docker/api/collections/images.rb +221 -0
  19. data/lib/docker/api/collections/networks.rb +85 -0
  20. data/lib/docker/api/collections/system.rb +98 -0
  21. data/lib/docker/api/collections/volumes.rb +54 -0
  22. data/lib/docker/api/config.rb +163 -0
  23. data/lib/docker/api/connection.rb +333 -0
  24. data/lib/docker/api/context.rb +124 -0
  25. data/lib/docker/api/errors.rb +159 -0
  26. data/lib/docker/api/operations.rb +3064 -0
  27. data/lib/docker/api/path.rb +33 -0
  28. data/lib/docker/api/platform.rb +68 -0
  29. data/lib/docker/api/query.rb +76 -0
  30. data/lib/docker/api/resource.rb +174 -0
  31. data/lib/docker/api/resources/container.rb +378 -0
  32. data/lib/docker/api/resources/exec.rb +48 -0
  33. data/lib/docker/api/resources/image.rb +209 -0
  34. data/lib/docker/api/resources/network.rb +90 -0
  35. data/lib/docker/api/resources/volume.rb +51 -0
  36. data/lib/docker/api/response.rb +110 -0
  37. data/lib/docker/api/session.rb +70 -0
  38. data/lib/docker/api/stream.rb +142 -0
  39. data/lib/docker/api/tar.rb +157 -0
  40. data/lib/docker/api/transport/base.rb +62 -0
  41. data/lib/docker/api/transport/fake.rb +154 -0
  42. data/lib/docker/api/transport/named_pipe.rb +58 -0
  43. data/lib/docker/api/transport/tcp.rb +49 -0
  44. data/lib/docker/api/transport/tls.rb +84 -0
  45. data/lib/docker/api/transport/unix.rb +35 -0
  46. data/lib/docker/api/transport.rb +113 -0
  47. data/lib/docker/api/version.rb +21 -0
  48. data/lib/docker/api.rb +68 -0
  49. data/sig/docker/api/core.rbs +109 -0
  50. data/sig/docker/api/operations.rbs +123 -0
  51. metadata +97 -0
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Copyright 2026 Tim Smith
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ module Docker
7
+ module API
8
+ module Transport
9
+ # A scripted daemon for tests, served over a real socket pair.
10
+ #
11
+ # The socket is genuine on purpose. `Net::BufferedIO` calls
12
+ # `read_nonblock`, `write` and `to_io` on whatever it is handed, and a
13
+ # StringIO does not honour that contract. Faking it produces tests that
14
+ # pass against a double the production code could never actually drive.
15
+ # `Socket.pair` gives real socket semantics with no network, so the
16
+ # connection layer is exercised exactly as it runs against a daemon --
17
+ # including chunked bodies, keep-alive and connection upgrades.
18
+ #
19
+ # @example Scripting one response
20
+ # fake = Docker::API::Transport::Fake.new([
21
+ # "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nok",
22
+ # ])
23
+ # client = Docker::API::Client.new(transport: fake, api_version: "1.55")
24
+ # client.system.info
25
+ # fake.requests.first #=> "GET /v1.55/info HTTP/1.1\r\n..."
26
+ class Fake < Base
27
+ # @return [Array<String>] raw request bytes, in the order received
28
+ attr_reader :requests
29
+
30
+ # @param responses [Array<String>] raw HTTP responses to serve, in order
31
+ def initialize(responses = [])
32
+ super()
33
+ @responses = Array(responses).dup
34
+ @requests = []
35
+ @mutex = Mutex.new
36
+ @threads = []
37
+ end
38
+
39
+ # Add another scripted response.
40
+ #
41
+ # @param response [String] raw HTTP response bytes
42
+ # @return [self]
43
+ def <<(response)
44
+ @mutex.synchronize { @responses << response }
45
+ self
46
+ end
47
+
48
+ # @return [IO] the client end of a socket pair with a server behind it
49
+ def connect
50
+ ours, theirs = Socket.pair(:UNIX, :STREAM)
51
+ thread = Thread.new { serve(theirs) }
52
+ thread.report_on_exception = false
53
+ @threads << thread
54
+ ours
55
+ end
56
+
57
+ # Wait for the scripted server to finish, so assertions about recorded
58
+ # requests do not race the thread that records them.
59
+ #
60
+ # @return [void]
61
+ def finish
62
+ @threads.each { |thread| thread.join(2) }
63
+ self
64
+ end
65
+
66
+ # @return [String]
67
+ def to_s
68
+ "#<Docker::API::Transport::Fake scripted=#{@responses.size}>"
69
+ end
70
+ alias_method :inspect, :to_s
71
+
72
+ private
73
+
74
+ # Serve requests until the client goes away or the script runs out.
75
+ #
76
+ # Hanging up once the last scripted response has been written is what
77
+ # lets a client that reads to end-of-stream finish. A server that
78
+ # politely waited for another request would leave every such read
79
+ # blocked forever, which is a hang rather than a failure and therefore
80
+ # far more annoying to diagnose.
81
+ #
82
+ # @param io [IO] the server end of the pair
83
+ # @return [void]
84
+ def serve(io)
85
+ loop do
86
+ request = read_request(io)
87
+ break if request.nil?
88
+
89
+ @mutex.synchronize { @requests << request }
90
+ response, remaining = @mutex.synchronize { [@responses.shift, @responses.size] }
91
+ break if response.nil?
92
+
93
+ io.write(response)
94
+ break if remaining == 0
95
+ end
96
+ rescue IOError, SystemCallError
97
+ nil
98
+ ensure
99
+ begin
100
+ io.close unless io.closed?
101
+ rescue IOError, SystemCallError
102
+ nil
103
+ end
104
+ end
105
+
106
+ # Read one complete HTTP request: the head, then the body, however
107
+ # the client chose to frame it.
108
+ #
109
+ # Chunked bodies matter here rather than being pedantry. A build
110
+ # context is streamed from an IO, so Net::HTTP sends it chunked with no
111
+ # Content-Length. A fake that stops after the head leaves the body in
112
+ # the socket, reads it as if it were the next request, and quietly
113
+ # consumes the response scripted for the call after this one.
114
+ #
115
+ # @param io [IO] the server end of the pair
116
+ # @return [String, nil] raw request bytes, or nil at end of stream
117
+ def read_request(io)
118
+ head = +""
119
+ until head.end_with?("\r\n\r\n")
120
+ line = io.gets
121
+ return nil if line.nil?
122
+
123
+ head << line
124
+ end
125
+
126
+ return head + read_chunked_body(io) if head.match?(/^Transfer-Encoding:\s*chunked/i)
127
+
128
+ length = head[/^Content-Length:\s*(\d+)/i, 1]
129
+ return head if length.nil?
130
+
131
+ head + io.read(Integer(length)).to_s
132
+ end
133
+
134
+ # @param io [IO] the server end of the pair
135
+ # @return [String] the decoded body
136
+ def read_chunked_body(io)
137
+ body = +""
138
+ loop do
139
+ size_line = io.gets
140
+ break if size_line.nil?
141
+
142
+ size = size_line.strip.split(";").first.to_i(16)
143
+ break if size == 0
144
+
145
+ body << io.read(size).to_s
146
+ io.read(2) # the CRLF that terminates the chunk
147
+ end
148
+ io.gets # the blank line after the terminating chunk
149
+ body
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Copyright 2026 Tim Smith
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ module Docker
7
+ module API
8
+ module Transport
9
+ # A daemon reached over a Windows named pipe, which is how Docker Desktop
10
+ # for Windows exposes the Engine API by default.
11
+ #
12
+ # Windows named pipes present as files, so a binary-mode File is a
13
+ # bidirectional stream that behaves closely enough to a socket for the
14
+ # layer above. This is the transport the docker-api gem never grew, which
15
+ # is why kitchen-dokken carries a standing TODO about Windows support.
16
+ class NamedPipe < Base
17
+ # The pipe Docker Desktop for Windows publishes by default.
18
+ DEFAULT_PIPE = "//./pipe/docker_engine"
19
+
20
+ # @return [String] the pipe path
21
+ attr_reader :path
22
+
23
+ # @param path [String] the pipe path
24
+ def initialize(path: DEFAULT_PIPE)
25
+ super()
26
+ @path = path
27
+ end
28
+
29
+ # @return [IO] the opened pipe
30
+ # @raise [Docker::API::ConnectionError]
31
+ def connect
32
+ dial("npipe://#{path}") do
33
+ pipe = File.open(windows_path, "r+b")
34
+ pipe.sync = true
35
+ pipe
36
+ end
37
+ end
38
+
39
+ # @return [String]
40
+ def to_s
41
+ "#<Docker::API::Transport::NamedPipe path=#{path}>"
42
+ end
43
+ alias_method :inspect, :to_s
44
+
45
+ private
46
+
47
+ # Accept both the forward-slash form that appears in DOCKER_HOST and
48
+ # the backslash form Windows itself uses, rather than making callers
49
+ # care which one they happen to have.
50
+ #
51
+ # @return [String]
52
+ def windows_path
53
+ path.tr("/", "\\")
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Copyright 2026 Tim Smith
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ module Docker
7
+ module API
8
+ module Transport
9
+ # A daemon reached over plain TCP. Unencrypted, so appropriate only on a
10
+ # trusted network or a loopback address.
11
+ class Tcp < Base
12
+ # @return [String] the host
13
+ attr_reader :host
14
+
15
+ # @return [Integer] the port
16
+ attr_reader :port
17
+
18
+ # @param host [String] the host to dial
19
+ # @param port [Integer] the port to dial
20
+ # @param open_timeout [Numeric] seconds to wait for the connection
21
+ def initialize(host:, port:, open_timeout: 10)
22
+ super()
23
+ @host = host
24
+ @port = Integer(port)
25
+ @open_timeout = open_timeout
26
+ end
27
+
28
+ # @return [IO] a connected TCP socket
29
+ # @raise [Docker::API::ConnectionError]
30
+ def connect
31
+ dial("tcp://#{host}:#{port}") do
32
+ Socket.tcp(host, port, connect_timeout: @open_timeout)
33
+ end
34
+ end
35
+
36
+ # @return [String]
37
+ def host_header
38
+ "#{host}:#{port}"
39
+ end
40
+
41
+ # @return [String]
42
+ def to_s
43
+ "#<Docker::API::Transport::Tcp host=#{host} port=#{port}>"
44
+ end
45
+ alias_method :inspect, :to_s
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Copyright 2026 Tim Smith
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ module Docker
7
+ module API
8
+ module Transport
9
+ # A daemon reached over TLS, as `DOCKER_CERT_PATH` configures.
10
+ #
11
+ # The handshake happens here rather than in Net::HTTP. That is deliberate:
12
+ # the layer above receives an already-encrypted socket and does not need
13
+ # to know whether TLS is in play, which keeps one code path for every
14
+ # transport.
15
+ class Tls < Tcp
16
+ # @param host [String] the host to dial
17
+ # @param port [Integer] the port to dial
18
+ # @param ca_file [String, nil] path to the CA bundle
19
+ # @param cert_file [String, nil] path to the client certificate
20
+ # @param key_file [String, nil] path to the client key
21
+ # @param verify [Boolean] whether to verify the daemon's certificate
22
+ # @param open_timeout [Numeric] seconds to wait for the connection
23
+ def initialize(host:, port:, ca_file: nil, cert_file: nil, key_file: nil,
24
+ verify: true, open_timeout: 10)
25
+ super(host: host, port: port, open_timeout: open_timeout)
26
+ @ca_file = ca_file
27
+ @cert_file = cert_file
28
+ @key_file = key_file
29
+ @verify = verify
30
+ end
31
+
32
+ # @return [OpenSSL::SSL::SSLSocket] a connected, handshaken socket
33
+ # @raise [Docker::API::ConnectionError]
34
+ def connect
35
+ dial("https://#{host}:#{port}") do
36
+ # The raw socket is closed by hand if anything between here and a
37
+ # completed handshake raises. sync_close only ties the two together
38
+ # once an SSLSocket exists and owns it, so an expired certificate,
39
+ # a hostname mismatch or an untrusted CA used to leave the
40
+ # descriptor open until GC got to it -- and a retry loop waiting for
41
+ # a daemon to come up exhausts descriptors rather than failing.
42
+ raw = super_socket
43
+ begin
44
+ socket = OpenSSL::SSL::SSLSocket.new(raw, ssl_context)
45
+ socket.hostname = host # SNI, which some proxies in front of a daemon require
46
+ socket.sync_close = true
47
+ socket.connect
48
+ socket
49
+ rescue StandardError
50
+ raw.close unless raw.closed?
51
+ raise
52
+ end
53
+ end
54
+ end
55
+
56
+ # @return [String]
57
+ def to_s
58
+ "#<Docker::API::Transport::Tls host=#{host} port=#{port} verify=#{@verify}>"
59
+ end
60
+ alias_method :inspect, :to_s
61
+
62
+ private
63
+
64
+ # @return [IO] the underlying TCP socket
65
+ def super_socket
66
+ Socket.tcp(host, port, connect_timeout: @open_timeout)
67
+ end
68
+
69
+ # @return [OpenSSL::SSL::SSLContext]
70
+ def ssl_context
71
+ context = OpenSSL::SSL::SSLContext.new
72
+ context.min_version = OpenSSL::SSL::TLS1_2_VERSION
73
+ context.verify_mode = @verify ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
74
+ context.ca_file = @ca_file if @ca_file
75
+ if @cert_file && @key_file
76
+ context.cert = OpenSSL::X509::Certificate.new(File.read(@cert_file))
77
+ context.key = OpenSSL::PKey.read(File.read(@key_file))
78
+ end
79
+ context
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Copyright 2026 Tim Smith
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ module Docker
7
+ module API
8
+ module Transport
9
+ # A daemon reached over a unix domain socket, which is how Docker is
10
+ # reached on Linux and macOS by default.
11
+ class Unix < Base
12
+ # @return [String] the socket path
13
+ attr_reader :path
14
+
15
+ # @param path [String] the socket path, e.g. "/var/run/docker.sock"
16
+ def initialize(path:)
17
+ super()
18
+ @path = path
19
+ end
20
+
21
+ # @return [IO] a connected unix socket
22
+ # @raise [Docker::API::ConnectionError]
23
+ def connect
24
+ dial("unix://#{path}") { UNIXSocket.new(path) }
25
+ end
26
+
27
+ # @return [String]
28
+ def to_s
29
+ "#<Docker::API::Transport::Unix path=#{path}>"
30
+ end
31
+ alias_method :inspect, :to_s
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Copyright 2026 Tim Smith
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ module Docker
7
+ module API
8
+ # Transports know how to reach a daemon, and nothing else.
9
+ #
10
+ # Every transport answers {Base#connect} with a connected IO. That single
11
+ # responsibility is what lets the same connection code drive a unix socket
12
+ # in development, TLS in CI and a Windows named pipe on a laptop.
13
+ module Transport
14
+ # Build the transport a Docker host URL calls for.
15
+ #
16
+ # @param url [String] a Docker host URL. Understands `unix://`, `tcp://`,
17
+ # `http://`, `https://`, `npipe://`, and a bare path (taken as a unix
18
+ # socket), matching what `DOCKER_HOST` may contain.
19
+ # @param tls [Hash, nil] TLS material: `:ca_file`, `:cert_file`,
20
+ # `:key_file`, `:verify`. Any of these upgrades a `tcp://` URL to TLS.
21
+ # @param open_timeout [Numeric] seconds to wait for a connection
22
+ # @return [Docker::API::Transport::Base]
23
+ # @raise [Docker::API::ConnectionError] if the scheme is not one we speak
24
+ #
25
+ # @example
26
+ # Transport.for(url: "unix:///var/run/docker.sock")
27
+ # Transport.for(url: "tcp://build:2376", tls: { ca_file: "ca.pem" })
28
+ def self.for(url:, tls: nil, open_timeout: 10)
29
+ scheme, rest = split(url.to_s)
30
+ tls = nil if tls.respond_to?(:empty?) && tls.empty?
31
+
32
+ case scheme
33
+ when "unix"
34
+ Unix.new(path: rest)
35
+ when "npipe"
36
+ NamedPipe.new(path: rest)
37
+ when "https"
38
+ tcp_transport(rest, tls || {}, open_timeout, secure: true)
39
+ when "tcp", "http"
40
+ tcp_transport(rest, tls, open_timeout, secure: !tls.nil?)
41
+ else
42
+ raise ConnectionError.new(
43
+ "cannot reach a Docker daemon over '#{scheme}' (from #{url.inspect}); " \
44
+ "expected one of unix, tcp, http, https or npipe"
45
+ )
46
+ end
47
+ end
48
+
49
+ # Split a host URL into a scheme and the remainder.
50
+ #
51
+ # Hand-parsed rather than handed to URI, because `unix:///var/run/x.sock`
52
+ # and `npipe:////./pipe/docker_engine` are not URLs that URI's generic
53
+ # parser handles the way Docker means them.
54
+ #
55
+ # @param url [String]
56
+ # @return [Array(String, String)] the scheme and the remainder
57
+ # @api private
58
+ def self.split(url)
59
+ if (match = url.match(%r{\A(?<scheme>[a-z][a-z0-9+.-]*)://(?<rest>.*)\z}i))
60
+ scheme = match[:scheme].downcase
61
+ rest = match[:rest]
62
+ # unix and npipe carry a path, and the leading slash is part of it.
63
+ rest = "/#{rest}" if %w{unix npipe}.include?(scheme) && !rest.start_with?("/")
64
+ [scheme, rest]
65
+ elsif url.start_with?("/", "\\") || url.empty?
66
+ # A bare path is what a daemon on a unix socket looks like.
67
+ ["unix", url.empty? ? "/var/run/docker.sock" : url]
68
+ else
69
+ ["tcp", url]
70
+ end
71
+ end
72
+ private_class_method :split
73
+
74
+ # @param authority [String] "host:port" or "host"
75
+ # @param tls [Hash, nil] TLS material
76
+ # @param open_timeout [Numeric]
77
+ # @param secure [Boolean]
78
+ # @return [Docker::API::Transport::Tcp, Docker::API::Transport::Tls]
79
+ # @api private
80
+ def self.tcp_transport(authority, tls, open_timeout, secure:)
81
+ host, port = split_authority(authority, secure: secure)
82
+
83
+ return Tcp.new(host: host, port: port, open_timeout: open_timeout) unless secure
84
+
85
+ tls ||= {}
86
+ Tls.new(
87
+ host: host, port: port, open_timeout: open_timeout,
88
+ ca_file: tls[:ca_file], cert_file: tls[:cert_file], key_file: tls[:key_file],
89
+ verify: tls.fetch(:verify, true)
90
+ )
91
+ end
92
+ private_class_method :tcp_transport
93
+
94
+ # @param authority [String]
95
+ # @param secure [Boolean]
96
+ # @return [Array(String, Integer)]
97
+ # @api private
98
+ def self.split_authority(authority, secure:)
99
+ authority = authority.sub(%r{/.*\z}, "")
100
+ default_port = secure ? 2376 : 2375
101
+ # `tcp://` with nothing after it means localhost, which is what the
102
+ # Docker CLI does with a bare scheme.
103
+ return ["localhost", default_port] if authority.empty?
104
+
105
+ host, _, port = authority.rpartition(":")
106
+ return [authority, default_port] if host.empty?
107
+
108
+ [host, port.empty? ? default_port : Integer(port)]
109
+ end
110
+ private_class_method :split_authority
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Copyright 2026 Tim Smith
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ module Docker
7
+ module API
8
+ # The gem's own version.
9
+ VERSION = "0.3.0"
10
+
11
+ # The newest Engine API version this gem vendors a specification for.
12
+ # Requests are never made against a version above this, even when the
13
+ # daemon offers one, because the generated layer only knows this shape.
14
+ MAX_API_VERSION = "1.55"
15
+
16
+ # The oldest Engine API version this gem will talk to. Docker 20.10 is the
17
+ # oldest release still plausibly in service; below it, enough of the API
18
+ # differs that failing loudly beats failing mysteriously.
19
+ MIN_API_VERSION = "1.41"
20
+ end
21
+ end
data/lib/docker/api.rb ADDED
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Copyright 2026 Tim Smith
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ require "json" unless defined?(JSON)
7
+ require "net/http" unless defined?(Net::HTTP)
8
+ require "openssl" unless defined?(OpenSSL)
9
+ require "socket" unless defined?(Socket)
10
+ require "stringio" unless defined?(StringIO)
11
+ require "uri" unless defined?(URI)
12
+
13
+ # The `docker-api` gem also owns the `Docker` namespace. This gem reopens it
14
+ # additively and defines nothing directly on it, so both can be loaded into one
15
+ # process -- which is what makes a gradual migration possible rather than a
16
+ # flag day.
17
+ module Docker
18
+ # A Ruby client for the modern Docker Engine API.
19
+ #
20
+ # The complete API surface is generated from Docker's own OpenAPI
21
+ # specification and reachable through {Client#operations}; an ergonomic
22
+ # hand-written layer of collections and resources sits on top of it.
23
+ #
24
+ # @example Talking to the local daemon
25
+ # client = Docker::API::Client.new
26
+ # client.system.info["ServerVersion"]
27
+ #
28
+ # @example Two daemons at once, with no shared state
29
+ # local = Docker::API::Client.new
30
+ # build = Docker::API::Client.new(url: "tcp://build.internal:2376")
31
+ module API
32
+ require_relative "api/version"
33
+ require_relative "api/errors"
34
+ require_relative "api/response"
35
+ require_relative "api/query"
36
+ require_relative "api/path"
37
+ require_relative "api/body"
38
+ require_relative "api/platform"
39
+ require_relative "api/tar"
40
+ require_relative "api/transport/base"
41
+ require_relative "api/transport/unix"
42
+ require_relative "api/transport/tcp"
43
+ require_relative "api/transport/tls"
44
+ require_relative "api/transport/named_pipe"
45
+ require_relative "api/transport/fake"
46
+ require_relative "api/transport"
47
+ require_relative "api/session"
48
+ require_relative "api/stream"
49
+ require_relative "api/connection"
50
+ require_relative "api/context"
51
+ require_relative "api/config"
52
+ require_relative "api/auth"
53
+ require_relative "api/operations"
54
+ require_relative "api/collection"
55
+ require_relative "api/resource"
56
+ require_relative "api/resources/exec"
57
+ require_relative "api/resources/container"
58
+ require_relative "api/resources/image"
59
+ require_relative "api/resources/network"
60
+ require_relative "api/resources/volume"
61
+ require_relative "api/collections/containers"
62
+ require_relative "api/collections/images"
63
+ require_relative "api/collections/networks"
64
+ require_relative "api/collections/volumes"
65
+ require_relative "api/collections/system"
66
+ require_relative "api/client"
67
+ end
68
+ end
@@ -0,0 +1,109 @@
1
+ # Hand-written signatures for the layers the generated operations depend on.
2
+ #
3
+ # Kept deliberately narrow: enough to type-check the generated file against the
4
+ # connection it calls, without pinning down internals that are free to change.
5
+
6
+ module Docker
7
+ module API
8
+ VERSION: String
9
+ MAX_API_VERSION: String
10
+ MIN_API_VERSION: String
11
+
12
+ class Error < StandardError
13
+ attr_reader operation: String?
14
+ attr_reader status: Integer?
15
+ attr_reader response: Docker::API::Response?
16
+
17
+ def initialize: (?String? message, ?operation: (String | Symbol)?, ?status: Integer?, ?response: Docker::API::Response?) -> void
18
+ def self.for: (status: Integer, ?operation: (String | Symbol)?, ?response: Docker::API::Response?) -> Docker::API::Error
19
+ end
20
+
21
+ class ConnectionError < Error
22
+ end
23
+
24
+ class TimeoutError < Error
25
+ end
26
+
27
+ class ClientError < Error
28
+ end
29
+
30
+ class BadRequest < ClientError
31
+ end
32
+
33
+ class Unauthorized < ClientError
34
+ end
35
+
36
+ class Forbidden < ClientError
37
+ end
38
+
39
+ class NotFound < ClientError
40
+ end
41
+
42
+ class NotModified < ClientError
43
+ end
44
+
45
+ class Conflict < ClientError
46
+ end
47
+
48
+ class ServerError < Error
49
+ end
50
+
51
+ class VersionUnsupported < Error
52
+ end
53
+
54
+ class StreamError < Error
55
+ end
56
+
57
+ class Response
58
+ attr_reader status: Integer
59
+ attr_reader headers: Hash[String, String]
60
+ attr_reader body: String
61
+
62
+ def initialize: (status: Integer, ?headers: Hash[untyped, untyped], ?body: String?) -> void
63
+ def json: () -> untyped
64
+ def success?: () -> bool
65
+ def []: ((String | Symbol) name) -> String?
66
+ end
67
+
68
+ class Connection
69
+ UNVERSIONED: Array[String]
70
+
71
+ def initialize: (transport: untyped, ?api_version: (String | Symbol), ?logger: untyped?, ?read_timeout: Numeric, ?open_timeout: Numeric) -> void
72
+ def api_version: () -> String?
73
+ def ping: () -> Docker::API::Response
74
+ def request: (
75
+ Symbol http_method,
76
+ String path,
77
+ ?query: Hash[untyped, untyped],
78
+ ?body: untyped,
79
+ ?headers: Hash[untyped, untyped],
80
+ ?expects: Array[Integer],
81
+ ?operation: (String | Symbol)?,
82
+ ?content_type: String?
83
+ ) ?{ (String) -> void } -> Docker::API::Response
84
+ def hijack: (
85
+ Symbol http_method,
86
+ String path,
87
+ ?query: Hash[untyped, untyped],
88
+ ?body: untyped,
89
+ ?headers: Hash[untyped, untyped],
90
+ ?operation: (String | Symbol)?
91
+ ) -> IO
92
+ end
93
+
94
+ module Path
95
+ SAFE: Regexp
96
+
97
+ def self.escape: (untyped value) -> String
98
+ end
99
+
100
+ module Query
101
+ def self.encode: (Hash[untyped, untyped]? params) -> String
102
+ end
103
+
104
+ module Platform
105
+ def self.oci: (untyped value) -> Hash[String, String]?
106
+ def self.string: (untyped value) -> String?
107
+ end
108
+ end
109
+ end