#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' describe "when using a hiera data provider" do include PuppetSpec::Compiler # There is a fully configured 'sample' environment in fixtures at this location let(:environmentpath) { parent_fixture('environments') } let(:facts) { Puppet::Node::Facts.new("facts", {}) } around(:each) do |example| # Initialize settings to get a full compile as close as possible to a real # environment load Puppet.settings.initialize_global_settings # Initialize loaders based on the environmentpath. It does not work to # just set the setting environmentpath for some reason - this achieves the same: # - first a loader is created, loading directory environments from the fixture (there is # one environment, 'sample', which will be loaded since the node references this # environment by name). # - secondly, the created env loader is set as 'environments' in the puppet context. # loader = Puppet::Environments::Directories.new(environmentpath, []) Puppet.override(:environments => loader) do example.run end end def compile_and_get_notifications(environment, code = nil) extract_notifications(compile(environment, code)) end def compile(environment, code = nil) Puppet[:code] = code if code node = Puppet::Node.new("testnode", :facts => facts, :environment => environment) compiler = Puppet::Parser::Compiler.new(node) block_given? ? compiler.compile { |catalog| yield(compiler); catalog } : compiler.compile end def extract_notifications(catalog) catalog.resources.map(&:ref).select { |r| r.start_with?('Notify[') }.map { |r| r[7..-2] } end it 'uses default configuration for environment and module data' do resources = compile_and_get_notifications('hiera_defaults') expect(resources).to include('module data param_a is 100, param default is 200, env data param_c is 300') end it 'reads hiera.yaml in environment root and configures multiple json and yaml providers' do resources = compile_and_get_notifications('hiera_env_config') expect(resources).to include("env data param_a is 10, env data param_b is 20, env data param_c is 30, env data param_d is 40, env data param_e is 50, env data param_yaml_utf8 is \u16EB\u16D2\u16E6, env data param_json_utf8 is \u16A0\u16C7\u16BB") end it 'reads hiera.yaml in module root and configures multiple json and yaml providers' do resources = compile_and_get_notifications('hiera_module_config') expect(resources).to include('module data param_a is 100, module data param_b is 200, module data param_c is 300, module data param_d is 400, module data param_e is 500') end it 'keeps lookup_options in one module separate from lookup_options in another' do resources1 = compile('hiera_modules', 'include one').resources.select {|r| r.ref.start_with?('Class[One]')} resources2 = compile('hiera_modules', 'include two').resources.select {|r| r.ref.start_with?('Class[One]')} expect(resources1).to eq(resources2) end it 'does not perform merge of values declared in environment and module when resolving parameters' do resources = compile_and_get_notifications('hiera_misc') expect(resources).to include('env 1, ') end it 'performs hash merge of values declared in environment and module' do resources = compile_and_get_notifications('hiera_misc', '$r = lookup(one::test::param, Hash[String,String], hash) notify{"${r[key1]}, ${r[key2]}":}') expect(resources).to include('env 1, module 2') end it 'performs unique merge of values declared in environment and module' do resources = compile_and_get_notifications('hiera_misc', '$r = lookup(one::array, Array[String], unique) notify{"${r}":}') expect(resources.size).to eq(1) expect(resources[0][1..-2].split(', ')).to contain_exactly('first', 'second', 'third', 'fourth') end it 'performs merge found in lookup_options in environment of values declared in environment and module' do resources = compile_and_get_notifications('hiera_misc', 'include one::lopts_test') expect(resources.size).to eq(1) expect(resources[0]).to eq('A, B, C, MA, MB, MC') end it 'performs merge found in lookup_options in module of values declared in environment and module' do resources = compile_and_get_notifications('hiera_misc', 'include one::loptsm_test') expect(resources.size).to eq(1) expect(resources[0]).to eq('A, B, C, MA, MB, MC') end it "will not find 'lookup_options' as a regular value" do expect { compile_and_get_notifications('hiera_misc', '$r = lookup("lookup_options")') }.to raise_error(Puppet::DataBinding::LookupError, /did not find a value/) end it 'does find unqualified keys in the environment' do resources = compile_and_get_notifications('hiera_misc', 'notify{lookup(ukey1):}') expect(resources).to include('Some value') end it 'does not find unqualified keys in the module' do expect do compile_and_get_notifications('hiera_misc', 'notify{lookup(ukey2):}') end.to raise_error(Puppet::ParseError, /did not find a value for the name 'ukey2'/) end it 'can use interpolation lookup method "alias"' do resources = compile_and_get_notifications('hiera_misc', 'notify{lookup(km_alias):}') expect(resources).to include('Value from interpolation with alias') end it 'can use interpolation lookup method "lookup"' do resources = compile_and_get_notifications('hiera_misc', 'notify{lookup(km_lookup):}') expect(resources).to include('Value from interpolation with lookup') end it 'can use interpolation lookup method "hiera"' do resources = compile_and_get_notifications('hiera_misc', 'notify{lookup(km_hiera):}') expect(resources).to include('Value from interpolation with hiera') end it 'can use interpolation lookup method "literal"' do resources = compile_and_get_notifications('hiera_misc', 'notify{lookup(km_literal):}') expect(resources).to include('Value from interpolation with literal') end it 'can use interpolation lookup method "scope"' do resources = compile_and_get_notifications('hiera_misc', '$target_scope = "with scope" notify{lookup(km_scope):}') expect(resources).to include('Value from interpolation with scope') end it 'can use interpolation using default lookup method (scope)' do resources = compile_and_get_notifications('hiera_misc', '$target_default = "with default" notify{lookup(km_default):}') expect(resources).to include('Value from interpolation with default') end it 'performs lookup using qualified expressions in interpolation' do resources = compile_and_get_notifications('hiera_misc', "$os = { name => 'Fedora' } notify{lookup(km_qualified):}") expect(resources).to include('Value from qualified interpolation OS = Fedora') end it 'can have multiple interpolate expressions in one value' do resources = compile_and_get_notifications('hiera_misc', 'notify{lookup(km_multi):}') expect(resources).to include('cluster/%{::cluster}/%{role}') end it 'performs single quoted interpolation' do resources = compile_and_get_notifications('hiera_misc', 'notify{lookup(km_sqalias):}') expect(resources).to include('Value from interpolation with alias') end it 'uses compiler lifecycle for caching' do Puppet[:code] = 'notify{lookup(one::my_var):}' node = Puppet::Node.new('testnode', :facts => facts, :environment => 'hiera_module_config') compiler = Puppet::Parser::Compiler.new(node) compiler.topscope['my_fact'] = 'server1' expect(extract_notifications(compiler.compile)).to include('server1') compiler = Puppet::Parser::Compiler.new(node) compiler.topscope['my_fact'] = 'server2' expect(extract_notifications(compiler.compile)).to include('server2') compiler = Puppet::Parser::Compiler.new(node) expect(extract_notifications(compiler.compile)).to include('In name.yaml') end it 'traps endless interpolate recursion' do expect do compile_and_get_notifications('hiera_misc', '$r1 = "%{r2}" $r2 = "%{r1}" notify{lookup(recursive):}') end.to raise_error(Puppet::DataBinding::RecursiveLookupError, /detected in \[recursive, r1, r2\]/) end it 'traps bad alias declarations' do expect do compile_and_get_notifications('hiera_misc', "$r1 = 'Alias within string %{alias(\"r2\")}' $r2 = '%{r1}' notify{lookup(recursive):}") end.to raise_error(Puppet::DataBinding::LookupError, /'alias' interpolation is only permitted if the expression is equal to the entire string/) end it 'reports syntax errors for JSON files' do expect do compile_and_get_notifications('hiera_bad_syntax_json') end.to raise_error(Puppet::DataBinding::LookupError, /Unable to parse \(#{environmentpath}[^)]+\):/) end it 'reports syntax errors for YAML files' do expect do compile_and_get_notifications('hiera_bad_syntax_yaml') end.to raise_error(Puppet::DataBinding::LookupError, /Unable to parse \(#{environmentpath}[^)]+\):/) end describe 'when using explain' do it 'will report config path (original and resolved), data path (original and resolved), and interpolation (before and after)' do compile('hiera_misc', '$target_scope = "with scope"') do |compiler| lookup_invocation = Puppet::Pops::Lookup::Invocation.new(compiler.topscope, {}, {}, true) value = Puppet::Pops::Lookup.lookup('km_scope', nil, nil, nil, nil, lookup_invocation) expect(lookup_invocation.explainer.to_s).to eq(<<EOS) Merge strategy first Data Binding "hiera" No such key: "km_scope" Data Provider "Hiera Data Provider, version 4" ConfigurationPath "#{environmentpath}/hiera_misc/hiera.yaml" Data Provider "common" Path "#{environmentpath}/hiera_misc/data/common.yaml" Original path: "common" Interpolation on "Value from interpolation %{scope("target_scope")}" Global Scope Found key: "target_scope" value: "with scope" Found key: "km_scope" value: "Value from interpolation with scope" Merged result: "Value from interpolation with scope" EOS end end it 'will report that merge options was found in the lookup_options hash' do compile('hiera_misc', '$target_scope = "with scope"') do |compiler| lookup_invocation = Puppet::Pops::Lookup::Invocation.new(compiler.topscope, {}, {}, true) value = Puppet::Pops::Lookup.lookup('one::loptsm_test::hash', nil, nil, nil, nil, lookup_invocation) expect(lookup_invocation.explainer.to_s).to eq(<<EOS) Using merge options from "lookup_options" hash Merge strategy deep Data Binding "hiera" No such key: "one::loptsm_test::hash" Data Provider "Hiera Data Provider, version 4" ConfigurationPath "#{environmentpath}/hiera_misc/hiera.yaml" Data Provider "common" Path "#{environmentpath}/hiera_misc/data/common.yaml" Original path: "common" Found key: "one::loptsm_test::hash" value: { "a" => "A", "b" => "B", "m" => { "ma" => "MA", "mb" => "MB" } } Module "one" using Data Provider "Hiera Data Provider, version 4" ConfigurationPath "#{environmentpath}/hiera_misc/modules/one/hiera.yaml" Data Provider "common" Path "#{environmentpath}/hiera_misc/modules/one/data/common.yaml" Original path: "common" Found key: "one::loptsm_test::hash" value: { "a" => "A", "c" => "C", "m" => { "ma" => "MA", "mc" => "MC", "mb" => "MB" } } Merged result: { "a" => "A", "c" => "C", "m" => { "ma" => "MA", "mc" => "MC", "mb" => "MB" }, "b" => "B" } EOS end end it 'will report lookup_options details in combination with details of found value' do compile('hiera_misc', '$target_scope = "with scope"') do |compiler| lookup_invocation = Puppet::Pops::Lookup::Invocation.new(compiler.topscope, {}, {}, Puppet::Pops::Lookup::Explainer.new(true)) value = Puppet::Pops::Lookup.lookup('one::loptsm_test::hash', nil, nil, nil, nil, lookup_invocation) expect(lookup_invocation.explainer.to_s).to eq(<<EOS) Searching for "lookup_options" Merge strategy hash Data Binding "hiera" No such key: "lookup_options" Data Provider "Hiera Data Provider, version 4" ConfigurationPath "#{environmentpath}/hiera_misc/hiera.yaml" Data Provider "common" Path "#{environmentpath}/hiera_misc/data/common.yaml" Original path: "common" Found key: "lookup_options" value: { "one::lopts_test::hash" => { "merge" => "deep" } } Module "one" using Data Provider "Hiera Data Provider, version 4" ConfigurationPath "#{environmentpath}/hiera_misc/modules/one/hiera.yaml" Data Provider "common" Path "#{environmentpath}/hiera_misc/modules/one/data/common.yaml" Original path: "common" Found key: "lookup_options" value: { "one::loptsm_test::hash" => { "merge" => "deep" } } Merged result: { "one::loptsm_test::hash" => { "merge" => "deep" }, "one::lopts_test::hash" => { "merge" => "deep" } } Searching for "one::loptsm_test::hash" Merge strategy deep Data Binding "hiera" No such key: "one::loptsm_test::hash" Data Provider "Hiera Data Provider, version 4" ConfigurationPath "#{environmentpath}/hiera_misc/hiera.yaml" Data Provider "common" Path "#{environmentpath}/hiera_misc/data/common.yaml" Original path: "common" Found key: "one::loptsm_test::hash" value: { "a" => "A", "b" => "B", "m" => { "ma" => "MA", "mb" => "MB" } } Module "one" using Data Provider "Hiera Data Provider, version 4" ConfigurationPath "#{environmentpath}/hiera_misc/modules/one/hiera.yaml" Data Provider "common" Path "#{environmentpath}/hiera_misc/modules/one/data/common.yaml" Original path: "common" Found key: "one::loptsm_test::hash" value: { "a" => "A", "c" => "C", "m" => { "ma" => "MA", "mc" => "MC", "mb" => "MB" } } Merged result: { "a" => "A", "c" => "C", "m" => { "ma" => "MA", "mc" => "MC", "mb" => "MB" }, "b" => "B" } EOS end end it 'will report config path (original and resolved), data path (original and resolved), and interpolation (before and after)' do compile('hiera_misc', '$target_scope = "with scope"') do |compiler| lookup_invocation = Puppet::Pops::Lookup::Invocation.new(compiler.topscope, {}, {}, Puppet::Pops::Lookup::Explainer.new(true, true)) value = Puppet::Pops::Lookup.lookup('one::loptsm_test::hash', nil, nil, nil, nil, lookup_invocation) expect(lookup_invocation.explainer.to_s).to eq(<<EOS) Merge strategy hash Data Binding "hiera" No such key: "lookup_options" Data Provider "Hiera Data Provider, version 4" ConfigurationPath "#{environmentpath}/hiera_misc/hiera.yaml" Data Provider "common" Path "#{environmentpath}/hiera_misc/data/common.yaml" Original path: "common" Found key: "lookup_options" value: { "one::lopts_test::hash" => { "merge" => "deep" } } Module "one" using Data Provider "Hiera Data Provider, version 4" ConfigurationPath "#{environmentpath}/hiera_misc/modules/one/hiera.yaml" Data Provider "common" Path "#{environmentpath}/hiera_misc/modules/one/data/common.yaml" Original path: "common" Found key: "lookup_options" value: { "one::loptsm_test::hash" => { "merge" => "deep" } } Merged result: { "one::loptsm_test::hash" => { "merge" => "deep" }, "one::lopts_test::hash" => { "merge" => "deep" } } EOS end end end def parent_fixture(dir_name) File.absolute_path(File.join(my_fixture_dir(), "../#{dir_name}")) end def resources_in(catalog) catalog.resources.map(&:ref) end end