require "uri"
require "socket"
require "http/client"
require "json"
require "../../core_ext/http/client"
require "../../core_ext/openssl/**"
require "../../core_ext/named_tuple/camelcase_keys"
require "../errors"
require "./containers"
require "./daemon"
require "./images"
require "./volumes"
class Docker::Api::ApiClient
DEFAULT_URL = "unix:///var/run/docker.sock"
getter socket_path : String
getter socket : UNIXSocket
property api_version : String = "latest"
def initialize(base_url = nil, @api_version = "latest")
base_url ||= DEFAULT_URL
uri = URI.parse base_url
@socket_path = uri.to_s
case uri.scheme
when "unix"
@socket_path = @socket_path.gsub(/unix:\/\//, "")
@socket = UNIXSocket.new(@socket_path)
else
raise NotImplementedError.new("local unix socket only supported at the time")
end
end
{% for method in %w(get post put head delete patch options) %}
def unix_{{method.id}}(
path : String,
headers : HTTP::Headers? = nil,
body : HTTP::Client::BodyType? = nil
)
headers ||= HTTP::Headers.new
headers = headers.dup
headers["Host"] ||= "docker"
headers["Connection"] = "close"
headers["Content-Type"] = "application/json"
if body && !body.empty?
headers["Content-Length"] = body.bytesize.to_s
else
body = nil
end
req = HTTP::Request.new(
"{{method.id.upcase}}",
URI.parse("http:/#{path}").to_s,
headers,
body
)
req_serialized = String.build do |s|
s << "#{req.method} #{path} HTTP/1.1\r\n"
req.headers.each do |k, v|
s << "#{k}: #{v.join(", ")}\r\n"
end
s << "\r\n"
s << (req.body ? req.body.to_s : "")
end
@socket = UNIXSocket.new(@socket_path)
begin
@socket.write req_serialized.to_slice
res_io = IO::Memory.new
buf = Bytes.new(8192)
while true
r = @socket.read(buf) rescue 0
break if r == 0
res_io.write buf[0, r]
end
res_io.rewind
res_str = res_io.gets_to_end
res_io.rewind
ensure
@socket.close
end
res = HTTP::Client::Response.from_io(res_io) do |response|
response
end
raw = res_str
.gsub("10f1\r\n", "")
.gsub("10f", "")
.gsub("11\r\n", "")
.gsub("11c4\r\n", "")
.gsub("0\r\n", "")
.gsub("1321\r\n", "")
.gsub("1322\r\n", "")
.gsub("13\r\n", "")
.gsub("14\r\n", "")
.gsub("1d\r\n", "")
.gsub("4f\r\n", "")
head, body = raw.split("\r\n\r\n", 2)
body = body.chomp("\r\n").chomp("\r\n")
status_line, *header_lines = head.split("\r\n")
http_version, status_code_str, *status_parts = status_line.split(" ")
status_code = status_code_str.to_i
status_message = status_parts.join(" ")
headers = HTTP::Headers.new
header_lines.each do |line|
next if line.empty?
name, value = line.split(":", 2)
headers.add name.strip, value.strip
end
if headers["Content-Length"]?.nil?
headers["Content-Length"] = "0"
end
HTTP::Client::Response.new(
status_code,
status_code != 204 ? body : nil,
headers,
status_message,
http_version,
status_code != 204 ? IO::Memory.new(body) : IO::Memory.new,
)
end
def {{method.id}}(path, headers : HTTP::Headers? = nil, body : HTTP::Client::BodyType? = nil)
path = "/#{api_version}#{path}" unless api_version.nil?
response = unix_{{method.id}} path, headers, body
raise Docker::ApiError.from_response(response) unless response.success?
response
end
def {{method.id}}(path, body : NamedTuple)
headers = HTTP::Headers.new
headers["Content-Type"] = "application/json"
{{method.id}} path, headers, body.camelcase_keys.to_json
end
def {{method.id}}(path, headers : HTTP::Headers, body : NamedTuple)
{{method.id}} path, headers, body.camelcase_keys.to_json
end
def {{method.id}}(path, headers : HTTP::Headers? = nil, body : HTTP::Client::BodyType = nil)
{{method.id}} path, headers, body do |response|
raise Docker::ApiError.from_response(response) unless response.success?
yield response
end
end
def {{method.id}}(path, body : NamedTuple)
headers = HTTP::Headers.new
headers["Content-Type"] = "application/json"
{{method.id}} path, headers, body.camelcase_keys.to_json do |response|
yield response
end
end
def {{method.id}}(path, headers : HTTP::Headers, body : NamedTuple)
headers["Content-Type"] = "application/json"
{{method.id}} path, headers, body.camelcase_keys.to_json do |response|
yield response
end
end
{% end %}
include Containers
include Daemon
include Images
include Volumes
end