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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 5bbb45769852b12c6bf2b83d523e33df37ae11e9
4
- data.tar.gz: 047aad5ff68eb41b049047c1650f8777acfacc3c
2
+ SHA256:
3
+ metadata.gz: 9608033a0b9eb159ab03971282ba58f6f66048dca66b7dd010d6ba8eeb19a8c1
4
+ data.tar.gz: 10fa57c840b21b088c9e36c6d4415de4c90f260da7aacf4a9afb50bde6c8d420
5
5
  SHA512:
6
- metadata.gz: bcceeb628caf19858c9c8486edad72001f9695d9b8ddafc3da10c9b407e54ce5a4c52812c88af876dee24d416aff200971d6f2851c4fe3b18ce46f0081ecd9ee
7
- data.tar.gz: d03770227658b51eebe1e89ab9d8225185e07bb19448b25d1f102f50f3e9c650b83ed1f4ad777b7d7dcd4b0465a256ffd01d48f71e7764db4d228f6ac109654f
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.0.0
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
- output_file.write(output_result_default_facts)
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
- modulefile_definition = Gem::Version.new(Puppet.version) > Gem::Version.new('4.0.0') ? '--modulepath /etc/puppet/modules' : ''
341
- puppet_command = "sudo su -c 'source /usr/local/rvm/scripts/rvm; puppet apply /etc/puppet/manifests/site.pp --confdir=/etc/puppet --verbose --detailed-exitcodes #{modulefile_definition}'"
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
- if exit_code != 2
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
@@ -0,0 +1,7 @@
1
+ class String
2
+ def to_bool
3
+ return true if self == true || self =~ (/(true|t|yes|y|1)$/i)
4
+ return false if self == false || self.empty? || self.nil? || self =~ (/(false|f|no|n|0)$/i)
5
+ raise ArgumentError.new("invalid value for Boolean: \"#{self}\"")
6
+ end
7
+ end
@@ -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.18"
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", "~> 1.6"
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.18
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: 2017-05-15 00:00:00.000000000 Z
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: '1.6'
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: '1.6'
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
- rubyforge_project:
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