├── .gitignore ├── Modulefile ├── README.md ├── lib ├── hiera │ └── backend │ │ └── module_data_backend.rb └── puppet │ └── indirector │ └── data_binding │ └── hiera.rb └── metadata.json /.gitignore: -------------------------------------------------------------------------------- 1 | /.project 2 | -------------------------------------------------------------------------------- /Modulefile: -------------------------------------------------------------------------------- 1 | name 'ripienaar-module_data' 2 | version '0.0.4' 3 | description 'A hiera backend to allow the use of data while writing sharable modules' 4 | project_page 'https://github.com/ripienaar/puppet-module-data' 5 | license 'ASL 2.0' 6 | author 'R.I.Pienaar ' 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | What? 2 | ===== 3 | 4 | **NOTE: There is now native support for this feature in Puppet 4 and 5 | this repo is now officially deprecated and moving to unmaintained.** 6 | 7 | **Additionally this module does not work on Puppet 4, will likely break your 8 | setup. Do not use it on Puppet 4, migrate to native Module Data support** 9 | 10 | **See https://www.devco.net/archives/2016/01/08/native-puppet-4-data-in-modules.php** 11 | 12 | While hiera does a decent job of separating code and data for users 13 | it is quite difficult for module authors to use hiera to create reusable 14 | modules. This is because the puppet backend is optional and even when 15 | it is loaded the module author cannot influence the hierarchy. 16 | 17 | With this commit we add a new module_data backend that loads data from 18 | within a module and allow the module author to set a hierarchy for this 19 | data. 20 | 21 | The goal of this backend is to allow module authors to specify true 22 | module default data in their modules but still allow users to override 23 | the data using the standard method - especially useful with the puppet 3 24 | hiera integration. 25 | 26 | This backend is always loaded as the least important tier in the 27 | hierarchy - unless a user choose to put it somewhere specific, but this 28 | backend will always be enabled. 29 | 30 | Given a module layout: 31 | 32 | your_module 33 | ├── data 34 | │ ├── hiera.yaml 35 | │ └── osfamily 36 | │ ├── Debian.yaml 37 | │ └── RedHat.yaml 38 | └── manifests 39 | └── init.pp 40 | 41 | The hiera.yaml is optional in this example it would be: 42 | 43 | --- 44 | :hierarchy: 45 | - osfamily/%{::osfamily} 46 | - common 47 | 48 | But when no hiera.yaml exist in the module, the default would be: 49 | 50 | --- 51 | :hierarchy: 52 | - common 53 | 54 | The data directory is then a standard Hiera data store. 55 | 56 | Status? 57 | ------- 58 | 59 | This is but a first stab at turning my old pull request for ticket 16856 60 | into a standalone module that any > 3.0.0 Puppet user can depend on to 61 | get this essential feature. 62 | 63 | Some more testing is needed, sanity checking for support versions etc so 64 | consider this a early feedback-saught release 65 | 66 | Contact? 67 | -------- 68 | 69 | R.I.Pienaar / rip@devco.net / @ripienaar / http://devco.net 70 | -------------------------------------------------------------------------------- /lib/hiera/backend/module_data_backend.rb: -------------------------------------------------------------------------------- 1 | class Hiera 2 | module Backend 3 | class Module_data_backend 4 | def initialize(cache=nil) 5 | require 'yaml' 6 | require 'hiera/filecache' 7 | 8 | if Puppet.version >= "4.0.0" 9 | Puppet.warning("The ripienaar-module_data Hiera backend is not supported on Puppet 4 and will quite likely break your setup, please upgrade to the native Puppet 4 Data in Modules. See http://srt.ly/jg for more details.") 10 | end 11 | 12 | Hiera.debug("Hiera Module Data backend starting") 13 | 14 | @cache = cache || Filecache.new 15 | end 16 | 17 | def load_module_config(module_name, environment) 18 | default_config = {:hierarchy => ["common"]} 19 | 20 | mod = Puppet::Module.find(module_name, environment) || Puppet::Module.find(module_name) 21 | 22 | return default_config unless mod 23 | 24 | path = mod.path 25 | module_config = File.join(path, "data", "hiera.yaml") 26 | config = {} 27 | 28 | if File.exist?(module_config) 29 | Hiera.debug("Reading config from %s file" % module_config) 30 | config = load_data(module_config) 31 | end 32 | 33 | config["path"] = path 34 | 35 | default_config.merge(config) 36 | end 37 | 38 | def load_data(path) 39 | return {} unless File.exist?(path) 40 | 41 | @cache.read(path, Hash, {}) do |data| 42 | if path.end_with? "/hiera.yaml" 43 | YAML.load(data, :deserialize_symbols => true) 44 | else 45 | YAML.load(data) 46 | end 47 | end 48 | end 49 | 50 | def no_answer 51 | if Puppet.version >= "4.0.0" 52 | Puppet.warning("The ripienaar-module_data Hiera backend is not supported on Puppet 4 and will quite likely break your setup, please upgrade to the native Puppet 4 Data in Modules. See http://srt.ly/jg for more details.") 53 | throw :no_such_key 54 | else 55 | nil 56 | end 57 | end 58 | 59 | def lookup(key, scope, order_override, resolution_type) 60 | answer = nil 61 | found = false 62 | 63 | Hiera.debug("Looking up %s in Module Data backend" % key) 64 | 65 | module_name = begin 66 | scope["module_name"] 67 | rescue Puppet::ParseError # Gets thrown if not in a module and strict_variables = true 68 | end 69 | 70 | unless module_name 71 | Hiera.debug("Skipping Module Data backend as this does not look like a module") 72 | return no_answer 73 | end 74 | 75 | config = load_module_config(scope["module_name"], scope["::environment"]) 76 | unless config["path"] 77 | Hiera.debug("Could not find a path to the module '%s' in environment '%s'" % [scope["module_name"], scope["::environment"]]) 78 | return no_answer 79 | end 80 | 81 | config[:hierarchy].insert(0, order_override) if order_override 82 | config[:hierarchy].each do |source| 83 | source = File.join(config["path"], "data", "%s.yaml" % Backend.parse_string(source, scope)) 84 | 85 | Hiera.debug("Looking for data in source %s" % source) 86 | data = load_data(source) 87 | 88 | raise("Data loaded from %s should be a hash but got %s" % [source, data.class]) unless data.is_a?(Hash) 89 | 90 | next if data.empty? 91 | next unless data.include?(key) 92 | found = true 93 | 94 | new_answer = Backend.parse_answer(data[key], scope) 95 | case resolution_type 96 | when :array 97 | raise("Hiera type mismatch: expected Array and got %s" % new_answer.class) unless (new_answer.kind_of?(Array) || new_answer.kind_of?(String)) 98 | answer ||= [] 99 | answer << new_answer 100 | 101 | when :hash 102 | raise("Hiera type mismatch: expected Hash and got %s" % new_answer.class) unless new_answer.kind_of?(Hash) 103 | answer ||= {} 104 | answer = Backend.merge_answer(new_answer, answer) 105 | else 106 | answer = new_answer 107 | break 108 | end 109 | end 110 | 111 | if !found 112 | return no_answer 113 | else 114 | return answer 115 | end 116 | end 117 | end 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /lib/puppet/indirector/data_binding/hiera.rb: -------------------------------------------------------------------------------- 1 | require "hiera" 2 | require "hiera/config" 3 | require "hiera/scope" 4 | 5 | begin 6 | require 'puppet/indirector/hiera' 7 | rescue LoadError => e 8 | begin 9 | require "puppet/indirector/code" 10 | rescue LoadError => e 11 | $stderr.puts "Couldn't require either of puppet/indirector/{hiera,code}!" 12 | end 13 | end 14 | 15 | 16 | class Hiera::Config 17 | class << self 18 | alias :old_load :load unless respond_to?(:old_load) 19 | 20 | def load(source) 21 | old_load(source) 22 | 23 | @config[:backends] << "module_data" unless @config[:backends].include?("module_data") 24 | 25 | @config 26 | end 27 | end 28 | end 29 | 30 | class Puppet::DataBinding::Hiera < Puppet::Indirector::Code 31 | desc "Retrieve data using Hiera." 32 | 33 | def initialize(*args) 34 | if ! Puppet.features.hiera? 35 | raise "Hiera terminus not supported without hiera library" 36 | end 37 | super 38 | end 39 | 40 | if defined?(::Psych::SyntaxError) 41 | DataBindingExceptions = [::StandardError, ::Psych::SyntaxError] 42 | else 43 | DataBindingExceptions = [::StandardError] 44 | end 45 | 46 | def find(request) 47 | hiera.lookup(request.key, nil, Hiera::Scope.new(request.options[:variables]), nil, nil) 48 | rescue *DataBindingExceptions => detail 49 | raise Puppet::DataBinding::LookupError.new(detail.message, detail) 50 | end 51 | 52 | private 53 | 54 | def self.hiera_config 55 | hiera_config = Puppet.settings[:hiera_config] 56 | config = {} 57 | 58 | if File.exist?(hiera_config) 59 | config = Hiera::Config.load(hiera_config) 60 | else 61 | Puppet.warning "Config file #{hiera_config} not found, using Hiera defaults" 62 | end 63 | 64 | config[:logger] = 'puppet' 65 | config 66 | end 67 | 68 | def self.hiera 69 | @hiera ||= Hiera.new(:config => hiera_config) 70 | end 71 | 72 | def hiera 73 | self.class.hiera 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "R.I.Pienaar ", 3 | "license": "ASL 2.0", 4 | "name": "ripienaar-module_data", 5 | "project_page": "https://github.com/ripienaar/puppet-module-data", 6 | "source": "https://github.com/ripienaar/puppet-module-data.git", 7 | "summary": "A hiera backend to allow the use of data while writing sharable modules", 8 | "version": "0.5.1", 9 | "dependencies": [] 10 | } 11 | --------------------------------------------------------------------------------