Current File : //bin/hiera |
#!/usr/bin/env ruby
# For security reasons, ensure that '.' is not on the load path
# This is primarily for 1.8.7 since 1.9.2+ doesn't put '.' on the load path
$LOAD_PATH.delete '.'
# CLI client for Hiera.
#
# To lookup the 'release' key for a node given Puppet YAML facts:
#
# $ hiera release 'rel/%{location}' --yaml some.node.yaml
#
# If the node yaml had a location fact the default would match that
# else you can supply scope values on the command line
#
# $ hiera release 'rel/%{location}' location=dc2 --yaml some.node.yaml
# Bundler and rubygems maintain a set of directories from which to
# load gems. If Bundler is loaded, let it determine what can be
# loaded. If it's not loaded, then use rubygems. But do this before
# loading any hiera code, so that our gem loading system is sane.
if not defined? ::Bundler
begin
require 'rubygems'
rescue LoadError
end
end
require 'hiera'
require 'hiera/util'
require 'optparse'
require 'pp'
options = {
:default => nil,
:config => File.join(Hiera::Util.config_dir, 'hiera.yaml'),
:scope => {},
:key => nil,
:verbose => false,
:resolution_type => :priority
}
# Loads the scope from YAML or JSON files
def load_scope(source, type=:yaml)
case type
when :mcollective
begin
require 'mcollective'
include MCollective::RPC
util = rpcclient("rpcutil")
util.progress = false
nodestats = util.custom_request("inventory", {}, source, {"identity" => source}).first
raise "Failed to retrieve facts for node #{source}: #{nodestats[:statusmsg]}" unless nodestats[:statuscode] == 0
scope = nodestats[:data][:facts]
rescue Exception => e
STDERR.puts "MCollective lookup failed: #{e.class}: #{e}"
exit 1
end
when :yaml
raise "Cannot find scope #{type} file #{source}" unless File.exist?(source)
require 'yaml'
# Attempt to load puppet in case we're going to be fed
# Puppet yaml files
begin
require 'puppet'
rescue
end
scope = YAML.load_file(source)
# Puppet makes dumb yaml files that do not promote data reuse.
scope = scope.values if scope.is_a?(Puppet::Node::Facts)
when :json
raise "Cannot find scope #{type} file #{source}" unless File.exist?(source)
require 'json'
scope = JSON.load(File.read(source))
when :inventory_service
# For this to work the machine running the hiera command needs access to
# /facts REST endpoint on your inventory server. This access is
# controlled in auth.conf and identification is by the certname of the
# machine running hiera commands.
#
# Another caveat is that if your inventory server isn't at the short dns
# name of 'puppet' you will need to set the inventory_sever option in
# your puppet.conf. Set it in either the master or main sections. It
# is fine to have the inventory_server option set even if the config
# doesn't have the fact_terminus set to rest.
begin
require 'puppet/util/run_mode'
$puppet_application_mode = Puppet::Util::RunMode[:master]
require 'puppet'
Puppet.settings.parse
Puppet::Node::Facts.indirection.terminus_class = :rest
scope = YAML.load(Puppet::Node::Facts.indirection.find(source).to_yaml)
# Puppet makes dumb yaml files that do not promote data reuse.
scope = scope.values if scope.is_a?(Puppet::Node::Facts)
rescue Exception => e
STDERR.puts "Puppet inventory service lookup failed: #{e.class}: #{e}"
exit 1
end
else
raise "Don't know how to load data type #{type}"
end
raise "Scope from #{type} file #{source} should be a Hash" unless scope.is_a?(Hash)
scope
end
OptionParser.new do |opts|
opts.banner = "Usage: hiera [options] key [default value] [variable='text'...]\n\nThe default value will be used if no value is found for the key. Scope variables\nwill be interpolated into %{variable} placeholders in the hierarchy and in\nreturned values.\n\n"
opts.on("--version", "-V", "Version information") do
puts Hiera.version
exit
end
opts.on("--debug", "-d", "Show debugging information") do
options[:verbose] = true
end
opts.on("--array", "-a", "Return all values as an array") do
options[:resolution_type] = :array
end
opts.on("--hash", "-h", "Return all values as a hash") do
options[:resolution_type] = :hash
end
opts.on("--config CONFIG", "-c", "Configuration file") do |v|
if File.exist?(v)
options[:config] = v
else
STDERR.puts "Cannot find config file: #{v}"
exit 1
end
end
opts.on("--json SCOPE", "-j", "JSON format file to load scope from") do |v|
begin
options[:scope] = load_scope(v, :json)
rescue Exception => e
STDERR.puts "Could not load JSON scope: #{e.class}: #{e}"
exit 1
end
end
opts.on("--yaml SCOPE", "-y", "YAML format file to load scope from") do |v|
begin
options[:scope] = load_scope(v)
rescue Exception => e
STDERR.puts "Could not load YAML scope: #{e.class}: #{e}"
exit 1
end
end
opts.on("--mcollective IDENTITY", "-m", "Use facts from a node (via mcollective) as scope") do |v|
begin
options[:scope] = load_scope(v, :mcollective)
rescue Exception => e
STDERR.puts "Could not load MCollective scope: #{e.class}: #{e}"
exit 1
end
end
opts.on("--inventory_service IDENTITY", "-i", "Use facts from a node (via Puppet's inventory service) as scope") do |v|
begin
options[:scope] = load_scope(v, :inventory_service)
rescue Exception => e
STDERR.puts "Could not load Puppet inventory service scope: #{e.class}: #{e}"
exit 1
end
end
end.parse!
# arguments can be:
#
# key default var=val another=val
#
# The var=val's assign scope
unless ARGV.empty?
options[:key] = ARGV.delete_at(0)
ARGV.each do |arg|
if arg =~ /^(.+?)=(.+?)$/
options[:scope][$1] = $2
else
unless options[:default]
options[:default] = arg.dup
else
STDERR.puts "Don't know how to parse scope argument: #{arg}"
end
end
end
else
STDERR.puts "Please supply a data item to look up"
exit 1
end
begin
hiera = Hiera.new(:config => options[:config])
rescue Exception => e
if options[:verbose]
raise
else
STDERR.puts "Failed to start Hiera: #{e.class}: #{e}"
exit 1
end
end
unless options[:verbose]
Hiera.logger = "noop"
end
ans = hiera.lookup(options[:key], options[:default], options[:scope], nil, options[:resolution_type])
if ans.is_a?(String)
puts ans
else
pp ans
end