UNPKG

cloud-red

Version:

Harnessing Serverless for your cloud integration needs

434 lines (402 loc) 11.9 kB
const http = require('http'); const https = require('https'); const util = require('util'); const express = require('express'); const bcrypt = require('bcryptjs'); const path = require('path'); const fs = require('fs-extra'); const { error } = require('./util/error'); const minimist = require('minimist'); const RED = require('../../core/lib/red.js'); const { createFile, genFlowJson } = require('./init'); const { Select } = require('enquirer'); let app = express(); /** * * @param {minimist.ParsedArgs} args * @returns {string} */ function selectUserDir(args) { let userDir = null; let paramUserDir = args._[1]; if (paramUserDir) { userDir = path.resolve(paramUserDir); } else { console.log( 'Warning! userDir has been set to CLOUD_RED_HOME, *not* to your current directory.' ); console.log( 'Run: "cloud-red open ." If you want to open current directory. Type: "cloud-red open ."' ); userDir = path.join( process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH, '.cloud-red' ); } return userDir; } async function selectFlowFile(userDir) { // check whether userDir exist or not if (!fs.existsSync(userDir)) { throw new Error(`${userDir} directory not found`); } const potentialFlowFiles = fs.readdirSync(userDir).filter(file => { return ( path.parse(file).ext === '.json' && file !== 'package.json' && file !== 'package-lock.json' && file !== 'tsconfig.json' && file !== 'cloud-red-config.json' && !file.startsWith('.') ); }); let foundFlowFilename = null; if (potentialFlowFiles.length === 0) { const DEFAULT_FLOW_FILENAME = 'flow.json'; createFile(userDir, DEFAULT_FLOW_FILENAME, genFlowJson()); foundFlowFilename = DEFAULT_FLOW_FILENAME; } else if (potentialFlowFiles.length === 1) { foundFlowFilename = potentialFlowFiles[0]; } else { const prompt = new Select({ name: 'fileSelected', message: 'Pick up the flowFile to open:', choices: potentialFlowFiles }); foundFlowFilename = prompt .run() .then(answer => answer) .catch(err => { error(`FlowFile is required to open the editor`, true); }); } return foundFlowFilename; } /** * * @param {string} userDir - user project directory * @param {minimist.ParsedArgs} args - arguments passed to `open` command * @returns {string} settings file */ function selectSettingsFile(userDir, args) { // Resolving the settings let settingsFile = null; // (a) case of user defined by flag if (args.settings) { if (fs.existsSync(path.join(userDir, args.settings))) { settingsFile = path.join(userDir, args.settings); } else { error(`Settings file "${args.settings}" not found`, true); } } // (b) settings not provided looking at the chain in this order: // 1) $CLOUD_RED_HOME/settings.js // 2) currentDir/settings.js, // 3) $HOME/.cloud-red/settings.js process.env.CLOUD_RED_HOME = process.env.CLOUD_RED_HOME || userDir; if (fs.existsSync(path.join(process.env.CLOUD_RED_HOME, 'settings.js'))) { settingsFile = path.join(process.env.CLOUD_RED_HOME, 'settings.js'); } else { // last resort looking in the homedir // if its there ... safe the location if ( fs.existsSync( path.join( process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH, '.cloud-red', 'settings.js' ) ) ) { settingsFile = path.join( process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH, '.cloud-red', 'settings.js' ); // otherwise write a default settings file } else { const targetSettingsFile = path.join( process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH, '.cloud-red', 'settings.js' ); let defaultSettings = path.join( __dirname, 'templates', 'default-settings.js' ); fs.copySync(defaultSettings, targetSettingsFile); settingsFile = targetSettingsFile; console.log(`Default settings created in ${targetSettingsFile}`); } } return settingsFile; } module.exports = async args => { // predefine the current directory as the userDir let userDir = selectUserDir(args); // Loading the settings file let settingsFile = selectSettingsFile(userDir, args); try { var settings = require(settingsFile); settings.settingsFile = settingsFile; } catch (err) { error( `Loading settings file: ${settingsFile} due to ${err.toString()}`, true ); } // Set the flowFile try { settings.flowFile = await selectFlowFile(userDir); } catch (err) { error(`Directory: "${userDir}" not found `, true); } // Set the UI port settings.uiPort = settings.uiPort || 1880; // Set the UI Host settings.uiHost = settings.uiHost || '0.0.0.0'; // Setting the userDir settings.userDir = userDir; // Setting the nodesDir settings.nodesDir = settings.nodesDir || path.join( process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH, '.cloud-red', './nodes' ); // Setting the httpRoot, httpAdminRoot and httpNodeRoot function formatRoot(root) { if (root[0] != '/') { root = '/' + root; } if (root.slice(-1) != '/') { root = root + '/'; } return root; } if (settings.httpRoot === false) { settings.httpAdminRoot = false; settings.httpNodeRoot = false; } else { settings.httpRoot = settings.httpRoot || '/'; settings.disableEditor = settings.disableEditor || false; } if (settings.httpAdminRoot !== false) { settings.httpAdminRoot = formatRoot( settings.httpAdminRoot || settings.httpRoot || '/' ); settings.httpAdminAuth = settings.httpAdminAuth || settings.httpAuth; } else { settings.disableEditor = true; } if (settings.httpNodeRoot !== false) { settings.httpNodeRoot = formatRoot( settings.httpNodeRoot || settings.httpRoot || '/' ); settings.httpNodeAuth = settings.httpNodeAuth || settings.httpAuth; } // Setting up the express app let server; if (settings.https) { server = https.createServer(settings.https, function(req, res) { app(req, res); }); } else { server = http.createServer(function(req, res) { app(req, res); }); } server.setMaxListeners(0); // Initializing RED ... try { RED.init(server, settings); } catch (err) { if (err.code == 'unsupported_version') { console.log('Unsupported version of node.js:', process.version); console.log('Cloud-RED requires node.js v4 or later'); } else if (err.code == 'not_built') { console.log('Cloud-RED has not been built. See README.md for details'); } else { console.log('Failed to start server:'); if (err.stack) { console.log(err.stack); } else { console.log(err); } } process.exit(1); } function basicAuthMiddleware(user, pass) { var basicAuth = require('basic-auth'); var checkPassword; var localCachedPassword; if (pass.length == '32') { // Assume its a legacy md5 password checkPassword = function(p) { return ( bcrypt .createHash('md5') .update(p, 'utf8') .digest('hex') === pass ); }; } else { checkPassword = function(p) { return bcrypt.compareSync(p, pass); }; } var checkPasswordAndCache = function(p) { // For BasicAuth routes we know the password cannot change without // a restart of Node-RED. This means we can cache the provided crypted // version to save recalculating each time. if (localCachedPassword === p) { return true; } var result = checkPassword(p); if (result) { localCachedPassword = p; } return result; }; return function(req, res, next) { if (req.method === 'OPTIONS') { return next(); } var requestUser = basicAuth(req); if ( !requestUser || requestUser.name !== user || !checkPasswordAndCache(requestUser.pass) ) { res.set('WWW-Authenticate', 'Basic realm="Authorization Required"'); return res.sendStatus(401); } next(); }; } if (settings.httpAdminRoot !== false && settings.httpAdminAuth) { RED.log.warn(RED.log._('server.httpadminauth-deprecated')); app.use( settings.httpAdminRoot, basicAuthMiddleware( settings.httpAdminAuth.user, settings.httpAdminAuth.pass ) ); } if (settings.httpAdminRoot !== false) { app.use(settings.httpAdminRoot, RED.httpAdmin); } if (settings.httpNodeRoot !== false && settings.httpNodeAuth) { app.use( settings.httpNodeRoot, basicAuthMiddleware( settings.httpNodeAuth.user, settings.httpNodeAuth.pass ) ); } if (settings.httpNodeRoot !== false) { app.use(settings.httpNodeRoot, RED.httpNode); } if (settings.httpStatic) { settings.httpStaticAuth = settings.httpStaticAuth || settings.httpAuth; if (settings.httpStaticAuth) { app.use( '/', basicAuthMiddleware( settings.httpStaticAuth.user, settings.httpStaticAuth.pass ) ); } app.use('/', express.static(settings.httpStatic)); } function getListenPath() { var port = settings.serverPort; if (port === undefined) { port = settings.uiPort; } var listenPath = 'http' + (settings.https ? 's' : '') + '://' + (settings.uiHost == '::' ? 'localhost' : settings.uiHost == '0.0.0.0' ? '127.0.0.1' : settings.uiHost) + ':' + port; if (settings.httpAdminRoot !== false) { listenPath += settings.httpAdminRoot; } else if (settings.httpStatic) { listenPath += '/'; } return listenPath; } // Starting RED ... RED.start() .then(function() { if ( settings.httpAdminRoot !== false || settings.httpNodeRoot !== false || settings.httpStatic ) { server.on('error', function(err) { if (err.errno === 'EADDRINUSE') { RED.log.error( RED.log._('server.unable-to-listen', { listenpath: getListenPath() }) ); RED.log.error(RED.log._('server.port-in-use')); } else { RED.log.error(RED.log._('server.uncaught-exception')); if (err.stack) { RED.log.error(err.stack); } else { RED.log.error(err); } } process.exit(1); }); server.listen(settings.uiPort, settings.uiHost, function() { if (settings.httpAdminRoot === false) { RED.log.info(RED.log._('server.admin-ui-disabled')); } settings.serverPort = server.address().port; process.title = 'Cloud-red'; RED.log.info( RED.log._('server.now-running', { listenpath: getListenPath() }) ); }); } else { RED.log.info(RED.log._('server.headless-mode')); } }) .catch(function(err) { RED.log.error(RED.log._('server.failed-to-start')); if (err.stack) { RED.log.error(err.stack); } else { RED.log.error(err); } }); process.on('uncaughtException', function(err) { util.log('[red] Uncaught Exception:'); if (err.stack) { util.log(err.stack); } else { util.log(err.toString()); } process.exit(1); }); process.on('SIGINT', function() { RED.stop().then(function() { process.exit(); }); }); };