UNPKG

@sap/cds-dk

Version:

Command line client and development toolkit for the SAP Cloud Application Programming Model

192 lines (161 loc) 6.13 kB
const { execFile } = require('child_process'); const cds = require('../../cds'); const Question = require('./question'); const { formatObjectsFixedWidth } = require('./strings'); const { schemaRegex } = require('./urls'); const DEBUG = cds.debug('cli'); function runCf(args, callback) { const env = { ...process.env, LC_ALL: 'en_US.UTF-8' }; // enforce English headings return execFile('cf', args, { env }, callback); } function getErrorMessage(error, stdout) { return stdout.includes('Not logged in') ? 'not logged in' : error && error.message || stdout.replace('\n', ' '); } async function getAppFromSuggestions() { const apps = await getApps(); if (apps.length === 0) { DEBUG?.('Failed to obtain list of apps from CF'); return undefined; } let choice; let choiceNr; do { choice = await Question.askQuestion(`App URL not given. From the following apps running in the CF org and space you are logged into, select one by entering a number or leave empty to quit: ${formatObjectsFixedWidth(apps, ['name', 'url'], 3, { numbering: true })} Your choice: `); console.log(); if (!choice) { DEBUG?.('No app URL selected'); return undefined; } choiceNr = Number(choice); } while (choiceNr > apps.length + 1); return apps[choiceNr - 1]; } async function getApps(nameRegex = /(?<!-db)$/) { DEBUG?.('Getting running apps from CF'); return new Promise(resolve => { runCf(['apps'], function(error, stdout) { if (! /^name\b/m.test(stdout)) { return resolve(errorResult(getErrorMessage(error, stdout))); } const appLines = getAppLines(stdout); const heading = appLines.shift(); if (!heading || appLines.length === 0) { return resolve(errorResult('CF reports no running apps')); } const titles = heading.includes('processes') ? ['processes', 'routes'] : ['instances', 'urls']; const offsets = { name: 0, instances: heading.indexOf(titles[0]), urls: heading.indexOf(titles[1]) }; const result = []; appLines.forEach((line) => { if (Number(getSubstring(line, offsets.instances).replace(/(^.+:|\/.*)/g, '')) === 0) { return; } const name = getSubstring(line, offsets.name); if (!nameRegex.test(name)) { return; } const urls = getSubstring(line, offsets.urls, true); if (!urls) { return; } urls.split(/,\s*/).forEach((url) => { if (!url.includes('://')) { url = 'https://' + url; } result.push({ name, url }); }); }); return resolve(result); }); }); function getSubstring(line, offset, allowSpace = false) { if (allowSpace) { return line.substr(offset).trimRight(); } const match = /^\S+/.exec(line.substr(offset)); if (!match) { return undefined; } return match[0]; } function getAppLines(stdout) { let relevant = false; return stdout.split('\n').filter((line) => { if (/^name\b/.test(line)) { relevant = true; return true; } return line.length && relevant; }); } function errorResult(message) { DEBUG?.(`Failed to get apps: ${message}`); return []; } } async function getSubdomain(appName) { DEBUG?.('Getting subdomain from CF env'); return new Promise(resolve => { runCf(['env', appName], function(error, stdout) { if (! /Provided:$/m.test(stdout)) { if (stdout.includes('not found')) { return resolve(errorResult(`CF reports that app '${appName}' is not found`)); } return resolve(errorResult(getErrorMessage(error, stdout))); } const nl = '\r?\n'; //more robust than os.EOL; const match = new RegExp(`^System-Provided:${nl}(?:\\{${nl}((.|${nl})*?)^}|((.|${nl})*?^}))`, 'm').exec(stdout); if (!match) { return resolve(errorResult(`CF reports no system-provided environment for app '${appName}'`)); } let content = (match[1] || match[3]).replace(/^(\s*)(?!")(\w+)/m, '"$2"'); if (! /^\s*{/.test(content)) { content = '{' + content + '}'; } let value = JSON.parse(content); for (const segment of ['VCAP_SERVICES', 'xsuaa', 0, 'credentials', 'identityzone']) { value = value[segment]; if (!value) { return resolve(errorResult(`CF reports insufficient information for app '${appName}' at segment '${segment}'`)); } } DEBUG?.(`Subdomain determined from CF app environment: ${value}`); return resolve(value); }); }); function errorResult(message) { DEBUG?.(`Failed to get subdomain: ${message}`); return undefined; } } async function getAppName(appUrl) { function equalIgnoringTls(url1, url2) { return url1.replace(schemaRegex, '') === url2.replace(schemaRegex, ''); } DEBUG?.('Getting app name from CF apps'); const apps = await getApps(); const app = apps.find(app => equalIgnoringTls(app.url, appUrl)); if (!app) { DEBUG?.(`Failed to find app with URL ${appUrl}. Running apps: ${apps.map(app => app.name)}`); return; } DEBUG?.(`App name determined from CF: ${app.name}`); return app.name; } module.exports = { getAppFromSuggestions, getAppName, getSubdomain, getApps };