Skip to content

Commit 848165c

Browse files
committed
Impl migration
1 parent 1951fa4 commit 848165c

22 files changed

+1028
-60
lines changed

package.json

Lines changed: 276 additions & 12 deletions
Large diffs are not rendered by default.

package.nls.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"containerApps.enableOutputTimestamps": "Prepends each line displayed in the output channel with a timestamp.",
77
"containerApps.browse": "Browse",
88
"containerApps.createContainerApp": "Create Container App...",
9+
"containerApps.editContainerApp": "Edit Container App (Advanced)",
910
"containerApps.deployImage": "Update Container App Image......",
1011
"containerApps.deployImageApi": "Update Container App Image (API)...",
1112
"containerApps.deleteContainerApp": "Delete Container App...",
@@ -14,6 +15,10 @@
1415
"containerApps.toggleVisibility": "Switch Ingress Visibility...",
1516
"containerApps.editTargetPort": "Edit Target Port...",
1617
"containerApps.chooseRevisionMode": "Choose Revision Mode...",
18+
"containerApps.createRevisionDraft": "Create Draft...",
19+
"containerApps.editRevisionDraft": "Edit Draft (Advanced)",
20+
"containerApps.deployRevisionDraft": "Deploy Changes...",
21+
"containerApps.discardRevisionDraft": "Discard Changes...",
1722
"containerApps.activateRevision": "Activate Revision",
1823
"containerApps.deactivateRevision": "Deactivate Revision",
1924
"containerApps.restartRevision": "Restart Revision",

resources/revision-draft.svg

Lines changed: 36 additions & 0 deletions
Loading

src/commands/editContainerApp.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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 { IActionContext } from "@microsoft/vscode-azext-utils";
8+
import { ext } from "../extensionVariables";
9+
import { ContainerAppItem } from "../tree/ContainerAppItem";
10+
import { localize } from "../utils/localize";
11+
import { pickContainerApp } from "../utils/pickContainerApp";
12+
13+
export async function editContainerApp(context: IActionContext, node?: ContainerAppItem): Promise<void> {
14+
node ??= await pickContainerApp(context);
15+
16+
if (node.containerApp.revisionsMode === KnownActiveRevisionsMode.Multiple) {
17+
throw new Error(localize('revisionModeError', 'The issued command can only be executed in single revision mode.'));
18+
}
19+
20+
await ext.revisionDraftFileSystem.editRevisionDraft(node);
21+
}

src/commands/registerCommands.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { deleteContainerApp } from './deleteContainerApp/deleteContainerApp';
1111
import { deleteManagedEnvironment } from './deleteManagedEnvironment/deleteManagedEnvironment';
1212
import { deployImage } from './deployImage/deployImage';
1313
import { deployImageApi } from './deployImage/deployImageApi';
14+
import { editContainerApp } from './editContainerApp';
1415
import { connectToGitHub } from './gitHub/connectToGitHub/connectToGitHub';
1516
import { disconnectRepo } from './gitHub/disconnectRepo/disconnectRepo';
1617
import { openGitHubRepo } from './gitHub/openGitHubRepo';
@@ -25,6 +26,9 @@ import { activateRevision } from './revision/activateRevision';
2526
import { chooseRevisionMode } from './revision/chooseRevisionMode';
2627
import { deactivateRevision } from './revision/deactivateRevision';
2728
import { restartRevision } from './revision/restartRevision';
29+
import { createRevisionDraft } from './revisionDraft/createRevisionDraft';
30+
import { discardRevisionDraft } from './revisionDraft/discardRevisionDraft';
31+
import { editRevisionDraft } from './revisionDraft/editRevisionDraft';
2832
import { addScaleRule } from './scaling/addScaleRule/addScaleRule';
2933
import { editScalingRange } from './scaling/editScalingRange';
3034

@@ -35,6 +39,7 @@ export function registerCommands(): void {
3539

3640
// container apps
3741
registerCommandWithTreeNodeUnwrapping('containerApps.createContainerApp', createContainerApp);
42+
registerCommandWithTreeNodeUnwrapping('containerApps.editContainerApp', editContainerApp);
3843
registerCommandWithTreeNodeUnwrapping('containerApps.deleteContainerApp', deleteContainerApp);
3944
registerCommandWithTreeNodeUnwrapping('containerApps.deployImage', deployImage);
4045
registerCommandWithTreeNodeUnwrapping('containerApps.deployImageApi', deployImageApi);
@@ -58,11 +63,17 @@ export function registerCommands(): void {
5863
registerCommandWithTreeNodeUnwrapping('containerApps.deactivateRevision', deactivateRevision);
5964
registerCommandWithTreeNodeUnwrapping('containerApps.restartRevision', restartRevision);
6065

66+
// revision draft
67+
registerCommandWithTreeNodeUnwrapping('containerApps.createRevisionDraft', createRevisionDraft);
68+
registerCommandWithTreeNodeUnwrapping('containerApps.editRevisionDraft', editRevisionDraft);
69+
registerCommandWithTreeNodeUnwrapping('containerApps.deployRevisionDraft', () => { throw new Error('Deploy revision draft not yet implemented.') });
70+
registerCommandWithTreeNodeUnwrapping('containerApps.discardRevisionDraft', discardRevisionDraft);
71+
6172
// scaling
6273
registerCommandWithTreeNodeUnwrapping('containerApps.editScalingRange', editScalingRange);
6374
registerCommandWithTreeNodeUnwrapping('containerApps.addScaleRule', addScaleRule);
6475

65-
//log streaming
76+
// log streaming
6677
registerCommandWithTreeNodeUnwrapping('containerApps.startStreamingLogs', startStreamingLogs);
6778
registerCommandWithTreeNodeUnwrapping('containerApps.stopStreamingLogs', stopStreamingLogs);
6879

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
import { Template } from "@azure/arm-appcontainers";
2+
import { nonNullValueAndProp } from "@microsoft/vscode-azext-utils";
3+
import { Disposable, Event, EventEmitter, FileChangeEvent, FileChangeType, FileStat, FileSystemProvider, FileType, TextDocument, Uri, window, workspace } from "vscode";
4+
import { URI } from "vscode-uri";
5+
import { ext } from "../../extensionVariables";
6+
import { ContainerAppItem } from "../../tree/ContainerAppItem";
7+
import { ContainerAppsItem } from "../../tree/ContainerAppsBranchDataProvider";
8+
import { RevisionDraftItem } from "../../tree/revisionManagement/RevisionDraftItem";
9+
import { RevisionItem } from "../../tree/revisionManagement/RevisionItem";
10+
import { localize } from "../../utils/localize";
11+
12+
const notSupported: string = localize('notSupported', 'This operation is not currently supported.');
13+
14+
export class RevisionDraftFile implements FileStat {
15+
type: FileType = FileType.File;
16+
size: number;
17+
ctime: number;
18+
mtime: number;
19+
20+
contents: Uint8Array;
21+
22+
constructor(contents: Uint8Array, readonly containerAppId: string, readonly baseRevisionName: string) {
23+
this.contents = contents;
24+
this.size = contents.byteLength;
25+
this.ctime = Date.now();
26+
this.mtime = Date.now();
27+
}
28+
}
29+
30+
/**
31+
* File system provider that allows for reading/writing/deploying container app revision drafts
32+
*
33+
* Enforces a policy of one revision draft per container app
34+
*/
35+
export class RevisionDraftFileSystem implements FileSystemProvider {
36+
static scheme: string = 'containerAppsRevisionDraft';
37+
38+
private readonly emitter: EventEmitter<FileChangeEvent[]> = new EventEmitter<FileChangeEvent[]>();
39+
private readonly bufferedEvents: FileChangeEvent[] = [];
40+
private fireSoonHandle?: NodeJS.Timer;
41+
42+
private draftStore: Record<string, RevisionDraftFile> = {};
43+
44+
get onDidChangeFile(): Event<FileChangeEvent[]> {
45+
return this.emitter.event;
46+
}
47+
48+
// Create
49+
createRevisionDraft(item: ContainerAppItem | RevisionItem | RevisionDraftItem): void {
50+
const uri: Uri = this.buildUriFromItem(item);
51+
if (this.doesUriExist(uri)) {
52+
return;
53+
}
54+
55+
let file: RevisionDraftFile | undefined;
56+
if (item instanceof ContainerAppItem) {
57+
/**
58+
* Sometimes there are micro-differences present between the latest revision template and the container app template.
59+
* They end up being essentially equivalent, but not so equivalent as to always pass a deep copy test (which is how we detect unsaved changes).
60+
*
61+
* Since only container app template data is shown in single revision mode, and since revision data is not directly present on
62+
* the container app tree item without further changes, it is easier to just use the container app template
63+
* as the primary source of truth when in single revision mode.
64+
*/
65+
const revisionContent: Uint8Array = Buffer.from(JSON.stringify(nonNullValueAndProp(item.containerApp, 'template'), undefined, 4));
66+
file = new RevisionDraftFile(revisionContent, item.containerApp.id, nonNullValueAndProp(item.containerApp, 'latestRevisionName'));
67+
} else {
68+
const revisionContent: Uint8Array = Buffer.from(JSON.stringify(nonNullValueAndProp(item.revision, 'template'), undefined, 4));
69+
file = new RevisionDraftFile(revisionContent, item.containerApp.id, nonNullValueAndProp(item.revision, 'name'));
70+
}
71+
72+
this.draftStore[uri.path] = file;
73+
this.fireSoon({ type: FileChangeType.Created, uri });
74+
}
75+
76+
// Read
77+
parseRevisionDraft<T extends ContainerAppsItem>(item: T): Template | undefined {
78+
const uri: URI = this.buildUriFromItem(item);
79+
if (!this.doesUriExist(uri)) {
80+
return undefined;
81+
}
82+
83+
return JSON.parse(this.readFile(uri).toString()) as Template;
84+
}
85+
86+
readFile(uri: Uri): Uint8Array {
87+
const contents = this.draftStore[uri.path].contents;
88+
return contents ? Buffer.from(contents) : Buffer.from('');
89+
}
90+
91+
doesContainerAppsItemHaveRevisionDraft<T extends ContainerAppsItem>(item: T): boolean {
92+
const uri: Uri = this.buildUriFromItem(item);
93+
return this.doesUriExist(uri);
94+
}
95+
96+
getRevisionDraftFile<T extends ContainerAppsItem>(item: T): RevisionDraftFile | undefined {
97+
const uri: Uri = this.buildUriFromItem(item);
98+
return this.draftStore[uri.path];
99+
}
100+
101+
stat(uri: Uri): FileStat {
102+
const file: RevisionDraftFile | undefined = this.draftStore[uri.path];
103+
104+
if (file) {
105+
return {
106+
type: file.type,
107+
ctime: file.ctime,
108+
mtime: file.mtime,
109+
size: file.size
110+
};
111+
} else {
112+
return { type: FileType.File, ctime: 0, mtime: 0, size: 0 };
113+
}
114+
}
115+
116+
// Update
117+
async editRevisionDraft(item: ContainerAppItem | RevisionItem | RevisionDraftItem): Promise<void> {
118+
const uri: Uri = this.buildUriFromItem(item);
119+
if (!this.doesUriExist(uri)) {
120+
this.createRevisionDraft(item);
121+
}
122+
123+
const textDoc: TextDocument = await workspace.openTextDocument(uri);
124+
await window.showTextDocument(textDoc);
125+
}
126+
127+
writeFile(uri: Uri, contents: Uint8Array): void {
128+
const file: RevisionDraftFile | undefined = this.draftStore[uri.path];
129+
if (!file || file.contents === contents) {
130+
return;
131+
}
132+
133+
file.contents = contents;
134+
file.size = contents.byteLength;
135+
file.mtime = Date.now();
136+
137+
this.draftStore[uri.path] = file;
138+
this.fireSoon({ type: FileChangeType.Changed, uri });
139+
140+
// Any new changes to the draft file can cause the states of a container app's children to change (e.g. displaying "Unsaved changes")
141+
ext.state.notifyChildrenChanged(file.containerAppId);
142+
}
143+
144+
// Delete
145+
discardRevisionDraft<T extends ContainerAppsItem>(item: T): void {
146+
const uri: Uri = this.buildUriFromItem(item);
147+
if (!this.doesUriExist(uri)) {
148+
return;
149+
}
150+
151+
this.delete(uri);
152+
}
153+
154+
delete(uri: Uri): void {
155+
delete this.draftStore[uri.path];
156+
this.fireSoon({ type: FileChangeType.Deleted, uri });
157+
}
158+
159+
// Helpers..
160+
private buildUriFromItem<T extends ContainerAppsItem>(item: T): Uri {
161+
return URI.parse(`${RevisionDraftFileSystem.scheme}:/${item.containerApp.name}.json`);
162+
}
163+
164+
private doesUriExist(uri: Uri): boolean {
165+
return !!this.draftStore[uri.path];
166+
}
167+
168+
// Adapted from: https://github.com/microsoft/vscode-extension-samples/blob/master/fsprovider-sample/src/fileSystemProvider.ts
169+
fireSoon(...events: FileChangeEvent[]): void {
170+
this.bufferedEvents.push(...events);
171+
172+
if (this.fireSoonHandle) {
173+
clearTimeout(this.fireSoonHandle);
174+
}
175+
176+
this.fireSoonHandle = setTimeout(() => {
177+
this.emitter.fire(this.bufferedEvents);
178+
this.bufferedEvents.length = 0;
179+
}, 5);
180+
}
181+
182+
watch(): Disposable {
183+
return new Disposable((): void => { /** Do nothing */ });
184+
}
185+
186+
readDirectory(): [string, FileType][] {
187+
throw new Error(notSupported);
188+
}
189+
190+
createDirectory(): void {
191+
throw new Error(notSupported);
192+
}
193+
194+
rename(): void {
195+
throw new Error(notSupported);
196+
}
197+
}

0 commit comments

Comments
 (0)