Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 10 additions & 20 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,6 @@
"title": "%containerApps.editContainerApp%",
"category": "Azure Container Apps"
},
{
"command": "containerApps.updateImage",
"title": "%containerApps.updateImage%",
"category": "Azure Container Apps"
},
{
"command": "containerApps.deployImageApi",
"title": "%containerApps.deployImageApi%",
Expand Down Expand Up @@ -203,6 +198,11 @@
"title": "%containerApps.openConsoleInPortal%",
"category": "Azure Container Apps"
},
{
"command": "containerApps.editContainer",
"title": "%containerApps.editContainer%",
"category": "Azure Container Apps"
},
{
"command": "containerApps.editScaleRange",
"title": "%containerApps.editScaleRange%",
Expand Down Expand Up @@ -373,11 +373,6 @@
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /containerAppItem(.*)revisionMode:single(.*)unsavedChanges:true/i",
"group": "3@2"
},
{
"command": "containerApps.updateImage",
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /containerAppItem(.*)revisionMode:single/i",
"group": "4@1"
},
{
"command": "containerApps.editContainerApp",
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /containerAppItem(.*)revisionMode:single/i",
Expand Down Expand Up @@ -438,11 +433,6 @@
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /revisionItem(.*)revisionState:active/i",
"group": "2@2"
},
{
"command": "containerApps.updateImage",
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /revisionDraft:false(.*)revisionItem/i",
"group": "3@1"
},
{
"command": "containerApps.deployRevisionDraft",
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /revisionDraftItem(.*)unsavedChanges:true/i",
Expand All @@ -463,16 +453,16 @@
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /revisionDraftItem/i",
"group": "1@2"
},
{
"command": "containerApps.updateImage",
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /revisionDraftItem/i",
"group": "2@1"
},
{
"command": "containerApps.editRevisionDraft",
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /revisionDraftItem/i",
"group": "3@1"
},
{
"command": "containerApps.editContainer",
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /containerItem/i",
"group": "1@1"
},
{
"command": "containerApps.editScaleRange",
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /scaleItem/i",
Expand Down
2 changes: 1 addition & 1 deletion package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"containerApps.createContainerApp": "Create Container App...",
"containerApps.createContainerAppFromWorkspace": "Create Container App from Workspace...",
"containerApps.editContainerApp": "Edit Container App (Advanced)...",
"containerApps.updateImage": "Update Container Image...",
"containerApps.editContainer": "Edit Container...",
"containerApps.deployImageApi": "Deploy Image to Container App (API)...",
"containerApps.deployWorkspaceProject": "Deploy Project from Workspace...",
"containerApps.deployWorkspaceProjectApi": "Deploy Project from Workspace (API)...",
Expand Down
16 changes: 16 additions & 0 deletions src/commands/editContainer/ContainerEditContext.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 ExecuteActivityContext } from "@microsoft/vscode-azext-utils";
import { type SetTelemetryProps } from "../../telemetry/SetTelemetryProps";
import { type ContainerUpdateTelemetryProps as TelemetryProps } from "../../telemetry/commandTelemetryProps";
import { type IContainerAppContext } from "../IContainerAppContext";
import { type ImageSourceBaseContext } from "../image/imageSource/ImageSourceContext";

export interface ContainerEditBaseContext extends IContainerAppContext, ImageSourceBaseContext, ExecuteActivityContext {
containersIdx: number;
}

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

import { type Container, type Revision } from "@azure/arm-appcontainers";
import { activityFailContext, activityFailIcon, activityProgressContext, activityProgressIcon, activitySuccessContext, activitySuccessIcon, createUniversallyUniqueContextValue, GenericParentTreeItem, GenericTreeItem, nonNullProp, type ExecuteActivityOutput } from "@microsoft/vscode-azext-utils";
import { type Progress } from "vscode";
import { type ContainerAppItem, type ContainerAppModel } from "../../tree/ContainerAppItem";
import { type RevisionsItemModel } from "../../tree/revisionManagement/RevisionItem";
import { localize } from "../../utils/localize";
import { getParentResourceFromItem } from "../../utils/revisionDraftUtils";
import { getContainerNameForImage } from "../image/imageSource/containerRegistry/getContainerNameForImage";
import { RevisionDraftUpdateBaseStep } from "../revisionDraft/RevisionDraftUpdateBaseStep";
import { type ContainerEditContext } from "./ContainerEditContext";

export class ContainerEditDraftStep<T extends ContainerEditContext> extends RevisionDraftUpdateBaseStep<T> {
public priority: number = 590;

constructor(baseItem: ContainerAppItem | RevisionsItemModel) {
super(baseItem);
}

public async execute(context: T, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise<void> {
progress.report({ message: localize('editingContainer', 'Editing container (draft)...') });
this.revisionDraftTemplate.containers ??= [];

const container: Container = this.revisionDraftTemplate.containers[context.containersIdx] ?? {};
container.name = getContainerNameForImage(nonNullProp(context, 'image'));
container.image = context.image;
container.env = context.environmentVariables;

await this.updateRevisionDraftWithTemplate(context);
}

public shouldExecute(context: T): boolean {
return context.containersIdx !== undefined && !!context.image;
}

public createSuccessOutput(): ExecuteActivityOutput {
const parentResource: ContainerAppModel | Revision = getParentResourceFromItem(this.baseItem);
return {
item: new GenericTreeItem(undefined, {
contextValue: createUniversallyUniqueContextValue(['containerEditDraftStepSuccessItem', activitySuccessContext]),
label: localize('editContainer', 'Edit container profile for container app "{0}" (draft)', parentResource.name),
iconPath: activitySuccessIcon,
}),
message: localize('editContainerSuccess', 'Successfully edited container profile for container app "{0}" (draft).', parentResource.name),
};
}

public createProgressOutput(): ExecuteActivityOutput {
const parentResource: ContainerAppModel | Revision = getParentResourceFromItem(this.baseItem);
return {
item: new GenericTreeItem(undefined, {
contextValue: createUniversallyUniqueContextValue(['containerEditDraftStepProgressItem', activityProgressContext]),
label: localize('editContainer', 'Edit container profile for container app "{0}" (draft)', parentResource.name),
iconPath: activityProgressIcon,
}),
};
}

public createFailOutput(): ExecuteActivityOutput {
const parentResource: ContainerAppModel | Revision = getParentResourceFromItem(this.baseItem);
return {
item: new GenericParentTreeItem(undefined, {
contextValue: createUniversallyUniqueContextValue(['containerEditDraftStepFailItem', activityFailContext]),
label: localize('editContainer', 'Edit container profile for container app "{0}" (draft)', parentResource.name),
iconPath: activityFailIcon,
}),
message: localize('editContainerFail', 'Failed to edit container profile for container app "{0}" (draft).', parentResource.name),
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ import { type RegistryCredentials, type Secret } from "@azure/arm-appcontainers"
import { AzureWizardExecuteStep, nonNullProp } from "@microsoft/vscode-azext-utils";
import * as deepEqual from "deep-eql";
import { type Progress } from "vscode";
import { ext } from "../../../extensionVariables";
import { getContainerEnvelopeWithSecrets, type ContainerAppModel } from "../../../tree/ContainerAppItem";
import { localize } from "../../../utils/localize";
import { updateContainerApp } from "../../updateContainerApp";
import { type UpdateImageContext } from "./updateImage";
import { ext } from "../../extensionVariables";
import { getContainerEnvelopeWithSecrets, type ContainerAppModel } from "../../tree/ContainerAppItem";
import { localize } from "../../utils/localize";
import { updateContainerApp } from "../updateContainerApp";
import { type ContainerEditContext } from "./ContainerEditContext";

export class UpdateRegistryAndSecretsStep extends AzureWizardExecuteStep<UpdateImageContext> {
export class RegistryAndSecretsUpdateStep<T extends ContainerEditContext> extends AzureWizardExecuteStep<T> {
public priority: number = 580;

public async execute(context: UpdateImageContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise<void> {
public async execute(context: T, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise<void> {
const containerApp: ContainerAppModel = nonNullProp(context, 'containerApp');
const containerAppEnvelope = await getContainerEnvelopeWithSecrets(context, context.subscription, containerApp);

Expand All @@ -28,20 +28,17 @@ export class UpdateRegistryAndSecretsStep extends AzureWizardExecuteStep<UpdateI
context.telemetry.properties.skippedRegistryCredentialUpdate = 'true';
return;
}

context.telemetry.properties.skippedRegistryCredentialUpdate = 'false';

progress.report({ message: localize('configuringSecrets', 'Configuring registry secrets...') });

containerAppEnvelope.configuration.secrets = context.secrets;
containerAppEnvelope.configuration.registries = context.registryCredentials;

await updateContainerApp(context, context.subscription, containerAppEnvelope);

ext.outputChannel.appendLog(localize('updatedSecrets', 'Updated container app "{0}" with new registry secrets.', containerApp.name));
}

public shouldExecute(context: UpdateImageContext): boolean {
public shouldExecute(context: T): boolean {
return !!context.registryCredentials && !!context.secrets;
}

Expand Down
70 changes: 70 additions & 0 deletions src/commands/editContainer/editContainer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { type Revision } from "@azure/arm-appcontainers";
import { AzureWizard, createSubscriptionContext, type IActionContext, type ISubscriptionContext } from "@microsoft/vscode-azext-utils";
import { type ContainerAppModel } from "../../tree/ContainerAppItem";
import { type ContainerItem } from "../../tree/containers/ContainerItem";
import { ContainersItem } from "../../tree/containers/ContainersItem";
import { createActivityContext } from "../../utils/activityUtils";
import { getManagedEnvironmentFromContainerApp } from "../../utils/getResourceUtils";
import { getVerifyProvidersStep } from "../../utils/getVerifyProvidersStep";
import { localize } from "../../utils/localize";
import { pickContainer } from "../../utils/pickItem/pickContainer";
import { getParentResourceFromItem, isTemplateItemEditable, TemplateItemNotEditableError } from "../../utils/revisionDraftUtils";
import { ImageSourceListStep } from "../image/imageSource/ImageSourceListStep";
import { RevisionDraftDeployPromptStep } from "../revisionDraft/RevisionDraftDeployPromptStep";
import { type ContainerEditContext } from "./ContainerEditContext";
import { ContainerEditDraftStep } from "./ContainerEditDraftStep";
import { RegistryAndSecretsUpdateStep } from "./RegistryAndSecretsUpdateStep";

// Edits both the 'image' and 'environmentVariables' portion of the container profile (draft)
export async function editContainer(context: IActionContext, node?: ContainersItem | ContainerItem): Promise<void> {
const item: ContainerItem | ContainersItem = node ?? await pickContainer(context, { autoSelectDraft: true });
const { containerApp, subscription } = item;

if (!isTemplateItemEditable(item)) {
throw new TemplateItemNotEditableError(item);
}

const subscriptionContext: ISubscriptionContext = createSubscriptionContext(subscription);
const parentResource: ContainerAppModel | Revision = getParentResourceFromItem(item);

let containersIdx: number;
if (ContainersItem.isContainersItem(item)) {
// The 'editContainer' command should only show up on a 'ContainersItem' when it only has one container, else the command would show up on the 'ContainerItem'
containersIdx = 0;
} else {
containersIdx = item.containersIdx;
}

const wizardContext: ContainerEditContext = {
...context,
...subscriptionContext,
...await createActivityContext(true),
subscription,
managedEnvironment: await getManagedEnvironmentFromContainerApp({ ...context, ...subscriptionContext }, containerApp),
containerApp,
containersIdx,
};
wizardContext.telemetry.properties.revisionMode = containerApp.revisionsMode;

const wizard: AzureWizard<ContainerEditContext> = new AzureWizard(wizardContext, {
title: localize('editContainer', 'Edit container profile for container app "{0}" (draft)', parentResource.name),
promptSteps: [
new ImageSourceListStep(),
new RevisionDraftDeployPromptStep(),
],
executeSteps: [
getVerifyProvidersStep<ContainerEditContext>(),
new RegistryAndSecretsUpdateStep(),
new ContainerEditDraftStep(item),
],
showLoadingPrompt: true,
});

await wizard.prompt();
await wizard.execute();
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,28 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { AzureWizardExecuteStep } from "@microsoft/vscode-azext-utils";
import { parseImageName } from "../../../../utils/imageNameUtils";
import { localize } from "../../../../utils/localize";
import { AzureWizardActivityOutputExecuteStep } from "../../../AzureWizardActivityOutputExecuteStep";
import { type ContainerRegistryImageSourceContext } from "./ContainerRegistryImageSourceContext";
import { getLoginServer } from "./getLoginServer";

export class ContainerRegistryImageConfigureStep extends AzureWizardExecuteStep<ContainerRegistryImageSourceContext> {
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 app image to "{0}".', context.image);
protected getFailString = (context: T) => localize('failOutput', 'Failed to set container app image to "{0}".', context.image);
protected getTreeItemLabel = (context: T) => localize('treeItemLabel', 'Set container app image to "{0}"', context.image);

public async execute(context: ContainerRegistryImageSourceContext): Promise<void> {
public async execute(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';
}

public shouldExecute(context: ContainerRegistryImageSourceContext): boolean {
public shouldExecute(context: T): boolean {
return !context.image;
}
}
Loading