Skip to content
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
87841ca
Initial scaffold for deployContainerApp
MicroFish91 Dec 18, 2024
1088867
Improve wizard title
MicroFish91 Dec 18, 2024
3e85ad6
Add retries for failed updates
MicroFish91 Dec 18, 2024
e0a7003
Update throw condition
MicroFish91 Dec 18, 2024
c35bd08
Improve activity children and output logs
MicroFish91 Dec 19, 2024
ef87f0f
Add todo
MicroFish91 Dec 19, 2024
631be79
Add resource output logs
MicroFish91 Dec 19, 2024
b537741
Add comment
MicroFish91 Dec 19, 2024
146661f
Merge branch 'mwf/adorable-plum' of https://github.com/microsoft/vsco…
MicroFish91 Dec 19, 2024
e4ede57
Fix a log message
MicroFish91 Dec 19, 2024
7906366
Always prompt registry
MicroFish91 Dec 19, 2024
7c8d543
Improvements to finding recommended pick
MicroFish91 Dec 19, 2024
b8a81fb
Impl
MicroFish91 Dec 20, 2024
a5db872
Add fix for imageSourceListStep
MicroFish91 Dec 20, 2024
c9292be
Merge branch 'mwf/homely-emerald' of https://github.com/microsoft/vsc…
MicroFish91 Dec 20, 2024
1feaddd
Improve placeHolder prompt
MicroFish91 Dec 20, 2024
a139430
Fix deployImage
MicroFish91 Dec 20, 2024
f2cd6a7
Merge branch 'mwf/homely-emerald' of https://github.com/microsoft/vsc…
MicroFish91 Dec 20, 2024
300a5da
Merge with main
MicroFish91 Dec 21, 2024
2d05935
Improve acr pick recommendations
MicroFish91 Dec 24, 2024
209d1ac
Only sort if sr exists
MicroFish91 Dec 24, 2024
e2c87c6
Add draft boolean to context and register more children for editConta…
MicroFish91 Dec 24, 2024
7a8787c
Update editContainerImage
MicroFish91 Dec 24, 2024
eff6574
Improve comment
MicroFish91 Dec 24, 2024
5b22cc9
Improve comment again
MicroFish91 Dec 24, 2024
bd3e538
Add guard clauses to key commands
MicroFish91 Dec 24, 2024
57661f6
Small improvements
MicroFish91 Dec 24, 2024
d2eddbb
Make pick description search case insensitive
MicroFish91 Dec 24, 2024
4ec09bb
Change to pickUtils
MicroFish91 Dec 24, 2024
85eba7d
Consolidate with pickUtils
MicroFish91 Dec 26, 2024
85d8d1c
Merge branch 'mwf/fiscal-beige' of https://github.com/microsoft/vscod…
MicroFish91 Dec 26, 2024
ac5b4dc
Merge branch 'mwf/obliged-gold' of https://github.com/microsoft/vscod…
MicroFish91 Dec 26, 2024
31db97f
Initial functions api support
MicroFish91 Dec 26, 2024
6f7cb74
Update changelog
MicroFish91 Dec 27, 2024
75df8de
Update suppress option
MicroFish91 Dec 27, 2024
6a98700
Feedback
MicroFish91 Dec 27, 2024
08428a0
Remove container app name
MicroFish91 Dec 27, 2024
7849f25
Merge branch 'mwf/gentle-amber' of https://github.com/microsoft/vscod…
MicroFish91 Dec 27, 2024
5faccfc
Update deployImageApi
MicroFish91 Dec 27, 2024
21cf2c0
Add todos
MicroFish91 Dec 27, 2024
a5ddc53
Update changelog
MicroFish91 Dec 30, 2024
6a433d9
Add activity children and support template updates
MicroFish91 Dec 30, 2024
316ea8c
Add parent resource reference
MicroFish91 Dec 30, 2024
d3c884c
Make command consistent with other deploy commands
MicroFish91 Dec 30, 2024
4604735
Scaffold environment variable preconfigure logic
MicroFish91 Dec 30, 2024
979d280
Change prompt
MicroFish91 Dec 31, 2024
64d07ab
Add output logs and prompt verbiage/key
MicroFish91 Dec 31, 2024
80b2964
Small fixes for activity log output
MicroFish91 Jan 7, 2025
a94731e
Merge with main
MicroFish91 Jan 10, 2025
bf9d0e8
Fix merge
MicroFish91 Jan 10, 2025
a086062
Add env path to description
MicroFish91 Jan 10, 2025
8ac696e
Update quick pick labels
MicroFish91 Jan 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@
},
{
"command": "containerApps.deployContainerApp",
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /containerAppItem/i",
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /containerAppItem(.*)revisionMode:single/i",
"group": "2@1"
},
{
Expand Down
10 changes: 8 additions & 2 deletions src/commands/api/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
# Change Log

### 0.0.2
### 0.0.3

### Added
* [[817]](https://github.com/microsoft/vscode-azurecontainerapps/pull/817) Added an API entry-point and compat wrapper for existing `deployImageApi` command

### Changed
* [[816]](https://github.com/microsoft/vscode-azurecontainerapps/pull/816) Added backward compatibility to ensure existing functionality remains unaffected by new managed identity features.

### 0.0.2

### Changed
* [[615]](https://github.com/microsoft/vscode-azurecontainerapps/pull/615) Removed ability to set option `ignoreExistingDeploySettings`. This will now happen automatically by default.

## 0.0.1
* Initial release

### Added

* [[578]](https://github.com/microsoft/vscode-azurecontainerapps/pull/578) Added an API entry-point to the `deployWorkspaceProject` command
11 changes: 11 additions & 0 deletions src/commands/api/deployImageApi/DeployImageApiContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { type ExecuteActivityContext } from "@microsoft/vscode-azext-utils";
import { type DeployImageApiTelemetryProps as TelemetryProps } from "../../../telemetry/commandTelemetryProps";
import { type SetTelemetryProps } from "../../../telemetry/SetTelemetryProps";
import { type ImageSourceBaseContext } from "../../image/imageSource/ImageSourceContext";

export type DeployImageApiContext = ImageSourceBaseContext & ExecuteActivityContext & SetTelemetryProps<TelemetryProps>;
43 changes: 43 additions & 0 deletions src/commands/api/deployImageApi/deployImageApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { callWithMaskHandling, callWithTelemetryAndErrorHandling, createSubscriptionContext, type IActionContext, type ISubscriptionActionContext } from "@microsoft/vscode-azext-utils";
import { ImageSource, acrDomain } from "../../../constants";
import { getDomainFromRegistryName, getRegistryFromAcrName } from "../../../utils/imageNameUtils";
import { pickContainer } from "../../../utils/pickItem/pickContainer";
import { deployImage } from "../../deployImage/deployImage";
import { type ContainerRegistryImageSourceContext } from "../../image/imageSource/containerRegistry/ContainerRegistryImageSourceContext";
import { type DeployImageToAcaOptionsContract } from "../vscode-azurecontainerapps.api";

export async function deployImageApi(deployImageOptions: DeployImageToAcaOptionsContract): Promise<void> {
return await callWithTelemetryAndErrorHandling('containerApps.api.deployImage', async (context: IActionContext & Partial<ContainerRegistryImageSourceContext>) => {
const node = await pickContainer(context);
const { subscription } = node;

Object.assign(context, { ...createSubscriptionContext(subscription), imageSource: ImageSource.ContainerRegistry }, deployImageOptions);

context.registryDomain = getDomainFromRegistryName(deployImageOptions.registryName);
if (context.registryDomain === acrDomain) {
context.registry = await getRegistryFromAcrName(<ISubscriptionActionContext>context, deployImageOptions.registryName);
}

// Mask sensitive data
if (deployImageOptions.secret) {
context.valuesToMask.push(deployImageOptions.secret);
}
if (deployImageOptions.username) {
context.valuesToMask.push(deployImageOptions.username);
}
context.valuesToMask.push(deployImageOptions.image);

if (deployImageOptions.secret) {
context.telemetry.properties.hasRegistrySecrets = 'true';
return callWithMaskHandling<void>(() => deployImage(context, node), deployImageOptions.secret);
} else {
context.telemetry.properties.hasRegistrySecrets = 'false';
return deployImage(context, node);
}
});
}
16 changes: 16 additions & 0 deletions src/commands/api/deployImageApi/deployImageApiCompat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { type IActionContext } from "@microsoft/vscode-azext-utils";
import { type ContainerRegistryImageSourceContext } from "../../image/imageSource/containerRegistry/ContainerRegistryImageSourceContext";
import { type DeployImageToAcaOptionsContract } from "../vscode-azurecontainerapps.api";
import { deployImageApi } from "./deployImageApi";

/**
* A compatibility wrapper for the legacy entrypoint utilizing `deployImageApi`
*/
export async function deployImageApiCompat(_: IActionContext & Partial<ContainerRegistryImageSourceContext>, deployImageOptions: DeployImageToAcaOptionsContract): Promise<void> {
return await deployImageApi(deployImageOptions);
}
14 changes: 13 additions & 1 deletion src/commands/api/deployWorkspaceProjectApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,21 @@ import { type DeployWorkspaceProjectContext } from "../deployWorkspaceProject/De
import { getDeployWorkspaceProjectResults, type DeployWorkspaceProjectResults } from "../deployWorkspaceProject/getDeployWorkspaceProjectResults";
import { type DeployWorkspaceProjectInternalContext } from "../deployWorkspaceProject/internal/DeployWorkspaceProjectInternalContext";
import { deployWorkspaceProjectInternal } from "../deployWorkspaceProject/internal/deployWorkspaceProjectInternal";
import { RegistryCredentialType } from "../registryCredentials/RegistryCredentialsAddConfigurationListStep";
import type * as api from "./vscode-azurecontainerapps.api";

export async function deployWorkspaceProjectApi(deployWorkspaceProjectOptions: api.DeployWorkspaceProjectOptionsContract): Promise<DeployWorkspaceProjectResults> {
return await callWithTelemetryAndErrorHandling('containerApps.api.deployWorkspaceProject', async (context: IActionContext): Promise<DeployWorkspaceProjectResults> => {
const { resourceGroupId, rootPath, dockerfilePath, srcPath, suppressConfirmation, suppressContainerAppCreation, shouldSaveDeploySettings } = deployWorkspaceProjectOptions;
const {
resourceGroupId,
rootPath,
dockerfilePath,
srcPath,
suppressRegistryPrompt,
suppressConfirmation,
suppressContainerAppCreation,
shouldSaveDeploySettings
} = deployWorkspaceProjectOptions;

const subscription: AzureSubscription = await subscriptionExperience(context, ext.rgApiV2.resources.azureResourceTreeDataProvider, {
selectBySubscriptionId: getSubscriptionIdFromOptions(deployWorkspaceProjectOptions),
Expand All @@ -38,12 +48,14 @@ export async function deployWorkspaceProjectApi(deployWorkspaceProjectOptions: a
rootFolder,
srcPath: srcPath ? Uri.file(srcPath).fsPath : undefined,
dockerfilePath: dockerfilePath ? Uri.file(dockerfilePath).fsPath : undefined,
newRegistryCredentialType: RegistryCredentialType.DockerLogin,
shouldSaveDeploySettings: !!shouldSaveDeploySettings,
});

const deployWorkspaceProjectContext: DeployWorkspaceProjectContext = await deployWorkspaceProjectInternal(deployWorkspaceProjectInternalContext, {
suppressActivity: true,
suppressConfirmation,
suppressRegistryPrompt: suppressRegistryPrompt ?? true,
suppressContainerAppCreation,
suppressProgress: true,
suppressWizardTitle: true,
Expand Down
6 changes: 4 additions & 2 deletions src/commands/api/getAzureContainerAppsApiProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
*--------------------------------------------------------------------------------------------*/

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

export function getAzureContainerAppsApiProvider(): apiUtils.AzureExtensionApiProvider {
return createApiProvider([<api.AzureContainerAppsExtensionApi>{
// Todo: Change this to 0.0.2 later. 0.0.2 is backwards compatible anyway so this change should be fine either way.
// Todo: Change this to 0.0.3 later. 0.0.3 is backwards compatible anyway so this change should be fine either way.
// For some reason it's causing a block on Function side, so just keep it at 0.0.1 until we figure out why
apiVersion: '0.0.1',
deployWorkspaceProject: deployWorkspaceProjectApi
deployImage: deployImageApi,
deployWorkspaceProject: deployWorkspaceProjectApi,
}]);
}
10 changes: 10 additions & 0 deletions src/commands/api/vscode-azurecontainerapps.api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ export interface AzureContainerAppsExtensionApi {
deployWorkspaceProject(options: DeployWorkspaceProjectOptionsContract): Promise<DeployWorkspaceProjectResults>;
}

// The interface of the command options passed to the Azure Container Apps extension's deployImageToAca command
// This interface is shared with the Docker extension (https://github.com/microsoft/vscode-docker)
export interface DeployImageToAcaOptionsContract {
image: string;
registryName: string;
username?: string;
secret?: string;
}

export interface DeployWorkspaceProjectOptionsContract {
// Existing resources
subscriptionId?: string;
Expand All @@ -19,6 +28,7 @@ export interface DeployWorkspaceProjectOptionsContract {
dockerfilePath?: string;

// Options
suppressRegistryPrompt?: boolean;
suppressConfirmation?: boolean; // Suppress any [resource] confirmation prompts
suppressContainerAppCreation?: boolean;
shouldSaveDeploySettings?: boolean;
Expand Down
12 changes: 12 additions & 0 deletions src/commands/deployContainerApp/deployContainerApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { KnownActiveRevisionsMode } from "@azure/arm-appcontainers";
import { type ResourceGroup } from "@azure/arm-resources";
import { LocationListStep, ResourceGroupListStep } from "@microsoft/vscode-azext-azureutils";
import { activityInfoIcon, activitySuccessContext, AzureWizard, createSubscriptionContext, createUniversallyUniqueContextValue, GenericTreeItem, nonNullProp, nonNullValue, type IActionContext, type ISubscriptionActionContext, type ISubscriptionContext } from "@microsoft/vscode-azext-utils";
Expand All @@ -16,15 +17,25 @@ import { getVerifyProvidersStep } from "../../utils/getVerifyProvidersStep";
import { localize } from "../../utils/localize";
import { pickContainerApp } from "../../utils/pickItem/pickContainerApp";
import { deployWorkspaceProject } from "../deployWorkspaceProject/deployWorkspaceProject";
import { editContainerCommandName } from "../editContainer/editContainer";
import { ContainerAppUpdateStep } from "../image/imageSource/ContainerAppUpdateStep";
import { ImageSourceListStep } from "../image/imageSource/ImageSourceListStep";
import { type ContainerAppDeployContext } from "./ContainerAppDeployContext";

const deployContainerAppCommandName: string = localize('deployContainerApp', 'Deploy to Container App...');

export async function deployContainerApp(context: IActionContext, node?: ContainerAppItem): Promise<void> {
const item: ContainerAppItem = node ?? await pickContainerApp(context);
const subscriptionContext: ISubscriptionContext = createSubscriptionContext(item.subscription);
const subscriptionActionContext: ISubscriptionActionContext = { ...context, ...subscriptionContext };

if (item.containerApp.revisionsMode === KnownActiveRevisionsMode.Multiple) {
throw new Error(localize('multipleRevisionsNotSupported', 'The container app cannot be updated using "{0}" while in multiple revisions mode. Navigate to the revision\'s container and execute "{1}" instead.', deployContainerAppCommandName, editContainerCommandName));
}
if ((item.containerApp.template?.containers?.length ?? 0) > 1) {
throw new Error(localize('multipleContainersNotSupported', 'The container app cannot be updated using "{0}" while having more than one active container. Navigate to the specific container instance and execute "{1}" instead.', deployContainerAppCommandName, editContainerCommandName));
}

// Prompt for image source before initializing the wizard in case we need to redirect the call to 'deployWorkspaceProject' instead
const imageSource: ImageSource = await promptImageSource(subscriptionActionContext);
if (imageSource === ImageSource.RemoteAcrBuild) {
Expand All @@ -40,6 +51,7 @@ export async function deployContainerApp(context: IActionContext, node?: Contain
managedEnvironment: await getManagedEnvironmentFromContainerApp(subscriptionActionContext, item.containerApp),
imageSource,
};
wizardContext.telemetry.properties.revisionMode = item.containerApp.revisionsMode;

if (isAzdExtensionInstalled()) {
wizardContext.telemetry.properties.isAzdExtensionInstalled = 'true';
Expand Down
12 changes: 12 additions & 0 deletions src/commands/deployImage/DeployImageContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { type Template } from "@azure/arm-appcontainers";
import { type DeployImageApiContext } from "../api/deployImageApi/DeployImageApiContext";

export interface DeployImageContext extends DeployImageApiContext {
containersIdx: number;
template: Template;
}
91 changes: 91 additions & 0 deletions src/commands/deployImage/deployImage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { type ResourceGroup } from "@azure/arm-resources";
import { LocationListStep, ResourceGroupListStep } from "@microsoft/vscode-azext-azureutils";
import { activityInfoIcon, activitySuccessContext, AzureWizard, createSubscriptionContext, createUniversallyUniqueContextValue, GenericTreeItem, nonNullValue, nonNullValueAndProp, type IActionContext, type ISubscriptionContext } from "@microsoft/vscode-azext-utils";
import { ext } from "../../extensionVariables";
import { type ContainerItem } from "../../tree/containers/ContainerItem";
import { createActivityContext } from "../../utils/activityUtils";
import { isAzdExtensionInstalled } from "../../utils/azdUtils";
import { getManagedEnvironmentFromContainerApp } from "../../utils/getResourceUtils";
import { getVerifyProvidersStep } from "../../utils/getVerifyProvidersStep";
import { localize } from "../../utils/localize";
import { getParentResource } from "../../utils/revisionDraftUtils";
import { ContainerAppOverwriteConfirmStep } from "../ContainerAppOverwriteConfirmStep";
import { showContainerAppNotification } from "../createContainerApp/showContainerAppNotification";
import { ContainerAppUpdateStep } from "../image/imageSource/ContainerAppUpdateStep";
import { ImageSourceListStep } from "../image/imageSource/ImageSourceListStep";
import { type ContainerRegistryImageSourceContext } from "../image/imageSource/containerRegistry/ContainerRegistryImageSourceContext";
import { type DeployImageContext } from "./DeployImageContext";

export async function deployImage(context: IActionContext & Partial<ContainerRegistryImageSourceContext>, node: ContainerItem): Promise<void> {
const { subscription, containerApp } = node;
const subscriptionContext: ISubscriptionContext = createSubscriptionContext(subscription);

const wizardContext: DeployImageContext = {
...context,
...subscriptionContext,
...await createActivityContext(true),
subscription,
managedEnvironment: await getManagedEnvironmentFromContainerApp({ ...context, ...subscriptionContext }, containerApp),
containerApp,
containersIdx: node.containersIdx,
template: nonNullValueAndProp(getParentResource(containerApp, node.revision), 'template'),
};

if (isAzdExtensionInstalled()) {
wizardContext.telemetry.properties.isAzdExtensionInstalled = 'true';
}

const resourceGroups: ResourceGroup[] = await ResourceGroupListStep.getResourceGroups(wizardContext);
wizardContext.resourceGroup = nonNullValue(
resourceGroups.find(rg => rg.name === containerApp.resourceGroup),
localize('containerAppResourceGroup', 'Expected to find the container app\'s resource group.'),
);

// Log resource group
wizardContext.activityChildren?.push(
new GenericTreeItem(undefined, {
contextValue: createUniversallyUniqueContextValue(['useExistingResourceGroupInfoItem', activitySuccessContext]),
label: localize('useResourceGroup', 'Using resource group "{0}"', wizardContext.resourceGroup.name),
iconPath: activityInfoIcon
})
);
ext.outputChannel.appendLog(localize('usingResourceGroup', 'Using resource group "{0}".', wizardContext.resourceGroup.name));

// Log container app
wizardContext.activityChildren?.push(
new GenericTreeItem(undefined, {
contextValue: createUniversallyUniqueContextValue(['useExistingContainerAppInfoItem', activitySuccessContext]),
label: localize('useContainerApp', 'Using container app "{0}"', wizardContext.containerApp?.name),
iconPath: activityInfoIcon
})
);
ext.outputChannel.appendLog(localize('usingContainerApp', 'Using container app "{0}".', wizardContext.containerApp?.name));

await LocationListStep.setLocation(wizardContext, containerApp.location);

const parentResourceName: string = getParentResource(containerApp, node.revision).name ?? containerApp.name;
const wizard: AzureWizard<DeployImageContext> = new AzureWizard(wizardContext, {
title: localize('deployImageTitle', 'Deploy image to "{0}"', parentResourceName),
promptSteps: [
new ImageSourceListStep(),
new ContainerAppOverwriteConfirmStep(),
],
executeSteps: [
getVerifyProvidersStep<DeployImageContext>(),
new ContainerAppUpdateStep(),
],
showLoadingPrompt: true
});

await wizard.prompt();
await wizard.execute();

if (!wizardContext.suppressNotification) {
void showContainerAppNotification(containerApp, true /** isUpdate */);
}
}
Loading