trm-core 10.0.0 → 10.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/changelog.txt +27 -3
  2. package/dist/actions/checkEngines/analyze.js +6 -0
  3. package/dist/actions/checkSapEntries/analyze.d.ts +2 -0
  4. package/dist/actions/checkSapEntries/analyze.js +100 -18
  5. package/dist/actions/commons/setSystemPackages.js +1 -1
  6. package/dist/actions/commons/utils/actionLocks.d.ts +2 -1
  7. package/dist/actions/commons/utils/actionLocks.js +8 -1
  8. package/dist/actions/commons/utils/index.d.ts +1 -0
  9. package/dist/actions/commons/utils/index.js +1 -0
  10. package/dist/actions/commons/utils/packageCleanup.d.ts +8 -0
  11. package/dist/actions/commons/utils/packageCleanup.js +59 -10
  12. package/dist/actions/commons/utils/releaseDeletionTransport.js +11 -4
  13. package/dist/actions/commons/utils/trmBackend.d.ts +20 -0
  14. package/dist/actions/commons/utils/trmBackend.js +64 -0
  15. package/dist/actions/install/checkSapEntries.js +1 -1
  16. package/dist/actions/install/checkTransports.js +40 -1
  17. package/dist/actions/install/generateLandscapeTransport.js +4 -1
  18. package/dist/actions/install/generateUpdateTransport.d.ts +7 -0
  19. package/dist/actions/install/generateUpdateTransport.js +75 -8
  20. package/dist/actions/install/importBatch.js +0 -0
  21. package/dist/actions/install/index.d.ts +10 -0
  22. package/dist/actions/install/index.js +6 -3
  23. package/dist/actions/install/init.js +12 -2
  24. package/dist/actions/install/prepareCust.js +9 -4
  25. package/dist/actions/install/prepareDevc.js +9 -4
  26. package/dist/actions/install/prepareLang.js +9 -4
  27. package/dist/actions/install/prepareTadir.js +9 -4
  28. package/dist/actions/install/setInstallDevclass.js +17 -3
  29. package/dist/actions/install/updatePackageData.d.ts +5 -0
  30. package/dist/actions/install/updatePackageData.js +37 -18
  31. package/dist/client/RESTClient.js +6 -5
  32. package/dist/client/RFCClient.js +3 -5
  33. package/dist/systemConnector/SystemConnectorBase.js +25 -3
  34. package/dist/transport/Transport.js +17 -3
  35. package/package.json +1 -1
@@ -19,6 +19,7 @@ const trm_commons_2 = require("trm-commons");
19
19
  const systemConnector_1 = require("../../systemConnector");
20
20
  const commons_1 = require("../../commons");
21
21
  const utils_1 = require("../commons/utils");
22
+ const crypto_1 = require("crypto");
22
23
  /** Throws when any of the objects is locked in a SAP transport request. */
23
24
  function checkObjectsLocks(e071) {
24
25
  return __awaiter(this, void 0, void 0, function* () {
@@ -205,6 +206,11 @@ exports.checkTransports = {
205
206
  if (skippedCust.includes(transport.trkorr)) {
206
207
  readEntries = false;
207
208
  }
209
+ // the fixed TADIR transport of trm-server/trm-rest only comes with the artifact (below)
210
+ if ((context.runtime.isTrmServer || context.runtime.isTrmRest) && transport.type === transport_1.TrmTransportIdentifier.TADIR
211
+ && utils_1.TRM_FIXED_TADIR_TRANSPORTS[context.runtime.package.data.manifest.name] === transport.trkorr) {
212
+ readEntries = false;
213
+ }
208
214
  if (readEntries) {
209
215
  const entries = yield context.rawInput.packageData.registry.transportEntries(context.runtime.package.data.manifest.name, context.runtime.package.data.manifest.version, transport.trkorr);
210
216
  const binaries = {
@@ -235,6 +241,29 @@ exports.checkTransports = {
235
241
  }
236
242
  }
237
243
  }
244
+ // The registry metadata can omit a transport of the release, and the fixed TADIR transport of
245
+ // trm-server and trm-rest is never downloaded alone: read them from the artifact, once its integrity is verified.
246
+ if (!context.runtime.transports.devc || !context.runtime.transports.tadir) {
247
+ trm_commons_1.Logger.log(`No ${!context.runtime.transports.devc ? 'DEVC' : 'TADIR'} transport to download from the registry: reading the release artifact`, true);
248
+ trm_commons_1.Logger.loading(`Reading package transports from the release artifact...`);
249
+ const artifact = yield context.rawInput.packageData.registry.downloadArtifact(context.runtime.package.data.manifest.name, context.runtime.package.data.manifest.version);
250
+ const expectedChecksum = context.runtime.package.data.checksum;
251
+ const checksum = (0, crypto_1.createHash)('sha512').update(artifact.binary).digest('base64');
252
+ if (!expectedChecksum || checksum !== expectedChecksum) {
253
+ trm_commons_1.Logger.error(`SECURITY ISSUE! Release artifact integrity does NOT match the registry SHA ${expectedChecksum}: received ${checksum}`, { important: true });
254
+ throw new Error(`Cannot continue due to security issues.`);
255
+ }
256
+ for (const binary of yield artifact.getTransportBinaries()) {
257
+ if (binary.type === transport_1.TrmTransportIdentifier.DEVC && !context.runtime.transports.devc) {
258
+ context.runtime.transports.devc = { binaries: binary };
259
+ mergeEntries(binary.entries);
260
+ }
261
+ else if (binary.type === transport_1.TrmTransportIdentifier.TADIR && !context.runtime.transports.tadir) {
262
+ context.runtime.transports.tadir = { binaries: binary };
263
+ mergeEntries(binary.entries);
264
+ }
265
+ }
266
+ }
238
267
  }
239
268
  //4- check devc and tadir existance
240
269
  if (!context.runtime.transports.devc) {
@@ -272,6 +301,7 @@ exports.checkTransports = {
272
301
  }
273
302
  //8- check objects existance
274
303
  const checkedTadir = existenceCheckObjects(mergedTADIR);
304
+ trm_commons_1.Logger.loading(`Checking whether ${checkedTadir.length} package object${checkedTadir.length === 1 ? '' : 's'} already exist${checkedTadir.length === 1 ? 's' : ''}...`);
275
305
  const existingObjects = checkedTadir.length > 0 ? yield findExistingObjects(context, checkedTadir) : [];
276
306
  // lock-resources checks again, once locked, that no other object appeared
277
307
  context.runtime.existingObjects = existingObjects;
@@ -279,9 +309,18 @@ exports.checkTransports = {
279
309
  //if updating and existing object is part of the package (devclass in hierarchy) ok, else throw error
280
310
  if (existingObjects.length > 0) {
281
311
  // Name the installed TRM package that contains each existing object: it can't be taken over silently.
312
+ trm_commons_1.Logger.loading(`Checking ownership of ${existingObjects.length} existing object${existingObjects.length === 1 ? '' : 's'}...`);
282
313
  const owners = new Map();
314
+ // objects share few SAP packages: look each one up once
315
+ const packageOwners = new Map();
283
316
  for (const o of existingObjects) {
284
- const owner = o.devclass ? yield (0, utils_1.getOwningInstallation)(o.devclass, context.rawInput.contextData.systemPackages || [], context.runtime.update) : undefined;
317
+ if (!o.devclass) {
318
+ continue;
319
+ }
320
+ if (!packageOwners.has(o.devclass)) {
321
+ packageOwners.set(o.devclass, (0, utils_1.getOwningInstallation)(o.devclass, context.rawInput.contextData.systemPackages || [], context.runtime.update));
322
+ }
323
+ const owner = yield packageOwners.get(o.devclass);
285
324
  if (owner) {
286
325
  owners.set(o, owner.packageName);
287
326
  }
@@ -125,7 +125,10 @@ exports.generateLandscapeTransport = {
125
125
  //8- lock objects
126
126
  //what is lockable gets locked here
127
127
  //all workbench should be lockable here
128
- yield context.output.transport.lock();
128
+ //a legacy backend has no API for it
129
+ if (!context.legacyBackend) {
130
+ yield context.output.transport.lock();
131
+ }
129
132
  }),
130
133
  revert: (context) => __awaiter(void 0, void 0, void 0, function* () {
131
134
  if (context.output.transport && (yield context.output.transport.canBeDeleted())) {
@@ -18,3 +18,10 @@ export declare function keepsInstalledCustomizing(context: InstallWorkflowContex
18
18
  * release without an incoming one.
19
19
  */
20
20
  export declare const generateUpdateTransport: Step<InstallWorkflowContext>;
21
+ /**
22
+ * Workflow step that cleans up an upgrade of trm-server or trm-rest, last in the install (see
23
+ * {@link generateUpdateTransport}); every other package is cleaned up before its import. The installed
24
+ * release runs the install: deleting its objects first would remove the APIs the install still calls.
25
+ * Once the upgrade is complete, only the objects the new release doesn't ship are deleted, with its APIs.
26
+ */
27
+ export declare const generatePostImportUpdateTransport: Step<InstallWorkflowContext>;
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.generateUpdateTransport = exports.deleteTemporaryCleanupPackages = void 0;
12
+ exports.generatePostImportUpdateTransport = exports.generateUpdateTransport = exports.deleteTemporaryCleanupPackages = void 0;
13
13
  exports.keepsInstalledCustomizing = keepsInstalledCustomizing;
14
14
  const trm_commons_1 = require("trm-commons");
15
15
  const stopWarning_1 = require("../stopWarning");
@@ -60,6 +60,11 @@ function keepsInstalledCustomizing(context) {
60
60
  exports.generateUpdateTransport = {
61
61
  name: 'generate-update-transport',
62
62
  filter: (context) => __awaiter(void 0, void 0, void 0, function* () {
63
+ if (context.runtime.selfUpdate) {
64
+ // The installed release runs this install: its objects are cleaned up after the import
65
+ trm_commons_1.Logger.log(`Upgrade cleanup of ${context.rawInput.packageData.name} runs after the import`, true);
66
+ return false;
67
+ }
63
68
  //a local (.trm) upgrade generates its deletion transport through the registry the artifact was published to
64
69
  if (context.runtime.update) {
65
70
  return true;
@@ -70,19 +75,78 @@ exports.generateUpdateTransport = {
70
75
  }
71
76
  }),
72
77
  run: (context) => __awaiter(void 0, void 0, void 0, function* () {
73
- var _a, _b, _c, _d;
78
+ yield cleanupPreviousRelease(context, false);
79
+ }),
80
+ revert: revertUpdateCleanup
81
+ };
82
+ /**
83
+ * Workflow step that cleans up an upgrade of trm-server or trm-rest, last in the install (see
84
+ * {@link generateUpdateTransport}); every other package is cleaned up before its import. The installed
85
+ * release runs the install: deleting its objects first would remove the APIs the install still calls.
86
+ * Once the upgrade is complete, only the objects the new release doesn't ship are deleted, with its APIs.
87
+ */
88
+ exports.generatePostImportUpdateTransport = {
89
+ name: 'generate-post-import-update-transport',
90
+ filter: (context) => __awaiter(void 0, void 0, void 0, function* () { return !!context.runtime.selfUpdate; }),
91
+ run: (context) => __awaiter(void 0, void 0, void 0, function* () {
92
+ var _a, _b, _c;
93
+ // The new release is installed: a failed cleanup leaves objects of the previous one, it doesn't undo the install
94
+ try {
95
+ yield cleanupPreviousRelease(context, true);
96
+ }
97
+ catch (error) {
98
+ let revertError;
99
+ if (!context.revert.deleImportStarted) {
100
+ try {
101
+ yield revertUpdateCleanup(context);
102
+ }
103
+ catch (e) {
104
+ revertError = e;
105
+ }
106
+ }
107
+ const version = context.runtime.update.manifest.get().version;
108
+ trm_commons_1.Logger.warning(`Cleanup of ${context.rawInput.packageData.name} ${version} failed: ${error instanceof Error ? error.message : String(error)}`, { important: true });
109
+ if (revertError) {
110
+ trm_commons_1.Logger.warning(`Cleanup could not be undone either: ${revertError instanceof Error ? revertError.message : String(revertError)}`, { important: true });
111
+ }
112
+ trm_commons_1.Logger.warning(`${context.rawInput.packageData.name} ${context.runtime.package.data.manifest.version} is installed. Objects of ${version} it doesn't ship may be left on ${systemConnector_1.SystemConnector.getDest()}: delete them manually.`, { important: true });
113
+ return;
114
+ }
115
+ // The landscape transport is already released: the deletion follows it to the target
116
+ const targetSystem = (_b = (_a = context.rawInput.installData) === null || _a === void 0 ? void 0 : _a.landscapeTransport) === null || _b === void 0 ? void 0 : _b.targetSystem;
117
+ if (targetSystem && ((_c = context.output) === null || _c === void 0 ? void 0 : _c.transport) && (0, utils_1.isDeletionForwardable)(context)) {
118
+ try {
119
+ yield (0, utils_1.forwardDeletionTransport)(context, targetSystem);
120
+ }
121
+ catch (error) {
122
+ trm_commons_1.Logger.warning(`Deletion transport ${context.revert.dele.trkorr} could not be added to the ${targetSystem} import queue: ${error instanceof Error ? error.message : String(error)}. Add it in STMS after ${context.output.transport.trkorr}.`, { important: true });
123
+ }
124
+ }
125
+ }),
126
+ revert: (context) => __awaiter(void 0, void 0, void 0, function* () {
127
+ // The imported release stays (see import-batch): objects it doesn't ship stay deleted too.
128
+ // A cleanup that stopped before its deletion transport is undone.
129
+ if (context.revert.deleImportStarted) {
130
+ trm_commons_1.Logger.log(`Keeping the cleanup of ${context.rawInput.packageData.name}: its imported release is not rolled back`, true);
131
+ return;
132
+ }
133
+ yield revertUpdateCleanup(context);
134
+ })
135
+ };
136
+ function cleanupPreviousRelease(context, incomingImported) {
137
+ return __awaiter(this, void 0, void 0, function* () {
138
+ var _a, _b, _c;
74
139
  if (!context.runtime.stopWarningShown) {
75
140
  context.runtime.stopWarningShown = true;
76
141
  (0, stopWarning_1.stopWarning)('install');
77
142
  }
78
143
  // The cleanup deletes and re-imports the root package: read it now, so the import can put it back
79
144
  // under the superpackage it has on this system (prepare-devc reads it after the cleanup).
80
- if (((_b = (_a = context.rawInput.installData) === null || _a === void 0 ? void 0 : _a.installDevclass) === null || _b === void 0 ? void 0 : _b.keepOriginal) && !context.runtime.rootDevclassBeforeImport) {
145
+ if (!incomingImported && ((_b = (_a = context.rawInput.installData) === null || _a === void 0 ? void 0 : _a.installDevclass) === null || _b === void 0 ? void 0 : _b.keepOriginal) && !context.runtime.rootDevclassBeforeImport) {
81
146
  context.runtime.rootDevclassBeforeImport = yield systemConnector_1.SystemConnector.getDevclass(context.runtime.package.hierarchy.devclass);
82
147
  }
83
148
  // Read the target lazily: a failure reading the previous release must still be tracked first.
84
149
  const installDevclass = (_c = context.rawInput.installData) === null || _c === void 0 ? void 0 : _c.installDevclass;
85
- const importData = (_d = context.rawInput.installData) === null || _d === void 0 ? void 0 : _d.import;
86
150
  const keepCustomizing = keepsInstalledCustomizing(context);
87
151
  if (keepCustomizing) {
88
152
  const skipped = context.runtime.skippedCust || [];
@@ -97,6 +161,7 @@ exports.generateUpdateTransport = {
97
161
  ? flattenDevclasses(context.runtime.package.hierarchy)
98
162
  : installDevclass.replacements.map(replacement => replacement.installDevclass);
99
163
  },
164
+ incomingImported,
100
165
  prefix: `(${transport_1.Transport.getTransportIcon()} Upgrade cleanup) `,
101
166
  actionName: 'Update',
102
167
  requireDeletion: false,
@@ -107,8 +172,10 @@ exports.generateUpdateTransport = {
107
172
  return incomingCustomizingRows(context);
108
173
  }
109
174
  });
110
- }),
111
- revert: (context) => __awaiter(void 0, void 0, void 0, function* () {
175
+ });
176
+ }
177
+ function revertUpdateCleanup(context) {
178
+ return __awaiter(this, void 0, void 0, function* () {
112
179
  // Rollback of the imported objects ran first: when it failed, they are still on the system
113
180
  // and the previous release must not be restored over them.
114
181
  const restore = !(context.revert.cleanupImported && !context.revert.cleanupSucceeded);
@@ -118,5 +185,5 @@ exports.generateUpdateTransport = {
118
185
  if (restore) {
119
186
  yield (0, utils_1.deleteCleanupStagingPackages)(context);
120
187
  }
121
- })
122
- };
188
+ });
189
+ }
Binary file
@@ -222,6 +222,11 @@ type WorkflowRuntime = {
222
222
  lockedNamespaces?: string[];
223
223
  /** Required SAP entries missing on the system: the install is aborted by check-engines, with the unmet engines. */
224
224
  missingSapEntries?: number;
225
+ /**
226
+ * Upgrade of trm-server or trm-rest: the installed release runs the install itself. Its objects are
227
+ * only cleaned up after the incoming release is imported, and the import can't be rolled back.
228
+ */
229
+ selfUpdate?: boolean;
225
230
  };
226
231
  /** Dependency queued for install: missing from the system, or installed in an incompatible version. */
227
232
  export type InstallDependencyEntry = {
@@ -300,6 +305,11 @@ export type InstallActionOutput = {
300
305
  /** Internal state shared by package-install workflow steps and rollback handlers. */
301
306
  export interface InstallWorkflowContext extends IActionContext {
302
307
  lockScope?: ActionLockScope;
308
+ /**
309
+ * trm-server/trm-rest upgrade on a backend older than the APIs this trm-core needs (see isLegacyTrmBackend):
310
+ * only the APIs of the installed generation are used.
311
+ */
312
+ legacyBackend?: boolean;
303
313
  /** Custom namespaces already locked by the parent installs of a dependency install. */
304
314
  inheritedNamespaceLocks?: string[];
305
315
  /** Original action input; optional groups are normalized during initialization. */
@@ -87,15 +87,18 @@ const installWorkflow = [
87
87
  generateLandscapeTransport_1.generateLandscapeTransport,
88
88
  executePostActivities_1.executePostActivities,
89
89
  releaseLandscapeTransport_1.releaseLandscapeTransport,
90
- updatePackageData_1.updatePackageData
90
+ updatePackageData_1.updatePackageData,
91
+ generateUpdateTransport_1.generatePostImportUpdateTransport
91
92
  ];
92
93
  function runInstall(inputData_1, retainRollback_1) {
93
94
  return __awaiter(this, arguments, void 0, function* (inputData, retainRollback, inheritedNamespaceLocks = []) {
94
95
  var _a;
95
- const lockScope = new utils_2.ActionLockScope(WORKFLOW_NAME);
96
- const context = { rawInput: inputData, lockScope, inheritedNamespaceLocks };
97
96
  //a local artifact is locked under the package it installs, as a remote install of it would be
98
97
  const lockPackage = yield (0, utils_2.resolveInstallPackage)(inputData.packageData.registry, inputData.packageData.name);
98
+ // A legacy backend has no action locks: only trm-server and trm-rest can install there, to upgrade it.
99
+ const legacyBackend = (0, utils_2.isTrmBackendPackage)(lockPackage.registry, lockPackage.name) && (yield (0, utils_2.isLegacyTrmBackend)());
100
+ const lockScope = new utils_2.ActionLockScope(WORKFLOW_NAME, !legacyBackend);
101
+ const context = { rawInput: inputData, lockScope, inheritedNamespaceLocks, legacyBackend };
99
102
  yield lockScope.acquire([(0, utils_2.packageLockResource)(lockPackage.registry, lockPackage.name)]);
100
103
  const release = () => __awaiter(this, void 0, void 0, function* () {
101
104
  var _a;
@@ -225,10 +225,16 @@ exports.init = {
225
225
  //5- check if already installed
226
226
  context.runtime.update = context.rawInput.contextData.systemPackages.find(o => manifest_1.Manifest.compare(o.manifest, new manifest_1.Manifest(context.runtime.package.data.manifest), false));
227
227
  if (context.runtime.update) {
228
+ const installedVersion = context.runtime.update.manifest.get().version;
229
+ // trm-server and trm-rest run this install: see the selfUpdate steps
230
+ context.runtime.selfUpdate = context.runtime.isTrmServer || context.runtime.isTrmRest;
228
231
  context.runtime.previousInstallPackages = yield systemConnector_1.SystemConnector.getInstallPackages(context.rawInput.packageData.name, context.runtime.installRegistry);
229
- context.runtime.previousInstallTransports = yield systemConnector_1.SystemConnector.getInstallTransports(context.rawInput.packageData.name, context.runtime.installRegistry);
232
+ // A legacy backend has no install transports table: trm-server and trm-rest ship
233
+ // no customizing or translations, so there is nothing recorded to read.
234
+ if (!context.legacyBackend) {
235
+ context.runtime.previousInstallTransports = yield systemConnector_1.SystemConnector.getInstallTransports(context.rawInput.packageData.name, context.runtime.installRegistry);
236
+ }
230
237
  const installVersion = context.runtime.package.data.manifest.version;
231
- const installedVersion = context.runtime.update.manifest.get().version;
232
238
  //transports TRM generated for the package (e.g. of a rolled back release) aren't changes made on the system
233
239
  const dirtyEntries = yield (0, utils_1.withoutOwnTrmTransports)(context.runtime.update.getDirtyEntries(), context.runtime.update.packageName);
234
240
  if ((0, semver_1.eq)(installVersion, installedVersion)) {
@@ -266,6 +272,10 @@ exports.init = {
266
272
  trm_commons_1.Logger.warning(`${context.rawInput.packageData.name} has changes made on ${systemConnector_1.SystemConnector.getDest()} that will be overwritten!`, { important: true });
267
273
  (0, utils_1.logDirtyEntries)(dirtyEntries);
268
274
  }
275
+ if (context.runtime.isTrmServer && (0, semver_1.valid)(installedVersion) && (0, semver_1.lt)(installedVersion, utils_1.TRM_SERVER_API_VERSION) && !(0, semver_1.lt)(installVersion, utils_1.TRM_SERVER_API_VERSION)) {
276
+ // the install continues with the imported release: an unauthorized user can't complete it
277
+ trm_commons_1.Logger.warning(`trm-server ${utils_1.TRM_SERVER_API_VERSION} authorizes users with authorization object ZTRM_AUTH (ACTVT 16) instead of table /ATRM/USERS: ${systemConnector_1.SystemConnector.getLogonUser()} and the other TRM users need it.`, { important: true });
278
+ }
269
279
  }
270
280
  }
271
281
  else {
@@ -94,10 +94,15 @@ exports.prepareCust = {
94
94
  trTarget: systemConnector_1.SystemConnector.getDest()
95
95
  });
96
96
  //3- test import transport
97
- trm_commons_1.Logger.loading(`Testing import...`);
98
- const testRc = yield cust.instance.import(true);
99
- if (testRc < 0 || testRc > 8) {
100
- throw new Error(`Test import of customizing failed: check logs.`);
97
+ if (context.legacyBackend) {
98
+ trm_commons_1.Logger.log(`Skipping test import: legacy TRM backend`, true);
99
+ }
100
+ else {
101
+ trm_commons_1.Logger.loading(`Testing import...`);
102
+ const testRc = yield cust.instance.import(true);
103
+ if (testRc < 0 || testRc > 8) {
104
+ throw new Error(`Test import of customizing failed: check logs.`);
105
+ }
101
106
  }
102
107
  }));
103
108
  }
@@ -93,10 +93,15 @@ exports.prepareDevc = {
93
93
  trTarget: systemConnector_1.SystemConnector.getDest()
94
94
  });
95
95
  //4- test import transport
96
- trm_commons_1.Logger.loading(`Testing import...`);
97
- const testRc = yield context.runtime.transports.devc.instance.import(true);
98
- if (testRc < 0 || testRc > 8) {
99
- throw new Error(`Test import of SAP packages failed: check logs.`);
96
+ if (context.legacyBackend) {
97
+ trm_commons_1.Logger.log(`Skipping test import: legacy TRM backend`, true);
98
+ }
99
+ else {
100
+ trm_commons_1.Logger.loading(`Testing import...`);
101
+ const testRc = yield context.runtime.transports.devc.instance.import(true);
102
+ if (testRc < 0 || testRc > 8) {
103
+ throw new Error(`Test import of SAP packages failed: check logs.`);
104
+ }
100
105
  }
101
106
  }));
102
107
  }),
@@ -90,10 +90,15 @@ exports.prepareLang = {
90
90
  trTarget: systemConnector_1.SystemConnector.getDest()
91
91
  });
92
92
  //3- test import transport
93
- trm_commons_1.Logger.loading(`Testing import...`);
94
- const testRc = yield context.runtime.transports.lang.instance.import(true);
95
- if (testRc < 0 || testRc > 8) {
96
- throw new Error(`Test import of translations failed: check logs.`);
93
+ if (context.legacyBackend) {
94
+ trm_commons_1.Logger.log(`Skipping test import: legacy TRM backend`, true);
95
+ }
96
+ else {
97
+ trm_commons_1.Logger.loading(`Testing import...`);
98
+ const testRc = yield context.runtime.transports.lang.instance.import(true);
99
+ if (testRc < 0 || testRc > 8) {
100
+ throw new Error(`Test import of translations failed: check logs.`);
101
+ }
97
102
  }
98
103
  }));
99
104
  }),
@@ -75,10 +75,15 @@ exports.prepareTadir = {
75
75
  trTarget: systemConnector_1.SystemConnector.getDest()
76
76
  });
77
77
  //3- test import transport
78
- trm_commons_1.Logger.loading(`Testing import...`);
79
- const testRc = yield context.runtime.transports.tadir.instance.import(true);
80
- if (testRc < 0 || testRc > 8) {
81
- throw new Error(`Test import of workbench failed: check logs.`);
78
+ if (context.legacyBackend) {
79
+ trm_commons_1.Logger.log(`Skipping test import: legacy TRM backend`, true);
80
+ }
81
+ else {
82
+ trm_commons_1.Logger.loading(`Testing import...`);
83
+ const testRc = yield context.runtime.transports.tadir.instance.import(true);
84
+ if (testRc < 0 || testRc > 8) {
85
+ throw new Error(`Test import of workbench failed: check logs.`);
86
+ }
82
87
  }
83
88
  }));
84
89
  }),
@@ -58,7 +58,21 @@ exports.setInstallDevclass = {
58
58
  }
59
59
  }),
60
60
  run: (context) => __awaiter(void 0, void 0, void 0, function* () {
61
- var _a, _b, _c;
61
+ var _a, _b, _c, _d;
62
+ // trm-server and trm-rest are installed in `$` packages (`$TRM...`), unless they are already in their
63
+ // original `/ATRM/` packages: then they stay there. They are never renamed otherwise.
64
+ if (context.runtime.isTrmServer || context.runtime.isTrmRest) {
65
+ const original = (((_a = context.runtime.update) === null || _a === void 0 ? void 0 : _a.getDevclass()) || '').trim().toUpperCase().startsWith('/ATRM/');
66
+ context.rawInput.installData.installDevclass.replacements = (0, addNamespace_1.flattenDevclasses)(context.runtime.package.hierarchy).map(originalDevclass => ({
67
+ originalDevclass,
68
+ installDevclass: original ? originalDevclass : (0, commons_1.adjustTrmServerRestDevclass)(originalDevclass)
69
+ }));
70
+ context.rawInput.installData.installDevclass.keepOriginal = context.rawInput.installData.installDevclass.replacements
71
+ .every(o => o.installDevclass === o.originalDevclass);
72
+ (0, addNamespace_1.getInstallNamespace)(context);
73
+ trm_commons_1.Logger.log(`${context.rawInput.packageData.name} SAP packages: ${context.rawInput.installData.installDevclass.replacements.map(o => `${o.originalDevclass} -> ${o.installDevclass}`).join(', ')}`, true);
74
+ return;
75
+ }
62
76
  //1- find already defined replacements in system
63
77
  if (context.rawInput.installData.installDevclass.replacements.length <= 0) {
64
78
  //no input replacements = get from the trm table devclass replacements the corresponding name
@@ -78,7 +92,7 @@ exports.setInstallDevclass = {
78
92
  //the release root can have a new original name (e.g. moved to a namespace): a renamed installation keeps its root
79
93
  //package, matched by position, so the installation stays where the customer put it
80
94
  const releaseRoot = context.runtime.package.hierarchy.devclass;
81
- const installedRoot = (_a = context.runtime.update) === null || _a === void 0 ? void 0 : _a.getDevclass();
95
+ const installedRoot = (_b = context.runtime.update) === null || _b === void 0 ? void 0 : _b.getDevclass();
82
96
  if (installedRoot && !context.rawInput.installData.installDevclass.replacements.some(o => o.originalDevclass === releaseRoot)) {
83
97
  const storedRoot = context.rawInput.installData.installDevclass.replacements.find(o => o.installDevclass.trim().toUpperCase() === installedRoot.trim().toUpperCase());
84
98
  if (storedRoot && storedRoot.installDevclass !== storedRoot.originalDevclass) {
@@ -133,7 +147,7 @@ exports.setInstallDevclass = {
133
147
  if (context.runtime.update && hasCustomization) {
134
148
  //the installed root devclass can be unknown: fall back to the stored root replacement
135
149
  const installedRootDevclass = context.runtime.update.getDevclass()
136
- || ((_c = (_b = context.runtime.previousInstallPackages) === null || _b === void 0 ? void 0 : _b.find(o => o.originalDevclass === context.runtime.package.hierarchy.devclass)) === null || _c === void 0 ? void 0 : _c.installDevclass);
150
+ || ((_d = (_c = context.runtime.previousInstallPackages) === null || _c === void 0 ? void 0 : _c.find(o => o.originalDevclass === context.runtime.package.hierarchy.devclass)) === null || _d === void 0 ? void 0 : _d.installDevclass);
137
151
  if (installedRootDevclass) {
138
152
  updateNamespace = (0, commons_1.getPackageNamespace)(installedRootDevclass);
139
153
  }
@@ -1,5 +1,10 @@
1
1
  import { Step } from "@simonegaffurini/sammarksworkflow";
2
2
  import { InstallWorkflowContext } from ".";
3
+ /**
4
+ * Records the installed release: TRM packages table row, install packages and install transports.
5
+ * A trm-server/trm-rest upgrade also records it right after its import, which is never rolled back.
6
+ */
7
+ export declare function recordInstalledRelease(context: InstallWorkflowContext): Promise<void>;
3
8
  /**
4
9
  * Workflow step that records the installed release in the target system's TRM package table.
5
10
  *
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.updatePackageData = void 0;
13
+ exports.recordInstalledRelease = recordInstalledRelease;
13
14
  const trm_commons_1 = require("trm-commons");
14
15
  const systemConnector_1 = require("../../systemConnector");
15
16
  const utils_1 = require("../commons/utils");
@@ -33,20 +34,12 @@ function installDevcRows(packageName, packageRegistry, replacements) {
33
34
  }));
34
35
  }
35
36
  /**
36
- * Workflow step that records the installed release in the target system's TRM package table.
37
- *
38
- * Creates/update record in TRM packages table, with its install packages and the
39
- * customizing and translation transports imported on this system
40
- *
41
- * 1- commit new values
42
- *
43
- */
44
- exports.updatePackageData = {
45
- name: 'update-package-data',
46
- run: (context) => __awaiter(void 0, void 0, void 0, function* () {
37
+ * Records the installed release: TRM packages table row, install packages and install transports.
38
+ * A trm-server/trm-rest upgrade also records it right after its import, which is never rolled back.
39
+ */
40
+ function recordInstalledRelease(context) {
41
+ return __awaiter(this, void 0, void 0, function* () {
47
42
  var _a, _b;
48
- trm_commons_1.Logger.loading(`Finalizing install...`);
49
- //1- commit new values
50
43
  trm_commons_1.Logger.loading(`Updating TRM data...`);
51
44
  const originalTransport = context.runtime.transports.tadir.instance.trkorr;
52
45
  const installTransport = (_a = context.output.transport) === null || _a === void 0 ? void 0 : _a.trkorr;
@@ -79,7 +72,8 @@ exports.updatePackageData = {
79
72
  : [])
80
73
  ]);
81
74
  context.revert.metadataPackageRegistry = packageRegistry;
82
- if (context.runtime.update) {
75
+ // A trm-server/trm-rest upgrade is not rolled back after its import: nothing to restore
76
+ if (context.runtime.update && !context.runtime.selfUpdate) {
83
77
  // The installed packages may have been read without their rows: read the stored row
84
78
  // before writing, so the revert restores it exactly, or removes the row it wrote.
85
79
  // A failed read aborts before any write.
@@ -101,18 +95,43 @@ exports.updatePackageData = {
101
95
  // fail while returning the response.
102
96
  context.revert.metadataWriteStarted = true;
103
97
  yield systemConnector_1.SystemConnector.setInstallDevc(installDevc);
104
- if (staleInstallDevc.length > 0) {
105
- yield systemConnector_1.SystemConnector.deleteInstallDevc(staleInstallDevc);
98
+ // A legacy backend has neither API: trm-server and trm-rest have no stale mappings nor install transports
99
+ if (!context.legacyBackend) {
100
+ if (staleInstallDevc.length > 0) {
101
+ yield systemConnector_1.SystemConnector.deleteInstallDevc(staleInstallDevc);
102
+ }
103
+ context.revert.metadataTransportsWriteStarted = true;
104
+ yield systemConnector_1.SystemConnector.setInstallTransports(context.rawInput.packageData.name, packageRegistry, installTr);
106
105
  }
107
- context.revert.metadataTransportsWriteStarted = true;
108
- yield systemConnector_1.SystemConnector.setInstallTransports(context.rawInput.packageData.name, packageRegistry, installTr);
109
106
  yield systemConnector_1.SystemConnector.updateTrmPackageData(context.revert.metadataPackageRow);
107
+ });
108
+ }
109
+ /**
110
+ * Workflow step that records the installed release in the target system's TRM package table.
111
+ *
112
+ * Creates/update record in TRM packages table, with its install packages and the
113
+ * customizing and translation transports imported on this system
114
+ *
115
+ * 1- commit new values
116
+ *
117
+ */
118
+ exports.updatePackageData = {
119
+ name: 'update-package-data',
120
+ run: (context) => __awaiter(void 0, void 0, void 0, function* () {
121
+ trm_commons_1.Logger.loading(`Finalizing install...`);
122
+ //1- commit new values
123
+ yield recordInstalledRelease(context);
110
124
  }),
111
125
  revert: (context) => __awaiter(void 0, void 0, void 0, function* () {
112
126
  // First installs remove the newly written row through the same atomic SAP operation.
113
127
  if (!context.revert.metadataWriteStarted) {
114
128
  return;
115
129
  }
130
+ if (context.runtime.selfUpdate) {
131
+ // The imported release stays installed (see import-batch): its record is kept
132
+ trm_commons_1.Logger.log(`Keeping the TRM data of the imported ${context.rawInput.packageData.name}`, true);
133
+ return;
134
+ }
116
135
  if (!context.runtime.update) {
117
136
  if (context.revert.metadataPackageRow) {
118
137
  yield systemConnector_1.SystemConnector.restoreInstallMetadata({
@@ -49,6 +49,7 @@ const trm_commons_1 = require("trm-commons");
49
49
  const _1 = require(".");
50
50
  const parse_multipart_data_1 = require("parse-multipart-data");
51
51
  const messageLog_1 = require("./messageLog");
52
+ const https_1 = require("https");
52
53
  const AXIOS_CTX = "RestServer";
53
54
  function getErrorMessage(error) {
54
55
  return error instanceof Error ? error.message : String(error);
@@ -96,6 +97,9 @@ class RESTClient {
96
97
  password: this._login.passwd
97
98
  },
98
99
  timeout: 30000, //default timeout
100
+ httpsAgent: new https_1.Agent({
101
+ rejectUnauthorized: false
102
+ })
99
103
  }, AXIOS_CTX);
100
104
  }
101
105
  open() {
@@ -506,11 +510,8 @@ class RESTClient {
506
510
  }
507
511
  importTransport(trkorr, system, test) {
508
512
  return __awaiter(this, void 0, void 0, function* () {
509
- const result = (yield this._axiosInstance.post('/import_tr', {
510
- system: system.trim().toUpperCase(),
511
- trkorr: trkorr.trim().toUpperCase(),
512
- test: test ? 'X' : ' '
513
- })).data;
513
+ // as RFC: TEST is only sent for a test import
514
+ const result = (yield this._axiosInstance.post('/import_tr', Object.assign({ system: system.trim().toUpperCase(), trkorr: trkorr.trim().toUpperCase() }, (test ? { test: 'X' } : {})))).data;
514
515
  if (result && result.testResult) {
515
516
  return result.testResult;
516
517
  }
@@ -631,11 +631,9 @@ class RFCClient {
631
631
  }
632
632
  importTransport(trkorr, system, test) {
633
633
  return __awaiter(this, void 0, void 0, function* () {
634
- const result = yield this._call("/ATRM/IMPORT_TR", {
635
- system: system.trim().toUpperCase(),
636
- trkorr: trkorr.trim().toUpperCase(),
637
- test: test ? 'X' : ' '
638
- });
634
+ // TEST is only sent for a test import: releases before trm-server 7 have no such parameter, and
635
+ // their RFC metadata stays cached in this process even after a newer release is imported
636
+ const result = yield this._call("/ATRM/IMPORT_TR", Object.assign({ system: system.trim().toUpperCase(), trkorr: trkorr.trim().toUpperCase() }, (test ? { test: 'X' } : {})));
639
637
  if (result && result.testResult) {
640
638
  return this.parseImportTransportResult(result['testResult'], '/ATRM/IMPORT_TR');
641
639
  }
@@ -430,14 +430,36 @@ class SystemConnectorBase {
430
430
  }
431
431
  getExistingObjects(objects) {
432
432
  return __awaiter(this, void 0, void 0, function* () {
433
- var ret = [];
433
+ // TADIR is read in batches of objects of the same type: one read per object is slow on large packages
434
+ const batchSize = 25;
435
+ const key = (pgmid, object, objName) => `${pgmid.trim().toUpperCase()} ${object.trim().toUpperCase()} ${objName.trim().toUpperCase()}`;
436
+ const found = new Map();
437
+ const singles = [];
438
+ const byType = new Map();
434
439
  for (const object of objects) {
440
+ // an option line holds 72 characters: "OR ( OBJ_NAME EQ '...' )"
441
+ if (object.objName.trim().replace(/'/g, "''").length > 50 || /\s(AND|OR)\s/i.test(object.objName)) {
442
+ singles.push(object);
443
+ continue;
444
+ }
445
+ const type = `${object.pgmid.trim().toUpperCase()} ${object.object.trim().toUpperCase()}`;
446
+ byType.set(type, [...(byType.get(type) || []), object]);
447
+ }
448
+ for (const [type, typeObjects] of byType) {
449
+ const [pgmid, object] = type.split(' ');
450
+ for (let i = 0; i < typeObjects.length; i += batchSize) {
451
+ const names = typeObjects.slice(i, i + batchSize).map(o => o.objName.trim().toUpperCase().replace(/'/g, "''"));
452
+ const tadir = yield this.readTable('TADIR', [{ fieldName: 'PGMID' }, { fieldName: 'OBJECT' }, { fieldName: 'OBJ_NAME' }, { fieldName: 'DEVCLASS' }, { fieldName: 'SRCSYSTEM' }, { fieldName: 'AUTHOR' }], `PGMID EQ '${pgmid}' AND OBJECT EQ '${object}' AND ( ${names.map(name => `OBJ_NAME EQ '${name}'`).join(' OR ')} )`);
453
+ tadir.forEach(o => found.set(key(o.pgmid, o.object, o.objName), o));
454
+ }
455
+ }
456
+ for (const object of singles) {
435
457
  const oTadir = yield this.getObject(object.pgmid, object.object, object.objName);
436
458
  if (oTadir) {
437
- ret.push(oTadir);
459
+ found.set(key(object.pgmid, object.object, object.objName), oTadir);
438
460
  }
439
461
  }
440
- return ret;
462
+ return objects.map(o => found.get(key(o.pgmid, o.object, o.objName))).filter(Boolean);
441
463
  });
442
464
  }
443
465
  getNamespace(namespace) {