Class: Puppet::HTTP::Client

Inherits:
Object show all
Defined in:
lib/puppet/http/client.rb

Overview

The HTTP client provides methods for making ‘GET`, `POST`, etc requests to HTTP(S) servers. It also provides methods for resolving Puppetserver REST service endpoints using SRV records and settings (such as `server_list`, `server`, `ca_server`, etc). Once a service endpoint has been resolved, there are methods for making REST requests (such as getting a node, sending facts, etc).

The client uses persistent HTTP connections by default unless the ‘Connection: close` header is specified and supports streaming response bodies.

By default the client only trusts the Puppet CA for HTTPS connections. However, if the ‘include_system_store` request option is set to true, then Puppet will trust certificates in the puppet-agent CA bundle.

Examples:

To access the HTTP client:

client = Puppet.runtime[:http]

To make an HTTP GET request:

response = client.get(URI("http://www.example.com"))

To make an HTTPS GET request, trusting the puppet CA and certs in Puppet’s CA bundle:

response = client.get(URI("https://www.example.com"), options: { include_system_store: true })

To use a URL containing special characters, such as spaces:

response = client.get(URI(Puppet::Util.uri_encode("https://www.example.com/path to file")))

To pass query parameters:

response = client.get(URI("https://www.example.com"), query: {'q' => 'puppet'})

To pass custom headers:

response = client.get(URI("https://www.example.com"), headers: {'Accept-Content' => 'application/json'})

To check if the response is successful (2xx):

response = client.get(URI("http://www.example.com"))
puts response.success?

To get the response code and reason:

response = client.get(URI("http://www.example.com"))
unless response.success?
  puts "HTTP #{response.code} #{response.reason}"
 end

To read response headers:

response = client.get(URI("http://www.example.com"))
puts response['Content-Type']

To stream the response body:

client.get(URI("http://www.example.com")) do |response|
  if response.success?
    response.read_body do |data|
      puts data
    end
  end
end

To handle exceptions:

begin
  client.get(URI("https://www.example.com"))
rescue Puppet::HTTP::ResponseError => e
  puts "HTTP #{e.response.code} #{e.response.reason}"
rescue Puppet::HTTP::ConnectionError => e
  puts "Connection error #{e.message}"
rescue Puppet::SSL::SSLError => e
  puts "SSL error #{e.message}"
rescue Puppet::HTTP::HTTPError => e
  puts "General HTTP error #{e.message}"
end

To route to the ‘:puppet` service:

session = client.create_session
service = session.route_to(:puppet)

To make a node request:

node = service.get_node(Puppet[:certname], environment: 'production')

To submit facts:

facts = Puppet::Indirection::Facts.indirection.find(Puppet[:certname])
service.put_facts(Puppet[:certname], environment: 'production', facts: facts)

To submit a report to the ‘:report` service:

report = Puppet::Transaction::Report.new
service = session.route_to(:report)
service.put_report(Puppet[:certname], report, environment: 'production')

Direct Known Subclasses

ExternalClient

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(pool: Puppet::HTTP::Pool.new(Puppet[:http_keepalive_timeout]), ssl_context: nil, system_ssl_context: nil, redirect_limit: 10, retry_limit: 100) ⇒ Client

Create a new http client instance. Use ‘Puppet.runtime` to get the current client instead of creating an instance of this class.

Parameters:

  • pool (Puppet::HTTP::Pool) (defaults to: Puppet::HTTP::Pool.new(Puppet[:http_keepalive_timeout]))

    pool of persistent Net::HTTP connections

  • ssl_context (Puppet::SSL::SSLContext) (defaults to: nil)

    ssl context to be used for connections

  • system_ssl_context (Puppet::SSL::SSLContext) (defaults to: nil)

    the system ssl context used if :include_system_store is set to true

  • redirect_limit (Integer) (defaults to: 10)

    default number of HTTP redirections to allow in a given request. Can also be specified per-request.

  • retry_limit (Integer) (defaults to: 100)

    number of HTTP retries allowed in a given request



105
106
107
108
109
110
111
112
113
114
115
# File 'lib/puppet/http/client.rb', line 105

def initialize(pool: Puppet::HTTP::Pool.new(Puppet[:http_keepalive_timeout]), ssl_context: nil, system_ssl_context: nil, redirect_limit: 10, retry_limit: 100)
  @pool = pool
  @default_headers = {
    'X-Puppet-Version' => Puppet.version,
    'User-Agent' => Puppet[:http_user_agent],
  }.freeze
  @default_ssl_context = ssl_context
  @default_system_ssl_context = system_ssl_context
  @default_redirect_limit = redirect_limit
  @retry_after_handler = Puppet::HTTP::RetryAfterHandler.new(retry_limit, Puppet[:runinterval])
end

Instance Attribute Details

#poolObject (readonly)



89
90
91
# File 'lib/puppet/http/client.rb', line 89

def pool
  @pool
end

Instance Method Details

#closevoid

This method returns an undefined value.

Close persistent connections in the pool.



304
305
306
307
308
# File 'lib/puppet/http/client.rb', line 304

def close
  @pool.close
  @default_ssl_context = nil
  @default_system_ssl_context = nil
end

#connect(uri, options: {}, &block) ⇒ Object

Open a connection to the given URI. It is typically not necessary to call this method as the client will create connections as needed when a request is made.

Parameters:

  • uri (URI)

    the connection destination

  • options (Hash) (defaults to: {})

Options Hash (options:):

  • :ssl_context (Puppet::SSL::SSLContext) — default: nil

    ssl context to be used for connections

  • :include_system_store (Boolean) — default: false

    if we should include the system store for connection



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/puppet/http/client.rb', line 137

def connect(uri, options: {}, &block)
  start = Time.now
  verifier = nil
  connected = false

  site = Puppet::HTTP::Site.from_uri(uri)
  if site.use_ssl?
    ssl_context = options.fetch(:ssl_context, nil)
    include_system_store = options.fetch(:include_system_store, false)
    ctx = resolve_ssl_context(ssl_context, include_system_store)
    verifier = Puppet::SSL::Verifier.new(site.host, ctx)
  end

  @pool.with_connection(site, verifier) do |http|
    connected = true
    if block_given?
      yield http
    end
  end
rescue Net::OpenTimeout => e
  raise_error(_("Request to %{uri} timed out connect operation after %{elapsed} seconds") % { uri: uri, elapsed: elapsed(start) }, e, connected)
rescue Net::ReadTimeout => e
  raise_error(_("Request to %{uri} timed out read operation after %{elapsed} seconds") % { uri: uri, elapsed: elapsed(start) }, e, connected)
rescue EOFError => e
  raise_error(_("Request to %{uri} interrupted after %{elapsed} seconds") % { uri: uri, elapsed: elapsed(start) }, e, connected)
rescue Puppet::SSL::SSLError
  raise
rescue Puppet::HTTP::HTTPError
  raise
rescue => e
  raise_error(_("Request to %{uri} failed after %{elapsed} seconds: %{message}") %
              { uri: uri, elapsed: elapsed(start), message: e.message }, e, connected)
end

#create_sessionPuppet::HTTP::Session

Create a new HTTP session. A session is the object through which services may be connected to and accessed.

Returns:



123
124
125
# File 'lib/puppet/http/client.rb', line 123

def create_session
  Puppet::HTTP::Session.new(self, build_resolvers)
end

#default_ssl_contextObject



310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/puppet/http/client.rb', line 310

def default_ssl_context
  cert = Puppet::X509::CertProvider.new
  password = cert.load_private_key_password

  ssl = Puppet::SSL::SSLProvider.new
  ctx = ssl.load_context(certname: Puppet[:certname], password: password)
  ssl.print(ctx)
  ctx
rescue => e
  # TRANSLATORS: `message` is an already translated string of why SSL failed to initialize
  Puppet.log_exception(e, _("Failed to initialize SSL: %{message}") % { message: e.message })
  # TRANSLATORS: `puppet agent -t` is a command and should not be translated
  Puppet.err(_("Run `puppet agent -t`"))
  raise e
end

#delete(url, headers: {}, params: {}, options: {}) ⇒ Puppet::HTTP::Response

Submits a DELETE HTTP request to the given url.

Parameters:

  • url (URI)

    the location to submit the http request

  • headers (Hash) (defaults to: {})

    merged with the default headers defined by the client

  • params (Hash) (defaults to: {})

    encoded and set as the url query

  • options (Hash) (defaults to: {})

    HTTP request options. Options not recognized by the HTTP implementation will be ignored.

Options Hash (options:):

  • :ssl_context (Puppet::SSL::SSLContext) — default: nil

    ssl context to be used for connections

  • :include_system_store (Boolean) — default: false

    if we should include the system store for connection

  • :redirect_limit (Integer) — default: 10

    The maximum number of HTTP redirections to allow for this request.

  • :basic_auth (Hash)

    A map of ‘:username` => `String` and `:password` => `String`

  • :metric_id (String)

    The metric id used to track metrics on requests.

Returns:



291
292
293
294
295
296
297
# File 'lib/puppet/http/client.rb', line 291

def delete(url, headers: {}, params: {}, options: {})
  url = encode_query(url, params)

  request = Net::HTTP::Delete.new(url, @default_headers.merge(headers))

  execute_streaming(request, options: options)
end

#get(url, headers: {}, params: {}, options: {}) {|Puppet::HTTP::Response| ... } ⇒ Puppet::HTTP::Response

Submits a GET HTTP request to the given url

Parameters:

  • url (URI)

    the location to submit the http request

  • headers (Hash) (defaults to: {})

    merged with the default headers defined by the client

  • params (Hash) (defaults to: {})

    encoded and set as the url query

  • options (Hash) (defaults to: {})

    HTTP request options. Options not recognized by the HTTP implementation will be ignored.

Options Hash (options:):

  • :ssl_context (Puppet::SSL::SSLContext) — default: nil

    ssl context to be used for connections

  • :include_system_store (Boolean) — default: false

    if we should include the system store for connection

  • :redirect_limit (Integer) — default: 10

    The maximum number of HTTP redirections to allow for this request.

  • :basic_auth (Hash)

    A map of ‘:username` => `String` and `:password` => `String`

  • :metric_id (String)

    The metric id used to track metrics on requests.

Yields:

Returns:



199
200
201
202
203
204
205
# File 'lib/puppet/http/client.rb', line 199

def get(url, headers: {}, params: {}, options: {}, &block)
  url = encode_query(url, params)

  request = Net::HTTP::Get.new(url, @default_headers.merge(headers))

  execute_streaming(request, options: options, &block)
end

#head(url, headers: {}, params: {}, options: {}) ⇒ Puppet::HTTP::Response

Submits a HEAD HTTP request to the given url

Parameters:

  • url (URI)

    the location to submit the http request

  • headers (Hash) (defaults to: {})

    merged with the default headers defined by the client

  • params (Hash) (defaults to: {})

    encoded and set as the url query

  • options (Hash) (defaults to: {})

    HTTP request options. Options not recognized by the HTTP implementation will be ignored.

Options Hash (options:):

  • :ssl_context (Puppet::SSL::SSLContext) — default: nil

    ssl context to be used for connections

  • :include_system_store (Boolean) — default: false

    if we should include the system store for connection

  • :redirect_limit (Integer) — default: 10

    The maximum number of HTTP redirections to allow for this request.

  • :basic_auth (Hash)

    A map of ‘:username` => `String` and `:password` => `String`

  • :metric_id (String)

    The metric id used to track metrics on requests.

Returns:



217
218
219
220
221
222
223
# File 'lib/puppet/http/client.rb', line 217

def head(url, headers: {}, params: {}, options: {})
  url = encode_query(url, params)

  request = Net::HTTP::Head.new(url, @default_headers.merge(headers))

  execute_streaming(request, options: options)
end

#post(url, body, headers: {}, params: {}, options: {}) {|Puppet::HTTP::Response| ... } ⇒ Puppet::HTTP::Response

Submits a POST HTTP request to the given url

Parameters:

  • url (URI)

    the location to submit the http request

  • body (String)

    the body of the POST request

  • headers (Hash) (defaults to: {})

    merged with the default headers defined by the client. The ‘Content-Type` header is required and should correspond to the type of data passed as the `body` argument.

  • params (Hash) (defaults to: {})

    encoded and set as the url query

  • options (Hash) (defaults to: {})

    HTTP request options. Options not recognized by the HTTP implementation will be ignored.

Options Hash (options:):

  • :ssl_context (Puppet::SSL::SSLContext) — default: nil

    ssl context to be used for connections

  • :include_system_store (Boolean) — default: false

    if we should include the system store for connection

  • :redirect_limit (Integer) — default: 10

    The maximum number of HTTP redirections to allow for this request.

  • :basic_auth (Hash)

    A map of ‘:username` => `String` and `:password` => `String`

  • :metric_id (String)

    The metric id used to track metrics on requests.

Yields:

Returns:

Raises:

  • (ArgumentError)


267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/puppet/http/client.rb', line 267

def post(url, body, headers: {}, params: {}, options: {}, &block)
  raise ArgumentError, "'post' requires a string 'body' argument" unless body.is_a?(String)

  url = encode_query(url, params)

  request = Net::HTTP::Post.new(url, @default_headers.merge(headers))
  request.body = body
  request.content_length = body.bytesize

  raise ArgumentError, "'post' requires a 'content-type' header" unless request['Content-Type']

  execute_streaming(request, options: options, &block)
end

#put(url, body, headers: {}, params: {}, options: {}) ⇒ Puppet::HTTP::Response

Submits a PUT HTTP request to the given url

Parameters:

  • url (URI)

    the location to submit the http request

  • body (String)

    the body of the PUT request

  • headers (Hash) (defaults to: {})

    merged with the default headers defined by the client. The ‘Content-Type` header is required and should correspond to the type of data passed as the `body` argument.

  • params (Hash) (defaults to: {})

    encoded and set as the url query

  • options (Hash) (defaults to: {})

    HTTP request options. Options not recognized by the HTTP implementation will be ignored.

Options Hash (options:):

  • :ssl_context (Puppet::SSL::SSLContext) — default: nil

    ssl context to be used for connections

  • :include_system_store (Boolean) — default: false

    if we should include the system store for connection

  • :redirect_limit (Integer) — default: 10

    The maximum number of HTTP redirections to allow for this request.

  • :basic_auth (Hash)

    A map of ‘:username` => `String` and `:password` => `String`

  • :metric_id (String)

    The metric id used to track metrics on requests.

Returns:

Raises:

  • (ArgumentError)


238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/puppet/http/client.rb', line 238

def put(url, body, headers: {}, params: {}, options: {})
  raise ArgumentError, "'put' requires a string 'body' argument" unless body.is_a?(String)

  url = encode_query(url, params)

  request = Net::HTTP::Put.new(url, @default_headers.merge(headers))
  request.body = body
  request.content_length = body.bytesize

  raise ArgumentError, "'put' requires a 'content-type' header" unless request['Content-Type']

  execute_streaming(request, options: options)
end