Skip to content

Commit 313c16c

Browse files
committed
feat: allow to pass additional HTTP headers in puppetdb requests
This can be useful if you want to access PuppetDB via custom authentication. Eg if you want to access PuppetDB behind an API gateway that requires oauth tokens or other custom headers.
1 parent 5590994 commit 313c16c

8 files changed

Lines changed: 161 additions & 5 deletions

File tree

documentation/bolt_connect_puppetdb.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ config](configuring_bolt.md) with the following values:
5050
| --- | --- | --- |
5151
| `cacert` | `String` | The path to the CA certificate for PuppetDB. |
5252
| `connect_timeout` | `Integer` | How long to wait in seconds when establishing connections with PuppetDB. |
53+
| `headers` | `Hash` | A map of HTTP headers to add to PuppetDB requests. |
5354
| `read_timeout` | `Integer` | How long to wait in seconds for a response from PuppetDB. |
5455
| `server_urls` | `Array` | An array of strings containing the PuppetDB host to connect to. Include the protocol `https` and the port, which is usually `8081`. For example, `https://my-puppetdb-server.example.com:8081`. The Bolt PuppetDB client attempts to connect to each host in the list until it makes a successful connection. |
5556

@@ -94,6 +95,16 @@ puppetdb:
9495
token: ~/.puppetlabs/token
9596
```
9697

98+
To use custom headers, such as for OAuth authentication:
99+
100+
```
101+
puppetdb:
102+
server_urls: ["https://puppet.example.com:8081"]
103+
cacert: /etc/puppetlabs/puppet/ssl/certs/ca.pem
104+
headers:
105+
Authorization: "Bearer <token-content>"
106+
```
107+
97108
## Configuring multiple PuppetDB instances
98109

99110
The Bolt PuppetDB Client supports connections to multiple PuppetDB instances. To
@@ -244,15 +255,15 @@ plan puppetdb_query_targets {
244255
# this returns an array of objects, each object containing a "certname" parameter:
245256
# [ {"certname": "node1"}, {"certname": "node2"} ]
246257
$query_results = puppetdb_query("nodes[certname] {}")
247-
258+
248259
# since puppetdb_query() returns the JSON results from the API call, we need to transform this
249260
# data into Targets to use it in one of the run_*() functions.
250261
# extract the "certname" values, so now we have an array of hostnames
251262
$certnames = $query_results.map |$r| { $r['certname'] }
252-
263+
253264
# transform the arary of certnames into an array of Targets
254265
$targets = get_targets($certnames)
255-
266+
256267
# gather facts about all of the nodes
257268
run_task('facts', $targets)
258269
}
@@ -270,10 +281,10 @@ plan puppetdb_plugin_targets {
270281
'query' => 'nodes[certname] {}',
271282
}
272283
$references = resolve_references($refs)
273-
284+
274285
# maps the results into a list of Target objects
275286
$targets = $references.map |$r| { Target.new($r) }
276-
287+
277288
# gather facts about all of the nodes
278289
run_task('facts', $targets)
279290
}

lib/bolt/config/options.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ module Options
7676
_example: 120,
7777
_plugin: true
7878
},
79+
"headers" => {
80+
description: "A map of HTTP headers to add to PuppetDB requests.",
81+
type: Hash,
82+
_example: { "Authorization" => "Bearer <token>" },
83+
_plugin: true
84+
},
7985
"key" => {
8086
description: "The private key for the certificate.",
8187
type: String,

lib/bolt/puppetdb/config.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,14 @@ def key
149149
@settings['key']
150150
end
151151

152+
def headers
153+
if @settings['headers'] && !@settings['headers'].is_a?(Hash)
154+
raise Bolt::PuppetDBError, "headers must be a Hash"
155+
end
156+
157+
@settings['headers']
158+
end
159+
152160
def validate_cert_and_key
153161
if (@settings['cert'] && !@settings['key']) ||
154162
(!@settings['cert'] && @settings['key'])

lib/bolt/puppetdb/instance.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ def uri
139139

140140
def headers
141141
headers = { 'Content-Type' => 'application/json' }
142+
headers.merge!(@config.headers) if @config.headers
142143
headers['X-Authentication'] = @config.token if @config.token
143144
headers
144145
end

schemas/bolt-defaults.schema.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,17 @@
299299
}
300300
]
301301
},
302+
"headers": {
303+
"description": "A map of HTTP headers to add to PuppetDB requests.",
304+
"oneOf": [
305+
{
306+
"type": "object"
307+
},
308+
{
309+
"$ref": "#/definitions/_plugin"
310+
}
311+
]
312+
},
302313
"key": {
303314
"description": "The private key for the certificate.",
304315
"oneOf": [
@@ -395,6 +406,17 @@
395406
}
396407
]
397408
},
409+
"headers": {
410+
"description": "A map of HTTP headers to add to PuppetDB requests.",
411+
"oneOf": [
412+
{
413+
"type": "object"
414+
},
415+
{
416+
"$ref": "#/definitions/_plugin"
417+
}
418+
]
419+
},
398420
"key": {
399421
"description": "The private key for the certificate.",
400422
"oneOf": [

schemas/bolt-project.schema.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,17 @@
429429
}
430430
]
431431
},
432+
"headers": {
433+
"description": "A map of HTTP headers to add to PuppetDB requests.",
434+
"oneOf": [
435+
{
436+
"type": "object"
437+
},
438+
{
439+
"$ref": "#/definitions/_plugin"
440+
}
441+
]
442+
},
432443
"key": {
433444
"description": "The private key for the certificate.",
434445
"oneOf": [
@@ -525,6 +536,17 @@
525536
}
526537
]
527538
},
539+
"headers": {
540+
"description": "A map of HTTP headers to add to PuppetDB requests.",
541+
"oneOf": [
542+
{
543+
"type": "object"
544+
},
545+
{
546+
"$ref": "#/definitions/_plugin"
547+
}
548+
]
549+
},
528550
"key": {
529551
"description": "The private key for the certificate.",
530552
"oneOf": [

spec/unit/puppetdb/config_spec.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,4 +254,25 @@
254254
Bolt::PuppetDB::Config.new(config: {}, load_defaults: true)
255255
end
256256
end
257+
258+
context 'when validating headers' do
259+
let(:options) { { 'server_urls' => ['https://puppetdb:8081'], 'headers' => headers } }
260+
let(:config) { Bolt::PuppetDB::Config.new(config: options) }
261+
262+
context 'with valid headers' do
263+
let(:headers) { { 'Authorization' => 'Bearer token' } }
264+
265+
it 'returns the headers' do
266+
expect(config.headers).to eq(headers)
267+
end
268+
end
269+
270+
context 'with invalid headers' do
271+
let(:headers) { 'Authorization: Bearer token' }
272+
273+
it 'raises an error' do
274+
expect { config.headers }.to raise_error(Bolt::PuppetDBError, "headers must be a Hash")
275+
end
276+
end
277+
end
257278
end
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
require 'bolt/puppetdb/instance'
5+
6+
describe Bolt::PuppetDB::Instance do
7+
let(:token) { 'token' }
8+
let(:config) do
9+
{
10+
'server_urls' => ["https://puppet.example.com:8081"],
11+
'cacert' => '/etc/puppetlabs/puppet/ssl/certs/ca.pem',
12+
'token' => '~/.puppetlabs/token'
13+
}
14+
end
15+
let(:instance) { described_class.new(config: config) }
16+
17+
before(:each) do
18+
allow(File).to receive(:exist?).and_return(true)
19+
allow(File).to receive(:read).and_call_original
20+
allow(File).to receive(:read).with(File.expand_path('~/.puppetlabs/token')).and_return(token)
21+
end
22+
23+
context "#headers" do
24+
it "includes Content-Type" do
25+
expect(instance.headers).to include('Content-Type' => 'application/json')
26+
end
27+
28+
it "includes X-Authentication token" do
29+
expect(instance.headers).to include('X-Authentication' => token)
30+
end
31+
32+
context "with custom headers" do
33+
let(:config) do
34+
{
35+
'server_urls' => ["https://puppet.example.com:8081"],
36+
'headers' => { 'Authorization' => 'Bearer info' }
37+
}
38+
end
39+
40+
it "includes custom headers" do
41+
expect(instance.headers).to include('Authorization' => 'Bearer info')
42+
end
43+
44+
it "does not include X-Authentication if no token" do
45+
# config does not have 'token', so it falls back to DEFAULT_TOKEN.
46+
# We need to simulate no default token file.
47+
allow(File).to receive(:exist?).with(Bolt::PuppetDB::Config::DEFAULT_TOKEN).and_return(false)
48+
expect(instance.headers).not_to have_key('X-Authentication')
49+
end
50+
end
51+
52+
context "with custom headers overlapping Content-Type" do
53+
let(:config) do
54+
{
55+
'server_urls' => ["https://puppet.example.com:8081"],
56+
'headers' => { 'Content-Type' => 'application/x-yaml' }
57+
}
58+
end
59+
60+
it "overrides default Content-Type" do
61+
expect(instance.headers).to include('Content-Type' => 'application/x-yaml')
62+
end
63+
end
64+
end
65+
end

0 commit comments

Comments
 (0)