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

@sap/cds-mtxs

Package Overview
Dependencies
Maintainers
0
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.18.2 to 2.0.2

srv/extensibility/set.js

43

CHANGELOG.md

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

## Version 2.0.2 - 2024-07-11
### Changed
- Calls to Service Manager now include the `Client-ID` and `Client-Version` headers.
### Fixed
- Reduced number of Service Manager requests to `service_instances` in the error case.
## Version 2.0.1 - 2024-07-04
### Added
- Added HANA build plugin mappings `.hdbeshconfig` and `.hdbcalculationview` required for enterprise search and embedded analytics integration.
- The Service Manager client reports the container state on timeouts.
### Fixed
- Improved robustness in case of temporary extension inconsistencies.
- The Service Manager client now stores instance and binding locations used for async polling in-memory, allowing parallel subscriptions for single-instance applications.
- The Service Manager client automatically recreates instances in "creation failed" state on subscription.
### Removed
- `@sap/instance-manager` is not supported any longer as a fallback to the built-in Service Manager client.
## Version 2.0.0 - 2024-06-19
### Added
- Additional endpoint to get the passcode URL.
- Dependencies for the SAP BTP Connectivity, Audit Logging, and Destinations services are automatically added if `cds.requires.[connectivity|audit-log|destinations]` properties are set, respectively.
### Changed
- `@sap/cds-mtxs` now requires `@sap/hdi-deploy >= 4`.
- Deprecated endpoint `upgradeAll` has been removed from `SaasProvisioningService`.
- Use the `cds.compile.to.hana` API to support cds plugins such as embedded analytics.
- When pushing an extension, the extension is blocked if it contains critical annotations.
## Version 1.18.2 - 2024-06-24

@@ -22,2 +63,3 @@

## Version 1.18.1 - 2024-05-16

@@ -62,2 +104,3 @@

- `/-/cds/saas-provisioning/upgrade` sent as an async request with payload `"tenants": ["*"]` will now return job information even if no tenants are found.
- Default restrictions for code extensions and security annotations are now aways applied.

@@ -64,0 +107,0 @@ ### Fixed

27

env.js

@@ -0,1 +1,4 @@

const path = require('path')
const cds = require('@sap/cds')
const hana_mt = { kind: 'hana',

@@ -54,2 +57,22 @@ "deploy-format": "hdbtable",

},
"audit-log": {
vcap: { label: "auditlog" },
subscriptionDependency: {
uaa: 'xsappname'
}
},
"portal": {
vcap: { label: "portal" },
subscriptionDependency: {
uaa: 'xsappname'
}
},
"connectivity": {
vcap: { label: "connectivity" },
subscriptionDependency: 'xsappname'
},
"destinations": {
vcap: { label: "destination" },
subscriptionDependency: 'xsappname'
},

@@ -91,3 +114,5 @@ ////////////////////////////////////////////////////////////////////////

"[mtx-sidecar]": {
i18n: {
"[development]": { root: path.resolve(cds.root, '../..') }
},
requires: {

@@ -94,0 +119,0 @@ db: {

94

lib/utils.js

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

// REVISIT: eliminate usage of these helpers
const { ensureNoDraftsSuffix, ensureUnlocalized } = require('@sap/cds/libx/_runtime/common/utils/draft')
const EXT_BACK_PACK = 'extensions__'
const getTargetRead = req => {
let name = ''
if (req.query.SELECT.from.join && req.query.SELECT.from.args) {
// join
name = req.query.SELECT.from.args.find(arg => arg.ref && arg.ref[0] !== 'DRAFT.DraftAdministrativeData').ref[0]
} else if (req.target.name.SET) {
// union
name = req.target.name.SET.args[0]._target.name
} else {
// simple select
name = req.target.name
}
return { name: ensureUnlocalized(ensureNoDraftsSuffix(name)) }
}
const getTargetWrite = (target, model) => {
return model.definitions[ensureUnlocalized(ensureNoDraftsSuffix(target.name))]
}
const isExtendedEntity = (entityName, model) => {
const entity = model.definitions[ensureUnlocalized(ensureNoDraftsSuffix(entityName))]
if (!entity) return false
return entity.elements[EXT_BACK_PACK] || Object.values(entity.elements).some(el => el['@cds.extension'])
}
const _hasExtendedEntityArgs = (args, model) => {
return args.find(arg => {
if (arg.ref) {
return arg.ref[0] !== 'DRAFT.DraftAdministrativeData' && isExtendedEntity(arg.ref[0], model)
}
if (arg.join) {
return _hasExtendedEntityArgs(arg.args, model)
}
})
}
const _hasExtendedExpand = (columns, targetName, model) => {
for (const col of columns) {
if (col.ref && col.expand) {
let targetNameModel = ensureUnlocalized(ensureNoDraftsSuffix(targetName))
if (cds.env.fiori?.lean_draft) targetNameModel = targetNameModel.replace(/\.drafts$/,'')
const expTargetName = model.definitions[targetNameModel].elements[col.ref[col.ref.length - 1]].target
if (isExtendedEntity(expTargetName, model)) return true
_hasExtendedExpand(col.expand, expTargetName, model)
}
}
}
const hasExtendedEntity = (req, model) => {
if (!req.query.SELECT) return false
if (req.query.SELECT.columns && req.target && _hasExtendedExpand(req.query.SELECT.columns, req.target.name, model)) {
return true
}
if (req.query.SELECT.from.join && req.query.SELECT.from.args) {
return _hasExtendedEntityArgs(req.query.SELECT.from.args, model)
}
if (req.target) {
if (req.target.name.SET) {
return isExtendedEntity(req.target.name.SET.args[0]._target.name, model)
}
return isExtendedEntity(req.target.name, model)
}
}
const getExtendedFields = (entityName, model) => {
const elements = model.definitions[ensureUnlocalized(ensureNoDraftsSuffix(entityName))].elements
return Object.values(elements)
.filter(element => {
return element['@cds.extension']
})
.map(element => {
return element.name
})
}
const getCompilerError = messages => {

@@ -148,8 +60,2 @@ const defaultMsg = 'Error while compiling extension'

module.exports = {
EXT_BACK_PACK,
getTargetRead,
getTargetWrite,
isExtendedEntity,
hasExtendedEntity,
getExtendedFields,
getCompilerError,

@@ -156,0 +62,0 @@ t0_,

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

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

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

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

const { headers, data, http } = context
DEBUG?.('received subscription request with', { data })
DEBUG?.('received subscription request with', { data: require('util').inspect(data, { depth: 11 }) })
const options = this._options4(metadata)

@@ -215,10 +215,30 @@ // REVISIT: removing as they are polluting logs -> clearer data/options separation

async _upgradeAll(context) {
const { tenants } = context.data
LOG.warn(`upgradeAll is deprecated. Use /-/cds/saas-provisioning/upgrade instead.`)
return this.__upgrade(tenants ?? ['*'])
}
_dependencies() {
return cds.env.requires[this.name]?.dependencies?.map(d => ({ xsappname: d })) ?? []
// Compat for cds.requires.multitenancy.dependencies
const provisioning = cds.env.requires[this.name] ?? cds.env.requires.multitenancy
if (provisioning?.dependencies) {
return provisioning?.dependencies?.map(d => ({ xsappname: d })) ?? []
}
// Construct from cds.requires
let dependencies = []
const extractDependency = (node, root) => {
if (typeof node === 'object' && node !== null) {
Object.entries(node).forEach(([key, value]) => {
if (typeof value === 'object') {
extractDependency(value, root[key])
} else {
dependencies.push(root[key][value])
}
})
} else if (typeof node === 'string') {
dependencies.push(root[node])
}
}
Object.entries(cds.env.requires).forEach(([name, req]) => {
const tree = req.subscriptionDependency
if (!tree) return
extractDependency(tree, cds.requires[name].credentials)
})
LOG.info('using SaaS dependencies', dependencies)
return dependencies.map(d => ({ xsappname: d }))
}

@@ -225,0 +245,0 @@

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

this.on('upgrade', super._upgrade)
this.on('upgradeAll', super._upgradeAll)
await super.init()

@@ -19,0 +18,0 @@ }

const cds = require('@sap/cds/lib')
const main = require('./config')
const addExtension = require('./extensibility/addExtension')
const { add, promote, setExtension } = require('./extensibility/add')
const { setExtension } = require('./extensibility/set')
const { push, pull } = require('./extensibility/push')
const { readExtension, updateExtension, deleteExtension } = require('./extensibility/crud')
const token = require('./extensibility/token')
const { transformExtendedFieldsCREATE, transformExtendedFieldsUPDATE } = require('./extensibility/handler/transformWRITE')
const { transformExtendedFieldsREAD } = require('./extensibility/handler/transformREAD')
const { transformExtendedFieldsRESULT } = require('./extensibility/handler/transformRESULT')
const { token, authMeta } = require('./extensibility/token')
const { addCodeAnnotations } = require('./extensibility/code-extensibility/addCodeAnnotLocal')

@@ -18,6 +14,3 @@

async init() {
this.on('addExtension', addExtension)
this.on('add', add)
this.on('promote', promote)
async init() {
this.on('push', push)

@@ -54,16 +47,8 @@ this.on('pull', pull)

cds.app.post('/-/cds/login/token', token)
cds.app.get('/-/cds/login/token', token)
cds.app.get('/-/cds/login/authorization-metadata', authMeta)
}
})
})
const { 'cds.xt.ModelProviderService': mps } = cds.services
if (!mps?._in_sidecar && cds.db?.model)
cds.db
.before('CREATE', transformExtendedFieldsCREATE)
.before('UPDATE', transformExtendedFieldsUPDATE)
.before('READ', transformExtendedFieldsREAD)
.after('READ', transformExtendedFieldsRESULT)
return super.init()
}
}
const cds = require('@sap/cds/lib')
const Extensions = 'cds.xt.Extensions'
const _updateExtensions = async function (ID, tag) {
const updateCqn = UPDATE(Extensions)
.with(`csn = REPLACE(csn, ',"@cds.extension":true', ''), activated = 'database'`)
.where({ activated: 'propertyBag' })
if (ID) {
updateCqn.where('ID =', ID)
} else if (tag) {
updateCqn.where('tag =', tag)
}
await cds.db.run(updateCqn)
}
const activate = async function ExtensibilityService_activate (ID, tag, tenant, csvs, async) {
const activate = async function (tenant, csvs, async) {
if (tenant) cds.context = { tenant }
await _updateExtensions(ID, tag)

@@ -20,0 +6,0 @@ if (async) {

const cds = require('@sap/cds/lib')
const { set_ } = require('./add')
const { set_ } = require('./set')

@@ -39,3 +39,3 @@ const TOMBSTONE_ID = '__tombstone'

if (!req.user.is('internal-user') && tenant && tenant !== req.tenant)
req.reject(403, `No permission to promote extensions by tenants other than ${req.tenant}`)
req.reject(403, `No permission to activate extensions by tenants other than ${req.tenant}`)
return tenant

@@ -42,0 +42,0 @@ }

const LinterMessage = require('./message')
const AT_REQUIRES = '@requires'
const AT_RESTRICT = '@restrict'
const AT_CDS_PERSISTENCE_JOURNAL = '@cds.persistence.journal'
const AT_SQL_APPEND = '@sql.append'
const AT_SQL_PREPEND = '@sql.prepend'
const checkedAnnotations = new Map([
[AT_REQUIRES, _createSecurityAnnotationWarning],
[AT_RESTRICT, _createSecurityAnnotationWarning],
[AT_CDS_PERSISTENCE_JOURNAL, _createJournalAnnotationWarning],
[AT_SQL_APPEND, _createSqlAnnotationWarning],
[AT_SQL_PREPEND, _createSqlAnnotationWarning]
// annotations with specific checks or messages
const checkedExtensionAnnotations = new Map([
['@requires', _createSecurityAnnotationWarning],
['@restrict', _createSecurityAnnotationWarning],
['@cds.persistence.journal', _createJournalAnnotationWarning],
['@mandatory', _createMandatoryAnnotationWarning],
['@assert.notNull', _createMandatoryAnnotationWarning],
['@assert.range', _createMandatoryAnnotationWarning]
])
function _createSqlAnnotationWarning(annotationName, annoOrElem) {
const criticalNewEntityAnnotations = /@sql.prepend|@sql.append|@cds.persistence.(?!skip)\w*/
const criticalExtensionAnnotations = new RegExp('^@(?:'
+ 'requires'
+ '|restrict'
+ '|readonly'
+ '|mandatory'
+ '|assert.*'
+ '|cds.persistence.*'
+ '|sql.append'
+ '|sql.prepend'
// service annotations
+ '|path'
+ '|impl'
+ '|cds.autoexpose'
+ '|cds.api.ignore'
+ '|odata.etag'
+ '|cds.query.limit'
+ '|cds.localized'
+ '|cds.valid.*'
+ '|cds.search)'
);
function locationString(element) {
const loc = element?.$location
if (!loc) return ''
const line = loc.line ? `${loc.line}:` : ''
return loc.col ? `${loc.file}:${line}${loc.col}:` : `${loc.file}:${line}`
}
function _createGenericAnnotationWarning(annotationName, annoOrElem) {
const message = `Annotation '${annotationName}' in '${
annoOrElem.annotate || annoOrElem.name
annoOrElem.element.annotate || annoOrElem.element.name || annoOrElem.parent.extend || annoOrElem.parent.annotate
}' is not supported in extensions`
return new LinterMessage(message, annoOrElem)
return new LinterMessage(locationString(annoOrElem.element) + message, annoOrElem.element)
}
function _createMandatoryAnnotationWarning(annotationName, annoOrElem) {
if (annoOrElem.element.default) return
const message = `Annotation '${annotationName}' in '${
annoOrElem.element.annotate || annoOrElem.element.name || annoOrElem.parent.extend || annoOrElem.parent.annotate
}' is not supported in extensions without default value`
return new LinterMessage(locationString(annoOrElem.element) + message, annoOrElem.element)
}
function _createSecurityAnnotationWarning(annotationName, annoOrElem) {
const message = `Security relevant annotation '${annotationName}' in '${
annoOrElem.annotate || annoOrElem.name
annoOrElem.element.annotate || annoOrElem.element.name
}' cannot be overwritten`
return new LinterMessage(message, annoOrElem)
return new LinterMessage(locationString(annoOrElem.element) + message, annoOrElem.element)
}

@@ -33,5 +66,5 @@

const message = `Enabling schema evolution in extensions using '${annotationName}' in '${
annoOrElem.annotate || annoOrElem.name
}' not yet supported`
return new LinterMessage(message, annoOrElem)
annoOrElem.element.annotate || annoOrElem.element.name
}' not supported`
return new LinterMessage(locationString(annoOrElem.element) + message, annoOrElem.element)
}

@@ -41,3 +74,3 @@

const message = `Extending entity '${element.extend}' is not supported as the corresponding database table has been enabled for schema evolution`
return new LinterMessage(message, element)
return new LinterMessage(locationString(element) + message, element)
}

@@ -51,6 +84,3 @@

// annotations via annotate - applies for all
const annotationExtensions = Object.values(reflectedCsn.extensions).filter(
value => value.annotate && Object.getOwnPropertyNames(value).filter(property => checkedAnnotations.get(property))
)
const annotationExtensions = []
const messages = []

@@ -61,11 +91,10 @@

() => true,
element => {
if (element[AT_SQL_PREPEND] || element[AT_SQL_APPEND]) {
if (!element.annotate) {
// do not add annotation extensions again
annotationExtensions.push(element)
(element, name, parent) => {
if (Object.getOwnPropertyNames(element).filter(property =>
property.startsWith('@') && criticalExtensionAnnotations.test(property)).length) {
annotationExtensions.push({element, name, parent})
}
}
if (element.extend) {
this._checkExtendedEntityAnnotations(fullCsn, element, messages)
// check base entity for incompatible annotations
this._checkExtendedEntityAnnotations(fullCsn, element, messages) // checks e. g. for journal annotations in base entity
}

@@ -76,9 +105,9 @@ },

// check entities and fields from definitions
// check entities and fields from new definitions
const annotatedDefinitions = []
reflectedCsn.forall(
() => true,
element => {
if (element[AT_SQL_PREPEND] || element[AT_SQL_APPEND] || element[AT_CDS_PERSISTENCE_JOURNAL]) {
annotatedDefinitions.push(element)
(element, name, parent) => {
if (Object.getOwnPropertyNames(element).filter(property => criticalNewEntityAnnotations.test(property)).length) {
annotatedDefinitions.push({element, name, parent})
}

@@ -89,4 +118,4 @@ },

for (const annotationExtension of annotationExtensions) {
const warning = this._checkAnnotation(annotationExtension, reflectedCsn.definitions, compileDir)
for (const annotation of [...annotationExtensions, ...annotatedDefinitions]) {
const warning = this._checkExtensionAnnotation(annotation, reflectedCsn.definitions, compileDir)
if (warning) {

@@ -97,32 +126,19 @@ messages.push(warning)

for (const annotatedDefinition of annotatedDefinitions) {
const warning = this._checkAnnotation(annotatedDefinition, reflectedCsn.definitions, compileDir)
if (warning) {
messages.push(warning)
}
}
return messages
}
_checkAnnotation(annotation, definitions, compileDir) {
if (!definitions[annotation.annotate]) {
return this._createAnnotationsWarning(annotation, compileDir)
_checkExtensionAnnotation(annotation, definitions) {
if (!definitions[annotation.element.annotate ?? annotation.parent?.annotate ?? annotation.parent?.extend]) {
const annotationName = Object.getOwnPropertyNames(annotation.element).filter(property => property.startsWith('@'))
if (annotationName.length) {
const fn = checkedExtensionAnnotations.get(annotationName[0]) ?? _createGenericAnnotationWarning
return fn(annotationName, annotation)
}
}
return null
}
_createAnnotationsWarning(annotation) {
const annotationName = Object.getOwnPropertyNames(annotation).filter(property => checkedAnnotations.get(property))
if (annotationName.length) {
const fn = checkedAnnotations.get(annotationName[0]).bind(this)
return fn(annotationName, annotation)
}
}
_checkExtendedEntityAnnotations(fullCsn, element, messages) {
const kind = fullCsn.definitions[element.extend]?.kind
if (kind === 'entity' && fullCsn.definitions[element.extend]?.[AT_CDS_PERSISTENCE_JOURNAL]) {
if (kind === 'entity' && fullCsn.definitions[element.extend]?.['@cds.persistence.journal']) {
messages.push(_createJournalEntityExtensionNotAllowedWarning(element))

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

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

for (let p of LEGACY_OPTIONS) if ((x = compat[p])) linter_options[p] = x // eslint-disable-line no-cond-assign
if (!Object.keys(linter_options).length) return []
const hasConfig = Object.keys(linter_options).length

@@ -27,6 +27,6 @@ const reflectedCsn = cds.reflect(extCsn)

const messages = [
...new NamespaceChecker().check(reflectedCsn, reflectedFullCsn, compileBaseDir, linter_options),
...new AnnotationsChecker().check(reflectedCsn, reflectedFullCsn, compileBaseDir, linter_options),
...new AllowlistChecker().check(reflectedCsn, reflectedFullCsn, compileBaseDir, linter_options),
...new CodeChecker().check(reflectedCsn, compileBaseDir, linter_options)
...(hasConfig ? new NamespaceChecker().check(reflectedCsn, reflectedFullCsn, compileBaseDir, linter_options) : []),
...new AnnotationsChecker().check(reflectedCsn, reflectedFullCsn, compileBaseDir, linter_options), // always mandatory
...(hasConfig ? new AllowlistChecker().check(reflectedCsn, reflectedFullCsn, compileBaseDir, linter_options) : []),
...new CodeChecker().check(reflectedCsn, compileBaseDir, linter_options) // always mandatory
]

@@ -33,0 +33,0 @@ deduplicateMessages(messages)

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

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

@@ -1,3 +0,1 @@

const { URL } = require('url');
const AuthProvider = require('./AuthProvider');

@@ -4,0 +2,0 @@ const { assertDefined } = require('./util/SecretsUtil');

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

module.exports = async function token(request, response) {
async function token(request, response) {
if (request.method === 'HEAD') {

@@ -33,12 +33,2 @@ return response.status(204).send();

const { credentials } = cds.env.requires.auth;
const isEmpty = o => !o || Object.keys(o).length === 0;
if (request.method === 'GET' && isEmpty(request.query)) {
const passcodeUrl = new AuthProvider(credentials, {}).passcodeUrl;
DEBUG?.(`Sending passcode URL to client:`, passcodeUrl);
return response.status(200).send({
passcodeUrl
});
}
const query = request.method === 'POST'

@@ -85,1 +75,17 @@ ? await parseBody(request)

}
async function authMeta(request, response) {
const { credentials } = cds.env.requires.auth;
const passcodeUrl = new AuthProvider(credentials, {}).passcodeUrl;
DEBUG?.(`Sending passcode URL to client:`, passcodeUrl);
// NOTE: Use snake_case for properties for compatibility with RFC 8414.
return response.status(200).send({
passcode_url: passcodeUrl
});
}
module.exports = {
token,
authMeta
}

@@ -219,3 +219,3 @@ const crypto = require('crypto')

try {
const cqn = SELECT('csn').from('cds.xt.Extensions')
const cqn = SELECT(['csn','tag']).from('cds.xt.Extensions').orderBy('tag','timestamp')
if (activated) cqn.where('activated=', 'database')

@@ -226,6 +226,9 @@ const exts = await cds.db.run(cqn)

const merged = { extensions: [], definitions: {} }
for (const { csn } of exts) {
let lastTag
for (const { csn, tag } of exts) {
if (lastTag === tag) continue // skip duplicates that might have been created due to race conditions
const {definitions,extensions} = JSON.parse(csn)
if (definitions) Object.assign (merged.definitions, definitions)
if (extensions) merged.extensions.push (...extensions)
lastTag = tag
}

@@ -232,0 +235,0 @@ return merged

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

const useOldIm = cds.env.requires['cds.xt.DeploymentService']?.['old-instance-manager']
// FIXME: Do that check in a better way, as ExtensibilityService is always on with mtx-sidecar preset

@@ -25,3 +23,3 @@ const isExtensible = cds.requires.extensibility || cds.requires['cds.xt.ExtensibilityService']

const hana = useOldIm ? require('./hana/inst-mgr') : require('./hana/srv-mgr')
const hana = require('./hana/srv-mgr')
exports.activated = 'HANA Database'

@@ -88,3 +86,3 @@

// @sap/instance-manager compat
// @sap/instance-manager API compat
const compat = 'provisioning_parameters' in createParams || 'binding_parameters' in createParams

@@ -190,7 +188,6 @@ if (compat) {

const out = await fs.mkdirp(outRoot,'src','gen'), gen = []
cds.env.cdsc = main.env.cdsc // add cdsc options from main
const options = { messages: [], sql_mapping: cds.env.sql.names, assertIntegrity:false }
const { definitions: hanaArtifacts } = cds.compiler.to.hdi.migration(csn, options);
const hanaArtifacts = _compileToHana(csn)
const { getArtifactCdsPersistenceName } = cds.compiler
const migrationTables = new Set(cds.reflect(csn)

@@ -201,7 +198,2 @@ .all(item => item.kind === 'entity' && item['@cds.persistence.journal'])

if (options.messages.length > 0) {
// REVISIT: how to deal with compiler info and warning messages
DEBUG?.('cds compilation messages:', options.messages)
}
for (const { name, suffix, sql } of hanaArtifacts) {

@@ -236,3 +228,3 @@ if (suffix !== '.hdbtable' || !migrationTables.has(name)) {

if (e.code !== 'EACCES') throw e
LOG?.(`Using temporary directory ${TEMP_DIR} for build result`)
LOG?.(`using temporary directory ${TEMP_DIR} for build result`)
const out = path.join(TEMP_DIR, 'gen', `${tenant}${folderSuffix}`)

@@ -285,8 +277,10 @@ await mkdirp(out)

await fs.write ({ file_suffixes: {
csv: { plugin_name: 'com.sap.hana.di.tabledata.source' },
hdbconstraint: { plugin_name: 'com.sap.hana.di.constraint' },
hdbindex: { plugin_name: 'com.sap.hana.di.index' },
hdbtable: { plugin_name: 'com.sap.hana.di.table' },
hdbtabledata: { plugin_name: 'com.sap.hana.di.tabledata' },
hdbview: { plugin_name: 'com.sap.hana.di.view' }
csv: { plugin_name: 'com.sap.hana.di.tabledata.source' },
hdbconstraint: { plugin_name: 'com.sap.hana.di.constraint' },
hdbindex: { plugin_name: 'com.sap.hana.di.index' },
hdbtable: { plugin_name: 'com.sap.hana.di.table' },
hdbtabledata: { plugin_name: 'com.sap.hana.di.tabledata' },
hdbview: { plugin_name: 'com.sap.hana.di.view' },
hdbcalculationview: { plugin_name: 'com.sap.hana.di.calculationview' },
hdbeshconfig: { plugin_name: 'com.sap.hana.di.eshconfig' }
}}) .to (out,'src','gen','.hdiconfig')

@@ -314,3 +308,3 @@ }

if (/authentication failed/i.test(e.message) || /SSL certificate validation failed/i.test(e.message)) {
const hana = useOldIm ? require('./hana/inst-mgr') : require('./hana/srv-mgr')
const hana = require('./hana/srv-mgr')
return hana.get(tenant, { disableCache: true })

@@ -331,1 +325,26 @@ } else {

function _compileToHana(csn) {
cds.env.cdsc = main.env.cdsc // add cdsc options from main
const options = { messages: [], sql_mapping: cds.env.sql.names, assertIntegrity: false }
let definitions = []
if (cds.compile.to.hana) {
const files = cds.compile.to.hana(csn, options);
for (const [content, { file }] of files) {
if (path.extname(file) !== '.json') {
const { name, ext: suffix } = path.parse(file)
definitions.push({ name, suffix, sql: content })
}
}
} else {
// compatibility with cds 7
const r = cds.compiler.to.hdi.migration(csn, options)
definitions = r.definitions
}
if (options.messages.length > 0) {
// REVISIT: how to deal with compiler info and warning messages
DEBUG?.('cds compilation messages:', options.messages)
}
return definitions
}

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

if (e.code !== 'EACCES') throw e
LOG?.(`Using temporary directory ${TEMP_DIR} for deployment logs`)
LOG?.(`using temporary directory ${TEMP_DIR} for deployment logs`)
return join(TEMP_DIR, 'logs')

@@ -72,2 +72,7 @@ }

const hdi_opts = _parse_env ('HDI_DEPLOY_OPTIONS', options)
try { require.resolve('hdb') } catch (e) {
if (e.code === 'MODULE_NOT_FOUND') hdi_opts.use_hdb = false
else throw e
}
if (hdi_opts.use_hdb !== false) hdi_opts.use_hdb = true
env.HDI_DEPLOY_OPTIONS = JSON.stringify (hdi_opts)

@@ -74,0 +79,0 @@ return env

@@ -5,3 +5,3 @@ const https = require('https')

const LOG = cds.log('mtx'), DEBUG = cds.debug('mtx|sm')
const { uuid } = cds.utils
const { uuid, fs, path } = cds.utils
const { cacheBindings = true } = cds.env.requires.multitenancy ?? {}

@@ -13,2 +13,5 @@ const { sm_url, url, clientid, clientsecret, certurl, certificate, key } = cds.env.requires.db.credentials

// In-memory storage -> later also distribute w/ Redis
const instanceLocations = new Map, bindingLocations = new Map
/* API */

@@ -20,36 +23,70 @@

const { binding_parameters, provisioning_parameters } = parameters ?? {}
let _instance, service_instance_id
let service_instance_id
if (instanceLocations.has(tenant)) {
const storedLocation = instanceLocations.get(tenant)
LOG.info('polling ongoing instance creation for', { tenant })
const polledInstance = await _poll(storedLocation)
service_instance_id = polledInstance.resource_id
instanceLocations.delete(tenant)
} else {
try {
_instance = await fetchApi('service_instances?async=true', {
const _instance = await fetchApi('service_instances?async=true', {
method: 'POST',
data: {
name, service_plan_id, parameters: provisioning_parameters,
labels: { tenant_id: [tenant] },
}
})
instanceLocations.set(tenant, _instance.headers.location)
service_instance_id = (await _poll(_instance.headers.location)).resource_id
instanceLocations.delete(tenant)
} catch (e) {
instanceLocations.delete(tenant)
const status = e.status ?? 500
if (status === 409 || e.error === 'Conflict') {
const instance = await _instance4(tenant)
if (!instance.ready || !instance.usable) {
const { type, state, errors } = instance?.last_operation ?? {}
LOG.info(`detected faulty instance for tenant '${tenant}' in state '${state}' for operation type '${type}'`)
if (type === 'create' && state === 'failed') {
LOG.info(`removing and recreating faulty instance for tenant '${tenant}'`,
DEBUG ? `with error: ${e.error}: ${e.description}. Last operation: ${errors?.error} ${errors?.description}` : ''
)
await remove(tenant)
return create(tenant, parameters)
} else {
e.message ??= ''
e.message += `${e.error}: ${e.description}. Last operation: ${errors?.error} ${errors?.description}`
throw e
}
}
service_instance_id = instance.id
} else {
cds.error(_errorMessage(e, 'creating', tenant), { status })
}
}
}
if (bindingLocations.has(tenant)) {
const storedLocation = bindingLocations.get(tenant)
LOG.info(`ongoing binding creation for tenant ${tenant}, polling existing request`)
try {
await _poll(storedLocation)
} finally {
bindingLocations.delete(tenant)
}
} else {
const _binding = await fetchApi('service_bindings?async=true', {
method: 'POST',
data: {
name, service_plan_id, parameters: provisioning_parameters,
labels: { tenant_id: [tenant] },
name: tenant + `-${uuid()}`, service_instance_id, binding_parameters,
labels: { tenant_id: [tenant], service_plan_id: [service_plan_id], managing_client_lib: ['instance-manager-client-lib'] }
}
})
service_instance_id = (await _poll(_instance.headers.location)).resource_id
} catch (e) {
const status = e.status ?? 500
if (status === 409 || e.error === 'Conflict') {
const instance = await _instance4(tenant)
if (!instance.ready || !instance.usable) {
const faultyInstance = await fetchApi(`service_instances/${instance.id}`)
const errors = faultyInstance?.data?.last_operation?.errors
e.message ??= ''
e.message += `${e.error}: ${e.description}. Last operation: ${errors?.error} ${errors?.description}`
throw e
}
service_instance_id = instance.id
} else {
cds.error(_errorMessage(e, 'creating', tenant), { status })
}
bindingLocations.set(tenant, _binding.headers.location)
await _poll(_binding.headers.location)
bindingLocations.delete(tenant)
}
const _binding = await fetchApi('service_bindings?async=true', {
method: 'POST',
data: {
name: tenant + `-${uuid()}`, service_instance_id, binding_parameters,
labels: { tenant_id: [tenant], service_plan_id: [service_plan_id], managing_client_lib: ['instance-manager-client-lib'] }
}
})
await _poll(_binding.headers.location)
const binding = { ...await get(tenant), tags: ['hana'] }

@@ -108,3 +145,3 @@ return cacheBindings ? _bindings4.cached[tenant] = binding : binding

const fieldQuery = `name eq '${await _instanceName4(tenant)}'`
const instances = await fetchApi('service_instances?async=true', {
const instances = await fetchApi('service_instances?async=true&attach_last_operations=true', {
params: { fieldQuery }

@@ -186,3 +223,3 @@ })

if (state === 'failed') return reject(errors[0] ?? errors)
if (attempts > maxAttempts) return reject(new Error(`Polling ${location} timed out after ${maxTime} seconds`))
if (attempts > maxAttempts) return reject(new Error(`Polling ${location} timed out after ${maxTime} seconds with state ${state}`))
setTimeout(++attempts && _next, 3000, resolve, reject)

@@ -199,2 +236,4 @@ }

const { version } = JSON.parse(fs.readFileSync(path.join(__dirname, '../../../package.json'), 'utf8'))
const fetchApi = async (url, conf = {}) => {

@@ -204,4 +243,6 @@ conf.headers ??= {}

conf.headers['Content-Type'] ??= 'application/json'
conf.headers['Client-ID'] ??= 'cap-mtx-sidecar'
conf.headers['Client-Version'] ??= version
conf.baseURL ??= sm_url + '/v1/'
return fetchResiliently(url, conf)
return fetchResiliently(conf.baseURL + url, conf)
}

@@ -212,5 +253,5 @@

conf.method ??= 'GET'
url = conf.baseURL ? conf.baseURL + url : url
try {
DEBUG?.('>', conf.method.toUpperCase(), url, inspect({
...(conf.headers && { headers: conf.headers }),
...(conf.params && { params: conf.params }),

@@ -241,6 +282,5 @@ ...(conf.data && { data: conf.data })

else return pruneAxiosErrors(error)
} else if (status in { 408: 1, 502: 1, 504: 1 }) {
delay = 300 * 2 ** (attempt - 1)
} else {
delay = 1000 * 3 ** (attempt - 1)
} else { // S-curve instead of exponential backoff to allow for high number of reattempts (∞)
const maxDelay = 30000, midpoint = 6, steepness = 0.4
delay = maxDelay * (1 + Math.tanh(steepness * (attempt - midpoint))) / 2
}

@@ -247,0 +287,0 @@ await new Promise((resolve) => setTimeout(resolve, delay))

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

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