UNPKG

deep-package-manager

Version:
625 lines (485 loc) 20 kB
/** * Created by AlexanderC on 6/4/15. */ /*eslint max-statements: [2, 100]*/ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.Frontend = undefined; var _fs = require('fs'); var _fs2 = _interopRequireDefault(_fs); var _fsExtra = require('fs-extra'); var _fsExtra2 = _interopRequireDefault(_fsExtra); var _path = require('path'); var _path2 = _interopRequireDefault(_path); var _Exec = require('../Helpers/Exec'); var _FileWalker = require('../Helpers/FileWalker'); var _InvalidArgumentException = require('../Exception/InvalidArgumentException'); var _jsonfile = require('jsonfile'); var _jsonfile2 = _interopRequireDefault(_jsonfile); var _MissingRootIndexException = require('./Exception/MissingRootIndexException'); var _FailedUploadingFileToS3Exception = require('./Exception/FailedUploadingFileToS3Exception'); var _AwsRequestSyncStack = require('../Helpers/AwsRequestSyncStack'); var _Action = require('../Microservice/Metadata/Action'); var _deepCore = require('deep-core'); var _deepCore2 = _interopRequireDefault(_deepCore); var _tmp = require('tmp'); var _tmp2 = _interopRequireDefault(_tmp); var _os = require('os'); var _os2 = _interopRequireDefault(_os); var _APIGatewayService = require('../Provisioning/Service/APIGatewayService'); var _SQSService = require('../Provisioning/Service/SQSService'); var _CognitoIdentityProviderService = require('../Provisioning/Service/CognitoIdentityProviderService'); var _DeployIdInjector = require('../Assets/DeployIdInjector'); var _Optimizer = require('../Assets/Optimizer'); var _Injector = require('../Tags/Injector'); var _EnvHashDriver = require('../Tags/Driver/EnvHashDriver'); var _ActionFlags = require('../Microservice/Metadata/Helpers/ActionFlags'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** * Frontend */ class Frontend { /** * @param {Property} property * @param {Object} microservicesConfig * @param {String} basePath * @param {String} deployId */ constructor(property, microservicesConfig, basePath, deployId) { this._property = property; this._microservicesConfig = microservicesConfig; this._basePath = _path2.default.normalize(basePath); this._deployId = deployId; } /** * @returns {String} */ get deployId() { return this._deployId; } /** * @param {Object} propertyConfig * @param {Boolean} localRuntime * @param {Boolean} backendTarget * @return {Object} */ static createConfig(propertyConfig, localRuntime = false, backendTarget = false) { let config = { env: propertyConfig.env, deployId: propertyConfig.deployId, awsRegion: propertyConfig.awsRegion, models: propertyConfig.models, identityPoolId: localRuntime ? 'us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xx0123456789' : '', identityProviders: '', microservices: {}, globals: propertyConfig.globals, searchDomains: propertyConfig.searchDomains, validationSchemas: propertyConfig.validationSchemas.map(validationSchema => { return validationSchema.name; }) }; if (backendTarget) { config.modelsSettings = propertyConfig.modelsSettings; } let apiGatewayBaseUrl = ''; if (propertyConfig.provisioning) { let cognitoConfig = propertyConfig.provisioning[_deepCore2.default.AWS.Service.COGNITO_IDENTITY]; let cognitoIdpConfig = propertyConfig.provisioning[_deepCore2.default.AWS.Service.COGNITO_IDENTITY_PROVIDER]; let iamConfig = propertyConfig.provisioning[_deepCore2.default.AWS.Service.IDENTITY_AND_ACCESS_MANAGEMENT]; config.identityPoolId = cognitoConfig.identityPool.IdentityPoolId; config.identityProviders = cognitoConfig.identityPool.SupportedLoginProviders || {}; if (cognitoIdpConfig.userPool && cognitoIdpConfig.userPoolClients) { config.identityProviders[cognitoIdpConfig.providerName] = { UserPoolId: cognitoIdpConfig.userPool.Id, // @note - fallback compatibility ^_^ ClientId: cognitoIdpConfig.userPoolClients[_CognitoIdentityProviderService.CognitoIdentityProviderService.SYSTEM_CLIENT_APP].ClientId, Clients: {} }; for (let clientName in cognitoIdpConfig.userPoolClients) { if (!cognitoIdpConfig.userPoolClients.hasOwnProperty(clientName)) { continue; } let client = cognitoIdpConfig.userPoolClients[clientName]; config.identityProviders[cognitoIdpConfig.providerName].Clients[clientName] = client.ClientId; } } if (backendTarget) { let cloudFrontConfig = propertyConfig.provisioning[_deepCore2.default.AWS.Service.CLOUD_FRONT]; let apiGatewayConfig = propertyConfig.provisioning[_deepCore2.default.AWS.Service.API_GATEWAY]; config.website = { cloudfront: cloudFrontConfig.domain, apigateway: apiGatewayConfig.api.baseUrl }; } // add Auth0 OIDC provider if (iamConfig.identityProvider && iamConfig.identityProvider.domain) { config.identityProviders[iamConfig.identityProvider.domain] = iamConfig.identityProvider.clientID; } apiGatewayBaseUrl = propertyConfig.provisioning[_deepCore2.default.AWS.Service.API_GATEWAY].api.baseUrl; let sqsQueues = propertyConfig.provisioning[_deepCore2.default.AWS.Service.SIMPLE_QUEUE_SERVICE].queues; config.rumQueue = sqsQueues[_SQSService.SQSService.RUM_QUEUE] || {}; let esDomains = propertyConfig.provisioning[_deepCore2.default.AWS.Service.ELASTIC_SEARCH].domains; let domains = {}; for (let domainKey in esDomains) { if (!esDomains.hasOwnProperty(domainKey)) { continue; } let domain = esDomains[domainKey]; domains[domainKey] = { type: _deepCore2.default.AWS.Service.ELASTIC_SEARCH, name: domain.DomainName, version: domain.ElasticsearchVersion, url: '' // @todo - find a way to retrieve provisioned domain url (it's available with a delay of ~15min) }; } // @note - here will be added CloudSearch domains also config.searchDomains = domains; } for (let microserviceIdentifier in propertyConfig.microservices) { if (!propertyConfig.microservices.hasOwnProperty(microserviceIdentifier)) { continue; } let microservice = propertyConfig.microservices[microserviceIdentifier]; let microserviceConfig = { isRoot: microservice.isRoot, parameters: microservice.parameters.frontend, resources: {} }; for (let resourceName in microservice.resources) { if (!microservice.resources.hasOwnProperty(resourceName)) { continue; } microserviceConfig.resources[resourceName] = {}; let resourceActions = microservice.resources[resourceName]; for (let actionName in resourceActions) { if (!resourceActions.hasOwnProperty(actionName)) { continue; } let action = resourceActions[actionName]; if (!backendTarget && action.scope === _ActionFlags.ActionFlags.PRIVATE) { continue; } let originalSource = action.type === _Action.Action.LAMBDA ? microservice.lambdas[action.identifier].arn : action.source; let apiEndpoint = null; if (_ActionFlags.ActionFlags.isApi(action.scope)) { apiEndpoint = apiGatewayBaseUrl + _APIGatewayService.APIGatewayService.pathify(microserviceIdentifier, resourceName, actionName); } microserviceConfig.resources[resourceName][action.name] = { type: action.type, methods: action.methods, forceUserIdentity: action.forceUserIdentity, validationSchema: action.validationSchema, apiCache: { enabled: action.cacheEnabled, ttl: action.cacheTtl }, region: propertyConfig.awsRegion, // @todo: set it from lambda provision scope: _ActionFlags.ActionFlags.stringify(action.scope), source: { api: apiEndpoint, original: backendTarget || _ActionFlags.ActionFlags.isDirect(action.scope) ? originalSource : null }, api: action.api }; if (localRuntime) { microserviceConfig.resources[resourceName][action.name].source._localPath = microservice.lambdas[action.identifier].localPath; } } } config.microservices[microserviceIdentifier] = microserviceConfig; } return config; } /** * @returns {String} */ get basePath() { return this._basePath; } /** * @param {Object} AWS * @param {String} bucketName * @returns {WaitFor} */ deploy(AWS, bucketName) { let s3 = this._property.provisioning.s3; let bucketRegion = s3.config.region; let syncStack = new _AwsRequestSyncStack.AwsRequestSyncStack(); //let walker = new FileWalker(FileWalker.RECURSIVE); //let sliceOffset = this.path.length + 1; // used to remove base path from file name // @todo: remove this hook when fixing s3 sync functionality let credentialsFile = _tmp2.default.tmpNameSync(); let credentials = `[profile deep]${_os2.default.EOL}`; credentials += `aws_access_key_id=${AWS.config.credentials.accessKeyId}${_os2.default.EOL}`; credentials += `aws_secret_access_key=${AWS.config.credentials.secretAccessKey}${_os2.default.EOL}`; credentials += `region=${AWS.config.region}${_os2.default.EOL}`; console.debug(`Dumping AWS tmp credentials into ${credentialsFile}`); _fs2.default.writeFileSync(credentialsFile, credentials); console.debug(`Syncing ${this.path} with ${bucketName} (non HTML, TTL=86400)`); let syncResultNoHtml = this._getSyncCommandNoHtml(credentialsFile, bucketName, bucketRegion).runSync(); if (syncResultNoHtml.failed) { throw new _FailedUploadingFileToS3Exception.FailedUploadingFileToS3Exception('*', bucketName, syncResultNoHtml.error); } console.debug(`Syncing ${this.path} with ${bucketName} (HTML only, TTL=600)`); let syncResultHtml = this._getSyncCommandHtmlOnly(credentialsFile, bucketName, bucketRegion).runSync(); if (syncResultHtml.failed) { throw new _FailedUploadingFileToS3Exception.FailedUploadingFileToS3Exception('*', bucketName, syncResultHtml.error); } console.debug(`Syncing ${this.path} with ${bucketName} (Video only, TTL=86400)`); let syncResultVideos = this._getSyncCommandVideoFiles(credentialsFile, bucketName, bucketRegion).runSync(); if (syncResultVideos.failed) { throw new _FailedUploadingFileToS3Exception.FailedUploadingFileToS3Exception('*', bucketName, syncResultVideos.error); } _fs2.default.unlinkSync(credentialsFile); // @todo: improve this by using directory upload //let files = walker.walk(this.path, FileWalker.skipDotsFilter()); //for (let i in files) { // if (!files.hasOwnProperty(i)) { // continue; // } // // let file = files[i]; // // let params = { // Bucket: bucketName, // Key: file.slice(sliceOffset), // Body: FileSystem.readFileSync(file), // ContentType: Mime.lookup(file) // }; // // syncStack.push(s3.putObject(params), (error, data) => { // if (error) { // throw new FailedUploadingFileToS3Exception(file, bucketName, error); // } // }); //} return syncStack.join(); } /** * @param {String} credentialsFile * @param {String} bucketName * @param {String} bucketRegion * @returns {Exec} * @private */ _getSyncCommandNoHtml(credentialsFile, bucketName, bucketRegion) { let excludesStr = '--exclude="*.html" '; // exclude *.html by default Frontend._videoAssetsExtensions.forEach(extension => { excludesStr += `--exclude="*.${extension}" `; }); return new _Exec.Exec(`export AWS_CONFIG_FILE=${credentialsFile};`, 'aws s3 sync', '--profile=deep', `--region=${bucketRegion}`, Frontend._contentEncodingExecOption, '--cache-control="max-age=604800"', '--include="*"', excludesStr, `'${this.path}'`, `'s3://${bucketName}'`); } /** * @param {String} credentialsFile * @param {String} bucketName * @param {String} bucketRegion * @returns {Exec} * @private */ _getSyncCommandHtmlOnly(credentialsFile, bucketName, bucketRegion) { return new _Exec.Exec(`export AWS_CONFIG_FILE=${credentialsFile};`, 'aws s3 sync', '--profile=deep', `--region=${bucketRegion}`, Frontend._contentEncodingExecOption, '--cache-control="max-age=604800"', '--exclude="*"', '--include="*.html"', `'${this.path}'`, `'s3://${bucketName}'`); } /** * @param {String} credentialsFile * @param {String} bucketName * @param {String} bucketRegion * @returns {Exec} * @private */ _getSyncCommandVideoFiles(credentialsFile, bucketName, bucketRegion) { let includesStr = ''; Frontend._videoAssetsExtensions.forEach(extension => { includesStr += `--include="*.${extension}" `; }); return new _Exec.Exec(`export AWS_CONFIG_FILE=${credentialsFile};`, 'aws s3 sync', '--profile=deep', `--region=${bucketRegion}`, '--cache-control="max-age=604800"', '--exclude="*"', includesStr, `'${this.path}'`, `'s3://${bucketName}'`); } /** * @returns {String[]} * @private */ static get _videoAssetsExtensions() { return ['avi', 'fvl', 'mp4', 'wmv', 'mov']; } /** * @returns {String} * @private */ static get _contentEncodingExecOption() { return Frontend._skipAssetsOptimizations ? null : '--content-encoding=gzip'; } /** * @param {Object} propertyConfig * @param {String} dumpPath * @param {Boolean} useSymlink * @returns {Frontend} */ static dumpValidationSchemas(propertyConfig, dumpPath, useSymlink = false) { let validationSchemas = propertyConfig.validationSchemas; let schemasPath = _path2.default.join(dumpPath, _deepCore2.default.AWS.Lambda.Runtime.VALIDATION_SCHEMAS_DIR); if (_fs2.default.existsSync(schemasPath)) { _fsExtra2.default.removeSync(schemasPath); } validationSchemas.forEach(schema => { let schemaPath = schema.schemaPath; let destinationSchemaPath = _path2.default.join(schemasPath, `${schema.name}.js`); if (useSymlink) { _fsExtra2.default.ensureSymlinkSync(schemaPath, destinationSchemaPath); } else { _fsExtra2.default.copySync(schemaPath, destinationSchemaPath); } }); return this; } /** * @param {Object} propertyConfig * @param {Function} callback * @return {*} */ build(propertyConfig, callback = () => {}) { if (!(propertyConfig instanceof Object)) { throw new _InvalidArgumentException.InvalidArgumentException(propertyConfig, 'Object'); } _fs2.default.mkdirSync(this.path); let workingMicroserviceConfig = null; for (let identifier in this._microservicesConfig) { if (!this._microservicesConfig.hasOwnProperty(identifier)) { continue; } let config = this._microservicesConfig[identifier]; let modulePath = this.modulePath(identifier); let frontendPath = config.autoload.frontend; _fs2.default.mkdirSync(modulePath); let walker = new _FileWalker.FileWalker(_FileWalker.FileWalker.RECURSIVE, '.deepignore'); // @todo: implement this in a smarter way if (config.isRoot) { workingMicroserviceConfig = config; try { let indexFile = _path2.default.join(frontendPath, 'index.html'); let indexStats = _fs2.default.lstatSync(indexFile); if (!indexStats.isFile()) { throw new _MissingRootIndexException.MissingRootIndexException(identifier); } } catch (e) { throw new _MissingRootIndexException.MissingRootIndexException(identifier); } // The root micro-service frontend files are moved into property document ro walker.copy(frontendPath, this.path); _fs2.default.rmdirSync(modulePath); } else { // All non root micro-service frontend files are namespaced by microservice identifier walker.copy(frontendPath, modulePath); } } let mainIndexFile = _path2.default.join(this.path, 'index.html'); // override default index.html if exists in non-root microservices for (let identifier in this._microservicesConfig) { if (!this._microservicesConfig.hasOwnProperty(identifier)) { continue; } let config = this._microservicesConfig[identifier]; let frontendPath = config.autoload.frontend; if (config.isRoot) { continue; } try { let indexFile = _path2.default.join(frontendPath, 'index.html'); if (_fs2.default.lstatSync(indexFile).isFile()) { _fsExtra2.default.copySync(indexFile, mainIndexFile); workingMicroserviceConfig = config; } } catch (e) { console.debug('Unable to copy file: ', e); } } Frontend.dumpValidationSchemas(this._property.config, this.path); _jsonfile2.default.writeFileSync(this.configPath, propertyConfig); _Injector.Injector.fileInjectAll(_path2.default.join(this.path, 'index.html'), propertyConfig, // @todo: separate GTM functionality? propertyConfig.globals.gtmContainerId, // it may be empty/undefined this._microservicesConfig, propertyConfig.globals.pageLoader, propertyConfig.globals.version, propertyConfig.globals.favicon, workingMicroserviceConfig); let deepServiceWorkerPath = _path2.default.join(this.path, 'deep-sw.js'); if (_fs2.default.existsSync(deepServiceWorkerPath)) { console.debug('Injecting env-hash tag into deep service worker'); _Injector.Injector.fileInject(deepServiceWorkerPath, new _EnvHashDriver.EnvHashDriver(propertyConfig.env, this._property.configObj.baseHash)); } if (Frontend._skipInjectDeployNumber) { return this._optimizeAssets(callback); } new _DeployIdInjector.DeployIdInjector(this.path, this._deployId).prepare(error => { let optCb = callback; if (error) { optCb = optError => { if (optError) { callback(new Error(`- OptimizerError: ${optError}${_os2.default.EOL}- DeployIdInjectorError: ${error}`)); return; } callback(error); }; } this._optimizeAssets(optCb); }); } /** * @param {Function} callback * @private */ _optimizeAssets(callback) { if (Frontend._skipAssetsOptimizations) { callback(null); return; } new _Optimizer.Optimizer(this.path).optimize(Frontend._videoAssetsExtensions, callback); } /** * @todo: get rid of this hook * * @returns {Boolean} * @private */ static get _skipAssetsOptimizations() { return process.env.hasOwnProperty('DEEP_SKIP_ASSETS_OPTIMIZATION'); } /** * @todo: get rid of this hook * * @returns {Boolean} * @private */ static get _skipInjectDeployNumber() { return process.env.hasOwnProperty('DEEP_SKIP_DEPLOY_ID_INJECT'); } /** * @param {String} moduleIdentifier * @returns {String} */ modulePath(moduleIdentifier) { return _path2.default.join(this.path, moduleIdentifier); } /** * @returns {String} */ get configPath() { return _path2.default.join(this.path, Frontend.CONFIG_FILE); } /** * @returns {String} */ get path() { return _path2.default.join(this._basePath, Frontend.PUBLIC_FOLDER); } /** * @returns {String} */ static get PUBLIC_FOLDER() { return '_public'; } /** * @returns {String} */ static get CONFIG_FILE() { return '_config.json'; } } exports.Frontend = Frontend;