in apps/vs-code-designer/src/app/commands/deploy/deploy.ts [139:401]
title: localize('aadDetails', 'Provide your AAD identity details to use with your Azure connections.'),
});
await wizard.prompt();
}
identityWizardContext.useAdvancedIdentity = true;
}
}
identityWizardContext?.useAdvancedIdentity ? await updateAppSettingsWithIdentityDetails(context, node, identityWizardContext) : undefined;
let isZipDeploy = false;
if (!isHybridLogicApp) {
await verifyAppSettings(context, node, version, language, originalDeployFsPath, !context.isNewApp);
const client = await node.site.createClient(actionContext);
const siteConfig: SiteConfigResource = await client.getSiteConfig();
isZipDeploy = siteConfig.scmType !== ScmType.LocalGit && siteConfig.scmType !== ScmType.GitHub;
if (getWorkspaceSetting<boolean>(showDeployConfirmationSetting, workspaceFolder.uri.fsPath) && !context.isNewApp && isZipDeploy) {
const warning: string = localize(
'confirmDeploy',
'Are you sure you want to deploy to "{0}"? This will overwrite any previous deployment and cannot be undone.',
client.fullName
);
context.telemetry.properties.cancelStep = 'confirmDestructiveDeployment';
const deployButton: MessageItem = { title: localize('deploy', 'Deploy') };
await context.ui.showWarningMessage(warning, { modal: true }, deployButton, DialogResponses.cancel);
context.telemetry.properties.cancelStep = '';
}
await runPreDeployTask(context, effectiveDeployFsPath, siteConfig.scmType);
if (isZipDeploy) {
validateGlobSettings(context, effectiveDeployFsPath);
}
}
await node.runWithTemporaryDescription(context, localize('deploying', 'Deploying...'), async () => {
// preDeploy tasks are only required for zipdeploy so subpath may not exist
let deployFsPath: string = effectiveDeployFsPath;
if (!isZipDeploy && !isPathEqual(effectiveDeployFsPath, originalDeployFsPath)) {
deployFsPath = originalDeployFsPath;
const noSubpathWarning = `WARNING: Ignoring deploySubPath "${getWorkspaceSetting(
deploySubpathSetting,
originalDeployFsPath
)}" for non-zip deploy.`;
ext.outputChannel.appendLog(noSubpathWarning);
}
if (isWorkflowApp) {
await cleanupPublishBinPath(context, effectiveDeployFsPath);
}
deployProjectPathForWorkflowApp = isWorkflowApp
? await getProjectPathToDeploy(node, workspaceFolder, settingsToExclude, deployFsPath, identityWizardContext, actionContext)
: undefined;
try {
if (isHybridLogicApp) {
await deployHybridLogicApp(context, node);
} else {
await innerDeploy(
node.site,
deployProjectPathForWorkflowApp !== undefined ? deployProjectPathForWorkflowApp : deployFsPath,
context
);
}
} finally {
if (deployProjectPathForWorkflowApp !== undefined && !isHybridLogicApp) {
await cleanAndRemoveDeployFolder(deployProjectPathForWorkflowApp);
}
}
});
if (!isHybridLogicApp) {
await node.loadAllChildren(context);
}
await notifyDeployComplete(node, context.workspaceFolder, isHybridLogicApp, settingsToExclude);
}
/**
* Shows tree item picker to select Logic App or create a new one.
* @param {IActionContext} context - Command context.
* @returns {Promise<SlotTreeItem>} Logic App slot tree item.
*/
async function getDeployLogicAppNode(context: IActionContext): Promise<SlotTreeItem> {
const placeHolder: string = localize('selectLogicApp', 'Select Logic App (Standard) in Azure');
const sub = await ext.rgApi.appResourceTree.showTreeItemPicker<AzExtParentTreeItem>(SubscriptionTreeItem.contextValue, context);
let [site, isAdvance] = (await context.ui.showQuickPick(getLogicAppsPicks(context, sub.subscription), { placeHolder })).data;
if (!site) {
if (isAdvance) {
return await createLogicAppAdvanced(context, sub);
}
return await createLogicApp(context, sub);
}
if (site.id.includes('Microsoft.App')) {
// NOTE(anandgmenon): Getting latest metadata for hybrid app as the one loaded from the cache can have outdateed definition and cause deployment to fail.
const clientContainer = await createContainerClient({ ...context, ...sub.subscription });
site = (await clientContainer.containerApps.get(site.id.split('/')[4], site.name)) as undefined as Site;
}
const resourceTree = new LogicAppResourceTree(sub.subscription, site);
return new SlotTreeItem(sub, resourceTree);
}
async function getLogicAppsPicks(
context: IActionContext,
subContext: ISubscriptionContext
): Promise<IAzureQuickPickItem<[Site | undefined, boolean]>[]> {
const logicAppsResolver = new LogicAppResolver();
const sites = await logicAppsResolver.getAppResourceSiteBySubscription(context, subContext);
const picks: { label: string; data: [Site, boolean]; description?: string }[] = [];
Array.from(sites.logicApps).forEach(([_id, site]) => {
picks.push({ label: site.name, data: [site, false] });
});
Array.from(sites.hybridLogicApps).forEach(([_id, site]) => {
picks.push({ label: `${site.name} (Hybrid)`, data: [site as unknown as Site, false] });
});
picks.sort((a, b) => a.label.localeCompare(b.label));
picks.unshift({
label: localize('selectLogicApp', '$(plus) Create new Logic App (Standard) in Azure...'),
data: [undefined, true],
description: localize('advanced', 'Advanced'),
});
picks.unshift({ label: localize('selectLogicApp', '$(plus) Create new Logic App (Standard) in Azure...'), data: [undefined, false] });
return picks;
}
/**
* Azure functions task `_GenerateFunctionsExtensionsMetadataPostPublish` moves `NetFxWorker`
* in `bin/` of publish path, It needs to be reverted as it's a special case where we have a
* Azure Function Extension inside a Logic App Extension.
* @param context {@link IActionContext}
* @param fsPath publish path for logic app extension
*/
async function cleanupPublishBinPath(_context: IActionContext, fsPath: string): Promise<void> {
const netFxWorkerBinPath = path.join(fsPath, 'bin', 'NetFxWorker');
const netFxWorkerAssetPath = path.join(fsPath, 'NetFxWorker');
if (await fse.pathExists(netFxWorkerBinPath)) {
return fse.move(netFxWorkerBinPath, netFxWorkerAssetPath, { overwrite: true });
}
}
async function validateGlobSettings(context: IActionContext, fsPath: string): Promise<void> {
const includeKey = 'zipGlobPattern';
const excludeKey = 'zipIgnorePattern';
const includeSetting: string | undefined = getWorkspaceSetting(includeKey, fsPath);
const excludeSetting: string | string[] | undefined = getWorkspaceSetting(excludeKey, fsPath);
if (includeSetting || excludeSetting) {
context.telemetry.properties.hasOldGlobSettings = 'true';
const message: string = localize(
'globSettingRemoved',
'"{0}" and "{1}" settings are no longer supported. Instead, place a ".funcignore" file at the root of your repo, using the same syntax as a ".gitignore" file.',
includeKey,
excludeKey
);
await context.ui.showWarningMessage(message);
}
}
async function managedApiConnectionsExists(workspaceFolder: WorkspaceFolder): Promise<boolean> {
const workspaceFolderPath = workspaceFolder.uri.fsPath;
const connectionsJson = await getConnectionsJson(workspaceFolderPath);
let connectionsData: ConnectionsData;
try {
connectionsData = JSON.parse(connectionsJson);
} catch {
return false;
}
return !!connectionsData.managedApiConnections && Object.keys(connectionsData.managedApiConnections).length > 0;
}
async function getProjectPathToDeploy(
node: SlotTreeItem,
workspaceFolder: WorkspaceFolder,
settingsToExclude: string[],
originalDeployFsPath: string,
identityWizardContext: IIdentityWizardContext,
actionContext: IActionContext
): Promise<string | undefined> {
const workspaceFolderPath = workspaceFolder.uri.fsPath;
const connectionsJson = await getConnectionsJson(workspaceFolderPath);
const parametersJson = await getParametersJson(workspaceFolderPath);
const targetAppSettings = await node.getApplicationSettings(identityWizardContext as IDeployContext);
const parameterizeConnectionsSetting = getGlobalSetting(parameterizeConnectionsInProjectLoadSetting);
let resolvedConnections: ConnectionsData;
let connectionsData: ConnectionsData;
function updateAuthenticationParameters(authValue: any): void {
if (connectionsData.managedApiConnections && Object.keys(connectionsData.managedApiConnections).length) {
for (const referenceKey of Object.keys(connectionsData.managedApiConnections)) {
parametersJson[`${referenceKey}-Authentication`].value = authValue;
actionContext.telemetry.properties.updateAuth = `updated "${referenceKey}-Authentication" parameter to ManagedServiceIdentity`;
}
}
}
function updateAuthenticationInConnections(authValue: any): void {
if (connectionsData.managedApiConnections && Object.keys(connectionsData.managedApiConnections).length) {
for (const referenceKey of Object.keys(connectionsData.managedApiConnections)) {
connectionsData.managedApiConnections[referenceKey].authentication = authValue;
actionContext.telemetry.properties.updateAuth = `updated "${referenceKey}" connection authentication to ManagedServiceIdentity`;
}
}
}
try {
connectionsData = JSON.parse(connectionsJson);
const authValue = { type: 'ManagedServiceIdentity' };
const advancedIdentityAuthValue = {
type: 'ActiveDirectoryOAuth',
audience: 'https://management.core.windows.net/',
credentialType: 'Secret',
clientId: `@appsetting('${workflowAppAADClientId}')`,
tenant: `@appsetting('${workflowAppAADTenantId}')`,
secret: `@appsetting('${workflowAppAADClientSecret}')`,
};
if (parameterizeConnectionsSetting) {
identityWizardContext?.useAdvancedIdentity
? updateAuthenticationParameters(advancedIdentityAuthValue)
: updateAuthenticationParameters(authValue);
} else {
identityWizardContext?.useAdvancedIdentity
? updateAuthenticationInConnections(advancedIdentityAuthValue)
: updateAuthenticationInConnections(authValue);
}
resolvedConnections = resolveConnectionsReferences(connectionsJson, parametersJson, targetAppSettings);
} catch {
actionContext.telemetry.properties.noAuthUpdate = 'No authentication update was made';
return undefined;
}
if (connectionsData.managedApiConnections && Object.keys(connectionsData.managedApiConnections).length) {
const deployProjectPath = path.join(path.dirname(workspaceFolderPath), `${path.basename(workspaceFolderPath)}-deploytemp`);
const connectionsFilePathDeploy = path.join(deployProjectPath, connectionsFileName);
const parametersFilePathDeploy = path.join(deployProjectPath, parametersFileName);
if (await fse.pathExists(deployProjectPath)) {
await cleanAndRemoveDeployFolder(deployProjectPath);
}
fse.mkdirSync(deployProjectPath);
await fse.copy(originalDeployFsPath, deployProjectPath, { overwrite: true });
for (const referenceKey of Object.keys(connectionsData.managedApiConnections)) {
try {
const connection = resolvedConnections.managedApiConnections[referenceKey].connection;
await createAclInConnectionIfNeeded(identityWizardContext, connection.id, node.site);
if (node.site.isSlot) {