@sap/cds-dk 10.0.6 → 10.0.7

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 (38) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/lib/import/asyncapi/parserContext.js +136 -54
  3. package/lib/init/template/nodejs/index.js +4 -2
  4. package/node_modules/@cap-js/openapi/lib/compile/csdl.js +3 -3
  5. package/node_modules/@cap-js/openapi/lib/compile/csdl2openapi.js +111 -103
  6. package/node_modules/@cap-js/openapi/lib/compile/diagram.js +4 -4
  7. package/node_modules/@cap-js/openapi/lib/compile/types.d.ts +3 -1
  8. package/node_modules/@cap-js/openapi/package.json +3 -3
  9. package/node_modules/@sap/cds/CHANGELOG.md +7 -0
  10. package/node_modules/@sap/cds/lib/compile/for/nodejs.js +1 -0
  11. package/node_modules/@sap/cds/libx/_runtime/messaging/common-utils/authorizedRequest.js +3 -2
  12. package/node_modules/@sap/cds/package.json +1 -1
  13. package/node_modules/@sap/cds-mtxs/CHANGELOG.md +36 -0
  14. package/node_modules/@sap/cds-mtxs/bin/cds-mtx-migrate.js +32 -48
  15. package/node_modules/@sap/cds-mtxs/bin/cds-mtx.js +1 -0
  16. package/node_modules/@sap/cds-mtxs/cds-plugin.js +18 -0
  17. package/node_modules/@sap/cds-mtxs/lib/migration/migration.js +3 -408
  18. package/node_modules/@sap/cds-mtxs/package.json +1 -1
  19. package/node_modules/@sap/cds-mtxs/srv/cf/abstract-provisioning-service.js +5 -6
  20. package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/annotations.js +5 -2
  21. package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/index.js +6 -5
  22. package/node_modules/@sap/cds-mtxs/srv/extensibility-service.cds +0 -11
  23. package/node_modules/@sap/cds-mtxs/srv/extensibility-service.js +12 -22
  24. package/node_modules/@sap/cds-mtxs/srv/jobs-service.js +30 -24
  25. package/node_modules/@sap/cds-mtxs/srv/model-provider.js +2 -2
  26. package/node_modules/@sap/cds-mtxs/srv/plugins/hana/ctnr-mgr-base.js +5 -2
  27. package/node_modules/@sap/cds-mtxs/srv/plugins/hana/hdi.js +5 -3
  28. package/node_modules/@sap/cds-mtxs/srv/plugins/hana/srv-mgr.js +3 -2
  29. package/node_modules/@sap/cds-mtxs/srv/plugins/hana/tms.js +17 -0
  30. package/node_modules/@sap/cds-mtxs/srv/plugins/hana.js +14 -13
  31. package/node_modules/sax/lib/sax.js +27 -9
  32. package/node_modules/sax/package.json +1 -1
  33. package/node_modules/ws/package.json +1 -1
  34. package/package.json +4 -4
  35. package/lib/init/template/nodejs/files/.gitignore.hbs +0 -34
  36. package/node_modules/@sap/cds-mtxs/lib/migration/extension-project-builder.js +0 -129
  37. package/node_modules/@sap/cds-mtxs/lib/migration/extension-project.js +0 -89
  38. package/node_modules/@sap/cds-mtxs/lib/migration/mtx-adapter.js +0 -230
@@ -1,12 +1,6 @@
1
- const path = require('path')
2
- const mtxAdapter = require('./mtx-adapter')
3
- const { createProjects } = require('./extension-project')
4
1
  const cds = require('@sap/cds')
5
- const LOG = cds.log('mtx'), DEBUG = cds.debug('mtx')
6
- const { mkdirp, rimraf, tar, exists, readdir, read, write, isdir, isfile } = cds.utils
7
- const fs = cds.utils.fs.promises
2
+ const LOG = cds.log('mtx')
8
3
 
9
- const linter = require('../../srv/extensibility/linter')
10
4
  const { t0_ } = require('../utils')
11
5
  const Tenants = 'cds.xt.Tenants'
12
6
 
@@ -14,14 +8,6 @@ const Tenants = 'cds.xt.Tenants'
14
8
  module.exports._hasMtEnv = () => cds.requires.multitenancy || (cds.env.requires['cds.xt.DeploymentService'] && cds.env.requires['cds.xt.ModelProviderService'])
15
9
  module.exports._hasExtensibilityEnv = () => cds.requires.extensibility || cds.env.requires['cds.xt.ExtensibilityService']
16
10
 
17
- module.exports._getExtensionProjectDirectory = function(dir) {
18
- if (dir) {
19
- LOG.log('Project directory', dir, 'does not exist.', dir, 'has been created.')
20
- return mkdirp(dir)
21
- }
22
- return mtxAdapter.mkdirTemp()
23
- }
24
-
25
11
  class MigrationResult {
26
12
  constructor() {
27
13
  this.connectedTenants = []
@@ -29,9 +15,8 @@ class MigrationResult {
29
15
  this.hasError = false
30
16
  }
31
17
 
32
- addTenant(tenant, meta = true) {
18
+ addTenant(tenant) {
33
19
  this.connectedTenants.push(tenant)
34
- if (meta) this.connectedTenants.push(mtxAdapter.getMetaTenantName(tenant))
35
20
  }
36
21
 
37
22
  log(tenant, message) {
@@ -59,17 +44,13 @@ function getT0() {
59
44
  return cds.env.requires.multitenancy?.t0 ?? 't0'
60
45
  }
61
46
 
62
- module.exports.getDbId = async function () {
63
- return mtxAdapter.getDbId()
64
- }
65
-
66
47
  module.exports.syncTenantList = async function syncTenantList(tenants, options, deleteEntries = false) {
67
48
  const EXCLUDED_TENANTS = /^MT_LIB_TENANT-.*/
68
49
 
69
50
  const migrationResult = new MigrationResult()
70
51
  const { dry, force } = options
71
52
  const t0 = getT0()
72
- migrationResult.addTenant(t0, false) // adds tenant for disconnect
53
+ migrationResult.addTenant(t0) // adds tenant for disconnect
73
54
 
74
55
  // get all containers + create metadata in t0
75
56
  const ds = await cds.connect.to('cds.xt.DeploymentService')
@@ -96,390 +77,4 @@ module.exports.syncTenantList = async function syncTenantList(tenants, options,
96
77
  }
97
78
  migrationResult.log(t0, `Tenant list created for ${JSON.stringify(tenantContainers)} ${dry ? '(dry run only)' : ''}.`)
98
79
  return migrationResult
99
- }
100
-
101
- module.exports.cleanupMetatenants = async function cleanup(tenants, options) {
102
- const migrationResult = new MigrationResult()
103
- const { dry } = options
104
-
105
- let tenantsToCleanup = tenants
106
- // handle tenant "*" -> retrieve all tenants
107
- if (tenants.includes('*')) {
108
- tenantsToCleanup = await mtxAdapter.getAllTenantIds()
109
- }
110
-
111
- const t0 = getT0()
112
- migrationResult.addTenant(t0, false) // adds tenant for disconnect
113
-
114
- const metatenantsToCleanup = {}
115
- for (const tenant of tenantsToCleanup) {
116
- if (tenant === t0) continue
117
- const metatenant = mtxAdapter.getMetaTenantName(tenant)
118
- metatenantsToCleanup[tenant] = metatenant
119
- }
120
-
121
- for (const [tenant, metatenant] of Object.entries(metatenantsToCleanup)) {
122
- migrationResult.addTenant(metatenant, false)
123
-
124
- try {
125
- // check if tenant was already migrated
126
- try {
127
- const migrated = tenant !== '__META__' ? await mtxAdapter.isMigrated(tenant) : { timestamp: true }
128
- if (!migrated.timestamp) {
129
- migrationResult.error(tenant, `Tenant ${tenant} was not migrated. Skipping cleanup.`, new Error(`Tenant ${tenant} was not migrated`))
130
- continue
131
- }
132
- } catch (e) {
133
- if (e.status === 404) {
134
- migrationResult.log(tenant, `No @sap/cds-mtx meta-tenant found for ${tenant}. Skipping cleanup.`)
135
- continue
136
- } else throw e
137
- }
138
-
139
- // do the cleanup
140
- if (!dry) {
141
- const ds = await cds.connect.to('cds.xt.DeploymentService')
142
- await ds.unsubscribe(metatenant)
143
- }
144
- migrationResult.log(metatenant, `Metatenant ${metatenant} successfully deleted.`)
145
- } catch (error) {
146
- migrationResult.error(metatenant, `Cleanup of tenant ${metatenant} failed.`, error)
147
- continue
148
- }
149
- }
150
-
151
- // only cleanup __META__ if all other meta tenants have been cleaned up successfully
152
- if (!dry && tenants.includes('*') && !migrationResult.hasError) {
153
- const ds = await cds.connect.to('cds.xt.DeploymentService')
154
- const commonMetaTenant = '__META__'
155
- await ds.unsubscribe(commonMetaTenant)
156
- migrationResult.log(commonMetaTenant, `Common metatenant ${commonMetaTenant} successfully deleted.`)
157
- }
158
-
159
- LOG.log('Cleanup done')
160
- return migrationResult
161
- }
162
-
163
- module.exports.checkMigration = async function checkMigration(req) {
164
- const { tenant } = req.data
165
- if (await mtxAdapter.hasExtensions(tenant)) {
166
- if (module.exports._hasExtensibilityEnv()) {
167
- if (!await mtxAdapter.isMigrated(tenant))
168
- req.reject(422, `Upgrade of tenant ${tenant} aborted. Extensions have not been migrated yet`)
169
- } else {
170
- req.reject(422, `Upgrade of tenant ${tenant} aborted. Old MTX Extensions exist but extensibility is not configured (cds.requires.extensibility is false)`)
171
- }
172
- }
173
- }
174
-
175
- module.exports.getMissingMtxTenants = async (existingTenants) => {
176
- if (await mtxAdapter.wasOldMtx()) {
177
- // TODO do this only once
178
- // get all mtx tenants
179
- const mtxTenants = await mtxAdapter.getAllTenantIds()
180
- // add metadata for non-existing entries
181
- return await Promise.all(mtxTenants.filter( mtxTenant => !existingTenants.includes(mtxTenant)).map( async mtxTenant => {
182
- await module.exports.addMetadata(mtxTenant, {
183
- subscribedTenantId: mtxTenant
184
- })
185
- return mtxTenant
186
- }))
187
- }
188
- return []
189
- }
190
-
191
- module.exports.migrate = async function migrate(tenants, options) {
192
-
193
- const { directory, dry, force, tagRule, tag: defaultTag, "skip-verification": skipVerification} = options
194
-
195
- const migrationResult = new MigrationResult()
196
- let tenantsToMigrate = tenants
197
-
198
- // handle tenant "*" -> retrieve all tenants
199
- if (tenants.includes('*')) {
200
- tenantsToMigrate = await mtxAdapter.getAllTenantIds()
201
- }
202
-
203
- const t0 = getT0()
204
- migrationResult.addTenant(t0, false) // adds tenant for disconnect
205
-
206
- const extensibility = module.exports._hasExtensibilityEnv()
207
- let projectsDir
208
-
209
- for (const tenant of tenantsToMigrate) {
210
-
211
- migrationResult.addTenant(tenant)
212
-
213
- // skip migration of t0
214
- if (tenant === t0) {
215
- migrationResult.log(tenant, `Skipping internal tenant ${tenant}.`)
216
- continue
217
- }
218
-
219
- try {
220
- // check if tenant was already migrated
221
- try {
222
- const migrated = await mtxAdapter.isMigrated(tenant)
223
- if (migrated?.timestamp && !force) {
224
- migrationResult.log(tenant, `Tenant ${tenant} is already migrated. Skipping migration.`)
225
- continue
226
- }
227
- } catch (e) {
228
- if (e.status === 404) {
229
- migrationResult.log(tenant, `No @sap/cds-mtx meta-tenant found for ${tenant}. Skipping migration.`)
230
- continue
231
- } else throw e
232
- }
233
-
234
- // Get from old mtx
235
- const metadata = await mtxAdapter.getMetadata(tenant)
236
-
237
- if (extensibility) {
238
-
239
- // update tenant metadata + extension tables
240
- // add extension tables - TODO could also be done on-the-fly with push
241
- const extTables = await cds.load(`${__dirname}/../../db/extensions.cds`)
242
- cds.model = cds.db.model = cds.compile.for.nodejs(extTables)
243
- if (!dry) {
244
- // disable extension deployment to avoid lengthy redeployments when doing the push()
245
- const ds = await cds.connect.to('cds.xt.DeploymentService')
246
- ds.prepend (srv => srv.on ('extend', () => { }))
247
-
248
- try {
249
- await cds.tx({ tenant}, tx => tx.run(SELECT.one(1).from('cds.xt.Extensions')))
250
- // add metadata in case it had not been added before
251
- await module.exports.addMetadata(tenant, metadata)
252
- } catch (error) {
253
- LOG.log('cds.xt.Extensions not yet deployed, deploying ...')
254
- DEBUG && DEBUG(error)
255
- await ds.subscribe({ tenant, metadata, options: { csn: extTables } }) // also creates metadata ...
256
- }
257
- }
258
-
259
- // run extend -> into memory?
260
- const mtxExtension = await mtxAdapter.getExtension(tenant)
261
-
262
- if (mtxExtension) {
263
- // allow different folder from command line to preserve results
264
- projectsDir = projectsDir || await module.exports._getExtensionProjectDirectory(directory)
265
- const tenantProjectFolder = path.join(projectsDir, tenant)
266
-
267
- const tags = await createProjects(mtxExtension, tenantProjectFolder, tagRule, defaultTag)
268
-
269
- // upload and verify extensions
270
- // iterate all tags
271
- migrationResult.log(tenant, `Created projects for tags "${tags}"`)
272
-
273
- // Verify migrated extension
274
- if (!skipVerification) {
275
- try {
276
- // check diff and abort if necessary
277
- const existingCsn = await mtxAdapter.getCsn(tenant)
278
-
279
- await fs.mkdir(path.join(tenantProjectFolder, 'mtx_original_csn'), { recursive: true })
280
- await fs.writeFile(path.join(tenantProjectFolder, 'mtx_original_csn', 'csn.json'), JSON.stringify(existingCsn, null, 2))
281
-
282
- await _verifyExtension(migrationResult, tenantProjectFolder, tenant, tags, existingCsn, options)
283
- migrationResult.log(tenant, `Extension verification successful for tenant ${tenant} [${tenantProjectFolder}]`)
284
- } catch (error) {
285
- migrationResult.error(tenant, `Extension verification failed for tenant ${tenant} [${tenantProjectFolder}]), skipping migration.`, error)
286
- continue
287
- }
288
- } else {
289
- migrationResult.log(tenant, `Extension verification skipped for tenant ${tenant}`)
290
- }
291
-
292
- if (!dry && force) {
293
- // cleanup in case extension ids have changed
294
- if (tenant) cds.context = { tenant }
295
- await DELETE.from('cds.xt.Extensions')
296
- }
297
-
298
- for (const tag of tags) {
299
- const projectFolder = path.join(tenantProjectFolder, tag)
300
-
301
- if (!dry) {
302
- try {
303
- const extensionTgz = await fs.readFile(path.join(projectFolder, 'gen', 'extension.tgz'))
304
- const es = await cds.connect.to('cds.xt.ExtensibilityService')
305
- await es.tx({ tenant, user: new cds.User.Privileged }, tx =>
306
- tx.push(extensionTgz.toString('base64'), tag)
307
- )
308
- migrationResult.log(tenant, `Extension for tenant ${tenant} and tag ${tag} pushed.`)
309
- } catch (error) {
310
- migrationResult.error(tenant, `Error pushing extension for tenant ${tenant} and tag ${tag}`, error)
311
- continue
312
- }
313
- }
314
- }
315
- }
316
- } else {
317
- // check if extensions exist -> abort if yes
318
- if (await mtxAdapter.hasExtensions(tenant)) throw new Error(`Extensions exist but extensibility is not configured (cds.requires.extensibilty is false)`)
319
-
320
- if (!dry) {
321
- await module.exports.addMetadata(tenant, metadata)
322
- migrationResult.log(tenant, `Metadata for tenant ${tenant} added.`)
323
- }
324
- }
325
-
326
- if (!dry) await mtxAdapter.setMigrated(tenant, tagRule, defaultTag, !!force /* ensure it is not undefined so that a record is written */)
327
- migrationResult.log(tenant, `Migration of tenant ${tenant} done.`)
328
- } catch (error) {
329
- migrationResult.error(tenant, `Migration of tenant ${tenant} failed.`, error)
330
- }
331
- }
332
-
333
- // cleanup tmpdir, keep directory if it was set externally
334
- if (!directory && projectsDir) await fs.rm(projectsDir, { recursive: true, force: true })
335
-
336
- LOG.log('Extension migration done')
337
- return migrationResult
338
- }
339
-
340
- module.exports.addMetadata = async function addMetadata(tenant, metadata) {
341
- try {
342
- // can't use UPSERT here so @cds.on.insert still works for createdAt
343
- await t0_(INSERT.into('cds.xt.Tenants', { ID: tenant, metadata: JSON.stringify(metadata) }))
344
- } catch (e) {
345
- if (e.message === 'ENTITY_ALREADY_EXISTS' || e.code === 301 || e.message.match(/unique constraint/i)) {
346
- await t0_(UPSERT.into('cds.xt.Tenants', { ID: tenant, metadata: JSON.stringify(metadata) }))
347
- } else throw e
348
- }
349
- }
350
-
351
- async function _verifyExtension(migrationResult, tenantProjectFolder, tenant, tags, existingCsn, options) {
352
-
353
- const { "ignore-migrations": ignoreMigrations } = options
354
-
355
- const projectFolders = tags
356
-
357
- const mp = await cds.connect.to('cds.xt.ModelProviderService')
358
- const mainCsn = await mp.getCsn({ tenant, flavor: 'inferred', activated: true, base: true, options: { skipMinify: true } })
359
-
360
- if (!mainCsn) throw new Error(`Verification error for tenant ${tenant}: Empty base model`)
361
-
362
- let previewCsn = mainCsn
363
-
364
- // calculate model with extensions to be pushed later
365
- for (const projectFolder of projectFolders) {
366
- const extensionCsnString = await fs.readFile(path.join(tenantProjectFolder, projectFolder, 'gen', 'ext', 'extension.csn'))
367
- const extensionCsn = JSON.parse(extensionCsnString)
368
- previewCsn = cds.extend(previewCsn).with(extensionCsn)
369
- }
370
-
371
- // ensure flavor
372
- const inferredExistingCsn = cds.compile({inferred: existingCsn}, {flavor: 'inferred'})
373
-
374
- const existingHana = cds.compiler.to.hdi.migration(inferredExistingCsn)
375
- const newHana = cds.compiler.to.hdi.migration(previewCsn)
376
-
377
- const diffMessages = []
378
-
379
- // are artifacts lost?
380
- const hanaDiffNewToOld = cds.compiler.to.hdi.migration(cds.minify(previewCsn), {}, cds.minify(existingHana.afterImage))
381
- if (hanaDiffNewToOld.deletions.length) {
382
- diffMessages.push(`Migrated model is missing artifacts:\n ${hanaDiffNewToOld.deletions.map( ({ name, suffix }) => `${name}${suffix}\n`)}`)
383
- }
384
-
385
- if (hanaDiffNewToOld.migrations.length) {
386
- const ignore = ignoreMigrations ?? '^sap.common'
387
- const relevantMigrations = hanaDiffNewToOld.migrations.filter( m => !new RegExp(ignore).test(m.name))
388
- migrationResult.log(tenant, `Table migrations found but ignored for /${ignore}/`)
389
-
390
- if (relevantMigrations.length) diffMessages.push(`Table migrations found\n` +
391
- `${relevantMigrations.map( ({ name, suffix, changeset }) => ` ${name}${suffix}: ${changeset.map(({sql}) => sql)}\n`)}`)
392
- }
393
-
394
- // does the new model contain more artifacts?
395
- const MTXS_ENTITIES = ['cds.xt.Extensions']
396
- const hanaDiffOldToNew = cds.compiler.to.hdi.migration(cds.minify(inferredExistingCsn), {}, cds.minify(newHana.afterImage)) // cds.xt.Extensions is allowed
397
- const filteredDeletions = hanaDiffOldToNew.deletions.filter( ({name}) => !MTXS_ENTITIES.includes(name))
398
- if (filteredDeletions.length) {
399
- diffMessages.push(`Migrated model has additional artifacts:\n ${hanaDiffOldToNew.deletions.map( ({ name, suffix }) => `${name}${suffix}\n`)}`)
400
- throw new Error(`Verification error for tenant ${tenant}: migrated model has additional artifacts:\n ${hanaDiffOldToNew.deletions.map( ({ name, suffix }) => `${name}${suffix}\n`)}`)
401
- }
402
-
403
- if (diffMessages.length) throw new Error(`Verification error for tenant ${tenant}:\n${diffMessages.join('\n')}`)
404
- }
405
-
406
- module.exports.getMigratedProjects = async (req, tagRule, defaultTag, tenant) => {
407
- const temp = await mtxAdapter.mkdirTemp()
408
-
409
- let parameters = { tagRule, defaultTag }
410
- if (!(tagRule || defaultTag)) {
411
- try {
412
- const storedParameters = await mtxAdapter.getMigrationParameters(tenant) ?? {}
413
- parameters = { ...parameters, ...storedParameters }
414
- } catch (e) {
415
- req.reject(404, `No migrated projects found for tenant ${tenant}: ${e.message}`)
416
- }
417
- }
418
-
419
- // run dry migration with force + own temp directory
420
- // { directory, dry, force, tagRule: tagRegex, tag: defaultTag, "skip-verification": skipVerification, "ignore-migrations": ignoreMigrations }
421
- await module.exports.migrate([tenant], { directory: temp, dry: true, skipVerification: true, tagRule: parameters.tagRule, tag: parameters.defaultTag, force: true})
422
-
423
- const projectsLocation = path.join(temp, tenant)
424
- if (!exists(projectsLocation)) req.reject(404, `No migrated projects found for tenant ${tenant}`)
425
-
426
- // postprocessing: remove cds-based base model, adjust references
427
- await module.exports.fixBaseModelReferences(projectsLocation)
428
-
429
- try {
430
- return await tar.cz(projectsLocation)
431
- } finally {
432
- rimraf(temp)
433
- }
434
- }
435
-
436
- module.exports.fixBaseModelReferences = async (directory) => {
437
-
438
- await traverse(directory, async (file) => {
439
- // remove base cds files
440
- if (/.*\/node_modules\/_base\//.test(file)) {
441
- await rimraf(file)
442
- }
443
-
444
- // remove gen folder
445
- if (new RegExp(`${directory}/.*/gen$`).test(file)) {
446
- await rimraf(file)
447
- }
448
-
449
- // fix using statements
450
- if (/.*\.cds$/.test(file) && isfile(file)) {
451
- const content = await read(file, 'utf-8')
452
- const fixedContent = content.replace(/'_base\/.*'/g, '\'_base\'')
453
- await write(file, fixedContent, 'utf-8')
454
- }
455
- })
456
-
457
- // add index.csn and .cdsrc.json
458
- await traverse(directory, async (file) => {
459
- if (/.*\/node_modules\/_base$/.test(file)) {
460
- await addBaseAndConfig(file)
461
- }
462
- })
463
-
464
- async function traverse(dir, visitor) {
465
- const list = await readdir(dir)
466
- for (const entry of list) {
467
- const fullPath = path.join(dir, entry)
468
- await visitor(fullPath)
469
- if (await isdir(fullPath)) {
470
- await traverse(fullPath, visitor)
471
- }
472
- }
473
- }
474
- }
475
-
476
- const addBaseAndConfig = async function (directory) {
477
- const { 'cds.xt.ModelProviderService': mps } = cds.services
478
- const csn = await mps.getCsn({
479
- base: true, // without any custom extensions
480
- flavor: 'xtended'
481
- })
482
- await write(path.join(directory, 'index.csn'), cds.compile.to.json(csn))
483
- const config = linter.configCopyFrom(cds.env)
484
- await write(path.join(directory, '.cdsrc.json'), JSON.stringify(config, null, 2))
485
80
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-mtxs",
3
- "version": "3.9.5",
3
+ "version": "4.0.2",
4
4
  "description": "SAP Cloud Application Programming Model - Multitenancy library",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "keywords": [
@@ -2,7 +2,6 @@ const cds = require('@sap/cds/lib')
2
2
  const LOG = cds.log('mtx'), DEBUG = cds.debug('mtx')
3
3
  const main = require('../config')
4
4
  const { fetchResiliently, token } = require('../../lib/utils')
5
- const migration = require('../../lib/migration/migration')
6
5
  const DeploymentService = 'cds.xt.DeploymentService'
7
6
  const JobsService = 'cds.xt.JobsService'
8
7
  const Tenants = 'cds.xt.Tenants'
@@ -89,6 +88,7 @@ module.exports = class ProvisioningService extends cds.ApplicationService {
89
88
 
90
89
 
91
90
  _toMetadataJson(metadata, subscribedTenantId) {
91
+ if (!metadata) return { subscribedTenantId };
92
92
  try {
93
93
  return JSON.parse(metadata);
94
94
  } catch (error) {
@@ -114,9 +114,7 @@ module.exports = class ProvisioningService extends cds.ApplicationService {
114
114
 
115
115
  async _getTenants() {
116
116
  const ds = await cds.connect.to(DeploymentService)
117
- const tenants = await ds.getTenants()
118
- const mtxTenants = await migration.getMissingMtxTenants(tenants)
119
- const all = [...tenants, ...mtxTenants]
117
+ const all = await ds.getTenants()
120
118
  if (cds.env.requires[this.name]?.upgrade?.ignoreNonExistingContainers) {
121
119
  const containers = await ds.getContainers()
122
120
  return all.filter(t => containers.includes(t))
@@ -140,12 +138,13 @@ module.exports = class ProvisioningService extends cds.ApplicationService {
140
138
  await resources4(out)
141
139
  await csvs4('base', out)
142
140
  }
143
- const { clusterSize = 1 } = cds.env.requires.multitenancy.jobs ?? cds.env.requires[this.name]?.jobs ?? {}
144
141
  const { 'cds.xt.DeploymentService': ds } = cds.services
142
+ const js = await cds.connect.to(JobsService)
143
+ const { clusterSize = 3 } = js._config()
145
144
  const dbToTenants = (clusterSize > 1 && ds) ? await ds.tenantsByDb(tenants) : [new Set(tenants)]
146
145
  LOG.info('upgrading', { tenants })
147
146
 
148
- const js = await cds.connect.to(JobsService)
147
+
149
148
  return await new Promise((resolve, reject) => {
150
149
  const tx = js.tx({ tenant: cds.context.tenant, user: new cds.User.Privileged() })
151
150
  const job = tx.enqueue(DeploymentService, 'upgrade', dbToTenants, { options }, async error => {
@@ -8,6 +8,7 @@ const checkedExtensionAnnotations = new Map([
8
8
  ['@cds.persistence.journal', _createJournalAnnotationWarning],
9
9
  ['@mandatory', _createMandatoryAnnotationWarning],
10
10
  ['@readonly', _createMandatoryAnnotationWarning],
11
+ ['@Common.FieldControl', _createMandatoryAnnotationWarning],
11
12
  ['@assert.notNull', _createMandatoryAnnotationWarning],
12
13
  ['@assert.range', _createMandatoryAnnotationWarning]
13
14
  ])
@@ -18,6 +19,7 @@ const criticalExtensionAnnotations = new RegExp('^@(?:'
18
19
  + '|restrict'
19
20
  + '|readonly'
20
21
  + '|mandatory'
22
+ + '|Common\\.FieldControl'
21
23
  + '|assert.*'
22
24
  + '|cds.persistence.*'
23
25
  + '|sql.append'
@@ -31,7 +33,8 @@ const criticalExtensionAnnotations = new RegExp('^@(?:'
31
33
  + '|cds.query.limit'
32
34
  + '|cds.localized'
33
35
  + '|cds.valid.*'
34
- + '|cds.search)'
36
+ + '|cds.search'
37
+ + '|Capabilities.*)'
35
38
  );
36
39
 
37
40
  function locationString(element) {
@@ -52,7 +55,7 @@ function _createMandatoryAnnotationWarning(annotationName, { element, parent })
52
55
  if (element.default || element[annotationName] === false) return
53
56
  const message = `Annotation '${annotationName}' in '${
54
57
  element.annotate || element.name || parent.extend || parent.annotate
55
- }' is not supported in extensions without default value`
58
+ }' is not supported in extensions`
56
59
  return new LinterMessage(locationString(element) + message, element)
57
60
  }
58
61
 
@@ -27,7 +27,7 @@ module.exports.legacyCheck = (env = cds.env) => {
27
27
  }
28
28
 
29
29
  module.exports.lint = (extCsn, fullCsn, env = cds.env, cloneExt = false) => {
30
- const conf = env.requires[EXT_SERVICE_NAME] || env.mtx || {}
30
+ const conf = env.requires[EXT_SERVICE_NAME] || env.requires.extensibility || env.mtx || {}
31
31
  const linter_options = {}
32
32
  let x
33
33
  for (let p of LINTER_OPTIONS) if ((x = conf[p])) {
@@ -62,13 +62,14 @@ module.exports.ERR_CDS_VALIDATION_FAILURE = 'ERR_CDS_VALIDATION_FAILURE'
62
62
  * Sparsely copies the linter-relevant config from the given env into the given target
63
63
  */
64
64
  module.exports.configCopyFrom = (env, target = {}) => {
65
- if (!env.requires?.[EXT_SERVICE_NAME]) return target
65
+ const source = env.requires?.[EXT_SERVICE_NAME] ?? env.requires?.extensibility
66
+ if (!source) return target
66
67
 
67
68
  const config = {}
68
- Object.keys(env.requires[EXT_SERVICE_NAME])
69
+ Object.keys(source)
69
70
  .filter(key => LINTER_OPTIONS.includes(key)) // only let some keys pass, to not compromise security
70
- .filter(key => !!env.requires[EXT_SERVICE_NAME][key])
71
- .forEach(key => (config[key] = env.requires[EXT_SERVICE_NAME][key]))
71
+ .filter(key => !!source[key])
72
+ .forEach(key => (config[key] = source[key]))
72
73
 
73
74
  target.requires = target.requires || {}
74
75
  target.requires[EXT_SERVICE_NAME] = config // overwrite ext.service key, though
@@ -69,14 +69,3 @@ extend service cds.xt.ExtensibilityService with @private {
69
69
  );
70
70
 
71
71
  }
72
-
73
- // Access to legacy content
74
- extend service cds.xt.ExtensibilityService {
75
-
76
- @requires: ['cds.ExtensionDeveloper']
77
- @private action getMigratedProjects(
78
- tagRule: String, // optional
79
- defaultTag: String, // optional
80
- tenant: String // optional
81
- ) returns LargeBinary;
82
- }
@@ -3,8 +3,8 @@ const cds = require('@sap/cds/lib')
3
3
  const { SELECT, DELETE } = cds.ql
4
4
  const { fs, path, tar, rimraf, read, readdir, mkdirp, write } = cds.utils
5
5
  const { token: authToken } = require('../lib/utils')
6
+ const main = require('./config')
6
7
  const linter = require('./extensibility/linter')
7
- const { getMigratedProjects } = require('../lib/migration/migration')
8
8
 
9
9
 
10
10
  const BUILT_IN_NAMESPACES = ['cds.core', 'cds.outbox', 'cds.xt'] // REVISIT: move to a more central place to be consumed also by linter
@@ -20,12 +20,14 @@ const ACTIVE = 2, DRAFT = 1, INACTIVE = 0
20
20
  //const packageJson = require(main.root + '/package.json');
21
21
  const APPID = require('../lib/utils').getAppId()
22
22
 
23
+ const conf = () => ({ ...main.env.requires.extensibility, ...cds.env.requires['cds.xt.ExtensibilityService'] })
24
+
23
25
  const _async = () => {
24
26
  return cds.context.http?.req?.headers?.prefer === 'respond-async'
25
27
  }
26
28
 
27
29
  const _includeExt = () => {
28
- return (cds.env.requires['cds.xt.ExtensibilityService']?.['check-existing-extensions'] !== false)
30
+ return conf()['check-existing-extensions'] !== false
29
31
  }
30
32
  class DbAdapter {
31
33
 
@@ -64,7 +66,7 @@ class DbAdapter {
64
66
  }
65
67
  }
66
68
 
67
- if (ext.csvs && Object.keys(ext.csvs).length > 0 && cds.env.requires['cds.xt.ExtensibilityService']?.activate?.['skip-csv']) throw Error('CSV deployment is disabled')
69
+ if (ext.csvs && Object.keys(ext.csvs).length > 0 && conf().activate?.['skip-csv']) throw Error('CSV deployment is disabled')
68
70
 
69
71
  // insert extension
70
72
  const tenant = ext.tenant
@@ -74,14 +76,9 @@ class DbAdapter {
74
76
  const tag = ext.ID
75
77
  await cds.tx({ tenant, user: new cds.User.Privileged() }, async tx => {
76
78
  // remove current inactive extension with tag
77
- // use native table names to avoid issues with missing appid
78
- if (tag) await tx.run(DELETE.from('cds_xt_Extensions').where({ tag, activated: { '!=': this.activated(ACTIVE) } }))
79
- try {
80
- await tx.run(INSERT.into('cds_xt_Extensions').entries({ ID, csn, i18n, sources: ext.archive ?? null, activated: this.activated(ext.status), tag, appid: ext.appid ?? APPID, timestamp: new Date().toISOString() }))
81
- } catch (error) {
82
- DEBUG?.('Trying legacy request without appid', error)
83
- await tx.run(INSERT.into('cds_xt_Extensions').entries({ ID, csn, i18n, sources: ext.archive ?? null, activated: this.activated(ext.status), tag, timestamp: new Date().toISOString() }))
84
- }
79
+ if (tag) await tx.run(DELETE.from('cds.xt.Extensions').where({ tag, activated: { '!=': this.activated(ACTIVE) } }))
80
+ // will break with missing appid
81
+ await tx.run(INSERT.into('cds.xt.Extensions').entries({ ID, csn, i18n, sources: ext.archive ?? null, activated: this.activated(ext.status), tag, appid: ext.appid ?? APPID, timestamp: new Date().toISOString() }))
85
82
  })
86
83
  return ID
87
84
  }
@@ -213,7 +210,7 @@ module.exports = class ExtensibilityService extends cds.ApplicationService {
213
210
  if (async) { // 202 is set by the job service
214
211
  return
215
212
  }
216
- const res = await _fetchExtensions(req.data.ID)
213
+ const res = await _fetchExtensions(req.data.ID, status)
217
214
  req.results = { ID: res?.tag, csn: res?.csn, i18n: res?.i18n !== '{}' ? res?.i18n : undefined, timestamp: res?.timestamp, appid: res?.appid, status: DbAdapter.status(res?.activated) } // REVISIT: return csvs?
218
215
  })
219
216
 
@@ -379,9 +376,9 @@ module.exports = class ExtensibilityService extends cds.ApplicationService {
379
376
  }
380
377
  })
381
378
 
382
- async function _fetchExtensions(ID) {
379
+ async function _fetchExtensions(ID, status) {
383
380
  try {
384
- return !ID ? await SELECT.from('cds.xt.Extensions') : await SELECT.one.from('cds.xt.Extensions').where({ tag: ID })
381
+ return !ID ? await SELECT.from('cds.xt.Extensions') : await SELECT.one.from('cds.xt.Extensions').where(status != undefined ? { tag: ID, activated: DbAdapter.activated(status) } : { tag: ID })
385
382
  } catch (error) {
386
383
  DEBUG?.('Trying legacy request without appid', error)
387
384
  const fields = ['ID', 'csn', 'tag', 'activated', 'timestamp', 'i18n']
@@ -401,13 +398,6 @@ module.exports = class ExtensibilityService extends cds.ApplicationService {
401
398
 
402
399
  })
403
400
 
404
- this.on('getMigratedProjects', req => {
405
- const { tagRule, defaultTag } = req.data
406
- const tenant = req.tenant // REVISIT: check if access for arbitrary tenants needed
407
- if (!tenant) req.reject(401, 'User not assigned to any tenant')
408
- return getMigratedProjects(req, tagRule || undefined, defaultTag || undefined, tenant)
409
- })
410
-
411
401
  cds.on('served', () => {
412
402
  cds.app?.post('/-/cds/login/token', express.urlencoded({ extended: false }), async (req, res) => {
413
403
  try {
@@ -483,7 +473,7 @@ module.exports = class ExtensibilityService extends cds.ApplicationService {
483
473
  else await UPDATE('cds.xt.Extensions', { tag, ID: extEntry.ID }).with({ activated: DbAdapter.activated(status) }) // update status
484
474
 
485
475
  // really activate extension
486
- if (status === ACTIVE && !(cds.env.requires['cds.xt.ExtensibilityService']?.activate?.['skip-db'] === true)) {
476
+ if (status === ACTIVE && !(conf().activate?.['skip-db'] === true)) {
487
477
  const { 'cds.xt.DeploymentService': ds } = cds.services
488
478
  if (!ds) {
489
479
  LOG.info('database activation skipped for extension', tag, '- no deployment service')