Skip to content

Commit ea2d2f0

Browse files
authored
Add deployImage as part of API export, add additional activity children, and allow more flexible deployment targets (#817)
1 parent 73a4b34 commit ea2d2f0

15 files changed

+214
-134
lines changed

src/commands/api/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
### 0.0.3
44

5+
### Added
6+
* [[817]](https://github.com/microsoft/vscode-azurecontainerapps/pull/817) Added an API entry-point and compat wrapper for existing `deployImageApi` command
7+
58
### Changed
69
* [[816]](https://github.com/microsoft/vscode-azurecontainerapps/pull/816) Added backward compatibility to ensure existing functionality remains unaffected by new managed identity features.
710

src/commands/api/deployImageApi.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { callWithMaskHandling, callWithTelemetryAndErrorHandling, createSubscriptionContext, type ExecuteActivityContext, type IActionContext, type ISubscriptionActionContext } from "@microsoft/vscode-azext-utils";
7+
import { ImageSource, acrDomain } from "../../constants";
8+
import { type DeployImageApiTelemetryProps as TelemetryProps } from "../../telemetry/commandTelemetryProps";
9+
import { type SetTelemetryProps } from "../../telemetry/SetTelemetryProps";
10+
import { getDomainFromRegistryName, getRegistryFromAcrName } from "../../utils/imageNameUtils";
11+
import { pickContainer } from "../../utils/pickItem/pickContainer";
12+
import { deployImage } from "../deployImage/deployImage";
13+
import { type ContainerRegistryImageSourceContext } from "../image/imageSource/containerRegistry/ContainerRegistryImageSourceContext";
14+
import { type ImageSourceBaseContext } from "../image/imageSource/ImageSourceContext";
15+
import { type DeployImageToAcaOptionsContract } from "./vscode-azurecontainerapps.api";
16+
17+
export type DeployImageApiContext = ImageSourceBaseContext & ExecuteActivityContext & SetTelemetryProps<TelemetryProps>;
18+
19+
export async function deployImageApi(deployImageOptions: DeployImageToAcaOptionsContract): Promise<void> {
20+
return await callWithTelemetryAndErrorHandling('containerApps.api.deployImage', async (context: IActionContext & Partial<ContainerRegistryImageSourceContext>) => {
21+
const node = await pickContainer(context);
22+
const { subscription } = node;
23+
24+
Object.assign(context, { ...createSubscriptionContext(subscription), imageSource: ImageSource.ContainerRegistry }, deployImageOptions);
25+
26+
context.registryDomain = getDomainFromRegistryName(deployImageOptions.registryName);
27+
if (context.registryDomain === acrDomain) {
28+
context.registry = await getRegistryFromAcrName(<ISubscriptionActionContext>context, deployImageOptions.registryName);
29+
}
30+
31+
// Mask sensitive data
32+
if (deployImageOptions.secret) {
33+
context.valuesToMask.push(deployImageOptions.secret);
34+
}
35+
if (deployImageOptions.username) {
36+
context.valuesToMask.push(deployImageOptions.username);
37+
}
38+
context.valuesToMask.push(deployImageOptions.image);
39+
40+
if (deployImageOptions.secret) {
41+
context.telemetry.properties.hasRegistrySecrets = 'true';
42+
return callWithMaskHandling<void>(() => deployImage(context, node), deployImageOptions.secret);
43+
} else {
44+
context.telemetry.properties.hasRegistrySecrets = 'false';
45+
return deployImage(context, node);
46+
}
47+
});
48+
}
49+
50+
/**
51+
* A compatibility wrapper for the legacy entrypoint utilizing `deployImageApi`
52+
*/
53+
export async function deployImageApiCompat(_: IActionContext & Partial<ContainerRegistryImageSourceContext>, deployImageOptions: DeployImageToAcaOptionsContract): Promise<void> {
54+
return await deployImageApi(deployImageOptions);
55+
}

src/commands/api/getAzureContainerAppsApiProvider.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { createApiProvider, type apiUtils } from "@microsoft/vscode-azext-utils";
7+
import { deployImageApi } from "./deployImageApi";
78
import { deployWorkspaceProjectApi } from "./deployWorkspaceProjectApi";
89
import type * as api from "./vscode-azurecontainerapps.api";
910

@@ -12,6 +13,7 @@ export function getAzureContainerAppsApiProvider(): apiUtils.AzureExtensionApiPr
1213
// Todo: Change this to 0.0.3 later. 0.0.3 is backwards compatible anyway so this change should be fine either way.
1314
// For some reason it's causing a block on Function side, so just keep it at 0.0.1 until we figure out why
1415
apiVersion: '0.0.1',
15-
deployWorkspaceProject: deployWorkspaceProjectApi
16+
deployImage: deployImageApi,
17+
deployWorkspaceProject: deployWorkspaceProjectApi,
1618
}]);
1719
}

src/commands/api/vscode-azurecontainerapps.api.d.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ export interface AzureContainerAppsExtensionApi {
88
deployWorkspaceProject(options: DeployWorkspaceProjectOptionsContract): Promise<DeployWorkspaceProjectResults>;
99
}
1010

11+
// The interface of the command options passed to the Azure Container Apps extension's deployImageToAca command
12+
// This interface is shared with the Docker extension (https://github.com/microsoft/vscode-docker)
13+
export interface DeployImageToAcaOptionsContract {
14+
image: string;
15+
registryName: string;
16+
username?: string;
17+
secret?: string;
18+
}
19+
1120
export interface DeployWorkspaceProjectOptionsContract {
1221
// Existing resources
1322
subscriptionId?: string;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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 Template } from "@azure/arm-appcontainers";
7+
import { type DeployImageApiContext } from "../api/deployImageApi";
8+
9+
export interface DeployImageContext extends DeployImageApiContext {
10+
containersIdx: number;
11+
template: Template;
12+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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 ResourceGroup } from "@azure/arm-resources";
7+
import { LocationListStep, ResourceGroupListStep } from "@microsoft/vscode-azext-azureutils";
8+
import { activityInfoIcon, activitySuccessContext, AzureWizard, createSubscriptionContext, createUniversallyUniqueContextValue, GenericTreeItem, nonNullValue, nonNullValueAndProp, type IActionContext, type ISubscriptionContext } from "@microsoft/vscode-azext-utils";
9+
import { ext } from "../../extensionVariables";
10+
import { type ContainerItem } from "../../tree/containers/ContainerItem";
11+
import { createActivityContext } from "../../utils/activityUtils";
12+
import { isAzdExtensionInstalled } from "../../utils/azdUtils";
13+
import { getManagedEnvironmentFromContainerApp } from "../../utils/getResourceUtils";
14+
import { getVerifyProvidersStep } from "../../utils/getVerifyProvidersStep";
15+
import { localize } from "../../utils/localize";
16+
import { getParentResource } from "../../utils/revisionDraftUtils";
17+
import { ContainerAppOverwriteConfirmStep } from "../ContainerAppOverwriteConfirmStep";
18+
import { showContainerAppNotification } from "../createContainerApp/showContainerAppNotification";
19+
import { ContainerAppUpdateStep } from "../image/imageSource/ContainerAppUpdateStep";
20+
import { ImageSourceListStep } from "../image/imageSource/ImageSourceListStep";
21+
import { type ContainerRegistryImageSourceContext } from "../image/imageSource/containerRegistry/ContainerRegistryImageSourceContext";
22+
import { type DeployImageContext } from "./DeployImageContext";
23+
24+
export async function deployImage(context: IActionContext & Partial<ContainerRegistryImageSourceContext>, node: ContainerItem): Promise<void> {
25+
const { subscription, containerApp } = node;
26+
const subscriptionContext: ISubscriptionContext = createSubscriptionContext(subscription);
27+
28+
const wizardContext: DeployImageContext = {
29+
...context,
30+
...subscriptionContext,
31+
...await createActivityContext(true),
32+
subscription,
33+
managedEnvironment: await getManagedEnvironmentFromContainerApp({ ...context, ...subscriptionContext }, containerApp),
34+
containerApp,
35+
containersIdx: node.containersIdx,
36+
template: nonNullValueAndProp(getParentResource(containerApp, node.revision), 'template'),
37+
};
38+
39+
if (isAzdExtensionInstalled()) {
40+
wizardContext.telemetry.properties.isAzdExtensionInstalled = 'true';
41+
}
42+
43+
const resourceGroups: ResourceGroup[] = await ResourceGroupListStep.getResourceGroups(wizardContext);
44+
wizardContext.resourceGroup = nonNullValue(
45+
resourceGroups.find(rg => rg.name === containerApp.resourceGroup),
46+
localize('containerAppResourceGroup', 'Expected to find the container app\'s resource group.'),
47+
);
48+
49+
// Log resource group
50+
wizardContext.activityChildren?.push(
51+
new GenericTreeItem(undefined, {
52+
contextValue: createUniversallyUniqueContextValue(['useExistingResourceGroupInfoItem', activitySuccessContext]),
53+
label: localize('useResourceGroup', 'Using resource group "{0}"', wizardContext.resourceGroup.name),
54+
iconPath: activityInfoIcon
55+
})
56+
);
57+
ext.outputChannel.appendLog(localize('usingResourceGroup', 'Using resource group "{0}".', wizardContext.resourceGroup.name));
58+
59+
// Log container app
60+
wizardContext.activityChildren?.push(
61+
new GenericTreeItem(undefined, {
62+
contextValue: createUniversallyUniqueContextValue(['useExistingContainerAppInfoItem', activitySuccessContext]),
63+
label: localize('useContainerApp', 'Using container app "{0}"', wizardContext.containerApp?.name),
64+
iconPath: activityInfoIcon
65+
})
66+
);
67+
ext.outputChannel.appendLog(localize('usingContainerApp', 'Using container app "{0}".', wizardContext.containerApp?.name));
68+
69+
await LocationListStep.setLocation(wizardContext, containerApp.location);
70+
71+
const parentResourceName: string = getParentResource(containerApp, node.revision).name ?? containerApp.name;
72+
const wizard: AzureWizard<DeployImageContext> = new AzureWizard(wizardContext, {
73+
title: localize('deployImageTitle', 'Deploy image to "{0}"', parentResourceName),
74+
promptSteps: [
75+
new ImageSourceListStep(),
76+
new ContainerAppOverwriteConfirmStep(),
77+
],
78+
executeSteps: [
79+
getVerifyProvidersStep<DeployImageContext>(),
80+
new ContainerAppUpdateStep(),
81+
],
82+
showLoadingPrompt: true
83+
});
84+
85+
await wizard.prompt();
86+
await wizard.execute();
87+
88+
if (!wizardContext.suppressNotification) {
89+
void showContainerAppNotification(containerApp, true /** isUpdate */);
90+
}
91+
}

src/commands/image/deployImageApi/deployImage.ts

Lines changed: 0 additions & 53 deletions
This file was deleted.

src/commands/image/deployImageApi/deployImageApi.ts

Lines changed: 0 additions & 59 deletions
This file was deleted.

src/commands/image/imageSource/ContainerAppUpdateStep.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { type Ingress } from "@azure/arm-appcontainers";
6+
import { type Container, type Ingress } from "@azure/arm-appcontainers";
77
import { activityFailContext, activityFailIcon, activitySuccessContext, activitySuccessIcon, AzureWizardExecuteStep, createUniversallyUniqueContextValue, GenericParentTreeItem, GenericTreeItem, nonNullProp, type ExecuteActivityOutput } from "@microsoft/vscode-azext-utils";
88
import * as retry from "p-retry";
99
import { type Progress } from "vscode";
@@ -57,15 +57,19 @@ export class ContainerAppUpdateStep<T extends ImageSourceContext & IngressContex
5757
containerAppEnvelope.configuration.secrets = context.secrets;
5858
containerAppEnvelope.configuration.registries = context.registryCredentials;
5959

60-
// We want to replace the old image
61-
containerAppEnvelope.template ||= {};
62-
containerAppEnvelope.template.containers = [];
60+
containerAppEnvelope.template = context.template ?? containerAppEnvelope.template ?? {};
61+
containerAppEnvelope.template.containers ||= [];
6362

64-
containerAppEnvelope.template.containers.push({
63+
const newContainer: Container = {
6564
env: context.environmentVariables,
6665
image: context.image,
6766
name: getContainerNameForImage(nonNullProp(context, 'image')),
68-
});
67+
};
68+
if (context.containersIdx) {
69+
containerAppEnvelope.template.containers[context.containersIdx] = newContainer;
70+
} else {
71+
containerAppEnvelope.template.containers = [newContainer];
72+
}
6973

7074
// Related: https://github.com/microsoft/vscode-azurecontainerapps/pull/805
7175
const retries = 4;

src/commands/image/imageSource/EnvFileListStep.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export class EnvFileListStep<T extends EnvFileListContext> extends AzureWizardPr
3838
}
3939

4040
public async prompt(context: T): Promise<void> {
41-
const existingData = context.containerApp?.template?.containers?.[context.containersIdx ?? 0].env;
41+
const existingData = context.template?.containers?.[context.containersIdx ?? 0].env ?? context.containerApp?.template?.containers?.[context.containersIdx ?? 0].env;
4242
context.envPath ??= await this.promptForEnvPath(context, !!existingData /** showHasExistingData */);
4343

4444
if (!context.envPath && existingData) {

0 commit comments

Comments
 (0)