deep-package-manager
Version:
DEEP Package Manager
625 lines (485 loc) • 20 kB
JavaScript
/**
* Created by AlexanderC on 6/4/15.
*/
/*eslint max-statements: [2, 100]*/
;
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;