Socket
Socket
Sign inDemoInstall

@sap/cds-mtxs

Package Overview
Dependencies
Maintainers
1
Versions
60
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sap/cds-mtxs - npm Package Compare versions

Comparing version 1.8.4 to 1.9.0

CHANGELOG.md

1

bin/cds-mtx.js

@@ -32,3 +32,2 @@ #!/usr/bin/env node

}
console.log(`cds-mtx ${cmd} successful.`)
}

@@ -35,0 +34,0 @@

@@ -120,2 +120,14 @@ const path = require('path')

module.exports.checkMigration = async function checkMigration(req) {
const { tenant } = req.data
if (await mtxAdapter.hasExtensions(tenant)) {
if (module.exports._hasExtensibilityEnv()) {
if (!await mtxAdapter.isMigrated(tenant))
req.reject(422, `Upgrade of tenant ${tenant} aborted. Extensions have not been migrated yet`)
} else {
req.reject(422, `Upgrade of tenant ${tenant} aborted. Old MTX Extensions exist but extensibility is not configured (cds.requires.extensibility is false)`)
}
}
}
module.exports.migrate = async function migrate(tenants, options) {

@@ -153,3 +165,3 @@

const migrated = await mtxAdapter.isMigrated(tenant)
if (migrated.timestamp && !force) {
if (migrated?.timestamp && !force) {
migrationResult.log(tenant, `Tenant ${tenant} is already migrated. Skipping migration.`)

@@ -216,3 +228,3 @@ continue

} catch (error) {
migrationResult.error(tenant, `Extension verification failed for tenant ${tenant} [${tenantProjectFolder}]), skipping migration`, error)
migrationResult.error(tenant, `Extension verification failed for tenant ${tenant} [${tenantProjectFolder}]), skipping migration.`, error)
continue

@@ -243,2 +255,5 @@ }

} else {
// check if extensions exist -> abort if yes
if (await mtxAdapter.hasExtensions(tenant)) throw new Error(`Extensions exist but extensibility is not configured (cds.requires.extensibilty is false)`)
if (!dry) {

@@ -305,6 +320,8 @@ await _addMetadata(tenant, metadata)

const diffMessages = []
// are artifacts lost?
const hanaDiffNewToOld = cds.compiler.to.hdi.migration(cds.minify(previewCsn), {}, cds.minify(existingHana.afterImage))
if (hanaDiffNewToOld.deletions.length) {
throw new Error(`Verification error for tenant ${tenant}: migrated model is missing artifacts:\n ${hanaDiffNewToOld.deletions.map( ({ name, suffix }) => `${name}${suffix}\n`)}`)
diffMessages.push(`Migrated model is missing artifacts:\n ${hanaDiffNewToOld.deletions.map( ({ name, suffix }) => `${name}${suffix}\n`)}`)
}

@@ -317,4 +334,4 @@

if (relevantMigrations.length) throw new Error(`Verification error for tenant ${tenant}: table migrations found\n` +
`${relevantMigrations.map( ({ name, suffix, changeset }) => `${name}${suffix}: ${changeset.map(({sql}) => sql)}\n`)}`)
if (relevantMigrations.length) diffMessages.push(`Table migrations found\n` +
`${relevantMigrations.map( ({ name, suffix, changeset }) => ` ${name}${suffix}: ${changeset.map(({sql}) => sql)}\n`)}`)
}

@@ -327,4 +344,7 @@

if (filteredDeletions.length) {
diffMessages.push(`Migrated model has additional artifacts:\n ${hanaDiffOldToNew.deletions.map( ({ name, suffix }) => `${name}${suffix}\n`)}`)
throw new Error(`Verification error for tenant ${tenant}: migrated model has additional artifacts:\n ${hanaDiffOldToNew.deletions.map( ({ name, suffix }) => `${name}${suffix}\n`)}`)
}
if (diffMessages.length) throw new Error(`Verification error for tenant ${tenant}:\n${diffMessages.join('\n')}`)
}

@@ -15,3 +15,3 @@ const cds = require('@sap/cds')

const GLOBAL_DATA_META_TENANT = '__META__'
function _tenantFilter(tenantId) {
function _tenantFilter(tenantId, index, allTenants) {
const t0 = cds.env.requires.multitenancy?.t0 ?? 't0'

@@ -23,2 +23,3 @@ return tenantId

&& tenantId !== t0
&& allTenants.includes(_prefixTenant(tenantId))
}

@@ -51,7 +52,12 @@

const result = (await cds.tx({ tenant: _prefixTenant(tenant) }, tx => tx.run(CONTENT_METADATA_QUERY, [type, domain])))
const { CONTENT } = result && result[0] || {}
const metadata = CONTENT ? JSON.parse(CONTENT) : {}
// more robust behaviour
if (!metadata.subscribedTenantId) metadata.subscribedTenantId = tenant
return metadata
const CONTENT = result?.[0]?.CONTENT || result?.[0]?.content // small tribute to sqlite for testing
if (type === 'onboarding') {
const metadata = CONTENT ? JSON.parse(CONTENT) : {}
// more robust behaviour
if (!metadata.subscribedTenantId) metadata.subscribedTenantId = tenant
return metadata
} else {
return CONTENT ? JSON.parse(CONTENT) : null
}
} catch (error) {

@@ -133,3 +139,3 @@ LOG.error(`Failed to query metadata for tenant '${tenant}' (domain: ${domain}): ${error}`)

} catch (error) {
this.logger.error(`Failed to query extensions for tenant '${tenant}' (domain: ${domain}): ${error}`);
LOG.error(`Failed to query extensions for tenant '${tenant}' (domain: ${domain}): ${error}`);
throw error;

@@ -147,15 +153,29 @@ }

module.exports.verifyMigration = async (tenant) => {
if (!module.exports.wasOldMtx()) return true
return module.exports.isMigrated(tenant)
module.exports.hasExtensions = async (tenant) => {
if (!(await module.exports.wasOldMtx())) return false
const MODEL_FILE_QUERY = 'SELECT FILENAME FROM TENANT_FILES WHERE TYPE = ? AND DOMAIN = ?';
const domain = cds.env.mtx && cds.env.mtx.domain || '__default__'
try {
const files = (await cds.tx({ tenant: _prefixTenant(tenant) }, tx => tx.run(MODEL_FILE_QUERY, ['extension', domain])));
return files.length
} catch (error) {
LOG.error(`Failed to query extensions for tenant '${tenant}' (domain: ${domain}): ${error}`);
return false;
}
}
const wasOldMtx = []
// use this before automatic tenant list update in upgrade
module.exports.wasOldMtx = async () => {
const hana = require('@sap/cds-mtxs/srv/plugins/hana/srv-mgr')
try {
return !!(await hana.get('__META__'))
} catch (error) {
if (error.status === 404) return false
else throw e
if (!wasOldMtx.length) {
const hana = require('@sap/cds-mtxs/srv/plugins/hana/srv-mgr')
try {
wasOldMtx.push(!!(await hana.get('__META__')))
} catch (error) {
if (error.status === 404) wasOldMtx.push(false)
else throw e
}
}
return wasOldMtx[0]
}

@@ -162,0 +182,0 @@

{
"name": "@sap/cds-mtxs",
"version": "1.8.4",
"version": "1.9.0",
"description": "SAP Cloud Application Programming Model - Multitenancy library",

@@ -16,3 +16,4 @@ "homepage": "https://cap.cloud.sap/",

"db/",
"env.js"
"env.js",
"CHANGELOG.md"
],

@@ -19,0 +20,0 @@ "bin": {

@@ -5,8 +5,2 @@ const cds = require ('@sap/cds/lib')

class MTXServices extends cds.Service { async init(){
if (cds.mtx) {
DEBUG?.('bootstrapping old MTX...')
await cds.mtx.in (cds.app) // old mtxExtension
if (!process.env.MTX_MIGRATION) return
}
// else...
DEBUG?.('bootstrapping MTX services...')

@@ -13,0 +7,0 @@ let { definitions } = cds.model

@@ -55,3 +55,3 @@ const cds = require('@sap/cds/lib')

const { subscribedTenantId } = data ?? {}
return params?.[0]?.subscribedTenantId ?? subscribedTenantId
return subscribedTenantId ?? params?.[0]?.subscribedTenantId
}

@@ -101,10 +101,10 @@

if (!one) cds.error(`Tenant ${tenant} not found`, { status: 404 })
return JSON.parse(one.metadata ?? '{}')
return { tenant, ...JSON.parse(one.metadata ?? '{}') }
}
return (await cds.tx({ tenant: t0 }, tx =>
tx.run(SELECT.from(Tenants, tenant => { tenant.ID, tenant.metadata }))
)).map(tenant => JSON.parse(tenant.metadata))
)).map(({ ID, metadata }) => ({ tenant: ID, ...JSON.parse(metadata) }))
}
async upgrade(tenantsIds) {
async upgrade(tenantsIds, options) {
if (!tenantsIds?.length) return

@@ -126,3 +126,3 @@ const tenantList = tenantsIds.includes('*') ? undefined : tenantsIds

await this.limiter(clusterSize, dbToTenants, tenants =>
this.limiter(workerSize ?? poolSize, Array.from(tenants), t => ds.tx({tenant:t}, tx => tx.upgrade(t)))
this.limiter(workerSize ?? poolSize, Array.from(tenants), t => ds.tx({tenant:t}, tx => tx.upgrade(t, options)))
)

@@ -138,3 +138,3 @@ await this._sendCallback('SUCCEEDED', 'Tenant upgrade succeeded')

// REVISIT: use jobs service for sync and async operations (might also be interesting for concurrency control)
return tx.enqueue(dbToTenants, 'upgrade', [], error => {
return tx.enqueue(dbToTenants, 'upgrade', [options], error => {
if (error) this._sendCallback('FAILED', 'Tenant upgrade failed')

@@ -141,0 +141,0 @@ else this._sendCallback('SUCCEEDED', 'Tenant upgrade succeeded')

@@ -0,1 +1,2 @@

const https = require('https')
const { URL } = require('url')

@@ -51,4 +52,6 @@ const axios = require('axios')

const { multitenancy, 'cds.xt.SaasProvisioningService': sps } = cds.env.requires
const { clientid, clientsecret, url } = multitenancy?.credentials ?? sps?.credentials ?? {}
if (!clientid || !clientsecret || !url) {
const { clientid, clientsecret, certurl, url, certificate, key } = multitenancy?.credentials ?? sps?.credentials ?? {}
const auth = certificate ? { maxRedirects: 0, httpsAgent: new https.Agent({ cert: certificate, key }) }
: { auth: { username: clientid, password: clientsecret } }
if (!clientid) {
cds.error('No saas-registry credentials available from the application environment.', { status: 401 })

@@ -58,16 +61,11 @@ }

try {
LOG.info(`getting saas-registry auth token from ${url}`)
const { data: { access_token } } = await axios(`${url}/oauth/token`, {
const authUrl = `${certurl ?? url}/oauth/token`
LOG.info(`getting saas-registry auth token from ${authUrl}`)
const { data: { access_token } } = await axios(authUrl, {
method: 'POST',
auth: {
username: clientid,
password: clientsecret,
},
headers: {
'Content-Type': 'application/json'
},
...auth,
params: {
grant_type: 'client_credentials',
response_type: 'token'
},
}
})

@@ -79,4 +77,4 @@ if (!access_token) {

} catch (error) {
cds.error('Could not get auth token for saas-registry: ' + error.message, { status: 401 }) // REVISIT: Just throw error?
cds.error('Could not get auth token for saas-registry: ' + error.message, { status: 401 })
}
}

@@ -33,3 +33,8 @@ const cds = require('@sap/cds/lib')

cds.on('served', () => { if (cds.app) cds.app.get('/-/cds/login/token', token) })
cds.on('served', () => {
if (cds.app) {
cds.app.post('/-/cds/login/token', token)
cds.app.get('/-/cds/login/token', token)
}
})

@@ -36,0 +41,0 @@ const { 'cds.xt.ModelProviderService': mps } = cds.services

@@ -28,4 +28,2 @@ const { URL } = require('url');

}
assertDefined('query', query);
}

@@ -32,0 +30,0 @@

const ClientCredentialsAuthProvider = require('./ClientCredentialsAuthProvider');
const PasswordAuthProvider = require('./PasswordAuthProvider');
const RefreshTokenAuthProvider = require('./RefreshTokenAuthProvider');
const { assertDefined } = require('./util/SecretsUtil');
module.exports = class AuthProviderFactory {
/**
* Validates all data relevant to the determination of the grant type.
*/
static #validate(credentials, query) {
assertDefined('query', query);
assertDefined('presence of refresh token or passcode or clientid', !!query.refresh_token || !!query.passcode || !!query.clientid);
}
static getAuthProvider(credentials, query) {
AuthProviderFactory.#validate(credentials, query);
return query.refresh_token

@@ -12,6 +22,4 @@ ? new RefreshTokenAuthProvider(credentials, query)

? new PasswordAuthProvider(credentials, query)
: query.clientid
? new ClientCredentialsAuthProvider(credentials, query)
: new Error('Unknown grant type');
: new ClientCredentialsAuthProvider(credentials, query);
}
}

@@ -8,2 +8,18 @@ const util = require('util');

async function parseBody(request) {
return new Promise((resolve, reject) => {
const chunks = [];
request.on('data', chunk => chunks.push(chunk));
request.on('end', () => {
try {
const body = Buffer.concat(chunks).toString();
request.body = Object.fromEntries(new URLSearchParams(body).entries());
resolve(request.body);
} catch (error) {
reject(error);
}
});
});
}
module.exports = async function token(request, response) {

@@ -17,3 +33,5 @@ if (request.method === 'HEAD') {

const { credentials } = cds.env.requires.auth;
const { query } = request;
const query = request.method === 'POST'
? await parseBody(request)
: request.query;

@@ -34,3 +52,3 @@ let authProvider;

...authProvider.clientAuth,
timeout: 10000
timeout: 1e4 // ms
}

@@ -37,0 +55,0 @@ );

@@ -28,10 +28,15 @@ const cds = require('@sap/cds/lib'), { uuid } = cds.utils

await retry(() => cds.tx({ tenant: t0 }, tx => tx.run(INSERT.into(Tasks, tasks))), LOG)
if (tasks.length) {
await retry(() => cds.tx({ tenant: t0 }, tx => tx.run(INSERT.into(Tasks, tasks))), LOG)
const ds = await cds.connect.to(DeploymentService)
const tx = ds.tx(cds.context)
_nextJob(jobs, task => {
const { 'cds.xt.DeploymentService': ds } = cds.services
return ds.tx({ tenant: cds.context.tenant }, tx => tx[op](task.tenant, ...args))
}, onJobDone).catch(err => LOG.error('next job raised an error', err))
} else {
await retry(() => cds.tx({ tenant: t0 }, tx =>
tx.run(UPDATE(Jobs, { ID: job_ID }).with({ status: FINISHED }))
), LOG)
}
_nextJob(jobs, task => tx[op](task.tenant, ...args), onJobDone)
.catch(err => LOG.error('next job raised an error', err))
const url = process.env.VCAP_APPLICATION ? 'https://' + JSON.parse(process.env.VCAP_APPLICATION).uris?.[0] : cds.server.url

@@ -38,0 +43,0 @@ cds.context.http?.res.set('Location', `${url}/-/cds/jobs/pollJob(ID='${job_ID}')`)

@@ -122,3 +122,3 @@ const fs = require('fs').promises

const extensions = !base && await _getExtensions4 (tenant, activated)
const extensions = !base && main.requires.extensibility && await _getExtensions4 (tenant, activated)
if (!extensions && checkExt) req.reject(404, 'Missing extensions')

@@ -125,0 +125,0 @@

@@ -18,2 +18,4 @@ const cds = require('@sap/cds/lib'), {db} = cds.env.requires

const migration = require('../../lib/migration/migration')
if (db?.kind === 'hana') {

@@ -58,3 +60,8 @@

})
// check migration before upgrade
ds.before('upgrade', async (req) => {
await migration.checkMigration(req)
})
})

@@ -192,4 +199,5 @@ }

if (updateCsvs) {
const _tabledata4 = require('@sap/cds/bin/build/provider/hana/2tabledata')
const tdata = await _tabledata4 (csn, { dirs: [path.join('gen',tenant,'src','gen','data')] })
const toHdbtabledata = cds.compile.to.hdbtabledata ?? require(path.join(cds.home, 'bin/build/provider/hana/2tabledata')) // cds@6 compatibility
const tdata = await toHdbtabledata(csn, { dirs: [path.join('gen', tenant, 'src', 'gen', 'data')] })
for (const [data, { file, csvFolder }] of tdata) {

@@ -214,3 +222,3 @@ gen.push (fs.promises.writeFile(path.join(csvFolder,file), JSON.stringify(data)))

DEBUG?.('preparing HANA deployment artifacts')
const _resources = ( csnFromParameter ) ? null : resources4(tenant)
const _resources = csnFromParameter ? null : resources4(tenant)

@@ -217,0 +225,0 @@ let container

const { join } = require('path')
const HdiDeployUtil = require('@sap/cds/bin/deploy/to-hana/hdiDeployUtil')
const { clean_env } = require('@sap/hdi-deploy/library')
const { deploy, clean_env } = require('@sap/hdi-deploy/library')
const cds = require ('@sap/cds/lib')
const LOG = cds.log('mtx|deploy'), DEBUG = cds.debug('mtx|deploy')
const { fs, mkdirp } = cds.utils
exports.deploy = async (hana, tenant, cwd, options) => {
const LOG = cds.log('mtx'), DEBUG = cds.debug('mtx')
const env = _hdi_env4(tenant,hana,options)

@@ -14,7 +13,26 @@ DEBUG?.(`deployment directory: ${cwd}`)

const logPath = join(cds.root, 'logs', `${cds.context.tenant}.log`)
await mkdirp('logs')
const writeStream = fs.createWriteStream(logPath)
await mkdirp('logs')
LOG.info('------------[BEGIN HDI-DEPLOY-OUTPUT]---------------')
try {
await HdiDeployUtil.deployTenant (cwd, env, LOG)
return await new Promise((resolve, reject) => {
deploy(cwd, env, (error, response) => {
if (error) return reject(error)
if (response?.exitCode) {
let message = `HDI deployment failed with exit code ${response.exitCode}`
if (response.signal) message += `. ${response.signal}`
return reject(new Error(message))
}
return resolve()
}, {
stderrCB: buffer => {
LOG.error(buffer.toString())
writeStream.write(buffer)
},
stdoutCB: buffer => {
DEBUG?.(buffer.toString())
writeStream.write(buffer)
}
})
})
} finally {

@@ -61,3 +79,3 @@ LOG.info('-------------[END HDI-DEPLOY-OUTPUT]----------------')

const _parse_env = (key, options) => {
const val = process.env[key]; if (!val) return {}
const val = process.env[key]; if (!val) return { ...options }
try {

@@ -64,0 +82,0 @@ return { ...JSON.parse (val), ...options }

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc