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

Refactor remote debugging commands into common library so we can support functions #522

Merged
merged 3 commits into from
Jul 2, 2019
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
75 changes: 64 additions & 11 deletions appservice/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions appservice/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"mocha": "^5.2.0",
"mocha-junit-reporter": "^1.22.0",
"mocha-multi-reporters": "^1.1.7",
"portfinder": "^1.0.20",
"tslint": "^5.16.0",
"tslint-microsoft-contrib": "5.0.1",
"typescript": "^3.4.5",
Expand Down
47 changes: 19 additions & 28 deletions appservice/src/TunnelProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import * as requestP from 'request-promise';
import { IParsedError, parseError } from 'vscode-azureextensionui';
import * as websocket from 'websocket';
import { ext } from './extensionVariables';
import { localize } from './localize';
import { SiteClient } from './SiteClient';
import { delay } from './utils/delay';

Expand Down Expand Up @@ -120,7 +121,7 @@ class TunnelSocket extends EventEmitter {
/**
* Interface for tunnel GetStatus API
*/
enum WebAppState {
enum AppState {
STARTED = 'STARTED',
STARTING = 'STARTING',
STOPPED = 'STOPPED'
Expand All @@ -129,7 +130,7 @@ enum WebAppState {
interface ITunnelStatus {
port: number;
canReachPort: boolean;
state: WebAppState;
state: AppState;
msg: string;
}

Expand Down Expand Up @@ -171,26 +172,16 @@ export class TunnelProxy {
this._server.unref();
}

// Starts up an app when it is found to be in the STOPPED state
// Apps can be in the STOPPED state for different reasons:
// 1. A stop request was sent through the Azure API (using the portal, using the extension, etc)
// - In this case it will respond with 403 until a start request is sent to the Azure API
// 2. The app is inactive, or was recently started
// - In this case it will stay stopped until a request is made to the app itself, waking it up
//
// To cover both cases, we send a start request followed by a ping to the app url
// Starts up an app by pinging it when it is found to be in the STOPPED state
private async startupApp(): Promise<void> {
ext.outputChannel.appendLine('[WebApp Tunnel] Sending start request...');
await this._client.start();

ext.outputChannel.appendLine('[WebApp Tunnel] Pinging app default url...');
ext.outputChannel.appendLine('[Tunnel] Pinging app default url...');
// tslint:disable-next-line:no-unsafe-any
const pingResponse: IncomingMessage = await requestP.get({
uri: this._client.defaultHostUrl,
simple: false, // allows the call to succeed without exception, even when status code is not 2XX
resolveWithFullResponse: true // allows access to the status code from the response
});
ext.outputChannel.appendLine(`[WebApp Tunnel] Ping responded with status code: ${pingResponse.statusCode}`);
ext.outputChannel.appendLine(`[Tunnel] Ping responded with status code: ${pingResponse.statusCode}`);
}

private async checkTunnelStatus(): Promise<void> {
Expand All @@ -209,32 +200,32 @@ export class TunnelProxy {
try {
// tslint:disable-next-line:no-unsafe-any
const responseBody: string = await requestP.get(statusOptions);
ext.outputChannel.appendLine(`[WebApp Tunnel] Checking status, body: ${responseBody}`);
ext.outputChannel.appendLine(`[Tunnel] Checking status, body: ${responseBody}`);

// tslint:disable-next-line:no-unsafe-any
tunnelStatus = JSON.parse(responseBody);
} catch (error) {
const parsedError: IParsedError = parseError(error);
ext.outputChannel.appendLine(`[WebApp Tunnel] Checking status, error: ${parsedError.message}`);
throw new Error(`Error getting tunnel status: ${parsedError.errorType}`);
ext.outputChannel.appendLine(`[Tunnel] Checking status, error: ${parsedError.message}`);
throw new Error(localize('tunnelStatusError', 'Error getting tunnel status: {0}', parsedError.errorType));
}

if (tunnelStatus.state === WebAppState.STARTED) {
if (tunnelStatus.state === AppState.STARTED) {
if ((tunnelStatus.port === 2222 && !this._isSsh) || (tunnelStatus.port !== 2222 && this._isSsh)) {
// Tunnel is pointed to default SSH port and still needs time to restart
throw new RetryableTunnelStatusError('WebApp is waiting for restart');
throw new RetryableTunnelStatusError();
} else if (tunnelStatus.canReachPort) {
return;
} else {
throw new Error('WebApp is started, but port is unreachable');
throw new Error(localize('tunnelUnreachable', 'App is started, but port is unreachable'));
}
} else if (tunnelStatus.state === WebAppState.STARTING) {
throw new RetryableTunnelStatusError('WebApp is starting');
} else if (tunnelStatus.state === WebAppState.STOPPED) {
} else if (tunnelStatus.state === AppState.STARTING) {
throw new RetryableTunnelStatusError();
} else if (tunnelStatus.state === AppState.STOPPED) {
await this.startupApp();
throw new RetryableTunnelStatusError('WebApp is starting from STOPPED state');
throw new RetryableTunnelStatusError();
} else {
throw new Error(`Unexpected WebApp state: ${tunnelStatus.state}`);
throw new Error(localize('tunnelStatusError', 'Unexpected app state: {0}', tunnelStatus.state));
}
}

Expand All @@ -252,14 +243,14 @@ export class TunnelProxy {
return;
} catch (error) {
if (!(error instanceof RetryableTunnelStatusError)) {
reject(new Error(`Unable to establish connection to application: ${parseError(error).message}`));
reject(new Error(localize('tunnelFailed', 'Unable to establish connection to application: {0}', parseError(error).message)));
return;
} // else allow retry
}

await delay(pollingIntervalMs);
}
reject(new Error('Unable to establish connection to application: Timed out'));
reject(new Error(localize('tunnelTimedOut', 'Unable to establish connection to application: Timed out')));
});
}

Expand Down
2 changes: 2 additions & 0 deletions appservice/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export * from './createAppService/SiteHostingPlanStep';
export * from './createAppService/SiteNameStep';
export * from './createAppService/SiteOSStep';
export * from './createAppService/SiteRuntimeStep';
export * from './remoteDebug/remoteDebugCommon';
export * from './remoteDebug/startRemoteDebug';
export * from './createSlot';
export * from './deploy/deploy';
export * from './deploy/runPreDeployTask';
Expand Down
49 changes: 49 additions & 0 deletions appservice/src/remoteDebug/remoteDebugCommon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { SiteConfigResource } from 'azure-arm-website/lib/models';
import * as vscode from 'vscode';
import { callWithTelemetryAndErrorHandling, DialogResponses, IActionContext } from 'vscode-azureextensionui';
import { ext } from '../extensionVariables';
import { localize } from '../localize';
import { SiteClient } from '../SiteClient';

export function reportMessage(message: string, progress: vscode.Progress<{}>): void {
ext.outputChannel.appendLine(message);
progress.report({ message: message });
}

export async function setRemoteDebug(isRemoteDebuggingToBeEnabled: boolean, confirmMessage: string, noopMessage: string | undefined, siteClient: SiteClient, siteConfig: SiteConfigResource, progress?: vscode.Progress<{}>, learnMoreLink?: string): Promise<void> {
const state: string | undefined = await siteClient.getState();
if (state && state.toLowerCase() === 'stopped') {
throw new Error(localize('remoteDebugStopped', 'The app must be running, but is currently in state "Stopped". Start the app to continue.'));
}

if (isRemoteDebuggingToBeEnabled !== siteConfig.remoteDebuggingEnabled) {
const confirmButton: vscode.MessageItem = isRemoteDebuggingToBeEnabled ? { title: 'Enable' } : { title: 'Disable' };

// don't have to check input as this handles cancels and learnMore responses
await ext.ui.showWarningMessage(confirmMessage, { modal: true, learnMoreLink }, confirmButton, DialogResponses.cancel);
siteConfig.remoteDebuggingEnabled = isRemoteDebuggingToBeEnabled;
if (progress) {
reportMessage(localize('remoteDebugUpdate', 'Updating site configuration to set remote debugging...'), progress);
}

await callWithTelemetryAndErrorHandling('appService.remoteDebugUpdateConfiguration', async (context: IActionContext) => {
context.errorHandling.suppressDisplay = true;
context.errorHandling.rethrow = true;
await siteClient.updateConfiguration(siteConfig);
});

if (progress) {
reportMessage(localize('remoteDebugUpdateDone', 'Updating site configuration done.'), progress);
}
} else {
// Update not needed
if (noopMessage) {
vscode.window.showWarningMessage(noopMessage);
}
}
}
Loading