@sap/cds-mtxs
Advanced tools
Comparing version 1.2.0 to 1.3.0
#!/usr/bin/env node | ||
/* eslint-disable no-console */ | ||
const cds = require('@sap/cds') | ||
const { _import, isfile, local, path } = cds.utils | ||
const cmd = process.argv[2] | ||
const SUPPORTED = ['subscribe', 'unsubscribe', 'upgrade'] | ||
const SUPPORTED = ['subscribe', 'unsubscribe'] | ||
async function runAction(action, tenant) { | ||
if (!action) _usage() | ||
if (!SUPPORTED.includes(action)) _usage(`Unknown command ${cmd}.`) | ||
if (!_hasMtEnv()) _handleError(`cds ${action} operation can only be run inside a multitenant application environment using @sap/cds-mtxs.`) | ||
if (!tenant) _handleError(`Please provide a tenant: cds ${action} <tenant id>`) | ||
async function cds_mtx(cmd, tenant) { | ||
if (!cmd) _usage() | ||
if (!SUPPORTED.includes(cmd)) _usage(`Unknown command ${cmd}.`) | ||
if (!_hasMtEnv()) _handleError(`cds ${cmd} operation can only be run inside a multitenant application environment using @sap/cds-mtxs.`) | ||
if (!tenant) _handleError(`Please provide a tenant: cds ${cmd} <tenant>`) | ||
const { 'cds.xt.DeploymentService':ds } = await cds.serve ([ | ||
@@ -17,7 +18,18 @@ '@sap/cds-mtxs/srv/deployment-service', | ||
]) | ||
await _local_server_js() | ||
await cds.emit('served') | ||
if (action === 'unsubscribe') { await cds.connect('db') } | ||
return await ds[action](tenant) | ||
if (cmd === 'unsubscribe') { await cds.connect() } | ||
return await ds[cmd](tenant) | ||
} | ||
// copied from cds.serve | ||
async function _local_server_js() { | ||
const _local = file => isfile(file) || isfile (path.join(cds.env.folders.srv,file)) | ||
let cli_js = process.env.CDS_TYPESCRIPT && _local('cli.ts') || _local('cli.js') | ||
if (cli_js) { | ||
console.log ('[cds] - loading server from', { file: local(cli_js) }) | ||
let fn = await _import(cli_js) | ||
} | ||
} | ||
// check for application environment | ||
@@ -53,4 +65,3 @@ // fails for edge cases (ignored for now): | ||
// eslint-disable-next-line no-console | ||
runAction(cmd, process.argv.slice(3)[0]).catch(console.error) | ||
const [,,cmd,tenant] = process.argv | ||
cds_mtx(cmd, tenant).catch(console.error) |
@@ -9,2 +9,25 @@ # Change Log | ||
## Version 1.3.0 - 2022-10-28 | ||
### Added | ||
- `cds.requires.multitenancy.for` lets you define tenant-specific creation and deployment configuration. | ||
- `cds.xt.DeploymentService`: The `t0` tenant is now onboarded on startup. | ||
- `POST /-/cds/deployment/subscribe` saves onboarding metadata in `t0`. | ||
- `POST /-/cds/deployment/unsubscribe` removes onboarding metadata for `t0`. | ||
- Parameters for `t0` tenant onboarding can now be specified via `cds.requires.multitenancy.for.t0`. Analogous to the configuraiton in `cds.xt.DeploymentService` you can specify options for `hdi` and `create`. | ||
### Changed | ||
- `@sap/instance-manager` has been replaced by a custom Service Manager client, which is now the default. You can switch back to the `@sap/instance-manager`-based client by setting `cds.requires['cds.xt.DeploymentService']['old-instance-manager']` to `true`. | ||
## Version 1.2.1 - tbd | ||
### Added | ||
- [BETA] Command line tool `cds-mtx` now also allows to run `upgrade` in an application environment, e. g. `npx cds-mtx upgrade tenant1` or `cds-mtx upgrade tenant1` if you have installed `@sap/cds-mtxs` globally. This redeploys the current application model. Potential service handlers can be registered in `cli.js` (`server.js` is not loaded) | ||
### Fixed | ||
- `/-/cds/saas-provisioning/upgrade` now also runs with DwC | ||
## Version 1.2.0 - 2022-10-06 | ||
@@ -50,3 +73,4 @@ | ||
- `GET /-/cds/deployment/getTables(tenant='<tenantId>)` returns all deployed tables for a tenant. | ||
- Command line tool `cds-mtx` allows to run `subscribe and unsubscribe` in an application environment, e. g. `npx cds-mtx subscribe tenant1` or `cds-mtx subscribe tenant1` if you have installed `@sap/cds-mtxs` globally | ||
- [BETA] Command line tool `cds-mtx` allows to run `subscribe and unsubscribe` in an application environment, e. g. `npx cds-mtx subscribe tenant1` or `cds-mtx subscribe tenant1` if you have installed `@sap/cds-mtxs` globally | ||
### Changed | ||
@@ -56,3 +80,3 @@ | ||
- `POST /-/cds/saas-provisioning/upgrade` accepts a list of tenants like `upgrade(['t1', 't2'])`. | ||
- `upgrade(['*'])` upgrades all tenants. | ||
+ `upgrade(['*'])` upgrades all tenants. | ||
- `POST /-/cds/saas-provisioning/upgrade` gets its tenants from the `t0` cache instead of the `saas-registry` service. | ||
@@ -59,0 +83,0 @@ - `POST /-/cds/saas-provisioning/upgradeAll` has been deprecated and will be removed. |
@@ -114,10 +114,2 @@ const fs = require('fs') | ||
const exists = async fileOrDir => { | ||
try { | ||
return await fs.promises.stat(fileOrDir) | ||
} catch (_) { | ||
return false | ||
} | ||
} | ||
module.exports = { | ||
@@ -131,4 +123,3 @@ EXT_BACK_PACK, | ||
getCompilerError, | ||
collectFiles, | ||
exists | ||
collectFiles | ||
} |
{ | ||
"name": "@sap/cds-mtxs", | ||
"version": "1.2.0", | ||
"version": "1.3.0", | ||
"description": "SAP Cloud Application Programming Model - Multitenancy library", | ||
@@ -22,5 +22,5 @@ "homepage": "https://cap.cloud.sap/", | ||
"dependencies": { | ||
"@sap/hdi-deploy": "^4", | ||
"axios": ">=0.27.2", | ||
"@sap/instance-manager": "^3", | ||
"axios": "^0.27.2" | ||
"@sap/hdi-deploy": "^4" | ||
}, | ||
@@ -27,0 +27,0 @@ "peerDependencies": { |
@@ -1,6 +0,7 @@ | ||
const cds = require('@sap/cds/lib')//, { axios } = cds.utils | ||
const cds = require('@sap/cds/lib') | ||
const LOG = cds.log('mtx') | ||
const { submit, getStatus, diagnose } = require('./jobs/async') | ||
const { asyncPool } = require('./jobs/parallel') | ||
const { submit, getStatus, diagnose } = require('../jobs/async') | ||
const { asyncPool } = require('../jobs/parallel') | ||
const SaasRegistryUtil = require('./saas-registry-util') | ||
const Tenants = 'cds.xt.Tenants' | ||
@@ -28,4 +29,2 @@ // TODO: Implement/validate scope/tenant ID checks | ||
await super.init() // ensure to call super.init() | ||
cds.once('served', () => this._resubscribeT0IfNeeded()) | ||
} | ||
@@ -39,3 +38,3 @@ | ||
async _subscribe(context) { | ||
const { subscribedTenantId: tenant } = context.data | ||
const tenant = this._getSubscribedTenant(context) | ||
LOG.info(`Subscribing tenant ${tenant}`) | ||
@@ -46,6 +45,3 @@ | ||
const tx = deploymentService.tx(context) | ||
await tx.subscribe(tenant, this._options(context.data)) | ||
await cds.tx({ tenant: this._t0 }, tx => | ||
tx.run(`INSERT INTO CDS_XT_TENANTS(ID, metadata) VALUES (?, ?)`, [tenant, JSON.stringify(context.data)]) | ||
) | ||
await tx.subscribe(tenant, context.data, this._options(context.data)) | ||
LOG.info(`Successfully subscribed tenant ${tenant}`) | ||
@@ -75,8 +71,9 @@ await this.emit('succeeded', { task: 'subscribe', result: await this._getAppUrl(context.data, context.headers) }) | ||
async _unsubscribe(context) { | ||
const { subscribedTenantId: tenant } = context.data | ||
const tenant = this._getSubscribedTenant(context) | ||
LOG.info(`Unsubscribing tenant ${tenant}`) | ||
const [{ metadata, METADATA } = {}] = await (async () => { try { return await cds.tx({ tenant: this._t0 }, tx => | ||
tx.run(`SELECT metadata FROM CDS_XT_TENANTS WHERE ID=?`, [tenant]) | ||
)} catch (error) { return [] }})() // REVISIT: What if there's no subdomain (currently silently ignored)? (e.g. tenant was onboarded via DeploymentService) | ||
// REVISIT: What if there's no subdomain (currently silently ignored)? (e.g. tenant was onboarded via DeploymentService) | ||
const { metadata } = (await cds.tx({ tenant: this._t0 }, tx => | ||
tx.run(SELECT.one.from(Tenants, { ID: tenant }, tenant => { tenant.metadata })) | ||
)) ?? {} | ||
@@ -86,6 +83,3 @@ const ds = await cds.connect.to('cds.xt.DeploymentService') | ||
try { | ||
await tx.unsubscribe(tenant, { metadata: JSON.parse(metadata ?? METADATA ?? '{}') }) | ||
await cds.tx({ tenant: this._t0 }, tx => | ||
tx.run(`DELETE FROM CDS_XT_TENANTS WHERE ID=?`, [tenant]) | ||
) | ||
await tx.unsubscribe(tenant, { metadata: JSON.parse(metadata ?? '{}') }) | ||
LOG.info(`Successfully unsubscribed tenant ${tenant}`) | ||
@@ -102,2 +96,7 @@ await this.emit('succeeded', { task: 'unsubscribe' }) | ||
get _t0() { | ||
return cds.env.requires.multitenancy.t0 ?? 't0' | ||
} | ||
getJobStatus(context) { | ||
@@ -137,14 +136,23 @@ const { jobID } = context.data | ||
const { subscribedTenantId: tenant } = context.data | ||
const tenant = this._getSubscribedTenant(context) | ||
return submit(tenant, this._subscribe.bind(this), context, { context }) | ||
} | ||
_getSubscribedTenant(context) { | ||
const { data: { subscribedTenantId }, params } = context | ||
return (params && params[0]?.subscribedTenantId) ?? subscribedTenantId | ||
} | ||
async read(context) { // TODO check params for get | ||
const { subscribedTenantId: tenant } = context.data | ||
const tenants = (await cds.tx({ tenant: this._t0 }, tx => | ||
tenant ? tx.run(`SELECT ID, metadata FROM CDS_XT_TENANTS WHERE ID=?`, [tenant]) | ||
: tx.run(`SELECT ID, metadata FROM CDS_XT_TENANTS`) | ||
)).map(tenant => (JSON.parse(tenant.metadata ?? tenant.METADATA))) // some DBs (HANA) upper-case field names | ||
if (tenant && !tenants[0]) cds.error(`Tenant ${tenant} not found`, { status: 404 }) | ||
return tenant ? tenants[0] : tenants | ||
const tenantId = this._getSubscribedTenant(context) | ||
if (tenantId) { | ||
const tenant = await cds.tx({ tenant: this._t0 }, tx => | ||
tx.run(SELECT.one.from(Tenants, { ID: tenantId }, tenant => { tenant.metadata })) | ||
) | ||
if (!tenant) cds.error(`Tenant ${tenantId} not found`, { status: 404 }) | ||
return JSON.parse(tenant.metadata) | ||
} | ||
return (await cds.tx({ tenant: this._t0 }, tx => | ||
tx.run(SELECT.from(Tenants, tenant => { tenant.ID, tenant.metadata })) | ||
)).map(tenant => JSON.parse(tenant.metadata)) | ||
} | ||
@@ -160,3 +168,3 @@ | ||
const { subscribedTenantId: tenant } = context.data | ||
const tenant = this._getSubscribedTenant(context) | ||
return submit(tenant, this._unsubscribe.bind(this), context, { context }) | ||
@@ -166,5 +174,3 @@ } | ||
async getDependencies() { | ||
return cds.env.requires['cds.xt.SaasProvisioningService']?.dependencies?.map( | ||
d => ({ 'xsappname': d }) | ||
) ?? [] | ||
return cds.env.requires['cds.xt.SaasProvisioningService']?.dependencies?.map(d => ({ xsappname: d })) ?? [] | ||
} | ||
@@ -182,8 +188,10 @@ | ||
const tenants = tenantList ?? (await cds.tx({ tenant: this._t0 }, tx => | ||
tx.run(`SELECT ID FROM CDS_XT_TENANTS`) | ||
tx.run(SELECT.from(Tenants, tenant => { tenant.ID })) | ||
)).map(({ ID }) => ID) | ||
const { isSync } = SaasRegistryUtil.getCallbackUrlsFromHeaders(context._.req) | ||
if (isSync) return this._updateAll(tenants, context) | ||
// REVISIT: Which tenant ID here? - used request tenant but it is not matching what we use in subscribe | ||
return submit(context.tenant, this._updateAll.bind(this), [tenants, context], { context }) | ||
// REVISIT: Which tenant ID here? | ||
// use tenant from context if available | ||
// use constant if not (dwc use case) | ||
return submit(context.tenant ?? 'provider', this._updateAll.bind(this), [tenants, context], { context }) | ||
} | ||
@@ -205,3 +213,4 @@ | ||
if (!isSync && !noCallback) { | ||
const { subscribedTenantId: tenant } = originalRequest.body // TODO evaluate params for new rest adapter | ||
/// TODO evaluate params for new rest adapter | ||
const tenant = this._getSubscribedTenant(originalRequest.body) | ||
@@ -226,16 +235,2 @@ const payload = { status, message, subscriptionUrl } | ||
} | ||
get _t0() { | ||
return process.env.CDS_REQUIRES_MULTITENANCY_T0 ?? 't0' | ||
} | ||
async _resubscribeT0IfNeeded() { | ||
const ds = await cds.connect.to('cds.xt.DeploymentService') | ||
await ds.tx({ tenant: this._t0 }, async tx => { | ||
if (!await tx.needsT0Redeployment()) return | ||
const csn = await cds.load(`${__dirname}/../../db/t0.cds`) | ||
await tx.subscribe({ tenant: this._t0, options: { csn }}) | ||
}) | ||
} | ||
} | ||
@@ -242,0 +237,0 @@ |
@@ -140,7 +140,2 @@ const { URL } = require('url') | ||
// TODO find out purpose of this method (currently unused) | ||
static getAuthFromHeaders(req) { | ||
return req?.headers?.mtx_status_callback && req?.headers?.authorization | ||
} | ||
static get SUBDOMAIN_PLACEHOLDER() { | ||
@@ -147,0 +142,0 @@ return 'tenant_subdomain' |
@@ -16,2 +16,4 @@ const cds = require ('@sap/cds/lib') | ||
plugins.unshift(path.join(dir,'common','metadata.js')) | ||
const loaded = plugins.map(each => ({ file:each, module:require(each) })) | ||
@@ -18,0 +20,0 @@ |
@@ -10,12 +10,16 @@ const cds = require('@sap/cds/lib'), { fs, path, tar, rimraf } = cds.utils | ||
const _compileProject = async function (extension, req) { | ||
const _compileProject = async function (extension) { | ||
const root = await fs.promises.mkdtemp(`${TEMP_DIR}${path.sep}extension-`) | ||
try { | ||
let files = (await tar.xvz(extension).to(root)) .filter (f => f.match(/\.(cds|csn)$/)) | ||
let csn = await cds.compile (files, { cwd: root, flavor: 'parsed' }) | ||
if (csn.requires) delete csn.requires | ||
return { csn, files } | ||
} catch (err) { | ||
if (err.messages) req.reject(422, getCompilerError(err.messages)) | ||
else throw err | ||
await tar.xvz(extension).to(root) | ||
let extCsn | ||
try { extCsn = await cds.utils.read(path.join(root, 'extension.csn')) } | ||
catch(e) { if (e.code !== 'ENOENT') throw e } | ||
let bundles | ||
try { bundles = await cds.utils.read(path.join(root, 'i18n', 'i18n.json')) } | ||
catch(e) { if (e.code !== 'ENOENT') throw e } | ||
return { extCsn: extCsn && JSON.parse(extCsn), bundles } | ||
} finally { | ||
@@ -62,3 +66,3 @@ rimraf (root) | ||
const sources = typeof extension === 'string' ? Buffer.from(extension, 'base64') : extension | ||
const { csn: extCsn } = await _compileProject(sources, req) | ||
const { extCsn, bundles } = await _compileProject(sources, req) | ||
if (!extCsn) req.reject(400, 'Missing or bad extension') | ||
@@ -95,2 +99,3 @@ if (!tag) tag = null | ||
csn: JSON.stringify(extCsn), | ||
i18n: bundles ? JSON.stringify(bundles) : null, | ||
sources, | ||
@@ -97,0 +102,0 @@ activated: 'database', |
const { URL } = require('url') | ||
const util = require('util'); | ||
const cds = require('@sap/cds/lib') | ||
@@ -50,13 +51,12 @@ const LOG = cds.log() | ||
} catch (error) { | ||
const rootCause = error.response?.data ? JSON.stringify(error.response?.data) : error.message | ||
error.message = `Authentication failed with root cause '${rootCause}'. Passcode URL: https://${parsedUrl.hostname}/passcode` | ||
const { | ||
constructor: { name }, | ||
message | ||
} = error | ||
const status = name in { JwtRequestError: 1, IncompleteJwtResponseError: 1 } ? 401 : error.response.status ?? 500 | ||
LOG.error(message) | ||
response.status(status).send({ message, status }) | ||
const data = error.response?.data | ||
const reason = data?.error /* RFC 6749 */ ?? error.message | ||
const details = data ? (` Details: '${data.error_description /* RFC 6749 */ || util.inspect(data)}'.`) : '' | ||
const message = `Authentication failed: ${reason}.${details} Passcode URL: https://${parsedUrl.hostname}/passcode` | ||
const status = error.response?.status ?? 500 | ||
Object.assign(error, { message }) | ||
LOG.error(error) | ||
response.status(status).send(error) | ||
} | ||
} | ||
} |
@@ -56,7 +56,9 @@ const fs = require('fs').promises | ||
const { res } = req._; if (res) res.set('Content-Type', 'application/xml') | ||
const { service, locale, flavor } = req.data | ||
const { service, model, locale, flavor } = req.data | ||
delete req.data.flavor // we need to delete the OData 'flavor' argument, as getCsn has a different CSN `flavor` argument | ||
const csn = await _getCsn(req) | ||
const csn = model ? model : await _getCsn(req) | ||
const edmx = cds.compile.to.edmx(csn, { service, flavor }) | ||
return cds.localize(csn, locale, edmx) | ||
const extBundle = await _getExtI18n(req) | ||
return cds.localize(csn, locale, edmx, extBundle) | ||
}) | ||
@@ -108,4 +110,4 @@ | ||
async function _getCsn (req, checkExt) { | ||
const { tenant, toggles, base, flavor, for:javaornode } = req.data | ||
const extensions = !base && await _getExtensions4 (req.data.tenant, req.data.activated) | ||
const { tenant, toggles, base, flavor, for:javaornode, activated } = req.data | ||
const extensions = !base && await _getExtensions4 (tenant, activated) | ||
if (!extensions && checkExt) req.reject(404, 'Missing extensions') | ||
@@ -130,16 +132,44 @@ | ||
if (!main.requires.extensibility || !tenant && main.requires.multitenancy) return | ||
const cqn = SELECT('csn').from('cds.xt.Extensions') | ||
if (activated) cqn.where('activated=', 'database') | ||
const exts = await cds.db.run(cqn) | ||
if (!exts.length) return | ||
try { | ||
const cqn = SELECT('csn').from('cds.xt.Extensions') | ||
if (activated) cqn.where('activated=', 'database') | ||
const exts = await cds.db.run(cqn) | ||
if (!exts.length) return | ||
const merged = { extensions: [], definitions: {} } | ||
for (let each of exts) { | ||
let {definitions,extensions} = JSON.parse(each.csn) | ||
if (definitions) Object.assign (merged.definitions, definitions) | ||
if (extensions) merged.extensions.push (...extensions) | ||
const merged = { extensions: [], definitions: {} } | ||
for (let each of exts) { | ||
let {definitions,extensions} = JSON.parse(each.csn) | ||
if (definitions) Object.assign (merged.definitions, definitions) | ||
if (extensions) merged.extensions.push (...extensions) | ||
} | ||
return merged | ||
} catch (error) { | ||
DEBUG && DEBUG('cds.xt.Extensions not yet deployed', error) | ||
return | ||
} | ||
return merged | ||
} | ||
async function _getExtI18n (req) { | ||
if (!main.requires.extensibility) return | ||
const { tenant, locale } = req.data | ||
if (!tenant && main.requires.multitenancy) return | ||
const cqn = SELECT('i18n').from('cds.xt.Extensions').where('i18n !=', null).orderBy('timestamp') | ||
const extBundles = await cds.db.run(cqn) | ||
let extBundle | ||
if (extBundles && extBundles.length) { | ||
extBundle = extBundles.reduce((acc, cur) => { | ||
const bundle = JSON.parse(cur.i18n) | ||
if (locale && bundle[locale]) acc[locale] = Object.assign(acc[locale] || {}, bundle[locale]) | ||
acc[''] = Object.assign(acc[''] || {}, bundle['']) // default locale | ||
return acc | ||
}, {}) | ||
extBundle = extBundle[locale] || extBundle[''] | ||
} | ||
return extBundle | ||
} | ||
} | ||
@@ -146,0 +176,0 @@ |
@@ -15,10 +15,10 @@ const cds = require('@sap/cds/lib'), {db} = cds.requires | ||
cds.on ('served', () => { | ||
const hana = require('./hana/inst-mgr') | ||
const { 'cds.xt.DeploymentService': ds } = cds.services | ||
const useOldIm = cds.env.requires['cds.xt.DeploymentService']?.['old-instance-manager'] | ||
const hana = useOldIm ? require('./hana/inst-mgr') : require('./hana/srv-mgr') | ||
ds.on ('subscribe', async req => { | ||
ds.on ('subscribe', req => { | ||
const { tenant:t, options: { _: params, csn } = {} } = req.data | ||
// REVISIT: in which scenarios do we need to get the tenant _before_ the subscription (e.g. shared service manager/domain concept) | ||
return prepareAndDeploy (t, hana.getOrCreate(t, _imCreateParams(params)), params, csn ?? csn4(), csn ? undefined : resources4(t)) | ||
return prepareAndDeploy (t, hana.acquire(t, _imCreateParams(t, params)), params, csn ?? csn4(), csn ? undefined : resources4(t)) | ||
}) | ||
@@ -42,22 +42,5 @@ ds.on ('upgrade', async req => { | ||
}) | ||
ds.on ('needsT0Redeployment', async function() { | ||
const tables = await this.getTables(_t0()) | ||
return !(tables.includes('CDS_XT_JOBS') && tables.includes('CDS_XT_TENANTS')) | ||
}) | ||
ds.on ('hasTenant', async req => { | ||
const { tenant:t } = req.data | ||
try { | ||
return await hana.get(t) !== null | ||
} catch (e) { | ||
if (e.code === 404) return false | ||
throw e | ||
} | ||
}) | ||
ds.on ('getTenantDb', req => { | ||
const { tenant:t } = req.data | ||
return hana.get(t) | ||
}) | ||
ds.on ('getTables', async req => { | ||
const { tenant:t } = req.data | ||
const { schema } = (t === _t0() ? await hana.getOrCreate(t) : await hana.get(t)).credentials | ||
const { schema } = (t === _t0() ? await hana.acquire(t, _imCreateParams(t)) : await hana.get(t)).credentials | ||
return (await cds.tx({ tenant: t }, async tx => | ||
@@ -67,3 +50,2 @@ await tx.run('SELECT TABLE_NAME FROM TABLES WHERE SCHEMA_NAME = ?', [schema]) | ||
}) | ||
}) | ||
@@ -74,8 +56,9 @@ | ||
function _t0() { | ||
return process.env.CDS_REQUIRES_MULTITENANCY_T0 ?? 't0' | ||
return cds.env.requires.multitenancy.t0 ?? 't0' | ||
} | ||
function _imCreateParams(params = {}) { | ||
const paramsFromEnv = cds.env.requires['cds.xt.DeploymentService']?.hdi?.create || {} | ||
return { ...paramsFromEnv, ...params?.hdi?.create } | ||
function _imCreateParams(tenant, params = {}) { | ||
const paramsFromEnv = cds.env.requires['cds.xt.DeploymentService']?.hdi?.create ?? {} | ||
const paramsFromTenantOptions = cds.env.requires.multitenancy.for?.[tenant]?.hdi?.create ?? {} | ||
return { ...paramsFromEnv, ...paramsFromTenantOptions, ...params?.hdi?.create } | ||
} | ||
@@ -168,3 +151,4 @@ function _hdiDeployParams(params = {}) { | ||
try { | ||
const hana = require('./hana/inst-mgr') | ||
const useOldIm = cds.env.requires['cds.xt.DeploymentService']?.['old-instance-manager'] | ||
const hana = useOldIm ? require('./hana/inst-mgr') : require('./hana/srv-mgr') | ||
await hana.deploy (container, tenant, out, options) | ||
@@ -171,0 +155,0 @@ LOG.info(`Successfully finished HANA deployment for tenant ${tenant}`) |
module.exports = new class InstanceManager { | ||
async create (t, _) { return (await _im()).create(t, _) } | ||
async delete (t) { try { return await (await _im()).delete(t) } catch (e) { if (e.statusCode !== 404) throw e } } | ||
async get (t) { return (await _im()).get(t) } | ||
async getOrCreate(t, _) { return await (await _im()).get(t) ?? (await _im()).create(t, _) } | ||
async getAll () { return (await _im()).getAll() } | ||
get deploy () { return super.deploy = require('./hdi').deploy } //> tunnelling this to provide a simpler API to users | ||
async create (t, _) { return (await _im()).create(t, _) } | ||
async delete (t) { try { return await (await _im()).delete(t) } catch (e) { if (e.statusCode !== 404) throw e } } | ||
async get (t) { return (await _im()).get(t) } | ||
async acquire(t, _) { return await (await _im()).get(t) ?? (await _im()).create(t, _) } | ||
async getAll () { return (await _im()).getAll() } | ||
get deploy () { return super.deploy = require('./hdi').deploy } //> tunnelling this to provide a simpler API to users | ||
} | ||
@@ -9,0 +9,0 @@ |
@@ -38,30 +38,2 @@ const cds = require('@sap/cds/lib'), {db} = cds.requires, {fs} = cds.utils | ||
}) | ||
// REVISIT: I think we should have a review on these creeping APIs ;) | ||
ds.on ('needsT0Redeployment', async function (req) { | ||
const tables = await this.getTables(_t0()) | ||
if (!(tables.includes('cds_xt_Jobs') && tables.includes('cds_xt_Tenants'))) { | ||
return true | ||
} | ||
// REVISIT: Don't informn when everything is as expected | ||
// LOG.info(`No redeployment for ${_t0()} needed`) | ||
return false | ||
}) | ||
ds.on ('hasTenant', async req => { | ||
const { tenant:t } = req.data | ||
const [{ 'count(*)': count }] = await cds.tx({ tenant: t }, tx => | ||
tx.run(`SELECT count(*) FROM sqlite_master WHERE type = 'table'`) | ||
) | ||
return count > 0 | ||
}) | ||
ds.on ('getTenantDb', req => { | ||
const { tenant:t } = req.data | ||
return cds.tx({ tenant: t }, tx => | ||
tx.run(`SELECT count(*) FROM sqlite_master WHERE type = 'table'`) | ||
) > 0 ? {} : null // TODO: What would be a sensible response here? | ||
}) | ||
ds.on ('getTables', async req => { | ||
@@ -74,3 +46,2 @@ const { tenant:t } = req.data | ||
async function csn4 (tenant) { | ||
@@ -81,8 +52,4 @@ const { 'cds.xt.ModelProviderService': mp } = cds.services | ||
function _t0() { | ||
return process.env.CDS_REQUIRES_MULTITENANCY_T0 ?? 't0' | ||
} | ||
// workaround for SQLite: | ||
if (!cds.env.requires.multitenancy) cds.env.requires.multitenancy = true | ||
}) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
127196
45
2502
9
0
- Removedaxios@0.27.2(transitive)
Updatedaxios@>=0.27.2