diff --git a/src/baseShell.ts b/src/baseShell.ts index 0b4bead..16d9095 100644 --- a/src/baseShell.ts +++ b/src/baseShell.ts @@ -1,12 +1,11 @@ "use strict"; import * as vscode from "vscode"; +import { terraformChannel } from "./terraformChannel"; export abstract class BaseShell { - protected outputChannel: vscode.OutputChannel; - constructor(outputChannel: vscode.OutputChannel) { - this.outputChannel = outputChannel; + constructor() { this.initShellInternal(); } @@ -14,7 +13,7 @@ export abstract class BaseShell { // We keep the TFConfiguration for the moment - will need to be updated to sync folders const tfActiveFile = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.document.fileName : null; - this.outputChannel.appendLine(`Running - ${tfCommand}, Active File: ${tfActiveFile}`); + terraformChannel.appendLine(`Running - ${tfCommand}, Active File: ${tfActiveFile}`); // Run Terraform command this.runTerraformInternal(tfCommand, workingDir); @@ -23,8 +22,8 @@ export abstract class BaseShell { public async runTerraformCmdAsync(tfCommand: string): Promise { const tfActiveFile = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.document.fileName : null; - this.outputChannel.appendLine(`Running - ${tfCommand}`); - this.outputChannel.show(); + terraformChannel.appendLine(`Running - ${tfCommand}`); + terraformChannel.show(); return this.runTerraformAsyncInternal(tfActiveFile, tfCommand); } @@ -37,11 +36,11 @@ export abstract class BaseShell { } protected output(label: string, message: string): void { - this.outputChannel.append(`[${label}] ${message}`); + terraformChannel.appendLine(`[${label}] ${message}`); } protected outputLine(label: string, message: string): void { - this.outputChannel.appendLine(`[${label}] ${message}`); + terraformChannel.appendLine(`[${label}] ${message}`); } protected isWindows(): boolean { diff --git a/src/cloudConsole.ts b/src/cloudConsole.ts index a4e033c..00dce8e 100644 --- a/src/cloudConsole.ts +++ b/src/cloudConsole.ts @@ -2,13 +2,14 @@ import * as path from "path"; import { clearInterval, setInterval, setTimeout } from "timers"; -import { commands, OutputChannel, window } from "vscode"; +import { commands, window } from "vscode"; import * as nls from "vscode-nls"; import { AzureAccount, AzureSession, AzureSubscription } from "./azure-account.api"; import { delay, Errors, getStorageAccountKey, getUserSettings, provisionConsole, resetConsole, runInTerminal, } from "./cloudConsoleLauncher"; +import { terraformChannel } from "./terraformChannel"; import { isNodeVersionValid } from "./utils/nodeUtils"; import { DialogOption, openUrlHint } from "./utils/uiUtils"; @@ -37,14 +38,13 @@ export function openCloudConsole( api: AzureAccount, subscription: AzureSubscription, os: IOS, - outputChannel: OutputChannel, tempFile: string) { return (async function retry(): Promise { - outputChannel.show(); - outputChannel.appendLine("Attempting to open CloudConsole - Connecting to cloudshell"); + terraformChannel.show(); + terraformChannel.appendLine("Attempting to open CloudConsole - Connecting to cloudshell"); /* tslint:disable:semicolon */ - const progress = delayedInterval(() => { outputChannel.append("..") }, 500); + const progress = delayedInterval(() => { terraformChannel.append("..") }, 500); /* tslint:enable:semicolon */ const isWindows = process.platform === "win32"; @@ -69,7 +69,7 @@ export function openCloudConsole( return; } - outputChannel.append("\nUsersettings obtained from Tokens - proceeding\n"); + terraformChannel.appendLine("Usersettings obtained from Tokens - proceeding"); // Finding the storage account const storageProfile = result.userSettings.storageProfile; @@ -92,7 +92,7 @@ export function openCloudConsole( storageAccount.subscriptionId, result.token.accessToken, storageAccount.storageAccountName).then((keys) => { - outputChannel.appendLine("Storage key obtained "); + terraformChannel.appendLine("Storage key obtained "); storageAccountKey = keys.body.keys[0].value; }); @@ -157,8 +157,8 @@ export function openCloudConsole( return [terminal, response, storageAccount.storageAccountName, storageAccountKey, fileShareName, storageAccount.resourceGroup]; })().catch((err) => { - outputChannel.append("\nConnecting to CloudShell failed with error: \n" + err); - outputChannel.show(); + terraformChannel.appendLine("Connecting to CloudShell failed with error: " + err); + terraformChannel.show(); throw err; }); } diff --git a/src/cloudShell.ts b/src/cloudShell.ts index 9dcfdb5..038c35c 100644 --- a/src/cloudShell.ts +++ b/src/cloudShell.ts @@ -12,6 +12,7 @@ import { openCloudConsole, OSes } from "./cloudConsole"; import { delay } from "./cloudConsoleLauncher"; import { aciConfig, Constants, exportContainerCmd, exportTestScript } from "./constants"; import { azFileDelete, azFilePush, escapeFile, TerminalType, TestOption, TFTerminal } from "./shared"; +import { terraformChannel } from "./terraformChannel"; import { DialogOption } from "./utils/uiUtils"; const tempFile = path.join(os.tmpdir(), "cloudshell" + vscode.env.sessionId + ".log"); @@ -23,7 +24,7 @@ export class CloudShell extends BaseShell { Constants.TerraformTerminalName); public async pushFiles(files: vscode.Uri[], syncAllFiles: boolean): Promise { - this.outputChannel.appendLine("Attempting to upload files to CloudShell"); + terraformChannel.appendLine("Attempting to upload files to CloudShell"); const RETRY_INTERVAL = 500; const RETRY_TIMES = 30; @@ -40,13 +41,13 @@ export class CloudShell extends BaseShell { for (const file of files.map((a) => a.fsPath)) { try { if (await fsExtra.pathExists(file)) { - this.outputChannel.appendLine(`Uploading file ${file} to cloud shell`); + terraformChannel.appendLine(`Uploading file ${file} to cloud shell`); await azFilePush(this.csTerm.storageAccountName, this.csTerm.storageAccountKey, this.csTerm.fileShareName, file); } } catch (err) { - this.outputChannel.appendLine(err); + terraformChannel.appendLine(err); } } @@ -75,12 +76,12 @@ export class CloudShell extends BaseShell { } else { for (const file of files.map((a) => a.fsPath)) { try { - this.outputChannel.appendLine(`Deleting file ${file} from cloud shell`); + terraformChannel.appendLine(`Deleting file ${file} from cloud shell`); await azFileDelete(this.csTerm.storageAccountName, this.csTerm.storageAccountKey, this.csTerm.fileShareName, file); } catch (err) { - this.outputChannel.appendLine(err); + terraformChannel.appendLine(err); } } } @@ -109,7 +110,7 @@ export class CloudShell extends BaseShell { protected initShellInternal() { vscode.window.onDidCloseTerminal(async (terminal) => { if (terminal === this.csTerm.terminal) { - this.outputChannel.appendLine("CloudShell terminal was closed"); + terraformChannel.appendLine("CloudShell terminal was closed"); await fsExtra.remove(tempFile); this.csTerm.terminal = null; } @@ -117,7 +118,7 @@ export class CloudShell extends BaseShell { } protected async syncWorkspaceInternal(file): Promise { - this.outputChannel.appendLine(`Deleting ${path.basename(file)} in CloudShell`); + terraformChannel.appendLine(`Deleting ${path.basename(file)} in CloudShell`); const retryInterval = 500; const retryTimes = 30; @@ -131,14 +132,14 @@ export class CloudShell extends BaseShell { this.csTerm.ws.send("rm " + path.relative(vscode.workspace.rootPath, file) + " \n"); // TODO: Add directory management } catch (err) { - this.outputChannel.appendLine(err); + terraformChannel.appendLine(err); } break; } } } else { vscode.window.showErrorMessage("Open a terminal first"); - this.outputChannel.appendLine("Terminal not opened when trying to transfer files"); + terraformChannel.appendLine("Terminal not opened when trying to transfer files"); } } @@ -200,7 +201,7 @@ export class CloudShell extends BaseShell { .getExtension("ms-vscode.azure-account")!.exports; const operationSystem = process.platform === "win32" ? OSes.Windows : OSes.Linux; - return await openCloudConsole(accountAPI, azureSubscription, operationSystem, this.outputChannel, tempFile); + return await openCloudConsole(accountAPI, azureSubscription, operationSystem, tempFile); } protected async runTFCommand(command: string, workdir: string, terminal: Terminal): Promise { @@ -235,7 +236,7 @@ export class CloudShell extends BaseShell { this.csTerm.storageAccountKey = terminal[3]; this.csTerm.fileShareName = terminal[4]; this.csTerm.ResourceGroup = terminal[5]; - this.outputChannel.appendLine("Obtained cloudshell terminal, retrying push files."); + terraformChannel.appendLine("Obtained cloudshell terminal, retrying push files."); resolve(true); } else { console.log("Open CloudShell cancelled by user."); diff --git a/src/extension.ts b/src/extension.ts index aaf2151..7c8b053 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -25,12 +25,8 @@ function getShell(): BaseShell { } export function activate(ctx: vscode.ExtensionContext) { - - const outputChannel: vscode.OutputChannel = vscode.window.createOutputChannel("VSCode extension for Azure Terraform"); - outputChannel.appendLine("Loading extension"); - - cs = new CloudShell(outputChannel); - is = new IntegratedShell(outputChannel); + cs = new CloudShell(); + is = new IntegratedShell(); initFileWatcher(ctx); ctx.subscriptions.push(vscode.workspace.onDidChangeConfiguration((e) => { @@ -67,7 +63,7 @@ export function activate(ctx: vscode.ExtensionContext) { ctx.subscriptions.push(vscode.commands.registerCommand("vscode-terraform-azure.visualize", async () => { if (await isDotInstalled()) { - await is.visualize(outputChannel); + await is.visualize(); } })); diff --git a/src/integratedShell.ts b/src/integratedShell.ts index 97f0092..74ec359 100644 --- a/src/integratedShell.ts +++ b/src/integratedShell.ts @@ -8,6 +8,7 @@ import { commands, Uri, ViewColumn, workspace } from "vscode"; import { BaseShell } from "./baseShell"; import { Constants } from "./constants"; import { TerminalType, TestOption, TFTerminal } from "./shared"; +import { terraformChannel } from "./terraformChannel"; import { isServicePrincipalSetInEnv } from "./utils/azureUtils"; import { executeCommand } from "./utils/cpUtils"; import { isDockerInstalled, runE2EInDocker, runLintInDocker } from "./utils/dockerUtils"; @@ -24,7 +25,7 @@ export class IntegratedShell extends BaseShell { private graphUri: Uri; // Creates a png of terraform resource graph to visualize the resources under management. - public async visualize(outputChannel: vscode.OutputChannel): Promise { + public async visualize(): Promise { await this.deletePng(); await executeCommand( @@ -34,7 +35,6 @@ export class IntegratedShell extends BaseShell { shell: true, cwd: vscode.workspace.workspaceFolders[0].uri.fsPath, }, - outputChannel, ); const output: string = await executeCommand( "terraform", @@ -43,11 +43,10 @@ export class IntegratedShell extends BaseShell { shell: true, cwd: vscode.workspace.workspaceFolders[0].uri.fsPath, }, - outputChannel, ); const tmpFile: string = path.join(os.tmpdir(), "terraformgraph.output"); await fse.writeFile(tmpFile, output); - await drawGraph(outputChannel, vscode.workspace.workspaceFolders[0].uri.fsPath, tmpFile); + await drawGraph(vscode.workspace.workspaceFolders[0].uri.fsPath, tmpFile); await commands.executeCommand("vscode.open", this.graphUri, ViewColumn.Two); } @@ -93,7 +92,7 @@ export class IntegratedShell extends BaseShell { } const containerName: string = vscode.workspace.getConfiguration("tf-azure").get("test-container"); - this.outputChannel.appendLine("Checking Azure Service Principal environment variables..."); + terraformChannel.appendLine("Checking Azure Service Principal environment variables..."); if (!isServicePrincipalSetInEnv()) { return; } @@ -101,7 +100,6 @@ export class IntegratedShell extends BaseShell { switch (TestType) { case TestOption.lint: { await runLintInDocker( - this.outputChannel, vscode.workspace.workspaceFolders[0].uri.fsPath + ":/tf-test/module", containerName, ); @@ -110,7 +108,6 @@ export class IntegratedShell extends BaseShell { case TestOption.e2enossh: { console.log("Running e2e test in " + process.env["ARM_TEST_LOCATION"]); await runE2EInDocker( - this.outputChannel, [ vscode.workspace.workspaceFolders[0].uri.fsPath + "/logs:/tf-test/module.kitchen", vscode.workspace.workspaceFolders[0].uri.fsPath + ":/tf-test/module", @@ -122,7 +119,6 @@ export class IntegratedShell extends BaseShell { case TestOption.e2ewithssh: { console.log("Running e2e test in " + process.env["ARM_TEST_LOCATION"]); await runE2EInDocker( - this.outputChannel, [ `${path.join(os.homedir(), ".ssh")}:/root/.ssh/`, vscode.workspace.workspaceFolders[0].uri.fsPath + "/logs:/tf-test/module.kitchen", @@ -143,7 +139,6 @@ export class IntegratedShell extends BaseShell { "docker", cmd.split(" "), { shell: true }, - this.outputChannel, ); } break; diff --git a/src/terraformChannel.ts b/src/terraformChannel.ts new file mode 100644 index 0000000..f217f75 --- /dev/null +++ b/src/terraformChannel.ts @@ -0,0 +1,32 @@ +"use strict"; + +import * as vscode from "vscode"; + +export interface ITerraformChannel { + appendLine(message: any, title?: string): void; + append(message: any): void; + show(): void; +} + +class TerraformChannel implements ITerraformChannel { + private readonly channel: vscode.OutputChannel = vscode.window.createOutputChannel("Azure Terraform"); + + public appendLine(message: any, title?: string): void { + if (title) { + const simplifiedTime = (new Date()).toISOString().replace(/z|t/gi, " ").trim(); // YYYY-MM-DD HH:mm:ss.sss + const hightlightingTitle = `[${title} ${simplifiedTime}]`; + this.channel.appendLine(hightlightingTitle); + } + this.channel.appendLine(message); + } + + public append(message: any): void { + this.channel.append(message); + } + + public show(): void { + this.channel.show(); + } +} + +export const terraformChannel: ITerraformChannel = new TerraformChannel(); diff --git a/src/utils/cpUtils.ts b/src/utils/cpUtils.ts index 0d52f84..0c18165 100644 --- a/src/utils/cpUtils.ts +++ b/src/utils/cpUtils.ts @@ -1,9 +1,9 @@ "use strict"; import * as cp from "child_process"; -import * as vscode from "vscode"; +import { terraformChannel } from "../terraformChannel"; -export async function executeCommand(command: string, args: string[], options: cp.SpawnOptions, outputChannel?: vscode.OutputChannel): Promise { +export async function executeCommand(command: string, args: string[], options: cp.SpawnOptions): Promise { return new Promise((resolve: (res: string) => void, reject: (e: Error) => void): void => { let result: string = ""; const childProc: cp.ChildProcess = cp.spawn(command, args, options); @@ -11,16 +11,10 @@ export async function executeCommand(command: string, args: string[], options: c childProc.stdout.on("data", (data: string | Buffer) => { data = data.toString(); result = result.concat(data); - if (outputChannel) { - outputChannel.append(data); - } + terraformChannel.append(data); }); - childProc.stderr.on("data", (data: string | Buffer) => { - if (outputChannel) { - outputChannel.append(data.toString()); - } - }); + childProc.stderr.on("data", (data: string | Buffer) => terraformChannel.append(data.toString())); childProc.on("error", reject); childProc.on("close", (code: number) => { diff --git a/src/utils/dockerUtils.ts b/src/utils/dockerUtils.ts index 4dbde63..458882e 100644 --- a/src/utils/dockerUtils.ts +++ b/src/utils/dockerUtils.ts @@ -1,6 +1,5 @@ "use strict"; -import * as vscode from "vscode"; import { executeCommand } from "./cpUtils"; import { openUrlHint } from "./uiUtils"; @@ -14,7 +13,7 @@ export async function isDockerInstalled(): Promise { } } -export async function runLintInDocker(outputChannel: vscode.OutputChannel, volumn: string, containerName: string): Promise { +export async function runLintInDocker(volumn: string, containerName: string): Promise { try { await executeCommand( "docker", @@ -30,14 +29,13 @@ export async function runLintInDocker(outputChannel: vscode.OutputChannel, volum "build", ], { shell: true }, - outputChannel, ); } catch (error) { throw new Error("Run lint task in Docker failed, Please switch to output channel for more details."); } } -export async function runE2EInDocker(outputChannel: vscode.OutputChannel, volumn: string[], containerName: string): Promise { +export async function runE2EInDocker(volumn: string[], containerName: string): Promise { try { await executeCommand( "docker", @@ -64,7 +62,6 @@ export async function runE2EInDocker(outputChannel: vscode.OutputChannel, volumn "e2e", ], { shell: true }, - outputChannel, ); } catch (error) { throw new Error("Run E2E test in Docker failed, Please switch to output channel for more details."); diff --git a/src/utils/dotUtils.ts b/src/utils/dotUtils.ts index 61bf964..fc9b791 100644 --- a/src/utils/dotUtils.ts +++ b/src/utils/dotUtils.ts @@ -1,6 +1,5 @@ "use strict"; -import * as vscode from "vscode"; import { executeCommand } from "./cpUtils"; import { openUrlHint } from "./uiUtils"; @@ -14,7 +13,7 @@ export async function isDotInstalled(): Promise { } } -export async function drawGraph(outputChannel: vscode.OutputChannel, workingDirectory: string, inputFile: string): Promise { +export async function drawGraph(workingDirectory: string, inputFile: string): Promise { await executeCommand( "dot", ["-Tpng", "-o", "graph.png", inputFile], @@ -22,6 +21,5 @@ export async function drawGraph(outputChannel: vscode.OutputChannel, workingDire cwd: workingDirectory, shell: true, }, - outputChannel, ); }