Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check for language server updates every 24 hours #595

Merged
merged 4 commits into from
Mar 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 31 additions & 18 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ const extensionId = 'hashicorp.terraform';
const appInsightsKey = '885372d2-6f3c-499f-9d25-b8b219983a52';
let reporter: TelemetryReporter;

let extensionPath: string;
let installPath: string;

export async function activate(context: vscode.ExtensionContext): Promise<any> {
const extensionVersion = vscode.extensions.getExtension(extensionId).packageJSON.version;
reporter = new TelemetryReporter(extensionId, extensionVersion, appInsightsKey);
context.subscriptions.push(reporter);
installPath = path.join(context.extensionPath, 'lsp');

extensionPath = context.extensionPath;
// get rid of pre-2.0.0 settings
if (config('terraform').has('languageServer.enabled')) {
try {
Expand All @@ -63,7 +63,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<any> {
const current = config('terraform').get('languageServer');
await config('terraform').update('languageServer', Object.assign(current, { external: true }), vscode.ConfigurationTarget.Global);
}
return startClients();
return updateLanguageServer();
}),
vscode.commands.registerCommand('terraform.disableLanguageServer', async () => {
if (enabled()) {
Expand Down Expand Up @@ -164,6 +164,29 @@ export function deactivate(): Promise<void[]> {
return stopClients();
}

async function updateLanguageServer() {
const delay = 1000 * 60 * 24;
setTimeout(updateLanguageServer, delay); // check for new updates every 24hrs

// skip install if a language server binary path is set
if (!config('terraform').get('languageServer.pathToBinary')) {
const installer = new LanguageServerInstaller(installPath, reporter);
const install = await installer.needsInstall();
if (install) {
await stopClients();
try {
await installer.install();
} catch (err) {
reporter.sendTelemetryException(err);
throw err;
} finally {
await installer.cleanupZips();
}
}
}
return startClients(); // on repeat runs with no install, this will be a no-op
}

async function startClients(folders = prunedFolderNames()) {
console.log('Starting:', folders);
const command = await pathToBinary();
Expand Down Expand Up @@ -260,20 +283,10 @@ let _pathToBinaryPromise: Promise<string>;
async function pathToBinary(): Promise<string> {
if (!_pathToBinaryPromise) {
let command: string = config('terraform').get('languageServer.pathToBinary');
if (!command) { // Skip install/upgrade if user has set custom binary path
const installDir = path.join(extensionPath, 'lsp');
const installer = new LanguageServerInstaller(reporter);
try {
await installer.install(installDir);
} catch (err) {
reporter.sendTelemetryException(err);
throw err;
} finally {
await installer.cleanupZips(installDir);
}
command = path.join(installDir, 'terraform-ls');
} else {
if (command) { // Skip install/upgrade if user has set custom binary path
reporter.sendTelemetryEvent('usePathToBinary');
} else {
command = path.join(installPath, 'terraform-ls');
}
_pathToBinaryPromise = Promise.resolve(command);
}
Expand Down Expand Up @@ -355,8 +368,8 @@ async function terraformCommand(command: string, languageServerExec = true): Pro
{ value: `terraform ${command}`, prompt: `Run in ${selectedModule}` }
);
if (terraformCommand) {
const terminal = vscode.window.terminals.find(t => t.name == terminalName ) ||
vscode.window.createTerminal({ name: `Terraform ${selectedModule}`, cwd: moduleURI });
const terminal = vscode.window.terminals.find(t => t.name == terminalName) ||
vscode.window.createTerminal({ name: `Terraform ${selectedModule}`, cwd: moduleURI });
terminal.sendText(terraformCommand);
terminal.show();
}
Expand Down
67 changes: 38 additions & 29 deletions src/languageServerInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,55 +8,64 @@ import TelemetryReporter from 'vscode-extension-telemetry';
import { exec } from './utils';
import { Release, getRelease } from '@hashicorp/js-releases';

const extensionVersion = vscode.extensions.getExtension('hashicorp.terraform').packageJSON.version;

export class LanguageServerInstaller {
constructor(
private directory: string,
private reporter: TelemetryReporter
) { }

public async install(directory: string): Promise<void> {
const extensionVersion = vscode.extensions.getExtension('hashicorp.terraform').packageJSON.version;
const userAgent = `Terraform-VSCode/${extensionVersion} VSCode/${vscode.version}`;
let isInstalled = false;
private userAgent = `Terraform-VSCode/${extensionVersion} VSCode/${vscode.version}`;
private release: Release;

public async needsInstall(): Promise<boolean> {
try {
this.release = await getRelease("terraform-ls", "latest", this.userAgent);
} catch (err) {
// if the releases site is inaccessible, report it and skip the install
this.reporter.sendTelemetryException(err);
return false;
}

let installedVersion: string;
try {
installedVersion = await getLsVersion(directory);
isInstalled = true;
installedVersion = await getLsVersion(this.directory);
} catch (err) {
// Most of the time, getLsVersion would produce "ENOENT: no such file or directory"
// Most of the time, getLsVersion will produce "ENOENT: no such file or directory"
// on a fresh installation (unlike upgrade). It’s also possible that the file or directory
// is inaccessible for some other reason, but we catch that separately.
if (err.code !== 'ENOENT') {
this.reporter.sendTelemetryException(err);
throw err;
}
isInstalled = false;
return true; // yes to new install
}

const currentRelease = await getRelease("terraform-ls", "latest", userAgent);
if (isInstalled) {
this.reporter.sendTelemetryEvent('foundLsInstalled', { terraformLsVersion: installedVersion });
if (semver.gt(currentRelease.version, installedVersion, { includePrerelease: true })) {
const selected = await vscode.window.showInformationMessage(`A new language server release is available: ${currentRelease.version}. Install now?`, 'Install', 'Cancel');
if (selected !== 'Install') { // selected is undefined if the user closes the message
return;
}
} else {
return;
}
this.reporter.sendTelemetryEvent('foundLsInstalled', { terraformLsVersion: installedVersion });
const upgrade = semver.gt(this.release.version, installedVersion, { includePrerelease: true });
if (upgrade) {
const selected = await vscode.window.showInformationMessage(`A new language server release is available: ${this.release.version}. Install now?`, 'Install', 'Cancel');
return (selected === "Install");
} else {
return false; // no upgrade available
}
}

public async install(): Promise<void> {
this.reporter.sendTelemetryEvent('installingLs', { terraformLsVersion: this.release.version });
try {
this.reporter.sendTelemetryEvent('installingLs', { terraformLsVersion: currentRelease.version });
await this.installPkg(currentRelease, directory, userAgent);
await this.installPkg(this.release);
} catch (err) {
vscode.window.showErrorMessage(`Unable to install terraform-ls: ${err.message}`);
throw err;
}

this.showChangelog(currentRelease.version);
this.showChangelog(this.release.version);
}

async installPkg(release: Release, installDir: string, userAgent: string): Promise<void> {
async installPkg(release: Release): Promise<void> {
const installDir = this.directory;
const destination = `${installDir}/terraform-ls_v${release.version}.zip`;
fs.mkdirSync(installDir, { recursive: true }); // create install directory if missing

Expand All @@ -67,7 +76,7 @@ export class LanguageServerInstaller {
throw new Error(`Install error: no matching terraform-ls binary for ${os}/${arch}`);
}
try {
this.removeOldBinary(installDir);
this.removeOldBinary();
} catch {
// ignore missing binary (new install)
}
Expand All @@ -78,20 +87,20 @@ export class LanguageServerInstaller {
title: "Installing terraform-ls"
}, async (progress) => {
progress.report({ increment: 30 });
await release.download(build.url, destination, userAgent);
await release.download(build.url, destination, this.userAgent);
progress.report({ increment: 30 });
await release.verify(destination, build.filename)
progress.report({ increment: 30 });
return release.unpack(installDir, destination)
});
}

removeOldBinary(directory: string): void {
fs.unlinkSync(lsBinPath(directory));
removeOldBinary(): void {
fs.unlinkSync(lsBinPath(this.directory));
}

public async cleanupZips(directory: string): Promise<string[]> {
return del(`${directory}/terraform-ls*.zip`, { force: true });
public async cleanupZips(): Promise<string[]> {
return del(`${this.directory}/terraform-ls*.zip`, { force: true });
}

showChangelog(version: string): void {
Expand Down