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,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'puppet-languageserver/session_state/document_store'
|
|
4
|
+
require 'puppet-languageserver/session_state/language_client'
|
|
5
|
+
require 'puppet-languageserver/session_state/object_cache'
|
|
6
|
+
|
|
7
|
+
module PuppetLanguageServer
|
|
8
|
+
class ClientSessionState
|
|
9
|
+
attr_reader :documents, :language_client, :object_cache, :connection_id
|
|
10
|
+
|
|
11
|
+
def initialize(message_handler, options = {})
|
|
12
|
+
@documents = options[:documents].nil? ? PuppetLanguageServer::SessionState::DocumentStore.new : options[:documents]
|
|
13
|
+
@language_client = options[:language_client].nil? ? PuppetLanguageServer::SessionState::LanguageClient.new(message_handler) : options[:language_client]
|
|
14
|
+
@object_cache = options[:object_cache].nil? ? PuppetLanguageServer::SessionState::ObjectCache.new : options[:object_cache]
|
|
15
|
+
@connection_id = options[:connection_id].nil? ? message_handler.protocol.connection.id : options[:connection_id]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Helper methods to know the state of the object cache
|
|
19
|
+
def default_classes_loaded?
|
|
20
|
+
object_cache.section_in_origin_exist?(:class, :default)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def default_datatypes_loaded?
|
|
24
|
+
object_cache.section_in_origin_exist?(:datatype, :default)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def default_functions_loaded?
|
|
28
|
+
object_cache.section_in_origin_exist?(:function, :default)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def default_types_loaded?
|
|
32
|
+
object_cache.section_in_origin_exist?(:type, :default)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def facts_loaded?
|
|
36
|
+
object_cache.section_in_origin_exist?(:fact, :default)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def static_data_loaded?
|
|
40
|
+
object_cache.origin_exist?(:bolt)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Loaders for object cache information
|
|
44
|
+
def load_default_data!(async = true)
|
|
45
|
+
PuppetLanguageServer.log_message(:info, "Loading Default Data via aggregate #{'(Async)' if async}...")
|
|
46
|
+
if async
|
|
47
|
+
sidecar_queue.enqueue('default_aggregate', [], false, connection_id)
|
|
48
|
+
sidecar_queue.enqueue('facts', [], false, connection_id)
|
|
49
|
+
else
|
|
50
|
+
sidecar_queue.execute('default_aggregate', [], false, connection_id)
|
|
51
|
+
sidecar_queue.execute('facts', [], false, connection_id)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
true
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def load_static_data!(async = true)
|
|
58
|
+
if async
|
|
59
|
+
Thread.new do
|
|
60
|
+
PuppetLanguageServer.log_message(:info, 'Loading static data (Async)...')
|
|
61
|
+
load_static_data_impl
|
|
62
|
+
end
|
|
63
|
+
else
|
|
64
|
+
PuppetLanguageServer.log_message(:info, 'Loading static data...')
|
|
65
|
+
load_static_data_impl
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
true
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def load_workspace_data!(async = true)
|
|
72
|
+
return true if documents.store_root_path.nil?
|
|
73
|
+
|
|
74
|
+
action_args = ['--local-workspace', documents.store_root_path]
|
|
75
|
+
PuppetLanguageServer.log_message(:info, "Loading Workspace Data via aggregate #{'(Async)' if async}...")
|
|
76
|
+
if async
|
|
77
|
+
sidecar_queue.enqueue('workspace_aggregate', action_args, false, connection_id)
|
|
78
|
+
else
|
|
79
|
+
sidecar_queue.execute('workspace_aggregate', action_args, false, connection_id)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
true
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def purge_workspace_data!
|
|
86
|
+
object_cache.remove_origin!(:workspace)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def sidecar_queue
|
|
92
|
+
PuppetLanguageServer::GlobalQueues.sidecar_queue
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def load_static_data_impl
|
|
96
|
+
bolt_static_data = PuppetLanguageServer::Sidecar::Protocol::AggregateMetadata.new
|
|
97
|
+
Dir.glob(File.join(PuppetLanguageServer.static_data_dir, 'bolt-*.json')) do |path|
|
|
98
|
+
PuppetLanguageServer.log_message(:debug, "Importing static data file #{path}...")
|
|
99
|
+
# No need to catch errors here. As this is static data and is tested in rspec
|
|
100
|
+
# Sure, we could have corrupt/missing files on disk, but then we have bigger issues
|
|
101
|
+
data = PuppetLanguageServer::Sidecar::Protocol::AggregateMetadata.new.from_json!(File.open(path, 'rb:UTF-8') { |f| f.read })
|
|
102
|
+
data.each_list { |_, list| bolt_static_data.concat!(list) }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
object_cache.import_sidecar_list!(bolt_static_data.classes, :class, :bolt)
|
|
106
|
+
object_cache.import_sidecar_list!(bolt_static_data.datatypes, :datatype, :bolt)
|
|
107
|
+
object_cache.import_sidecar_list!(bolt_static_data.functions, :function, :bolt)
|
|
108
|
+
object_cache.import_sidecar_list!(bolt_static_data.types, :type, :bolt)
|
|
109
|
+
|
|
110
|
+
bolt_static_data.each_list do |k, v|
|
|
111
|
+
if v.nil?
|
|
112
|
+
PuppetLanguageServer.log_message(:debug, "Static bolt data returned no #{k}")
|
|
113
|
+
else
|
|
114
|
+
PuppetLanguageServer.log_message(:debug, "Static bolt data returned #{v.count} #{k}")
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PuppetLanguageServer
|
|
4
|
+
module CrashDump
|
|
5
|
+
def self.default_crash_file
|
|
6
|
+
File.join(Dir.tmpdir, 'puppet_language_server_crash.txt')
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.write_crash_file(err, session_state, filename = nil, additional = {})
|
|
10
|
+
# Create the crash text
|
|
11
|
+
|
|
12
|
+
puppet_version = Puppet.version rescue 'Unknown' # rubocop:disable Style/RescueModifier
|
|
13
|
+
facter_version = Facter.version rescue 'Unknown' # rubocop:disable Style/RescueModifier
|
|
14
|
+
languageserver_version = PuppetLanguageServer.version rescue 'Unknown' # rubocop:disable Style/RescueModifier
|
|
15
|
+
|
|
16
|
+
crashtext = <<~TEXT
|
|
17
|
+
Puppet Language Server Crash File
|
|
18
|
+
-=--=--=--=--=--=--=--=--=--=--=-
|
|
19
|
+
#{DateTime.now.strftime('%a %b %e %Y %H:%M:%S %Z')}
|
|
20
|
+
Puppet Version #{puppet_version}
|
|
21
|
+
Facter Version #{facter_version}
|
|
22
|
+
Ruby Version #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}
|
|
23
|
+
Language Server Version #{languageserver_version}
|
|
24
|
+
|
|
25
|
+
Error: #{err}
|
|
26
|
+
|
|
27
|
+
Backtrace
|
|
28
|
+
---------
|
|
29
|
+
#{err.backtrace.join("\n")}
|
|
30
|
+
|
|
31
|
+
TEXT
|
|
32
|
+
# Append the documents in the cache
|
|
33
|
+
session_state.documents.document_uris.each do |uri|
|
|
34
|
+
crashtext += "Document - #{uri}\n---\n#{session_state.documents.document_content(uri)}\n\n"
|
|
35
|
+
end
|
|
36
|
+
# Append additional objects from the crash
|
|
37
|
+
additional.each do |k, v|
|
|
38
|
+
crashtext += "#{k}\n---\n#{v}\n\n"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
crash_file = filename.nil? ? default_crash_file : filename
|
|
42
|
+
File.binwrite(crash_file, crashtext)
|
|
43
|
+
rescue # rubocop:disable Style/RescueStandardError
|
|
44
|
+
# Swallow all errors. Errors in the error handler should not
|
|
45
|
+
# terminate the application
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
nil
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PuppetLanguageServer
|
|
4
|
+
module Epp
|
|
5
|
+
module ValidationProvider
|
|
6
|
+
def self.validate(content, _max_problems = 100)
|
|
7
|
+
result = []
|
|
8
|
+
# TODO: Need to implement max_problems
|
|
9
|
+
_problems = 0
|
|
10
|
+
|
|
11
|
+
begin
|
|
12
|
+
parser = Puppet::Pops::Parser::EvaluatingParser::EvaluatingEppParser.new
|
|
13
|
+
parser.parse_string(content, nil)
|
|
14
|
+
rescue StandardError => e
|
|
15
|
+
# Sometimes the error is in the cause not the root object itself
|
|
16
|
+
e = e.cause if !e.respond_to?(:line) && e.respond_to?(:cause)
|
|
17
|
+
ex_line = e.respond_to?(:line) && !e.line.nil? ? e.line - 1 : nil # Line numbers from puppet exceptions are base 1
|
|
18
|
+
ex_pos = e.respond_to?(:pos) && !e.pos.nil? ? e.pos : nil # Pos numbers from puppet are base 1
|
|
19
|
+
|
|
20
|
+
message = e.respond_to?(:message) ? e.message : nil
|
|
21
|
+
message = e.basic_message if message.nil? && e.respond_to?(:basic_message)
|
|
22
|
+
|
|
23
|
+
unless ex_line.nil? || ex_pos.nil? || message.nil?
|
|
24
|
+
result << LSP::Diagnostic.new('severity' => LSP::DiagnosticSeverity::ERROR,
|
|
25
|
+
'range' => LSP.create_range(ex_line, ex_pos, ex_line, ex_pos + 1),
|
|
26
|
+
'source' => 'Puppet',
|
|
27
|
+
'message' => message)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
result
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PuppetLanguageServer
|
|
4
|
+
module FacterHelper
|
|
5
|
+
# Facts
|
|
6
|
+
def self.fact(session_state, name)
|
|
7
|
+
session_state.object_cache.object_by_name(:fact, name)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.fact_value(session_state, name)
|
|
11
|
+
object = session_state.object_cache.object_by_name(:fact, name)
|
|
12
|
+
object.nil? ? nil : object.value
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.fact_names(session_state)
|
|
16
|
+
session_state.object_cache.object_names_by_section(:fact).map(&:to_s)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.facts_to_hash(session_state)
|
|
20
|
+
fact_hash = {}
|
|
21
|
+
session_state.object_cache.objects_by_section(:fact) { |factname, fact| fact_hash[factname.to_s] = fact.value }
|
|
22
|
+
fact_hash
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'puppet-languageserver/global_queues/single_instance_queue'
|
|
4
|
+
require 'puppet_editor_services/server'
|
|
5
|
+
require 'open3'
|
|
6
|
+
|
|
7
|
+
module PuppetLanguageServer
|
|
8
|
+
module GlobalQueues
|
|
9
|
+
class SidecarQueueJob < SingleInstanceQueueJob
|
|
10
|
+
attr_accessor :action, :additional_args, :handle_errors, :connection_id
|
|
11
|
+
|
|
12
|
+
def initialize(action, additional_args, handle_errors, connection_id)
|
|
13
|
+
super("#{action}-#{connection_id}")
|
|
14
|
+
@action = action
|
|
15
|
+
@additional_args = additional_args
|
|
16
|
+
@handle_errors = handle_errors
|
|
17
|
+
@connection_id = connection_id
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Module for enqueing and running sidecar jobs asynchronously
|
|
22
|
+
# When adding a job, it will remove any other for the same
|
|
23
|
+
# job in the queue, so that only the latest job needs to be processed.
|
|
24
|
+
class SidecarQueue < SingleInstanceQueue
|
|
25
|
+
def max_queue_threads
|
|
26
|
+
2
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def job_class
|
|
30
|
+
SidecarQueueJob
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def execute_job(job_object)
|
|
34
|
+
super
|
|
35
|
+
connection = connection_from_connection_id(job_object.connection_id)
|
|
36
|
+
raise "Connection is not available for connection id #{job_object.connection_id}" if connection.nil?
|
|
37
|
+
|
|
38
|
+
sidecar_path = File.expand_path(File.join(__dir__, '..', '..', '..', 'puppet-languageserver-sidecar'))
|
|
39
|
+
args = ['--action', job_object.action].concat(job_object.additional_args).concat(sidecar_args_from_connection(connection))
|
|
40
|
+
cmd = ['ruby', sidecar_path].concat(args)
|
|
41
|
+
PuppetLanguageServer.log_message(:debug, "SidecarQueue Thread: Running sidecar #{cmd}")
|
|
42
|
+
stdout, stderr, status = run_sidecar(cmd)
|
|
43
|
+
PuppetLanguageServer.log_message(:warning, "SidecarQueue Thread: Calling sidecar with #{args.join(' ')} returned exitcode #{status.exitstatus}, #{stderr}")
|
|
44
|
+
return nil unless status.exitstatus.zero?
|
|
45
|
+
|
|
46
|
+
# It's possible server has closed the connection while the sidecar is running.
|
|
47
|
+
# So raise if the connection is no longer available
|
|
48
|
+
raise "Connection is no longer available for connection id #{job_object.connection_id}" if connection_from_connection_id(job_object.connection_id).nil?
|
|
49
|
+
|
|
50
|
+
session_state = session_state_from_connection(connection)
|
|
51
|
+
raise "Session state is not available for connection id #{job_object.connection_id}" if session_state.nil?
|
|
52
|
+
|
|
53
|
+
cache = session_state.object_cache
|
|
54
|
+
|
|
55
|
+
# Correctly encode the result as UTF8
|
|
56
|
+
result = stdout.bytes.pack('U*')
|
|
57
|
+
|
|
58
|
+
case job_object.action.downcase
|
|
59
|
+
when 'default_aggregate'
|
|
60
|
+
lists = PuppetLanguageServer::Sidecar::Protocol::AggregateMetadata.new.from_json!(result)
|
|
61
|
+
cache.import_sidecar_list!(lists.classes, :class, :default)
|
|
62
|
+
cache.import_sidecar_list!(lists.datatypes, :datatype, :default)
|
|
63
|
+
cache.import_sidecar_list!(lists.functions, :function, :default)
|
|
64
|
+
cache.import_sidecar_list!(lists.types, :type, :default)
|
|
65
|
+
|
|
66
|
+
lists.each_list do |k, v|
|
|
67
|
+
if v.nil?
|
|
68
|
+
PuppetLanguageServer.log_message(:debug, "SidecarQueue Thread: default_aggregate returned no #{k}")
|
|
69
|
+
else
|
|
70
|
+
PuppetLanguageServer.log_message(:debug, "SidecarQueue Thread: default_aggregate returned #{v.count} #{k}")
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
when 'default_classes'
|
|
75
|
+
list = PuppetLanguageServer::Sidecar::Protocol::PuppetClassList.new.from_json!(result)
|
|
76
|
+
cache.import_sidecar_list!(list, :class, :default)
|
|
77
|
+
PuppetLanguageServer.log_message(:debug, "SidecarQueue Thread: default_classes returned #{list.count} items")
|
|
78
|
+
|
|
79
|
+
when 'default_datatypes'
|
|
80
|
+
list = PuppetLanguageServer::Sidecar::Protocol::PuppetDataTypeList.new.from_json!(result)
|
|
81
|
+
cache.import_sidecar_list!(list, :datatype, :default)
|
|
82
|
+
PuppetLanguageServer.log_message(:debug, "SidecarQueue Thread: default_datatypes returned #{list.count} items")
|
|
83
|
+
|
|
84
|
+
when 'default_functions'
|
|
85
|
+
list = PuppetLanguageServer::Sidecar::Protocol::PuppetFunctionList.new.from_json!(result)
|
|
86
|
+
cache.import_sidecar_list!(list, :function, :default)
|
|
87
|
+
PuppetLanguageServer.log_message(:debug, "SidecarQueue Thread: default_functions returned #{list.count} items")
|
|
88
|
+
|
|
89
|
+
when 'default_types'
|
|
90
|
+
list = PuppetLanguageServer::Sidecar::Protocol::PuppetTypeList.new.from_json!(result)
|
|
91
|
+
cache.import_sidecar_list!(list, :type, :default)
|
|
92
|
+
PuppetLanguageServer.log_message(:debug, "SidecarQueue Thread: default_types returned #{list.count} items")
|
|
93
|
+
|
|
94
|
+
when 'facts'
|
|
95
|
+
list = PuppetLanguageServer::Sidecar::Protocol::FactList.new.from_json!(result)
|
|
96
|
+
cache.import_sidecar_list!(list, :fact, :default)
|
|
97
|
+
PuppetLanguageServer.log_message(:debug, "SidecarQueue Thread: facts returned #{list.count} items")
|
|
98
|
+
|
|
99
|
+
when 'node_graph'
|
|
100
|
+
return PuppetLanguageServer::Sidecar::Protocol::PuppetNodeGraph.new.from_json!(result)
|
|
101
|
+
|
|
102
|
+
when 'resource_list'
|
|
103
|
+
return PuppetLanguageServer::Sidecar::Protocol::ResourceList.new.from_json!(result)
|
|
104
|
+
|
|
105
|
+
when 'workspace_aggregate'
|
|
106
|
+
lists = PuppetLanguageServer::Sidecar::Protocol::AggregateMetadata.new.from_json!(result)
|
|
107
|
+
cache.import_sidecar_list!(lists.classes, :class, :workspace)
|
|
108
|
+
cache.import_sidecar_list!(lists.datatypes, :datatype, :workspace)
|
|
109
|
+
cache.import_sidecar_list!(lists.functions, :function, :workspace)
|
|
110
|
+
cache.import_sidecar_list!(lists.types, :type, :workspace)
|
|
111
|
+
|
|
112
|
+
lists.each_list do |k, v|
|
|
113
|
+
if v.nil?
|
|
114
|
+
PuppetLanguageServer.log_message(:debug, "SidecarQueue Thread: workspace_aggregate returned no #{k}")
|
|
115
|
+
else
|
|
116
|
+
PuppetLanguageServer.log_message(:debug, "SidecarQueue Thread: workspace_aggregate returned #{v.count} #{k}")
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
when 'workspace_classes'
|
|
121
|
+
list = PuppetLanguageServer::Sidecar::Protocol::PuppetClassList.new.from_json!(result)
|
|
122
|
+
cache.import_sidecar_list!(list, :class, :workspace)
|
|
123
|
+
PuppetLanguageServer.log_message(:debug, "SidecarQueue Thread: workspace_classes returned #{list.count} items")
|
|
124
|
+
|
|
125
|
+
when 'workspace_datatypes'
|
|
126
|
+
list = PuppetLanguageServer::Sidecar::Protocol::PuppetDataTypeList.new.from_json!(result)
|
|
127
|
+
cache.import_sidecar_list!(list, :datatype, :workspace)
|
|
128
|
+
PuppetLanguageServer.log_message(:debug, "SidecarQueue Thread: workspace_datatypes returned #{list.count} items")
|
|
129
|
+
|
|
130
|
+
when 'workspace_functions'
|
|
131
|
+
list = PuppetLanguageServer::Sidecar::Protocol::PuppetFunctionList.new.from_json!(result)
|
|
132
|
+
cache.import_sidecar_list!(list, :function, :workspace)
|
|
133
|
+
PuppetLanguageServer.log_message(:debug, "SidecarQueue Thread: workspace_functions returned #{list.count} items")
|
|
134
|
+
|
|
135
|
+
when 'workspace_types'
|
|
136
|
+
list = PuppetLanguageServer::Sidecar::Protocol::PuppetTypeList.new.from_json!(result)
|
|
137
|
+
cache.import_sidecar_list!(list, :type, :workspace)
|
|
138
|
+
PuppetLanguageServer.log_message(:debug, "SidecarQueue Thread: workspace_types returned #{list.count} items")
|
|
139
|
+
|
|
140
|
+
else
|
|
141
|
+
PuppetLanguageServer.log_message(:error, "SidecarQueue Thread: Unknown action #{job_object.action}")
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
true
|
|
145
|
+
rescue StandardError => e
|
|
146
|
+
raise unless job_object.handle_errors
|
|
147
|
+
|
|
148
|
+
PuppetLanguageServer.log_message(:error, "SidecarQueue Thread: Error running action #{job_object.action}. #{e}\n#{e.backtrace}")
|
|
149
|
+
nil
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
private
|
|
153
|
+
|
|
154
|
+
def connection_from_connection_id(connection_id)
|
|
155
|
+
PuppetEditorServices::Server.current_server.connection(connection_id)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def session_state_from_connection(connection)
|
|
159
|
+
return if connection.nil?
|
|
160
|
+
|
|
161
|
+
handler = connection.protocol.handler
|
|
162
|
+
handler.respond_to?(:session_state) ? handler.session_state : nil
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def run_sidecar(cmd)
|
|
166
|
+
Open3.capture3(*cmd)
|
|
167
|
+
|
|
168
|
+
# The following code should ONLY be uncommented when debugging acceptance tests failures.
|
|
169
|
+
# It caches the sidecar responses so tests sidecar calls will be (VERY) quick, which speeds
|
|
170
|
+
# up feedback loops. It has a rubocop failure on purpose so that it fails automated CI checks
|
|
171
|
+
# and should never be merged in a Pull Request
|
|
172
|
+
#
|
|
173
|
+
# output_dir = File.expand_path(File.join(__dir__, '..', '..', '..', 'output'))
|
|
174
|
+
# FileUtils.mkdir_p(output_dir) unless Dir.exist?(output_dir)
|
|
175
|
+
# require 'digest'
|
|
176
|
+
# cache_file = File.join(output_dir, Digest::MD5.hexdigest(cmd.join(' ')) + '.json')
|
|
177
|
+
# if File.exist?(cache_file)
|
|
178
|
+
# return [
|
|
179
|
+
# File.read(cache_file, mode: 'rb', encoding: 'utf-8'),
|
|
180
|
+
# '',
|
|
181
|
+
# Struct.new(:exitstatus).new(0)
|
|
182
|
+
# ]
|
|
183
|
+
# end
|
|
184
|
+
# # Rubocop failure is here on purpose
|
|
185
|
+
# stdout,stderr,status = Open3.capture3(*cmd)
|
|
186
|
+
# File.write(cache_file, stdout, mode: 'wb', encoding: 'utf-8')
|
|
187
|
+
# [stdout, stderr, status]
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def sidecar_args_from_connection(connection)
|
|
191
|
+
return nil if connection.nil?
|
|
192
|
+
|
|
193
|
+
options = connection.server.handler_options
|
|
194
|
+
return [] if options.nil?
|
|
195
|
+
|
|
196
|
+
result = []
|
|
197
|
+
result << '--no-cache' if options[:disable_sidecar_cache]
|
|
198
|
+
result << "--puppet-version=#{Puppet.version}"
|
|
199
|
+
result << "--feature-flags=#{options[:flags].join(',')}" if options[:flags] && !options[:flags].empty?
|
|
200
|
+
result << "--puppet-settings=#{options[:puppet_settings].join(',')}" if options[:puppet_settings] && !options[:puppet_settings].empty?
|
|
201
|
+
result
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PuppetLanguageServer
|
|
4
|
+
module GlobalQueues
|
|
5
|
+
class SingleInstanceQueueJob
|
|
6
|
+
# Unique key for the job. The SingleInstanceQueue uses the key
|
|
7
|
+
# to ensure that only a single instance is in the queue
|
|
8
|
+
attr_reader :key
|
|
9
|
+
|
|
10
|
+
def initialize(key)
|
|
11
|
+
@key = key
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Base class for enqueing and running queued jobs asynchronously
|
|
16
|
+
# When adding a job, it will remove any other for the same
|
|
17
|
+
# key in the queue, so that only the latest job needs to be processed.
|
|
18
|
+
class SingleInstanceQueue
|
|
19
|
+
def initialize
|
|
20
|
+
@queue = []
|
|
21
|
+
@queue_mutex = Mutex.new
|
|
22
|
+
@queue_threads_mutex = Mutex.new
|
|
23
|
+
@queue_threads = []
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Default is one thread to process the queue
|
|
27
|
+
def max_queue_threads
|
|
28
|
+
1
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# The ruby Job class that this queue operates on
|
|
32
|
+
# Should be inherited from SingleInstanceQueueJob
|
|
33
|
+
# @api asbtract
|
|
34
|
+
def job_class
|
|
35
|
+
SingleInstanceQueueJob
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def new_job(*args)
|
|
39
|
+
job_class.new(*args)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Helpful method to create, then enqueue a job
|
|
43
|
+
def enqueue(*args)
|
|
44
|
+
enqueue_job(new_job(*args))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Enqueue a job
|
|
48
|
+
def enqueue_job(job_object)
|
|
49
|
+
raise "Invalid job object for #{self.class}. Got #{job_object.class} but expected #{job_class}" unless job_object.is_a?(job_class)
|
|
50
|
+
|
|
51
|
+
@queue_mutex.synchronize do
|
|
52
|
+
@queue.reject! { |queue_item| queue_item.key == job_object.key }
|
|
53
|
+
@queue << job_object
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
@queue_threads_mutex.synchronize do
|
|
57
|
+
# Clear up any done threads
|
|
58
|
+
@queue_threads.reject! { |thr| thr.nil? || !thr.alive? }
|
|
59
|
+
# Append a new thread if we have space
|
|
60
|
+
if @queue_threads.count < max_queue_threads
|
|
61
|
+
@queue_threads << Thread.new do
|
|
62
|
+
thread_worker
|
|
63
|
+
rescue => e # rubocop:disable Style/RescueStandardError
|
|
64
|
+
PuppetLanguageServer.log_message(:error, "Error in #{self.class} Thread: #{e}")
|
|
65
|
+
raise
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
nil
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Helpful method to create, then enqueue a job
|
|
73
|
+
def execute(*args)
|
|
74
|
+
execute_job(new_job(*args))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Synchronously executes the same work as an enqueued item. Does not consume a queue thread
|
|
78
|
+
# The thread worker calls this method when processing enqueued items
|
|
79
|
+
# @abstract
|
|
80
|
+
def execute_job(job_object)
|
|
81
|
+
raise "Invalid job object for #{self.class}. Got #{job_object.class} but expected #{job_class}" unless job_object.is_a?(job_class)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Wait for the queue to become empty
|
|
85
|
+
def drain_queue
|
|
86
|
+
@queue_threads.each do |item|
|
|
87
|
+
item.join unless item.nil? || !item.alive?
|
|
88
|
+
end
|
|
89
|
+
nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Testing helper resets the queue and prepopulates it with
|
|
93
|
+
# a known arbitrary configuration.
|
|
94
|
+
# ONLY USE THIS FOR TESTING!
|
|
95
|
+
def reset_queue(initial_state = [])
|
|
96
|
+
@queue_mutex.synchronize do
|
|
97
|
+
@queue = initial_state
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
# Thread worker which processes all jobs in the queue and calls the sidecar for each action
|
|
104
|
+
def thread_worker
|
|
105
|
+
work_item = nil
|
|
106
|
+
loop do
|
|
107
|
+
@queue_mutex.synchronize do
|
|
108
|
+
return if @queue.empty?
|
|
109
|
+
|
|
110
|
+
work_item = @queue.shift
|
|
111
|
+
end
|
|
112
|
+
return if work_item.nil?
|
|
113
|
+
|
|
114
|
+
# Perform action
|
|
115
|
+
begin
|
|
116
|
+
# When running async (i.e. from a thread swallow any output)
|
|
117
|
+
_result = execute_job(work_item)
|
|
118
|
+
rescue StandardError => e
|
|
119
|
+
PuppetLanguageServer.log_message(:error, "#{self.class} Thread: Error running job #{work_item.key}. #{e}\n#{e.backtrace}")
|
|
120
|
+
nil
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'puppet-languageserver/global_queues/single_instance_queue'
|
|
4
|
+
require 'puppet_editor_services/server'
|
|
5
|
+
|
|
6
|
+
module PuppetLanguageServer
|
|
7
|
+
module GlobalQueues
|
|
8
|
+
class ValidationQueueJob < SingleInstanceQueueJob
|
|
9
|
+
attr_accessor :file_uri, :doc_version, :connection_id, :options
|
|
10
|
+
|
|
11
|
+
def initialize(file_uri, doc_version, connection_id, options = {})
|
|
12
|
+
super(file_uri)
|
|
13
|
+
@file_uri = file_uri
|
|
14
|
+
@doc_version = doc_version
|
|
15
|
+
@connection_id = connection_id
|
|
16
|
+
@options = options
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Class for enqueing and running document level validation asynchronously
|
|
21
|
+
#
|
|
22
|
+
# Uses a single instance queue so only the latest document needs to be processed.
|
|
23
|
+
# It will also ignore sending back validation results to the client if the document is updated during the validation process
|
|
24
|
+
class ValidationQueue < SingleInstanceQueue
|
|
25
|
+
def max_queue_threads
|
|
26
|
+
1
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def job_class
|
|
30
|
+
ValidationQueueJob
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def execute_job(job_object)
|
|
34
|
+
super
|
|
35
|
+
session_state = session_state_from_connection_id(job_object.connection_id)
|
|
36
|
+
document_store = session_state.nil? ? nil : session_state.documents
|
|
37
|
+
raise "Document store is not available for connection id #{job_object.connection_id}" unless document_store
|
|
38
|
+
|
|
39
|
+
# Check if the document still exists
|
|
40
|
+
doc = document_store.get_document(job_object.file_uri)
|
|
41
|
+
unless doc
|
|
42
|
+
# Send an empty diagnostics message to clear the diagnostics
|
|
43
|
+
send_diagnostics(job_object.connection_id, job_object.file_uri, [])
|
|
44
|
+
PuppetLanguageServer.log_message(:debug, "#{self.class.name}: Ignoring #{job_object.file_uri} as it is has been removed.")
|
|
45
|
+
return
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Check if the document is the latest version
|
|
49
|
+
content = document_store.document_content(job_object.file_uri, job_object.doc_version)
|
|
50
|
+
unless content
|
|
51
|
+
PuppetLanguageServer.log_message(:debug, "#{self.class.name}: Ignoring #{job_object.file_uri} as it is not the latest version.")
|
|
52
|
+
return
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Perform validation
|
|
56
|
+
options = job_object.options.dup
|
|
57
|
+
results = case document_store.document_type(job_object.file_uri)
|
|
58
|
+
when :manifest
|
|
59
|
+
options[:tasks_mode] = document_store.plan_file?(job_object.file_uri)
|
|
60
|
+
PuppetLanguageServer::Manifest::ValidationProvider.validate(session_state, content, options)
|
|
61
|
+
when :epp
|
|
62
|
+
PuppetLanguageServer::Epp::ValidationProvider.validate(content)
|
|
63
|
+
when :puppetfile
|
|
64
|
+
options[:document_uri] = job_object.file_uri
|
|
65
|
+
PuppetLanguageServer::Puppetfile::ValidationProvider.validate(content, options)
|
|
66
|
+
else
|
|
67
|
+
[]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Because this may be asynchronous it's possible the user has edited the document while we're performing validation.
|
|
71
|
+
# Check if the document is still latest version and ignore the results if it's no longer the latest
|
|
72
|
+
current_version = document_store.document_version(job_object.file_uri)
|
|
73
|
+
if current_version != job_object.doc_version
|
|
74
|
+
PuppetLanguageServer.log_message(:debug, "ValidationQueue Thread: Ignoring #{job_object.file_uri} as has changed version from #{job_object.doc_version} to #{current_version}")
|
|
75
|
+
return
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Send the response
|
|
79
|
+
send_diagnostics(job_object.connection_id, job_object.file_uri, results)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def session_state_from_connection_id(connection_id)
|
|
85
|
+
connection = PuppetEditorServices::Server.current_server.connection(connection_id)
|
|
86
|
+
return if connection.nil?
|
|
87
|
+
|
|
88
|
+
handler = connection.protocol.handler
|
|
89
|
+
handler.respond_to?(:session_state) ? handler.session_state : nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def send_diagnostics(connection_id, file_uri, diagnostics)
|
|
93
|
+
connection = PuppetEditorServices::Server.current_server.connection(connection_id)
|
|
94
|
+
return if connection.nil?
|
|
95
|
+
|
|
96
|
+
connection.protocol.encode_and_send(
|
|
97
|
+
::PuppetEditorServices::Protocol::JsonRPCMessages.new_notification('textDocument/publishDiagnostics', 'uri' => file_uri, 'diagnostics' => diagnostics)
|
|
98
|
+
)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'puppet-languageserver/global_queues/validation_queue'
|
|
4
|
+
require 'puppet-languageserver/global_queues/sidecar_queue'
|
|
5
|
+
|
|
6
|
+
module PuppetLanguageServer
|
|
7
|
+
module GlobalQueues
|
|
8
|
+
def self.validate_queue
|
|
9
|
+
@validate_queue ||= ValidationQueue.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.sidecar_queue
|
|
13
|
+
@sidecar_queue ||= SidecarQueue.new
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|