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", "")
.gsub("3a\r\n", "")
.gsub("3e\r\n", "")
.gsub("5e\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