Skip to content

Commit 74d8c7e

Browse files
authored
Final "Build Image in Azure" (#292)
* Integrate docker `buildImageInAzure` code (#287) * Migrate docker buildImageInAzure code * Add storage-blob dependency * Spelling change * Incorporate comments * Add BuildImageStep * Incorporate requested changes * Change activity message * Change DockerFileitemStep.ts name * Connect buildImageInAzure to Create Container App (#290) * Add capabilities for remoteAcrBuild * cleanup * Small changes * Add name change to ImageSourceListStep also * PR feedback * Add error message and utilize ImageSource for buildType
1 parent e6779f8 commit 74d8c7e

18 files changed

+664
-22
lines changed

package-lock.json

Lines changed: 199 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@
330330
"dependencies": {
331331
"@azure/arm-appcontainers": "^1.1.0",
332332
"@azure/arm-containerregistry": "^10.0.0",
333+
"@azure/storage-blob": "^12.4.1",
333334
"@azure/arm-operationalinsights": "^8.0.0",
334335
"@azure/arm-resources": "^4.2.2",
335336
"@azure/container-registry": "1.0.0-beta.5",
@@ -341,7 +342,8 @@
341342
"dotenv": "^16.0.0",
342343
"open": "^8.0.4",
343344
"semver": "^7.3.5",
344-
"vscode-nls": "^4.1.1"
345+
"vscode-nls": "^4.1.1",
346+
"vscode-uri": "^3.0.2"
345347
},
346348
"extensionDependencies": [
347349
"ms-vscode.azure-account",

src/commands/deploy/IDeployBaseContext.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@
66
import type { EnvironmentVar, RegistryCredentials, Secret } from "@azure/arm-appcontainers";
77
import { ISubscriptionActionContext } from "@microsoft/vscode-azext-utils";
88
import { AzureSubscription } from "@microsoft/vscode-azureresources-api";
9-
import { ImageSourceValues } from "../../constants";
9+
import { ImageSource, ImageSourceValues } from "../../constants";
1010
import { ContainerAppModel } from "../../tree/ContainerAppItem";
1111

1212
export interface IDeployBaseContext extends ISubscriptionActionContext {
1313
subscription: AzureSubscription;
1414
targetContainer?: ContainerAppModel;
1515

1616
imageSource?: ImageSourceValues;
17+
buildType?: ImageSource.LocalDockerBuild | ImageSource.RemoteAcrBuild;
1718
showQuickStartImage?: boolean;
1819

1920
// Base image attributes used as a precursor for either creating or updating a container app

src/commands/deploy/ImageSourceListStep.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { localize } from "../../utils/localize";
99
import { setQuickStartImage } from "../createContainerApp/setQuickStartImage";
1010
import { EnvironmentVariablesListStep } from "./EnvironmentVariablesListStep";
1111
import { IDeployBaseContext } from "./IDeployBaseContext";
12+
import { BuildFromProjectListStep } from "./buildImageInAzure/BuildFromProjectListStep";
1213
import { ContainerRegistryListStep } from "./deployFromRegistry/ContainerRegistryListStep";
1314
import { DeployFromRegistryConfigureStep } from "./deployFromRegistry/DeployFromRegistryConfigureStep";
1415

@@ -17,13 +18,13 @@ export class ImageSourceListStep extends AzureWizardPromptStep<IDeployBaseContex
1718
const imageSourceLabels: string[] = [
1819
localize('externalRegistry', 'Use existing image'),
1920
localize('quickStartImage', 'Use quickstart image'),
20-
// localize('buildFromProject', 'Build from project'),
21+
localize('buildFromProject', 'Build from project remotely using Azure Container Registry'),
2122
];
2223

2324
const placeHolder: string = localize('imageBuildSourcePrompt', 'Select an image source for the container app');
2425
const picks: IAzureQuickPickItem<ImageSourceValues | undefined>[] = [
2526
{ label: imageSourceLabels[0], data: ImageSource.ExternalRegistry, suppressPersistence: true },
26-
// { label: imageSourceLabels[2], data: undefined, suppressPersistence: true },
27+
{ label: imageSourceLabels[2], data: ImageSource.RemoteAcrBuild, suppressPersistence: true },
2728
];
2829

2930
if (context.showQuickStartImage) {
@@ -49,6 +50,10 @@ export class ImageSourceListStep extends AzureWizardPromptStep<IDeployBaseContex
4950
promptSteps.push(new ContainerRegistryListStep());
5051
executeSteps.push(new DeployFromRegistryConfigureStep());
5152
break;
53+
case ImageSource.RemoteAcrBuild:
54+
promptSteps.push(new BuildFromProjectListStep());
55+
executeSteps.push(new DeployFromRegistryConfigureStep());
56+
break;
5257
default:
5358
// Todo: Steps that lead to additional 'Build from project' options
5459
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.md in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { AzureWizardExecuteStep, AzureWizardPromptStep, IAzureQuickPickItem, IWizardOptions } from "@microsoft/vscode-azext-utils";
7+
import { ImageSource } from "../../../constants";
8+
import { localize } from "../../../utils/localize";
9+
import { IDeployBaseContext } from "../IDeployBaseContext";
10+
import { AcrListStep } from "../deployFromRegistry/acr/AcrListStep";
11+
import { BuildImageStep } from "./BuildImageStep";
12+
import { DockerFileItemStep } from "./DockerFileItemStep";
13+
import { IBuildImageInAzureContext } from "./IBuildImageInAzureContext";
14+
import { ImageNameStep } from "./ImageNameStep";
15+
import { OSPickStep } from "./OSPickStep";
16+
import { RootFolderStep } from "./RootFolderStep";
17+
import { RunStep } from "./RunStep";
18+
import { TarFileStep } from "./TarFileStep";
19+
import { UploadSourceCodeStep } from "./UploadSourceCodeStep";
20+
21+
const buildFromProjectLabels: string[] = [
22+
localize('azure', 'Build from project remotely using Azure Container Registry')
23+
//localize('docker', 'Build from project locally using Docker')
24+
];
25+
26+
export class BuildFromProjectListStep extends AzureWizardPromptStep<IDeployBaseContext> {
27+
public async prompt(context: IDeployBaseContext): Promise<void> {
28+
const placeHolder: string = localize('buildType', 'Select how you want to build your project');
29+
const picks: IAzureQuickPickItem<ImageSource.LocalDockerBuild | ImageSource.RemoteAcrBuild>[] = [
30+
{ label: buildFromProjectLabels[0], data: ImageSource.RemoteAcrBuild, suppressPersistence: true },
31+
//{ label: buildFromProjectLabels[1], data: ImageSource.LocalDockerBuild, suppressPersistence: true }
32+
];
33+
34+
context.buildType = (await context.ui.showQuickPick(picks, { placeHolder })).data;
35+
}
36+
37+
public async configureBeforePrompt(context: IDeployBaseContext): Promise<void> {
38+
if (buildFromProjectLabels.length === 1) {
39+
context.buildType = ImageSource.RemoteAcrBuild;
40+
}
41+
}
42+
43+
public shouldPrompt(context: IDeployBaseContext): boolean {
44+
return !context.buildType;
45+
}
46+
47+
public async getSubWizard(context: IBuildImageInAzureContext): Promise<IWizardOptions<IDeployBaseContext> | undefined> {
48+
const promptSteps: AzureWizardPromptStep<IDeployBaseContext>[] = [];
49+
const executeSteps: AzureWizardExecuteStep<IDeployBaseContext>[] = [];
50+
51+
switch (context.buildType) {
52+
case ImageSource.RemoteAcrBuild:
53+
promptSteps.push(new AcrListStep(), new RootFolderStep(), new DockerFileItemStep(), new ImageNameStep(), new OSPickStep());
54+
executeSteps.push(new TarFileStep(), new UploadSourceCodeStep(), new RunStep(), new BuildImageStep());
55+
break;
56+
//TODO: case for 'Build from project locally using Docker'
57+
}
58+
return { promptSteps, executeSteps };
59+
}
60+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.md in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { AzureWizardExecuteStep } from "@microsoft/vscode-azext-utils";
7+
import { acrDomain } from "../../../constants";
8+
import { localize } from "../../../utils/localize";
9+
import { IBuildImageInAzureContext } from "./IBuildImageInAzureContext";
10+
import { buildImageInAzure } from "./buildImageInAzure";
11+
12+
export class BuildImageStep extends AzureWizardExecuteStep<IBuildImageInAzureContext> {
13+
public priority: number = 225;
14+
15+
public async execute(context: IBuildImageInAzureContext): Promise<void> {
16+
context.registryDomain = acrDomain;
17+
18+
const run = await buildImageInAzure(context);
19+
const outputImages = run?.outputImages;
20+
context.telemetry.properties.outputImages = outputImages?.length?.toString();
21+
22+
if (outputImages) {
23+
const image = outputImages[0];
24+
context.image = `${image.registry}/${image.repository}:${image.tag}`;
25+
} else {
26+
throw new Error(localize('noImagesBuilt', 'Failed to build image.'));
27+
}
28+
}
29+
30+
public shouldExecute(context: IBuildImageInAzureContext): boolean {
31+
return !context.image;
32+
}
33+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.md in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { AzureWizardPromptStep } from '@microsoft/vscode-azext-utils';
7+
import { DOCKERFILE_GLOB_PATTERN } from "../../../constants";
8+
import { localize } from '../../../utils/localize';
9+
import { selectWorkspaceFile } from "../../../utils/workspaceUtils";
10+
import { IBuildImageInAzureContext } from "./IBuildImageInAzureContext";
11+
12+
export class DockerFileItemStep extends AzureWizardPromptStep<IBuildImageInAzureContext> {
13+
public async prompt(context: IBuildImageInAzureContext): Promise<void> {
14+
context.dockerFilePath = await selectWorkspaceFile(context, localize('dockerFilePick', 'Select a Dockerfile'), { filters: { 'Dockerfile': ['Dockerfile', 'Dockerfile.*'] } }, DOCKERFILE_GLOB_PATTERN);
15+
}
16+
17+
public shouldPrompt(context: IBuildImageInAzureContext): boolean {
18+
return !context.dockerFilePath;
19+
}
20+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.md in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import type { Run as AcrRun, ContainerRegistryManagementClient } from '@azure/arm-containerregistry';
7+
import * as vscode from 'vscode';
8+
import { IDeployFromRegistryContext } from '../deployFromRegistry/IDeployFromRegistryContext';
9+
10+
export interface IBuildImageInAzureContext extends IDeployFromRegistryContext {
11+
rootFolder: vscode.WorkspaceFolder;
12+
dockerFilePath: string;
13+
imageName: string;
14+
os: 'Windows' | 'Linux';
15+
16+
uploadedSourceLocation: string;
17+
tarFilePath: string;
18+
19+
client: ContainerRegistryManagementClient;
20+
resourceGroupName: string;
21+
registryName: string;
22+
run: AcrRun
23+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.md in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { AzureWizardPromptStep } from "@microsoft/vscode-azext-utils";
7+
import { URI, Utils } from "vscode-uri";
8+
import { localize } from "../../../utils/localize";
9+
import { IBuildImageInAzureContext } from "./IBuildImageInAzureContext";
10+
11+
export class ImageNameStep extends AzureWizardPromptStep<IBuildImageInAzureContext> {
12+
public async prompt(context: IBuildImageInAzureContext): Promise<void> {
13+
const suggestedImageName = await getSuggestedName(context, context.dockerFilePath);
14+
15+
context.imageName = await context.ui.showInputBox({
16+
prompt: localize('imageNamePrompt', 'Enter a name for the image'),
17+
value: suggestedImageName ? localize('dockerfilePlaceholder', suggestedImageName) : ''
18+
});
19+
}
20+
21+
public shouldPrompt(context: IBuildImageInAzureContext): boolean {
22+
return !context.imageName;
23+
}
24+
25+
}
26+
27+
async function getSuggestedName(context: IBuildImageInAzureContext, dockerFilePath: string): Promise<string | undefined> {
28+
let suggestedImageName: string | undefined;
29+
suggestedImageName = Utils.dirname(URI.parse(dockerFilePath)).path.split('/').pop();
30+
if (suggestedImageName === '') {
31+
if (context.rootFolder) {
32+
suggestedImageName = Utils.basename(context.rootFolder.uri).toLowerCase().replace(/\s/g, '');
33+
}
34+
}
35+
suggestedImageName += ":{{.Run.ID}}";
36+
return suggestedImageName;
37+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.md in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { AzureWizardPromptStep, IAzureQuickPickItem } from "@microsoft/vscode-azext-utils";
7+
import { localize } from "../../../utils/localize";
8+
import { IBuildImageInAzureContext } from "./IBuildImageInAzureContext";
9+
10+
export class OSPickStep extends AzureWizardPromptStep<IBuildImageInAzureContext> {
11+
public async prompt(context: IBuildImageInAzureContext): Promise<void> {
12+
const placeHolder: string = localize('imageOSPrompt', 'Select image base OS');
13+
const picks: IAzureQuickPickItem<'Windows' | 'Linux'>[] = [
14+
{ label: 'Linux', data: 'Linux', suppressPersistence: true },
15+
{ label: 'Windows', data: 'Windows', suppressPersistence: true },
16+
];
17+
18+
context.os = (await context.ui.showQuickPick(picks, { placeHolder })).data;
19+
}
20+
21+
public shouldPrompt(context: IBuildImageInAzureContext): boolean {
22+
return !context.os;
23+
}
24+
}

0 commit comments

Comments
 (0)