Skip to content

Commit

Permalink
appservice: Refactor deployment code into steps (#1554)
Browse files Browse the repository at this point in the history
* Refactor deployment code into steps

* Small fixes

* Forgot DelayFirstWebAppDeployStep. Make aspPromise on the context

* Save the file

* PR feedback

* Fixin' da lint man

* Save file

* Weirdness with files not saving

* Merge from main

* Delete DeployGithubExecuteStep.ts

* Delete appservice/src/deploy/wizard/DeployExecuteBaseStep.ts

* Deploy with flexconsumption

* Rename DeployExecuteStepBase from DeployExecuteBaseStep

* Bump appservice version for breaking change
  • Loading branch information
nturinski committed Jan 23, 2024
1 parent 6d10cac commit 5ddc769
Show file tree
Hide file tree
Showing 25 changed files with 563 additions and 261 deletions.
4 changes: 2 additions & 2 deletions appservice/package-lock.json

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

2 changes: 1 addition & 1 deletion appservice/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@microsoft/vscode-azext-azureappservice",
"author": "Microsoft Corporation",
"version": "2.2.7",
"version": "2.3.0",
"description": "Common tools for developing Azure App Service extensions for VS Code",
"tags": [
"azure",
Expand Down
16 changes: 15 additions & 1 deletion appservice/src/deploy/IDeployContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { IActionContext } from '@microsoft/vscode-azext-utils';
import { AppServicePlan } from '@azure/arm-appservice';
import { ExecuteActivityContext, IActionContext } from '@microsoft/vscode-azext-utils';
import { WorkspaceFolder } from 'vscode';
import { ParsedSite, SiteClient } from '../SiteClient';

export enum AppSource {
setting = 'setting',
Expand All @@ -29,4 +31,16 @@ export interface IDeployContext extends IActionContext {
flexConsumptionRemoteBuild?: boolean;
stopAppBeforeDeploy?: boolean;
syncTriggersPostDeploy?: boolean;
/**
* id retrieved from scm-deployment-id header to track deployment
*/
locationUrl?: string;
}

// only used by the tools package facilitate creating the wizard execute steps
export interface InnerDeployContext extends IDeployContext, ExecuteActivityContext {
site: ParsedSite;
client: SiteClient;
fsPath: string;
aspPromise: Promise<AppServicePlan | undefined>
}
38 changes: 0 additions & 38 deletions appservice/src/deploy/delayFirstWebAppDeploy.ts

This file was deleted.

121 changes: 12 additions & 109 deletions appservice/src/deploy/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,121 +3,24 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import type { AppServicePlan, SiteConfigResource } from '@azure/arm-appservice';
import * as fse from 'fs-extra';
import * as path from 'path';
import { l10n, ProgressLocation, window } from 'vscode';
import { ext } from '../extensionVariables';
import { ScmType } from '../ScmType';
import { AppServicePlan } from '@azure/arm-appservice';
import { AzureWizard, ExecuteActivityContext } from '@microsoft/vscode-azext-utils';
import { l10n } from 'vscode';
import { ParsedSite } from '../SiteClient';
import { randomUtils } from '../utils/randomUtils';
import { deployToStorageAccount } from './deployToStorageAccount';
import { deployWar } from './deployWar';
import { deployZip } from './deployZip';
import { IDeployContext } from './IDeployContext';
import { localGitDeploy } from './localGitDeploy';
import { startPostDeployTask } from './runDeployTask';
import { syncTriggersPostDeploy } from './syncTriggersPostDeploy';
import { IDeployContext, InnerDeployContext } from './IDeployContext';
import { createDeployExecuteSteps } from './wizard/createDeployWizard';

/**
* NOTE: This leverages a command with id `ext.prefix + '.showOutputChannel'` that should be registered by each extension
*/
export async function deploy(site: ParsedSite, fsPath: string, context: IDeployContext): Promise<void> {
export async function deploy(site: ParsedSite, fsPath: string, context: IDeployContext & ExecuteActivityContext): Promise<void> {
const client = await site.createClient(context);
const config: SiteConfigResource = await client.getSiteConfig();
// We use the AppServicePlan in a few places, but we don't want to delay deployment, so start the promise now and save as a const
const aspPromise: Promise<AppServicePlan | undefined> = client.getAppServicePlan();
try {
context.telemetry.properties.sourceHash = randomUtils.getPseudononymousStringHash(fsPath);
context.telemetry.properties.destHash = randomUtils.getPseudononymousStringHash(site.fullName);
context.telemetry.properties.scmType = String(config.scmType);
context.telemetry.properties.isSlot = site.isSlot ? 'true' : 'false';
context.telemetry.properties.alwaysOn = config.alwaysOn ? 'true' : 'false';
context.telemetry.properties.linuxFxVersion = getLinuxFxVersionForTelemetry(config);
context.telemetry.properties.nodeVersion = String(config.nodeVersion);
context.telemetry.properties.pythonVersion = String(config.pythonVersion);
context.telemetry.properties.hasCors = config.cors ? 'true' : 'false';
context.telemetry.properties.hasIpSecurityRestrictions = config.ipSecurityRestrictions && config.ipSecurityRestrictions.length > 0 ? 'true' : 'false';
context.telemetry.properties.javaVersion = String(config.javaVersion);
context.telemetry.properties.siteKind = site.kind;
client.getState().then(
(state: string) => {
context.telemetry.properties.state = state;
},
() => {
// ignore
});
aspPromise.then(
(plan: AppServicePlan | undefined) => {
if (plan) {
context.telemetry.properties.planStatus = String(plan.status);
context.telemetry.properties.planKind = String(plan.kind);
if (plan.sku) {
context.telemetry.properties.planSize = String(plan.sku.size);
context.telemetry.properties.planTier = String(plan.sku.tier);
}
}
},
() => {
// ignore
});
} catch (error) {
// Ignore
}

const title: string = l10n.t('Deploying to "{0}"... Check [output window](command:{1}) for status.', site.fullName, ext.prefix + '.showOutputChannel');
await window.withProgress({ location: ProgressLocation.Notification, title }, async () => {
if (context.stopAppBeforeDeploy) {
ext.outputChannel.appendLog(l10n.t('Stopping app...'), { resourceName: site.fullName });
await client.stop();
}

ext.outputChannel.appendLog(l10n.t('Starting deployment...'), { resourceName: site.fullName });
try {
if (!context.deployMethod && config.scmType === ScmType.GitHub) {
throw new Error(l10n.t('"{0}" is connected to a GitHub repository. Push to GitHub repository to deploy.', site.fullName));
} else if (!context.deployMethod && config.scmType === ScmType.LocalGit) {
await localGitDeploy(site, { fsPath: fsPath }, context);
} else {
if (!(await fse.pathExists(fsPath))) {
throw new Error(l10n.t('Failed to deploy path that does not exist: {0}', fsPath));
}

const javaRuntime = site.isLinux ? config.linuxFxVersion : config.javaContainer;
if (javaRuntime && /^(tomcat|wildfly|jboss)/i.test(javaRuntime)) {
await deployWar(context, site, fsPath);
} else if (javaRuntime && /^java/i.test(javaRuntime) && !site.isFunctionApp) {
const pathFileMap = new Map<string, string>([
[path.basename(fsPath), 'app.jar']
]);
await deployZip(context, site, fsPath, aspPromise, pathFileMap);
} else if (context.deployMethod === 'storage') {
await deployToStorageAccount(context, fsPath, site);
} else {
await deployZip(context, site, fsPath, aspPromise);
}
}
} finally {
if (context.stopAppBeforeDeploy) {
ext.outputChannel.appendLog(l10n.t('Starting app...'), { resourceName: site.fullName });
await client.start();
}
}

await startPostDeployTask(context, fsPath, config.scmType, site.fullName);

if (context.syncTriggersPostDeploy) {
// Don't sync triggers if app is stopped https://github.com/microsoft/vscode-azurefunctions/issues/1608
const state: string | undefined = await client.getState();
if (state?.toLowerCase() === 'running') {
await syncTriggersPostDeploy(context, site);
}
}
});
}

function getLinuxFxVersionForTelemetry(config: SiteConfigResource): string {
const linuxFxVersion = config.linuxFxVersion || '';
// Docker values point to the user's specific image, which we don't want to track
return /^docker/i.test(linuxFxVersion) ? 'docker' : linuxFxVersion;
const innerContext: InnerDeployContext = Object.assign(context, { site, fsPath, client, aspPromise });
const title: string = l10n.t('Deploying to app "{0}"', site.fullName);
const executeSteps = await createDeployExecuteSteps(innerContext);
const wizard: AzureWizard<InnerDeployContext> = new AzureWizard<InnerDeployContext>(innerContext, { executeSteps, title });
innerContext.activityTitle = title;
await wizard.execute();
}
28 changes: 0 additions & 28 deletions appservice/src/deploy/deployWar.ts

This file was deleted.

51 changes: 0 additions & 51 deletions appservice/src/deploy/deployZip.ts

This file was deleted.

24 changes: 2 additions & 22 deletions appservice/src/deploy/runDeployTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

import { IActionContext, UserCancelledError } from '@microsoft/vscode-azext-utils';
import * as vscode from 'vscode';
import { ext } from '../extensionVariables';
import { ScmType } from '../ScmType';
import { ext } from '../extensionVariables';
import { taskUtils } from '../utils/taskUtils';
import { IDeployContext } from './IDeployContext';

Expand Down Expand Up @@ -44,33 +44,13 @@ export async function tryRunPreDeployTask(context: IDeployContext, deployFsPath:
return preDeployTaskResult;
}

/**
* Starts the post deploy task, but doesn't wait for the result (not worth it)
*/
export async function startPostDeployTask(context: IDeployContext, deployFsPath: string, scmType: string | undefined, resourceName: string): Promise<void> {
const settingKey: string = 'postDeployTask';
const taskName: string | undefined = vscode.workspace.getConfiguration(ext.prefix, vscode.Uri.file(deployFsPath)).get(settingKey);
context.telemetry.properties.hasPostDeployTask = String(!!taskName);

if (taskName && shouldExecuteTask(context, scmType, settingKey, taskName)) {
const task: vscode.Task | undefined = await taskUtils.findTask(deployFsPath, taskName);
context.telemetry.properties.foundPostDeployTask = String(!!task);
if (task) {
await taskUtils.executeIfNotActive(task);
ext.outputChannel.appendLog(vscode.l10n.t('Started {0} "{1}".', settingKey, taskName), { resourceName });
} else {
ext.outputChannel.appendLog(vscode.l10n.t('WARNING: Failed to find {0} "{1}".', settingKey, taskName), { resourceName });
}
}
}

export interface IPreDeployTaskResult {
taskName: string | undefined;
exitCode: number | undefined;
failedToFindTask: boolean;
}

function shouldExecuteTask(context: IDeployContext, scmType: string | undefined, settingKey: string, taskName: string): boolean {
export function shouldExecuteTask(context: IDeployContext, scmType: string | undefined, settingKey: string, taskName: string): boolean {
// We don't run deploy tasks for non-zipdeploy since that stuff should be handled by kudu
const shouldExecute: boolean = context.deployMethod === 'storage' || context.deployMethod === 'zip' || (scmType !== ScmType.LocalGit && scmType !== ScmType.GitHub);
if (!shouldExecute) {
Expand Down
Loading

0 comments on commit 5ddc769

Please sign in to comment.