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


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

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)


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

def pool
  @pool
end

Instance Method Details

#closevoid

This method returns an undefined value.

Close persistent connections in the pool.


301
302
303
304
305
# File 'lib/puppet/http/client.rb', line 301

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


136
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
# File 'lib/puppet/http/client.rb', line 136

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:


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

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

#default_ssl_contextObject


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

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:


288
289
290
291
292
293
294
# File 'lib/puppet/http/client.rb', line 288

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:


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

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:


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

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)

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

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)

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

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