Skip to content

Commit

Permalink
Merge pull request #215 from glennsarti/gh-213-use-fact-sidecar
Browse files Browse the repository at this point in the history
(GH-213) Use Facts from the Sidecar
  • Loading branch information
jpogran authored Jan 22, 2020
2 parents 0215658 + a8c5e1f commit 131c2ea
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 93 deletions.
11 changes: 8 additions & 3 deletions lib/puppet-languageserver-sidecar/facter_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,23 @@ 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
facts = PuppetLanguageServer::Sidecar::Protocol::FactList.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)
result.values.each do |key, value|
# TODO: This isn't strictly correct e.g. fully qualified facts will look a bit odd.
# Consider a fact called foo.bar.baz = 'Hello'. Even though the fact name is `foo.bar.baz`
# it will appear in the facts object as `facts['foo'] = { 'bar' => { 'baz' => 'Hello' }}`
facts << PuppetLanguageServer::Sidecar::Protocol::Fact.new.from_h!('key' => key, 'value' => value)
end
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")
PuppetLanguageServerSidecar.log_message(:debug, "[FacterHelper::retrieve_facts] Finished loading #{facts.count} facts")
facts
end
end
Expand Down
77 changes: 32 additions & 45 deletions lib/puppet-languageserver/facter_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,62 +2,49 @@

module PuppetLanguageServer
module FacterHelper
@ops_lock = Mutex.new
@facts_loaded = nil

def self.reset
@ops_lock.synchronize do
_reset
end
def self.cache
PuppetLanguageServer::PuppetHelper.cache
end

def self.load_facts_async
Thread.new do
load_facts
end
def self.sidecar_queue
PuppetLanguageServer::PuppetHelper.sidecar_queue
end

# Facts
def self.facts_loaded?
@facts_loaded.nil? ? false : @facts_loaded
end

def self.load_facts
@ops_lock.synchronize do
_load_facts
end
end

def self.facts
return {} if @facts_loaded == false
@ops_lock.synchronize do
_load_facts if @fact_hash.nil?
@fact_hash.clone
end
end

# DO NOT ops_lock on any of these methods
# deadlocks will ensue!
def self._reset
@facts_loaded = nil
Facter.reset
@fact_hash = nil
end
private_class_method :_reset

def self._load_facts
_reset
@fact_hash = {}
begin
Facter.loadfacts
@fact_hash = Facter.to_hash
rescue StandardError => e
PuppetLanguageServer.log_message(:error, "[FacterHelper::_load_facts] Error loading facts #{e.message} #{e.backtrace}")
rescue LoadError => e
PuppetLanguageServer.log_message(:error, "[FacterHelper::_load_facts] Error loading facts (LoadError) #{e.message} #{e.backtrace}")
end
PuppetLanguageServer.log_message(:debug, "[FacterHelper::_load_facts] Finished loading #{@fact_hash.keys.count} facts")
def self.assert_facts_loaded
@facts_loaded = true
end
private_class_method :_load_facts

def self.load_facts
@facts_loaded = false
sidecar_queue.execute_sync('facts', [])
end

def self.load_facts_async
@facts_loaded = false
sidecar_queue.enqueue('facts', [])
end

def self.fact(name)
return nil if @facts_loaded == false
cache.object_by_name(:fact, name)
end

def self.fact_value(name)
return nil if @facts_loaded == false
object = cache.object_by_name(:fact, name)
object.nil? ? nil : object.value
end

def self.fact_names
return [] if @facts_loaded == false
cache.object_names_by_section(:fact).map(&:to_s)
end
end
end
4 changes: 2 additions & 2 deletions lib/puppet-languageserver/manifest/completion_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ def self.keywords(keywords = [], &block)
end

def self.all_facts(&block)
PuppetLanguageServer::FacterHelper.facts.each_key do |name|
PuppetLanguageServer::FacterHelper.fact_names.each do |name|
item = LSP::CompletionItem.new(
'label' => name.to_s,
'insertText' => "'#{name}'",
Expand Down Expand Up @@ -206,7 +206,7 @@ def self.resolve(completion_item)
data = result.data
case data['type']
when 'variable_expr_fact'
value = PuppetLanguageServer::FacterHelper.facts[data['expr']]
value = PuppetLanguageServer::FacterHelper.fact_value(data['expr'])
# TODO: More things?
result.documentation = value.to_s

Expand Down
5 changes: 3 additions & 2 deletions lib/puppet-languageserver/manifest/hover_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,9 @@ def self.get_hover_content_for_access_expression(path, expr)

# Content generation functions
def self.get_fact_content(factname)
return nil unless PuppetLanguageServer::FacterHelper.facts.key?(factname)
value = PuppetLanguageServer::FacterHelper.facts[factname]
fact = PuppetLanguageServer::FacterHelper.fact(factname)
return nil if fact.nil?
value = fact.value
content = "**#{factname}** Fact\n\n"

if value.is_a?(Hash)
Expand Down
1 change: 0 additions & 1 deletion lib/puppet-languageserver/puppet_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,6 @@ def self.purge_workspace
def self.sidecar_queue
@sidecar_queue_obj ||= PuppetLanguageServer::SidecarQueue.new(@helper_options)
end
private_class_method :sidecar_queue

def self.with_temporary_file(content)
tempfile = Tempfile.new('langserver-sidecar')
Expand Down
2 changes: 1 addition & 1 deletion lib/puppet-languageserver/puppet_helper/cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
module PuppetLanguageServer
module PuppetHelper
class Cache
SECTIONS = %i[class type function datatype].freeze
SECTIONS = %i[class type function datatype fact].freeze
ORIGINS = %i[default workspace bolt].freeze

def initialize(_options = {})
Expand Down
28 changes: 15 additions & 13 deletions lib/puppet-languageserver/sidecar_protocol.rb
Original file line number Diff line number Diff line change
Expand Up @@ -521,26 +521,28 @@ def list_for_object_class(klass)
end
end

class Facts < Hash
include Base
class Fact < BasePuppetObject
attr_accessor :value

def from_h!(value)
value.keys.each { |key| self[key] = value[key] }
self
def to_h
super.to_h.merge(
'value' => value
)
end

def to_json(*options)
::JSON.generate(to_h, options)
end
def from_h!(value)
super

def from_json!(json_string)
obj = ::JSON.parse(json_string)
obj.each do |key, value|
self[key] = value
end
self.value = value['value']
self
end
end

class FactList < BasePuppetObjectList
def child_type
Fact
end
end
end
end
end
7 changes: 7 additions & 0 deletions lib/puppet-languageserver/sidecar_queue.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@ def execute_sync(action, additional_args, handle_errors = false)

PuppetLanguageServer::PuppetHelper.assert_default_types_loaded

when 'facts'
list = PuppetLanguageServer::Sidecar::Protocol::FactList.new.from_json!(result)
@cache.import_sidecar_list!(list, :fact, :default)
PuppetLanguageServer.log_message(:debug, "SidecarQueue Thread: facts returned #{list.count} items")

PuppetLanguageServer::FacterHelper.assert_facts_loaded

when 'node_graph'
return PuppetLanguageServer::Sidecar::Protocol::NodeGraph.new.from_json!(result)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,33 @@ def run_sidecar(cmd_options)
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'] }
RSpec::Matchers.define :contain_child_with_key do |key|
match do |actual|
!(actual.index { |item| item.key == key }).nil?
end

failure_message do |actual|
"expected that #{actual.class.to_s} would contain a child with key #{key}"
end
end

let(:default_fact_names) { %i[hostname fixture_agent_custom_fact] }
let(:module_fact_names) { %i[fixture_module_custom_fact fixture_module_external_fact] }
let(:environment_fact_names) { %i[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
deserial = PuppetLanguageServer::Sidecar::Protocol::FactList.new
expect { deserial.from_json!(result) }.to_not raise_error

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

module_fact_names.each do |name|
expect(deserial).not_to include(name)
expect(deserial).to_not contain_child_with_key(name)
end
end
end
Expand All @@ -51,15 +60,15 @@ def run_sidecar(cmd_options)

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
deserial = PuppetLanguageServer::Sidecar::Protocol::FactList.new
expect { deserial.from_json!(result) }.to_not raise_error

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

module_fact_names.each do |name|
expect(deserial).to include(name)
expect(deserial).to contain_child_with_key(name)
end
end
end
Expand All @@ -74,15 +83,15 @@ def run_sidecar(cmd_options)

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
deserial = PuppetLanguageServer::Sidecar::Protocol::FactList.new
expect { deserial.from_json!(result) }.to_not raise_error

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

environment_fact_names.each do |name|
expect(deserial).to include(name)
expect(deserial).to contain_child_with_key(name)
end
end
end
Expand Down
11 changes: 10 additions & 1 deletion spec/languageserver/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ def wait_for_puppet_loading
break if PuppetLanguageServer::PuppetHelper.default_functions_loaded? &&
PuppetLanguageServer::PuppetHelper.default_types_loaded? &&
PuppetLanguageServer::PuppetHelper.default_classes_loaded? &&
PuppetLanguageServer::PuppetHelper.default_datatypes_loaded?
PuppetLanguageServer::PuppetHelper.default_datatypes_loaded? &&
PuppetLanguageServer::FacterHelper.facts_loaded?
sleep(1)
interation += 1
next if interation < 90
raise <<-ERRORMSG
Puppet has not be initialised in time:
facts_loaded? = #{PuppetLanguageServer::FacterHelper.facts_loaded?}
functions_loaded? = #{PuppetLanguageServer::PuppetHelper.default_functions_loaded?}
types_loaded? = #{PuppetLanguageServer::PuppetHelper.default_types_loaded?}
classes_loaded? = #{PuppetLanguageServer::PuppetHelper.default_classes_loaded?}
Expand Down Expand Up @@ -59,6 +61,13 @@ def add_random_basepuppetobject_values!(value)
value
end

def random_sidecar_fact(key = nil)
result = add_random_basepuppetobject_values!(PuppetLanguageServer::Sidecar::Protocol::Fact.new())
result.key = key unless key.nil?
result.value = 'value' + rand(1000).to_s
result
end

def random_sidecar_puppet_class(key = nil)
result = add_random_basepuppetobject_values!(PuppetLanguageServer::Sidecar::Protocol::PuppetClass.new())
result.key = key unless key.nil?
Expand Down
Loading

0 comments on commit 131c2ea

Please sign in to comment.