puppet-runner 0.0.20 → 0.0.27

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: 0ef7b7a722f72764a0d2b013624442fbf1a953a78f9ccd380d718209f53acf61
4
- data.tar.gz: c060b7c2a5d188de60cda6afc41f9beb6e1693aed4966d609396596cbbdf6c57
2
+ SHA1:
3
+ metadata.gz: 0f8efcfb59ec97544f71bd48cef975038d26309d
4
+ data.tar.gz: c79062ae1be29f79cde741a014b8a66997ed1504
5
5
  SHA512:
6
- metadata.gz: 67de10395d2f7c802a5b00e8c0a15b2149a33514b1cbda0a811cdd34d97a108b5b343b142386918e6aa957ead50fcc0ffe38bc503a56d877bfd89d85faa243af
7
- data.tar.gz: 864c05e7c5b9223d01fc09e1b43ae2925904542128b5ecd81310c60167706918a1768a833679f10fb545cb27850335f803c32fa5b49c282fc71588d679153be2
6
+ metadata.gz: 9968eaf857daa85f81af9cd69bd76095a0845888787af4298383e6ed466d91c75d6d7e6e499ac56360d5d181d0daf8a0e121d9d8cafa7735b27b5c4d7f38eacd
7
+ data.tar.gz: c3d6892856cda4d15562deac8c53396694472185b8c2d17bdb9b8ea8a87c74026ffc28aa4546d679ffad2467826d398edb89fb557d948e497db010123177c7b7
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
@@ -27,8 +27,8 @@ doc = <<DOCOPT
27
27
  Adaptavist puppet runner
28
28
 
29
29
  Usage:
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] [-k]
31
- 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]
32
32
  puppet-runner -h | --help
33
33
 
34
34
  Options:
@@ -38,11 +38,15 @@ Options:
38
38
  -d DESTINATION_DIR --dest_dir DESTINATION_DIR Directory for result hiera config.
39
39
  -t TEMPLATES --templates TEMPLATES Directory containing templates and defaults folder with functionality templates and default facts
40
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
41
43
  -p PUPPET_APPLY --puppet_apply PUPPET_APPLY Custom puppet apply command to run
42
44
  -r PUPPETFILE_CONFIG --puppetfile_config puppetfile_config Puppetfile composition config file
43
45
  -o PUPPETFILE_OUTPUT_PATH --puppetfile_output_path PUPPETFILE_OUTPUT_PATH Result Puppetfile path
44
46
  -e EYAML_KEY_PATH --eyaml_key_path EYAML_KEY_PATH Path to eyaml encryption key pair
45
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
46
50
  Commands:
47
51
  all Runs the following commands prepare, start
48
52
  start Runs puppet apply
@@ -107,12 +111,36 @@ def extract_type_from_hash(input)
107
111
  type = val["type"]
108
112
  end
109
113
  end
110
- {key => type }
114
+ {type => key }
111
115
  }
112
116
  end
113
117
  res
114
118
  end
115
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, return_fact_value=false)
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
+ # if we have not been told to return the fact value, then return the fact key, else the value wil be returned
136
+ val = "%{::#{fact_name}}" if return_fact_value == false
137
+ end
138
+ final_val = val
139
+ end
140
+ final_val
141
+ end
142
+
143
+
116
144
  begin
117
145
  options = Docopt::docopt(doc)
118
146
  rescue Docopt::Exit => e
@@ -126,6 +154,7 @@ if options['all'] || options['prepare']
126
154
  input_dir = options["--config_dir"] || options["-c"]
127
155
  dest_dir = options["--dest_dir"] || options["-d"]
128
156
  facts_dest_dir = options["--facts_dest_dir"] || options["-f"]
157
+ custom_facts_dir = options["--custom_facts_dir"] || options["-x"] || nil
129
158
  templates = options["--templates"] || options["-t"]
130
159
  puppetfile_config_path = options["--puppetfile_config"] || options["-r"]
131
160
  puppetfile_output_path = options["--puppetfile_output_path"] || options["-o"]
@@ -133,6 +162,7 @@ if options['all'] || options['prepare']
133
162
  hostname = options["--servername"] || options["-s"] || Facter.value("hostname")
134
163
  puts "Hostname #{hostname}"
135
164
  keep_facts = true if options["-k"] or options["--keep-facts"]
165
+
136
166
 
137
167
  config_file_path = path_join_glob(input_dir, hostname+".yaml")
138
168
  templates_dir = path_join_glob(templates, "templates")
@@ -170,7 +200,8 @@ if options['all'] || options['prepare']
170
200
  prefixed_facts_comments = {}
171
201
  puppetfile_config = YAML.load_file(puppetfile_config_path) || {}
172
202
  puppetfile_dependencies = []
173
- global_transform_data = []
203
+ global_data_types = []
204
+ nil_transform_present = false
174
205
  # functionalities:
175
206
  # # In honor of Henry...
176
207
  # 1_app:
@@ -211,17 +242,7 @@ if options['all'] || options['prepare']
211
242
  fact_comments_as_string = extract_comment_from_hash(default_facts).to_s
212
243
 
213
244
  # get a list of each fields type if set (if not they will report as strings)
214
- data_type = extract_type_from_hash(default_facts)
215
-
216
- # Check to see if any of the fields are booleans, if so add an entry to the transform_data array
217
- transform_data = []
218
- data_type.each do | field|
219
- field.each do | field_name, field_type |
220
- if field_type == 'boolean'
221
- transform_data << field_name
222
- end
223
- end
224
- end
245
+ data_types = extract_type_from_hash(default_facts)
225
246
 
226
247
  if to_add.is_a?(Hash)
227
248
  # if prefixes are not defined skip replacing
@@ -239,7 +260,13 @@ if options['all'] || options['prepare']
239
260
  facts_as_string = facts_as_string.gsub(/#{prefix}/, "#{replace_prefixes_with}")
240
261
  fact_comments_as_string = fact_comments_as_string.gsub(/#{prefix}/, "#{replace_prefixes_with}")
241
262
  prefixed_required_facts = prefixed_required_facts.merge(required_facts.map! { |item| item.gsub(/#{prefix}/, "#{replace_prefixes_with}") })
242
- transform_data = transform_data.map {|s| s.gsub(/#{prefix}/, "#{replace_prefixes_with}")} if !transform_data.empty?
263
+ if !data_types.empty?
264
+ data_types.each do | transform |
265
+ transform.each do|key, value|
266
+ transform[key] = value.gsub(/#{prefix}/, "#{replace_prefixes_with}")
267
+ end
268
+ end
269
+ end
243
270
  end
244
271
  end
245
272
  end
@@ -252,7 +279,13 @@ if options['all'] || options['prepare']
252
279
  facts_as_string = facts_as_string.gsub(/#{prefix}/, "#{replace_prefixes_with}")
253
280
  fact_comments_as_string = fact_comments_as_string.gsub(/#{prefix}/, "#{replace_prefixes_with}")
254
281
  prefixed_required_facts = prefixed_required_facts.merge(required_facts.map! { |item| item.gsub(/#{prefix}/, "#{replace_prefixes_with}") })
255
- transform_data = transform_data.map {|s| s.gsub(/#{prefix}/, "#{replace_prefixes_with}")} if !transform_data.empty?
282
+ if !data_types.empty?
283
+ data_types.each do | transform |
284
+ transform.each do|key, value|
285
+ transform[key] = value.gsub(/#{prefix}/, "#{replace_prefixes_with}")
286
+ end
287
+ end
288
+ end
256
289
  end
257
290
  end
258
291
  end
@@ -267,8 +300,8 @@ if options['all'] || options['prepare']
267
300
  default_fact_comments = extract_comment_from_hash(plain_facts)
268
301
  prefixed_required_facts = prefixed_required_facts.merge(required_facts)
269
302
  end
270
- # add the "local" transformation list to the global one
271
- global_transform_data.push(*transform_data)
303
+ # add the "local" data type list to the global one
304
+ global_data_types.push(*data_types)
272
305
 
273
306
  result_template.deep_merge!(template)
274
307
  # default_facts_prefixed is Array of hashes as the result of map, this will create hash from it
@@ -287,37 +320,109 @@ if options['all'] || options['prepare']
287
320
 
288
321
  File.open(output_encrypted_facts_file_path, 'w+') do |output_file|
289
322
  output_result_default_facts = result_default_facts.deep_merge!(custom_facts, {:merge_hash_arrays => true}).to_yaml
323
+
324
+ # convert final facts to hash for inter fact resolution and also for potential transformations later
325
+ $all_facts = YAML.load(output_result_default_facts)
326
+
327
+ #convert result templat to string
328
+ result_template = result_template.to_s
329
+
330
+ # loop through facts looking for any that reference other facts
331
+ $all_facts.each do | fact_key, fact_val |
332
+ if fact_val.instance_of?(String) and fact_val.match(/%{::.*.}/)
333
+ debug "Fact #{fact_key} references another fact or facts"
334
+ # find each instance of a fact within the value (it may contain multiple facts
335
+ # i.e "TEST%{::fact1}TEST%{::fact2}"
336
+ fact_val.gsub(/}/,"}\n").scan(/%{::.*.}/).each do | fact |
337
+ # if the fact is part of a larger string set a flag to tell resolve_fact
338
+ # to return the final fact value instead of the finalfact key!
339
+ if fact.length != fact_val.length
340
+ return_fact_value = true
341
+ else
342
+ return_fact_value = false
343
+ end
344
+
345
+ # resolve the fact down to its last reference to another fact (not the end value) if return_fact_value is false
346
+ # or down to the end value if return_fact_value is true
347
+ resolved_fact = resolve_fact(fact, 5, 0, return_fact_value)
348
+ # if the resolved fact name is not the same as the original fact we found referenced
349
+ # then replace the value in fact_val
350
+ if !resolved_fact.nil? and resolved_fact != fact
351
+ fact_val = fact_val.gsub(/#{fact}/,"#{resolved_fact}")
352
+ end
353
+ end
354
+ debug "Attempting to replace fact '#{fact_key}' with value '#{fact_val}' in compiled template"
355
+ # replace the original fact reference in the template with the resovled value
356
+ # this is done before global teansformation as they may change the final value again
357
+ result_template = result_template.gsub(/\%{::#{fact_key}}/, "#{fact_val}")
358
+ end
359
+ end
360
+
361
+ # add comments above any fact lines
290
362
  prefixed_facts_comments.each do |pattern, replacement|
291
363
  if replacement != nil
292
364
  output_result_default_facts.gsub!(/^#{pattern}/, "\##{replacement}\n#{pattern}")
293
365
  end
294
366
  end
295
367
 
296
- output_file.write(output_result_default_facts)
368
+ # merge custom facts if parameter provided
369
+ custom_facts_all = {}
370
+ if (custom_facts_dir)
371
+ Dir.glob("#{custom_facts_dir}/*.yaml").sort.each do |custom_facts_file|
372
+ custom_facts_from_file = YAML.load_file(custom_facts_file) || {}
373
+ custom_facts_all.merge!(custom_facts_from_file)
374
+ end
375
+ end
376
+ # merge, prefer custom facts
377
+ merged_all_facts = YAML.load(output_result_default_facts).merge!(custom_facts_all)
378
+
379
+ # write the temp encrypted facts file
380
+ output_file.write(merged_all_facts.to_yaml)
297
381
 
298
382
  # now that the merged final facts are present look for any global transformations to apply
299
- # global transformations are currently just booleans that need to be expressed directly in output file
300
- if !global_transform_data.empty?
301
- result_template = result_template.to_s
302
- all_facts = YAML.load(output_result_default_facts)
303
- global_transform_data.each do | transform_value |
304
- debug "Attempting to replace boolean value for fact #{transform_value}"
305
- begin
306
- # convert the final template to a string, replace any facts that are booleans with their actual value and then convert the result bacl to YAML
307
- # convert the fact to boolean and then back to string during the replace, this allows validation that the fact is actually a boolean
308
- result_template = result_template.gsub(/\"\%{::#{transform_value}}\"/, all_facts[transform_value].to_bool.to_s)
309
- rescue
310
- warning "Unable to convert fact #{transform_value} with value #{all_facts[transform_value]} into boolean, conversion will be skipped"
383
+ # global transformations are currently either booleans that need to be expressed directly in
384
+ # the output file or nilable values that need to be expressed as unquoted tildas (~)
385
+ if !global_data_types.empty?
386
+ global_data_types.each do | transform |
387
+ transform.each do|transform_type, transform_value|
388
+ begin
389
+ if transform_type == 'boolean'
390
+ debug "Attempting to replace boolean value for fact #{transform_value}"
391
+ # convert the fact to boolean and then back to string during the replace, this allows validation that the fact is actually a boolean
392
+ result_template = result_template.gsub(/\"\%{::#{transform_value}}\"/, $all_facts[transform_value].to_bool.to_s)
393
+ elsif transform_type == 'nilable'
394
+ # replace fact reference with tilda if the value is nil, empty or already a tilda
395
+ # due to issues with ruby hash values being unquoted we will quote it now and remove the quotes later
396
+ if $all_facts[transform_value].nil? or $all_facts[transform_value].empty? or $all_facts[transform_value] == "~"
397
+ debug "Attempting to replace nilable value for fact #{transform_value}"
398
+ # replace value with tilda
399
+ result_template = result_template.gsub(/\"\%{::#{transform_value}}\"/, '"~"')
400
+ # identify that we have made at least one nil transformation
401
+ nil_transform_present = true
402
+ end
403
+ end
404
+ rescue
405
+ warning "Unable to convert fact #{transform_value} with value #{all_facts[transform_value]} into #{transform_type}, conversion will be skipped"
406
+ end
311
407
  end
312
408
  end
313
- result_template = eval(result_template)
314
409
  end
315
410
  end
316
411
 
412
+ # convert result_template back to hash
413
+ result_template = eval(result_template)
414
+
317
415
  # Write results
318
416
  File.open(output_file_path, 'w+') do |output_file|
319
417
  YAML.dump(result_template, output_file)
320
418
  end
419
+
420
+ # hack to get around the fact we have to pass tilda as quoted earlier
421
+ if nil_transform_present
422
+ compiled_hiera = File.read(output_file_path)
423
+ replaced_hiera = compiled_hiera.gsub('"~"', '~')
424
+ File.open(output_file_path, "w") {|new_hiera| new_hiera.puts replaced_hiera}
425
+ end
321
426
 
322
427
  # decrypt facts file because Puppet doesn't appear to be able to read encrypted facts
323
428
  require 'hiera/backend/eyaml/plugins'
@@ -359,7 +464,6 @@ if options['all'] || options['prepare']
359
464
  end
360
465
  }
361
466
  end
362
-
363
467
  output_file.write(decrypted.join)
364
468
  end
365
469
 
@@ -414,10 +518,33 @@ end
414
518
  # start puppet
415
519
  if (options['start'] || options['all']) && !stop_apply
416
520
  require 'puppet'
417
- modulefile_definition = Gem::Version.new(Puppet.version) > Gem::Version.new('4.0.0') ? '--modulepath /etc/puppet/modules' : ''
418
- 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}'"
521
+
522
+ # set dry run option if the flag has been set
523
+ if options["-n"] or options["--dry-run"]
524
+ dry_run = '--noop'
525
+ verbose_output = true
526
+ else
527
+ dry_run = ''
528
+ end
529
+
530
+ # if the user has specified a module path pass it to puppet
531
+ if options["--module_path"]
532
+ modulefile_definition = "--modulepath #{options['--module_path']}"
533
+ elsif options["-m"]
534
+ modulefile_definition = "--modulepath #{options['-m']}"
535
+ # if no modulepath has been set default to /etc/puppet/modules for puppet 4 and above and blank for older versions
536
+ else
537
+ modulefile_definition = Gem::Version.new(Puppet.version) > Gem::Version.new('4.0.0') ? '--modulepath /etc/puppet/modules' : ''
538
+ end
539
+
540
+ # construct defaut puppet apply command
541
+ 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}'"
542
+
543
+ # if a custom puppet apply command has been set use if, otherwise use the default generated above
419
544
  to_execute = options["--puppet_apply"] || options["-p"] || puppet_command
420
545
  debug "Running #{to_execute}"
546
+
547
+ # execute puppet apply and capture return code
421
548
  `#{to_execute}`
422
549
  exit_code = $?.exitstatus
423
550
 
@@ -436,7 +563,11 @@ if (options['start'] || options['all']) && !stop_apply
436
563
  warning "Unable to locate Facts file, please urgently locate and remove this as it holds unencrypted values"
437
564
  end
438
565
 
439
- if exit_code != 2
566
+ # if we see a bad exit code report it, for refrerence good codes are:
567
+ # 0: The run succeeded with no changes or failures; the system was already in the desired state.
568
+ # 2: The run succeeded, and some resources were changed.
569
+ if exit_code != 2 and exit_code != 0
440
570
  raise "execute_puppet exit status: #{exit_code}"
441
571
  end
442
572
  end
573
+
@@ -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.20"
7
+ spec.version = "0.0.27"
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.20
4
+ version: 0.0.27
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: 2018-04-05 00:00:00.000000000 Z
12
+ date: 2022-02-03 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
@@ -173,7 +173,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
173
173
  version: '0'
174
174
  requirements: []
175
175
  rubyforge_project:
176
- rubygems_version: 2.7.6
176
+ rubygems_version: 2.6.8
177
177
  signing_key:
178
178
  specification_version: 4
179
179
  summary: Preprocessor for hiera config