UNPKG

@sap/cds-mtxs

Version:

SAP Cloud Application Programming Model - Multitenancy library

153 lines (125 loc) 6.16 kB
const cds = require('@sap/cds') const { fs, path } = cds.utils const { token, fetchResiliently } = require('../../../lib/utils') const { t0 = 't0' } = cds.requires.multitenancy ?? {} /* API */ class AbstractContainerManagerClient { constructor({ clientid, clientsecret, url }) { this.clientid = clientid this.clientsecret = clientsecret this.url = url this.failures = new Map() this.version = JSON.parse(fs.readFileSync(path.join(__dirname, '../../../package.json'), 'utf8')).version this.cachedToken = null this.sm_url = null } create = () => { throw new Error('Method not implemented.') } get = async () => { throw new Error('Method not implemented.') } getAll = async () => { throw new Error('Method not implemented.') } acquire = async (tenant, parameters) => { try { return await this.get(tenant, { disableCache: true }) } catch (e) { if (e.status === 404) return this.create(tenant, parameters) throw e } } deploy = (container, tenant, out, options, deployEnv) => { return require('./hdi').deploy(container, tenant, out, options, deployEnv) } remove = async () => { throw new Error('Method not implemented.') } _token = async () => { if (this.cachedToken && this.cachedToken.expiry >= Date.now() + 30_000) { return `Bearer ${this.cachedToken.access_token}` } const raw = await token(this) const { access_token, expires_in } = JSON.parse(raw) this.cachedToken = { access_token, expiry: Date.now() + expires_in * 1000 } return `Bearer ${access_token}` } fetchApi = async (url, conf, { failures = 0, retryUntil } = {}) => { const { version } = JSON.parse(fs.readFileSync(path.join(__dirname, '../../../package.json'), 'utf8')) conf.headers ??= {} conf.headers.Authorization ??= await this._token() conf.headers['Accept'] ??= 'application/json' conf.headers['Content-Type'] ??= 'application/json' conf.headers['Client-ID'] ??= 'cap-mtx-sidecar' conf.headers['Client-Name'] ??= 'cap-mtx-sidecar' conf.headers['Client-Version'] ??= version conf.headers['X-CorrelationID'] ??= cds.context?.id conf.headers['X-Correlation-ID'] ??= cds.context?.id conf.baseURL ??= url.startsWith('http') ? '' : this.sm_url + '/v1/' const { retries, maxRetryAfter } = cds.requires?.multitenancy?.serviceManager ?? cds.requires?.multitenancy?.containerManager ?? {} return fetchResiliently(conf.baseURL + url, conf, { retries: retries ?? 10, maxRetryAfter: maxRetryAfter ?? 5000, retryUntil, failures }) } _poll = async (location, conf = {}) => { let attempts = 0, maxAttempts = 60, pollingTimeout = 3000, maxTime = pollingTimeout * maxAttempts / 1000 const url = location.includes('/v1/') ? location.slice('/v1/'.length) : location const _next = async (resolve, reject) => { let response try { response = await this.fetchApi(url, conf) } catch (err) { return reject(err) } if (this._succeeded(response)) return resolve(response) if (this._failed(response)) return reject(this._pollError(response)) if (attempts > maxAttempts) return reject(new Error(`Polling ${location} timed out after ${maxTime} seconds with state ${response.data?.state ?? 'unknown'}`)) setTimeout(++attempts && _next, pollingTimeout, resolve, reject) } return new Promise(_next) } _succeeded = (response) => response.data?.state === 'succeeded' _failed = (response) => response.data?.state === 'failed' _pollError = (response) => response.data.errors[0] ?? response.data.errors static _errorMessage = (e, action, tenant) => { const msg = `Error ${action} tenant ${tenant}: ${e.response?.data?.error ?? e.code ?? e.message ?? 'unknown error'}` const cause = e.description || e.cause ? require('os').EOL + `Root Cause: ${e.description ?? e.cause}` : '' return msg + cause } _createParams = () => { throw new Error('Method not implemented.') } // collect all hdi parameters _hdiParams = (tenant, params = {}, metadata) => { const createParamsFromEnv = cds.env.requires['cds.xt.DeploymentService']?.hdi?.create ?? {} const createParamsFromTenantOptions = cds.env.requires['cds.xt.DeploymentService']?.for?.[tenant]?.hdi?.create ?? {} const createParams = { ...createParamsFromEnv, ...createParamsFromTenantOptions, ...params?.hdi?.create } // @sap/instance-manager API compat this._checkLegacyConfig(createParams) // flatter @sap/cds-mtxs config const bindParamsFromEnv = cds.env.requires['cds.xt.DeploymentService']?.hdi?.bind ?? {} const bindParamsFromTenantOptions = cds.env.requires['cds.xt.DeploymentService']?.for?.[tenant]?.hdi?.bind ?? {} const bindParams = { ...bindParamsFromEnv, ...bindParamsFromTenantOptions, ...params?.hdi?.bind } const final = {} const provisioningParams = { ...this._encryptionParams(metadata), ...createParams } if (Object.keys(provisioningParams).length > 0) final.create = provisioningParams if (Object.keys(bindParams).length > 0) final.bind = bindParams if (tenant === t0) delete final.create?.dataEncryption return Object.keys(final).length > 0 ? final : null } _encryptionParams = (data) => { return (data?.globalAccountGUID ?? data?.subscriber?.globalAccountId) ? { subscriptionContext: { // crmId: '', globalAccountID: data.globalAccountGUID ?? data.subscriber.globalAccountId, subAccountID: data.subscribedSubaccountId ?? data.subscriber.subaccountId, applicationName: data.subscriptionAppName ?? data.rootApplication?.appName } } : {} } _checkLegacyConfig = (createParams) => { const compat = 'provisioning_parameters' in createParams || 'binding_parameters' in createParams if (compat) { const capire = 'https://cap.cloud.sap/docs/guides/multitenancy/mtxs#deployment-config' cds.error(`Warning: Legacy configuration for deployment parameters is used. Please update to the new, simplified deployment options. See ${capire} for details.`) } } } module.exports = AbstractContainerManagerClient