Skip to content

Commit 9b954d8

Browse files
committed
(GH-130) Prepare to use PDK ruby if puppet agent not available
Previously the language server required a puppet-agent installation however some users are not able to install a puppet-agent, but instead the PDK. This commit changes the connect.ts script; - Refactors the ruby detection into two helpers, getLanguageServerFromPDK and getLanguageServerFromPuppetAgent. These helpers are responsible for detecting and setting up the process creation for their respective ruby environments on any platform type - Removes the need for environment.bat on Windows by setting up the process env variables at process creation - Updates the logging to be more verbose in the detection methods Note the functionality to actually use the PDK is currently disabled as the configuration settings and user story need to be completed first. However to not lose the effort, the code still exists but is commented out.
1 parent 67a80a0 commit 9b954d8

File tree

1 file changed

+182
-42
lines changed

1 file changed

+182
-42
lines changed

client/src/connection.ts

Lines changed: 182 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -209,12 +209,31 @@ export class ConnectionManager implements IConnectionManager {
209209
callback(proc);
210210
}
211211

212-
private createLanguageServerProcess(serverExe: string, callback : Function) {
213-
this.logger.debug('Language server found at: ' + serverExe)
212+
private getDirectories(parent) {
213+
return fs.readdirSync(parent).filter(function (file) {
214+
return fs.statSync(path.join(parent, file)).isDirectory();
215+
});
216+
}
214217

215-
let cmd: string = undefined;
216-
let args = [serverExe];
217-
let options : cp.SpawnOptions = {};
218+
private pathEnvSeparator() {
219+
if (process.platform == 'win32') {
220+
return ";";
221+
} else {
222+
return ":";
223+
}
224+
}
225+
226+
private getLanguageServerFromPuppetAgent(serverExe) {
227+
let logPrefix: string = '[getLanguageServerFromPuppetAgent] ';
228+
// setup defaults
229+
let spawn_options: cp.SpawnOptions = {}
230+
spawn_options.env = process.env;
231+
let result = {
232+
command: 'ruby',
233+
args: [serverExe],
234+
options: spawn_options,
235+
}
236+
let puppetAgentDir: string = null;
218237

219238
// type Platform = 'aix'
220239
// | 'android'
@@ -234,69 +253,190 @@ export class ConnectionManager implements IConnectionManager {
234253
comspec = path.join(process.env["WINDIR"],"sysnative","cmd.exe");
235254
programFiles = process.env["ProgramW6432"];
236255
}
237-
let puppetDir: string = undefined;
256+
238257
if (this.connectionConfiguration.puppetAgentDir == undefined) {
239-
puppetDir = path.join(programFiles, "Puppet Labs", "Puppet");
258+
puppetAgentDir = path.join(programFiles, "Puppet Labs", "Puppet");
240259
} else {
241-
puppetDir = this.connectionConfiguration.puppetAgentDir;
242-
}
243-
let environmentBat : string = path.join(puppetDir,"bin","environment.bat")
244-
245-
if (!fs.existsSync(puppetDir)) {
246-
this.setSessionFailure("Could not find Puppet Agent at " + puppetDir);
247-
vscode.window.showWarningMessage('Could not find Puppet Agent installed at "' + puppetDir + '". Functionality will be limited to syntax highlighting');
248-
return;
260+
puppetAgentDir = this.connectionConfiguration.puppetAgentDir;
249261
}
250262

251-
cmd = comspec;
252-
args = ['/K','CALL',environmentBat,'&&','ruby.exe',serverExe]
253-
options = {
254-
env: process.env,
255-
stdio: 'pipe',
256-
};
263+
result.options.stdio = 'pipe';
257264
break;
258265
default:
259-
this.logger.debug('Starting language server')
260-
261-
let rubyPath: string = undefined;
262266
if (this.connectionConfiguration.puppetAgentDir == undefined) {
263-
rubyPath = '/opt/puppetlabs/puppet/bin/ruby';
267+
puppetAgentDir = '/opt/puppetlabs/puppet';
264268
} else {
265-
rubyPath = path.join(this.connectionConfiguration.puppetAgentDir, "bin", "ruby");
269+
puppetAgentDir = this.connectionConfiguration.puppetAgentDir;
266270
}
267-
if (fs.existsSync(rubyPath)) { cmd = rubyPath }
268-
269-
// Default to ruby on the path
270-
if (cmd == undefined) { cmd = 'ruby' }
271-
options = {
272-
shell: true,
273-
env: process.env,
274-
stdio: 'pipe',
275-
};
271+
272+
result.options.stdio = 'pipe';
273+
result.options.shell = true;
274+
break;
275+
}
276+
// Check if this really is a Puppet Agent installation
277+
if (!fs.existsSync(path.join(puppetAgentDir, "VERSION"))) {
278+
this.logger.debug(logPrefix + "Could not find a valid Puppet Agent installation at " + puppetAgentDir);
279+
return null;
280+
} else {
281+
this.logger.debug(logPrefix + "Found a valid Puppet Agent installation at " + puppetAgentDir);
276282
}
277283

278-
if (cmd == undefined) {
279-
this.setSessionFailure("Unable to start the Language Server on this platform");
280-
vscode.window.showWarningMessage('The Puppet Language Server is not supported on this platform (' + process.platform + '). Functionality will be limited to syntax highlighting');
284+
let puppetDir = path.join(puppetAgentDir,"puppet");
285+
let facterDir = path.join(puppetAgentDir,"facter");
286+
let hieraDir = path.join(puppetAgentDir,"hiera");
287+
let mcoDir = path.join(puppetAgentDir,"mcollective");
288+
let rubydir = path.join(puppetAgentDir,"sys","ruby");
289+
let rubylib = path.join(puppetDir,"lib") + this.pathEnvSeparator() + path.join(facterDir,"lib") + this.pathEnvSeparator() + path.join(hieraDir,"lib") + this.pathEnvSeparator() + path.join(mcoDir,"lib")
290+
291+
if (process.platform == 'win32') {
292+
// Translate all slashes to / style to avoid puppet/ruby issue #11930
293+
rubylib = rubylib.replace(/\\/g,"/");
294+
}
295+
296+
// Setup the process environment variables
297+
if (result.options.env.PATH == undefined) { result.options.env.PATH = ''; }
298+
if (result.options.env.RUBYLIB == undefined) { result.options.env.RUBYLIB = ''; }
299+
result.options.env.RUBY_DIR = rubydir;
300+
result.options.env.PATH = path.join(puppetDir,"bin") + this.pathEnvSeparator() + path.join(facterDir,"bin") + this.pathEnvSeparator() + path.join(hieraDir,"bin") + this.pathEnvSeparator() + path.join(mcoDir,"bin") +
301+
this.pathEnvSeparator() + path.join(puppetAgentDir,"bin") + this.pathEnvSeparator() + path.join(rubydir,"bin") + this.pathEnvSeparator() + path.join(puppetAgentDir,"sys","tools","bin") +
302+
this.pathEnvSeparator() + result.options.env.PATH;
303+
result.options.env.RUBYLIB = rubylib + this.pathEnvSeparator() + result.options.env.RUBYLIB;
304+
result.options.env.RUBYOPT = 'rubygems';
305+
result.options.env.SSL_CERT_FILE = path.join(puppetDir,"ssl","cert.pem");
306+
result.options.env.SSL_CERT_DIR = path.join(puppetDir,"ssl","certs");
307+
308+
this.logger.debug(logPrefix + "Using environment variable RUBY_DIR=" + result.options.env.RUBY_DIR);
309+
this.logger.debug(logPrefix + "Using environment variable PATH=" + result.options.env.PATH);
310+
this.logger.debug(logPrefix + "Using environment variable RUBYLIB=" + result.options.env.RUBYLIB);
311+
this.logger.debug(logPrefix + "Using environment variable RUBYOPT=" + result.options.env.RUBYOPT);
312+
this.logger.debug(logPrefix + "Using environment variable SSL_CERT_FILE=" + result.options.env.SSL_CERT_FILE);
313+
this.logger.debug(logPrefix + "Using environment variable SSL_CERT_DIR=" + result.options.env.SSL_CERT_DIR);
314+
315+
return result;
316+
}
317+
318+
// Commented out for the moment. This will be enabled once the configuration and
319+
// exact user story is figured out.
320+
//
321+
// private getLanguageServerFromPDK(serverExe) {
322+
// let logPrefix: string = '[getLanguageServerFromPDK] ';
323+
// // setup defaults
324+
// let spawn_options: cp.SpawnOptions = {}
325+
// spawn_options.env = process.env;
326+
// let result = {
327+
// command: 'ruby',
328+
// args: [serverExe],
329+
// options: spawn_options,
330+
// }
331+
// let pdkDir: string = null;
332+
333+
// // type Platform = 'aix'
334+
// // | 'android'
335+
// // | 'darwin'
336+
// // | 'freebsd'
337+
// // | 'linux'
338+
// // | 'openbsd'
339+
// // | 'sunos'
340+
// // | 'win32';
341+
// switch (process.platform) {
342+
// case 'win32':
343+
// let comspec: string = process.env["COMSPEC"];
344+
// let programFiles = process.env["ProgramFiles"];
345+
// if (process.env["PROCESSOR_ARCHITEW6432"] == "AMD64") {
346+
// // VSCode is running as 32bit process on a 64bit Operating System. Need to break out
347+
// // of the 32bit using the sysnative redirection and environment variables
348+
// comspec = path.join(process.env["WINDIR"],"sysnative","cmd.exe");
349+
// programFiles = process.env["ProgramW6432"];
350+
// }
351+
352+
// pdkDir = path.join(programFiles, "Puppet Labs", "DevelopmentKit");
353+
354+
// result.options.stdio = 'pipe';
355+
// break;
356+
// default:
357+
// pdkDir = '/opt/puppetlabs/pdk';
358+
359+
// result.options.stdio = 'pipe';
360+
// result.options.shell = true;
361+
// break;
362+
// }
363+
// // Check if this really is a PDK installation
364+
// if (!fs.existsSync(path.join(pdkDir, "PDK_VERSION"))) {
365+
// this.logger.debug(logPrefix + "Could not find a valid PDK installation at " + pdkDir);
366+
// return null;
367+
// } else {
368+
// this.logger.debug(logPrefix + "Found a valid PDK installation at " + pdkDir);
369+
// }
370+
371+
// // Now to detect ruby versions
372+
// let subdirs = this.getDirectories(path.join(pdkDir,"private", "ruby"));
373+
// if (subdirs.length == 0) { return null; }
374+
// let rubyDir = path.join(pdkDir,"private", "ruby",subdirs[0]);
375+
376+
// subdirs = this.getDirectories(path.join(pdkDir,"share","cache","ruby"));
377+
// if (subdirs.length == 0) { return null; }
378+
// let gemDir = path.join(pdkDir,"share","cache","ruby",subdirs[0]);
379+
380+
// let rubylib = path.join(pdkDir,'lib')
381+
// if (process.platform == 'win32') {
382+
// // Translate all slashes to / style to avoid puppet/ruby issue #11930
383+
// rubylib = rubylib.replace(/\\/g,"/");
384+
// gemDir = gemDir.replace(/\\/g,"/");
385+
// }
386+
387+
// // Setup the process environment variables
388+
// if (result.options.env.PATH == undefined) { result.options.env.PATH = '' }
389+
// if (result.options.env.RUBYLIB == undefined) { result.options.env.RUBYLIB = '' }
390+
391+
// result.options.env.RUBY_DIR = rubyDir;
392+
// result.options.env.PATH = path.join(pdkDir,'bin') + this.pathEnvSeparator() + path.join(rubyDir,'bin') + this.pathEnvSeparator() + result.options.env.PATH;
393+
// result.options.env.RUBYLIB = path.join(pdkDir,'lib') + this.pathEnvSeparator() + result.options.env.RUBYLIB;
394+
// result.options.env.GEM_PATH = gemDir;
395+
// result.options.env.GEM_HOME = gemDir;
396+
// result.options.env.RUBYOPT = 'rubygems';
397+
398+
// this.logger.debug(logPrefix + "Using environment variable RUBY_DIR=" + result.options.env.RUBY_DIR);
399+
// this.logger.debug(logPrefix + "Using environment variable PATH=" + result.options.env.PATH);
400+
// this.logger.debug(logPrefix + "Using environment variable RUBYLIB=" + result.options.env.RUBYLIB);
401+
// this.logger.debug(logPrefix + "Using environment variable GEM_PATH=" + result.options.env.GEM_PATH);
402+
// this.logger.debug(logPrefix + "Using environment variable GEM_HOME=" + result.options.env.GEM_HOME);
403+
// this.logger.debug(logPrefix + "Using environment variable RUBYOPT=" + result.options.env.RUBYOPT);
404+
405+
// return result;
406+
// }
407+
408+
private createLanguageServerProcess(serverExe: string, callback : Function) {
409+
let logPrefix: string = '[createLanguageServerProcess] ';
410+
this.logger.debug(logPrefix + 'Language server found at: ' + serverExe)
411+
412+
let localServer = null
413+
414+
if (localServer == null) { localServer = this.getLanguageServerFromPuppetAgent(serverExe); }
415+
// if (localServer == null) { localServer = this.getLanguageServerFromPDK(serverExe); }
416+
417+
if (localServer == null) {
418+
this.logger.warning(logPrefix + "Could not find a valid Puppet Agent installation");
419+
this.setSessionFailure("Could not find a valid Puppet Agent installation");
420+
vscode.window.showWarningMessage('Could not find a valid Puppet Agent installation. Functionality will be limited to syntax highlighting');
281421
return;
282422
}
283423

284424
let connMgr : ConnectionManager = this;
285425
let logger = this.logger;
286426
// Start a server to get a random port
287-
this.logger.debug('Creating server process to identify random port')
427+
this.logger.debug(logPrefix + 'Creating server process to identify random port')
288428
const server = net.createServer()
289429
.on('close', () => {
290-
logger.debug('Server process to identify random port disconnected');
291-
connMgr.startLanguageServerProcess(cmd, args, options, callback);
430+
logger.debug(logPrefix + 'Server process to identify random port disconnected');
431+
connMgr.startLanguageServerProcess(localServer.command, localServer.args, localServer.options, callback);
292432
})
293433
.on('error', (err) => {
294434
throw err;
295435
});
296436

297437
// Listen on random port
298438
server.listen(0);
299-
this.logger.debug('Selected port for local language server: ' + server.address().port);
439+
this.logger.debug(logPrefix + 'Selected port for local language server: ' + server.address().port);
300440
connMgr.connectionConfiguration.port = server.address().port;
301441
server.close();
302442
}

0 commit comments

Comments
 (0)