Adding redshift start / stop

This commit is contained in:
Manuel Romero
2025-07-24 12:18:29 +02:00
parent 272415faf0
commit 38cc793016
9 changed files with 781 additions and 9 deletions

View File

@@ -1,6 +1,7 @@
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");
@@ -334,9 +335,54 @@ async function start(provision, triggerUserId) {
}
// 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;

View File

@@ -100,6 +100,27 @@ async function checkIAMUserAccessKey(provision) {
}
}
async function startRedshiftCluster(provId, userId) {
try {
let provision = await db.provision.getById(provId);
if ( !provision ) return;
return await awscli.startRedshiftCluster(provision, userId);
} catch (err) {
console.log("CLI# ERROR startRedshiftCluster", err);
}
}
async function stopRedshiftCluster(provId, userId) {
try {
let provision = await db.provision.getById(provId);
if ( !provision ) return;
return await awscli.stopRedshiftCluster(provision, userId);
} catch (err) {
console.log("CLI# ERROR stopRedshiftCluster", err);
}
}
module.exports.deallocate = deallocate;
module.exports.start = start;
module.exports.updateVmsTags = updateVmsTags;
@@ -108,4 +129,6 @@ module.exports.startDb = startDb;
module.exports.createSnapshots = createSnapshots;
module.exports.rotateStorageAccountKey = rotateStorageAccountKey;
module.exports.rotateIAMUserAccessKey = rotateIAMUserAccessKey;
module.exports.checkIAMUserAccessKey = checkIAMUserAccessKey;
module.exports.checkIAMUserAccessKey = checkIAMUserAccessKey;
module.exports.startRedshiftCluster = startRedshiftCluster;
module.exports.stopRedshiftCluster = stopRedshiftCluster;

View File

@@ -5,6 +5,7 @@
"@aws-sdk/client-ec2": "^3.590.0",
"@aws-sdk/client-iam": "^3.758.0",
"@aws-sdk/client-rds": "^3.590.0",
"@aws-sdk/client-redshift": "^3.848.0",
"@azure/arm-compute": "^22.3.0",
"@azure/arm-dns": "^5.1.0",
"@azure/arm-storage": "^18.3.0",

File diff suppressed because it is too large Load Diff

View File

@@ -772,6 +772,8 @@ router.post('/:userId/provisions/:id/deallocatevms', passport.ensureAuthenticate
});
} else if(provision.scenario === 'awsqmi-rds') {
cli.stopDb(provision._id, triggerUser._id);
} else if(provision.scenario === 'awsqmi-redshitft') {
cli.stopRedshiftCluster(provision._id, triggerUser._id);
} else {
cli.deallocate(provision._id, triggerUser._id);
}
@@ -840,6 +842,8 @@ router.post('/:userId/provisions/:id/startvms', passport.ensureAuthenticatedAndI
});
} else if(provision.scenario === 'awsqmi-rds') {
cli.startDb(provision._id, triggerUser._id);
} else if(provision.scenario === 'awsqmi-redshitft') {
cli.startRedshiftCluster(provision._id, triggerUser._id);
} else {
cli.start(provision._id, triggerUser._id);
}

View File

@@ -33,7 +33,7 @@
<i><mdb-icon fas icon="info-circle"></mdb-icon> This will just <b>temporary stop</b> this database instance <b>only for 7 days</b>, then it will auto restart. You can restart at any point.</i>
</p>
</div>
<div *ngIf="provision.scenario === 'azqmi-synapse'">
<div *ngIf="provision.scenario === 'awsqmi-redshift' || provision.scenario === 'azqmi-synapse'">
<p>
<br>
<i><mdb-icon fas icon="info-circle"></mdb-icon> This will stop this database instance. You can restart it at any point though. </i>
@@ -52,7 +52,7 @@
<i><mdb-icon fas icon="info-circle"></mdb-icon> As scheduled, next shutdown will be at <b>{{provision.schedule.localeShutdownTime}}h</b> ({{provision.schedule.localTimezone}})</i>
</p>
</div>
<div *ngIf="provision.scenario === 'awsqmi-rds' || provision.scenario === 'azqmi-synapse'">
<div *ngIf="provision.scenario === 'awsqmi-redshift' || provision.scenario === 'awsqmi-rds' || provision.scenario === 'azqmi-synapse'">
<p>
<br>
<i><mdb-icon fas icon="info-circle"></mdb-icon> Be cost-conscious and stop this intance when you are done using it. </i>

View File

@@ -89,10 +89,10 @@
<button style="margin-right: 2px;" mdbTooltip="Configure provision" *ngIf="provision.canManage && provision.status === 'provisioned'" (click)="openScheduleModal(provision)" class="lui-button">
<mdb-icon fas icon="cog"></mdb-icon>
</button>
<button style="margin-right: 2px;" mdbTooltip="Stop" *ngIf="(provision.scenario === 'awsqmi-rds' || provision.scenario === 'azqmi-synapse' || provision.vmImage && provision.vmImage.vm1 || provision.options && provision.options.vm1) && provision.status === 'provisioned' && provision.statusVms === 'Running'" (click)="openConfirmStopModal(provision)" class="lui-button">
<button style="margin-right: 2px;" mdbTooltip="Stop" *ngIf="(provision.scenario === 'awsqmi-redshift' || provision.scenario === 'awsqmi-rds' || provision.scenario === 'azqmi-synapse' || provision.vmImage && provision.vmImage.vm1 || provision.options && provision.options.vm1) && provision.status === 'provisioned' && provision.statusVms === 'Running'" (click)="openConfirmStopModal(provision)" class="lui-button">
<span class="lui-icon lui-icon--stop" aria-hidden="true"></span>
</button>
<button style="margin-right: 2px;" mdbTooltip="Start" *ngIf="(provision.scenario === 'awsqmi-rds' || provision.scenario === 'azqmi-synapse' || provision.vmImage && provision.vmImage.vm1 || provision.options && provision.options.vm1) && provision.status === 'provisioned' && provision.statusVms === 'Stopped'" (click)="openConfirmStartModal(provision)" class="lui-button">
<button style="margin-right: 2px;" mdbTooltip="Start" *ngIf="(provision.scenario === 'awsqmi-redshift' || provision.scenario === 'awsqmi-rds' || provision.scenario === 'azqmi-synapse' || provision.vmImage && provision.vmImage.vm1 || provision.options && provision.options.vm1) && provision.status === 'provisioned' && provision.statusVms === 'Stopped'" (click)="openConfirmStartModal(provision)" class="lui-button">
<span class="lui-icon lui-icon--run" aria-hidden="true"></span>
</button>
<!--<button style="margin-right: 5px;" mdbTooltip="Renew 24x7 period" *ngIf="(!provision.schedule || provision.schedule.is24x7) && provision.status === 'provisioned' && provision.statusVms === 'Running'" (click)="openConfirmExtendModal(provision)" class="lui-button">

View File

@@ -101,10 +101,10 @@
<button style="margin-right: 2px;" mdbTooltip="Configure provision" *ngIf="provision.status === 'provisioned'" (click)="openScheduleModal(provision)" class="lui-button">
<mdb-icon fas icon="cog"></mdb-icon>
</button>
<button style="margin-right: 2px;" mdbTooltip="Stop" *ngIf="(provision.scenario === 'awsqmi-rds' || provision.scenario === 'azqmi-synapse' || provision.vmImage && provision.vmImage.vm1 || provision.options && provision.options.vm1) && provision.status === 'provisioned' && provision.statusVms === 'Running'" (click)="openConfirmStopModal(provision)" class="lui-button">
<button style="margin-right: 2px;" mdbTooltip="Stop" *ngIf="(provision.scenario === 'awsqmi-redshift' || provision.scenario === 'awsqmi-rds' || provision.scenario === 'azqmi-synapse' || provision.vmImage && provision.vmImage.vm1 || provision.options && provision.options.vm1) && provision.status === 'provisioned' && provision.statusVms === 'Running'" (click)="openConfirmStopModal(provision)" class="lui-button">
<span class="lui-icon lui-icon--stop" aria-hidden="true"></span>
</button>
<button style="margin-right: 2px;" mdbTooltip="Start" *ngIf="(provision.scenario === 'awsqmi-rds' || provision.scenario === 'azqmi-synapse' || provision.vmImage && provision.vmImage.vm1 || provision.options && provision.options.vm1) && provision.status === 'provisioned' && provision.statusVms === 'Stopped'" (click)="openConfirmStartModal(provision)" class="lui-button">
<button style="margin-right: 2px;" mdbTooltip="Start" *ngIf="(provision.scenario === 'awsqmi-redshift' || provision.scenario === 'awsqmi-rds' || provision.scenario === 'azqmi-synapse' || provision.vmImage && provision.vmImage.vm1 || provision.options && provision.options.vm1) && provision.status === 'provisioned' && provision.statusVms === 'Stopped'" (click)="openConfirmStartModal(provision)" class="lui-button">
<span class="lui-icon lui-icon--run" aria-hidden="true"></span>
</button>
<button style="margin-right: 2px;" mdbTooltip="Open Remote Access (VPN needed)" *ngIf="(provision.outputs && (provision.outputs.WEB_SSH_ACCESS_WITH_GUACAMOLE || provision.outputs.WEB_RDP_ACCESS_WITH_GUACAMOLE) && provision.status === 'provisioned')" (click)="openRemoteAccess(provision)" class="lui-button">

View File

@@ -104,10 +104,10 @@
<button style="margin-right: 3px;" title="Abort provision" *ngIf="!provision.isDestroyed && provision.status === 'provisioning'" (click)="openConfirmAbortModal(provision)" class="lui-button lui-text-danger">
Abort
</button>
<button style="margin-right: 3px;" title="Stop" *ngIf="(provision.scenario === 'awsqmi-rds' || provision.scenario === 'azqmi-synapse' || provision.vmImage && provision.vmImage.vm1 || provision.options && provision.options.vm1) && !provision.isDestroyed && provision.status === 'provisioned' && (provision.statusVms === 'Running' || provision.statusVms === 'N/A') " (click)="openConfirmStopModal(provision)" class="lui-button">
<button style="margin-right: 3px;" title="Stop" *ngIf="(provision.scenario === 'awsqmi-redshift' || provision.scenario === 'awsqmi-rds' || provision.scenario === 'azqmi-synapse' || provision.vmImage && provision.vmImage.vm1 || provision.options && provision.options.vm1) && !provision.isDestroyed && provision.status === 'provisioned' && (provision.statusVms === 'Running' || provision.statusVms === 'N/A') " (click)="openConfirmStopModal(provision)" class="lui-button">
<span class="lui-icon lui-icon--stop" aria-hidden="true"></span>
</button>
<button style="margin-right: 3px;" title="Start" *ngIf="(provision.scenario === 'awsqmi-rds' || provision.scenario === 'azqmi-synapse' || provision.vmImage && provision.vmImage.vm1 || provision.options && provision.options.vm1) && !provision.isDestroyed && provision.status === 'provisioned' && provision.statusVms === 'Stopped'" (click)="openConfirmStartModal(provision)" class="lui-button">
<button style="margin-right: 3px;" title="Start" *ngIf="(provision.scenario === 'awsqmi-redshift' || provision.scenario === 'awsqmi-rds' || provision.scenario === 'azqmi-synapse' || provision.vmImage && provision.vmImage.vm1 || provision.options && provision.options.vm1) && !provision.isDestroyed && provision.status === 'provisioned' && provision.statusVms === 'Stopped'" (click)="openConfirmStartModal(provision)" class="lui-button">
<span class="lui-icon lui-icon--run" aria-hidden="true"></span>
</button>
<button style="margin-right: 3px;" title="Destroy provision" *ngIf="!provision.isDestroyed && (!provision.destroy || provision.destroy.status !== 'destroying') && (provision.status === 'provisioned' || provision.status === 'error' || provision.status === 'aborted' )" (click)="openConfirmDestroyModal(provision)" class="lui-button lui-text-danger">