Skip to content

Commit 701df12

Browse files
authored
Merge pull request #175 from TheMeier/puppetdb_header
feat: allow to pass additional HTTP headers in puppetdb requests
2 parents aad10b6 + 37879be commit 701df12

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)