-
Notifications
You must be signed in to change notification settings - Fork 142
Expand file tree
/
Copy pathdeployment.ts
More file actions
332 lines (289 loc) Β· 12.1 KB
/
deployment.ts
File metadata and controls
332 lines (289 loc) Β· 12.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
import path from 'path';
import { create as tarCreate, t as tarT } from 'tar';
import tmp from 'tmp';
import vscode, { Uri } from 'vscode';
import { ActionTools } from '../../api/actions';
import IBMi from '../../api/IBMi';
import IBMiContent from '../../api/IBMiContent';
import { Tools } from '../../api/Tools';
import { instance } from '../../instantiate';
import { DeploymentParameters } from '../../typings';
import { DeployTools } from './deployTools';
export namespace Deployment {
export interface MD5Entry {
path: string
md5: string
}
export const BUTTON_BASE = `$(cloud-upload) Deploy`;
export const BUTTON_WORKING = `$(sync~spin) Deploying`;
export const deploymentLog = vscode.window.createOutputChannel(`IBM i Deployment`);
export const button = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 0);
export const workspaceChanges: Map<vscode.WorkspaceFolder, Map<string, vscode.Uri>> = new Map;
let fixCCSID: boolean | undefined;
export function initialize(context: vscode.ExtensionContext) {
button.command = {
command: `code-for-ibmi.launchDeploy`,
title: `Launch Deploy`
}
button.text = BUTTON_BASE;
context.subscriptions.push(
button,
deploymentLog,
vscode.commands.registerCommand(`code-for-ibmi.launchActionsSetup`, DeployTools.launchActionsSetup),
vscode.commands.registerCommand(`code-for-ibmi.launchDeploy`, DeployTools.launchDeploy),
vscode.commands.registerCommand(`code-for-ibmi.deploySelected`, async (item?: Uri, items?: Uri[]) => {
if(!(item instanceof Uri)) {
vscode.window.showErrorMessage(`No files selected for deployment.`);
return;
}
const selectedItems = Array.isArray(items) && items.length > 0 ? items : [item];
const itemsByWorkspace: Uri[][] = [];
selectedItems.forEach(uri => {
const workspace = vscode.workspace.getWorkspaceFolder(uri);
if (workspace) {
let uris = itemsByWorkspace[workspace.index];
if (!uris) uris = [];
uris.push(uri);
itemsByWorkspace[workspace.index] = uris;
}
});
await Promise.all(
itemsByWorkspace.map((workspaceItems, workspaceIndex) => DeployTools.launchDeploy(workspaceIndex, "selected", workspaceItems))
);
}),
vscode.commands.registerCommand(`code-for-ibmi.setDeployLocation`, DeployTools.setDeployLocation)
);
const workspaces = vscode.workspace.workspaceFolders;
if (workspaces && workspaces.length > 0) {
workspaceWatcher().then(context.subscriptions.push);
}
instance.subscribe(
context,
'connected',
`Initialize deployment`,
() => {
const workspaces = vscode.workspace.workspaceFolders;
const connection = instance.getConnection();
const storage = instance.getStorage();
if (workspaces && connection && storage) {
const config = connection.getConfig();
if (workspaces.length > 0 && !config.readOnlyMode) {
button.show();
} else {
button.hide();
}
const existingPaths = storage.getDeployment();
if (workspaces.length === 1) {
const workspace = workspaces[0];
if (existingPaths && !existingPaths[workspace.uri.fsPath]) {
const possibleDeployDir = DeployTools.buildPossibleDeploymentDirectory(workspace);
vscode.window.showInformationMessage(
`Deploy directory for Workspace not setup. Would you like to default to '${possibleDeployDir}'?`,
`Yes`,
`Ignore`
).then(async result => {
if (result === `Yes`) {
DeployTools.setDeployLocation({ path: possibleDeployDir }, workspace);
}
});
}
ActionTools.getActions(workspace).then(result => {
if (result.length === 0) {
vscode.window.showInformationMessage(
`There are no local Actions defined for this project.`,
`Run Setup`
).then(result => {
if (result === `Run Setup`)
vscode.commands.executeCommand(`code-for-ibmi.launchActionsSetup`);
});
}
})
}
}
});
instance.subscribe(
context,
'disconnected',
`Clear deployment`,
() => {
fixCCSID = undefined;
button.hide();
})
}
export function getConnection(): IBMi {
const connection = instance.getConnection();
if (!connection) {
throw new Error("Please connect to an IBM i");
}
return connection;
}
export function getContent(): IBMiContent {
const connection = getConnection();
if (!connection) {
throw new Error("Please connect to an IBM i");
}
return connection.getContent();
}
export async function createRemoteDirectory(remotePath: string) {
return await getConnection().sendCommand({
command: `mkdir -p "${remotePath}"`
});
}
async function workspaceWatcher() {
const invalidFs = [`member`, `streamfile`];
const watcher = vscode.workspace.createFileSystemWatcher(`**`);
const getChangesMap = (uri: vscode.Uri) => {
if (!invalidFs.includes(uri.scheme) && !uri.fsPath.includes(`.git`)) {
const workspaceFolder = vscode.workspace.getWorkspaceFolder(uri);
if (workspaceFolder) {
let changes = workspaceChanges.get(workspaceFolder);
if (!changes) {
changes = new Map;
workspaceChanges.set(workspaceFolder, changes);
}
return changes;
}
}
}
const checkLocalActionsFiles = async (uri: vscode.Uri | vscode.WorkspaceFolder) => {
let workspace: vscode.WorkspaceFolder | undefined;
if (uri instanceof vscode.Uri) {
if (uri.path.endsWith('/.vscode/actions.json')) {
workspace = vscode.workspace.getWorkspaceFolder(uri);
}
else {
return;
}
}
else {
workspace = uri;
}
vscode.commands.executeCommand(`setContext`, `code-for-ibmi:hasLocalActions`, workspace ? (await ActionTools.getActions(workspace)).length > 0 : false);
};
watcher.onDidChange(uri => {
getChangesMap(uri)?.set(uri.fsPath, uri);
});
watcher.onDidCreate(async uri => {
checkLocalActionsFiles(uri);
const fileStat = await vscode.workspace.fs.stat(uri);
if (fileStat.type === vscode.FileType.File) {
getChangesMap(uri)?.set(uri.fsPath, uri);
}
});
watcher.onDidDelete(uri => {
checkLocalActionsFiles(uri);
getChangesMap(uri)?.delete(uri.fsPath);
});
vscode.workspace.workspaceFolders?.forEach(checkLocalActionsFiles);
return watcher;
}
export async function showErrorButton() {
if (await vscode.window.showErrorMessage(`Deployment failed.`, `View Log`)) {
deploymentLog.show();
}
}
export async function getWorkspaceFolder(workspaceIndex?: number) {
if (workspaceIndex !== undefined) {
return vscode.workspace.workspaceFolders?.find(dir => dir.index === workspaceIndex);
} else {
const workspaces = vscode.workspace.workspaceFolders;
if (workspaces && workspaces.length > 0) {
if (workspaces.length === 1) {
return workspaces[0];
} else {
const chosen = await vscode.window.showQuickPick(workspaces.map(dir => dir.name), {
placeHolder: `Select workspace to deploy`
});
if (chosen) {
return workspaces.find(dir => dir.name === chosen);
}
}
}
}
}
export function toMD5Entry(line: string): MD5Entry {
const parts = line.split(/\s+/);
return {
md5: parts[0].trim(),
path: parts[1].trim().substring(2) //these path starts with ./
};
}
export function toRelative(root: vscode.Uri, file: vscode.Uri) {
return path.relative(root.path, file.path).replace(/\\/g, `/`);
}
export async function findFiles(parameters: DeploymentParameters, includePattern: string, excludePattern?: string) {
const root = parameters.workspaceFolder.uri;
return (await vscode.workspace.findFiles(new vscode.RelativePattern(parameters.workspaceFolder, includePattern),
excludePattern ? new vscode.RelativePattern(parameters.workspaceFolder, excludePattern) : null))
.filter(file => {
if (parameters.ignoreRules) {
const relative = toRelative(root, file);
return !parameters.ignoreRules.ignores(relative);
}
else {
return true;
}
});
}
export async function deleteFiles(parameters: DeploymentParameters, toDelete: string[]) {
if (toDelete.length) {
Deployment.deploymentLog.appendLine(`\nDeleted:\n\t${toDelete.join('\n\t')}\n`);
await Deployment.getConnection().sendCommand({ directory: parameters.remotePath, command: `rm -rf ${toDelete.join(' ')}` });
}
}
export async function sendCompressed(parameters: DeploymentParameters, files: vscode.Uri[], progress: vscode.Progress<{ message?: string }>) {
const connection = getConnection();
const localTarball = tmp.fileSync({ postfix: ".tar" });
const remoteTarball = Tools.ensureFullPath(path.posix.join(getConnection().getConfig().tempDir || '.vscode/tmp', `deploy_${Tools.makeid()}.tar`), getConnection().getConfig().homeDirectory);
try {
const toSend = files.map(file => path.relative(parameters.workspaceFolder.uri.fsPath, file.fsPath));
progress?.report({ message: `creating deployment tarball for ${toSend.length} file(s)...` });
tarCreate({ cwd: parameters.workspaceFolder.uri.fsPath, sync: true, file: localTarball.name }, toSend);
deploymentLog.appendLine(`Created deployment tarball ${localTarball.name}`);
progress?.report({ message: `sending deployment tarball...` });
await connection.client!.putFile(localTarball.name, remoteTarball);
deploymentLog.appendLine(`Uploaded deployment tarball as ${remoteTarball}`);
progress?.report({ message: `extracting deployment tarball to ${parameters.remotePath}...` });
//Extract and remove tar's PaxHeader metadata folder
const result = await connection.sendCommand({ command: `${connection.remoteFeatures.tar} -xof ${remoteTarball} && rm -rf PaxHeader`, directory: parameters.remotePath });
if (result.code !== 0) {
throw new Error(`Tarball extraction failed: ${result.stderr}`)
}
const entries: string[] = [];
tarT({ sync: true, file: localTarball.name, onentry: entry => entries.push(entry.path) });
deploymentLog.appendLine(`${entries.length} file(s) uploaded to ${parameters.remotePath}`);
entries.sort().map(e => `\t${e}`).forEach(deploymentLog.appendLine);
if (await mustFixCCSID()) {
progress?.report({ message: 'Fixing files CCSID...' });
const fix = await connection.sendCommand({ command: `${connection.remoteFeatures.setccsid} -R 1208 ${parameters.remotePath}` });
if (fix.code === 0) {
deploymentLog.appendLine(`Deployed files' CCSID set to 1208`);
}
else {
deploymentLog.appendLine(`Failed to set deployed files' CCSID to 1208: ${fix.stderr}`);
}
}
}
finally {
deploymentLog.appendLine('');
await connection.sendCommand({ command: `rm ${remoteTarball}` })
deploymentLog.appendLine(`${remoteTarball} deleted`);
localTarball.removeCallback();
deploymentLog.appendLine(`${localTarball.name} deleted`);
}
}
/**
* Check if default CCSID of created/deployed files is not 1208 (utf-8).
*
* @returns `true` if the default CCSID of IFS files is not 1208.
*/
async function mustFixCCSID() {
if (fixCCSID === undefined) {
const connection = getConnection();
fixCCSID = Boolean(connection.remoteFeatures.attr) &&
Boolean(connection.remoteFeatures.setccsid) &&
(await connection.sendCommand({ command: `touch codeforiccsidtest && ${connection.remoteFeatures.attr} codeforiccsidtest CCSID && rm codeforiccsidtest` })).stdout !== "1208";
}
return fixCCSID;
}
}