hiera 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of hiera might be problematic. Click here for more details.
- data/bin/hiera +143 -0
- data/lib/hiera.rb +63 -0
- data/lib/hiera/backend.rb +102 -0
- data/lib/hiera/backend/yaml_backend.rb +45 -0
- data/lib/hiera/config.rb +50 -0
- data/lib/hiera/console_logger.rb +13 -0
- metadata +67 -0
data/bin/hiera
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
#!/bin/env ruby
|
2
|
+
|
3
|
+
# CLI client for Hiera.
|
4
|
+
#
|
5
|
+
# To lookup the 'release' key for a node given Puppet YAML facts:
|
6
|
+
#
|
7
|
+
# $ hiera release 'rel/%{location}' --yaml some.node.yaml
|
8
|
+
#
|
9
|
+
# If the node yaml had a location fact the default would match that
|
10
|
+
# else you can supply scope values on the command line
|
11
|
+
#
|
12
|
+
# $ hiera release 'rel/%{location}' location=dc2 --yaml some.node.yaml
|
13
|
+
|
14
|
+
require 'rubygems'
|
15
|
+
require 'hiera'
|
16
|
+
require 'optparse'
|
17
|
+
|
18
|
+
|
19
|
+
options = {:default => nil, :config => "/etc/hiera.yaml", :scope => {}, :key => nil, :verbose => false}
|
20
|
+
|
21
|
+
class Hiera::Noop_logger
|
22
|
+
class << self
|
23
|
+
def warn(msg);end
|
24
|
+
def debug(msg);end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Loads the scope from YAML or JSON files
|
29
|
+
def load_scope(file, type=:yaml)
|
30
|
+
raise "Cannot find scope #{type} file #{file}" unless File.exist?(file)
|
31
|
+
|
32
|
+
case type
|
33
|
+
when :yaml
|
34
|
+
require 'yaml'
|
35
|
+
|
36
|
+
# Attempt to load puppet in case we're going to be fed
|
37
|
+
# Puppet yaml files
|
38
|
+
begin
|
39
|
+
require 'puppet'
|
40
|
+
rescue
|
41
|
+
end
|
42
|
+
|
43
|
+
scope = YAML.load_file(file)
|
44
|
+
|
45
|
+
# Puppet makes dumb yaml files that do not promote data reuse.
|
46
|
+
scope = scope.values if scope.is_a?(Puppet::Node::Facts)
|
47
|
+
when :json
|
48
|
+
require 'json'
|
49
|
+
|
50
|
+
scope = JSON.load(File.read(file))
|
51
|
+
else
|
52
|
+
raise "Don't know how to load data type #{type}"
|
53
|
+
end
|
54
|
+
|
55
|
+
raise "Scope from #{type} file #{file} should be a Hash" unless scope.is_a?(Hash)
|
56
|
+
|
57
|
+
scope
|
58
|
+
end
|
59
|
+
|
60
|
+
OptionParser.new do |opts|
|
61
|
+
opts.on("--version", "-V", "Version information") do
|
62
|
+
puts Hiera.version
|
63
|
+
exit
|
64
|
+
end
|
65
|
+
|
66
|
+
opts.on("--debug", "-d", "Show debugging information") do
|
67
|
+
options[:verbose] = true
|
68
|
+
end
|
69
|
+
|
70
|
+
opts.on("--config CONFIG", "-c", "Configuration file") do |v|
|
71
|
+
if File.exist?(v)
|
72
|
+
options[:config] = v
|
73
|
+
else
|
74
|
+
STDERR.puts "Cannot find config file: #{v}"
|
75
|
+
exit 1
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
opts.on("--json SCOPE", "-j", "JSON format file to load scope from") do |v|
|
80
|
+
begin
|
81
|
+
options[:scope] = load_scope(v, :json)
|
82
|
+
rescue Exception => e
|
83
|
+
STDERR.puts "Could not load JSON scope: #{e.class}: #{e}"
|
84
|
+
exit 1
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
opts.on("--yaml SCOPE", "-y", "YAML format file to load scope from") do |v|
|
89
|
+
begin
|
90
|
+
options[:scope] = load_scope(v)
|
91
|
+
rescue Exception => e
|
92
|
+
STDERR.puts "Could not load YAML scope: #{e.class}: #{e}"
|
93
|
+
exit 1
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end.parse!
|
97
|
+
|
98
|
+
# arguments can be:
|
99
|
+
#
|
100
|
+
# key default var=val another=val
|
101
|
+
#
|
102
|
+
# The var=val's assign scope
|
103
|
+
unless ARGV.empty?
|
104
|
+
options[:key] = ARGV.delete_at(0)
|
105
|
+
|
106
|
+
ARGV.each do |arg|
|
107
|
+
if arg =~ /^(.+?)=(.+?)$/
|
108
|
+
options[:scope][$1] = $2
|
109
|
+
else
|
110
|
+
unless options[:default]
|
111
|
+
options[:default] = arg.dup
|
112
|
+
else
|
113
|
+
STDERR.puts "Don't know how to parse scope argument: #{arg}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
else
|
118
|
+
STDERR.puts "Please supply a data item to look up"
|
119
|
+
exit 1
|
120
|
+
end
|
121
|
+
|
122
|
+
begin
|
123
|
+
hiera = Hiera.new(:config => options[:config])
|
124
|
+
rescue Exception => e
|
125
|
+
if options[:verbose]
|
126
|
+
raise
|
127
|
+
else
|
128
|
+
STDERR.puts "Failed to start Hiera: #{e.class}: #{e}"
|
129
|
+
exit 1
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
unless options[:verbose]
|
134
|
+
Hiera.logger = "noop"
|
135
|
+
end
|
136
|
+
|
137
|
+
ans = hiera.lookup(options[:key], options[:default], options[:scope])
|
138
|
+
|
139
|
+
if ans.is_a?(String)
|
140
|
+
puts ans
|
141
|
+
else
|
142
|
+
p ans
|
143
|
+
end
|
data/lib/hiera.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
class Hiera
|
5
|
+
VERSION = "0.1.0"
|
6
|
+
|
7
|
+
autoload :Config, "hiera/config"
|
8
|
+
autoload :Backend, "hiera/backend"
|
9
|
+
autoload :Console_logger, "hiera/console_logger"
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def version
|
13
|
+
VERSION
|
14
|
+
end
|
15
|
+
|
16
|
+
# Loggers are pluggable, just provide a class called
|
17
|
+
# Hiera::Foo_logger and respond to :warn and :debug
|
18
|
+
#
|
19
|
+
# See hiera-puppet for an example that uses the Puppet
|
20
|
+
# loging system instead of our own
|
21
|
+
def logger=(logger)
|
22
|
+
loggerclass = "#{logger.capitalize}_logger"
|
23
|
+
|
24
|
+
require "hiera/#{logger}_logger" unless constants.include?(loggerclass)
|
25
|
+
|
26
|
+
@logger = const_get(loggerclass)
|
27
|
+
rescue Exception => e
|
28
|
+
@logger = Console_logger
|
29
|
+
warn("Failed to load #{logger} logger: #{e.class}: #{e}")
|
30
|
+
end
|
31
|
+
|
32
|
+
def warn(msg); @logger.warn(msg); end
|
33
|
+
def debug(msg); @logger.debug(msg); end
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :options, :config
|
37
|
+
|
38
|
+
# If the config option is a string its assumed to be a filename,
|
39
|
+
# else a hash of what would have been in the YAML config file
|
40
|
+
def initialize(options={})
|
41
|
+
options[:config] ||= "/etc/hiera.yaml"
|
42
|
+
|
43
|
+
@config = Config.load(options[:config])
|
44
|
+
|
45
|
+
Config.load_backends
|
46
|
+
end
|
47
|
+
|
48
|
+
# Calls the backends to do the actual lookup.
|
49
|
+
#
|
50
|
+
# The scope can be anything that responds to [], if you have input
|
51
|
+
# data like a Puppet Scope that does not you can wrap that data in a
|
52
|
+
# class that has a [] method that fetches the data from your source.
|
53
|
+
# See hiera-puppet for an example of this.
|
54
|
+
#
|
55
|
+
# The order-override will insert as first in the hierarchy a data source
|
56
|
+
# of your choice.
|
57
|
+
#
|
58
|
+
# TODO: resolution_type is to eventually support top down priority based
|
59
|
+
# lookups or bottom up merging type lookups like an ENC might need
|
60
|
+
def lookup(key, default, scope, order_override=nil, resolution_type=:priority)
|
61
|
+
Backend.lookup(key, default, scope, order_override, resolution_type)
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
class Hiera
|
2
|
+
module Backend
|
3
|
+
class << self
|
4
|
+
# Data lives in /var/lib/hiera by default. If a backend
|
5
|
+
# supplies a datadir in the config it will be used and
|
6
|
+
# subject to variable expansion based on scope
|
7
|
+
def datadir(backend, scope)
|
8
|
+
backend = backend.to_sym
|
9
|
+
default = "/var/lib/hiera"
|
10
|
+
|
11
|
+
if Config.include?(backend)
|
12
|
+
parse_string(Config[backend][:datadir] || default, scope)
|
13
|
+
else
|
14
|
+
parse_string(default, scope)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Constructs a list of data sources to search
|
19
|
+
#
|
20
|
+
# If you give it a specific hierarchy it will just use that
|
21
|
+
# else it will use the global configured one, failing that
|
22
|
+
# it will just look in the 'common' data source.
|
23
|
+
#
|
24
|
+
# An override can be supplied that will be pre-pended to the
|
25
|
+
# hierarchy.
|
26
|
+
#
|
27
|
+
# The source names will be subject to variable expansion based
|
28
|
+
# on scope
|
29
|
+
def datasources(scope, override=nil, hierarchy=nil)
|
30
|
+
if hierarchy
|
31
|
+
hierarchy = [hierarchy]
|
32
|
+
elsif Config.include?(:hierarchy)
|
33
|
+
hierarchy = [Config[:hierarchy]].flatten
|
34
|
+
else
|
35
|
+
hierarchy = ["common"]
|
36
|
+
end
|
37
|
+
|
38
|
+
hierarchy.insert(0, override) if override
|
39
|
+
|
40
|
+
hierarchy.flatten.map do |source|
|
41
|
+
source = parse_string(source, scope)
|
42
|
+
yield(source) unless source == ""
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Parse a string like '%{foo}' against a supplied
|
47
|
+
# scope and additional scope. If either scope or
|
48
|
+
# extra_scope includes the varaible 'foo' it will
|
49
|
+
# be replaced else an empty string will be placed.
|
50
|
+
#
|
51
|
+
# If both scope and extra_data has "foo" scope
|
52
|
+
# will win. See hiera-puppet for an example of
|
53
|
+
# this to make hiera aware of additional non scope
|
54
|
+
# variables
|
55
|
+
def parse_string(data, scope, extra_data={})
|
56
|
+
return nil unless data
|
57
|
+
|
58
|
+
tdata = data.clone
|
59
|
+
|
60
|
+
if tdata.is_a?(String)
|
61
|
+
while tdata =~ /%\{(.+?)\}/
|
62
|
+
var = $1
|
63
|
+
val = scope[var] || extra_data[var] || ""
|
64
|
+
|
65
|
+
tdata.gsub!(/%\{#{var}\}/, val)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
return tdata
|
70
|
+
end
|
71
|
+
|
72
|
+
# Calls out to all configured backends in the order they
|
73
|
+
# were specified. The first one to answer will win.
|
74
|
+
#
|
75
|
+
# This lets you declare multiple backends, a possible
|
76
|
+
# use case might be in Puppet where a Puppet module declares
|
77
|
+
# default data using in-module data while users can override
|
78
|
+
# using JSON/YAML etc. By layering the backends and putting
|
79
|
+
# the Puppet one last you can override module author data
|
80
|
+
# easily.
|
81
|
+
#
|
82
|
+
# Backend instances are cached so if you need to connect to any
|
83
|
+
# databases then do so in your constructor, future calls to your
|
84
|
+
# backend will not create new instances
|
85
|
+
def lookup(key, default, scope, order_override, resolution_type)
|
86
|
+
@backends ||= {}
|
87
|
+
answer = nil
|
88
|
+
|
89
|
+
Config[:backends].each do |backend|
|
90
|
+
if constants.include?("#{backend.capitalize}_backend")
|
91
|
+
@backends[backend] ||= Backend.const_get("#{backend.capitalize}_backend").new
|
92
|
+
answer = @backends[backend].lookup(key, scope, order_override, resolution_type)
|
93
|
+
|
94
|
+
break if answer
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
answer || parse_string(default, scope)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class Hiera
|
2
|
+
module Backend
|
3
|
+
class Yaml_backend
|
4
|
+
def initialize
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
Hiera.debug("Hiera YAML backend starting")
|
8
|
+
end
|
9
|
+
|
10
|
+
def lookup(key, scope, order_override, resolution_type)
|
11
|
+
answer = nil
|
12
|
+
|
13
|
+
Hiera.debug("Looking up #{key} in YAML backend")
|
14
|
+
|
15
|
+
datadir = Backend.datadir(:yaml, scope)
|
16
|
+
|
17
|
+
raise "Cannot find data directory #{datadir}" unless File.directory?(datadir)
|
18
|
+
|
19
|
+
Backend.datasources(scope, order_override) do |source|
|
20
|
+
unless answer
|
21
|
+
Hiera.debug("Looking for data source #{source}")
|
22
|
+
|
23
|
+
datafile = File.join([datadir, "#{source}.yaml"])
|
24
|
+
|
25
|
+
unless File.exist?(datafile)
|
26
|
+
Hiera.warn("Cannot find datafile #{datafile}, skipping")
|
27
|
+
next
|
28
|
+
end
|
29
|
+
|
30
|
+
data = YAML.load_file(datafile)
|
31
|
+
|
32
|
+
next if data.empty?
|
33
|
+
next unless data.include?(key)
|
34
|
+
|
35
|
+
answer = Backend.parse_string(data[key], scope)
|
36
|
+
else
|
37
|
+
break
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
answer
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/hiera/config.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
class Hiera::Config
|
2
|
+
class << self
|
3
|
+
# Takes a string or hash as input, strings are treated as filenames
|
4
|
+
# hashes are stored as data that would have been in the config file
|
5
|
+
#
|
6
|
+
# Unless specified it will only use YAML as backend with a single
|
7
|
+
# 'common' hierarchy and console logger
|
8
|
+
def load(source)
|
9
|
+
@config = {:backends => "yaml",
|
10
|
+
:hierarchy => "common"}
|
11
|
+
|
12
|
+
if source.is_a?(String)
|
13
|
+
raise "Config file #{source} not found" unless File.exist?(source)
|
14
|
+
|
15
|
+
@config.merge! YAML.load_file(source)
|
16
|
+
elsif source.is_a?(Hash)
|
17
|
+
@config.merge! source
|
18
|
+
end
|
19
|
+
|
20
|
+
@config[:backends] = [ @config[:backends] ].flatten
|
21
|
+
|
22
|
+
if @config.include?(:logger)
|
23
|
+
Hiera.logger = @config[:logger].to_s
|
24
|
+
else
|
25
|
+
@config[:logger] = "console"
|
26
|
+
Hiera.logger = "console"
|
27
|
+
end
|
28
|
+
|
29
|
+
@config
|
30
|
+
end
|
31
|
+
|
32
|
+
def load_backends
|
33
|
+
@config[:backends].each do |backend|
|
34
|
+
begin
|
35
|
+
require "hiera/backend/#{backend.downcase}_backend"
|
36
|
+
rescue LoadError => e
|
37
|
+
Hiera.warn "Cannot load backend #{backend}: #{e}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def include?(key)
|
43
|
+
@config.include?(key)
|
44
|
+
end
|
45
|
+
|
46
|
+
def [](key)
|
47
|
+
@config[key]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hiera
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- R.I.Pienaar
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-06-05 00:00:00 +01:00
|
18
|
+
default_executable: hiera
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: A pluggable data store for hierarcical data
|
22
|
+
email: rip@devco.net
|
23
|
+
executables:
|
24
|
+
- hiera
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files: []
|
28
|
+
|
29
|
+
files:
|
30
|
+
- bin/hiera
|
31
|
+
- lib/hiera.rb
|
32
|
+
- lib/hiera/backend.rb
|
33
|
+
- lib/hiera/config.rb
|
34
|
+
- lib/hiera/console_logger.rb
|
35
|
+
- lib/hiera/backend/yaml_backend.rb
|
36
|
+
has_rdoc: true
|
37
|
+
homepage: http://devco.net/
|
38
|
+
licenses: []
|
39
|
+
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
segments:
|
50
|
+
- 0
|
51
|
+
version: "0"
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
requirements: []
|
60
|
+
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 1.3.6
|
63
|
+
signing_key:
|
64
|
+
specification_version: 3
|
65
|
+
summary: Light weight hierarcical data store
|
66
|
+
test_files: []
|
67
|
+
|