msmg-hiera-templater 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/hiera-templater +67 -0
- data/lib/erb_reflective.rb +23 -0
- data/lib/hiera_template.rb +71 -0
- data/msmg-hiera-templater.gemspec +18 -0
- data/readme.md +111 -0
- metadata +77 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 93f1bedbdcc88d9f3ae602533735b89d35c99b52525536a63b83bc982278541b
|
4
|
+
data.tar.gz: d078a5d8fd9adc3c32c2380320cf504fc1f9547524689b2a5624bbfc0eb0d6c4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1d91190aba1abd9d9946d41899e3a0e63a87135ab6f2d388b214e676a0f646fa2d2c2cbd042d2d145a11483b4d941f712c421b3971d6ac895df7b71f87fe26cb
|
7
|
+
data.tar.gz: 215c48e206f3ebc32701e12e622f46ae9ab4eb7d4b9b3c9f31db95071ab212ae4ef9b63960270c7ae74dc427dc6ddff3cd36a94f79425817ac1833ae95a8ecfc
|
data/bin/hiera-templater
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'optparse'
|
5
|
+
require_relative '../lib/hiera_template'
|
6
|
+
|
7
|
+
args = { config: HieraTemplate::DEFAULT_CONFIG }
|
8
|
+
opt_parser = OptionParser.new do |opts|
|
9
|
+
opts.banner = <<~USAGE
|
10
|
+
Usage: #{File.basename(__FILE__)} [options] <file/folder>
|
11
|
+
|
12
|
+
Processes a specific .erb template or a folder hierarchy for .erb files.
|
13
|
+
Note that only files named with an .erb suffix will be processed and the output file will match the
|
14
|
+
name but with the suffix removed.
|
15
|
+
|
16
|
+
The erb files in the folder must use hiera('key') to retrieve a data item.
|
17
|
+
USAGE
|
18
|
+
|
19
|
+
opts.on('-c CONFIG_FILE', '--config=CONFIG_FILE', String,
|
20
|
+
"hiera configuration file (default #{args[:config]})") do |c|
|
21
|
+
args[:config] = c
|
22
|
+
end
|
23
|
+
|
24
|
+
opts.on('-r', '--recurse', TrueClass, 'Recurse a specified folder rather than specify a file name') do |n|
|
25
|
+
args[:recurse] = n
|
26
|
+
end
|
27
|
+
|
28
|
+
opts.on('-f', '--force', TrueClass, 'Force overwrite if output file exists') do |f|
|
29
|
+
args[:overwrite] = f
|
30
|
+
end
|
31
|
+
|
32
|
+
opts.on('-h', '--help', 'Prints this help') do
|
33
|
+
puts opts
|
34
|
+
exit
|
35
|
+
end
|
36
|
+
end
|
37
|
+
opt_parser.parse!
|
38
|
+
name = ARGV.shift
|
39
|
+
unless name && ((File.directory?(name) && args[:recurse]) || (File.exist?(name) && File.extname(name) == '.erb'))
|
40
|
+
puts opt_parser.help
|
41
|
+
exit 1
|
42
|
+
end
|
43
|
+
templater = HieraTemplate.new(args[:config])
|
44
|
+
|
45
|
+
def strip_suffix(file, args)
|
46
|
+
new_file = File.join(File.dirname(file), File.basename(file, '.erb'))
|
47
|
+
return new_file if args[:overwrite] || !File.exist?(new_file)
|
48
|
+
|
49
|
+
abort "#{new_file} already exists (based on template #{file})"
|
50
|
+
end
|
51
|
+
|
52
|
+
def process_file(templater, file, args)
|
53
|
+
content = File.read(file)
|
54
|
+
output = templater.render(content)
|
55
|
+
return unless output.is_a?(String) # Detect custom output
|
56
|
+
|
57
|
+
out_file = strip_suffix(file, args)
|
58
|
+
File.write(out_file, output)
|
59
|
+
end
|
60
|
+
|
61
|
+
if args[:recurse]
|
62
|
+
Dir.glob(File.join(name, '**', '*.erb')) do |file|
|
63
|
+
process_file(templater, file, args)
|
64
|
+
end
|
65
|
+
else
|
66
|
+
process_file(templater, name, args)
|
67
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'erb'
|
4
|
+
|
5
|
+
# Override some ERB behaviour to make it reflect methods supported
|
6
|
+
# by an object that is passed to it
|
7
|
+
class ERBReflective < ERB
|
8
|
+
# Simply store the object reference passed in
|
9
|
+
# then call the superclass behaviour with our binding instead
|
10
|
+
def result(other_object)
|
11
|
+
@other_object = other_object
|
12
|
+
super(binding)
|
13
|
+
end
|
14
|
+
|
15
|
+
def method_missing(meth, *args, &block)
|
16
|
+
super unless respond_to_missing? meth
|
17
|
+
@other_object&.send(meth, *args, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def respond_to_missing?(meth)
|
21
|
+
@other_object&.respond_to? meth
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'facter'
|
5
|
+
require 'hiera'
|
6
|
+
|
7
|
+
require_relative 'erb_reflective'
|
8
|
+
|
9
|
+
# Templating handler object used by ERBReflective
|
10
|
+
class HieraTemplate
|
11
|
+
DEFAULT_CONFIG = '/etc/puppet/hiera.yaml'
|
12
|
+
|
13
|
+
def initialize(hiera_config = DEFAULT_CONFIG)
|
14
|
+
@scope = Facter.to_hash # Time consuming, do it once if possible!
|
15
|
+
# Just so we can handle old facter references starting ::
|
16
|
+
legacy = @scope.map { |k, v| ["::#{k}", v] }
|
17
|
+
@scope.merge!(Hash[legacy])
|
18
|
+
@hiera = Hiera.new(config: hiera_config)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Template render
|
22
|
+
def render(content)
|
23
|
+
@blocks = []
|
24
|
+
rendered = ERBReflective.new(content, nil, '-').result(self)
|
25
|
+
return rendered unless @blocks.any?
|
26
|
+
|
27
|
+
# See custom_out
|
28
|
+
@blocks.each { |block| block.call(rendered) }
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
# Methods used by ERBReflective to access hiera data
|
33
|
+
# See above, full content is not available until the template is rendered so
|
34
|
+
# we only stash the block references that we are given and call it later
|
35
|
+
def custom_out(&block)
|
36
|
+
raise StandardError, 'custom_out requires a block argument' unless block_given?
|
37
|
+
|
38
|
+
@blocks << block
|
39
|
+
end
|
40
|
+
|
41
|
+
# The DSL
|
42
|
+
|
43
|
+
# If hiera does not exist we will try facter data in scope to unify the API
|
44
|
+
def hiera(key, default = nil)
|
45
|
+
@hiera.lookup(key, nil, @scope) || @scope.dig(*key.split('.')) || default
|
46
|
+
end
|
47
|
+
|
48
|
+
def hiera_hash(key, default = nil)
|
49
|
+
@hiera.lookup(key, default, @scope, nil, :hash)
|
50
|
+
end
|
51
|
+
|
52
|
+
def hiera_array(key, default = nil)
|
53
|
+
@hiera.lookup(key, default, @scope, nil, :array)
|
54
|
+
end
|
55
|
+
|
56
|
+
def mkdir_p(path, owner: nil, group: nil, mode: nil)
|
57
|
+
custom_out do |_|
|
58
|
+
FileUtils.mkdir_p(path)
|
59
|
+
FileUtils.chown_R(owner, group, path) if owner || group
|
60
|
+
FileUtils.chmod_R(mode, path) if mode
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def custom_file(path, owner: nil, group: nil, mode: nil)
|
65
|
+
custom_out do |content|
|
66
|
+
File.write(path, content)
|
67
|
+
FileUtils.chown(owner, group, path) if owner || group
|
68
|
+
FileUtils.chmod(mode, path) if mode
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'msmg-hiera-templater'
|
5
|
+
s.version = '0.0.1'
|
6
|
+
s.authors = ['Andrew Smith']
|
7
|
+
s.email = ['andrew.smith at moneysupermarket.com']
|
8
|
+
s.required_ruby_version = '>=2.5'
|
9
|
+
s.summary = 'Render ERB templates from heira without requiring puppet'
|
10
|
+
s.description = 'MSM pubicly available Ruby'
|
11
|
+
s.homepage = 'https://github.com/MSMFG/msmg-hiera-templater'
|
12
|
+
s.license = 'Apache-2.0'
|
13
|
+
s.files = `git ls-files -z`.split("\x0")
|
14
|
+
s.add_runtime_dependency 'facter', ['~>4.2']
|
15
|
+
s.add_runtime_dependency 'hiera', ['~>3.7']
|
16
|
+
s.bindir = 'bin'
|
17
|
+
s.executables << 'hiera-templater'
|
18
|
+
end
|
data/readme.md
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
# Hiera templater without puppet
|
2
|
+
|
3
|
+
Requires facter and hiera gems, compatible with latest gems and versions installed on MSM instances.
|
4
|
+
|
5
|
+
## Background
|
6
|
+
|
7
|
+
The idea behind this prohect is to allow the power of hiera to be extended to folks that wish to use it
|
8
|
+
in scenarios where they don't want to use puppet.
|
9
|
+
|
10
|
+
## Operation - basics
|
11
|
+
|
12
|
+
The CLI has only one required option, a file or a path to be parsed, path parsing must be enabled by passing the -r (recursive flag) and
|
13
|
+
the CLI will then look for any file suffixed in .erb.
|
14
|
+
|
15
|
+
In basic mode of operation the templater has a safeguard blocking the overwrite of existing files (possibly from previous runs) unless the
|
16
|
+
-f flag (force) is also provided.
|
17
|
+
|
18
|
+
Syntax as per help
|
19
|
+
|
20
|
+
```
|
21
|
+
andrew.smith@C02Z2A0SLVCG hiera-templater % ruby hiera_templater.rb
|
22
|
+
Usage: hiera_templater.rb [options] <file/folder>
|
23
|
+
|
24
|
+
Processes a specific .erb template or a folder hierarchy for .erb files.
|
25
|
+
Note that only files named with an .erb suffix will be processed and the output file will match the
|
26
|
+
name but with the suffix removed.
|
27
|
+
|
28
|
+
The erb files in the folder must use hiera('key') to retrieve a data item.
|
29
|
+
-c, --config=CONFIG_FILE hiera configuration file (default /etc/puppet/hiera.yaml)
|
30
|
+
-r, --recurse Recurse a specified folder rather than specify a file name
|
31
|
+
-f, --force Force overwrite if output file exists
|
32
|
+
-h, --help Prints this help
|
33
|
+
andrew.smith@C02Z2A0SLVCG hiera-templater %
|
34
|
+
```
|
35
|
+
|
36
|
+
NOTE: In all modes of operation please attempt to utilise the recurse feature to process all your files, do NOT wrap this in another script as you will pay a 0.6 second penalty for each time the Facter library is initialised!!
|
37
|
+
|
38
|
+
In basic operation the files are delivered to the same folder as the erb template with the erb suffix removed. All template files must have the trailing .erb suffix.
|
39
|
+
|
40
|
+
ERB templates are processed with one of 3 hiera DSL lookup functions, these are..
|
41
|
+
|
42
|
+
### hiera(<hey>, \[default\])
|
43
|
+
|
44
|
+
A key, regardless of type is retrieved, this form will return any type including hash and array and can be given an optional default value for
|
45
|
+
values that cannot be found.
|
46
|
+
|
47
|
+
The hiera function ALSO includes a lookup for facter keys if not found in hiera, neither of the two other lookup functions do this.
|
48
|
+
|
49
|
+
### hiera_array(<key>, \[default\])
|
50
|
+
|
51
|
+
Type checked version of the above that will fail if a non array type is found.
|
52
|
+
|
53
|
+
### hiera_hash(<key>, \[default\])
|
54
|
+
|
55
|
+
Type checked version of the above that will fail if a non hash type is found.
|
56
|
+
|
57
|
+
## Operation - More DSL usage
|
58
|
+
|
59
|
+
If one wishes to output files to a specific folder rather than have then land automatically as placed by the script one can use one of 2 high level DSL
|
60
|
+
functions.
|
61
|
+
|
62
|
+
Noting that as soon as either of these or the more advanced custom_out function is used one is entirely responsible for the output to file, the tool will
|
63
|
+
no longer automatically do that for you in these cases.
|
64
|
+
|
65
|
+
### mkdir_p(\<path\>, \[owner: \<owner\>\], \[group: \<group\>\], \[mode: \<mode\>\]))
|
66
|
+
|
67
|
+
This DSL function calls custom_out and is a helper to create a folder structure recursively with optional ownership and mode specifications.
|
68
|
+
|
69
|
+
### custom_file(\<path\>, \[owner: \<owner\>\], \[group: \<group\>\], \[mode: \<mode\>\])
|
70
|
+
|
71
|
+
This DSL function calls custom_out with a preset block to ensure the output file is written with the path and optionally ownership and mode specified.
|
72
|
+
|
73
|
+
Note that custom_out blocks are performed in the order specified but following rendering so please ensure that any dependent file strictures are made (mkdir_p) before custome_file is used if it is dependent upon that path.
|
74
|
+
|
75
|
+
## Operation - Advanced usage
|
76
|
+
|
77
|
+
The templater implements custom_out as a function which takes a block of Ruby code as a parameter, this block should implement a parameter which will receive the rendered content (whether it uses it or not) and is free to utilise that content for more advanced purposes.
|
78
|
+
|
79
|
+
## DSL Example
|
80
|
+
```
|
81
|
+
[root@ip-10-96-3-54 ~]# cat testing/dsl.erb
|
82
|
+
[Facter only]
|
83
|
+
My IP is: <%= hiera('ipaddress_primary') %>
|
84
|
+
|
85
|
+
[Something missing]
|
86
|
+
Non existant: <%= hiera('clearly::not_existing', 'but has default') %>
|
87
|
+
|
88
|
+
<% mkdir_p('/tmp/test')
|
89
|
+
mkdir_p('/tmp/test2', owner: 'sysadmin', mode: 0500)
|
90
|
+
mkdir_p('/tmp/test3', group: 'sysadmin', mode: 0550)
|
91
|
+
mkdir_p('/tmp/test4', owner: 'root', group: 'sysadmin', mode: 0750)
|
92
|
+
custom_file('/tmp/test4/testfile', owner: 'root', group: 'sysadmin', mode: 0640) -%>
|
93
|
+
```
|
94
|
+
|
95
|
+
## Advanced example
|
96
|
+
```
|
97
|
+
[root@ip-10-96-3-54 ~]# cat testing/custom_out.erb
|
98
|
+
[Facter only]
|
99
|
+
My IP is: <%= hiera('ipaddress_primary') %>
|
100
|
+
|
101
|
+
[Something missing]
|
102
|
+
Non existant: <%= hiera('clearly::not_existing', 'but has default') %>
|
103
|
+
|
104
|
+
<% custom_out do |content|
|
105
|
+
File.write('/tmp/footest', content)
|
106
|
+
FileUtils.chmod(0640, '/tmp/footest')
|
107
|
+
FileUtils.chown('root', 'sysadmin', '/tmp/footest')
|
108
|
+
end -%>
|
109
|
+
<% custom_out { |content| puts content } -%>
|
110
|
+
[root@ip-10-96-3-54 ~]#
|
111
|
+
```
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: msmg-hiera-templater
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andrew Smith
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-09-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: facter
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: hiera
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.7'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.7'
|
41
|
+
description: MSM pubicly available Ruby
|
42
|
+
email:
|
43
|
+
- andrew.smith at moneysupermarket.com
|
44
|
+
executables:
|
45
|
+
- hiera-templater
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- bin/hiera-templater
|
50
|
+
- lib/erb_reflective.rb
|
51
|
+
- lib/hiera_template.rb
|
52
|
+
- msmg-hiera-templater.gemspec
|
53
|
+
- readme.md
|
54
|
+
homepage: https://github.com/MSMFG/msmg-hiera-templater
|
55
|
+
licenses:
|
56
|
+
- Apache-2.0
|
57
|
+
metadata: {}
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '2.5'
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
requirements: []
|
73
|
+
rubygems_version: 3.1.4
|
74
|
+
signing_key:
|
75
|
+
specification_version: 4
|
76
|
+
summary: Render ERB templates from heira without requiring puppet
|
77
|
+
test_files: []
|