Skip to content

Commit e3ae022

Browse files
authored
Add deployRevisionDraft support for all revision modes (#414)
1 parent 7c29db3 commit e3ae022

File tree

11 files changed

+240
-14
lines changed

11 files changed

+240
-14
lines changed

package.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,12 @@
127127
"title": "%containerApps.editRevisionDraft%",
128128
"category": "Azure Container Apps"
129129
},
130+
{
131+
"command": "containerApps.deployRevisionDraft",
132+
"title": "%containerApps.deployRevisionDraft%",
133+
"category": "Azure Container Apps",
134+
"icon": "$(cloud-upload)"
135+
},
130136
{
131137
"command": "containerApps.discardRevisionDraft",
132138
"title": "%containerApps.discardRevisionDraft%",
@@ -241,6 +247,16 @@
241247
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /containerAppItem/i",
242248
"group": "2@2"
243249
},
250+
{
251+
"command": "containerApps.deployRevisionDraft",
252+
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /containerAppItem(.*)revisionMode:single(.*)unsavedChanges:true/i",
253+
"group": "inline@1"
254+
},
255+
{
256+
"command": "containerApps.deployRevisionDraft",
257+
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /containerAppItem(.*)revisionMode:single(.*)unsavedChanges:true/i",
258+
"group": "3@1"
259+
},
244260
{
245261
"command": "containerApps.discardRevisionDraft",
246262
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /containerAppItem(.*)revisionMode:single(.*)unsavedChanges:true/i",
@@ -306,6 +322,16 @@
306322
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /revisionItem(.*)revisionState:active/i",
307323
"group": "2@4"
308324
},
325+
{
326+
"command": "containerApps.deployRevisionDraft",
327+
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /revisionDraftItem(.*)unsavedChanges:true/i",
328+
"group": "inline@1"
329+
},
330+
{
331+
"command": "containerApps.deployRevisionDraft",
332+
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /revisionDraftItem(.*)unsavedChanges:true/i",
333+
"group": "1@1"
334+
},
309335
{
310336
"command": "containerApps.discardRevisionDraft",
311337
"when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /revisionDraftItem/i",

package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"containerApps.chooseRevisionMode": "Choose Revision Mode...",
1919
"containerApps.createRevisionDraft": "Create Draft...",
2020
"containerApps.editRevisionDraft": "Edit Draft (Advanced)...",
21+
"containerApps.deployRevisionDraft": "Deploy Changes...",
2122
"containerApps.discardRevisionDraft": "Discard Changes...",
2223
"containerApps.activateRevision": "Activate Revision",
2324
"containerApps.deactivateRevision": "Deactivate Revision",

src/commands/registerCommands.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { chooseRevisionMode } from './revision/chooseRevisionMode';
2727
import { deactivateRevision } from './revision/deactivateRevision';
2828
import { restartRevision } from './revision/restartRevision';
2929
import { createRevisionDraft } from './revisionDraft/createRevisionDraft';
30+
import { deployRevisionDraft } from './revisionDraft/deployRevisionDraft/deployRevisionDraft';
3031
import { discardRevisionDraft } from './revisionDraft/discardRevisionDraft';
3132
import { editRevisionDraft } from './revisionDraft/editRevisionDraft';
3233
import { addScaleRule } from './scaling/addScaleRule/addScaleRule';
@@ -70,6 +71,7 @@ export function registerCommands(): void {
7071
// revision draft
7172
registerCommandWithTreeNodeUnwrapping('containerApps.createRevisionDraft', createRevisionDraft);
7273
registerCommandWithTreeNodeUnwrapping('containerApps.editRevisionDraft', editRevisionDraft);
74+
registerCommandWithTreeNodeUnwrapping('containerApps.deployRevisionDraft', deployRevisionDraft);
7375
registerCommandWithTreeNodeUnwrapping('containerApps.discardRevisionDraft', discardRevisionDraft);
7476

7577
// scaling
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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 { AzureWizardPromptStep } from "@microsoft/vscode-azext-utils";
7+
import { localize } from "../../../utils/localize";
8+
import type { IDeployRevisionDraftContext } from "./IDeployRevisionDraftContext";
9+
10+
export class DeployRevisionDraftConfirmStep extends AzureWizardPromptStep<IDeployRevisionDraftContext> {
11+
public async prompt(context: IDeployRevisionDraftContext): Promise<void> {
12+
await context.ui.showWarningMessage(
13+
localize('deployRevisionWarning', 'This will deploy any unsaved changes to container app "{0}".', context.containerApp?.name),
14+
{ modal: true },
15+
{ title: localize('continue', 'Continue') }
16+
);
17+
}
18+
19+
public shouldPrompt(context: IDeployRevisionDraftContext): boolean {
20+
return !!context.template;
21+
}
22+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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 { KnownActiveRevisionsMode } from "@azure/arm-appcontainers";
7+
import { AzureWizardExecuteStep, nonNullProp } from "@microsoft/vscode-azext-utils";
8+
import type { Progress } from "vscode";
9+
import { ext } from "../../../extensionVariables";
10+
import { ContainerAppItem, ContainerAppModel, getContainerEnvelopeWithSecrets } from "../../../tree/ContainerAppItem";
11+
import { RevisionDraftItem } from "../../../tree/revisionManagement/RevisionDraftItem";
12+
import { localize } from "../../../utils/localize";
13+
import { updateContainerApp } from "../../../utils/updateContainerApp";
14+
import type { IDeployRevisionDraftContext } from "./IDeployRevisionDraftContext";
15+
16+
export class DeployRevisionDraftStep extends AzureWizardExecuteStep<IDeployRevisionDraftContext> {
17+
public priority: number = 260;
18+
19+
public async execute(context: IDeployRevisionDraftContext, progress: Progress<{ message?: string | undefined; increment?: number | undefined }>): Promise<void> {
20+
const containerApp: ContainerAppModel = nonNullProp(context, 'containerApp');
21+
const containerAppEnvelope = await getContainerEnvelopeWithSecrets(context, context.subscription, containerApp);
22+
containerAppEnvelope.template = nonNullProp(context, 'template');
23+
24+
const creatingRevision: string = localize('creatingRevision', 'Creating revision...');
25+
progress.report({ message: creatingRevision });
26+
27+
const id: string = containerApp.revisionsMode === KnownActiveRevisionsMode.Single ? containerApp.id : `${containerApp.id}/${RevisionDraftItem.idSuffix}`;
28+
29+
await ext.state.runWithTemporaryDescription(id, creatingRevision, async () => {
30+
await updateContainerApp(context, context.subscription, containerAppEnvelope);
31+
const updatedContainerApp = await ContainerAppItem.Get(context, context.subscription, containerApp.resourceGroup, containerApp.name);
32+
33+
if (containerApp.revisionsMode === KnownActiveRevisionsMode.Multiple) {
34+
// Display the name of the newly created revision when in multiple revisions mode
35+
context.activityTitle = localize('deployRevision', 'Deploy revision "{0}" to container app "{1}"', updatedContainerApp.latestRevisionName, containerApp.name);
36+
}
37+
});
38+
}
39+
40+
public shouldExecute(context: IDeployRevisionDraftContext): boolean {
41+
return !!context.template;
42+
}
43+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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 { Template } from "@azure/arm-appcontainers";
7+
import type { ExecuteActivityContext } from "@microsoft/vscode-azext-utils";
8+
import type { IContainerAppContext } from "../../IContainerAppContext";
9+
10+
export interface IDeployRevisionDraftContext extends IContainerAppContext, ExecuteActivityContext {
11+
template: Template | undefined;
12+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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 { ContainerAppsAPIClient, KnownActiveRevisionsMode, Revision } from "@azure/arm-appcontainers";
7+
import { uiUtils } from "@microsoft/vscode-azext-azureutils";
8+
import { AzureWizard, AzureWizardExecuteStep, AzureWizardPromptStep, IActionContext, createSubscriptionContext, nonNullProp } from "@microsoft/vscode-azext-utils";
9+
import * as deepEqual from "deep-eql";
10+
import { ext } from "../../../extensionVariables";
11+
import type { ContainerAppItem, ContainerAppModel } from "../../../tree/ContainerAppItem";
12+
import { RevisionDraftItem } from "../../../tree/revisionManagement/RevisionDraftItem";
13+
import { createActivityContext } from "../../../utils/activityUtils";
14+
import { createContainerAppsAPIClient } from "../../../utils/azureClients";
15+
import { delay } from "../../../utils/delay";
16+
import { localize } from "../../../utils/localize";
17+
import { pickContainerApp } from "../../../utils/pickContainerApp";
18+
import { DeployRevisionDraftConfirmStep } from "./DeployRevisionDraftConfirmStep";
19+
import { DeployRevisionDraftStep } from "./DeployRevisionDraftStep";
20+
import type { IDeployRevisionDraftContext } from "./IDeployRevisionDraftContext";
21+
22+
export async function deployRevisionDraft(context: IActionContext, node?: ContainerAppItem | RevisionDraftItem): Promise<void> {
23+
const item = node ?? await pickContainerApp(context);
24+
25+
const { subscription, containerApp } = item;
26+
27+
const wizardContext: IDeployRevisionDraftContext = {
28+
...context,
29+
...createSubscriptionContext(subscription),
30+
...(await createActivityContext()),
31+
subscription,
32+
containerApp,
33+
template: ext.revisionDraftFileSystem.parseRevisionDraft(item),
34+
};
35+
36+
if (!await hasUnsavedChanges(wizardContext, item)) {
37+
throw new Error(localize('noUnsavedChanges', 'No unsaved changes detected to deploy to container app "{0}".', containerApp.name));
38+
}
39+
40+
const promptSteps: AzureWizardPromptStep<IDeployRevisionDraftContext>[] = [
41+
new DeployRevisionDraftConfirmStep()
42+
];
43+
44+
const executeSteps: AzureWizardExecuteStep<IDeployRevisionDraftContext>[] = [
45+
new DeployRevisionDraftStep()
46+
];
47+
48+
const wizard: AzureWizard<IDeployRevisionDraftContext> = new AzureWizard(wizardContext, {
49+
title: localize('deploy', 'Deploy changes to container app "{0}"', containerApp.name),
50+
promptSteps,
51+
executeSteps,
52+
});
53+
54+
await wizard.prompt();
55+
await wizard.execute();
56+
57+
if (item.containerApp.revisionsMode === KnownActiveRevisionsMode.Single) {
58+
ext.revisionDraftFileSystem.discardRevisionDraft(item);
59+
} else {
60+
await ext.state.showDeleting(
61+
`${item.containerApp.id}/${RevisionDraftItem.idSuffix}`,
62+
async () => {
63+
// Add a short delay to display the deleting message
64+
await delay(5);
65+
ext.revisionDraftFileSystem.discardRevisionDraft(item);
66+
}
67+
);
68+
}
69+
70+
ext.state.notifyChildrenChanged(item.containerApp.managedEnvironmentId);
71+
}
72+
73+
async function hasUnsavedChanges(context: IDeployRevisionDraftContext, item: ContainerAppItem | RevisionDraftItem): Promise<boolean> {
74+
const containerApp: ContainerAppModel = nonNullProp(context, 'containerApp');
75+
76+
if (context.containerApp?.revisionsMode === KnownActiveRevisionsMode.Single) {
77+
return !!containerApp.template && !!context.template && !deepEqual(containerApp.template, context.template);
78+
} else {
79+
const client: ContainerAppsAPIClient = await createContainerAppsAPIClient(context);
80+
const revisions: Revision[] = await uiUtils.listAllIterator(client.containerAppsRevisions.listRevisions(containerApp.resourceGroup, containerApp.name));
81+
82+
const baseRevisionName: string | undefined = ext.revisionDraftFileSystem.getRevisionDraftFile(item)?.baseRevisionName;
83+
const baseRevision: Revision | undefined = revisions.find(revision => baseRevisionName && revision.name === baseRevisionName);
84+
85+
return !!baseRevision?.template && !!context.template && !deepEqual(baseRevision.template, context.template);
86+
}
87+
}

src/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,6 @@ export const DOCKERFILE_GLOB_PATTERN = '**/{*.[dD][oO][cC][kK][eE][rR][fF][iI][l
7474

7575
export const revisionModeSingleContextValue: string = 'revisionMode:single';
7676
export const revisionModeMultipleContextValue: string = 'revisionMode:multiple';
77+
78+
export const unsavedChangesTrueContextValue: string = 'unsavedChanges:true';
79+
export const unsavedChangesFalseContextValue: string = 'unsavedChanges:false';

src/tree/ContainerAppItem.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import * as deepEqual from "deep-eql";
1111
import { TreeItem, TreeItemCollapsibleState, Uri } from "vscode";
1212
import { DeleteAllContainerAppsStep } from "../commands/deleteContainerApp/DeleteAllContainerAppsStep";
1313
import { IDeleteContainerAppWizardContext } from "../commands/deleteContainerApp/IDeleteContainerAppWizardContext";
14-
import { revisionModeMultipleContextValue, revisionModeSingleContextValue } from "../constants";
14+
import { revisionModeMultipleContextValue, revisionModeSingleContextValue, unsavedChangesFalseContextValue, unsavedChangesTrueContextValue } from "../constants";
1515
import { ext } from "../extensionVariables";
1616
import { createActivityContext } from "../utils/activityUtils";
1717
import { createContainerAppsAPIClient, createContainerAppsClient } from "../utils/azureClients";
@@ -21,12 +21,10 @@ import { treeUtils } from "../utils/treeUtils";
2121
import type { ContainerAppsItem, TreeElementBase } from "./ContainerAppsBranchDataProvider";
2222
import { LogsGroupItem } from "./LogsGroupItem";
2323
import { ConfigurationItem } from "./configurations/ConfigurationItem";
24+
import { RevisionsDraftModel } from "./revisionManagement/RevisionDraftItem";
2425
import { RevisionItem } from "./revisionManagement/RevisionItem";
2526
import { RevisionsItem } from "./revisionManagement/RevisionsItem";
2627

27-
const unsavedChangesTrueContextValue: string = 'unsavedChanges:true';
28-
const unsavedChangesFalseContextValue: string = 'unsavedChanges:false';
29-
3028
export interface ContainerAppModel extends ContainerApp {
3129
id: string;
3230
name: string;
@@ -35,7 +33,7 @@ export interface ContainerAppModel extends ContainerApp {
3533
revisionsMode: KnownActiveRevisionsMode;
3634
}
3735

38-
export class ContainerAppItem implements ContainerAppsItem {
36+
export class ContainerAppItem implements ContainerAppsItem, RevisionsDraftModel {
3937
static readonly contextValue: string = 'containerAppItem';
4038
static readonly contextValueRegExp: RegExp = new RegExp(ContainerAppItem.contextValue);
4139

@@ -164,7 +162,7 @@ export class ContainerAppItem implements ContainerAppsItem {
164162
ext.state.notifyChildrenChanged(this.containerApp.managedEnvironmentId);
165163
}
166164

167-
private hasUnsavedChanges(): boolean {
165+
hasUnsavedChanges(): boolean {
168166
const draftTemplate = ext.revisionDraftFileSystem.parseRevisionDraft(this);
169167
if (!this.containerApp.template || !draftTemplate) {
170168
return false;

0 commit comments

Comments
 (0)