diff --git a/package.json b/package.json index b5680e0..beec86d 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "onCommand:vscode-terraform-azure.validate", "onCommand:vscode-terraform-azure.refresh", "onCommand:vscode-terraform-azure.destroy", - "onCommand:vscode-terraform-azure.cloudshell" + "onCommand:vscode-terraform-azure.cloudshell", + "onCommand:vscode-terraform-azure.visualize" ], "main": "./out/extension", "contributes": { @@ -67,7 +68,12 @@ "command": "vscode-terraform-azure.cloudshell", "title": "cloudshell", "category": "Terraform" - } + }, + { + "command": "vscode-terraform-azure.visualize", + "title": "visualize", + "category": "Terraform" + } ] }, "scripts": { diff --git a/src/baseShell.ts b/src/baseShell.ts index b246452..22d7927 100644 --- a/src/baseShell.ts +++ b/src/baseShell.ts @@ -33,8 +33,17 @@ export abstract class BaseShell { } + public async runTerraformCmdAsync(TFCommand: string): Promise { + var TFConfiguration = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.document.fileName : null; + this._outputChannel.append("Starting Cloudshell - Terraform") + this._outputChannel.show(); + + return this.runTerraformAsyncInternal(TFConfiguration, TFCommand); + + } //protected abstract runPlaybookInternal(playbook: string); protected abstract runTerraformInternal(TFConfiguration: string, TFCommand: string); + protected abstract runTerraformAsyncInternal(TFConfiguration: string, TFCommand: string) : Promise; protected abstract initShellInternal(); protected abstract syncWorkspaceInternal(); } diff --git a/src/cloudShell.ts b/src/cloudShell.ts index 3201fbf..94d4359 100644 --- a/src/cloudShell.ts +++ b/src/cloudShell.ts @@ -3,7 +3,7 @@ import { BaseShell } from './baseShell'; import * as vscode from 'vscode'; import { AzureAccount } from './azure-account.api'; -import { Constants } from './constants'; +import { Constants } from './Constants'; import * as path from 'path'; //import * as opn from 'opn'; import * as fsExtra from 'fs-extra'; @@ -32,6 +32,10 @@ export class CloudShell extends BaseShell { } + protected runTerraformAsyncInternal(TFConfiguration: string, TFCommand: string) : Promise{ + return null; + } + protected startCloudShell(TFconfiguration: string, TFCommand: string): void { const msgOption: vscode.MessageOptions = { modal: false }; const msgItem: vscode.MessageItem = { title: 'Confirm' }; diff --git a/src/extension.ts b/src/extension.ts index 4c5b9dc..ffb830d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -2,13 +2,13 @@ // The module 'vscode' contains the VS Code extensibility API // Import the module and reference it with the alias vscode in your code below import * as vscode from 'vscode'; - import { extensions, commands, Disposable, window } from 'vscode'; import { AzureAccount }from './azure-account.api'; import { AzureServiceClient } from 'ms-rest-azure'; import { CloudShell } from './cloudShell'; import { IntegratedShell } from './integratedShell'; import { BaseShell } from './baseShell'; +import { join } from 'path'; export var CSTerminal: boolean; @@ -49,28 +49,30 @@ export function activate(ctx: vscode.ExtensionContext) { })); ctx.subscriptions.push(vscode.commands.registerCommand('vscode-terraform-azure.plan', () =>{ - activeShell.runTerraformCmd("plan"); + activeShell.runTerraformCmd("terraform plan"); })); ctx.subscriptions.push(vscode.commands.registerCommand('vscode-terraform-azure.apply', () => { - activeShell.runTerraformCmd("apply"); + activeShell.runTerraformCmd("terraform apply"); })); ctx.subscriptions.push(vscode.commands.registerCommand('vscode-terraform-azure.destroy', () => { - activeShell.runTerraformCmd("destroy"); + activeShell.runTerraformCmd("terraform destroy"); })); ctx.subscriptions.push(vscode.commands.registerCommand('vscode-terraform-azure.refresh', () => { - activeShell.runTerraformCmd("refresh"); + activeShell.runTerraformCmd("terraform refresh"); })); ctx.subscriptions.push(vscode.commands.registerCommand('vscode-terraform-azure.validate', () => { - activeShell.runTerraformCmd("validate"); + activeShell.runTerraformCmd("terraform validate"); })); if (!CSTerminal) { ctx.subscriptions.push(vscode.commands.registerCommand('vscode-terraform-azure.visualize', () => { - activeShell.runTerraformCmd("visualize") + var iShell : IntegratedShell; + iShell = activeShell; + iShell.visualize(); })); ctx.subscriptions.push(vscode.commands.registerCommand('vscode-terraform-azure.execTest', () =>{ //TODO @@ -79,8 +81,6 @@ export function activate(ctx: vscode.ExtensionContext) { else { } - - // "tf-azure.terminal": "integrated" var dir = vscode.workspace.workspaceFolders[0].uri.fsPath; // = vscode.workspace.onDidChangeTextDocument diff --git a/src/integratedShell.ts b/src/integratedShell.ts index e925978..b281660 100644 --- a/src/integratedShell.ts +++ b/src/integratedShell.ts @@ -2,38 +2,76 @@ import * as vscode from 'vscode'; import { BaseShell } from './baseShell'; -import { extensions, commands, Disposable, window } from 'vscode'; +import { join } from 'path'; +import { extensions, commands, workspace, Disposable, Uri, window, ViewColumn } from 'vscode'; import { TFTerminal, TerminalType} from './shared' import { Constants } from './constants' - +const fs = require('fs-extra') export class IntegratedShell extends BaseShell { - - protected iTerm = new TFTerminal( + static readonly GRAPH_FILE_NAME = './graph.png'; + private graphUri : Uri; + + //terminal wrapper + public term = new TFTerminal( TerminalType.Integrated, Constants.TerraformTerminalName); + //init shell env and hook up close handler. Close handler ensures if user closes terminal window, + // that extension is notified and can handle appropriately. protected initShellInternal() { + //set path for + this.graphUri = Uri.file(join(workspace.rootPath || '', IntegratedShell.GRAPH_FILE_NAME)); + if ('onDidCloseTerminal' in vscode.window) { (vscode.window).onDidCloseTerminal((terminal) => { - if (terminal == this.iTerm.terminal ) { + if (terminal == this.term.terminal ) { this.outputLine('Terraform Terminal closed', terminal.name); - this.iTerm.terminal = null; + this.term.terminal = null; } }); } } - + //run synchronous tf cmd protected runTerraformInternal(TFconfiguration: string, TFCommand: string): void { this.checkCreateTerminal(); - - var term = this.iTerm.terminal; - + var term = this.term.terminal; term.show(); term.sendText(TFCommand); return; } + //run tf cmd async and return promise. + protected runTerraformAsyncInternal(TFConfiguration: string, TFCommand: string) : Promise{ + this.checkCreateTerminal(); + + var term = this.term.terminal; + + term.show(); + + let ret = term.sendText(TFCommand); + return Promise.all([ret]); + } + + public visualize(): void { + this.deletePng(); + this.runTerraformCmdAsync("terraform graph | dot -Tpng > graph.png").then(() => { + this.displayPng(); + }); + } + + private deletePng() : void { + if(fs.existsSync(this.graphUri.path)){ + fs.unlinkSync(this.graphUri.path) + } + } + + private displayPng(): void { + //add 2 second delay before opening file due to file creation latency + setTimeout(() => {commands.executeCommand('vscode.open', this.graphUri,ViewColumn.Two);} + ,2000); + } + protected syncWorkspaceInternal() { //not implemented for integrated terminal @@ -41,8 +79,8 @@ export class IntegratedShell extends BaseShell { private checkCreateTerminal(): void { - if ( this.iTerm.terminal == null ) { - this.iTerm.terminal = vscode.window.createTerminal( + if ( this.term.terminal == null ) { + this.term.terminal = vscode.window.createTerminal( Constants.TerraformTerminalName); } } diff --git a/src/utilities.ts b/src/utilities.ts new file mode 100644 index 0000000..31a6704 --- /dev/null +++ b/src/utilities.ts @@ -0,0 +1,58 @@ +'use strict' + +import * as vscode from 'vscode'; +import * as child_process from 'child_process'; +import * as path from 'path'; +import * as fsExtra from 'fs-extra'; +import * as os from 'os'; +import { Constants } from './constants'; + +export function localExecCmd(cmd: string, args: string[], outputChannel: vscode.OutputChannel, cb: Function): void { + try { + var cp = require('child_process').spawn(cmd, args); + + cp.stdout.on('data', function (data) { + if (outputChannel) { + outputChannel.append(String(data)); + outputChannel.show(); + } + }); + + cp.stderr.on('data', function (data) { + if (outputChannel) outputChannel.append(String(data)); + }); + + cp.on('close', function (code) { + if (cb) { + if (0 == code) { + cb(); + } else { + var e = new Error("External command failed"); + e.stack = "exit code: " + code; + cb(e); + } + } + }); + } catch (e) { + e.stack = "ERROR: " + e; + if (cb) cb(e); + } +} + + +export function isDockerInstalled(outputChannel: vscode.OutputChannel/*, cb: Function*/): Boolean { + var retVal = false; + + if (process.platform === 'win32') { + localExecCmd('cmd.exe', ['/c', 'docker', '-v'], outputChannel, function (err) { + if (err) { + vscode.window.showErrorMessage('Docker isn\'t installed, please install Docker to continue (https://www.docker.com/).'); + } else { + retVal = true; + } + }); + } + + return retVal; +} +