Skip to content

Commit 94c41d6

Browse files
authored
Add an Edit Container... command (#769)
1 parent 77301cb commit 94c41d6

File tree

15 files changed

+291
-168
lines changed

15 files changed

+291
-168
lines changed

package.json

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,6 @@
8080
"title": "%containerApps.editContainerApp%",
8181
"category": "Azure Container Apps"
8282
},
83-
{
84-
"command": "containerApps.updateImage",
85-
"title": "%containerApps.updateImage%",
86-
"category": "Azure Container Apps"
87-
},
8883
{
8984
"command": "containerApps.deployImageApi",
9085
"title": "%containerApps.deployImageApi%",
@@ -203,6 +198,11 @@
203198
"title": "%containerApps.openConsoleInPortal%",
204199
"category": "Azure Container Apps"
205200
},
201+
{
202+
"command": "containerApps.editContainer",
203+
"title": "%containerApps.editContainer%",
204+
"category": "Azure Container Apps"
205+
},
206206
{
207207
"command": "containerApps.editScaleRange",
208208
"title": "%containerApps.editScaleRange%",
@@ -373,11 +373,6 @@
373373
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /containerAppItem(.*)revisionMode:single(.*)unsavedChanges:true/i",
374374
"group": "3@2"
375375
},
376-
{
377-
"command": "containerApps.updateImage",
378-
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /containerAppItem(.*)revisionMode:single/i",
379-
"group": "4@1"
380-
},
381376
{
382377
"command": "containerApps.editContainerApp",
383378
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /containerAppItem(.*)revisionMode:single/i",
@@ -438,11 +433,6 @@
438433
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /revisionItem(.*)revisionState:active/i",
439434
"group": "2@2"
440435
},
441-
{
442-
"command": "containerApps.updateImage",
443-
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /revisionDraft:false(.*)revisionItem/i",
444-
"group": "3@1"
445-
},
446436
{
447437
"command": "containerApps.deployRevisionDraft",
448438
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /revisionDraftItem(.*)unsavedChanges:true/i",
@@ -463,16 +453,16 @@
463453
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /revisionDraftItem/i",
464454
"group": "1@2"
465455
},
466-
{
467-
"command": "containerApps.updateImage",
468-
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /revisionDraftItem/i",
469-
"group": "2@1"
470-
},
471456
{
472457
"command": "containerApps.editRevisionDraft",
473458
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /revisionDraftItem/i",
474459
"group": "3@1"
475460
},
461+
{
462+
"command": "containerApps.editContainer",
463+
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /containerItem/i",
464+
"group": "1@1"
465+
},
476466
{
477467
"command": "containerApps.editScaleRange",
478468
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /scaleItem/i",

package.nls.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"containerApps.createContainerApp": "Create Container App...",
1010
"containerApps.createContainerAppFromWorkspace": "Create Container App from Workspace...",
1111
"containerApps.editContainerApp": "Edit Container App (Advanced)...",
12-
"containerApps.updateImage": "Update Container Image...",
12+
"containerApps.editContainer": "Edit Container...",
1313
"containerApps.deployImageApi": "Deploy Image to Container App (API)...",
1414
"containerApps.deployWorkspaceProject": "Deploy Project from Workspace...",
1515
"containerApps.deployWorkspaceProjectApi": "Deploy Project from Workspace (API)...",
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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 { type ExecuteActivityContext } from "@microsoft/vscode-azext-utils";
7+
import { type SetTelemetryProps } from "../../telemetry/SetTelemetryProps";
8+
import { type ContainerUpdateTelemetryProps as TelemetryProps } from "../../telemetry/commandTelemetryProps";
9+
import { type IContainerAppContext } from "../IContainerAppContext";
10+
import { type ImageSourceBaseContext } from "../image/imageSource/ImageSourceContext";
11+
12+
export interface ContainerEditBaseContext extends IContainerAppContext, ImageSourceBaseContext, ExecuteActivityContext {
13+
containersIdx: number;
14+
}
15+
16+
export type ContainerEditContext = ContainerEditBaseContext & SetTelemetryProps<TelemetryProps>;
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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 { type Container, type Revision } from "@azure/arm-appcontainers";
7+
import { activityFailContext, activityFailIcon, activityProgressContext, activityProgressIcon, activitySuccessContext, activitySuccessIcon, createUniversallyUniqueContextValue, GenericParentTreeItem, GenericTreeItem, nonNullProp, type ExecuteActivityOutput } from "@microsoft/vscode-azext-utils";
8+
import { type Progress } from "vscode";
9+
import { type ContainerAppItem, type ContainerAppModel } from "../../tree/ContainerAppItem";
10+
import { type RevisionsItemModel } from "../../tree/revisionManagement/RevisionItem";
11+
import { localize } from "../../utils/localize";
12+
import { getParentResourceFromItem } from "../../utils/revisionDraftUtils";
13+
import { getContainerNameForImage } from "../image/imageSource/containerRegistry/getContainerNameForImage";
14+
import { RevisionDraftUpdateBaseStep } from "../revisionDraft/RevisionDraftUpdateBaseStep";
15+
import { type ContainerEditContext } from "./ContainerEditContext";
16+
17+
export class ContainerEditDraftStep<T extends ContainerEditContext> extends RevisionDraftUpdateBaseStep<T> {
18+
public priority: number = 590;
19+
20+
constructor(baseItem: ContainerAppItem | RevisionsItemModel) {
21+
super(baseItem);
22+
}
23+
24+
public async execute(context: T, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise<void> {
25+
progress.report({ message: localize('editingContainer', 'Editing container (draft)...') });
26+
this.revisionDraftTemplate.containers ??= [];
27+
28+
const container: Container = this.revisionDraftTemplate.containers[context.containersIdx] ?? {};
29+
container.name = getContainerNameForImage(nonNullProp(context, 'image'));
30+
container.image = context.image;
31+
container.env = context.environmentVariables;
32+
33+
await this.updateRevisionDraftWithTemplate(context);
34+
}
35+
36+
public shouldExecute(context: T): boolean {
37+
return context.containersIdx !== undefined && !!context.image;
38+
}
39+
40+
public createSuccessOutput(): ExecuteActivityOutput {
41+
const parentResource: ContainerAppModel | Revision = getParentResourceFromItem(this.baseItem);
42+
return {
43+
item: new GenericTreeItem(undefined, {
44+
contextValue: createUniversallyUniqueContextValue(['containerEditDraftStepSuccessItem', activitySuccessContext]),
45+
label: localize('editContainer', 'Edit container profile for container app "{0}" (draft)', parentResource.name),
46+
iconPath: activitySuccessIcon,
47+
}),
48+
message: localize('editContainerSuccess', 'Successfully edited container profile for container app "{0}" (draft).', parentResource.name),
49+
};
50+
}
51+
52+
public createProgressOutput(): ExecuteActivityOutput {
53+
const parentResource: ContainerAppModel | Revision = getParentResourceFromItem(this.baseItem);
54+
return {
55+
item: new GenericTreeItem(undefined, {
56+
contextValue: createUniversallyUniqueContextValue(['containerEditDraftStepProgressItem', activityProgressContext]),
57+
label: localize('editContainer', 'Edit container profile for container app "{0}" (draft)', parentResource.name),
58+
iconPath: activityProgressIcon,
59+
}),
60+
};
61+
}
62+
63+
public createFailOutput(): ExecuteActivityOutput {
64+
const parentResource: ContainerAppModel | Revision = getParentResourceFromItem(this.baseItem);
65+
return {
66+
item: new GenericParentTreeItem(undefined, {
67+
contextValue: createUniversallyUniqueContextValue(['containerEditDraftStepFailItem', activityFailContext]),
68+
label: localize('editContainer', 'Edit container profile for container app "{0}" (draft)', parentResource.name),
69+
iconPath: activityFailIcon,
70+
}),
71+
message: localize('editContainerFail', 'Failed to edit container profile for container app "{0}" (draft).', parentResource.name),
72+
};
73+
}
74+
}

src/commands/image/updateImage/UpdateRegistryAndSecretsStep.ts renamed to src/commands/editContainer/RegistryAndSecretsUpdateStep.ts

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@ import { type RegistryCredentials, type Secret } from "@azure/arm-appcontainers"
77
import { AzureWizardExecuteStep, nonNullProp } from "@microsoft/vscode-azext-utils";
88
import * as deepEqual from "deep-eql";
99
import { type Progress } from "vscode";
10-
import { ext } from "../../../extensionVariables";
11-
import { getContainerEnvelopeWithSecrets, type ContainerAppModel } from "../../../tree/ContainerAppItem";
12-
import { localize } from "../../../utils/localize";
13-
import { updateContainerApp } from "../../updateContainerApp";
14-
import { type UpdateImageContext } from "./updateImage";
10+
import { ext } from "../../extensionVariables";
11+
import { getContainerEnvelopeWithSecrets, type ContainerAppModel } from "../../tree/ContainerAppItem";
12+
import { localize } from "../../utils/localize";
13+
import { updateContainerApp } from "../updateContainerApp";
14+
import { type ContainerEditContext } from "./ContainerEditContext";
1515

16-
export class UpdateRegistryAndSecretsStep extends AzureWizardExecuteStep<UpdateImageContext> {
16+
export class RegistryAndSecretsUpdateStep<T extends ContainerEditContext> extends AzureWizardExecuteStep<T> {
1717
public priority: number = 580;
1818

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

@@ -28,20 +28,17 @@ export class UpdateRegistryAndSecretsStep extends AzureWizardExecuteStep<UpdateI
2828
context.telemetry.properties.skippedRegistryCredentialUpdate = 'true';
2929
return;
3030
}
31-
3231
context.telemetry.properties.skippedRegistryCredentialUpdate = 'false';
3332

3433
progress.report({ message: localize('configuringSecrets', 'Configuring registry secrets...') });
35-
3634
containerAppEnvelope.configuration.secrets = context.secrets;
3735
containerAppEnvelope.configuration.registries = context.registryCredentials;
3836

3937
await updateContainerApp(context, context.subscription, containerAppEnvelope);
40-
4138
ext.outputChannel.appendLog(localize('updatedSecrets', 'Updated container app "{0}" with new registry secrets.', containerApp.name));
4239
}
4340

44-
public shouldExecute(context: UpdateImageContext): boolean {
41+
public shouldExecute(context: T): boolean {
4542
return !!context.registryCredentials && !!context.secrets;
4643
}
4744

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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 Revision } from "@azure/arm-appcontainers";
7+
import { AzureWizard, createSubscriptionContext, type IActionContext, type ISubscriptionContext } from "@microsoft/vscode-azext-utils";
8+
import { type ContainerAppModel } from "../../tree/ContainerAppItem";
9+
import { type ContainerItem } from "../../tree/containers/ContainerItem";
10+
import { ContainersItem } from "../../tree/containers/ContainersItem";
11+
import { createActivityContext } from "../../utils/activityUtils";
12+
import { getManagedEnvironmentFromContainerApp } from "../../utils/getResourceUtils";
13+
import { getVerifyProvidersStep } from "../../utils/getVerifyProvidersStep";
14+
import { localize } from "../../utils/localize";
15+
import { pickContainer } from "../../utils/pickItem/pickContainer";
16+
import { getParentResourceFromItem, isTemplateItemEditable, TemplateItemNotEditableError } from "../../utils/revisionDraftUtils";
17+
import { ImageSourceListStep } from "../image/imageSource/ImageSourceListStep";
18+
import { RevisionDraftDeployPromptStep } from "../revisionDraft/RevisionDraftDeployPromptStep";
19+
import { type ContainerEditContext } from "./ContainerEditContext";
20+
import { ContainerEditDraftStep } from "./ContainerEditDraftStep";
21+
import { RegistryAndSecretsUpdateStep } from "./RegistryAndSecretsUpdateStep";
22+
23+
// Edits both the 'image' and 'environmentVariables' portion of the container profile (draft)
24+
export async function editContainer(context: IActionContext, node?: ContainersItem | ContainerItem): Promise<void> {
25+
const item: ContainerItem | ContainersItem = node ?? await pickContainer(context, { autoSelectDraft: true });
26+
const { containerApp, subscription } = item;
27+
28+
if (!isTemplateItemEditable(item)) {
29+
throw new TemplateItemNotEditableError(item);
30+
}
31+
32+
const subscriptionContext: ISubscriptionContext = createSubscriptionContext(subscription);
33+
const parentResource: ContainerAppModel | Revision = getParentResourceFromItem(item);
34+
35+
let containersIdx: number;
36+
if (ContainersItem.isContainersItem(item)) {
37+
// 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'
38+
containersIdx = 0;
39+
} else {
40+
containersIdx = item.containersIdx;
41+
}
42+
43+
const wizardContext: ContainerEditContext = {
44+
...context,
45+
...subscriptionContext,
46+
...await createActivityContext(true),
47+
subscription,
48+
managedEnvironment: await getManagedEnvironmentFromContainerApp({ ...context, ...subscriptionContext }, containerApp),
49+
containerApp,
50+
containersIdx,
51+
};
52+
wizardContext.telemetry.properties.revisionMode = containerApp.revisionsMode;
53+
54+
const wizard: AzureWizard<ContainerEditContext> = new AzureWizard(wizardContext, {
55+
title: localize('editContainer', 'Edit container profile for container app "{0}" (draft)', parentResource.name),
56+
promptSteps: [
57+
new ImageSourceListStep(),
58+
new RevisionDraftDeployPromptStep(),
59+
],
60+
executeSteps: [
61+
getVerifyProvidersStep<ContainerEditContext>(),
62+
new RegistryAndSecretsUpdateStep(),
63+
new ContainerEditDraftStep(item),
64+
],
65+
showLoadingPrompt: true,
66+
});
67+
68+
await wizard.prompt();
69+
await wizard.execute();
70+
}

src/commands/image/imageSource/containerRegistry/ContainerRegistryImageConfigureStep.ts

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

6-
import { AzureWizardExecuteStep } from "@microsoft/vscode-azext-utils";
76
import { parseImageName } from "../../../../utils/imageNameUtils";
7+
import { localize } from "../../../../utils/localize";
8+
import { AzureWizardActivityOutputExecuteStep } from "../../../AzureWizardActivityOutputExecuteStep";
89
import { type ContainerRegistryImageSourceContext } from "./ContainerRegistryImageSourceContext";
910
import { getLoginServer } from "./getLoginServer";
1011

11-
export class ContainerRegistryImageConfigureStep extends AzureWizardExecuteStep<ContainerRegistryImageSourceContext> {
12+
export class ContainerRegistryImageConfigureStep<T extends ContainerRegistryImageSourceContext> extends AzureWizardActivityOutputExecuteStep<T> {
1213
public priority: number = 570;
14+
public stepName: string = 'containerRegistryImageConfigureStep';
15+
protected getSuccessString = (context: T) => localize('successOutput', 'Successfully set container app image to "{0}".', context.image);
16+
protected getFailString = (context: T) => localize('failOutput', 'Failed to set container app image to "{0}".', context.image);
17+
protected getTreeItemLabel = (context: T) => localize('treeItemLabel', 'Set container app image to "{0}"', context.image);
1318

14-
public async execute(context: ContainerRegistryImageSourceContext): Promise<void> {
19+
public async execute(context: T): Promise<void> {
1520
context.image = `${getLoginServer(context)}/${context.repositoryName}:${context.tag}`;
1621

1722
const { registryName, registryDomain } = parseImageName(context.image);
1823
context.telemetry.properties.registryName = registryName;
1924
context.telemetry.properties.registryDomain = registryDomain ?? 'other';
2025
}
2126

22-
public shouldExecute(context: ContainerRegistryImageSourceContext): boolean {
27+
public shouldExecute(context: T): boolean {
2328
return !context.image;
2429
}
2530
}

0 commit comments

Comments
 (0)