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

appservice: Refactor deployment code into steps #1554

Merged
merged 19 commits into from
Jan 23, 2024
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
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