Socket
Socket
Sign inDemoInstall

@sap/cds-mtxs

Package Overview
Dependencies
2
Maintainers
1
Versions
53
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.10.0 to 1.11.0

srv/extensibility/linter/allowlist.js

1

bin/cds-mtx.js

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

await cds.db.disconnect(cds.requires.multitenancy.t0)
await cds.db.disconnect(tenant)
}

@@ -32,0 +33,0 @@ }

@@ -9,2 +9,18 @@ # Change Log

## Version 1.11.0 - 2023-09-01
### Added
- `/-/cds/extensibility/push` now also accepts the `prefer: respond-async` header for asynchronous requests.
- `/-/cds/model-provider/getCsn` uses an LRU cache for base model CSNs, limited to 5 entries by default. This cache size can be configured using `cds.requires['cds.xt.ModelProviderService'].cacheSize`.
- The `treat_unmodified_as_modified` parameter is now allowed for HDI deployments.
### Fixed
- The Service Manager client now returns all bindings for partial cache misses for `cds.requires.['cds.xt.SaasProvisioningService'].jobs.clusterSize > 1`.
- The Service Manager client will now wait with exponential back-off for unexpected error codes.
- The Service Manager client will now print the correct root cause for errors with a `description` field.
- Command `cds-mtx` now terminates immediately after execution is finished.
## Version 1.10.0 - 2023-07-31

@@ -11,0 +27,0 @@

54

lib/pruneAxiosErrors.js

@@ -1,29 +0,27 @@

require('axios').interceptors.response.use(
response => response,
axError => {
const { url, method } = axError.config ?? {};
const { code, response } = axError;
const { status, data } = response ?? {};
const reason = data?.error /* RFC 6749 */ ?? axError.message;
const prefix = (url && method
? `${method.toUpperCase()} ${url} `
: '') +
'failed';
const message = prefix +
(status || code ? ':' : '') +
(status ? ` ${status}` : '') +
(code ? ` ${code}` : '') +
`. ${reason}.`;
const error = new Error(message);
error.status = status;
const cds = require('@sap/cds');
if (cds.debug('req|mtx')) {
error.cause = axError;
}
if (data) {
cds.log('req').error(`Details on error '${prefix}': ` +
(data.error_description /* RFC 6749 */ || require('util').inspect(data)) + `'`);
}
return Promise.reject(error);
module.exports = axError => {
const { inspect } = require('util');
const { url, method } = axError.config ?? {};
const { code, response } = axError;
const { status, data } = response ?? {};
const reason = data?.error /* RFC 6749 */ ? inspect(data.error) : axError.message;
const prefix = (url && method
? `${method.toUpperCase()} ${url} `
: '') +
'failed';
const message = prefix +
(status || code ? ':' : '') +
(status ? ` ${status}` : '') +
(code ? ` ${code}` : '') +
`. ${reason}.`;
const error = new Error(message);
error.status = status;
const cds = require('@sap/cds');
if (cds.debug('req|mtx')) {
error.cause = axError;
}
);
if (data) {
cds.log('req').error(`Details on error '${prefix}': ` +
(data.error_description /* RFC 6749 */ || inspect(data)) + `'`);
}
return Promise.reject(error);
}
{
"name": "@sap/cds-mtxs",
"version": "1.10.0",
"version": "1.11.0",
"description": "SAP Cloud Application Programming Model - Multitenancy library",

@@ -25,5 +25,5 @@ "homepage": "https://cap.cloud.sap/",

"dependencies": {
"axios": ">=0.27.2",
"axios": "^1",
"@sap/hdi-deploy": "^4"
}
}

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

this.on('DELETE', 'tenant', this.delete)
await super.init() // ensure to call super.init() // REVISIT: Why?
await super.init()
}

@@ -95,3 +94,3 @@

async read(context) { // TODO check params for get
async read(context) {
const tenant = this._getSubscribedTenant(context)

@@ -127,3 +126,3 @@ if (tenant) {

const dbToTenants = clusterSize > 1 ? await this._tenantsByDb(tenants) : [new Set(tenants)]
LOG.info('upgrading tenants', tenants)
LOG.info('upgrading', { tenants })
if (isSync) {

@@ -221,3 +220,2 @@ try {

if (!isSync && callbackUrl) {
/// TODO evaluate params for new rest adapter
const tenant = this._getSubscribedTenant(originalRequest.body)

@@ -224,0 +222,0 @@ const payload = { status, message, subscriptionUrl }

const https = require('https')
const { URL } = require('url')
const axios = require('axios')
const cds = require('@sap/cds/lib')
const LOG = cds.log('mtx'), DEBUG = cds.debug('mtx')
require('../../lib/pruneAxiosErrors')
const axiosInstance = require('axios').create();
axiosInstance.interceptors.response.use(response => response, require('../../lib/pruneAxiosErrors'))

@@ -31,5 +31,5 @@ module.exports = new class SaasRegistryUtil {

try {
return await axios(callbackUrl, { method: 'PUT', headers, data })
return await axiosInstance(callbackUrl, { method: 'PUT', headers, data })
} catch (error) {
cds.error('Error sending result callback to saas-registry: ' + error.message) // REVISIT: Just throw error?
cds.error('Error sending result callback to saas-registry: ' + error.message)
}

@@ -64,3 +64,3 @@ }

LOG.info(`getting saas-registry auth token from ${authUrl}`)
const { data: { access_token } } = await axios(authUrl, {
const { data: { access_token } } = await axiosInstance(authUrl, {
method: 'POST',

@@ -67,0 +67,0 @@ ...auth,

@@ -16,10 +16,15 @@ const cds = require('@sap/cds/lib')

const activate = async function ExtensibilityService_activate (ID, tag, tenant, csvs) {
const activate = async function ExtensibilityService_activate (ID, tag, tenant, csvs, async) {
if (tenant) cds.context = { tenant }
await _updateExtensions(ID, tag)
const { 'cds.xt.DeploymentService': ds } = cds.services
await ds.extend(tenant, csvs)
if (async) {
const { 'cds.xt.JobsService': js } = cds.services
return js.enqueue([new Set([tenant])], 'extend', [csvs])
} else {
const { 'cds.xt.DeploymentService': ds } = cds.services
await ds.extend(tenant, csvs)
}
}
module.exports = activate
const config = require('./config')
const LinterMessage = require('../linter/linterMessage')
const LinterMessage = require('../linter/message')

@@ -54,7 +54,7 @@ const parse_options = {

const ast = acorn.parse(code, parse_options)
walk.full(ast, node => {
walk.full(ast, node => {
if (config.restrict.language_keywords.includes(node.type)) {
findings.push(new LinterMessage(`${file} (${node.start}-${node.end}): Includes a forbidden construct ${node.type}`, { $location: { file } }))
}
config.restrict.language_concepts.forEach(concept => {

@@ -65,3 +65,3 @@ const finding = concepts[concept](node, file)

}
})
})

@@ -68,0 +68,0 @@ if (config.restrict.globals.includes(node.name)) {

const cds = require('@sap/cds/lib')
const { set_ } = require('./add')
const TOMBSTONE_ID = '__tombstone'
const readExtension = async function (req) {

@@ -9,3 +11,3 @@ const tenant = _tenant(req)

const ext = !req.data?.ID ? await SELECT.from('cds.xt.Extensions') : await SELECT.one.from('cds.xt.Extensions').where({ tag: req.data.ID })
if (Array.isArray(ext)) return ext.map(item => ({ ID: item.tag, csn: item.csn, timestamp: item.timestamp }))
if (Array.isArray(ext)) return ext.filter(item => item.tag !== TOMBSTONE_ID).map(item => ({ ID: item.tag, csn: item.csn, timestamp: item.timestamp }))
if (ext) return { ID: ext.tag, csn: ext.csn, timestamp: ext.timestamp }

@@ -27,3 +29,8 @@ return ext

return !req.data?.ID ? DELETE.from('cds.xt.Extensions') : DELETE.from('cds.xt.Extensions').where({ tag: req.data.ID })
const result = !req.data?.ID ? await DELETE.from('cds.xt.Extensions') : await DELETE.from('cds.xt.Extensions').where({ tag: req.data.ID })
// leave tombstone for deployment - ID cannot be used in case of all extensions (no ID passed)
await set_(req, { extension: ['{}'], tag: TOMBSTONE_ID, tenant: tenant ?? req.tenant })
return result
}

@@ -30,0 +37,0 @@

const cds = require('@sap/cds/lib')
const { deduplicateMessages } = require('@sap/cds-compiler')
const NamespaceChecker = require('./namespace_checker')
const AnnotationsChecker = require('./annotations_checker')
const AllowlistChecker = require('./allowlist_checker')
const CodeChecker = require('./code-checker')
const NamespaceChecker = require('./namespace')
const AnnotationsChecker = require('./annotations')
const AllowlistChecker = require('./allowlist')
const CodeChecker = require('./code')

@@ -9,0 +9,0 @@ const LINTER_OPTIONS = ['element-prefix', 'extension-allowlist', 'namespace-blocklist']

@@ -17,4 +17,4 @@ const cds = require('@sap/cds/lib'), { fs, path, tar, rimraf } = cds.utils

try {
const { extCsn, bundles, csvs } = await readData(extension, root)
const { extCsn, bundles, csvs } = await readData(extension, root)
if (main.requires.extensibility?.code) await addCodeAnnotations(root, extCsn, req.tenant)

@@ -79,3 +79,3 @@

// extension linters
const findings = linter.lint(extCsn, csn, cds.env)
const findings = linter.lint(extCsn, csn)
if (findings.length > 0) {

@@ -99,5 +99,6 @@ let message = `Validation for ${tag} failed with ${findings.length} finding(s):\n\n`

LOG.info(`activating extension '${tag}' ...`)
await activate(ID, null, tenant, csvs)
const async = cds.context.http?.req?.headers?.prefer === 'respond-async'
await activate(ID, null, tenant, csvs, async)
}
module.exports = { push, pull }

@@ -6,3 +6,4 @@ const cds = require('@sap/cds/lib');

const DEBUG = cds.debug('req|mtx');
require('../../../lib/pruneAxiosErrors');
const axiosInstance = require('axios').create();
axiosInstance.interceptors.response.use(response => response, require('../../../lib/pruneAxiosErrors'));

@@ -46,3 +47,3 @@ async function parseBody(request) {

try {
const { data } = await require('axios').post(
const { data } = await axiosInstance.post(
authProvider.authUrl,

@@ -49,0 +50,0 @@ authProvider.postData,

@@ -5,11 +5,11 @@ const { path, tar, read, readdir } = cds.utils

await tar.xvz(extension).to(root)
let extCsn = {}
try { extCsn = JSON.parse(await read(path.join(root, 'extension.csn'))) }
catch(e) { if (e.code !== 'ENOENT') throw e }
let bundles
try { bundles = await read(path.join(root, 'i18n', 'i18n.json')) }
catch(e) { if (e.code !== 'ENOENT') throw e }
let csvs = {}

@@ -24,3 +24,3 @@ try {

catch(e) { if (e.code !== 'ENOENT') throw e }
return { extCsn, bundles, csvs }

@@ -27,0 +27,0 @@ }

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

const crypto = require('crypto')
const fs = require('fs').promises

@@ -101,2 +102,3 @@ const path = require('path')

/** Implementation for getCsn */
const baseCache = new Map, extensionCache = new Map
async function _getCsn (req, checkExt) {

@@ -122,5 +124,11 @@ const { tenant, toggles, base, flavor, for:javaornode, activated } = req.data

DEBUG?.('loading models for', { tenant, toggles } ,'from', models.map (cds.utils.local))
let csn = await cds.load (models, { flavor, silent:true })
if (csn.meta?.flavor === 'inferred') csn = cds.minify (csn)
if (extensions) csn = cds.extend (csn) .with (extensions)
let csn = await lru4(baseCache, JSON.stringify({ models, flavor }), async () => {
const csn = await cds.load(models, { flavor, silent:true })
return csn.meta?.flavor === 'inferred' ? cds.minify(csn) : csn
})
if (extensions) {
const key = crypto.createHash('sha256').update(JSON.stringify({ extensions, models })).digest('hex')
csn = lru4(extensionCache, key, () => cds.extend (csn) .with (extensions))
}
if (javaornode) csn = cds.compile.for[javaornode] (csn)

@@ -133,3 +141,25 @@

/** Implementation for getExtensions */
/**
* A Least Recently Used (LRU) cache.
* @template T
* @param {string} key - Unique string for cache look-up.
* @param {() => T} fn - Function producing result to be cached.
* @returns {T}
*/
function lru4(cache, key, fn) {
// Starting simple, might later have different/dynamic cache sizes
const cacheSize = cds.requires['cds.xt.ModelProviderService']?.cacheSize ?? 5
let _csn = cache.get(key)
if (_csn) {
cache.delete(key)
cache.set(key, _csn)
} else {
cache.set(key, _csn = fn())
if (cache.size > cacheSize) {
cache.delete(cache.keys().next().value)
}
}
return _csn
}
async function _getExtensions4 (tenant, activated = false) {

@@ -151,3 +181,3 @@ if (!main.requires.extensibility || !tenant && main.requires.multitenancy) return

} catch (error) {
DEBUG?.('cds.xt.Extensions not yet deployed', error) // REVISIT: Questionable usage of try-catch pattern
DEBUG?.(`cds.xt.Extensions not yet deployed for tenant ${tenant}`, error) // REVISIT: Questionable usage of try-catch pattern
}

@@ -187,3 +217,3 @@ }

} catch (error) {
DEBUG?.('cds.xt.Extensions not yet deployed', error) // REVISIT: Questionable usage of try-catch pattern
DEBUG?.(`cds.xt.Extensions not yet deployed for tenant ${tenant}`, error) // REVISIT: Questionable usage of try-catch pattern
return BASE_MODEL_ETAG

@@ -216,3 +246,3 @@ }

} catch (e) {
DEBUG?.('cds.xt.Extensions not yet deployed', e) // REVISIT: Questionable usage of try-catch pattern
DEBUG?.(`cds.xt.Extensions not yet deployed for tenant ${tenant}`, e) // REVISIT: Questionable usage of try-catch pattern
return null

@@ -219,0 +249,0 @@ }

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

exports.activated = 'Generic metadata'
exports.activated = 'Generic Metadata'

@@ -8,0 +8,0 @@ // Add database-agnostic metadata handlers to DeploymentService...

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

if (csn) await build (csn,tenant,updateCsvs)
DEBUG?.('HANA build successfully finished')
DEBUG?.('finished HANA build')
}

@@ -250,0 +250,0 @@ if (csnFromParameter) {

@@ -10,3 +10,3 @@ const { join } = require('path')

DEBUG?.(`deployment directory: ${cwd}`)
DEBUG?.(`effective HDI options: ${env.HDI_DEPLOY_OPTIONS}`)
DEBUG?.(`effective HDI options:`, env.HDI_DEPLOY_OPTIONS)

@@ -70,3 +70,4 @@ const logPath = join(cds.root, 'logs', `${cds.context.tenant}.log`)

trace:1,
parameter:1
parameter:1,
treat_unmodified_as_modified:1
}))

@@ -73,0 +74,0 @@ if (invalid.length) {

const https = require('https')
const cds = require('@sap/cds')
const LOG = cds.log('mtx'), DEBUG = cds.debug('mtx')
const LOG = cds.log('mtx'), DEBUG = cds.debug('mtx|sm')
const { uuid } = cds.utils

@@ -8,4 +8,3 @@ const { cacheBindings = true } = cds.env.requires.multitenancy ?? {}

const axios = require('axios')
require('../../../lib/pruneAxiosErrors')
const api = axios.create({ baseURL: sm_url + '/v1/', 'Content-Type': 'application/json' })
const api = axios.create({ baseURL: sm_url + '/v1/', headers: { 'Content-Type': 'application/json' }})
api.interceptors.request.use(async config => {

@@ -15,2 +14,3 @@ config.headers.Authorization = await _token()

})
api.interceptors.response.use(response => response, require('../../../lib/pruneAxiosErrors'))

@@ -94,4 +94,5 @@ /* API */

async function _bindings4(tenants, options = {}) {
const uncached = cacheBindings && !options.disableCache && _bindings4.cached && tenants !== '*' ? tenants.filter(t => !(t in _bindings4.cached)) : tenants
DEBUG && DEBUG('Retrieving', { tenants }, { uncached })
const useCache = cacheBindings && !options.disableCache && tenants !== '*'
const uncached = useCache ? tenants.filter(t => !(t in _bindings4.cached)) : tenants
DEBUG?.('retrieving', { tenants }, { uncached })
if (uncached.length === 0) return tenants.map(t => _bindings4.cached[t])

@@ -101,13 +102,15 @@ const _tenantFilter = () => ` and tenant_id in (${uncached.map(t => `'${t}'`).join(', ')})`

const labelQuery = `service_plan_id eq '${await _planId()}'` + tenantFilter
const bindings = []; let token
const fetched = []; let token
do {
// eslint-disable-next-line no-await-in-loop
const { items, token: nextPageToken } = (await api.get('service_bindings', { params: { token, labelQuery }})).data
bindings.push(...items)
fetched.push(...items)
token = nextPageToken
} while (token)
if (cacheBindings) Object.assign(_bindings4.cached ?? {},
Object.fromEntries(bindings.filter(b => b.labels?.tenant_id).map(b => [b.labels.tenant_id[0], b]))
)
return bindings
if (useCache) {
const cacheMisses = Object.fromEntries(fetched.filter(b => b.labels?.tenant_id).map(b => [b.labels.tenant_id[0], b]))
Object.assign(_bindings4.cached, cacheMisses)
return tenants.map(t => _bindings4.cached[t])
}
return fetched
}

@@ -160,3 +163,3 @@

const msg = `Error ${action} tenant ${tenant}: ${e.response?.data?.error ?? e.code ?? 'unknown error'}`
const cause = e.cause ? require('os').EOL + `Root Cause: ${e.description ?? e.cause}` : ''
const cause = e.description || e.cause ? require('os').EOL + `Root Cause: ${e.description ?? e.cause}` : ''
return msg + cause

@@ -181,3 +184,3 @@ }

delay = 300 * 2 ** (attempt - 1)
} else if (status in { 500: 1, 503: 1 }) {
} else {
delay = 1000 * 3 ** (attempt - 1)

@@ -184,0 +187,0 @@ }

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

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

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc