lib/docker/remote/client.rb in docker-remote-0.4.0 vs lib/docker/remote/client.rb in docker-remote-0.5.0
- old
+ new
@@ -11,10 +11,14 @@
class Client
include Utils
attr_reader :registry_url, :repo, :username, :password
+ PORTMAP = { 'ghcr.io' => 443 }.freeze
+ DEFAULT_PORT = 443
+ STANDARD_PORTS = [DEFAULT_PORT, 80].freeze
+
def initialize(registry_url, repo, username = nil, password = nil)
@registry_url = registry_url
@repo = repo
@username = username
@password = password
@@ -111,20 +115,83 @@
response
end
def registry_uri
- @registry_uri ||= URI.parse(registry_url)
+ @registry_uri ||= begin
+ host_port, *rest = registry_url.split('/')
+ host, port = host_port.split(':')
+
+ ports = if port
+ [port.to_i]
+ elsif prt = PORTMAP[host]
+ [prt]
+ else
+ STANDARD_PORTS
+ end
+
+ port = ports.find { |port| can_connect?(host, port) }
+
+ unless port
+ raise DockerRemoteError, "couldn't determine what port to connect to"
+ end
+
+ scheme = port == DEFAULT_PORT ? 'https' : 'http'
+ URI.parse("#{scheme}://#{host}:#{port}/#{rest.join('/')}")
+ end
end
def make_http(uri)
Net::HTTP.new(uri.host, uri.port).tap do |http|
http.use_ssl = true if uri.scheme == 'https'
end
end
def registry_http
@registry_http ||= make_http(registry_uri)
+ end
+
+ # Adapted from: https://spin.atomicobject.com/2013/09/30/socket-connection-timeout-ruby/
+ def can_connect?(host, port)
+ # Convert the passed host into structures the non-blocking calls
+ # can deal with
+ addr = Socket.getaddrinfo(host, nil)
+ sockaddr = Socket.pack_sockaddr_in(port, addr[0][3])
+ timeout = 3
+
+ Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0).tap do |socket|
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
+
+ begin
+ # Initiate the socket connection in the background. If it doesn't fail
+ # immediately it will raise an IO::WaitWritable (Errno::EINPROGRESS)
+ # indicating the connection is in progress.
+ socket.connect_nonblock(sockaddr)
+
+ rescue IO::WaitWritable
+ # IO.select will block until the socket is writable or the timeout
+ # is exceeded - whichever comes first.
+ if IO.select(nil, [socket], nil, timeout)
+ begin
+ # Verify there is now a good connection
+ socket.connect_nonblock(sockaddr)
+ rescue Errno::EISCONN
+ # Good news everybody, the socket is connected!
+ socket.close
+ return true
+ rescue
+ # An unexpected exception was raised - the connection is no good.
+ socket.close
+ end
+ else
+ # IO.select returns nil when the socket is not ready before timeout
+ # seconds have elapsed
+ socket.close
+ end
+ end
+ end
+
+ false
end
end
end
end