|
2 | 2 | * Copyright (c) Microsoft Corporation. All rights reserved. |
3 | 3 | * Licensed under the MIT License. See License.md in the project root for license information. |
4 | 4 | *--------------------------------------------------------------------------------------------*/ |
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'; |
8 | 9 | import { getAzureContainerAppsApi } from '../../../getExtensionApi'; |
9 | 10 | import { localize } from '../../../localize'; |
10 | 11 | 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'; |
31 | 14 |
|
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 | + } |
38 | 19 |
|
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); |
54 | 48 | } |
55 | 49 | } |
56 | 50 |
|
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>[] = []; |
60 | 65 |
|
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++; |
64 | 70 | } |
| 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; |
65 | 99 | } |
66 | | - return false |
67 | 100 | } |
0 commit comments