Skip to content

Commit

Permalink
Support deployment to Azure Container Apps with ACA extension (#3794)
Browse files Browse the repository at this point in the history
  • Loading branch information
bwateratmsft authored Feb 6, 2023
1 parent 64a084f commit eff6906
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 3 deletions.
14 changes: 13 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
"onCommand:vscode-docker.registries.copyRemoteFullTag",
"onCommand:vscode-docker.registries.deleteImage",
"onCommand:vscode-docker.registries.deployImageToAzure",
"onCommand:vscode-docker.registries.deployImageToAca",
"onCommand:vscode-docker.registries.disconnectRegistry",
"onCommand:vscode-docker.registries.dockerHub.openInBrowser",
"onCommand:vscode-docker.registries.help",
Expand Down Expand Up @@ -584,6 +585,11 @@
"when": "view == dockerRegistries && viewItem =~ /(DockerV2|DockerHubV2);Tag;/ && isAzureAccountInstalled",
"group": "regs_tag_1_general@4"
},
{
"command": "vscode-docker.registries.deployImageToAca",
"when": "view == dockerRegistries && viewItem =~ /(DockerV2|DockerHubV2|GitLabV4);Tag;/ && isAzureAccountInstalled",
"group": "regs_tag_1_general@6"
},
{
"command": "vscode-docker.registries.azure.untagImage",
"when": "view == dockerRegistries && viewItem == azure;DockerV2;Tag;",
Expand Down Expand Up @@ -2594,6 +2600,11 @@
"title": "%vscode-docker.commands.registries.deployImageToAzure%",
"category": "%vscode-docker.commands.category.dockerRegistries%"
},
{
"command": "vscode-docker.registries.deployImageToAca",
"title": "%vscode-docker.commands.registries.deployImageToAca%",
"category": "%vscode-docker.commands.category.dockerRegistries%"
},
{
"command": "vscode-docker.registries.disconnectRegistry",
"title": "%vscode-docker.commands.registries.disconnectRegistry%",
Expand Down Expand Up @@ -2869,7 +2880,8 @@
"id": "azDeploy",
"title": "%vscode-docker.walkthrough.dockerStart.azDeploy.title%",
"completionEvents": [
"onCommand:vscode-docker.registries.deployImageToAzure"
"onCommand:vscode-docker.registries.deployImageToAzure",
"onCommand:vscode-docker.registries.deployImageToAca"
],
"description": "%vscode-docker.walkthrough.dockerStart.azDeploy.description%",
"when": "isAzureAccountInstalled",
Expand Down
1 change: 1 addition & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@
"vscode-docker.commands.registries.copyRemoteFullTag": "Copy Full Tag",
"vscode-docker.commands.registries.deleteImage": "Delete Image...",
"vscode-docker.commands.registries.deployImageToAzure": "Deploy Image to Azure App Service...",
"vscode-docker.commands.registries.deployImageToAca": "Deploy Image to Azure Container Apps...",
"vscode-docker.commands.registries.disconnectRegistry": "Disconnect",
"vscode-docker.commands.registries.dockerHub.openInBrowser": "Open in Browser",
"vscode-docker.commands.registries.help": "Registries Help",
Expand Down
2 changes: 2 additions & 0 deletions src/commands/registerCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import { registerWorkspaceCommand } from "./registerWorkspaceCommand";
import { createAzureRegistry } from "./registries/azure/createAzureRegistry";
import { deleteAzureRegistry } from "./registries/azure/deleteAzureRegistry";
import { deleteAzureRepository } from "./registries/azure/deleteAzureRepository";
import { deployImageToAca } from "./registries/azure/deployImageToAca";
import { deployImageToAzure } from "./registries/azure/deployImageToAzure";
import { openInAzurePortal } from "./registries/azure/openInAzurePortal";
import { buildImageInAzure } from "./registries/azure/tasks/buildImageInAzure";
Expand Down Expand Up @@ -167,6 +168,7 @@ export function registerCommands(): void {
registerCommand('vscode-docker.registries.copyRemoteFullTag', copyRemoteFullTag);
registerCommand('vscode-docker.registries.deleteImage', deleteRemoteImage);
registerCommand('vscode-docker.registries.deployImageToAzure', deployImageToAzure);
registerCommand('vscode-docker.registries.deployImageToAca', deployImageToAca);
registerCommand('vscode-docker.registries.disconnectRegistry', disconnectRegistry);
registerCommand('vscode-docker.registries.help', registryHelp);
registerWorkspaceCommand('vscode-docker.registries.logInToDockerCli', logInToDockerCli);
Expand Down
109 changes: 109 additions & 0 deletions src/commands/registries/azure/deployImageToAca.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as semver from 'semver';
import * as vscode from 'vscode';
import { DialogResponses, IActionContext, nonNullProp, UserCancelledError } from '@microsoft/vscode-azext-utils';
import { RemoteTagTreeItem } from '../../../tree/registries/RemoteTagTreeItem';
import { localize } from '../../../localize';
import { ext } from '../../../extensionVariables';
import { registryExpectedContextValues } from '../../../tree/registries/registryContextValues';
import { RegistryTreeItemBase } from '../../../tree/registries/RegistryTreeItemBase';
import { AzureRegistryTreeItem } from '../../../tree/registries/azure/AzureRegistryTreeItem';
import { DockerHubNamespaceTreeItem } from '../../../tree/registries/dockerHub/DockerHubNamespaceTreeItem';
import { addImageTaggingTelemetry } from '../../images/tagImage';
import { parseDockerLikeImageName } from '../../../runtimes/docker/clients/DockerClientBase/parseDockerLikeImageName';

const acaExtensionId = 'ms-azuretools.vscode-azurecontainerapps';
const minimumAcaExtensionVersion = '0.4.0';

// The interface of the command options passed to the Azure Container Apps extension's deployImageToAca command
interface DeployImageToAcaOptionsContract {
image: string;
registryName: string;
username?: string;
secret?: string;
}

export async function deployImageToAca(context: IActionContext, node?: RemoteTagTreeItem): Promise<void> {
// Assert installation of the ACA extension
if (!isAcaExtensionInstalled()) {
// This will always throw a `UserCancelledError` but with the appropriate step name
// based on user choice about installation
await openAcaInstallPage(context);
}

if (!node) {
node = await ext.registriesTree.showTreeItemPicker<RemoteTagTreeItem>([registryExpectedContextValues.dockerHub.tag, registryExpectedContextValues.dockerV2.tag], context);
}

const commandOptions: Partial<DeployImageToAcaOptionsContract> = {
image: node.fullTag,
};

addImageTaggingTelemetry(context, commandOptions.image, '');

const registry: RegistryTreeItemBase = node.parent.parent;
if (registry instanceof AzureRegistryTreeItem) {
// No additional work to do; ACA can handle this on its own
} else {
const { auth } = await registry.getDockerCliCredentials() as { auth?: { username?: string, password?: string } };

if (!auth?.username || !auth?.password) {
throw new Error(localize('vscode-docker.commands.registries.azure.deployImageToAca.noCredentials', 'No credentials found for registry "{0}".', registry.label));
}

if (registry instanceof DockerHubNamespaceTreeItem) {
// Ensure Docker Hub images are prefixed with 'docker.io/...'
if (!/^docker\.io\//i.test(commandOptions.image)) {
commandOptions.image = 'docker.io/' + commandOptions.image;
}
}

commandOptions.username = auth.username;
commandOptions.secret = auth.password;
}

commandOptions.registryName = nonNullProp(parseDockerLikeImageName(commandOptions.image), 'registry');

// Don't wait
void vscode.commands.executeCommand('containerApps.deployImageApi', commandOptions);
}

function isAcaExtensionInstalled(): boolean {
const acaExtension = vscode.extensions.getExtension(acaExtensionId);

if (!acaExtension?.packageJSON?.version) {
// If the ACA extension is not present, or the package JSON didn't come through, or the version is not present, then it's not installed
return false;
}

const acaVersion = semver.coerce(acaExtension.packageJSON.version);
const minVersion = semver.coerce(minimumAcaExtensionVersion);

return semver.gte(acaVersion, minVersion);
}

async function openAcaInstallPage(context: IActionContext): Promise<void> {
const message = localize(
'vscode-docker.commands.registries.azure.deployImageToAca.installAcaExtension',
'Version {0} or higher of the Azure Container Apps extension is required to deploy to Azure Container Apps. Would you like to install it now?',
minimumAcaExtensionVersion
);

const installButton: vscode.MessageItem = {
title: localize('vscode-docker.commands.registries.azure.deployImageToAca.install', 'Install'),
};

const response = await context.ui.showWarningMessage(message, { modal: true }, installButton, DialogResponses.cancel);

if (response !== installButton) {
throw new UserCancelledError('installAcaExtensionDeclined');
}

await vscode.commands.executeCommand('extension.open', acaExtensionId);

throw new UserCancelledError('installAcaExtensionOpened');
}
7 changes: 5 additions & 2 deletions src/scaffolding/copyWizardContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ export function copyWizardContext(wizardContext: Partial<ScaffoldingWizardContex
}

for (const prop of Object.keys(priorWizardContext)) {
// Skip telemetry + error handling
if (prop === 'errorHandling' || prop === 'telemetry') {
// Skip properties from IActionContext
if (prop === 'telemetry' ||
prop === 'errorHandling' ||
prop === 'ui' ||
prop === 'valuesToMask') {
continue;
}

Expand Down

0 comments on commit eff6906

Please sign in to comment.