puppet-editor-services 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +510 -0
- data/CODEOWNERS +2 -0
- data/CODE_OF_CONDUCT.md +46 -0
- data/CONTRIBUTING.md +54 -0
- data/Gemfile +53 -0
- data/LICENSE +201 -0
- data/README.md +308 -0
- data/Rakefile +185 -0
- data/bin/puppet-debugserver +8 -0
- data/bin/puppet-languageserver +7 -0
- data/bin/puppet-languageserver-sidecar +7 -0
- data/lib/dsp/dsp.rb +7 -0
- data/lib/dsp/dsp_base.rb +62 -0
- data/lib/dsp/dsp_protocol.rb +4619 -0
- data/lib/lsp/lsp.rb +10 -0
- data/lib/lsp/lsp_base.rb +63 -0
- data/lib/lsp/lsp_custom.rb +170 -0
- data/lib/lsp/lsp_enums.rb +143 -0
- data/lib/lsp/lsp_protocol.rb +2785 -0
- data/lib/lsp/lsp_protocol_callhierarchy.proposed.rb +239 -0
- data/lib/lsp/lsp_protocol_colorprovider.rb +100 -0
- data/lib/lsp/lsp_protocol_configuration.rb +82 -0
- data/lib/lsp/lsp_protocol_declaration.rb +73 -0
- data/lib/lsp/lsp_protocol_foldingrange.rb +129 -0
- data/lib/lsp/lsp_protocol_implementation.rb +75 -0
- data/lib/lsp/lsp_protocol_progress.rb +200 -0
- data/lib/lsp/lsp_protocol_selectionrange.rb +79 -0
- data/lib/lsp/lsp_protocol_sematictokens.proposed.rb +340 -0
- data/lib/lsp/lsp_protocol_typedefinition.rb +75 -0
- data/lib/lsp/lsp_protocol_workspacefolders.rb +174 -0
- data/lib/lsp/lsp_types.rb +1534 -0
- data/lib/puppet-debugserver/debug_session/break_points.rb +137 -0
- data/lib/puppet-debugserver/debug_session/flow_control.rb +161 -0
- data/lib/puppet-debugserver/debug_session/hook_handlers.rb +295 -0
- data/lib/puppet-debugserver/debug_session/puppet_session_run_mode.rb +66 -0
- data/lib/puppet-debugserver/debug_session/puppet_session_state.rb +122 -0
- data/lib/puppet-debugserver/hooks.rb +132 -0
- data/lib/puppet-debugserver/message_handler.rb +277 -0
- data/lib/puppet-debugserver/puppet_debug_session.rb +541 -0
- data/lib/puppet-debugserver/puppet_monkey_patches.rb +118 -0
- data/lib/puppet-languageserver/client_session_state.rb +119 -0
- data/lib/puppet-languageserver/crash_dump.rb +50 -0
- data/lib/puppet-languageserver/epp/validation_provider.rb +34 -0
- data/lib/puppet-languageserver/facter_helper.rb +25 -0
- data/lib/puppet-languageserver/global_queues/sidecar_queue.rb +205 -0
- data/lib/puppet-languageserver/global_queues/single_instance_queue.rb +126 -0
- data/lib/puppet-languageserver/global_queues/validation_queue.rb +102 -0
- data/lib/puppet-languageserver/global_queues.rb +16 -0
- data/lib/puppet-languageserver/manifest/completion_provider.rb +331 -0
- data/lib/puppet-languageserver/manifest/definition_provider.rb +99 -0
- data/lib/puppet-languageserver/manifest/document_symbol_provider.rb +228 -0
- data/lib/puppet-languageserver/manifest/folding_provider.rb +226 -0
- data/lib/puppet-languageserver/manifest/format_on_type_provider.rb +143 -0
- data/lib/puppet-languageserver/manifest/hover_provider.rb +221 -0
- data/lib/puppet-languageserver/manifest/signature_provider.rb +169 -0
- data/lib/puppet-languageserver/manifest/validation_provider.rb +127 -0
- data/lib/puppet-languageserver/message_handler.rb +462 -0
- data/lib/puppet-languageserver/providers.rb +18 -0
- data/lib/puppet-languageserver/puppet_helper.rb +108 -0
- data/lib/puppet-languageserver/puppet_lexer_helper.rb +55 -0
- data/lib/puppet-languageserver/puppet_monkey_patches.rb +39 -0
- data/lib/puppet-languageserver/puppet_parser_helper.rb +212 -0
- data/lib/puppet-languageserver/puppetfile/validation_provider.rb +185 -0
- data/lib/puppet-languageserver/server_capabilities.rb +48 -0
- data/lib/puppet-languageserver/session_state/document_store.rb +272 -0
- data/lib/puppet-languageserver/session_state/language_client.rb +239 -0
- data/lib/puppet-languageserver/session_state/object_cache.rb +162 -0
- data/lib/puppet-languageserver/sidecar_protocol.rb +532 -0
- data/lib/puppet-languageserver/uri_helper.rb +46 -0
- data/lib/puppet-languageserver-sidecar/cache/base.rb +36 -0
- data/lib/puppet-languageserver-sidecar/cache/filesystem.rb +111 -0
- data/lib/puppet-languageserver-sidecar/cache/null.rb +27 -0
- data/lib/puppet-languageserver-sidecar/facter_helper.rb +41 -0
- data/lib/puppet-languageserver-sidecar/puppet_environment_monkey_patches.rb +52 -0
- data/lib/puppet-languageserver-sidecar/puppet_helper.rb +281 -0
- data/lib/puppet-languageserver-sidecar/puppet_modulepath_monkey_patches.rb +146 -0
- data/lib/puppet-languageserver-sidecar/puppet_monkey_patches.rb +9 -0
- data/lib/puppet-languageserver-sidecar/puppet_parser_helper.rb +77 -0
- data/lib/puppet-languageserver-sidecar/puppet_strings_helper.rb +399 -0
- data/lib/puppet-languageserver-sidecar/puppet_strings_monkey_patches.rb +16 -0
- data/lib/puppet-languageserver-sidecar/sidecar_protocol_extensions.rb +16 -0
- data/lib/puppet-languageserver-sidecar/workspace.rb +89 -0
- data/lib/puppet_debugserver.rb +164 -0
- data/lib/puppet_editor_services/connection/base.rb +62 -0
- data/lib/puppet_editor_services/connection/stdio.rb +25 -0
- data/lib/puppet_editor_services/connection/tcp.rb +34 -0
- data/lib/puppet_editor_services/handler/base.rb +16 -0
- data/lib/puppet_editor_services/handler/debug_adapter.rb +63 -0
- data/lib/puppet_editor_services/handler/json_rpc.rb +133 -0
- data/lib/puppet_editor_services/logging.rb +45 -0
- data/lib/puppet_editor_services/protocol/base.rb +27 -0
- data/lib/puppet_editor_services/protocol/debug_adapter.rb +135 -0
- data/lib/puppet_editor_services/protocol/debug_adapter_messages.rb +171 -0
- data/lib/puppet_editor_services/protocol/json_rpc.rb +241 -0
- data/lib/puppet_editor_services/protocol/json_rpc_messages.rb +200 -0
- data/lib/puppet_editor_services/server/base.rb +42 -0
- data/lib/puppet_editor_services/server/stdio.rb +85 -0
- data/lib/puppet_editor_services/server/tcp.rb +349 -0
- data/lib/puppet_editor_services/server.rb +15 -0
- data/lib/puppet_editor_services/version.rb +36 -0
- data/lib/puppet_editor_services.rb +8 -0
- data/lib/puppet_languageserver.rb +263 -0
- data/lib/puppet_languageserver_sidecar.rb +361 -0
- data/puppet-debugserver +11 -0
- data/puppet-editor-services.gemspec +29 -0
- data/puppet-languageserver +15 -0
- data/puppet-languageserver-sidecar +14 -0
- metadata +240 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PuppetLanguageServerSidecar
|
|
4
|
+
module Cache
|
|
5
|
+
class FileSystem < Base
|
|
6
|
+
attr_reader :cache_dir
|
|
7
|
+
|
|
8
|
+
def initialize(_options = {})
|
|
9
|
+
super
|
|
10
|
+
require 'digest'
|
|
11
|
+
require 'json'
|
|
12
|
+
require 'tmpdir'
|
|
13
|
+
|
|
14
|
+
@cache_dir = File.join(Dir.tmpdir, 'puppet-vscode-cache')
|
|
15
|
+
begin
|
|
16
|
+
Dir.mkdir(@cache_dir) unless Dir.exist?(@cache_dir)
|
|
17
|
+
rescue Errno::ENOENT => e
|
|
18
|
+
PuppetLanguageServerSidecar.log_message(:error, "[PuppetLanguageServerSidecar::Cache::FileSystem] An error occured while creating file cache. Disabling cache: #{e}")
|
|
19
|
+
@cache_dir = nil
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def active?
|
|
24
|
+
!@cache_dir.nil?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def load(absolute_path, section)
|
|
28
|
+
return nil unless active?
|
|
29
|
+
|
|
30
|
+
file_key = file_key(absolute_path, section)
|
|
31
|
+
cache_file = File.join(cache_dir, cache_filename(file_key))
|
|
32
|
+
|
|
33
|
+
content = read_file(cache_file)
|
|
34
|
+
return nil if content.nil?
|
|
35
|
+
|
|
36
|
+
json_obj = JSON.parse(content)
|
|
37
|
+
return nil if json_obj.nil?
|
|
38
|
+
|
|
39
|
+
# Check that this is from the same language server version
|
|
40
|
+
unless json_obj['sidecar_version'] == PuppetLanguageServerSidecar.version
|
|
41
|
+
PuppetLanguageServerSidecar.log_message(:debug, "[PuppetLanguageServerSidecar::Cache::FileSystem.load] Error loading #{absolute_path}: Expected sidecar_version version #{PuppetLanguageServerSidecar.version} but found #{json_obj['sidecar_version']}")
|
|
42
|
+
return nil
|
|
43
|
+
end
|
|
44
|
+
# Check that the source file hash matches
|
|
45
|
+
content_hash = calculate_hash(absolute_path)
|
|
46
|
+
if json_obj['file_hash'] != content_hash
|
|
47
|
+
PuppetLanguageServerSidecar.log_message(:debug, "[PuppetLanguageServerSidecar::Cache::FileSystem.load] Error loading #{absolute_path}: Expected file_hash of #{content_hash} but found #{json_obj['file_hash']}")
|
|
48
|
+
return nil
|
|
49
|
+
end
|
|
50
|
+
PuppetLanguageServerSidecar.log_message(:debug, "[PuppetLanguageServerSidecar::Cache::FileSystem.load] Loading #{absolute_path} from cache")
|
|
51
|
+
|
|
52
|
+
json_obj['data']
|
|
53
|
+
rescue RuntimeError => e
|
|
54
|
+
PuppetLanguageServerSidecar.log_message(:debug, "[PuppetLanguageServerSidecar::Cache::FileSystem.load] Error loading #{absolute_path}: #{e}")
|
|
55
|
+
raise
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def save(absolute_path, section, content_string)
|
|
59
|
+
return false unless active?
|
|
60
|
+
|
|
61
|
+
file_key = file_key(absolute_path, section)
|
|
62
|
+
cache_file = File.join(cache_dir, cache_filename(file_key))
|
|
63
|
+
|
|
64
|
+
content = { 'data' => content_string }
|
|
65
|
+
# Inject metadata
|
|
66
|
+
content['sidecar_version'] = PuppetLanguageServerSidecar.version
|
|
67
|
+
content['file_hash'] = calculate_hash(absolute_path)
|
|
68
|
+
content['created'] = Time.now.utc.strftime('%FT%T')
|
|
69
|
+
content['path'] = absolute_path
|
|
70
|
+
content['section'] = section
|
|
71
|
+
|
|
72
|
+
PuppetLanguageServerSidecar.log_message(:debug, "[PuppetLanguageServerSidecar::Cache::FileSystem.save] Saving #{absolute_path} to cache")
|
|
73
|
+
save_file(cache_file, content.to_json)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def clear!
|
|
77
|
+
return unless active?
|
|
78
|
+
|
|
79
|
+
PuppetLanguageServerSidecar.log_message(:warn, '[PuppetLanguageServerSidecar::Cache::FileSystem.clear] Filesystem based cache is being cleared')
|
|
80
|
+
FileUtils.rm(Dir.glob(File.join(cache_dir, '*')), force: true)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def file_key(filepath, section)
|
|
86
|
+
# Strictly speaking some file systems are case sensitive but ruby/puppet throws a fit
|
|
87
|
+
# with naming if you do
|
|
88
|
+
filepath.downcase + File::SEPARATOR + section
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def read_file(filepath)
|
|
92
|
+
return nil unless File.exist?(filepath)
|
|
93
|
+
|
|
94
|
+
File.binread(filepath)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def save_file(filepath, content)
|
|
98
|
+
File.binwrite(filepath, content)
|
|
99
|
+
true
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def cache_filename(file_key)
|
|
103
|
+
"#{Digest::SHA256.hexdigest(file_key)}.txt"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def calculate_hash(filepath)
|
|
107
|
+
Digest::SHA256.hexdigest(read_file(filepath))
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PuppetLanguageServerSidecar
|
|
4
|
+
module Cache
|
|
5
|
+
class Null < Base
|
|
6
|
+
def initialize(options = {})
|
|
7
|
+
super
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def active?
|
|
11
|
+
false
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def load(*)
|
|
15
|
+
nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def save(*)
|
|
19
|
+
true
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def clear!
|
|
23
|
+
nil
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PuppetLanguageServerSidecar
|
|
4
|
+
module FacterHelper
|
|
5
|
+
def self.current_environment
|
|
6
|
+
begin
|
|
7
|
+
env = Puppet.lookup(:environments).get!(Puppet.settings[:environment])
|
|
8
|
+
return env unless env.nil?
|
|
9
|
+
rescue Puppet::Environments::EnvironmentNotFound
|
|
10
|
+
PuppetLanguageServerSidecar.log_message(:warning, "[FacterHelper::current_environment] Unable to load environment #{Puppet.settings[:environment]}")
|
|
11
|
+
rescue StandardError => e
|
|
12
|
+
PuppetLanguageServerSidecar.log_message(:warning, "[FacterHelper::current_environment] Error loading environment #{Puppet.settings[:environment]}: #{e}")
|
|
13
|
+
end
|
|
14
|
+
Puppet.lookup(:current_environment)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.retrieve_facts(_cache, _options = {})
|
|
18
|
+
require 'puppet/indirector/facts/facter'
|
|
19
|
+
|
|
20
|
+
PuppetLanguageServerSidecar.log_message(:debug, '[FacterHelper::retrieve_facts] Starting')
|
|
21
|
+
facts = PuppetLanguageServer::Sidecar::Protocol::FactList.new
|
|
22
|
+
begin
|
|
23
|
+
req = Puppet::Indirector::Request.new(:facts, :find, 'language_server', nil, environment: current_environment)
|
|
24
|
+
result = Puppet::Node::Facts::Facter.new.find(req)
|
|
25
|
+
result.values.each do |key, value|
|
|
26
|
+
# TODO: This isn't strictly correct e.g. fully qualified facts will look a bit odd.
|
|
27
|
+
# Consider a fact called foo.bar.baz = 'Hello'. Even though the fact name is `foo.bar.baz`
|
|
28
|
+
# it will appear in the facts object as `facts['foo'] = { 'bar' => { 'baz' => 'Hello' }}`
|
|
29
|
+
facts << PuppetLanguageServer::Sidecar::Protocol::Fact.new.from_h!('key' => key, 'value' => value)
|
|
30
|
+
end
|
|
31
|
+
rescue StandardError => e
|
|
32
|
+
PuppetLanguageServerSidecar.log_message(:error, "[FacterHelper::_load_facts] Error loading facts #{e.message} #{e.backtrace}")
|
|
33
|
+
rescue LoadError => e
|
|
34
|
+
PuppetLanguageServerSidecar.log_message(:error, "[FacterHelper::_load_facts] Error loading facts (LoadError) #{e.message} #{e.backtrace}")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
PuppetLanguageServerSidecar.log_message(:debug, "[FacterHelper::retrieve_facts] Finished loading #{facts.count} facts")
|
|
38
|
+
facts
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Puppet::Environments
|
|
4
|
+
class Directories
|
|
5
|
+
# Monkey patch the environment loader. When it attempts to load the special sidecar
|
|
6
|
+
# environment, create a new Puppet::Node::Environment object for the workspace
|
|
7
|
+
alias original_get get
|
|
8
|
+
def get(name)
|
|
9
|
+
if name.intern == PuppetLanguageServerSidecar::PuppetHelper::SIDECAR_PUPPET_ENVIRONMENT.intern
|
|
10
|
+
env_symbol = name.intern
|
|
11
|
+
setting_values = Puppet.settings.values(env_symbol, Puppet.settings.preferred_run_mode)
|
|
12
|
+
env = Puppet::Node::Environment.create(
|
|
13
|
+
env_symbol,
|
|
14
|
+
Puppet::Node::Environment.split_path(setting_values.interpolate(:modulepath)),
|
|
15
|
+
setting_values.interpolate(:manifest),
|
|
16
|
+
setting_values.interpolate(:config_version)
|
|
17
|
+
)
|
|
18
|
+
return env
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
original_get(name)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Monkey patch the environment loader. When it attempts to load the special sidecar
|
|
25
|
+
# environment.conf file, create a new Puppet::Settings::EnvironmentConf object
|
|
26
|
+
# from the workspace.
|
|
27
|
+
alias original_get_conf get_conf
|
|
28
|
+
def get_conf(name)
|
|
29
|
+
if name.intern == PuppetLanguageServerSidecar::PuppetHelper::SIDECAR_PUPPET_ENVIRONMENT.intern
|
|
30
|
+
conf = Puppet::Settings::EnvironmentConf.load_from(PuppetLanguageServerSidecar::Workspace.root_path, @global_module_path)
|
|
31
|
+
# Unfortunately the environment.conf expects OS style delimiters which means
|
|
32
|
+
# it fails if written for windows and read on Unix and vice versa. So we just
|
|
33
|
+
# inline munge the modulepath
|
|
34
|
+
modpath = conf.get_raw_setting(:modulepath).value
|
|
35
|
+
modpath.gsub!(':', File::PATH_SEPARATOR) if File::PATH_SEPARATOR != ':'
|
|
36
|
+
modpath.gsub!(';', File::PATH_SEPARATOR) if File::PATH_SEPARATOR != ';'
|
|
37
|
+
return conf
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
original_get_conf(name)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Monkey patch the environment. Normally it's not possible to modify environment settings.
|
|
46
|
+
# Add a method to get the underlying environment settings which can be used to modify
|
|
47
|
+
# settings on the fly.
|
|
48
|
+
class Puppet::Settings::EnvironmentConf
|
|
49
|
+
def get_raw_setting(setting_name)
|
|
50
|
+
section.setting(setting_name) if section
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'puppet/indirector/face'
|
|
4
|
+
|
|
5
|
+
module PuppetLanguageServerSidecar
|
|
6
|
+
module PuppetHelper
|
|
7
|
+
SIDECAR_PUPPET_ENVIRONMENT = 'sidecarenvironment'
|
|
8
|
+
|
|
9
|
+
# Ignore certain data types. For more information see https://tickets.puppetlabs.com/browse/DOCUMENT-1020
|
|
10
|
+
# TypeReference - Internal to the Puppet Data Type system
|
|
11
|
+
# TypeAlias - Internal to the Puppet Data Type system
|
|
12
|
+
# Object - While useful, typically only needed when extended the type system as opposed to general use
|
|
13
|
+
# TypeSet - While useful, typically only needed when extended the type system as opposed to general use
|
|
14
|
+
IGNORE_DATATYPE_NAMES = %w[TypeReference TypeAlias Object TypeSet ObjectTypeExten Iterable AbstractTimeData TypeWithContained].freeze
|
|
15
|
+
|
|
16
|
+
# Resource Face
|
|
17
|
+
def self.get_puppet_resource(typename, title = nil)
|
|
18
|
+
result = PuppetLanguageServer::Sidecar::Protocol::PuppetClassList.new
|
|
19
|
+
resources = if title.nil?
|
|
20
|
+
Puppet::Face[:resource, '0.0.1'].search(typename)
|
|
21
|
+
else
|
|
22
|
+
Puppet::Face[:resource, '0.0.1'].find("#{typename}/#{title}")
|
|
23
|
+
end
|
|
24
|
+
return result if resources.nil?
|
|
25
|
+
|
|
26
|
+
resources = [resources] unless resources.is_a?(Array)
|
|
27
|
+
prune_resource_parameters(resources).each do |item|
|
|
28
|
+
obj = PuppetLanguageServer::Sidecar::Protocol::Resource.new
|
|
29
|
+
obj.manifest = item.to_manifest
|
|
30
|
+
result << obj
|
|
31
|
+
end
|
|
32
|
+
result
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Puppet Strings loading
|
|
36
|
+
def self.available_documentation_types
|
|
37
|
+
%I[class datatype function type]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.retrieve_default_data_types
|
|
41
|
+
default_types = PuppetLanguageServer::Sidecar::Protocol::PuppetDataTypeList.new
|
|
42
|
+
# There's no actual record of all available types, but we can use the Puppet::Pops::Types to implicitly get their names
|
|
43
|
+
name_list = []
|
|
44
|
+
|
|
45
|
+
Puppet::Pops::Types.constants.each do |const|
|
|
46
|
+
thing = Puppet::Pops::Types.const_get(const)
|
|
47
|
+
# Not that this is a reference to a type, not an INSTANCE of that type.
|
|
48
|
+
# So we can't do .is_a? checks. But instead look for methods on the thing
|
|
49
|
+
# that would indicate it's a Puppet Type
|
|
50
|
+
# _pcore_type : is present on all Pops type objects
|
|
51
|
+
# simple_name : comes from PAnyType
|
|
52
|
+
next unless thing.respond_to?(:_pcore_type) && thing.respond_to?(:simple_name)
|
|
53
|
+
# Ignore certain data types
|
|
54
|
+
next if IGNORE_DATATYPE_NAMES.include?(thing.simple_name)
|
|
55
|
+
# Don't need duplicates
|
|
56
|
+
next if name_list.include?(thing.simple_name)
|
|
57
|
+
|
|
58
|
+
name_list << thing.simple_name
|
|
59
|
+
|
|
60
|
+
obj = PuppetLanguageServer::Sidecar::Protocol::PuppetDataType.new
|
|
61
|
+
obj.key = thing.simple_name
|
|
62
|
+
obj.source = nil
|
|
63
|
+
obj.calling_source = nil
|
|
64
|
+
obj.line = nil
|
|
65
|
+
obj.doc = "The #{thing.simple_name} core data type"
|
|
66
|
+
obj.is_type_alias = false
|
|
67
|
+
obj.alias_of = nil
|
|
68
|
+
# So far, no core data types have attributes
|
|
69
|
+
obj.attributes = []
|
|
70
|
+
default_types << obj
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
default_types
|
|
74
|
+
end
|
|
75
|
+
private_class_method :retrieve_default_data_types
|
|
76
|
+
|
|
77
|
+
# Retrieve objects via the Puppet 4 API loaders
|
|
78
|
+
def self.retrieve_via_puppet_strings(cache, options = {})
|
|
79
|
+
PuppetLanguageServerSidecar.log_message(:debug, '[PuppetHelper::retrieve_via_puppet_strings] Starting')
|
|
80
|
+
|
|
81
|
+
object_types = options[:object_types].nil? ? available_documentation_types : options[:object_types]
|
|
82
|
+
object_types.select! { |i| available_documentation_types.include?(i) }
|
|
83
|
+
|
|
84
|
+
result = PuppetLanguageServer::Sidecar::Protocol::AggregateMetadata.new
|
|
85
|
+
return result if object_types.empty?
|
|
86
|
+
|
|
87
|
+
current_env = current_environment
|
|
88
|
+
for_agent = options[:for_agent].nil? ? true : options[:for_agent]
|
|
89
|
+
Puppet::Pops::Loaders.new(current_env, for_agent)
|
|
90
|
+
|
|
91
|
+
finder = PuppetPathFinder.new(current_env, object_types)
|
|
92
|
+
paths = finder.find(options[:root_path])
|
|
93
|
+
|
|
94
|
+
paths.each do |path|
|
|
95
|
+
file_doc = PuppetLanguageServerSidecar::PuppetStringsHelper.file_documentation(path, finder.puppet_path, cache)
|
|
96
|
+
next if file_doc.nil?
|
|
97
|
+
|
|
98
|
+
if object_types.include?(:class) # rubocop:disable Style/IfUnlessModifier This reads better
|
|
99
|
+
file_doc.classes.each { |item| result.append!(item) }
|
|
100
|
+
end
|
|
101
|
+
if object_types.include?(:datatype) # rubocop:disable Style/IfUnlessModifier This reads better
|
|
102
|
+
file_doc.datatypes.each { |item| result.append!(item) }
|
|
103
|
+
end
|
|
104
|
+
if object_types.include?(:function) # rubocop:disable Style/IfUnlessModifier This reads better
|
|
105
|
+
file_doc.functions.each { |item| result.append!(item) }
|
|
106
|
+
end
|
|
107
|
+
next unless object_types.include?(:type)
|
|
108
|
+
|
|
109
|
+
file_doc.types.each do |item|
|
|
110
|
+
result.append!(item) unless name == 'whit' || name == 'component'
|
|
111
|
+
finder.temp_file.unlink if item.key == 'file' && File.exist?(finder.temp_file.path) # Remove the temp_file.rb if it exists
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Remove Puppet3 functions which have a Puppet4 function already loaded
|
|
116
|
+
if object_types.include?(:function) && !result.functions.nil?
|
|
117
|
+
pup4_functions = result.functions.select { |i| i.function_version == 4 }.map { |i| i.key }
|
|
118
|
+
result.functions.reject! { |i| i.function_version == 3 && pup4_functions.include?(i.key) }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Add the inbuilt data types if there's no root path
|
|
122
|
+
result.concat!(retrieve_default_data_types) if object_types.include?(:datatype) && options[:root_path].nil?
|
|
123
|
+
|
|
124
|
+
result.each_list { |key, item| PuppetLanguageServerSidecar.log_message(:debug, "[PuppetHelper::retrieve_via_puppet_strings] Finished loading #{item.count} #{key}") }
|
|
125
|
+
result
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Private functions
|
|
129
|
+
|
|
130
|
+
def self.prune_resource_parameters(resources)
|
|
131
|
+
# From https://github.com/puppetlabs/puppet/blob/488661d84e54904124514ab9e4500e81b10f84d1/lib/puppet/application/resource.rb#L146-L148
|
|
132
|
+
if resources.is_a?(Array)
|
|
133
|
+
resources.map(&:prune_parameters)
|
|
134
|
+
else
|
|
135
|
+
resources.prune_parameters
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
private_class_method :prune_resource_parameters
|
|
139
|
+
|
|
140
|
+
def self.current_environment
|
|
141
|
+
begin
|
|
142
|
+
env = Puppet.lookup(:environments).get!(Puppet.settings[:environment])
|
|
143
|
+
return env unless env.nil?
|
|
144
|
+
rescue Puppet::Environments::EnvironmentNotFound
|
|
145
|
+
PuppetLanguageServerSidecar.log_message(:warning, "[PuppetHelper::current_environment] Unable to load environment #{Puppet.settings[:environment]}")
|
|
146
|
+
rescue StandardError => e
|
|
147
|
+
PuppetLanguageServerSidecar.log_message(:warning, "[PuppetHelper::current_environment] Error loading environment #{Puppet.settings[:environment]}: #{e}")
|
|
148
|
+
end
|
|
149
|
+
Puppet.lookup(:current_environment)
|
|
150
|
+
end
|
|
151
|
+
private_class_method :current_environment
|
|
152
|
+
|
|
153
|
+
# A helper class to find the paths for different kinds of things related to Puppet, for example
|
|
154
|
+
# DataType ruby files or manifests.
|
|
155
|
+
class PuppetPathFinder
|
|
156
|
+
attr_reader :object_types, :puppet_path, :temp_file
|
|
157
|
+
|
|
158
|
+
# @param puppet_env [Puppet::Node::Environment] The environment to search within
|
|
159
|
+
# @param object_types [Symbol] The types of objects that will be searched for. See available_documentation_types for the complete list
|
|
160
|
+
def initialize(puppet_env, object_types)
|
|
161
|
+
# Path to every module
|
|
162
|
+
@module_paths = puppet_env.modules.map(&:path)
|
|
163
|
+
# Path to the environment
|
|
164
|
+
@env_path = puppet_env.configuration.path_to_env
|
|
165
|
+
# Path to the puppet installation
|
|
166
|
+
@puppet_path = if puppet_env.loaders.nil? # No loaders have been created yet
|
|
167
|
+
nil
|
|
168
|
+
elsif puppet_env.loaders.puppet_system_loader.nil?
|
|
169
|
+
nil
|
|
170
|
+
elsif puppet_env.loaders.puppet_system_loader.lib_root?
|
|
171
|
+
File.join(puppet_env.loaders.puppet_system_loader.path, '..')
|
|
172
|
+
else
|
|
173
|
+
puppet_env.loaders.puppet_system_loader.path
|
|
174
|
+
end
|
|
175
|
+
# Path to the cached puppet files e.g. pluginsync
|
|
176
|
+
@vardir_path = Puppet.settings[:vardir]
|
|
177
|
+
|
|
178
|
+
@object_types = object_types
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Find all puppet related files, optionally from within a root path
|
|
182
|
+
# @param from_root_path [String] The path which files can be found within. If nil, only the default Puppet locations are searched e.g. vardir
|
|
183
|
+
# @return [Array[String]] A list of all files that are found. This is the absolute path to the file.
|
|
184
|
+
def find(from_root_path = nil)
|
|
185
|
+
require 'tempfile'
|
|
186
|
+
paths = []
|
|
187
|
+
search_paths = @module_paths.nil? ? [] : @module_paths
|
|
188
|
+
search_paths << @env_path unless @env_path.nil?
|
|
189
|
+
search_paths << @vardir_path unless @vardir_path.nil?
|
|
190
|
+
search_paths << @puppet_path unless @puppet_path.nil?
|
|
191
|
+
|
|
192
|
+
searched_globs = []
|
|
193
|
+
search_paths.each do |search_root|
|
|
194
|
+
PuppetLanguageServerSidecar.log_message(:debug, "[PuppetPathFinder] Potential search root '#{search_root}'")
|
|
195
|
+
next if search_root.nil?
|
|
196
|
+
|
|
197
|
+
# We need absolute paths from here on in.
|
|
198
|
+
search_root = File.expand_path(search_root)
|
|
199
|
+
next unless path_in_root?(from_root_path, search_root) && Dir.exist?(search_root)
|
|
200
|
+
|
|
201
|
+
PuppetLanguageServerSidecar.log_message(:debug, "[PuppetPathFinder] Using '#{search_root}' as a directory to search")
|
|
202
|
+
# name of temp file to store the file type definitions (if any)
|
|
203
|
+
@temp_file = Tempfile.new('file.rb')
|
|
204
|
+
all_object_info.each do |object_type, paths_to_search|
|
|
205
|
+
next unless object_types.include?(object_type)
|
|
206
|
+
|
|
207
|
+
# TODO: next unless object_type is included
|
|
208
|
+
paths_to_search.each do |path_info|
|
|
209
|
+
path = File.join(search_root, path_info[:relative_dir])
|
|
210
|
+
glob = path + path_info[:glob]
|
|
211
|
+
next if searched_globs.include?(glob) # No point searching twice
|
|
212
|
+
next unless Dir.exist?(path)
|
|
213
|
+
|
|
214
|
+
searched_globs << glob
|
|
215
|
+
PuppetLanguageServerSidecar.log_message(:debug, "[PuppetPathFinder] Searching glob '#{glob}''")
|
|
216
|
+
|
|
217
|
+
Dir.glob(glob) do |filename|
|
|
218
|
+
# if filename matches file.rb or /file/<any>.rb then we need to loop through each file type definition
|
|
219
|
+
if filename.match?(%r{/type/file/.*.rb|/type/file.rb})
|
|
220
|
+
PuppetLanguageServerSidecar.log_message(:debug, "[PuppetPathFinder] Found file type definition at '#{filename}'.")
|
|
221
|
+
# Read each file type definition and write it to the temp file
|
|
222
|
+
@temp_file.write(File.read(filename))
|
|
223
|
+
else
|
|
224
|
+
paths << filename
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
# Add the temp_file.rb to the paths array for searching (if exists)
|
|
231
|
+
if @temp_file && File.exist?(@temp_file.path)
|
|
232
|
+
paths << @temp_file.path
|
|
233
|
+
@temp_file.close
|
|
234
|
+
end
|
|
235
|
+
paths
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
private
|
|
239
|
+
|
|
240
|
+
# Simple text based path checking
|
|
241
|
+
# Is [path] in the [root]
|
|
242
|
+
# @param root [String] The Root path
|
|
243
|
+
# @param path [String] The path to check if it's within the root
|
|
244
|
+
# @return [Boolean]
|
|
245
|
+
def path_in_root?(root, path)
|
|
246
|
+
# Doesn't matter what the root is, if the path is nil, it's false
|
|
247
|
+
return false if path.nil?
|
|
248
|
+
# Doesn't matter what the path is, if the root is nil it's true.
|
|
249
|
+
return true if root.nil?
|
|
250
|
+
# If the path is less than root, then it has to be false
|
|
251
|
+
return false if root.length > path.length
|
|
252
|
+
|
|
253
|
+
# Is the beginning of the path, the same as the root
|
|
254
|
+
value = path.slice(0, root.length)
|
|
255
|
+
value.casecmp(root).zero?
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# The metadata for all object types and where they can be found on the filesystem
|
|
259
|
+
# @return [Hash[Symbol => Hash[Symbol => String]]]
|
|
260
|
+
def all_object_info
|
|
261
|
+
{
|
|
262
|
+
class: [
|
|
263
|
+
{ relative_dir: 'manifests', glob: '/**/*.pp' } # Pretty much everything in most modules
|
|
264
|
+
],
|
|
265
|
+
datatype: [
|
|
266
|
+
{ relative_dir: 'lib/puppet/datatypes', glob: '/**/*.rb' }, # Custom Data Types
|
|
267
|
+
{ relative_dir: 'types', glob: '/**/*.pp' } # Data Type aliases
|
|
268
|
+
],
|
|
269
|
+
function: [
|
|
270
|
+
{ relative_dir: 'functions', glob: '/**/*.pp' }, # Contains custom functions written in the Puppet language.
|
|
271
|
+
{ relative_dir: 'lib/puppet/functions', glob: '/**/*.rb' }, # Contains functions written in Ruby for the modern Puppet::Functions API
|
|
272
|
+
{ relative_dir: 'lib/puppet/parser/functions', glob: '/**/*.rb' } # Contains functions written in Ruby for the legacy Puppet::Parser::Functions API
|
|
273
|
+
],
|
|
274
|
+
type: [
|
|
275
|
+
{ relative_dir: 'lib/puppet/type', glob: '/{,file/}*.rb' } # Contains Puppet resource types. Resource types like `file` can live in subdirs, hence the glob
|
|
276
|
+
]
|
|
277
|
+
}
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Inject the workspace libdir on the fly
|
|
4
|
+
require 'puppet/util/autoload'
|
|
5
|
+
module Puppet
|
|
6
|
+
module Util
|
|
7
|
+
class Autoload
|
|
8
|
+
class << self
|
|
9
|
+
alias original_module_directories module_directories
|
|
10
|
+
def module_directories(env)
|
|
11
|
+
result = original_module_directories(env)
|
|
12
|
+
return result unless PuppetLanguageServerSidecar::Workspace.has_module_metadata?
|
|
13
|
+
|
|
14
|
+
workspace_lib = File.join(PuppetLanguageServerSidecar::Workspace.root_path, 'lib')
|
|
15
|
+
return result unless FileTest.directory?(workspace_lib)
|
|
16
|
+
|
|
17
|
+
result << workspace_lib
|
|
18
|
+
|
|
19
|
+
result
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Monkey patch the module loader and inject a workspace module
|
|
27
|
+
# into the modules memoization variable
|
|
28
|
+
require 'puppet/node/environment'
|
|
29
|
+
class Puppet::Node::Environment
|
|
30
|
+
alias original_modules modules
|
|
31
|
+
alias original_modules_by_path modules_by_path
|
|
32
|
+
|
|
33
|
+
# The Puppet::Util::Json class doesn't exist in all puppet version. Instead
|
|
34
|
+
# just vendor the code here as it's a simple JSON loader only for metadata.json.
|
|
35
|
+
# https://github.com/puppetlabs/puppet/blob/5.5.0/lib/puppet/util/json.rb#L32-L49
|
|
36
|
+
def workspace_load_json(string, options = {})
|
|
37
|
+
if defined? MultiJson
|
|
38
|
+
begin
|
|
39
|
+
MultiJson.load(string, options)
|
|
40
|
+
rescue MultiJson::ParseError => e
|
|
41
|
+
raise Puppet::Util::Json::ParseError.build(e, string)
|
|
42
|
+
end
|
|
43
|
+
else
|
|
44
|
+
begin
|
|
45
|
+
string = string.read if string.respond_to?(:read)
|
|
46
|
+
|
|
47
|
+
options[:symbolize_names] = true if options.delete(:symbolize_keys)
|
|
48
|
+
::JSON.parse(string, options)
|
|
49
|
+
rescue JSON::ParserError => e
|
|
50
|
+
raise Puppet::Util::Json::ParseError.build(e, string)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def create_workspace_module_object(path)
|
|
56
|
+
# Read the metadata to find the actual module name
|
|
57
|
+
md_file = File.join(PuppetLanguageServerSidecar::Workspace.root_path, 'metadata.json')
|
|
58
|
+
begin
|
|
59
|
+
metadata = workspace_load_json(File.read(md_file, encoding: 'utf-8'))
|
|
60
|
+
return nil if metadata['name'].nil?
|
|
61
|
+
|
|
62
|
+
# Extract the actual module name
|
|
63
|
+
if Puppet::Module.is_module_directory_name?(metadata['name'])
|
|
64
|
+
module_name = metadata['name']
|
|
65
|
+
elsif Puppet::Module.is_module_namespaced_name?(metadata['name'])
|
|
66
|
+
# Based on regex at https://github.com/puppetlabs/puppet/blob/f5ca8c05174c944f783cfd0b18582e2160b77d0e/lib/puppet/module.rb#L54
|
|
67
|
+
result = /^[a-zA-Z0-9]+-([a-z][a-z0-9_]*)$/.match(metadata['name'])
|
|
68
|
+
module_name = result[1]
|
|
69
|
+
else
|
|
70
|
+
# TODO: This is an invalid puppet module name in the metadata.json. Should we log an error/warning?
|
|
71
|
+
return nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# The Puppet::Module initializer was changed in
|
|
75
|
+
# https://github.com/puppetlabs/puppet/commit/935c0311dbaf1df03937822525c36b26de5390ef
|
|
76
|
+
# We need to switch the creation based on whether the modules_strict_semver? method is available
|
|
77
|
+
return Puppet::Module.new(module_name, path, self, modules_strict_semver?) if respond_to?(:modules_strict_semver?)
|
|
78
|
+
|
|
79
|
+
Puppet::Module.new(module_name, path, self)
|
|
80
|
+
rescue StandardError
|
|
81
|
+
nil
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def modules
|
|
86
|
+
if @modules.nil?
|
|
87
|
+
original_modules
|
|
88
|
+
if PuppetLanguageServerSidecar::Workspace.has_module_metadata?
|
|
89
|
+
workspace_module = create_workspace_module_object(PuppetLanguageServerSidecar::Workspace.root_path)
|
|
90
|
+
@modules << workspace_module unless workspace_module.nil?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
@modules
|
|
94
|
+
else
|
|
95
|
+
original_modules
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def modules_by_path
|
|
100
|
+
result = original_modules_by_path
|
|
101
|
+
|
|
102
|
+
result.keys.each do |key|
|
|
103
|
+
if key == PuppetLanguageServerSidecar::Workspace.root_path && PuppetLanguageServerSidecar::Workspace.has_module_metadata?
|
|
104
|
+
workspace_module = create_workspace_module_object(key)
|
|
105
|
+
result[key] = workspace_module.nil? ? [] : [workspace_module]
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
result
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Inject the workspace as a module in all modulepaths
|
|
114
|
+
require 'puppet/settings/environment_conf'
|
|
115
|
+
class Puppet::Settings::EnvironmentConf
|
|
116
|
+
alias original_modulepath modulepath
|
|
117
|
+
|
|
118
|
+
def modulepath
|
|
119
|
+
result = original_modulepath
|
|
120
|
+
|
|
121
|
+
if PuppetLanguageServerSidecar::Workspace.has_module_metadata? # rubocop:disable Style/IfUnlessModifier Nicer to read like this
|
|
122
|
+
result = result + File::PATH_SEPARATOR + PuppetLanguageServerSidecar::Workspace.root_path
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
result
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Inject the workspace into the facter search paths
|
|
130
|
+
require 'puppet/indirector/facts/facter'
|
|
131
|
+
class Puppet::Node::Facts::Facter
|
|
132
|
+
class << self
|
|
133
|
+
alias original_setup_search_paths setup_search_paths
|
|
134
|
+
def setup_search_paths(request)
|
|
135
|
+
result = original_setup_search_paths(request)
|
|
136
|
+
return result unless PuppetLanguageServerSidecar::Workspace.has_module_metadata?
|
|
137
|
+
|
|
138
|
+
additional_dirs = %w[lib plugins].map { |path| File.join(PuppetLanguageServerSidecar::Workspace.root_path, path, 'facter') }
|
|
139
|
+
.select { |path| FileTest.directory?(path) }
|
|
140
|
+
|
|
141
|
+
return result if additional_dirs.empty?
|
|
142
|
+
|
|
143
|
+
Facter.search(*additional_dirs)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# MUST BE LAST!!!!!!
|
|
4
|
+
# Suppress any warning messages to STDOUT. It can pollute stdout when running in STDIO mode
|
|
5
|
+
Puppet::Util::Log.newdesttype :null_logger do
|
|
6
|
+
def handle(msg)
|
|
7
|
+
PuppetLanguageServerSidecar.log_message(:debug, "[PUPPET LOG] [#{msg.level}] #{msg.message}")
|
|
8
|
+
end
|
|
9
|
+
end
|