Files
qmi-cloud/qmi-cloud-common/awscli.js
2025-07-24 12:18:29 +02:00

389 lines
14 KiB
JavaScript

const { RDSClient, StartDBClusterCommand, StartDBInstanceCommand, StopDBInstanceCommand, StopDBClusterCommand } = require("@aws-sdk/client-rds");
const { EC2Client, DescribeInstancesCommand, StopInstancesCommand, StartInstancesCommand } = require("@aws-sdk/client-ec2");
const { IAMClient, CreateAccessKeyCommand, ListAccessKeysCommand, DeleteAccessKeyCommand } = require("@aws-sdk/client-iam");
const { RedshiftClient, PauseClusterCommand, ResumeClusterCommand } = require("@aws-sdk/client-redshift");
const db = require("./mongo");
const utils = require("./utils");
function _getRgName(provision) {
let rgName = provision.scenario.toUpperCase();
rgName = rgName.replace(/AZQMI/g, 'QMI');
rgName = rgName + "-" + provision._id.toString();
return rgName;
}
function _getRegion(provision) {
let region = "eu-west-1";
if ( provision.deployOpts.location === 'East US') {
region = "us-east-1";
} else if ( provision.deployOpts.location === 'Southeast Asia' ) {
region = "ap-southeast-1";
}
return region;
}
function _getDBType(provision) {
let type = "instance";
if (provision.options && provision.options.db && provision.options.db.selected && provision.options.db.selected.value) {
let selectedLower = provision.options.db.selected.value.toLowerCase();
if (selectedLower.indexOf("aurora") !== -1) {
type = "cluster";
} else {
type = "instance";
}
}
return type;
}
function _getDbIndentifier(provision) {
var out;
if ( provision.outputs['db_instance_id'] ) {
out = provision.outputs['db_instance_id'];
} else if (provision.outputs['db_instance_endpoint']) {
out = provision.outputs['db_instance_endpoint'].split(".")[0];
}
return out;
}
async function rotateAccessKey(provision) {
if (!provision || !provision.outputs ) {
console.log(`AWSCLI# rotateAccessKey - Provision not found or Provision (${provision._id}) does not contain Outputs`);
return null;
}
var username;
var prefix = "";
if (provision.outputs['iam_user_name']){
username = provision.outputs['iam_user_name'];
} else if ( provision.outputs['S3_iam_user_name'] ) {
prefix = "S3_";
username = provision.outputs['S3_iam_user_name'];
} else if ( provision.outputs['s3_iam_user_name'] ) {
prefix = "s3_";
username = provision.outputs['s3_iam_user_name'];
} else {
console.log(`AWSCLI# rotateAccessKey - Provision (${provision._id}) does not contain an IAM user`);
return null;
}
try {
const client = new IAMClient({ region: _getRegion(provision) });
// Step 1: Create a new access key
const createKeyCommand = new CreateAccessKeyCommand({ UserName: username });
const newKey = await client.send(createKeyCommand);
console.log(`AWSCLI# rotateAccessKey (${username}) - New Access Key Created:`, newKey.AccessKey);
// You should now update your application with newKey.AccessKeyId and newKey.SecretAccessKey
// Step 2: List existing access keys
const listKeysCommand = new ListAccessKeysCommand({ UserName: username });
const keys = await client.send(listKeysCommand);
// Find the old access key (assuming we only need to keep the latest one)
for (const key of keys.AccessKeyMetadata) {
if (key.AccessKeyId !== newKey.AccessKey.AccessKeyId) {
// Step 3: Delete the old access key
console.log(`AWSCLI# rotateAccessKey (${username}): Deleting old access key: ${key.AccessKeyId}`);
const deleteKeyCommand = new DeleteAccessKeyCommand({
UserName: username,
AccessKeyId: key.AccessKeyId,
});
await client.send(deleteKeyCommand);
}
}
console.log(`AWSCLI# rotateAccessKey (${username}): Old Access Key Deleted Successfully.`);
let outputs = provision.outputs;
outputs[prefix+'iam_user_access_key'] = newKey.AccessKey.AccessKeyId;
outputs[prefix+'iam_user_access_secret'] = newKey.AccessKey.SecretAccessKey;
return await db.provision.update(provision._id, {"outputs": outputs});
} catch (error) {
console.log("AWSCLI# rotateAccessKey Error rotating access key:", error);
return null;
}
}
async function checkAccessKeys(provision) {
if (!provision || !provision.outputs ) {
console.log(`AWSCLI# checkAccessKeys - Provision not found or Provision (${provision._id}) does not contain Outputs`);
return null;
}
var username;
if (provision.outputs['iam_user_name']){
username = provision.outputs['iam_user_name'];
} else if ( provision.outputs['S3_iam_user_name'] ) {
username = provision.outputs['S3_iam_user_name'];
} else if ( provision.outputs['s3_iam_user_name'] ) {
username = provision.outputs['s3_iam_user_name'];
} else {
console.log(`AWSCLI# checkAccessKeys - Provision (${provision._id}) does not contain an IAM user`);
return null;
}
try {
const client = new IAMClient({ region: _getRegion(provision) });
// Step 1: List all access keys for the user
const listKeysCommand = new ListAccessKeysCommand({ UserName: username });
const response = await client.send(listKeysCommand);
console.log(`AWSCLI# checkAccessKeys (${username})`);
var out = [];
response.AccessKeyMetadata.forEach((key) => {
// Step 2: Check if the key is older than 90 days
const createdDate = new Date(key.CreateDate);
const now = new Date();
const ageInDays = (now - createdDate) / (1000 * 60 * 60 * 24);
key.ageInDays = ageInDays;
key.is90dExpired = ageInDays > 90;
out.push(key);
});
if (out.length === 0) {
out.push({
is90dExpired: true
});
}
return out;
} catch (error) {
console.error("Error checking access keys:", error);
return null;
}
}
async function stopDbInstance(provision, triggerUserId, isSendEmailAfter) {
if (provision.statusVms === 'Stopped' || provision.statusVms === 'Stopping'){
return;
}
var identifier = _getDbIndentifier(provision);
if ( !identifier ) {
return;
}
await db.provision.update(provision._id.toString(), {"statusVms": "Stopping" });
db.event.add({ user: triggerUserId, provision: provision._id, type: `db.stopping` });
let region = _getRegion(provision);
let type = _getDBType(provision);
console.log(`AWSCLI# DB (${type})-(${identifier}) in region (${region}) stopping...`);
try {
const client = new RDSClient({ region: region });
var params, command;
if (type === "instance") {
params = {
DBInstanceIdentifier: identifier
};
command = new StopDBInstanceCommand(params);
} else {
params = {
DBClusterIdentifier: identifier
};
command = new StopDBClusterCommand(params);
}
const response = await client.send(command);
console.log(`AWSCLI# DB (${type})-(${identifier}) stopped!`);
//console.log(`AWSCLI# DB`, response);
return response;
} catch (error) {
console.log("AWSCLI# ERROR stopping DB: "+identifier, error);
await db.provision.update(provision._id.toString(), {"statusVms": "Running" });
return;
}
}
async function startDbInstance(provision, triggerUserId) {
if (provision.statusVms === 'Running' || provision.statusVms === 'Starting'){
return;
}
var identifier = _getDbIndentifier(provision);
if ( !identifier ) {
return;
}
await db.provision.update(provision._id.toString(), {"statusVms": "Starting" });
db.event.add({ user: triggerUserId, provision: provision._id, type: `db.starting` });
let region = _getRegion(provision);
let type = _getDBType(provision);
console.log(`AWSCLI# DB (${type})-(${identifier}) in region (${region}) starting...`);
try {
const client = new RDSClient({ region: region });
var params, command;
if (type === "instance") {
params = {
DBInstanceIdentifier: identifier
};
command = new StartDBInstanceCommand(params);
} else {
params = {
DBClusterIdentifier: identifier
};
command = new StartDBClusterCommand(params);
}
const response = await client.send(command);
console.log(`AWSCLI# DB (${identifier}) started!`);
await utils.afterStartVms( provision, triggerUserId, "db" );
//console.log(`AWSCLI# DB`, response);
return response;
} catch (error) {
console.log("AWSCLI# ERROR starting DB: "+identifier, error);
await db.provision.update(provision._id.toString(), {"statusVms": "Stopped" });
return;
}
}
async function deallocate(provision, triggerUserId, isSendEmailAfter) {
let rgName = _getRgName(provision);
let region = _getRegion(provision);
console.log("AWSCLI# Stopping EC2s for resource group: "+rgName);
var client = new EC2Client({region: region});
var params = {
DryRun: false,
Filters: [
{
Name: 'tag:Name',
Values: [ `fort-${provision._id}` ]
},
]
};
try {
var command = new DescribeInstancesCommand(params);
const data = await client.send(command);
if (data && data.Reservations && data.Reservations.length) {
var ec2Ids = data.Reservations[0].Instances.map(ec2=> ec2.InstanceId);
console.log("AWSCLI# Stopping Ec2s ids...", ec2Ids);
params = {
DryRun: false,
InstanceIds: ec2Ids
};
command = new StopInstancesCommand(params);
await client.send(command);
await utils.afterStopVms( provision, triggerUserId, isSendEmailAfter );
}
} catch (error) {
console.log("AWSCLI# ERROR describing/stopping EC2s: "+rgName, error);
await db.provision.update(provision._id.toString(), {"statusVms": "Running" });
}
}
async function start(provision, triggerUserId) {
let rgName = _getRgName(provision);
let region = _getRegion(provision);
console.log("AWSCLI# Stopping EC2s for resource group: "+rgName);
var client = new EC2Client({region: region});
var params = {
DryRun: false,
Filters: [
{
Name: 'tag:Name',
Values: [ `fort-${provision._id}` ]
},
]
};
try {
var command = new DescribeInstancesCommand(params);
const data = await client.send(command);
if (data && data.Reservations && data.Reservations.length) {
var ec2Ids = data.Reservations[0].Instances.map(ec2=> ec2.InstanceId);
console.log("AWSCLI# Starting Ec2s ids...", ec2Ids);
params = {
DryRun: false,
InstanceIds: ec2Ids
};
command = new StartInstancesCommand(params);
await client.send(command);
console.log("AWSCLI# Ec2s started!");
await utils.afterStartVms( provision, triggerUserId );
}
} catch (error) {
console.log("AWSCLI# ERROR describing/starting EC2s: "+rgName, error);
await db.provision.update(provision._id.toString(), {"statusVms": "Stopped" });
}
}
// Stop Redshift cluster
async function stopRedshiftCluster(provision, triggerUserId) {
/*if (!provision || !provision.outputs || !provision.outputs['redshift_cluster_id']) {
console.log('AWSCLI# stopRedshiftCluster - Missing Redshift cluster ID in provision');
return null;
}*/
const clusterId = "qmi-"+provision._id.toString();
const region = _getRegion(provision);
try {
const client = new RedshiftClient({ region });
const command = new PauseClusterCommand({ ClusterIdentifier: clusterId });
const response = await client.send(command);
console.log(`AWSCLI# Redshift cluster (${clusterId}) paused.`);
await utils.afterStopVms( provision, triggerUserId, false, "redshift-cluster" );
return response;
} catch (error) {
console.log(`AWSCLI# ERROR pausing Redshift cluster: ${clusterId}`, error);
return null;
}
}
// Start Redshift cluster
async function startRedshiftCluster(provision, triggerUserId) {
/*if (!provision || !provision.outputs || !provision.outputs['redshift_cluster_id']) {
console.log('AWSCLI# startRedshiftCluster - Missing Redshift cluster ID in provision');
return null;
}*/
const clusterId = "qmi-"+provision._id.toString();
const region = _getRegion(provision);
try {
const client = new RedshiftClient({ region });
const command = new ResumeClusterCommand({ ClusterIdentifier: clusterId });
const response = await client.send(command);
console.log(`AWSCLI# Redshift cluster (${clusterId}) resumed.`);
await utils.afterStartVms( provision, triggerUserId, "redshift-cluster" );
return response;
} catch (error) {
console.log(`AWSCLI# ERROR resuming Redshift cluster: ${clusterId}`, error);
return null;
}
}
module.exports.deallocate = deallocate;
module.exports.start = start;
module.exports.stopDbInstance = stopDbInstance;
module.exports.startDbInstance = startDbInstance;
module.exports.rotateAccessKey = rotateAccessKey;
module.exports.checkAccessKeys = checkAccessKeys;
module.exports.stopRedshiftCluster = stopRedshiftCluster;
module.exports.startRedshiftCluster = startRedshiftCluster;