Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@sap/cds-mtxs

Package Overview
Dependencies
Maintainers
1
Versions
62
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.17.0 to 1.18.0

37

bin/cds-mtx.js

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

async function cds_mtx(cmd, tenant) {
async function cds_mtx(cmd, tenant, body) {

@@ -21,2 +21,11 @@ let connectedTenants = [tenant]

if (/.*,.*/.test(tenant)) return _handleError(`List of tenants not supported: ${tenant}`)
// parse body and handle error
let parsedMetadata
if (body) {
try {
parsedMetadata = JSON.parse(body)
} catch (e) {
return _handleError(`Invalid subscription body: ${e.message}: ${body} `)
}
}
const { 'cds.xt.DeploymentService':ds, 'cds.xt.SaasProvisioningService':sps } = await cds.serve ([

@@ -34,2 +43,3 @@ '@sap/cds-mtxs/srv/deployment-service',

if (tenant === '*') {
if (cmd !== 'upgrade') return _handleError('"*" only supported for upgrade command')
const tenants = await sps.read('tenant')

@@ -40,6 +50,8 @@ connectedTenants = tenants.map(t => t.subscribedTenantId)

}
await ds[cmd](tenant)
await ds[cmd](tenant, parsedMetadata, parsedMetadata)
} catch(e) {
console.error(e.message)
process.exit(1)
if (isCli) {
console.error(e.message)
process.exit(1)
} else throw e
} finally {

@@ -82,4 +94,4 @@ if (cds.db) {

async function _handleError(message) {
console.error(message)
if (isCli) {
console.error(message)
process.exit(1)

@@ -95,3 +107,3 @@ }

cds-mtx <command> <tenant>
cds-mtx <command> <tenant> [--body <json>]

@@ -103,2 +115,10 @@ COMMANDS

upgrade upgrade a tenant
EXAMPLES
cds-mtx subscribe t1
cds-mtx subscribe t1 --body '{ "_": { "hdi": { "create": { "database_id": "<database id>" } } } }'
cds-mtx unsubscribe t1
cds-mtx upgrade t1
cds-mtx upgrade *
`

@@ -109,5 +129,6 @@ )

if (isCli) {
const [, , cmd, tenant] = process.argv
;(async () => await cds_mtx(cmd, tenant))()
const [, , cmd, tenant, option, json] = process.argv
if (option && option !== '--body') _usage(`Invalid option ${option}`)
;(async () => await cds_mtx(cmd, tenant, option ? json : undefined))()
}
module.exports = { cds_mtx }

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

## Version 1.18.0 - 2024-04-29
### Added
- `cds-mtx subscribe <tenant> --body <json>` now allows to pass tenant metadata and HDI parameters.
### Changed
- Retries for failed upgrades are more resilient, using an exponential backoff mechanism and more retries.
### Fixed
- Extension linter is now also called if extensions are created via API.
- The Service Manager credentials cache is correctly invalidated following a resubscription.
## Version 1.17.0 - 2024-03-25

@@ -11,0 +26,0 @@

@@ -25,3 +25,3 @@ const path = require('path')

await fs.rm(projectFolder, { recursive: true, force: true })
} catch (e) {
} catch (_) {
// ignore

@@ -28,0 +28,0 @@ }

@@ -408,3 +408,3 @@ const path = require('path')

} catch (e) {
req.reject(404, `No migrated projects found for tenant ${tenant}`)
req.reject(404, `No migrated projects found for tenant ${tenant}: ${e.message}`)
}

@@ -411,0 +411,0 @@ }

@@ -113,9 +113,9 @@ const LOG = cds.log('mtx')

* @param {number} [retryCount=5]
* @param {number} [retryGap=5000]
* @param {number} [initialRetryGap=5000]
* @returns {Promise<T>}
*/
const retry = async(fn, retryCount = 5, retryGap = 5 * 1000) => {
const retry = async (fn, retryCount = cds.requires.multitenancy.retries ?? 10, initialRetryGap = 5000) => {
let errorCount = 0
let finalError
let retryGap = initialRetryGap
while (errorCount < retryCount - 1) {

@@ -131,2 +131,3 @@ try {

await promisify(setTimeout)(retryGap) // eslint-disable-line no-await-in-loop
retryGap *= 1.5
}

@@ -133,0 +134,0 @@ }

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

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

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

cert = new X509Certificate(buffer)
} catch (e) {
} catch (_) {
return req.reject(401, 'Invalid certificate')

@@ -129,0 +129,0 @@ }

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

const activateExt = require('./activate')
const { runLinter } = require('./utils')

@@ -65,3 +66,3 @@ const _isCSN = str => str.substring(0, 1) === '{'

} catch (e) {
req.reject(422, 'Invalid json content in i18n.json')
req.reject(422, `Invalid json content in i18n.json: ${e.message}`)
}

@@ -104,3 +105,4 @@ return

} catch (err) {
req.reject(400, getCompilerError(err.messages))
if (err.code === 'ERR_CDS_COMPILATION_FAILURE') req.reject(422, getCompilerError(err.messages))
else req.reject(400, err.message)
}

@@ -157,2 +159,5 @@ }

// call linter
await runLinter(tenant, extCsn, tag, req)
const { bundles, csvs } = _getFiles(resources, req)

@@ -159,0 +164,0 @@ if (tag) await DELETE.from('cds.xt.Extensions').where({ tag })

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

const activate = require('./activate')
const { getCompilerError } = require('../../lib/utils')
const { addCodeAnnotations } = require('./code-extensibility/addCodeAnnotProd')
const { readData } = require('./utils')
const { readData, runLinter } = require('./utils')

@@ -33,3 +32,3 @@ const TEMP_DIR = fs.realpathSync(require('os').tmpdir())

tenant: req.tenant,
toggles: Object.keys(cds.context.features || {}), // with all enabled feature extensions
toggles: cds.context.features, // with all enabled feature extensions
base: true, // without any custom extensions

@@ -80,21 +79,4 @@ flavor: 'xtended'

// do validation after extension table update - trust transaction handling for rollback
// compiler validation
LOG.info(`validating extension '${tag}' ...`)
const { 'cds.xt.ModelProviderService': mps } = cds.services
// REVISIT: Isn't that also done during activate?
let csn
try {
csn = await mps.getCsn(tenant, Object.keys(cds.context.features || {}))
} catch (err) {
return req.reject(400, getCompilerError(err.messages))
}
// extension linters
const findings = linter.lint(extCsn, csn)
if (findings.length > 0) {
let message = `Validation for ${tag} failed with ${findings.length} finding(s):\n\n`
message += findings.map(f => ' - ' + f.message).join('\n') + '\n'
return req.reject(422, message)
}
await runLinter(tenant, extCsn, tag, req)

@@ -101,0 +83,0 @@ LOG.info(`activating extension '${tag}' ...`)

const { path, tar, read, readdir } = cds.utils
const linter = require('./linter')
const LOG = cds.log('mtx')
const { getCompilerError } = require('../../lib/utils')

@@ -27,2 +30,24 @@ const readData = async function (extension, root) {

module.exports = { readData }
const runLinter = async function (tenant, extCsn, tag, req) {
LOG.info(`validating extension '${tag}' ...`)
const { 'cds.xt.ModelProviderService': mps } = cds.services
// REVISIT: Isn't that also done during activate?
let csn
try {
csn = await mps.getCsn(tenant, cds.context.features)
} catch (err) {
return req.reject(400, getCompilerError(err.messages))
}
const findings = linter.lint(extCsn, csn)
if (findings.length > 0) {
let message = `Validation for ${tag} failed with ${findings.length} finding(s):\n\n`
message += findings.map(f => ' - ' + f.message).join('\n') + '\n'
return req.reject(422, message)
}
}
module.exports = { readData, runLinter }

@@ -49,6 +49,8 @@ const crypto = require('crypto')

this.before(['getCsn', 'getEdmx', 'getExtCsn', 'getI18n'], req => {
const regex = /^[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)*$|^\$hash$|^\*$/
const toggles = req.data?.toggles
const invalid = Array.isArray(toggles) ? toggles.find(t => !regex.test(t)) :
typeof toggles === 'string' && !regex.test(toggles) && toggles
let toggles = req.data?.toggles
if (!toggles) return
else if (Array.isArray(toggles)) ; //> go on below...
else if (typeof toggles === 'object') toggles = Object.keys(toggles)
else if (typeof toggles === 'string') toggles = toggles.split(',')
const invalid = toggles.find(t => t !== '*' && !/^[\w-]+$/.test(t))
if (invalid) return req.reject(400, `Unsupported input toggle param ${invalid}`)

@@ -151,3 +153,3 @@ })

async function _getCsn (req, checkExt) {
const { tenant, toggles, base, flavor, for:javaornode, activated } = req.data
let { tenant, toggles, base, flavor, for:javaornode, activated } = req.data

@@ -167,2 +169,3 @@ if (conf._in_sidecar) {

if (toggles && typeof toggles === 'object' && !Array.isArray(toggles)) toggles = Object.keys(toggles)
const features = (!toggles || !main.requires.toggles) ? [] : toggles === '*' || toggles.includes('*') ? [fts] : toggles.map (f => fts.replace('*',f))

@@ -169,0 +172,0 @@ const models = cds.resolve (['*',...features], main); if (!models) return

const https = require('https')
const { inspect } = require('util')
const cds = require('@sap/cds')

@@ -7,2 +8,3 @@ const LOG = cds.log('mtx'), DEBUG = cds.debug('mtx|sm')

const { sm_url, url, clientid, clientsecret, certurl, certificate, key } = cds.env.requires.db.credentials
const COLORS = !!process.stdout.isTTY && !!process.stderr.isTTY && !process.env.NO_COLOR
const axios = require('axios')

@@ -12,9 +14,14 @@ const api = axios.create({ baseURL: sm_url + '/v1/', headers: { 'Content-Type': 'application/json' }})

conf.headers.Authorization = await _token()
DEBUG?.(conf.method.toUpperCase(), conf.baseURL + conf.url, {
DEBUG?.('>', conf.method.toUpperCase(), conf.baseURL + conf.url, inspect({
...(conf.params && { params: conf.params }),
...(conf.data && { data: conf.data })
})
}, { depth: 11, compact: false, colors: COLORS }))
return conf
})
api.interceptors.response.use(response => response, require('../../../lib/pruneAxiosErrors'))
api.interceptors.response.use(response => {
const { method, baseURL, url, status, statusText } = response.config
DEBUG?.('<', method.toUpperCase(), baseURL + url, status, statusText,
inspect(response.data, { depth: 11, colors: COLORS }))
return response
}, require('../../../lib/pruneAxiosErrors'))

@@ -135,5 +142,5 @@ /* API */

} while (token)
const cacheMisses = Object.fromEntries(fetched.filter(b => b.labels?.tenant_id).map(b => [b.labels.tenant_id[0], b]))
Object.assign(_bindings4.cached, cacheMisses)
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])

@@ -140,0 +147,0 @@ }

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