Skip to content

Commit ae9fd10

Browse files
authored
Fix containerized functions deployment regression along w/ misc. improvements (#4708)
1 parent 630c3c4 commit ae9fd10

File tree

5 files changed

+95
-57
lines changed

5 files changed

+95
-57
lines changed

src/commands/createFunctionApp/containerImage/DeployWorkspaceProjectStep.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export class DeployWorkspaceProjectStep extends AzureWizardExecuteStepWithActivi
3838
context.deployWorkspaceResult = await containerAppsApi.deployWorkspaceProject({
3939
resourceGroupId: context.resourceGroup?.id,
4040
rootPath: context.rootPath,
41+
srcPath: context.rootPath,
4142
dockerfilePath: context.dockerfilePath,
4243
suppressConfirmation: true,
4344
suppressContainerAppCreation: true,

src/commands/createFunctionApp/containerImage/detectDockerfile.ts

Lines changed: 84 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,66 +2,99 @@
22
* Copyright (c) Microsoft Corporation. All rights reserved.
33
* Licensed under the MIT License. See License.md in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
5-
import { AzExtFsExtra } from '@microsoft/vscode-azext-utils';
6-
import * as vscode from 'vscode';
7-
import { dockerfileGlobPattern, hostFileName } from '../../../constants';
5+
import { AzExtFsExtra, nonNullProp, type IActionContext, type IAzureQuickPickItem } from '@microsoft/vscode-azext-utils';
6+
import * as path from 'path';
7+
import { RelativePattern, workspace, type MessageItem, type Uri, type WorkspaceFolder } from 'vscode';
8+
import { browseItem, dockerfileGlobPattern } from '../../../constants';
89
import { getAzureContainerAppsApi } from '../../../getExtensionApi';
910
import { localize } from '../../../localize';
1011
import { type ICreateFunctionAppContext } from '../../../tree/SubscriptionTreeItem';
11-
import { findFiles } from '../../../utils/workspace';
12-
import { isFunctionProject } from '../../createNewProject/verifyIsProject';
13-
import path = require('path');
14-
15-
export async function detectDockerfile(context: ICreateFunctionAppContext): Promise<void> {
16-
// don't overwrite the workspace folder on the context if it's already set
17-
if (vscode.workspace.workspaceFolders && !context.workspaceFolder) {
18-
context.workspaceFolder = vscode.workspace.workspaceFolders[0];
19-
const workspacePath = context.workspaceFolder.uri.fsPath;
20-
let dockerfilePath: string = workspacePath
21-
context.rootPath = workspacePath;
22-
23-
//check for dockerfile location
24-
if (await isFunctionProject(workspacePath)) {
25-
const files = (await findFiles(context.workspaceFolder, `**/${dockerfileGlobPattern}`));
26-
if (files.length === 0) {
27-
return;
28-
}
29-
dockerfilePath = path.dirname(files[0].fsPath);
30-
}
12+
import { getRootWorkspaceFolder } from '../../../utils/workspace';
13+
import { tryGetFunctionProjectRoot } from '../../createNewProject/verifyIsProject';
3114

32-
// check if host.json is in the same directory as the Dockerfile
33-
if ((await findFiles(dockerfilePath, hostFileName)).length > 0) {
34-
context.dockerfilePath = (await findFiles(dockerfilePath, dockerfileGlobPattern))[0].fsPath;
35-
} else {
36-
context.dockerfilePath = undefined;
37-
}
15+
export async function detectDockerfile(context: ICreateFunctionAppContext): Promise<string | undefined> {
16+
if (!workspace.workspaceFolders?.length) {
17+
return undefined;
18+
}
3819

39-
// prompt user to proceed with containerized function app creation
40-
if (context.dockerfilePath) {
41-
const placeHolder: string = localize('detectedDockerfile', 'Dockerfile detected. What would you like to deploy?');
42-
const containerImageButton: vscode.MessageItem = { title: localize('containerImage', 'Container Image') };
43-
const codeButton: vscode.MessageItem = { title: localize('code', 'Code') };
44-
const buttons: vscode.MessageItem[] = [containerImageButton, codeButton];
45-
const result: vscode.MessageItem = await context.ui.showWarningMessage(placeHolder, { modal: true }, ...buttons);
46-
47-
// if yes, ensure container apps extension is installed before proceeding
48-
if (result === containerImageButton) {
49-
await getAzureContainerAppsApi(context);
50-
} else if (result === codeButton) {
51-
context.dockerfilePath = undefined;
52-
}
53-
}
20+
context.workspaceFolder ??= await getRootWorkspaceFolder() as WorkspaceFolder;
21+
context.rootPath ??= await tryGetFunctionProjectRoot(context, context.workspaceFolder, 'prompt') ?? context.workspaceFolder.uri.fsPath;
22+
23+
const pattern: RelativePattern = new RelativePattern(context.rootPath, `**/${dockerfileGlobPattern}`);
24+
const dockerfiles: Uri[] = await workspace.findFiles(pattern);
25+
context.telemetry.properties.dockerfileCount = String(dockerfiles.length);
26+
27+
if (dockerfiles.length === 0) {
28+
context.telemetry.properties.containerizedDockerfileCount = '0';
29+
return undefined;
30+
}
31+
32+
const useContainerImage: boolean = await promptUseContainerImage(context);
33+
context.telemetry.properties.useContainerImage = String(useContainerImage);
34+
35+
if (!useContainerImage) {
36+
return undefined;
37+
}
38+
39+
// ensure container apps extension is installed before proceeding
40+
await getAzureContainerAppsApi(context);
41+
42+
if (dockerfiles.length === 1) {
43+
const dockerfilePath: string = dockerfiles[0].fsPath;
44+
context.telemetry.properties.containerizedDockerfileCount = await detectFunctionsDockerfile(dockerfilePath) ? '1' : '0';
45+
return dockerfilePath;
46+
} else {
47+
return await promptChooseDockerfile(context, dockerfiles);
5448
}
5549
}
5650

57-
export async function detectFunctionsDockerfile(file: string): Promise<boolean> {
58-
const content = await AzExtFsExtra.readFile(file);
59-
const lines: string[] = content.split('\n');
51+
async function promptUseContainerImage(context: IActionContext): Promise<boolean> {
52+
const placeHolder: string = localize('detectedDockerfile', 'Dockerfile detected. What would you like to deploy?');
53+
const containerImageButton: MessageItem = { title: localize('containerImage', 'Container Image') };
54+
const codeButton: MessageItem = { title: localize('code', 'Code') };
55+
const buttons: MessageItem[] = [containerImageButton, codeButton];
56+
const result: MessageItem = await context.ui.showWarningMessage(placeHolder, { modal: true }, ...buttons);
57+
return result === containerImageButton;
58+
}
59+
60+
async function promptChooseDockerfile(context: ICreateFunctionAppContext, dockerfiles: Uri[]): Promise<string> {
61+
const checkContainerizedDockerfiles: Promise<boolean>[] = dockerfiles.map(d => detectFunctionsDockerfile(d.fsPath));
62+
63+
let containerizedDockerfileCount: number = 0;
64+
const picks: IAzureQuickPickItem<string | undefined>[] = [];
6065

61-
for (const line of lines) {
62-
if (line.includes('mcr.microsoft.com/azure-functions')) {
63-
return true;
66+
for (const [i, dockerfile] of dockerfiles.entries()) {
67+
const isContainerizedDockerfile: boolean = await checkContainerizedDockerfiles[i];
68+
if (isContainerizedDockerfile) {
69+
containerizedDockerfileCount++;
6470
}
71+
72+
const relativeDirectory: string = '.' + path.sep + path.relative(nonNullProp(context, 'rootPath'), dockerfile.fsPath);
73+
const functionsImage: string = localize('functionsImage', 'Functions Image');
74+
75+
picks.push({
76+
label: path.basename(dockerfile.fsPath),
77+
description: isContainerizedDockerfile ? `${relativeDirectory} (${functionsImage})` : relativeDirectory,
78+
data: dockerfile.fsPath,
79+
});
80+
}
81+
context.telemetry.properties.containerizedDockerfileCount = String(containerizedDockerfileCount);
82+
83+
picks.push(browseItem);
84+
85+
const dockerfilePath: string | undefined = (await context.ui.showQuickPick(picks, {
86+
placeHolder: localize('dockerfilePick', 'Choose a Dockerfile from your current workspace.'),
87+
suppressPersistence: true,
88+
})).data;
89+
90+
return dockerfilePath || (await context.ui.showOpenDialog({ filters: {} }))[0].fsPath;
91+
}
92+
93+
async function detectFunctionsDockerfile(dockerfilePath: string): Promise<boolean> {
94+
try {
95+
const content = await AzExtFsExtra.readFile(dockerfilePath);
96+
return content.includes('mcr.microsoft.com/azure-functions');
97+
} catch {
98+
return false;
6599
}
66-
return false
67100
}

src/commands/createFunctionApp/createCreateFunctionAppComponents.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,16 @@ export async function createCreateFunctionAppComponents(context: ICreateFunction
5353
replication: StorageAccountReplication.LRS
5454
};
5555

56-
await detectDockerfile(context);
56+
wizardContext.dockerfilePath = await detectDockerfile(wizardContext);
57+
5758
if (wizardContext.workspaceFolder) {
5859
wizardContext.durableStorageType = await durableUtils.getStorageTypeFromWorkspace(language, wizardContext.workspaceFolder.uri.fsPath);
5960
wizardContext.telemetry.properties.durableStorageType = wizardContext.durableStorageType;
6061
}
6162

62-
promptSteps.push(new SiteNameStep(context.dockerfilePath ? "containerizedFunctionApp" : "functionApp"));
63+
promptSteps.push(new SiteNameStep(wizardContext.dockerfilePath ? "containerizedFunctionApp" : "functionApp"));
6364

64-
if (context.dockerfilePath) {
65+
if (wizardContext.dockerfilePath) {
6566
const containerizedfunctionAppWizard = await createContainerizedFunctionAppWizard();
6667
promptSteps.push(...containerizedfunctionAppWizard.promptSteps);
6768
executeSteps.push(...containerizedfunctionAppWizard.executeSteps);
@@ -75,13 +76,13 @@ export async function createCreateFunctionAppComponents(context: ICreateFunction
7576
if (!wizardContext.advancedCreation) {
7677
LocationListStep.addStep(wizardContext, promptSteps);
7778
// if the user is deploying to a container app, do not use a flex consumption plan
78-
wizardContext.useFlexConsumptionPlan = true && !context.dockerfilePath;
79+
wizardContext.useFlexConsumptionPlan = !wizardContext.dockerfilePath;
7980
wizardContext.stackFilter = getRootFunctionsWorkerRuntime(wizardContext.language);
8081
promptSteps.push(new ConfigureCommonNamesStep());
8182
executeSteps.push(new ResourceGroupCreateStep());
8283
executeSteps.push(new StorageAccountCreateStep(storageAccountCreateOptions));
8384
executeSteps.push(new AppInsightsCreateStep());
84-
if (!context.dockerfilePath) {
85+
if (!wizardContext.dockerfilePath) {
8586
executeSteps.push(new AppServicePlanCreateStep());
8687
executeSteps.push(new LogAnalyticsCreateStep());
8788
}

src/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ export const remoteBuildSetting: string = 'scmDoBuildDuringDeployment';
2323
export const javaBuildTool: string = 'javaBuildTool';
2424
export const functionSubpathSetting: string = 'functionSubpath';
2525

26+
export const browseItem: IAzureQuickPickItem<undefined> = { label: localize('browse', '$(file-directory) Browse...'), description: '', data: undefined };
27+
2628
export enum ProjectLanguage {
2729
CSharp = 'C#',
2830
CSharpScript = 'C#Script',

src/utils/workspace.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { AzExtFsExtra, UserCancelledError, type IActionContext, type IAzureQuick
77
import * as globby from 'globby';
88
import * as path from 'path';
99
import * as vscode from 'vscode';
10+
import { browseItem } from '../constants';
1011
import { localize } from '../localize';
1112
import * as fsUtils from './fs';
1213

@@ -101,7 +102,7 @@ export async function selectWorkspaceItem(context: IActionContext, placeHolder:
101102
folderPicks.push({ label: path.basename(fsPath), description: fsPath, data: fsPath });
102103
}
103104
}
104-
folderPicks.push({ label: localize('browse', '$(file-directory) Browse...'), description: '', data: undefined });
105+
folderPicks.push(browseItem);
105106
folder = await context.ui.showQuickPick(folderPicks, { placeHolder });
106107
}
107108
return folder && folder.data ? folder.data : (await context.ui.showOpenDialog(options))[0].fsPath;

0 commit comments

Comments
 (0)