puppet-runner 0.0.18 → 0.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.travis.yml +5 -1
- data/README.md +272 -1
- data/bin/puppet-runner +224 -11
- data/lib/avst/string_ext.rb +7 -0
- data/puppet-runner.gemspec +2 -2
- metadata +8 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9608033a0b9eb159ab03971282ba58f6f66048dca66b7dd010d6ba8eeb19a8c1
|
4
|
+
data.tar.gz: 10fa57c840b21b088c9e36c6d4415de4c90f260da7aacf4a9afb50bde6c8d420
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0b5a94a383db7ed9236316a79ec462af20e4de9d71d023342d44ec4dedb52bfb99d5a3f5fd7c78f9e41a488cfbfe1652c7a8809bc9ab59affe57fe90c619e9d8
|
7
|
+
data.tar.gz: 87767da63ef9b57dd99de76bf5946e258be53c8f62d3d5502b4c14b0007de762625f401f7bfe2afede26346ef5c1c53f461fb73411ecf4814dcf8c9442bd9f14
|
data/.travis.yml
CHANGED
@@ -1,4 +1,8 @@
|
|
1
1
|
language: ruby
|
2
|
+
before_install:
|
3
|
+
# https://github.com/travis-ci/travis-rubies/issues/57#issuecomment-458981237
|
4
|
+
- "find /home/travis/.rvm/rubies -wholename '*default/bundler-*.gemspec' -delete"
|
5
|
+
- gem install bundler --version 2.2.10
|
2
6
|
notifications:
|
3
7
|
email: false
|
4
8
|
hipchat:
|
@@ -7,7 +11,7 @@ notifications:
|
|
7
11
|
template:
|
8
12
|
- 'Gem - %{repository} #%{build_number} (%{build_url}) by %{author}: %{message}'
|
9
13
|
rvm:
|
10
|
-
- 2.
|
14
|
+
- 2.4.0
|
11
15
|
deploy:
|
12
16
|
provider: rubygems
|
13
17
|
api_key:
|
data/README.md
CHANGED
@@ -8,7 +8,7 @@ Executable gem that composes hiera configuration and facts and execute puppet ap
|
|
8
8
|
|
9
9
|
## Usage
|
10
10
|
|
11
|
-
* puppet-runner (prepare|all) [-c CONFIG_DIR] [-t TEMPLATES] [-d DESTINATION_DIR] [-f FACTS_DEST] [-s SERVERNAME] [-r PUPPETFILE_CONFIG] [-o PUPPETFILE_OUTPUT_PATH] [-e EYAML_KEY_PATH]
|
11
|
+
* puppet-runner (prepare|all) [-c CONFIG_DIR] [-t TEMPLATES] [-d DESTINATION_DIR] [-f FACTS_DEST] [-s SERVERNAME] [-r PUPPETFILE_CONFIG] [-o PUPPETFILE_OUTPUT_PATH] [-e EYAML_KEY_PATH] [-x CUSTOM_FACTS_DIR]
|
12
12
|
* puppet-runner start [-p PUPPET_APPLY]
|
13
13
|
* puppet-runner -h | --help
|
14
14
|
|
@@ -101,6 +101,271 @@ With this setup we define prefix -> substitution_string pair for each prefix in
|
|
101
101
|
|
102
102
|
"hostname"_facts.yaml - must contain all prefixed custom facts to customize the setup
|
103
103
|
|
104
|
+
### Fact Metadata
|
105
|
+
|
106
|
+
The defaults file contains all the facts (variables) that are needed to run the tempalte, the basic format of this is:
|
107
|
+
```
|
108
|
+
fact_name: 'fact_value'
|
109
|
+
```
|
110
|
+
|
111
|
+
So in this instance fact_name will have a default value of fact_value, which can be overiden by the user in their own facts file.
|
112
|
+
|
113
|
+
However it is also possible to add metadata to the facts by changing the format, the current metadata is `comment` and `type`, this extened format looks like:
|
114
|
+
|
115
|
+
```
|
116
|
+
fact_name:
|
117
|
+
value: 'fact_value'
|
118
|
+
comment: 'this is an important fact'
|
119
|
+
type: 'string'
|
120
|
+
```
|
121
|
+
|
122
|
+
The two metadata values are:
|
123
|
+
`comment` - This is a description for the fact to help identify what it is for, it is added above the fact in the reultant facts files written by puppet-runner
|
124
|
+
`type` - This identifies the data type of the fact, by default if this meatadata is not set the value defaults to `string`, current valid types are:
|
125
|
+
**string**
|
126
|
+
**boolean**
|
127
|
+
**nilable**
|
128
|
+
|
129
|
+
### Fact Types
|
130
|
+
|
131
|
+
As mentioned above it is possible to assign a "type" metadata element to each fact, the reasons is that facter delivers all its data as a string, this can cause issues so the "type" metadata allows puppet-runner to do something special for other data typs:
|
132
|
+
|
133
|
+
#### string
|
134
|
+
|
135
|
+
All data is passed by default as a string, setting the type to string does not do anythign special
|
136
|
+
|
137
|
+
#### boolean
|
138
|
+
|
139
|
+
If the type is set to boolean this tells puppet-runner that we want to pass in true boolean values, a conversion from the string value into true boolean is attempted. If the conversions is successfull the fact reference in the final compiled hiera document (/etc/puppet/hiera/<HOSTNAME>.eyaml) would be replaced with the actual boolean value so that puppet does not recive its string representation.
|
140
|
+
|
141
|
+
Example:
|
142
|
+
|
143
|
+
Template
|
144
|
+
```
|
145
|
+
---
|
146
|
+
|
147
|
+
prefixes:
|
148
|
+
- yum_
|
149
|
+
|
150
|
+
classes:
|
151
|
+
- yum
|
152
|
+
|
153
|
+
dependencies:
|
154
|
+
- yum
|
155
|
+
- puppi
|
156
|
+
|
157
|
+
yum::defaultrepo: "%{::yum_defaultrepo}"
|
158
|
+
```
|
159
|
+
|
160
|
+
If the defaults does not use metadata (or uses a type of string) and the facts are set as below
|
161
|
+
```
|
162
|
+
---
|
163
|
+
|
164
|
+
yum_defaultrepo: "true"
|
165
|
+
```
|
166
|
+
|
167
|
+
Then the value writen into /etc/puppet/hiera/<HOSTNAME>.eyaml would be
|
168
|
+
|
169
|
+
```
|
170
|
+
yum::defaultrepo: "%{::yum_defaultrepo}"
|
171
|
+
```
|
172
|
+
|
173
|
+
Whereas if the default was set to boolean as below:
|
174
|
+
```
|
175
|
+
---
|
176
|
+
|
177
|
+
yum_defaultrepo:
|
178
|
+
value: "true"
|
179
|
+
type: "boolean"
|
180
|
+
```
|
181
|
+
|
182
|
+
Then the value writen into /etc/puppet/hiera/<HOSTNAME>.eyaml would be
|
183
|
+
|
184
|
+
```
|
185
|
+
yum::defaultrepo: true
|
186
|
+
```
|
187
|
+
|
188
|
+
#### nilable
|
189
|
+
|
190
|
+
If the type is set to nilable this tells puppet-runner that we want to pass in a nil/undef (null) value instead of an empty string, if the fact value is blank '' it will be converted into a tilda (~) as this is the nil representation in hiera. If the conversions is successfull the fact reference in the final compiled hiera document (/etc/puppet/hiera/<HOSTNAME>.eyaml) would be replaced with a tilda. Please note this only works for puppet variables that are defaulted in the code to undef, if they have a value passing nil to them will result in the code default still being set
|
191
|
+
|
192
|
+
Example:
|
193
|
+
|
194
|
+
Template
|
195
|
+
```
|
196
|
+
---
|
197
|
+
classes:
|
198
|
+
- artifactory
|
199
|
+
|
200
|
+
|
201
|
+
artifactory::conf:
|
202
|
+
tarball_location_file: "%{::artifactory_file_location}"
|
203
|
+
tarball_location_url: "%{::artifactory_url_location}"
|
204
|
+
.........
|
205
|
+
```
|
206
|
+
|
207
|
+
If the defaults does not use metadata (or uses a type of string) and the facts are set as below
|
208
|
+
```
|
209
|
+
---
|
210
|
+
|
211
|
+
artifactory_file_location: '/tmp/file.zip'
|
212
|
+
artifactory_url_location:
|
213
|
+
```
|
214
|
+
|
215
|
+
They are therefore both mandatory fileds and puppet-runner will ask for you to give a value for both, however in reality these are mutually exclusive, one will take precident over the other so passing them both could have unforseen consequences.
|
216
|
+
|
217
|
+
The other otpion is to set the facts to be empty string, however this still passed an empty string into puppet which, unless the code has been written to discount that, could still cause issues:
|
218
|
+
```
|
219
|
+
artifactory_file_location: '/tmp/file.zip'
|
220
|
+
artifactory_url_location: ''
|
221
|
+
```
|
222
|
+
|
223
|
+
Then the value writen into /etc/puppet/hiera/<HOSTNAME>.eyaml would be
|
224
|
+
|
225
|
+
```
|
226
|
+
artifactory::conf:
|
227
|
+
tarball_location_file: "%{::artifactory_file_location}"
|
228
|
+
tarball_location_url: "%{::artifactory_url_location}"
|
229
|
+
```
|
230
|
+
|
231
|
+
Whereas if the metadata type was set to nilable and a value of '' (empty string is supplied) in the defaults
|
232
|
+
```
|
233
|
+
---
|
234
|
+
|
235
|
+
artifactory_file_location:
|
236
|
+
value: ''
|
237
|
+
comment: 'blah'
|
238
|
+
type: 'nilable'
|
239
|
+
artifactory_url_location:
|
240
|
+
value: ''
|
241
|
+
comment: 'other blah'
|
242
|
+
type: 'nilable'
|
243
|
+
```
|
244
|
+
|
245
|
+
And the facts were set with a value for one and empty string for the other as below:
|
246
|
+
```
|
247
|
+
artifactory_file_location: '/tmp/file.zip'
|
248
|
+
artifactory_url_location: ''
|
249
|
+
```
|
250
|
+
|
251
|
+
Then the value writen into /etc/puppet/hiera/<HOSTNAME>.eyaml would be
|
252
|
+
|
253
|
+
```
|
254
|
+
artifactory::conf:
|
255
|
+
tarball_location_file: "%{::artifactory_file_location}"
|
256
|
+
tarball_location_url: ~
|
257
|
+
```
|
258
|
+
|
259
|
+
### Inter fact references
|
260
|
+
|
261
|
+
There are a number of occasions where you may want one fact to point to the value of another, either because you want it to be exactly the same or you want your fact to be a superset of the other, puppet-runner will evaluate facts that reference another fact and present the last fact reference to puppet, the resolution will recursivly resolve facts down to their base fact to a max depth of 5, after which it will stop in order to prevent infinite loops, this will cause a failure of the fact lookup.
|
262
|
+
|
263
|
+
#### Example 1: Fact directly referneces another fact
|
264
|
+
|
265
|
+
In this example we want our fact to reference another, in this example we will point a custom fact at a system fact (although you can point it at any fact, system or custom)
|
266
|
+
|
267
|
+
**template:**
|
268
|
+
````
|
269
|
+
---
|
270
|
+
|
271
|
+
prefixes:
|
272
|
+
- vpn_snat_
|
273
|
+
|
274
|
+
classes:
|
275
|
+
- fw
|
276
|
+
|
277
|
+
dependencies:
|
278
|
+
- fw
|
279
|
+
- firewall
|
280
|
+
- stdlib
|
281
|
+
|
282
|
+
fw::rules: &fw_rules
|
283
|
+
"%{::vpn_snat_description}":
|
284
|
+
chain: "%{::vpn_snat_chain}"
|
285
|
+
tosource: "%{::vpn_snat_tosource}"
|
286
|
+
jump: "%{::vpn_snat_jump}"
|
287
|
+
source: "%{::vpn_snat_source}"
|
288
|
+
table: "%{::vpn_snat_table}"
|
289
|
+
proto: "%{::vpn_snat_proto}"
|
290
|
+
````
|
291
|
+
|
292
|
+
We want the `tosource` value to be set with the IP address for the servers eth0 adapter, there is already a system fact for this `ipaddress_eth0`
|
293
|
+
|
294
|
+
We set the facts as below:
|
295
|
+
|
296
|
+
````
|
297
|
+
vpn_snat_description: '000 VPN SNAT Configuration'
|
298
|
+
vpn_snat_chain: 'POSTROUTING'
|
299
|
+
vpn_snat_tosource: "%{::ipaddress_eth0}"
|
300
|
+
vpn_snat_jump: 'SNAT'
|
301
|
+
vpn_snat_source: '172.28.254.0/23'
|
302
|
+
vpn_snat_table: 'nat'
|
303
|
+
vpn_snat_proto: 'all'
|
304
|
+
````
|
305
|
+
|
306
|
+
Pupet-runner will resolve the `vpn_snat_tosource` faqt down to the first fact it references, which is `ipaddress_eth0`, as a result the value writen into /etc/puppet/hiera/<HOSTNAME>.eyaml would be
|
307
|
+
|
308
|
+
````
|
309
|
+
fw::rules: &fw_rules
|
310
|
+
"%{::vpn_snat_description}":
|
311
|
+
chain: "%{::vpn_snat_chain}"
|
312
|
+
tosource: "%{::ipaddress_eth0}"
|
313
|
+
jump: "%{::vpn_snat_jump}"
|
314
|
+
source: "%{::vpn_snat_source}"
|
315
|
+
table: "%{::vpn_snat_table}"
|
316
|
+
proto: "%{::vpn_snat_proto}"
|
317
|
+
````
|
318
|
+
|
319
|
+
#### Example 2: Fact includes another fact as part of its value
|
320
|
+
|
321
|
+
In this example we want our fact to be a superset of another fact.
|
322
|
+
|
323
|
+
**template:**
|
324
|
+
````
|
325
|
+
---
|
326
|
+
|
327
|
+
prefixes:
|
328
|
+
- tripwire_
|
329
|
+
|
330
|
+
classes:
|
331
|
+
- tripwire
|
332
|
+
|
333
|
+
dependencies:
|
334
|
+
- tripwire
|
335
|
+
- stdlib
|
336
|
+
- concat
|
337
|
+
|
338
|
+
tripwire::local_passphrase: '%{::tripwire_local_passphrase}'
|
339
|
+
tripwire::site_passphrase: '%{::tripwire_site_passphrase}'
|
340
|
+
tripwire::tripwire_email: '%{::tripwire_tripwire_email}'
|
341
|
+
tripwire::tripwire_policy_file: '%{::tripwire_tripwire_policy_file}'
|
342
|
+
````
|
343
|
+
|
344
|
+
We want the `site_passphrase` value to be the same as `local_passphrase` but with _LOCAL at the end
|
345
|
+
|
346
|
+
We set the facts as below:
|
347
|
+
|
348
|
+
````
|
349
|
+
tripwire_global_passphrase: 'super_secret'
|
350
|
+
tripwire_local_passphrase: "%{::tripwire_site_passphrase}_LOCAL"
|
351
|
+
tripwire_site_passphrase: "%{::tripwire_global_passphrase}
|
352
|
+
tripwire_tripwire_email: 'blackhole'
|
353
|
+
tripwire_tripwire_policy_file: 'false'
|
354
|
+
````
|
355
|
+
|
356
|
+
Note here we have added a custom fact that is not references in any template or default, this will still be avaliable via facter in the normal way.
|
357
|
+
|
358
|
+
The fact `tripwire_site_passphrase` will resolve down to `tripwire_global_passphrase` as in the previous example, however the fact `tripwire_local_passphrase` will be resolved twice (once to `tripwire_site_passphrase` and then again down to `tripwire_global_passphrase`)
|
359
|
+
|
360
|
+
As a result the value writen into /etc/puppet/hiera/<HOSTNAME>.eyaml would be
|
361
|
+
|
362
|
+
````
|
363
|
+
tripwire::local_passphrase: '%{::tripwire_global_passphrase}_LOCAL'
|
364
|
+
tripwire::site_passphrase: '%{::tripwire_global_passphrase}'
|
365
|
+
tripwire::tripwire_email: '%{::tripwire_tripwire_email}'
|
366
|
+
tripwire::tripwire_policy_file: '%{::tripwire_tripwire_policy_file}'
|
367
|
+
````
|
368
|
+
|
104
369
|
#### TEMPLATES
|
105
370
|
Must contain 2 subdirectories.
|
106
371
|
- templates - template yaml files
|
@@ -151,10 +416,16 @@ Path to output Puppetfile.
|
|
151
416
|
* -d DESTINATION_DIR --dest_dir DESTINATION_DIR Directory for result hiera config.
|
152
417
|
* -t TEMPLATES --templates TEMPLATES Directory containing templates and defaults folder with functionality templates and default facts
|
153
418
|
* -f FACTS_DEST --facts_dest_dir FACTS_DEST Destination directory to store result facts
|
419
|
+
* -x CUSTOM_FACTS_DIR --custom_facts_dir CUSTOM_FACTS_DIR Directory containing yaml files with custom facts that will be merged with ones from <hostname>_facts.yaml, custom facts can overwrite them
|
154
420
|
* -r PUPPETFILE_CONFIG --puppetfile_config puppetfile_config Puppetfile composition config file
|
155
421
|
* -o PUPPETFILE_OUTPUT_PATH --puppetfile_output_path PUPPETFILE_OUTPUT_PATH Result Puppetfile path
|
156
422
|
* -e EYAML_KEY_PATH --eyaml_key_pair EYAML_KEY_PATH Path to eyaml encryption key pair
|
157
423
|
* -p PUPPET_APPLY --puppet_apply PUPPET_APPLY Custom puppet apply command to run
|
424
|
+
* -k --keep-facts Flag to keep the encrypted facts file in /tmp for analysis
|
425
|
+
* -n --dry-run Flag to indicate puppet should run in dry run mode (--noop), this also sets the verbose flag to true
|
426
|
+
* -v --verbose Flag to indicate that all output from puppet apply should be displayed instead of just stdout
|
427
|
+
Commands:
|
428
|
+
|
158
429
|
|
159
430
|
Commands:
|
160
431
|
|
data/bin/puppet-runner
CHANGED
@@ -21,13 +21,14 @@ require 'deep_merge'
|
|
21
21
|
require 'docopt'
|
22
22
|
require 'colorize'
|
23
23
|
require 'facter'
|
24
|
+
require 'avst/string_ext'
|
24
25
|
|
25
26
|
doc = <<DOCOPT
|
26
27
|
Adaptavist puppet runner
|
27
28
|
|
28
29
|
Usage:
|
29
|
-
puppet-runner (prepare|all) [-c CONFIG_DIR] [-t TEMPLATES] [-d DESTINATION_DIR] [-f FACTS_DEST] [-s SERVERNAME] [-p PUPPET_APPLY] [-r PUPPETFILE_CONFIG] [-o PUPPETFILE_OUTPUT_PATH] [-e EYAML_KEY_PATH]
|
30
|
-
puppet-runner start [-p PUPPET_APPLY]
|
30
|
+
puppet-runner (prepare|all) [-c CONFIG_DIR] [-t TEMPLATES] [-d DESTINATION_DIR] [-f FACTS_DEST] [-s SERVERNAME] [-p PUPPET_APPLY] [-r PUPPETFILE_CONFIG] [-o PUPPETFILE_OUTPUT_PATH] [-e EYAML_KEY_PATH] [-m MODULE_PATH] [-x CUSTOM_FACTS_DIR] [-k] [-n] [-v]
|
31
|
+
puppet-runner start [-p PUPPET_APPLY] [-m MODULE_PATH] [-n] [-v]
|
31
32
|
puppet-runner -h | --help
|
32
33
|
|
33
34
|
Options:
|
@@ -37,10 +38,15 @@ Options:
|
|
37
38
|
-d DESTINATION_DIR --dest_dir DESTINATION_DIR Directory for result hiera config.
|
38
39
|
-t TEMPLATES --templates TEMPLATES Directory containing templates and defaults folder with functionality templates and default facts
|
39
40
|
-f FACTS_DEST --facts_dest_dir FACTS_DEST Destination directory to store result facts
|
41
|
+
-x CUSTOM_FACTS_DIR --custom_facts_dir CUSTOM_FACTS_DIR Directory containing yaml files with custom facts that will be merged with ones from <hostname>_facts.yaml, custom facts can overwrite them
|
42
|
+
-m MODULE_PATH --module_path MODULE_PATH Path to find puppet modules, can be colon (:) delimited
|
40
43
|
-p PUPPET_APPLY --puppet_apply PUPPET_APPLY Custom puppet apply command to run
|
41
44
|
-r PUPPETFILE_CONFIG --puppetfile_config puppetfile_config Puppetfile composition config file
|
42
45
|
-o PUPPETFILE_OUTPUT_PATH --puppetfile_output_path PUPPETFILE_OUTPUT_PATH Result Puppetfile path
|
43
46
|
-e EYAML_KEY_PATH --eyaml_key_path EYAML_KEY_PATH Path to eyaml encryption key pair
|
47
|
+
-k --keep-facts Flag to keep the encrypted facts file in /tmp for analysis
|
48
|
+
-n --dry-run Flag to indicate puppet should run in dry run mode (--noop), this also sets the verbose flag to true
|
49
|
+
-v --verbose Flag to indicate that all output from puppet apply should be displayed instead of just stdout
|
44
50
|
Commands:
|
45
51
|
all Runs the following commands prepare, start
|
46
52
|
start Runs puppet apply
|
@@ -95,6 +101,45 @@ def extract_comment_from_hash(input)
|
|
95
101
|
res
|
96
102
|
end
|
97
103
|
|
104
|
+
def extract_type_from_hash(input)
|
105
|
+
res = {}
|
106
|
+
if input
|
107
|
+
res = input.map{|key, val|
|
108
|
+
if val != nil
|
109
|
+
type = 'string'
|
110
|
+
if val.is_a?(Hash) and val["type"] != nil
|
111
|
+
type = val["type"]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
{type => key }
|
115
|
+
}
|
116
|
+
end
|
117
|
+
res
|
118
|
+
end
|
119
|
+
|
120
|
+
# function to resolve inter fact references, it should return the last fact name (not value)
|
121
|
+
def resolve_fact(fact_name, max_depth=5, current_depth=0)
|
122
|
+
final_val = nil
|
123
|
+
fact_name = fact_name.sub(/%{::/,'').sub(/}/,'')
|
124
|
+
if max_depth == current_depth
|
125
|
+
warning "Fact resolution has reached a depth of 5 facts, aborting lookup"
|
126
|
+
else
|
127
|
+
# attempt to get value
|
128
|
+
val = $all_facts[fact_name] || Facter.value(fact_name) || nil
|
129
|
+
|
130
|
+
if val == "%{::#{fact_name}}"
|
131
|
+
warning "Fact resolves to itself, skipping"
|
132
|
+
elsif val.instance_of?(String) and val.match(/%{::.*.}/)
|
133
|
+
val = resolve_fact(val,max_depth, current_depth + 1)
|
134
|
+
else
|
135
|
+
val = "%{::#{fact_name}}"
|
136
|
+
end
|
137
|
+
final_val = val
|
138
|
+
end
|
139
|
+
final_val
|
140
|
+
end
|
141
|
+
|
142
|
+
|
98
143
|
begin
|
99
144
|
options = Docopt::docopt(doc)
|
100
145
|
rescue Docopt::Exit => e
|
@@ -102,17 +147,21 @@ rescue Docopt::Exit => e
|
|
102
147
|
end
|
103
148
|
|
104
149
|
stop_apply = false
|
150
|
+
keep_facts = false
|
105
151
|
|
106
152
|
if options['all'] || options['prepare']
|
107
153
|
input_dir = options["--config_dir"] || options["-c"]
|
108
154
|
dest_dir = options["--dest_dir"] || options["-d"]
|
109
155
|
facts_dest_dir = options["--facts_dest_dir"] || options["-f"]
|
156
|
+
custom_facts_dir = options["--custom_facts_dir"] || options["-x"] || nil
|
110
157
|
templates = options["--templates"] || options["-t"]
|
111
158
|
puppetfile_config_path = options["--puppetfile_config"] || options["-r"]
|
112
159
|
puppetfile_output_path = options["--puppetfile_output_path"] || options["-o"]
|
113
160
|
eyaml_key_path = options["--eyaml_key_path"] || options["-e"] || "/etc/puppet/config"
|
114
161
|
hostname = options["--servername"] || options["-s"] || Facter.value("hostname")
|
115
162
|
puts "Hostname #{hostname}"
|
163
|
+
keep_facts = true if options["-k"] or options["--keep-facts"]
|
164
|
+
|
116
165
|
|
117
166
|
config_file_path = path_join_glob(input_dir, hostname+".yaml")
|
118
167
|
templates_dir = path_join_glob(templates, "templates")
|
@@ -150,6 +199,8 @@ if options['all'] || options['prepare']
|
|
150
199
|
prefixed_facts_comments = {}
|
151
200
|
puppetfile_config = YAML.load_file(puppetfile_config_path) || {}
|
152
201
|
puppetfile_dependencies = []
|
202
|
+
global_data_types = []
|
203
|
+
nil_transform_present = false
|
153
204
|
# functionalities:
|
154
205
|
# # In honor of Henry...
|
155
206
|
# 1_app:
|
@@ -189,6 +240,9 @@ if options['all'] || options['prepare']
|
|
189
240
|
|
190
241
|
fact_comments_as_string = extract_comment_from_hash(default_facts).to_s
|
191
242
|
|
243
|
+
# get a list of each fields type if set (if not they will report as strings)
|
244
|
+
data_types = extract_type_from_hash(default_facts)
|
245
|
+
|
192
246
|
if to_add.is_a?(Hash)
|
193
247
|
# if prefixes are not defined skip replacing
|
194
248
|
if prefixes
|
@@ -205,6 +259,13 @@ if options['all'] || options['prepare']
|
|
205
259
|
facts_as_string = facts_as_string.gsub(/#{prefix}/, "#{replace_prefixes_with}")
|
206
260
|
fact_comments_as_string = fact_comments_as_string.gsub(/#{prefix}/, "#{replace_prefixes_with}")
|
207
261
|
prefixed_required_facts = prefixed_required_facts.merge(required_facts.map! { |item| item.gsub(/#{prefix}/, "#{replace_prefixes_with}") })
|
262
|
+
if !data_types.empty?
|
263
|
+
data_types.each do | transform |
|
264
|
+
transform.each do|key, value|
|
265
|
+
transform[key] = value.gsub(/#{prefix}/, "#{replace_prefixes_with}")
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
208
269
|
end
|
209
270
|
end
|
210
271
|
end
|
@@ -217,6 +278,13 @@ if options['all'] || options['prepare']
|
|
217
278
|
facts_as_string = facts_as_string.gsub(/#{prefix}/, "#{replace_prefixes_with}")
|
218
279
|
fact_comments_as_string = fact_comments_as_string.gsub(/#{prefix}/, "#{replace_prefixes_with}")
|
219
280
|
prefixed_required_facts = prefixed_required_facts.merge(required_facts.map! { |item| item.gsub(/#{prefix}/, "#{replace_prefixes_with}") })
|
281
|
+
if !data_types.empty?
|
282
|
+
data_types.each do | transform |
|
283
|
+
transform.each do|key, value|
|
284
|
+
transform[key] = value.gsub(/#{prefix}/, "#{replace_prefixes_with}")
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
220
288
|
end
|
221
289
|
end
|
222
290
|
end
|
@@ -231,6 +299,9 @@ if options['all'] || options['prepare']
|
|
231
299
|
default_fact_comments = extract_comment_from_hash(plain_facts)
|
232
300
|
prefixed_required_facts = prefixed_required_facts.merge(required_facts)
|
233
301
|
end
|
302
|
+
# add the "local" data type list to the global one
|
303
|
+
global_data_types.push(*data_types)
|
304
|
+
|
234
305
|
result_template.deep_merge!(template)
|
235
306
|
# default_facts_prefixed is Array of hashes as the result of map, this will create hash from it
|
236
307
|
result_default_facts.merge!(default_facts_prefixed.reduce Hash.new, :merge)
|
@@ -240,20 +311,107 @@ if options['all'] || options['prepare']
|
|
240
311
|
end
|
241
312
|
end
|
242
313
|
end
|
243
|
-
# Write results
|
244
|
-
File.open(output_file_path, 'w+') do |output_file|
|
245
|
-
YAML.dump(result_template, output_file)
|
246
|
-
end
|
247
314
|
custom_facts_path = path_join_glob(input_dir, "#{hostname}_facts.yaml")
|
248
315
|
custom_facts = YAML.load_file(custom_facts_path) || {}
|
316
|
+
|
317
|
+
# add a fact for the localtion of facter
|
318
|
+
custom_facts['facter_file_location'] = output_facts_file_path
|
319
|
+
|
249
320
|
File.open(output_encrypted_facts_file_path, 'w+') do |output_file|
|
250
321
|
output_result_default_facts = result_default_facts.deep_merge!(custom_facts, {:merge_hash_arrays => true}).to_yaml
|
322
|
+
|
323
|
+
# convert final facts to hash for inter fact resolution and also for potential transformations later
|
324
|
+
$all_facts = YAML.load(output_result_default_facts)
|
325
|
+
|
326
|
+
#convert result templat to string
|
327
|
+
result_template = result_template.to_s
|
328
|
+
|
329
|
+
# loop through facts looking for any that reference other facts
|
330
|
+
$all_facts.each do | fact_key, fact_val |
|
331
|
+
if fact_val.instance_of?(String) and fact_val.match(/%{::.*.}/)
|
332
|
+
debug "Fact #{fact_key} references another fact or facts"
|
333
|
+
# find each instance of a fact within the value (it may contain multiple facts
|
334
|
+
# i.e "TEST%{::fact1}TEST%{::fact2}"
|
335
|
+
fact_val.gsub(/}/,"}\n").scan(/%{::.*.}/).each do | fact |
|
336
|
+
# resolve the fact down to its last reference to another fact (not the end value)
|
337
|
+
resolved_fact = resolve_fact(fact)
|
338
|
+
# if the resolved fact name is not the same as the original fact we found referenced
|
339
|
+
# then replace the value in fact_val
|
340
|
+
if !resolved_fact.nil? and resolved_fact != fact
|
341
|
+
fact_val = fact_val.gsub(/#{fact}/,"#{resolved_fact}")
|
342
|
+
end
|
343
|
+
end
|
344
|
+
debug "Attempting to replace fact '#{fact_key}' with value '#{fact_val}' in compiled template"
|
345
|
+
# replace the original fact reference in the template with the resovled value
|
346
|
+
# this is done before global teansformation as they may change the final value again
|
347
|
+
result_template = result_template.gsub(/\"\%{::#{fact_key}}\"/, "\"#{fact_val}\"")
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
# add comments above any fact lines
|
251
352
|
prefixed_facts_comments.each do |pattern, replacement|
|
252
353
|
if replacement != nil
|
253
354
|
output_result_default_facts.gsub!(/^#{pattern}/, "\##{replacement}\n#{pattern}")
|
254
355
|
end
|
255
356
|
end
|
256
|
-
|
357
|
+
|
358
|
+
# merge custom facts if parameter provided
|
359
|
+
custom_facts_all = {}
|
360
|
+
if (custom_facts_dir)
|
361
|
+
Dir.glob("#{custom_facts_dir}/*.yaml").sort.each do |custom_facts_file|
|
362
|
+
custom_facts_from_file = YAML.load_file(custom_facts_file) || {}
|
363
|
+
custom_facts_all.merge!(custom_facts_from_file)
|
364
|
+
end
|
365
|
+
end
|
366
|
+
# merge, prefer custom facts
|
367
|
+
merged_all_facts = YAML.load(output_result_default_facts).merge!(custom_facts_all)
|
368
|
+
|
369
|
+
# write the temp encrypted facts file
|
370
|
+
output_file.write(merged_all_facts.to_yaml)
|
371
|
+
|
372
|
+
# now that the merged final facts are present look for any global transformations to apply
|
373
|
+
# global transformations are currently either booleans that need to be expressed directly in
|
374
|
+
# the output file or nilable values that need to be expressed as unquoted tildas (~)
|
375
|
+
if !global_data_types.empty?
|
376
|
+
global_data_types.each do | transform |
|
377
|
+
transform.each do|transform_type, transform_value|
|
378
|
+
begin
|
379
|
+
if transform_type == 'boolean'
|
380
|
+
debug "Attempting to replace boolean value for fact #{transform_value}"
|
381
|
+
# convert the fact to boolean and then back to string during the replace, this allows validation that the fact is actually a boolean
|
382
|
+
result_template = result_template.gsub(/\"\%{::#{transform_value}}\"/, $all_facts[transform_value].to_bool.to_s)
|
383
|
+
elsif transform_type == 'nilable'
|
384
|
+
# replace fact reference with tilda if the value is nil, empty or already a tilda
|
385
|
+
# due to issues with ruby hash values being unquoted we will quote it now and remove the quotes later
|
386
|
+
if $all_facts[transform_value].nil? or $all_facts[transform_value].empty? or $all_facts[transform_value] == "~"
|
387
|
+
debug "Attempting to replace nilable value for fact #{transform_value}"
|
388
|
+
# replace value with tilda
|
389
|
+
result_template = result_template.gsub(/\"\%{::#{transform_value}}\"/, '"~"')
|
390
|
+
# identify that we have made at least one nil transformation
|
391
|
+
nil_transform_present = true
|
392
|
+
end
|
393
|
+
end
|
394
|
+
rescue
|
395
|
+
warning "Unable to convert fact #{transform_value} with value #{all_facts[transform_value]} into #{transform_type}, conversion will be skipped"
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
# convert result_template back to hash
|
403
|
+
result_template = eval(result_template)
|
404
|
+
|
405
|
+
# Write results
|
406
|
+
File.open(output_file_path, 'w+') do |output_file|
|
407
|
+
YAML.dump(result_template, output_file)
|
408
|
+
end
|
409
|
+
|
410
|
+
# hack to get around the fact we have to pass tilda as quoted earlier
|
411
|
+
if nil_transform_present
|
412
|
+
compiled_hiera = File.read(output_file_path)
|
413
|
+
replaced_hiera = compiled_hiera.gsub('"~"', '~')
|
414
|
+
File.open(output_file_path, "w") {|new_hiera| new_hiera.puts replaced_hiera}
|
257
415
|
end
|
258
416
|
|
259
417
|
# decrypt facts file because Puppet doesn't appear to be able to read encrypted facts
|
@@ -298,7 +456,20 @@ if options['all'] || options['prepare']
|
|
298
456
|
end
|
299
457
|
output_file.write(decrypted.join)
|
300
458
|
end
|
459
|
+
|
460
|
+
# unless asked not to, attempt to remove the encrypted facts file
|
461
|
+
if keep_facts
|
462
|
+
debug "Removal of tmp encrypted facts file #{output_encrypted_facts_file_path} skipped at users request"
|
463
|
+
else
|
464
|
+
debug "Attempting to remove tmp encrypted facts file #{output_encrypted_facts_file_path}"
|
465
|
+
begin
|
466
|
+
FileUtils.rm output_encrypted_facts_file_path
|
467
|
+
rescue
|
468
|
+
warning "Unable to remove tmp encrypted facts file #{output_encrypted_facts_file_path}"
|
469
|
+
end
|
470
|
+
end
|
301
471
|
|
472
|
+
# create puppetfile from the dictionary
|
302
473
|
File.open(puppetfile_output_path, 'w+') do |output_file|
|
303
474
|
header = "#!/usr/bin/env ruby\n\n"
|
304
475
|
output_file.write(header)
|
@@ -334,16 +505,58 @@ if options['all'] || options['prepare']
|
|
334
505
|
end
|
335
506
|
|
336
507
|
|
337
|
-
# start puppet
|
508
|
+
# start puppet
|
338
509
|
if (options['start'] || options['all']) && !stop_apply
|
339
510
|
require 'puppet'
|
340
|
-
|
341
|
-
|
511
|
+
|
512
|
+
# set dry run option if the flag has been set
|
513
|
+
if options["-n"] or options["--dry-run"]
|
514
|
+
dry_run = '--noop'
|
515
|
+
verbose_output = true
|
516
|
+
else
|
517
|
+
dry_run = ''
|
518
|
+
end
|
519
|
+
|
520
|
+
# if the user has specified a module path pass it to puppet
|
521
|
+
if options["--module_path"]
|
522
|
+
modulefile_definition = "--modulepath #{options['--module_path']}"
|
523
|
+
elsif options["-m"]
|
524
|
+
modulefile_definition = "--modulepath #{options['-m']}"
|
525
|
+
# if no modulepath has been set default to /etc/puppet/modules for puppet 4 and above and blank for older versions
|
526
|
+
else
|
527
|
+
modulefile_definition = Gem::Version.new(Puppet.version) > Gem::Version.new('4.0.0') ? '--modulepath /etc/puppet/modules' : ''
|
528
|
+
end
|
529
|
+
|
530
|
+
# construct defaut puppet apply command
|
531
|
+
puppet_command = "sudo su -c 'source /usr/local/rvm/scripts/rvm; puppet apply #{dry_run} /etc/puppet/manifests/site.pp --confdir=/etc/puppet --verbose --detailed-exitcodes #{modulefile_definition}'"
|
532
|
+
|
533
|
+
# if a custom puppet apply command has been set use if, otherwise use the default generated above
|
342
534
|
to_execute = options["--puppet_apply"] || options["-p"] || puppet_command
|
343
535
|
debug "Running #{to_execute}"
|
536
|
+
|
537
|
+
# execute puppet apply and capture return code
|
344
538
|
`#{to_execute}`
|
345
539
|
exit_code = $?.exitstatus
|
346
|
-
|
540
|
+
|
541
|
+
# attempt to remove the fact file as its unencrypted (we do not care about exit status)
|
542
|
+
fact_file_location = Facter.value("facter_file_location")
|
543
|
+
if fact_file_location
|
544
|
+
if File.file?(fact_file_location)
|
545
|
+
debug "Attempting to remove fact file #{fact_file_location}"
|
546
|
+
begin
|
547
|
+
FileUtils.rm fact_file_location
|
548
|
+
rescue
|
549
|
+
warning "Unable to remove facts file #{fact_file_location}.\nPlease urgently remove this as it holds unencrypted values"
|
550
|
+
end
|
551
|
+
end
|
552
|
+
else
|
553
|
+
warning "Unable to locate Facts file, please urgently locate and remove this as it holds unencrypted values"
|
554
|
+
end
|
555
|
+
|
556
|
+
# if we see a bad exit code report it, for refrerence good codes are:
|
557
|
+
# 0: The run succeeded with no changes or failures; the system was already in the desired state.
|
558
|
+
# 2: The run succeeded, and some resources were changed.
|
559
|
+
if exit_code != 2 and exit_code != 0
|
347
560
|
raise "execute_puppet exit status: #{exit_code}"
|
348
561
|
end
|
349
562
|
end
|
data/puppet-runner.gemspec
CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = "puppet-runner"
|
7
|
-
spec.version = "0.0.
|
7
|
+
spec.version = "0.0.26"
|
8
8
|
spec.authors = ["Martin Brehovsky", "Matthew Hope"]
|
9
9
|
spec.email = ["mbrehovsky@adaptavist.com"]
|
10
10
|
spec.summary = %q{Preprocessor for hiera config}
|
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
18
|
spec.require_paths = ["lib"]
|
19
19
|
|
20
|
-
spec.add_development_dependency "bundler", "
|
20
|
+
spec.add_development_dependency "bundler", "2.2.10"
|
21
21
|
spec.add_development_dependency "rake"
|
22
22
|
spec.add_dependency "docopt", ">= 0.5.0"
|
23
23
|
spec.add_dependency "colorize", ">= 0.7.3"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: puppet-runner
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.26
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Martin Brehovsky
|
@@ -9,22 +9,22 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2021-05-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
requirements:
|
18
|
-
- -
|
18
|
+
- - '='
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version:
|
20
|
+
version: 2.2.10
|
21
21
|
type: :development
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
|
-
- -
|
25
|
+
- - '='
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version:
|
27
|
+
version: 2.2.10
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: rake
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
@@ -140,6 +140,7 @@ files:
|
|
140
140
|
- README.md
|
141
141
|
- Rakefile
|
142
142
|
- bin/puppet-runner
|
143
|
+
- lib/avst/string_ext.rb
|
143
144
|
- puppet-runner.gemspec
|
144
145
|
- test/configs/defaults/confluence.yaml
|
145
146
|
- test/configs/defaults/connector_proxy.yaml
|
@@ -171,8 +172,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
171
172
|
- !ruby/object:Gem::Version
|
172
173
|
version: '0'
|
173
174
|
requirements: []
|
174
|
-
|
175
|
-
rubygems_version: 2.4.5
|
175
|
+
rubygems_version: 3.0.8
|
176
176
|
signing_key:
|
177
177
|
specification_version: 4
|
178
178
|
summary: Preprocessor for hiera config
|