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) Use Facts from the Sidecar #215

Merged
merged 2 commits into from
Jan 22, 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
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