diff --git a/lib/puppet-strings/hiera.rb b/lib/puppet-strings/hiera.rb new file mode 100644 index 000000000..8d3e3e631 --- /dev/null +++ b/lib/puppet-strings/hiera.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module PuppetStrings + module Hiera + require_relative 'hiera/hierarchy_data_path' + require_relative 'hiera/data' + + def self.load_config + PuppetStrings::Hiera::Data.new('hiera.yaml') + end + end +end diff --git a/lib/puppet-strings/hiera/data.rb b/lib/puppet-strings/hiera/data.rb new file mode 100644 index 000000000..3ce9a75f6 --- /dev/null +++ b/lib/puppet-strings/hiera/data.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +module PuppetStrings::Hiera + class Data + attr_reader :config_path, :data_paths + + def initialize(config_path) + @config_path = config_path + @data_paths = [] + + load_config + end + + def files + @files ||= begin + result = {} + + data_paths.each do |dp| + dp.matches.each do |file, interpolations| + unless result.key?(file) + result[file] = interpolations + end + end + end + + result + end + end + + # @return [Hash[String, Hash[String, Any]]] + # Full variable (class::var) -> filename: value + def overrides + @overrides ||= begin + overrides = {} + + files.each_key do |file| + data = YAML.load(File.read(file)) + data.each do |key, value| + overrides[key] ||= {} + overrides[key][file] = value + end + end + + overrides + end + end + + # @return [Hash[String, Hash[String, Any]]] + # variable -> filename: value + def for_class(class_name) + result = {} + overrides.each do |key, value| + override_class_name, _, variable = key.rpartition('::') + if override_class_name == class_name + result[variable] = value + end + end + result + end + + def to_s + config_path + end + + private + + def load_config + return unless File.exist?(config_path) + + config = YAML.load(File.read(config_path)) + + unless config['version'] == 5 + raise "Unsupported version '#{config['version']}'" + end + + hierarchy = config['hierarchy'] + return unless hierarchy + + hierarchy.each do |level| + data_hash = level['data_hash'] || config['defaults']['data_hash'] + next unless data_hash == 'yaml_data' + + datadir = level['datadir'] || config['defaults']['datadir'] + + if level['path'] + data_paths << PuppetStrings::Hiera::HierarchyDataPath.new(datadir, level['path']) + elsif level['paths'] + level['paths'].each do |path| + data_paths << PuppetStrings::Hiera::HierarchyDataPath.new(datadir, path) + end + end + end + end + end +end diff --git a/lib/puppet-strings/hiera/hierarchy_data_path.rb b/lib/puppet-strings/hiera/hierarchy_data_path.rb new file mode 100644 index 000000000..93b7eaa56 --- /dev/null +++ b/lib/puppet-strings/hiera/hierarchy_data_path.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module PuppetStrings::Hiera + class HierarchyDataPath + attr_reader :datadir, :path, :regex, :mapping + + def initialize(datadir, path) + @datadir = datadir + @path = path + @regex, @mapping = HierarchyDataPath.path2regex(path) + end + + def matches + result = {} + + Dir.chdir(datadir) do + Dir['**'].each do |entry| + next unless File.file?(entry) + + regex.match(entry) do |match| + full_path = File.join(datadir, entry) + interpolations = {} + + mapping.each do |name, interpolation| + interpolations[interpolation] = match.named_captures[name] + end + + result[full_path] = interpolations + end + end + end + + result + end + + def self.path2regex(path) + mapping = {} + + intermediate_result = path + + # First pass - intermediate replacements + path.scan(/%{[^}]+}/).each_with_index do |interpolation, i| + replacement = "X_INTERPOLATION_#{i}_X" + mapping[replacement] = interpolation[2..-2] + intermediate_result = intermediate_result.sub(interpolation, replacement) + end + + # Second pass - escape any special chars + escaped = Regexp.escape(intermediate_result) + + # Third pass - replacement intermediates with regex + mapping.each_key do |replacement| + escaped = escaped.sub(replacement, "(?<#{replacement}>.+)") + end + + [Regexp.new(escaped), mapping] + end + + def to_s + File.join(datadir, path) + end + end +end diff --git a/lib/puppet-strings/markdown/base.rb b/lib/puppet-strings/markdown/base.rb index 2a40cf515..d6fe605cc 100644 --- a/lib/puppet-strings/markdown/base.rb +++ b/lib/puppet-strings/markdown/base.rb @@ -143,6 +143,18 @@ def defaults @registry[:defaults] unless @registry[:defaults].nil? end + # Overrides from Hiera + # + # Hiera overrides only apply to classes. Each entry is a tuple of the + # filename it's defined in, a mapping of interpolations that were applied + # in the filename and the value inside the file. + # + # @return [Array[Tuple[String, Hash[String, String], Any]]] + # Any overrides from Hiera. + def hiera_overrides + [] + end + # @return [Hash] information needed for the table of contents def toc_info { diff --git a/lib/puppet-strings/markdown/puppet_class.rb b/lib/puppet-strings/markdown/puppet_class.rb index 4eda6c57e..7b36c06b0 100644 --- a/lib/puppet-strings/markdown/puppet_class.rb +++ b/lib/puppet-strings/markdown/puppet_class.rb @@ -9,6 +9,24 @@ def initialize(registry) super(registry, 'class') end + def hiera_overrides + @hiera_overrides ||= begin + hiera = PuppetStrings::Hiera.load_config + overrides = hiera.for_class(name) + + result = {} + + overrides.each do |variable, files| + result[variable] = files.map do |filename, value| + interpolations = hiera.files[filename] + [filename, interpolations, value] + end + end + + result + end + end + def render super(@template) end diff --git a/lib/puppet-strings/markdown/templates/classes_and_defines.erb b/lib/puppet-strings/markdown/templates/classes_and_defines.erb index 83c64ec2f..55c60cb2c 100644 --- a/lib/puppet-strings/markdown/templates/classes_and_defines.erb +++ b/lib/puppet-strings/markdown/templates/classes_and_defines.erb @@ -81,6 +81,19 @@ Options: <% if defaults && defaults[param[:name]] -%> Default value: `<%= defaults[param[:name]] %>` +<% end -%> +<% if hiera_overrides[param[:name]] -%> +
+ Hiera overrides in a detailed table + + | Filename | Interpolations | Value | + |----------|----------------|-------| +<% hiera_overrides[param[:name]].each do |filename, interpolations, value| -%> + | `<%= filename %>` | <%= interpolations.map { |i, v| "`#{i}`: `#{v}`" }.join("
") %> | `<%= value %>` | +<% end -%> + +
+ <% end -%> <% end -%> <% end -%> diff --git a/lib/puppet-strings/yard.rb b/lib/puppet-strings/yard.rb index 0d6111737..c80d4dd90 100644 --- a/lib/puppet-strings/yard.rb +++ b/lib/puppet-strings/yard.rb @@ -8,6 +8,7 @@ module PuppetStrings::Yard require 'puppet-strings/yard/handlers' require 'puppet-strings/yard/tags' require 'puppet-strings/yard/parsers' + require 'puppet-strings/hiera' require 'puppet-strings/monkey_patches/display_object_command' # Sets up YARD for use with puppet-strings. diff --git a/lib/puppet-strings/yard/code_objects/class.rb b/lib/puppet-strings/yard/code_objects/class.rb index 2a3c9b1c7..2f675831f 100644 --- a/lib/puppet-strings/yard/code_objects/class.rb +++ b/lib/puppet-strings/yard/code_objects/class.rb @@ -55,6 +55,7 @@ def to_hash hash[:docstring] = PuppetStrings::Yard::Util.docstring_to_hash(docstring) defaults = Hash[*parameters.reject{ |p| p[1].nil? }.flatten] hash[:defaults] = defaults unless defaults.nil? || defaults.empty? + #hash[:hiera_overrides] = hiera_overrides if hiera_overrides.any? hash[:source] = source unless source.nil? || source.empty? hash end