@sap/cds-dk
Version:
Command line client and development toolkit for the SAP Cloud Application Programming Model
192 lines (161 loc) • 6.13 kB
JavaScript
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
};