require 'spec_helper' require 'puppet/indirector/facts/facter' class Puppet::Node::Facts::Facter::MyCollection < Hash; end describe Puppet::Node::Facts::Facter do FS = Puppet::FileSystem it "should be a subclass of the Code terminus" do expect(Puppet::Node::Facts::Facter.superclass).to equal(Puppet::Indirector::Code) end it "should have documentation" do expect(Puppet::Node::Facts::Facter.doc).not_to be_nil end it "should be registered with the configuration store indirection" do indirection = Puppet::Indirector::Indirection.instance(:facts) expect(Puppet::Node::Facts::Facter.indirection).to equal(indirection) end it "should have its name set to :facter" do expect(Puppet::Node::Facts::Facter.name).to eq(:facter) end before :each do @facter = Puppet::Node::Facts::Facter.new allow(Facter).to receive(:to_hash).and_return({}) @name = "me" @request = double('request', :key => @name) @environment = double('environment') allow(@request).to receive(:environment).and_return(@environment) allow(@request).to receive(:options).and_return({}) allow(@request.environment).to receive(:modules).and_return([]) allow(@request.environment).to receive(:modulepath).and_return([]) end describe 'when finding facts' do it 'should reset facts' do expect(Facter).to receive(:reset).ordered expect(Puppet::Node::Facts::Facter).to receive(:setup_search_paths).ordered @facter.find(@request) end it 'should add the puppetversion and agent_specified_environment facts' do expect(Facter).to receive(:reset).ordered expect(Facter).to receive(:add).with(:puppetversion) expect(Facter).to receive(:add).with(:agent_specified_environment) @facter.find(@request) end it 'should include external facts' do expect(Facter).to receive(:reset).ordered expect(Puppet::Node::Facts::Facter).to receive(:setup_external_search_paths).ordered expect(Puppet::Node::Facts::Facter).to receive(:setup_search_paths).ordered @facter.find(@request) end it "should return a Facts instance" do expect(@facter.find(@request)).to be_instance_of(Puppet::Node::Facts) end it "should return a Facts instance with the provided key as the name" do expect(@facter.find(@request).name).to eq(@name) end it "should return the Facter facts as the values in the Facts instance" do expect(Facter).to receive(:resolve).and_return("one" => "two") facts = @facter.find(@request) expect(facts.values["one"]).to eq("two") end it "should add local facts" do facts = Puppet::Node::Facts.new("foo") expect(Puppet::Node::Facts).to receive(:new).and_return(facts) expect(facts).to receive(:add_local_facts) @facter.find(@request) end it "should sanitize facts" do facts = Puppet::Node::Facts.new("foo") expect(Puppet::Node::Facts).to receive(:new).and_return(facts) expect(facts).to receive(:sanitize) @facter.find(@request) end it "can exclude legacy facts" do Puppet[:include_legacy_facts] = false expect(Puppet.runtime[:facter]).to receive(:resolve).with('') .and_return({'kernelversion' => '1.2.3'}) values = @facter.find(@request).values expect(values).to be_an_instance_of(Hash) expect(values).to include({'kernelversion' => '1.2.3'}) end it "can exclude legacy facts using buggy facter implementations that return a Hash subclass" do Puppet[:include_legacy_facts] = false collection = Puppet::Node::Facts::Facter::MyCollection["kernelversion" => '1.2.3'] expect(Puppet.runtime[:facter]).to receive(:resolve).with('') .and_return(collection) values = @facter.find(@request).values expect(values).to be_an_instance_of(Hash) expect(values).to include({'kernelversion' => '1.2.3'}) end end it 'should fail when saving facts' do expect { @facter.save(@facts) }.to raise_error(Puppet::DevError) end it 'should fail when destroying facts' do expect { @facter.destroy(@facts) }.to raise_error(Puppet::DevError) end describe 'when setting up search paths' do let(:factpath1) { File.expand_path 'one' } let(:factpath2) { File.expand_path 'two' } let(:factpath) { [factpath1, factpath2].join(File::PATH_SEPARATOR) } let(:modulepath) { File.expand_path 'module/foo' } let(:modulelibfacter) { File.expand_path 'module/foo/lib/facter' } let(:modulepluginsfacter) { File.expand_path 'module/foo/plugins/facter' } before :each do expect(FileTest).to receive(:directory?).with(factpath1).and_return(true) expect(FileTest).to receive(:directory?).with(factpath2).and_return(true) allow(@request.environment).to receive(:modulepath).and_return([modulepath]) allow(@request).to receive(:options).and_return({}) expect(Dir).to receive(:glob).with("#{modulepath}/*/lib/facter").and_return([modulelibfacter]) expect(Dir).to receive(:glob).with("#{modulepath}/*/plugins/facter").and_return([modulepluginsfacter]) Puppet[:factpath] = factpath end it 'should skip files' do expect(FileTest).to receive(:directory?).with(modulelibfacter).and_return(false) expect(FileTest).to receive(:directory?).with(modulepluginsfacter).and_return(false) expect(Facter).to receive(:search).with(factpath1, factpath2) Puppet::Node::Facts::Facter.setup_search_paths @request end it 'should add directories' do expect(FileTest).to receive(:directory?).with(modulelibfacter).and_return(true) expect(FileTest).to receive(:directory?).with(modulepluginsfacter).and_return(true) expect(Facter).to receive(:search).with(modulelibfacter, modulepluginsfacter, factpath1, factpath2) Puppet::Node::Facts::Facter.setup_search_paths @request end end describe 'when setting up external search paths' do let(:pluginfactdest) { File.expand_path 'plugin/dest' } let(:modulepath) { File.expand_path 'module/foo' } let(:modulefactsd) { File.expand_path 'module/foo/facts.d' } before :each do expect(FileTest).to receive(:directory?).with(pluginfactdest).and_return(true) mod = Puppet::Module.new('foo', modulepath, @request.environment) allow(@request.environment).to receive(:modules).and_return([mod]) Puppet[:pluginfactdest] = pluginfactdest end it 'should skip files' do expect(File).to receive(:directory?).with(modulefactsd).and_return(false) expect(Facter).to receive(:search_external).with([pluginfactdest]) Puppet::Node::Facts::Facter.setup_external_search_paths @request end it 'should add directories' do expect(File).to receive(:directory?).with(modulefactsd).and_return(true) expect(Facter).to receive(:search_external).with([modulefactsd, pluginfactdest]) Puppet::Node::Facts::Facter.setup_external_search_paths @request end end describe 'when :resolve_options is true' do let(:options) { { resolve_options: true, user_query: ["os", "timezone"] } } let(:facts) { Puppet::Node::Facts.new("foo") } before :each do allow(@request).to receive(:options).and_return(options) allow(Puppet::Node::Facts).to receive(:new).and_return(facts) allow(facts).to receive(:add_local_facts) end it 'should call Facter.resolve method' do expect(Facter).to receive(:resolve).with("os timezone") @facter.find(@request) end it 'should NOT add local facts' do expect(facts).not_to receive(:add_local_facts) @facter.find(@request) end context 'when --show-legacy flag is present' do let(:options) { { resolve_options: true, user_query: ["os", "timezone"], show_legacy: true } } it 'should call Facter.resolve method with show-legacy' do expect(Facter).to receive(:resolve).with("os timezone --show-legacy") @facter.find(@request) end end context 'when --timing flag is present' do let(:options) { { resolve_options: true, user_query: ["os", "timezone"], timing: true } } it 'calls Facter.resolve with --timing' do expect(Facter).to receive(:resolve).with("os timezone --timing") @facter.find(@request) end end describe 'when Facter version is lower than 4.0.40' do before :each do allow(Facter).to receive(:respond_to?).and_return(false) allow(Facter).to receive(:respond_to?).with(:resolve).and_return(false) end it 'raises an error' do expect { @facter.find(@request) }.to raise_error(Puppet::Error, "puppet facts show requires version 4.0.40 or greater of Facter.") end end describe 'when setting up external search paths' do let(:options) { { resolve_options: true, user_query: ["os", "timezone"], external_dir: 'some/dir' } } let(:pluginfactdest) { File.expand_path 'plugin/dest' } let(:modulepath) { File.expand_path 'module/foo' } let(:modulefactsd) { File.expand_path 'module/foo/facts.d' } before :each do expect(FileTest).to receive(:directory?).with(pluginfactdest).and_return(true) mod = Puppet::Module.new('foo', modulepath, @request.environment) allow(@request.environment).to receive(:modules).and_return([mod]) Puppet[:pluginfactdest] = pluginfactdest end it 'should skip files' do expect(File).to receive(:directory?).with(modulefactsd).and_return(false) expect(Facter).to receive(:search_external).with([pluginfactdest, options[:external_dir]]) Puppet::Node::Facts::Facter.setup_external_search_paths @request end it 'should add directories' do expect(File).to receive(:directory?).with(modulefactsd).and_return(true) expect(Facter).to receive(:search_external).with([modulefactsd, pluginfactdest, options[:external_dir]]) Puppet::Node::Facts::Facter.setup_external_search_paths @request end end describe 'when setting up search paths' do let(:factpath1) { File.expand_path 'one' } let(:factpath2) { File.expand_path 'two' } let(:factpath) { [factpath1, factpath2].join(File::PATH_SEPARATOR) } let(:modulepath) { File.expand_path 'module/foo' } let(:modulelibfacter) { File.expand_path 'module/foo/lib/facter' } let(:modulepluginsfacter) { File.expand_path 'module/foo/plugins/facter' } let(:options) { { resolve_options: true, custom_dir: 'some/dir' } } before :each do expect(FileTest).to receive(:directory?).with(factpath1).and_return(true) expect(FileTest).to receive(:directory?).with(factpath2).and_return(true) allow(@request.environment).to receive(:modulepath).and_return([modulepath]) expect(Dir).to receive(:glob).with("#{modulepath}/*/lib/facter").and_return([modulelibfacter]) expect(Dir).to receive(:glob).with("#{modulepath}/*/plugins/facter").and_return([modulepluginsfacter]) Puppet[:factpath] = factpath end it 'should skip files' do expect(FileTest).to receive(:directory?).with(modulelibfacter).and_return(false) expect(FileTest).to receive(:directory?).with(modulepluginsfacter).and_return(false) expect(Facter).to receive(:search).with(factpath1, factpath2, options[:custom_dir]) Puppet::Node::Facts::Facter.setup_search_paths @request end it 'should add directories' do expect(FileTest).to receive(:directory?).with(modulelibfacter).and_return(true) expect(FileTest).to receive(:directory?).with(modulepluginsfacter).and_return(false) expect(Facter).to receive(:search).with(modulelibfacter, factpath1, factpath2, options[:custom_dir]) Puppet::Node::Facts::Facter.setup_search_paths @request end end end end