-
Notifications
You must be signed in to change notification settings - Fork 12
/
Manifest.ts
434 lines (382 loc) · 13.1 KB
/
Manifest.ts
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
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright IBM Corp, 2019
*
*/
"use strict";
import { BundlePart, IBundlePartDataType } from "./BundlePart";
import { IHandlerParameters } from "@brightside/imperative";
import * as parser from "fast-xml-parser";
const serialiser = new parser.j2xParser({ignoreAttributes: false, attributeNamePrefix: ""});
/**
* Interface to represent the manifest data for a CICS Bundle.
*
* @export
* @interface IManifestType
*/
interface IManifestType {
manifest: IManifestContents;
}
/**
* Interface to represent the manifest data contents for CICS Bundle.
*
* @export
* @interface IManifestContents
*/
interface IManifestContents {
id?: string;
xmlns?: string;
bundleVersion?: number;
bundleRelease?: number;
bundleMajorVer?: number;
bundleMinorVer?: number;
bundleMicroVer?: number;
define?: IBundlePartDataType[];
}
/**
* Class to represent a CICS Bundle manifest.
*
* @export
* @class Manifest
*/
export class Manifest {
private MAX_BUNDLEID_LEN = 64;
private fs = require("fs");
private path = require("path");
private manifestAsJson: IManifestType = undefined;
private bundleDirectory: string;
private metainfDir: string;
private manifestFile: string;
private manifestExists: boolean = false;
private merge: boolean;
private overwrite: boolean;
private params: IHandlerParameters;
private manifestAction = " create";
/**
* Constructor for creating a Manifest.
*
* @static
* @param {string} directory - The bundle directory.
* @param {boolean} merge - Changes to the bundle manifest should be merged into any existing manifest.
* @param {boolean} overwrite - Changes to the bundle contents should replace any existing contents.
* @param {IHandlerParameters} params - The current Imperative handler parameters
* @throws ImperativeError
* @memberof Manifest
*/
constructor(directory: string, merge: boolean, overwrite: boolean, params?: IHandlerParameters) {
this.merge = merge;
this.overwrite = overwrite;
this.bundleDirectory = this.path.normalize(directory);
this.metainfDir = this.bundleDirectory + "/META-INF";
this.manifestFile = this.metainfDir + "/cics.xml";
this.params = params;
// If 'merge' is set then attempt to read any existing manifest that may
// already exist. Subsequent changes will be merged with the existing
// content.
if (this.merge === true) {
try {
this.readManifest();
} catch (ex) {
// Something went wrong. If it's an ENOENT response then there was
// no manifest to read, so that can be ignored, otherwise propagate
// the exception back to the caller.
if (ex.code !== "ENOENT") {
throw ex;
}
}
}
// If we've not read an existing manifest, create a new one
if (this.manifestAsJson === undefined) {
this.manifestAsJson = { manifest:
{ xmlns: "http://www.ibm.com/xmlns/prod/cics/bundle",
bundleVersion: 1,
bundleRelease: 0 } };
}
}
/**
* Perform whatever validation can be done in advance of attempting to save the
* manifest, thereby reducing the possibility of a failure after some of the
* bundle parts have already been persisted to the file system.
*
* @throws ImperativeError
* @memberof Manifest
*/
public prepareForSave() {
// Does the meta-inf directory already exist?
if (!this.fs.existsSync(this.metainfDir)) {
// we'll have to create it during the save, do we have write permission?
try {
this.fs.accessSync(this.bundleDirectory, this.fs.constants.W_OK);
}
catch (err) {
throw new Error("cics-deploy requires write permission to: " + this.bundleDirectory);
}
return;
}
// Do we have write permission to the META-INF dir?
try {
this.fs.accessSync(this.metainfDir, this.fs.constants.W_OK);
}
catch (err) {
throw new Error("cics-deploy requires write permission to: " + this.metainfDir);
}
// Does a manifest file already exist?
if (this.fs.existsSync(this.manifestFile)) {
if (this.overwrite === false) {
throw new Error("A bundle manifest file already exists. Specify --overwrite to replace it, or --merge to merge changes into it.");
}
if (this.merge) {
this.manifestAction = " merge";
}
else {
this.manifestAction = " overwrite";
}
// Do we have write permission to the manifest?
try {
this.fs.accessSync(this.manifestFile, this.fs.constants.W_OK);
}
catch (err) {
throw new Error("cics-deploy requires write permission to: " + this.manifestFile);
}
}
}
/**
* Save the Manifest file.
*
* @throws ImperativeError
* @memberof Manifest
*/
public save() {
// Create the META-INF directory if it doesn't already exist
if (!this.fs.existsSync(this.metainfDir)) {
try {
this.logCreation(this.metainfDir);
this.fs.mkdirSync(this.metainfDir);
}
catch (err) {
throw new Error("An error occurred attempting to create '" + this.metainfDir + "': " + err.message);
}
}
// Write the cics.xml manifest
try {
this.logCreation(this.manifestFile, this.manifestAction);
this.fs.writeFileSync(this.manifestFile, this.getXML(), "utf8");
}
catch (err) {
throw new Error("An error occurred attempting to write manifest file '" + this.manifestFile + "': " + err.message);
}
this.manifestExists = true;
}
/**
* Validates that a manifest file exists. If the manifest has not yet been saved then it is
* invalid.
*
* @returns {object}
* @throws ImperativeError
* @memberof Manifest
*/
public validate() {
if (!this.manifestExists) {
throw new Error("No bundle manifest file found: " + this.manifestFile);
}
}
/**
* Returns the Manifest contents as a JSON Object.
*
* @returns {object}
* @throws ImperativeError
* @memberof Manifest
*/
public getJson(): object {
return this.manifestAsJson;
}
/**
* Returns the Manifest contents as XML text.
*
* @returns {string}
* @throws ImperativeError
* @memberof Manifest
*/
public getXML(): string {
return serialiser.parse(this.manifestAsJson) + "\n";
}
/**
* Returns the Bundle's identity (id) value.
*
* @returns {string}
* @throws ImperativeError
* @memberof Manifest
*/
public getBundleId(): string {
return this.manifestAsJson.manifest.id;
}
/**
* Set the Bundle's identity (id) value.
* @param {string} id - The identiry value for the Bundle.
* @throws ImperativeError
* @memberof Manifest
*/
public setBundleId(id: string) {
if (id === undefined) {
throw new Error("BundleId not set.");
}
const mangledId = this.mangle(id, this.MAX_BUNDLEID_LEN);
this.manifestAsJson.manifest.id = mangledId;
}
/**
* Returns the Bundle's version number.
*
* @returns {string}
* @throws ImperativeError
* @memberof Manifest
*/
public getBundleVersion(): string {
if (this.manifestAsJson.manifest.bundleMajorVer === undefined) {
this.manifestAsJson.manifest.bundleMajorVer = 1;
}
if (this.manifestAsJson.manifest.bundleMinorVer === undefined) {
this.manifestAsJson.manifest.bundleMinorVer = 0;
}
if (this.manifestAsJson.manifest.bundleMicroVer === undefined) {
this.manifestAsJson.manifest.bundleMicroVer = 0;
}
return this.manifestAsJson.manifest.bundleMajorVer + "." +
this.manifestAsJson.manifest.bundleMinorVer + "." +
this.manifestAsJson.manifest.bundleMicroVer;
}
/**
* Set the Bundle's version number.
* @param {number} majorVersion - The major version number.
* @param {number} minorVersion - The minor version number.
* @param {number} microVersion - The micro version number.
* @throws ImperativeError
* @memberof Manifest
*/
public setBundleVersion(majorVersion: number, minorVersion: number, microVersion: number) {
if (Number.isInteger(majorVersion) && majorVersion > 0) {
this.manifestAsJson.manifest.bundleMajorVer = majorVersion;
}
else {
throw new Error("Invalid Bundle major version specified: " + majorVersion + ".");
}
if (Number.isInteger(minorVersion) && minorVersion >= 0) {
this.manifestAsJson.manifest.bundleMinorVer = minorVersion;
}
else {
throw new Error("Invalid Bundle minor version specified: " + minorVersion + ".");
}
if (Number.isInteger(microVersion) && microVersion >= 0) {
this.manifestAsJson.manifest.bundleMicroVer = microVersion;
}
else {
throw new Error("Invalid Bundle micro version specified: " + microVersion + ".");
}
}
/**
* Add a resource definition to the Manifest. If a part of the same name and type
* already exists then it is replaced with the new part data.
*
* @param {BundlePart} bundlePart - the BundlePart to add.
* @throws ImperativeError
* @memberof Manifest
*/
public addDefinition(bundlePart: BundlePart) {
const definition = bundlePart.getPartData();
// Ensure that the definitions array is usable
this.prepareDefinitionsArray();
// Search the array for a definition with the same name and
// type as the one we're trying to add, if found then update
// it with the new path.
let i: number;
for (i = 0; i < this.manifestAsJson.manifest.define.length; i++) {
const candidate = this.manifestAsJson.manifest.define[i];
if (candidate.name === definition.name &&
candidate.type === definition.type) {
candidate.path = definition.path;
return;
}
}
// If we've not replaced an existing item in the list, add
// the new item to the end.
this.manifestAsJson.manifest.define.push(definition);
}
/**
* Does the bundle contain any resource definitions of the
* specified type?
*
* @param {String} resourceType - the resource type
* @throws ImperativeError
* @memberof Manifest
*/
public containsDefinitionsOfType(resourceType: string): boolean {
// Ensure that the definitions array is usable
this.prepareDefinitionsArray();
// Search the array for a definition with the nominated type
let i: number;
for (i = 0; i < this.manifestAsJson.manifest.define.length; i++) {
const candidate = this.manifestAsJson.manifest.define[i];
if (candidate.type === resourceType) {
return true;
}
}
return false;
}
private prepareDefinitionsArray() {
// Create the array of definitions if it doesn't already exist
if (this.manifestAsJson.manifest.define === undefined) {
this.manifestAsJson.manifest.define = [];
}
// If what already exists is an object (as may happen when appending
// to an existing XML manifest with only one entry) convert it to an array;
if (Array.isArray(this.manifestAsJson.manifest.define) === false) {
const obj = this.manifestAsJson.manifest.define as any;
this.manifestAsJson.manifest.define = [];
this.manifestAsJson.manifest.define.push(obj as IBundlePartDataType);
}
}
// Read an existing manifest file into an object
private readManifest() {
// read the manifest from the file system
const xmltext = this.fs.readFileSync(this.manifestFile, "utf8");
try {
// Reading the file worked, so convert the contents into a JSON Object
this.manifestAsJson = parser.parse(xmltext, {ignoreAttributes: false, attributeNamePrefix: "", trimValues: true});
}
catch (exception)
{
throw new Error("Parsing error occurred reading a CICS manifest file: " + exception.message);
}
// Validate that the manifest we've read is usable.
if (this.manifestAsJson.manifest === undefined) {
throw new Error("Existing CICS Manifest file found with unparsable content: " + JSON.stringify(this.manifestAsJson));
}
// Now check the namespace
if (this.manifestAsJson.manifest.xmlns !== "http://www.ibm.com/xmlns/prod/cics/bundle") {
throw new Error("Existing CICS Manifest file found with unexpected namespace: " + this.manifestAsJson.manifest.xmlns + " .");
}
this.manifestExists = true;
}
// Function for mangaling a string for inclusion in the manifest
private mangle(text: string, maxLength: number): string {
// replace underscore with hyphen
const text2 = text.replace(/_/g, "-");
// Convert all unsupported characters to X
const text3 = text2.replace(/[^0-9,^A-Z,^a-z\-]/g, "X");
// Now truncate the string
const text4 = text3.substring(0, maxLength);
return text4;
}
private logCreation(file: string, action?: string) {
if (action === undefined) {
action = " create";
}
if (this.params !== undefined) {
this.params.response.console.log(action + " : " + this.path.relative(this.bundleDirectory, file));
}
}
}