Module: Kitchen::Driver::Azure::Http

Defined in:
lib/kitchen/driver/azure/http.rb

Overview

A very small JSON-over-HTTP helper built on the standard library.

The driver makes a handful of straightforward requests, so this replaces Faraday and its middleware chain rather than depending on them.

Defined Under Namespace

Classes: Response

Constant Summary collapse

TRANSIENT_ERRORS =

Network-level failures worth retrying rather than surfacing.

Returns:

  • (Array<Class>)
[
  Net::OpenTimeout,
  Net::ReadTimeout,
  Errno::ECONNREFUSED,
  Errno::ECONNRESET,
  Errno::EHOSTUNREACH,
  Errno::ENETUNREACH,
  Errno::EPIPE,
  EOFError,
  SocketError,
  OpenSSL::SSL::SSLError,
].freeze
IPAddr.new("169.254.0.0/16").freeze
OPEN_TIMEOUT =

Seconds to wait for a connection and for a response.

Returns:

  • (Integer)
30
READ_TIMEOUT =

Returns:

  • (Integer)
120

Class Method Summary collapse

Class Method Details

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns whether the host is a link-local address.

Parameters:

  • host (String)

Returns:

  • (Boolean)

    whether the host is a link-local address.



133
134
135
136
137
138
# File 'lib/kitchen/driver/azure/http.rb', line 133

def self.link_local?(host)
  LINK_LOCAL.include?(IPAddr.new(host.to_s))
rescue IPAddr::Error
  # Not an IP address at all, so not the metadata service.
  false
end

.perform(uri, request) ⇒ Net::HTTPResponse

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Sends a request, honouring the proxy environment variables.

Parameters:

  • uri (URI)
  • request (Net::HTTPRequest)

Returns:

  • (Net::HTTPResponse)


95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/kitchen/driver/azure/http.rb', line 95

def self.perform(uri, request)
  proxy = proxy_for(uri)
  http = if proxy
           Net::HTTP.new(uri.host, uri.port, proxy.host, proxy.port, proxy.user, proxy.password)
         else
           # Explicitly nil rather than Net::HTTP's default of :ENV,
           # which would send it back to the environment to pick a
           # proxy we have just decided against.
           Net::HTTP.new(uri.host, uri.port, nil)
         end

  http.use_ssl = uri.scheme == "https"
  http.open_timeout = OPEN_TIMEOUT
  http.read_timeout = READ_TIMEOUT
  http.start { |connection| connection.request(request) }
end

.proxy_for(uri) ⇒ URI?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The proxy to reach a URL through, if any.

Azure's instance metadata service answers on a link-local address, which exists only on the local link and which no proxy can route to. Sending it through one breaks managed identity authentication outright: the connection hangs until the open timeout, surfaces as a TransientError, and is then retried. Every Azure SDK carves the same exception out.

Parameters:

  • uri (URI)

Returns:

  • (URI, nil)


124
125
126
127
128
# File 'lib/kitchen/driver/azure/http.rb', line 124

def self.proxy_for(uri)
  return nil if link_local?(uri.host)

  uri.find_proxy
end

.request(method:, url:, headers: {}, body: nil) ⇒ Response

Performs a request.

Parameters:

  • method (Symbol)

    :get, :head, :put, :post or :delete.

  • url (String)

    absolute URL.

  • headers (Hash{String => String}) (defaults to: {})

    request headers.

  • body (String, nil) (defaults to: nil)

    request body, already encoded.

Returns:

Raises:



77
78
79
80
81
82
83
84
85
86
87
# File 'lib/kitchen/driver/azure/http.rb', line 77

def self.request(method:, url:, headers: {}, body: nil)
  uri = URI.parse(url)
  request = request_class(method).new(uri)
  headers.each { |name, value| request[name] = value }
  request.body = body if body

  response = perform(uri, request)
  Response.new(response.code.to_i, response.body.to_s)
rescue *TRANSIENT_ERRORS => e
  raise TransientError, "#{e.class}: #{e.message}"
end

.request_class(method) ⇒ Class

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the matching Net::HTTP request class.

Parameters:

  • method (Symbol)

Returns:

  • (Class)

    the matching Net::HTTP request class.

Raises:

  • (ArgumentError)

    for an unsupported method.



144
145
146
147
148
149
150
151
152
153
# File 'lib/kitchen/driver/azure/http.rb', line 144

def self.request_class(method)
  case method
  when :get then Net::HTTP::Get
  when :head then Net::HTTP::Head
  when :put then Net::HTTP::Put
  when :post then Net::HTTP::Post
  when :delete then Net::HTTP::Delete
  else raise ArgumentError, "Unsupported HTTP method: #{method}"
  end
end