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