Skip to content

Commit c7fdb81

Browse files
committed
(GH-26) Refactor workspace detection
Previously the --local-workspace option was mostly ignored in the puppetfile validation. However this has caused problems when validating inside control repositories which do not have a metadata.json file (instead it's Puppetfile). This commit refactors the workspace detection out of the puppetfile validation and moves it into the Document Store. This commit also adds a simple timed based caching mechanism to reduce File IO as it can be exercised vigourously when users are typing and validation is occuring very often. An arbitrary value of 60 seconds was chosen the TTL of the cache. This means that after 60 seconds the workspace will be re-evaluated to make sure the user is still in a module (metadata.json) or control repo (Puppetfile).
1 parent 85f123c commit c7fdb81

File tree

9 files changed

+216
-0
lines changed

9 files changed

+216
-0
lines changed

lib/puppet-languageserver.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@ def self.init_puppet(options)
126126
log_message(:info, 'Initializing Puppet Helper Cache...')
127127
PuppetLanguageServer::PuppetHelper.configure_cache(options[:cache])
128128

129+
log_message(:debug, 'Initializing Document Store...')
130+
PuppetLanguageServer::DocumentStore.initialize_store(options)
131+
129132
log_message(:info, 'Initializing settings...')
130133
if options[:fast_start_langserver]
131134
Thread.new do

lib/puppet-languageserver/document_store.rb

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,85 @@ def self.document_type(uri)
5151
:unknown
5252
end
5353
end
54+
55+
# Workspace management
56+
WORKSPACE_CACHE_TTL_SECONDS = 60
57+
def self.initialize_store(options = {})
58+
@workspace_path = options[:workspace]
59+
@workspace_info_cache = {
60+
:expires => Time.new - 120
61+
}
62+
end
63+
64+
def self.store_root_path
65+
store_details[:root_path]
66+
end
67+
68+
def self.store_has_module_metadata?
69+
store_details[:has_metadatajson]
70+
end
71+
72+
def self.store_has_puppetfile?
73+
store_details[:has_puppetfile]
74+
end
75+
76+
# Given a path, locate a metadata.json or Puppetfile file to determine where the
77+
# root of the module/control repo actually is
78+
def self.find_root_path(path)
79+
return nil if path.nil?
80+
81+
filepath = Pathname.new(path).expand_path
82+
return nil unless filepath.exist?
83+
84+
if filepath.directory?
85+
directory = filepath
86+
else
87+
directory = filepath.dirname
88+
end
89+
90+
root_path = nil
91+
directory.ascend do |p|
92+
if p.join('metadata.json').exist? || p.join('Puppetfile').exist?
93+
root_path = p.to_s
94+
break
95+
end
96+
end
97+
98+
root_path
99+
end
100+
private_class_method :find_root_path
101+
102+
def self.store_details
103+
return @workspace_info_cache unless @workspace_info_cache[:never_expires] || @workspace_info_cache[:expires] < Time.new
104+
# TTL has expired, time to calculate the document store details
105+
106+
new_cache = {
107+
:root_path => nil,
108+
:has_puppetfile => false,
109+
:has_metadatajson => false
110+
}
111+
if @workspace_path.nil?
112+
# If we have never been given a local workspace path on the command line then there is really no
113+
# way to know where the module file system path is. Therefore the root_path is nil and assume that
114+
# puppetfile and metadata.json does not exist. And don't bother trying to re-evaluate
115+
new_cache[:never_expires] = true
116+
else
117+
root_path = find_root_path(@workspace_path)
118+
if root_path.nil?
119+
new_cache[:root_path] = @workspace_path
120+
else
121+
new_cache[:root_path] = root_path
122+
new_cache[:has_metadatajson] = File.exist?(File.join(root_path, 'metadata.json'))
123+
new_cache[:has_puppetfile] = File.exist?(File.join(root_path, 'Puppetfile'))
124+
end
125+
end
126+
new_cache[:expires] = Time.new + WORKSPACE_CACHE_TTL_SECONDS
127+
128+
@doc_mutex.synchronize do
129+
@workspace_info_cache = new_cache
130+
end
131+
@workspace_info_cache
132+
end
133+
private_class_method :store_details
54134
end
55135
end
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
forge 'https://forge.puppetlabs.com/'
2+
3+
# Modules from the Puppet Forge
4+
mod 'puppetlabs-stdlib', '1.0.0'

spec/languageserver/fixtures/control_repos/valid/data/.gitkeep

Whitespace-only changes.

spec/languageserver/fixtures/control_repos/valid/site/profile/.gitkeep

Whitespace-only changes.

spec/languageserver/fixtures/control_repos/valid/site/role/.gitkeep

Whitespace-only changes.

spec/languageserver/fixtures/module_sources/valid/manifests/.gitkeep

Whitespace-only changes.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "testfixture-valid",
3+
"version": "0.1.0",
4+
"author": "testfixture",
5+
"summary": "Skeleton module test fixture",
6+
"license": "Apache-2.0",
7+
"source": "http://localhost",
8+
"dependencies": []
9+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
require 'spec_helper'
2+
3+
describe 'PuppetLanguageServer::DocumentStore' do
4+
let(:subject) { PuppetLanguageServer::DocumentStore }
5+
6+
RSpec.shared_examples 'an empty workspace' do |expected_root_path|
7+
it 'should return the workspace directory for the root_path' do
8+
expect(PuppetLanguageServer::DocumentStore.store_root_path).to eq(expected_root_path)
9+
end
10+
11+
it 'should not find module metadata' do
12+
expect(PuppetLanguageServer::DocumentStore.store_has_module_metadata?).to be false
13+
end
14+
15+
it 'should not find puppetfile' do
16+
expect(PuppetLanguageServer::DocumentStore.store_has_puppetfile?).to be false
17+
end
18+
end
19+
20+
RSpec.shared_examples 'a puppetfile workspace' do |expected_root_path|
21+
it 'should return the control repo root for the root_path' do
22+
expect(PuppetLanguageServer::DocumentStore.store_root_path).to eq(expected_root_path)
23+
end
24+
25+
it 'should not find module metadata' do
26+
expect(PuppetLanguageServer::DocumentStore.store_has_module_metadata?).to be false
27+
end
28+
29+
it 'should find puppetfile' do
30+
expect(PuppetLanguageServer::DocumentStore.store_has_puppetfile?).to be true
31+
end
32+
end
33+
34+
RSpec.shared_examples 'a metadata.json workspace' do |expected_root_path|
35+
it 'should return the control repo root for the root_path' do
36+
expect(PuppetLanguageServer::DocumentStore.store_root_path).to eq(expected_root_path)
37+
end
38+
39+
it 'should find module metadata' do
40+
expect(PuppetLanguageServer::DocumentStore.store_has_module_metadata?).to be true
41+
end
42+
43+
it 'should not find puppetfile' do
44+
expect(PuppetLanguageServer::DocumentStore.store_has_puppetfile?).to be false
45+
end
46+
end
47+
48+
# Empty or missing workspace
49+
context 'given a workspace option which is nil' do
50+
let(:server_options) { {} }
51+
52+
before(:each) do
53+
PuppetLanguageServer::DocumentStore.initialize_store(server_options)
54+
end
55+
56+
it_should_behave_like 'an empty workspace', nil
57+
end
58+
59+
context 'given a workspace option with a missing directory' do
60+
let(:server_options) { { :workspace => '/a/directory/which/does/not/exist' } }
61+
62+
before(:each) do
63+
PuppetLanguageServer::DocumentStore.initialize_store(server_options)
64+
end
65+
66+
it_should_behave_like 'an empty workspace', '/a/directory/which/does/not/exist'
67+
end
68+
69+
# Puppetfile style workspaces
70+
context 'given a workspace option with a puppetfile' do
71+
expected_root = File.join($fixtures_dir, 'control_repos', 'valid')
72+
73+
let(:server_options) { { :workspace => expected_root } }
74+
75+
before(:each) do
76+
PuppetLanguageServer::DocumentStore.initialize_store(server_options)
77+
end
78+
79+
it_should_behave_like 'a puppetfile workspace', expected_root
80+
end
81+
82+
context 'given a workspace option which has a parent directory with a puppetfile' do
83+
expected_root = File.join($fixtures_dir, 'control_repos', 'valid')
84+
deep_path = File.join(expected_root, 'site', 'profile')
85+
86+
let(:server_options) { { :workspace => deep_path } }
87+
88+
before(:each) do
89+
PuppetLanguageServer::DocumentStore.initialize_store(server_options)
90+
end
91+
92+
it_should_behave_like 'a puppetfile workspace', expected_root
93+
end
94+
95+
# Module metadata style workspaces
96+
context 'given a workspace option with metadata.json' do
97+
expected_root = File.join($fixtures_dir, 'module_sources', 'valid')
98+
99+
let(:server_options) { { :workspace => expected_root } }
100+
101+
before(:each) do
102+
PuppetLanguageServer::DocumentStore.initialize_store(server_options)
103+
end
104+
105+
it_should_behave_like 'a metadata.json workspace', expected_root
106+
end
107+
108+
context 'given a workspace option which has a parent directory with metadata.json' do
109+
expected_root = File.join($fixtures_dir, 'module_sources', 'valid')
110+
deep_path = File.join(expected_root, 'manifests')
111+
112+
let(:server_options) { { :workspace => deep_path } }
113+
114+
before(:each) do
115+
PuppetLanguageServer::DocumentStore.initialize_store(server_options)
116+
end
117+
118+
it_should_behave_like 'a metadata.json workspace', expected_root
119+
end
120+
end

0 commit comments

Comments
 (0)