Skip to content

Commit

Permalink
Improve output and activity logs for deployContainerApp (#806)
Browse files Browse the repository at this point in the history
  • Loading branch information
MicroFish91 authored Dec 20, 2024
1 parent 3141e01 commit 77ce544
Show file tree
Hide file tree
Showing 18 changed files with 171 additions and 69 deletions.
23 changes: 22 additions & 1 deletion src/commands/deployContainerApp/deployContainerApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@

import { type ResourceGroup } from "@azure/arm-resources";
import { LocationListStep, ResourceGroupListStep } from "@microsoft/vscode-azext-azureutils";
import { AzureWizard, createSubscriptionContext, nonNullProp, nonNullValue, type IActionContext, type ISubscriptionActionContext, type ISubscriptionContext } from "@microsoft/vscode-azext-utils";
import { activityInfoIcon, activitySuccessContext, AzureWizard, createSubscriptionContext, createUniversallyUniqueContextValue, GenericTreeItem, nonNullProp, nonNullValue, type IActionContext, type ISubscriptionActionContext, type ISubscriptionContext } from "@microsoft/vscode-azext-utils";
import { ImageSource } from "../../constants";
import { ext } from "../../extensionVariables";
import { type ContainerAppItem } from "../../tree/ContainerAppItem";
import { createActivityContext } from "../../utils/activityUtils";
import { isAzdExtensionInstalled } from "../../utils/azdUtils";
Expand Down Expand Up @@ -50,6 +51,26 @@ export async function deployContainerApp(context: IActionContext, node?: Contain
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, item.containerApp.location);

const wizard: AzureWizard<ContainerAppDeployContext> = new AzureWizard(wizardContext, {
Expand Down
19 changes: 5 additions & 14 deletions src/commands/image/deployImageApi/deployImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@
* Licensed under the MIT License. See License.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { AzureWizard, createSubscriptionContext, type AzureWizardPromptStep, type IActionContext, type ISubscriptionContext } from "@microsoft/vscode-azext-utils";
import { AzureWizard, createSubscriptionContext, type IActionContext, type ISubscriptionContext } from "@microsoft/vscode-azext-utils";
import { type ContainerAppItem } from "../../../tree/ContainerAppItem";
import { createActivityContext } from "../../../utils/activityUtils";
import { getManagedEnvironmentFromContainerApp } from "../../../utils/getResourceUtils";
import { getVerifyProvidersStep } from "../../../utils/getVerifyProvidersStep";
import { getImageNameWithoutTag } from "../../../utils/imageNameUtils";
import { localize } from "../../../utils/localize";
import { ContainerAppOverwriteConfirmStep } from "../../ContainerAppOverwriteConfirmStep";
import { showContainerAppNotification } from "../../createContainerApp/showContainerAppNotification";
import { IngressPromptStep } from "../../ingress/IngressPromptStep";
import { ContainerAppUpdateStep } from "../imageSource/ContainerAppUpdateStep";
import { ImageSourceListStep } from "../imageSource/ImageSourceListStep";
import { type ContainerRegistryImageSourceContext } from "../imageSource/containerRegistry/ContainerRegistryImageSourceContext";
Expand All @@ -33,19 +31,12 @@ export async function deployImage(context: IActionContext & Partial<ContainerReg

wizardContext.telemetry.properties.revisionMode = containerApp.revisionsMode;

const promptSteps: AzureWizardPromptStep<DeployImageApiContext>[] = [
new ImageSourceListStep(),
];

// If more than the image tag changed, prompt for ingress again
if (getImageNameWithoutTag(wizardContext.containerApp?.template?.containers?.[0].image ?? '') !== getImageNameWithoutTag(wizardContext.image ?? '')) {
promptSteps.push(new IngressPromptStep());
}
promptSteps.push(new ContainerAppOverwriteConfirmStep());

const wizard: AzureWizard<DeployImageApiContext> = new AzureWizard(wizardContext, {
title: localize('deploy', 'Deploy image to container app "{0}"', containerApp.name),
promptSteps,
promptSteps: [
new ImageSourceListStep(),
new ContainerAppOverwriteConfirmStep(),
],
executeSteps: [
getVerifyProvidersStep<DeployImageApiContext>(),
new ContainerAppUpdateStep(),
Expand Down
16 changes: 14 additions & 2 deletions src/commands/image/imageSource/ContainerAppUpdateStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
*--------------------------------------------------------------------------------------------*/

import { type Ingress } from "@azure/arm-appcontainers";
import { AzureWizardExecuteStep, GenericParentTreeItem, GenericTreeItem, activityFailContext, activityFailIcon, activitySuccessContext, activitySuccessIcon, createUniversallyUniqueContextValue, nonNullProp, type ExecuteActivityOutput } from "@microsoft/vscode-azext-utils";
import { activityFailContext, activityFailIcon, activitySuccessContext, activitySuccessIcon, AzureWizardExecuteStep, createUniversallyUniqueContextValue, GenericParentTreeItem, GenericTreeItem, nonNullProp, type ExecuteActivityOutput } from "@microsoft/vscode-azext-utils";
import * as retry from "p-retry";
import { type Progress } from "vscode";
import { ext } from "../../../extensionVariables";
import { getContainerEnvelopeWithSecrets, type ContainerAppModel } from "../../../tree/ContainerAppItem";
import { localize } from "../../../utils/localize";
import { type IngressContext } from "../../ingress/IngressContext";
import { enabledIngressDefaults } from "../../ingress/enableIngress/EnableIngressStep";
import { DisableIngressStep } from "../../ingress/disableIngress/DisableIngressStep";
import { enabledIngressDefaults, EnableIngressStep } from "../../ingress/enableIngress/EnableIngressStep";
import { RegistryCredentialType } from "../../registryCredentials/RegistryCredentialsAddConfigurationListStep";
import { updateContainerApp } from "../../updateContainerApp";
import { type ImageSourceContext } from "./ImageSourceContext";
Expand Down Expand Up @@ -41,6 +42,17 @@ export class ContainerAppUpdateStep<T extends ImageSourceContext & IngressContex
ingress = containerAppEnvelope.configuration.ingress;
}

// Display ingress log outputs
if (ingress) {
const { item, message } = EnableIngressStep.createSuccessOutput({ ...context, enableExternal: ingress.external, targetPort: ingress.targetPort });
item && context.activityChildren?.push(item);
message && ext.outputChannel.appendLog(message);
} else {
const { item, message } = DisableIngressStep.createSuccessOutput({ ...context, enableIngress: false });
item && context.activityChildren?.push(item);
message && ext.outputChannel.appendLog(message);
}

containerAppEnvelope.configuration.ingress = ingress;
containerAppEnvelope.configuration.secrets = context.secrets;
containerAppEnvelope.configuration.registries = context.registryCredentials;
Expand Down
19 changes: 14 additions & 5 deletions src/commands/image/imageSource/EnvFileListStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,18 +121,27 @@ export class EnvFileListStep<T extends EnvFileListContext> extends AzureWizardPr

const logMessage: string = localize('skippedEnvVarsMessage',
'Skipped environment variable configuration for the container app' +
(setEnvironmentVariableOption === SetEnvironmentVariableOption.NoDotEnv ? ' because no .env files were detected. ' : '. ')
(setEnvironmentVariableOption === SetEnvironmentVariableOption.NoDotEnv ? ' because no .env files were detected.' : '.')
);
ext.outputChannel.appendLog(logMessage);
} else {
} else if (setEnvironmentVariableOption === SetEnvironmentVariableOption.ProvideFile) {
context.activityChildren?.push(
new GenericTreeItem(undefined, {
contextValue: createUniversallyUniqueContextValue(['envFileListStepSuccessItem', setEnvironmentVariableOption, activitySuccessContext]),
label: localize('saveEnvVarsLabel', 'Save environment variable configuration'),
contextValue: createUniversallyUniqueContextValue(['environmentVariablesListStepSuccessItem', activitySuccessContext]),
label: localize('saveEnvVarsFileLabel', 'Save environment variables using provided .env file'),
iconPath: activitySuccessIcon
})
);
ext.outputChannel.appendLog(localize('savedEnvVarsFileMessage', 'Saved environment variables using provided .env file "{0}".', context.envPath));
} else if (setEnvironmentVariableOption === SetEnvironmentVariableOption.UseExisting) {
context.activityChildren?.push(
new GenericTreeItem(undefined, {
contextValue: createUniversallyUniqueContextValue(['environmentVariablesListStepSuccessItem', activitySuccessContext]),
label: localize('useExistingEnvVarsLabel', 'Use existing environment variable configuration'),
iconPath: activitySuccessIcon
})
);
ext.outputChannel.appendLog(localize('savedEnvVarsMessage', 'Saved environment variable configuration.'));
ext.outputChannel.appendLog(localize('useExistingEnvVarsMessage', 'Used existing environment variable configuration.'));
}
}
}
1 change: 1 addition & 0 deletions src/commands/image/imageSource/ImageSourceContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface ImageSourceBaseContext extends RegistryCredentialsContext, ICon
imageSource?: ImageSource;
showQuickStartImage?: boolean;

containersIdx?: number;
image?: string;

envPath?: string;
Expand Down
24 changes: 20 additions & 4 deletions src/commands/image/imageSource/ImageSourceListStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,29 @@ export class ImageSourceListStep extends AzureWizardPromptStep<ImageSourceContex
context.telemetry.properties.imageSource = ImageSource.QuickstartImage;
break;
case ImageSource.ContainerRegistry:
promptSteps.push(new ContainerRegistryListStep(), new RegistryCredentialsAddConfigurationListStep());
executeSteps.push(new ContainerRegistryImageConfigureStep());
promptSteps.push(
new ContainerRegistryListStep(),
new RegistryCredentialsAddConfigurationListStep(),
new ContainerRegistryImageConfigureStep(),
);
context.telemetry.properties.imageSource = ImageSource.ContainerRegistry;
break;
case ImageSource.RemoteAcrBuild:
promptSteps.push(new RootFolderStep(), new DockerfileItemStep(), new SourcePathStep(), new AcrListStep(), new RegistryCredentialsAddConfigurationListStep(), new ImageNameStep(), new OSPickStep());
executeSteps.push(new TarFileStep(), new UploadSourceCodeStep(), new RunStep(), new BuildImageStep(), new ContainerRegistryImageConfigureStep());
promptSteps.push(
new RootFolderStep(),
new DockerfileItemStep(),
new SourcePathStep(),
new AcrListStep(),
new RegistryCredentialsAddConfigurationListStep(),
new ImageNameStep(),
new OSPickStep(),
);
executeSteps.push(
new TarFileStep(),
new UploadSourceCodeStep(),
new RunStep(),
new BuildImageStep(),
);
context.telemetry.properties.imageSource = ImageSource.RemoteAcrBuild;
break;
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,48 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { parseImageName } from "../../../../utils/imageNameUtils";
import { activitySuccessContext, activitySuccessIcon, AzureWizardPromptStep, createUniversallyUniqueContextValue, GenericTreeItem, type IWizardOptions } from "@microsoft/vscode-azext-utils";
import { ext } from "../../../../extensionVariables";
import { getImageNameWithoutTag, parseImageName } from "../../../../utils/imageNameUtils";
import { localize } from "../../../../utils/localize";
import { AzureWizardActivityOutputExecuteStep } from "../../../AzureWizardActivityOutputExecuteStep";
import { IngressPromptStep } from "../../../ingress/IngressPromptStep";
import { type ContainerRegistryImageSourceContext } from "./ContainerRegistryImageSourceContext";
import { getLoginServer } from "./getLoginServer";

export class ContainerRegistryImageConfigureStep<T extends ContainerRegistryImageSourceContext> extends AzureWizardActivityOutputExecuteStep<T> {
public priority: number = 570;
public stepName: string = 'containerRegistryImageConfigureStep';
protected getSuccessString = (context: T) => localize('successOutput', 'Successfully set container image to "{0}".', context.image);
protected getFailString = (context: T) => localize('failOutput', 'Failed to set container image to "{0}".', context.image);
protected getTreeItemLabel = (context: T) => localize('treeItemLabel', 'Set container image to "{0}"', context.image);

public async execute(context: T): Promise<void> {
context.image = `${getLoginServer(context)}/${context.repositoryName}:${context.tag}`;
export class ContainerRegistryImageConfigureStep<T extends ContainerRegistryImageSourceContext> extends AzureWizardPromptStep<T> {
public async configureBeforePrompt(context: T): Promise<void> {
context.image ||= `${getLoginServer(context)}/${context.repositoryName}:${context.tag}`;

const { registryName, registryDomain } = parseImageName(context.image);
context.telemetry.properties.registryName = registryName;
context.telemetry.properties.registryDomain = registryDomain ?? 'other';

// Output logs
context.activityChildren?.push(
new GenericTreeItem(undefined, {
contextValue: createUniversallyUniqueContextValue(['containerRegistryImageConfigureStepItem', activitySuccessContext]),
label: localize('configureTargetImageLabel', 'Configure target image "{0}"', context.image),
iconPath: activitySuccessIcon
})
);
ext.outputChannel.appendLog(localize('configureTargetImageMessage', 'Configured target image "{0}".', context.image));
}

public async prompt(): Promise<void> {
// Don't prompt, just need to use the subwizard
}

public shouldPrompt(): boolean {
return false;
}

public shouldExecute(context: T): boolean {
return !context.image;
public async getSubWizard(context: T): Promise<IWizardOptions<T> | undefined> {
// If more than the image tag changed, prompt for ingress again
if (getImageNameWithoutTag(context.containerApp?.template?.containers?.[context.containersIdx ?? 0].image ?? '') !== getImageNameWithoutTag(context.image ?? '')) {
return {
promptSteps: [new IngressPromptStep()],
};
}
return undefined;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class RegistryImageInputStep extends AzureWizardPromptStep<ContainerRegis
// Try to suggest an image name only when the user is deploying to a Container App
let value: string | undefined;
if (context.containerApp) {
const { registryDomain, imageNameReference } = parseImageName(getLatestContainerAppImage(context.containerApp));
const { registryDomain, imageNameReference } = parseImageName(getLatestContainerAppImage(context.containerApp, context.containersIdx ?? 0));

// Only bother carrying over the suggestion if the old image was from a third party registry
if (registryDomain !== acrDomain && imageNameReference !== quickStartImageName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export class AcrListStep extends AzureWizardPromptStep<ContainerRegistryImageSou
let suggestedRegistry: string | undefined;
let srExists: boolean = false;
if (context.containerApp) {
const { registryDomain, registryName, imageNameReference } = parseImageName(getLatestContainerAppImage(context.containerApp));
const { registryDomain, registryName, imageNameReference } = parseImageName(getLatestContainerAppImage(context.containerApp, context.containersIdx ?? 0));

// If the image is not the default quickstart image, then we can try to suggest a registry based on the latest Container App image
if (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class AcrRepositoriesListStep extends RegistryRepositoriesListStepBase {
let suggestedRepository: string | undefined;
let srExists: boolean = false;
if (context.containerApp) {
const { registryDomain, registryName, repositoryName } = parseImageName(getLatestContainerAppImage(context.containerApp));
const { registryDomain, registryName, repositoryName } = parseImageName(getLatestContainerAppImage(context.containerApp, context.containersIdx ?? 0));
if (
context.containerApp.revisionsMode === KnownActiveRevisionsMode.Single &&
registryDomain === acrDomain &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class DockerHubContainerRepositoryListStep extends RegistryRepositoriesLi
let suggestedRepository: string | undefined;
let srExists: boolean = false;
if (context.containerApp) {
const { registryDomain, namespace, repositoryName } = parseImageName(getLatestContainerAppImage(context.containerApp));
const { registryDomain, namespace, repositoryName } = parseImageName(getLatestContainerAppImage(context.containerApp, context.containersIdx ?? 0));
if (
context.containerApp.revisionsMode === KnownActiveRevisionsMode.Single &&
registryDomain === dockerHubDomain &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export class DockerHubNamespaceInputStep extends AzureWizardPromptStep<Container
private getSuggestedNamespace(context: ContainerRegistryImageSourceContext): string {
let suggestedNamespace: string | undefined;
if (context.containerApp) {
const { registryDomain, namespace } = parseImageName(getLatestContainerAppImage(context.containerApp));
const { registryDomain, namespace } = parseImageName(getLatestContainerAppImage(context.containerApp, context.containersIdx ?? 0));
if (context.containerApp.revisionsMode === KnownActiveRevisionsMode.Single && registryDomain === dockerHubDomain) {
suggestedNamespace = namespace;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import { type ContainerApp } from "@azure/arm-appcontainers";

export function getLatestContainerAppImage(containerApp: ContainerApp): string | undefined {
// We are currently only supporting one active container image per app
return containerApp.template?.containers?.[0]?.image;
export function getLatestContainerAppImage(containerApp: ContainerApp, containersIdx: number): string | undefined {
return containerApp.template?.containers?.[containersIdx]?.image;
}
Loading

0 comments on commit 77ce544

Please sign in to comment.