Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(GH-213) Gather facts using the Sidecar #214

Merged
merged 1 commit into from
Jan 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions lib/puppet-languageserver-sidecar/facter_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

module PuppetLanguageServerSidecar
module FacterHelper
def self.current_environment
begin
env = Puppet.lookup(:environments).get!(Puppet.settings[:environment])
return env unless env.nil?
rescue Puppet::Environments::EnvironmentNotFound
PuppetLanguageServerSidecar.log_message(:warning, "[FacterHelper::current_environment] Unable to load environment #{Puppet.settings[:environment]}")
rescue StandardError => e
PuppetLanguageServerSidecar.log_message(:warning, "[FacterHelper::current_environment] Error loading environment #{Puppet.settings[:environment]}: #{e}")
end
Puppet.lookup(:current_environment)
end

def self.retrieve_facts(_cache, _options = {})
require 'puppet/indirector/facts/facter'

PuppetLanguageServerSidecar.log_message(:debug, '[FacterHelper::retrieve_facts] Starting')
facts = PuppetLanguageServer::Sidecar::Protocol::Facts.new
begin
req = Puppet::Indirector::Request.new(:facts, :find, 'language_server', nil, environment: current_environment)
result = Puppet::Node::Facts::Facter.new.find(req)
facts.from_h!(result.values)
rescue StandardError => e
PuppetLanguageServerSidecar.log_message(:error, "[FacterHelper::_load_facts] Error loading facts #{e.message} #{e.backtrace}")
rescue LoadError => e
PuppetLanguageServerSidecar.log_message(:error, "[FacterHelper::_load_facts] Error loading facts (LoadError) #{e.message} #{e.backtrace}")
end

PuppetLanguageServerSidecar.log_message(:debug, "[FacterHelper::retrieve_facts] Finished loading #{facts.keys.count} facts")
facts
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,21 @@ def modulepath
result
end
end

# Inject the workspace into the facter search paths
require 'puppet/indirector/facts/facter'
class Puppet::Node::Facts::Facter # rubocop:disable Style/ClassAndModuleChildren
class << self
alias_method :original_setup_search_paths, :setup_search_paths
def setup_search_paths(request)
result = original_setup_search_paths(request)
return result unless PuppetLanguageServerSidecar::Workspace.has_module_metadata?

additional_dirs = %w[lib plugins].map { |path| File.join(PuppetLanguageServerSidecar::Workspace.root_path, path, 'facter') }
.select { |path| FileTest.directory?(path) }

return result if additional_dirs.empty?
Facter.search(*additional_dirs)
end
end
end
21 changes: 21 additions & 0 deletions lib/puppet-languageserver/sidecar_protocol.rb
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,27 @@ def list_for_object_class(klass)
raise "Unknown object class #{klass.name}"
end
end

class Facts < Hash
include Base

def from_h!(value)
value.keys.each { |key| self[key] = value[key] }
self
end

def to_json(*options)
::JSON.generate(to_h, options)
end

def from_json!(json_string)
obj = ::JSON.parse(json_string)
obj.each do |key, value|
self[key] = value
end
self
end
end
end
end
end
10 changes: 10 additions & 0 deletions lib/puppet_languageserver_sidecar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ def self.require_gems(options)
puppet_parser_helper
sidecar_protocol_extensions
workspace
facter_helper
]

# Load files based on feature flags
Expand Down Expand Up @@ -117,6 +118,7 @@ def self.require_gems(options)
workspace_datatypes
workspace_functions
workspace_types
facts
].freeze

class CommandLineParser
Expand Down Expand Up @@ -389,6 +391,14 @@ def self.execute(options)
PuppetLanguageServerSidecar::PuppetHelper.retrieve_types(null_cache)
end

when 'facts'
# Can't cache for facts
cache = PuppetLanguageServerSidecar::Cache::Null.new
# Inject the workspace etc. if present
injected = inject_workspace_as_module
inject_workspace_as_environment unless injected
PuppetLanguageServerSidecar::FacterHelper.retrieve_facts(cache)

else
log_message(:error, "Unknown action #{options[:action]}. Expected one of #{ACTION_LIST}")
end
Expand Down
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Facter.add('fixture_agent_custom_fact') do
setcode do
'fixture_agent_custom_fact_value'
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
fixture_environment_external_fact: "fixture_environment_external_fact_value"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Facter.add('fixture_environment_custom_fact') do
setcode do
'fixture_environment_custom_fact_value'
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
fixture_module_external_fact: "fixture_module_external_fact_value"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Facter.add('fixture_module_custom_fact') do
setcode do
'fixture_module_custom_fact_value'
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
require 'spec_helper'
require 'open3'

describe 'PuppetLanguageServerSidecar::FacterHelper' do
let(:subject) { PuppetLanguageServerSidecar::FacterHelper }

def run_sidecar(cmd_options)
# Use a new array so we don't affect the original cmd_options)
cmd = cmd_options.dup

# Append the puppet test-fixtures
cmd << '--puppet-settings'
cmd << "--vardir,#{File.join($fixtures_dir, 'real_agent', 'cache')},--confdir,#{File.join($fixtures_dir, 'real_agent', 'confdir')}"

cmd.unshift('puppet-languageserver-sidecar')
cmd.unshift('ruby')
stdout, _stderr, status = Open3.capture3(*cmd)

raise "Expected exit code of 0, but got #{status.exitstatus} #{_stderr}" unless status.exitstatus.zero?
return stdout.bytes.pack('U*')
end

let(:default_fact_names) { ['hostname', 'fixture_agent_custom_fact'] }
let(:module_fact_names) { ['fixture_module_custom_fact', 'fixture_module_external_fact'] }
let(:environment_fact_names) { ['fixture_environment_custom_fact', 'fixture_environment_external_fact'] }

describe 'when running facts action' do
let (:cmd_options) { ['--action', 'facts'] }

it 'should return a deserializable facts object with all default facts' do
result = run_sidecar(cmd_options)
deserial = PuppetLanguageServer::Sidecar::Protocol::Facts.new
expect { deserial.from_json!(result) }.to_not raise_error

default_fact_names.each do |name|
expect(deserial).to include(name)
end

module_fact_names.each do |name|
expect(deserial).not_to include(name)
end
end
end

context 'given a workspace containing a module' do
# Test fixtures used is fixtures/valid_module_workspace
let(:workspace) { File.join($fixtures_dir, 'valid_module_workspace') }

describe 'when running facts action' do
let (:cmd_options) { ['--action', 'facts', '--local-workspace', workspace] }

it 'should return a deserializable facts object with default facts and workspace facts' do
result = run_sidecar(cmd_options)
deserial = PuppetLanguageServer::Sidecar::Protocol::Facts.new
expect { deserial.from_json!(result) }.to_not raise_error

default_fact_names.each do |name|
expect(deserial).to include(name)
end

module_fact_names.each do |name|
expect(deserial).to include(name)
end
end
end
end

context 'given a workspace containing an environment.conf' do
# Test fixtures used is fixtures/valid_environment_workspace
let(:workspace) { File.join($fixtures_dir, 'valid_environment_workspace') }

describe 'when running facts action' do
let (:cmd_options) { ['--action', 'facts', '--local-workspace', workspace] }

it 'should return a deserializable facts object with default facts and workspace facts' do
result = run_sidecar(cmd_options)
deserial = PuppetLanguageServer::Sidecar::Protocol::Facts.new
expect { deserial.from_json!(result) }.to_not raise_error

default_fact_names.each do |name|
expect(deserial).to include(name)
end

environment_fact_names.each do |name|
expect(deserial).to include(name)
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
deserial = subject_klass.new.from_json!(serial)

subject.keys.each do |key|
expect(deserial[key]).to eq(deserial[key])
expect(deserial[key]).to eq(subject[key])
end
end
end
Expand Down Expand Up @@ -131,6 +131,30 @@
end
end

describe 'Facts' do
let(:subject_klass) { PuppetLanguageServer::Sidecar::Protocol::Facts }
let(:subject) {
value = subject_klass.new
value['val1_' + rand(1000).to_s] = rand(1000).to_s
value['val2_' + rand(1000).to_s] = rand(1000).to_s
value['val3_' + rand(1000).to_s] = rand(1000).to_s
value
}

it_should_behave_like 'a base Sidecar Protocol object'

describe '#from_json!' do
it "should deserialize a serialized value" do
serial = subject.to_json
deserial = subject_klass.new.from_json!(serial)

subject.keys.each do |key|
expect(deserial[key]).to eq(subject[key])
end
end
end
end

describe 'NodeGraph' do
let(:subject_klass) { PuppetLanguageServer::Sidecar::Protocol::NodeGraph }
let(:subject) {
Expand Down