bixlabs-generator-jhipster
Version:
Spring Boot + Angular/React/Vue in one handy generator
327 lines (307 loc) • 8.54 kB
JavaScript
/**
* Copyright 2013-2021 the original author or authors from the JHipster project.
*
* This file is part of the JHipster project, see https://www.jhipster.tech/
* for more information.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const _ = require('lodash');
const fs = require('fs');
const chalk = require('chalk');
const AWS = require('aws-sdk');
const ProgressBar = require('progress');
const ora = require('ora');
const utils = require('../utils');
const AwsSSM = require('./lib/ssm');
const AwsECR = require('./lib/ecr');
const AwsCF = require('./lib/cloudFormation');
const DEFAULT_REGION = 'us-east-1';
const S3_MIN_PART_SIZE = 5242880;
let credentials;
let ec2;
// let ecr;
let s3;
let sts;
// Instances from ./lib. Composed with aws-sdk
let SSM;
let ECR;
let CF;
module.exports = {
DEFAULT_REGION,
SSM: () => SSM,
ECR: () => ECR,
CF: () => CF,
createS3Bucket,
getDockerLogin,
listRegions,
listSubnets,
listVpcs,
saveCredentialsInAWS,
initAwsStuff,
sanitizeBucketName,
uploadTemplate,
};
/**
* Init AWS stuff like ECR and whatnot.
*
* @param ecrConfig The config used to instanciate ECR
*/
function initAwsStuff(region = DEFAULT_REGION) {
ec2 = new AWS.EC2({ region });
// ecr = new AWS.ECR({ region });
s3 = new AWS.S3();
sts = new AWS.STS();
SSM = new AwsSSM(region);
ECR = new AwsECR(region);
CF = new AwsCF(region);
}
/**
* Wraps the promise in a CLI spinner
* @param promise
*/
function spinner(promise, text = 'loading', spinnerIcon = 'monkey') {
const spinner = ora({ spinner: spinnerIcon, text }).start();
return new Promise((resolve, reject) => {
promise
.then(resolved => {
spinner.stop();
resolve(resolved);
})
.catch(err => {
spinner.stop();
reject(err);
});
});
}
/**
* listRegions() returns a Promise, which resolves to an array of AWS region objects,
* with "Endpoint" and "RegionName" properties
*
* @param region to use. Defaults to us-east-1
* @returns {Promise<EC2.Region[]>}
*/
function listRegions() {
return spinner(
ec2
.describeRegions({})
.promise()
.then(data => data.Regions)
);
}
/**
* listVpcs() returns a Promise, which resolves to an array of AWS VPC objects
* @returns {Promise<EC2.Vpc[]>}
*/
function listVpcs() {
return spinner(
ec2
.describeVpcs({})
.promise()
.then(data => data.Vpcs)
);
}
/**
* listSubnets() returns a Promise, which resolves to an array of
* Subnets available within the supplied VPC ID
* @param vpcId of the VPC with the subnets
* @returns {Promise<EC2.Subnet[]>}
*/
function listSubnets(vpcId) {
const params = {
Filters: [
{
Name: 'vpc-id',
Values: [vpcId],
},
{
Name: 'state',
Values: ['available'],
},
],
};
return spinner(
ec2
.describeSubnets(params)
.promise()
.then(data => data.Subnets)
);
}
/**
* Get the credentials from the ~/.aws/credentials file using the AWS_PROFILE env var to get the profile.
*
* @param profile The AWS profile to get the credentials from. Default to 'default'
* @returns {Promise} Will resolve with no parameters if it succeeds, rejects with the error if it fails (no credentials found for given profile.
*/
function saveCredentialsInAWS(profile = 'default') {
credentials = new AWS.SharedIniFileCredentials({ profile });
return new Promise((resolve, reject) =>
credentials.refresh(err => {
if (err) {
reject(err);
}
AWS.config.credentials = credentials;
resolve();
})
);
}
/**
* Retrieve decoded information to authenticate to Docker with AWS credentials.
* @returns {Promise} Returns a promise that resolves when the informations are retrieved.
*/
function getDockerLogin() {
return spinner(
new Promise((resolve, reject) =>
_getAuthorizationToken().then(authToken =>
sts
.getCallerIdentity({})
.promise()
.then(data => {
const decoded = utils.decodeBase64(authToken.authorizationToken);
const splitResult = decoded.split(':');
resolve({
username: splitResult[0],
password: splitResult[1],
accountId: data.Account,
});
})
.catch(() => reject(new Error("Couldn't retrieve the user informations")))
)
)
);
}
/**
* Fetch Authentication token from AWS to authenticate with Docker
* @returns {Promise} Returns a promise that resolves when the informations are retrieved.
* @private
*/
function _getAuthorizationToken() {
return spinner(
new Promise((resolve, reject) =>
ECR.sdk
.getAuthorizationToken({})
.promise()
.then(data => {
if (!_.has(data, 'authorizationData.0')) {
reject(new Error('No authorization data found.'));
return;
}
resolve(data.authorizationData[0]);
})
)
);
}
/**
* Create a S3 Bucket in said region with said bucketBaseName.
* the bucketBaseName will be used to create a
* @param bucketName the name of the bucket to create.
* @param region the region to create the bucket in.
* @returns {Promise}
*/
function createS3Bucket(bucketName, region = DEFAULT_REGION) {
const createBuckerParams = {
Bucket: bucketName,
};
return spinner(
new Promise((resolve, reject) =>
s3
.headBucket({
Bucket: bucketName,
})
.promise()
.catch(error => {
if (error.code !== 'NotFound') {
reject(
new Error(
`The S3 Bucket ${chalk.bold(bucketName)} in region ${chalk.bold(
region
)} already exists and you don't have access to it. Error code: ${chalk.bold(error.code)}`
)
);
}
})
.then(() =>
s3
.createBucket(createBuckerParams)
.promise()
.then(resolve)
.catch(error =>
reject(
new Error(
`There was an error during the creation of the S3 Bucket ${chalk.bold(bucketName)} in region ${chalk.bold(region)}`
)
)
)
)
)
);
}
/**
* Upload the template in the S3Bucket
* @param bucketName S3 Bucket name to upload the template into
* @param filename Name to give to the file in the Bucket
* @param path Path to the file
* @returns {Promise}
*/
function uploadTemplate(bucketName, filename, path) {
return spinner(
new Promise((resolve, reject) =>
fs.stat(path, (error, stats) => {
if (!stats) {
reject(new Error(`File ${chalk.bold(path)} not found`));
}
const upload = s3.upload(
{
Bucket: bucketName,
Key: filename,
Body: fs.createReadStream(path),
},
{
partSize: Math.max(stats.size, S3_MIN_PART_SIZE),
queueSize: 1,
}
);
let bar;
upload.on('httpUploadProgress', evt => {
if (!bar && evt.total) {
const total = evt.total / 1000000;
bar = new ProgressBar('uploading [:bar] :percent :etas', {
complete: '=',
incomplete: ' ',
width: 20,
total,
clear: true,
});
}
const curr = evt.loaded / 1000000;
bar.tick(curr - bar.curr);
});
return upload.promise().then(resolve).catch(reject);
})
)
);
}
/**
* Sanitize the bucketName following the rule found here:
* http://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-s3-bucket-naming-requirements.html
* @param bucketName
* @returns {string}
*/
function sanitizeBucketName(bucketName) {
return _(bucketName)
.split('.')
.filter(e => e)
.map(_.toLower)
.map(e => _.replace(e, '_', '-'))
.join('.');
}