cloudmason 1.9.33 → 2.0.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commands/helpers/stacks/asg.yaml +1 -1
- package/commands/ssh_build.js +104 -173
- package/commands/update_app.js +8 -154
- package/package.json +1 -1
package/commands/ssh_build.js
CHANGED
|
@@ -20,17 +20,6 @@ const {
|
|
|
20
20
|
waitUntilInstanceTerminated
|
|
21
21
|
} = require('@aws-sdk/client-ec2');
|
|
22
22
|
|
|
23
|
-
const {
|
|
24
|
-
IAMClient,
|
|
25
|
-
CreateRoleCommand,
|
|
26
|
-
PutRolePolicyCommand,
|
|
27
|
-
CreateInstanceProfileCommand,
|
|
28
|
-
AddRoleToInstanceProfileCommand,
|
|
29
|
-
RemoveRoleFromInstanceProfileCommand,
|
|
30
|
-
DeleteInstanceProfileCommand,
|
|
31
|
-
DeleteRolePolicyCommand,
|
|
32
|
-
DeleteRoleCommand
|
|
33
|
-
} = require('@aws-sdk/client-iam');
|
|
34
23
|
|
|
35
24
|
const { Client } = require('ssh2');
|
|
36
25
|
const fs = require('fs');
|
|
@@ -38,49 +27,45 @@ const path = require('path');
|
|
|
38
27
|
|
|
39
28
|
// All SSH setup commands - array of [description, command]
|
|
40
29
|
const SETUP_COMMANDS = [
|
|
41
|
-
['
|
|
30
|
+
['Upgrading to latest AL2023 release', 'sudo dnf upgrade --releasever=latest -y'],
|
|
31
|
+
['Setting up NodeSource for Node.js 24 LTS', 'curl -fsSL https://rpm.nodesource.com/setup_24.x | sudo bash -'],
|
|
42
32
|
['Installing nodejs', 'sudo dnf install -y nodejs'],
|
|
43
33
|
['Node version', 'node --version'],
|
|
44
34
|
['Installing cloudwatch agent', 'sudo dnf install -y amazon-cloudwatch-agent'],
|
|
45
35
|
['Installing python', 'sudo dnf -y install python3'],
|
|
46
36
|
['Installing unzip', 'sudo dnf -y install unzip'],
|
|
47
37
|
['Installing pm2', 'sudo npm install -g pm2'],
|
|
48
|
-
['Creating app directory', 'sudo mkdir -p /app'],
|
|
38
|
+
['Creating app directory', 'sudo mkdir -p /home/ec2-user/app'],
|
|
49
39
|
];
|
|
50
40
|
|
|
51
41
|
|
|
52
42
|
class EC2AMIBuilder {
|
|
53
|
-
constructor(amiName, instanceType = 'm6a.large',
|
|
54
|
-
if (!amiName || !
|
|
55
|
-
throw new Error('amiName and
|
|
43
|
+
constructor(amiName, instanceType = 'm6a.large', localZipPath) {
|
|
44
|
+
if (!amiName || !localZipPath) {
|
|
45
|
+
throw new Error('amiName and localZipPath are required parameters');
|
|
56
46
|
}
|
|
57
|
-
|
|
47
|
+
|
|
58
48
|
this.amiName = amiName;
|
|
59
49
|
this.instanceType = instanceType;
|
|
60
|
-
this.
|
|
61
|
-
|
|
50
|
+
this.localZipPath = localZipPath;
|
|
51
|
+
|
|
62
52
|
// AWS clients
|
|
63
|
-
const region = process.env.AWS_REGION || 'us-east-1';
|
|
53
|
+
const region = process.env.orgRegion || process.env.AWS_REGION || 'us-east-1';
|
|
64
54
|
this.ec2Client = new EC2Client({ region });
|
|
65
|
-
|
|
66
|
-
|
|
55
|
+
|
|
67
56
|
// Generate unique names for temporary resources
|
|
68
57
|
this.timestamp = Date.now();
|
|
69
58
|
this.keyPairName = `ec2-builder-keypair-${this.timestamp}`;
|
|
70
59
|
this.securityGroupName = `ec2-builder-sg-${this.timestamp}`;
|
|
71
|
-
this.iamRoleName = `ec2-builder-role-${this.timestamp}`;
|
|
72
|
-
this.instanceProfileName = `ec2-builder-profile-${this.timestamp}`;
|
|
73
60
|
this.privateKeyPath = path.join(__dirname, `${this.keyPairName}.pem`);
|
|
74
|
-
|
|
61
|
+
|
|
75
62
|
// Resource tracking for cleanup
|
|
76
63
|
this.createdResources = {
|
|
77
64
|
instanceId: null,
|
|
78
65
|
keyPairName: null,
|
|
79
|
-
securityGroupId: null
|
|
80
|
-
iamRoleName: null,
|
|
81
|
-
instanceProfileName: null
|
|
66
|
+
securityGroupId: null
|
|
82
67
|
};
|
|
83
|
-
|
|
68
|
+
|
|
84
69
|
this.sshConnection = null;
|
|
85
70
|
this.publicIp = null;
|
|
86
71
|
}
|
|
@@ -225,95 +210,18 @@ class EC2AMIBuilder {
|
|
|
225
210
|
return securityGroupId;
|
|
226
211
|
}
|
|
227
212
|
|
|
228
|
-
async createIAMRole() {
|
|
229
|
-
console.log('👤 Creating IAM role for S3 access...');
|
|
230
|
-
|
|
231
|
-
// Trust policy for EC2
|
|
232
|
-
const trustPolicy = {
|
|
233
|
-
Version: '2012-10-17',
|
|
234
|
-
Statement: [
|
|
235
|
-
{
|
|
236
|
-
Effect: 'Allow',
|
|
237
|
-
Principal: { Service: 'ec2.amazonaws.com' },
|
|
238
|
-
Action: 'sts:AssumeRole'
|
|
239
|
-
}
|
|
240
|
-
]
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
// Create IAM role
|
|
244
|
-
const createRoleCommand = new CreateRoleCommand({
|
|
245
|
-
RoleName: this.iamRoleName,
|
|
246
|
-
AssumeRolePolicyDocument: JSON.stringify(trustPolicy),
|
|
247
|
-
Description: 'Temporary role for EC2 AMI builder to access S3'
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
await this.iamClient.send(createRoleCommand);
|
|
251
|
-
this.createdResources.iamRoleName = this.iamRoleName;
|
|
252
|
-
|
|
253
|
-
// S3 access policy
|
|
254
|
-
const s3Policy = {
|
|
255
|
-
Version: '2012-10-17',
|
|
256
|
-
Statement: [
|
|
257
|
-
{
|
|
258
|
-
Effect: 'Allow',
|
|
259
|
-
Action: [
|
|
260
|
-
's3:GetObject',
|
|
261
|
-
's3:GetObjectVersion'
|
|
262
|
-
],
|
|
263
|
-
Resource: '*'
|
|
264
|
-
}
|
|
265
|
-
]
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
// Attach inline policy
|
|
269
|
-
const putPolicyCommand = new PutRolePolicyCommand({
|
|
270
|
-
RoleName: this.iamRoleName,
|
|
271
|
-
PolicyName: 'S3AccessPolicy',
|
|
272
|
-
PolicyDocument: JSON.stringify(s3Policy)
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
await this.iamClient.send(putPolicyCommand);
|
|
276
|
-
|
|
277
|
-
// Create instance profile
|
|
278
|
-
const createProfileCommand = new CreateInstanceProfileCommand({
|
|
279
|
-
InstanceProfileName: this.instanceProfileName
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
await this.iamClient.send(createProfileCommand);
|
|
283
|
-
this.createdResources.instanceProfileName = this.instanceProfileName;
|
|
284
|
-
|
|
285
|
-
// Add role to instance profile
|
|
286
|
-
const addRoleCommand = new AddRoleToInstanceProfileCommand({
|
|
287
|
-
InstanceProfileName: this.instanceProfileName,
|
|
288
|
-
RoleName: this.iamRoleName
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
await this.iamClient.send(addRoleCommand);
|
|
292
|
-
|
|
293
|
-
// Wait for IAM propagation
|
|
294
|
-
console.log('⏳ Waiting for IAM role propagation...');
|
|
295
|
-
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
296
|
-
|
|
297
|
-
console.log(`✅ IAM role created: ${this.iamRoleName}`);
|
|
298
|
-
return this.instanceProfileName;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
213
|
async launchInstance() {
|
|
302
214
|
console.log('🚀 Launching EC2 instance...');
|
|
303
|
-
|
|
215
|
+
|
|
304
216
|
const amiId = await this.getLatestAmazonLinuxAMI();
|
|
305
217
|
const keyPairName = await this.createKeyPair();
|
|
306
218
|
const securityGroupId = await this.createSecurityGroup();
|
|
307
|
-
|
|
308
|
-
|
|
219
|
+
|
|
309
220
|
const command = new RunInstancesCommand({
|
|
310
221
|
ImageId: amiId,
|
|
311
222
|
InstanceType: this.instanceType,
|
|
312
223
|
KeyName: keyPairName,
|
|
313
224
|
SecurityGroupIds: [securityGroupId],
|
|
314
|
-
IamInstanceProfile: {
|
|
315
|
-
Name: instanceProfileName
|
|
316
|
-
},
|
|
317
225
|
MinCount: 1,
|
|
318
226
|
MaxCount: 1,
|
|
319
227
|
BlockDeviceMappings: [
|
|
@@ -339,12 +247,12 @@ class EC2AMIBuilder {
|
|
|
339
247
|
|
|
340
248
|
const result = await this.ec2Client.send(command);
|
|
341
249
|
this.createdResources.instanceId = result.Instances[0].InstanceId;
|
|
342
|
-
|
|
250
|
+
|
|
343
251
|
console.log(`✅ Instance launched: ${this.createdResources.instanceId}`);
|
|
344
|
-
|
|
252
|
+
|
|
345
253
|
await this.waitForInstanceRunning();
|
|
346
254
|
await this.getInstancePublicIP();
|
|
347
|
-
|
|
255
|
+
|
|
348
256
|
console.log(`🌐 Instance public IP: ${this.publicIp}`);
|
|
349
257
|
}
|
|
350
258
|
|
|
@@ -456,33 +364,93 @@ class EC2AMIBuilder {
|
|
|
456
364
|
console.log('✅ System setup completed');
|
|
457
365
|
}
|
|
458
366
|
|
|
459
|
-
async
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
367
|
+
async uploadAppViaSFTP() {
|
|
368
|
+
return new Promise((resolve, reject) => {
|
|
369
|
+
console.log('📤 Uploading application via SFTP...');
|
|
370
|
+
console.log(` Local file: ${this.localZipPath}`);
|
|
371
|
+
|
|
372
|
+
this.sshConnection.sftp((err, sftp) => {
|
|
373
|
+
if (err) {
|
|
374
|
+
console.error('❌ SFTP session error:', err.message);
|
|
375
|
+
return reject(err);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const readStream = fs.createReadStream(this.localZipPath);
|
|
379
|
+
const writeStream = sftp.createWriteStream('/tmp/app.zip');
|
|
380
|
+
|
|
381
|
+
const fileSize = fs.statSync(this.localZipPath).size;
|
|
382
|
+
let uploaded = 0;
|
|
383
|
+
|
|
384
|
+
readStream.on('data', (chunk) => {
|
|
385
|
+
uploaded += chunk.length;
|
|
386
|
+
const percent = Math.round((uploaded / fileSize) * 100);
|
|
387
|
+
process.stdout.write(`\r Progress: ${percent}% (${Math.round(uploaded / 1024)}KB / ${Math.round(fileSize / 1024)}KB)`);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
writeStream.on('close', () => {
|
|
391
|
+
console.log('\n✅ SFTP upload completed');
|
|
392
|
+
resolve();
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
writeStream.on('error', (err) => {
|
|
396
|
+
console.error('\n❌ SFTP write error:', err.message);
|
|
397
|
+
reject(err);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
readStream.on('error', (err) => {
|
|
401
|
+
console.error('\n❌ File read error:', err.message);
|
|
402
|
+
reject(err);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
readStream.pipe(writeStream);
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
async uploadAndSetupApp() {
|
|
411
|
+
console.log('📦 Setting up application...');
|
|
412
|
+
|
|
413
|
+
// Upload via SFTP
|
|
414
|
+
await this.uploadAppViaSFTP();
|
|
415
|
+
|
|
416
|
+
// Application setup commands
|
|
463
417
|
const appCommands = [
|
|
464
|
-
['
|
|
465
|
-
['
|
|
466
|
-
['Cleaning up package archive', '
|
|
467
|
-
['Directory files', 'ls -
|
|
468
|
-
['Showing application structure', 'find /app -maxdepth 2 -name "node_modules" -prune -o -print']
|
|
418
|
+
['Extracting application package', 'sudo unzip -o /tmp/app.zip -d /home/ec2-user/app'],
|
|
419
|
+
['Setting ownership to ec2-user', 'sudo chown -R ec2-user:ec2-user /home/ec2-user/app'],
|
|
420
|
+
['Cleaning up package archive', 'rm -f /tmp/app.zip'],
|
|
421
|
+
['Directory files', 'ls -la /home/ec2-user/app'],
|
|
422
|
+
['Showing application structure', 'find /home/ec2-user/app -maxdepth 2 -name "node_modules" -prune -o -print']
|
|
469
423
|
];
|
|
470
|
-
|
|
424
|
+
|
|
471
425
|
// Execute all app setup commands
|
|
472
426
|
for (const [description, command] of appCommands) {
|
|
473
427
|
await this.executeCommand(command, description);
|
|
474
428
|
}
|
|
475
|
-
|
|
429
|
+
|
|
476
430
|
console.log('✅ Application setup completed');
|
|
477
431
|
}
|
|
478
432
|
|
|
479
433
|
async createAMI() {
|
|
480
434
|
console.log('📸 Creating AMI from instance...');
|
|
481
|
-
|
|
482
|
-
// Cleanup commands before AMI creation
|
|
435
|
+
|
|
436
|
+
// Cleanup commands before AMI creation - remove all sensitive data
|
|
483
437
|
const cleanupCommands = [
|
|
484
|
-
|
|
485
|
-
['
|
|
438
|
+
// Remove SSH authorized keys (contains the temporary build key)
|
|
439
|
+
['Removing SSH authorized keys', 'rm -f ~/.ssh/authorized_keys && sudo rm -f /root/.ssh/authorized_keys'],
|
|
440
|
+
// Remove SSH host keys (new instances will regenerate their own)
|
|
441
|
+
['Removing SSH host keys', 'sudo rm -f /etc/ssh/ssh_host_*'],
|
|
442
|
+
// Clean cloud-init so it runs fresh on new instances
|
|
443
|
+
['Cleaning cloud-init data', 'sudo rm -rf /var/lib/cloud/*'],
|
|
444
|
+
// Reset machine-id for unique instance identification
|
|
445
|
+
['Resetting machine-id', 'sudo truncate -s 0 /etc/machine-id'],
|
|
446
|
+
// Clean bash history for all users
|
|
447
|
+
['Cleaning bash history', 'rm -f ~/.bash_history && sudo rm -f /root/.bash_history'],
|
|
448
|
+
// Clean logs and temp files
|
|
449
|
+
['Cleaning logs and temp files', 'sudo rm -rf /tmp/* /var/tmp/* /var/log/messages* /var/log/secure* /var/log/cloud-init*.log'],
|
|
450
|
+
// Clean DNF cache
|
|
451
|
+
['Cleaning DNF cache', 'sudo dnf clean all'],
|
|
452
|
+
// Verify cleanup and check disk usage
|
|
453
|
+
['Checking disk usage', 'df -h && du -sh /home/ec2-user/app']
|
|
486
454
|
];
|
|
487
455
|
|
|
488
456
|
// Execute cleanup commands
|
|
@@ -526,7 +494,7 @@ class EC2AMIBuilder {
|
|
|
526
494
|
console.log('⏳ Waiting for AMI to be available (this may take several minutes)...');
|
|
527
495
|
|
|
528
496
|
await waitUntilImageAvailable(
|
|
529
|
-
{ client: this.ec2Client, maxWaitTime:
|
|
497
|
+
{ client: this.ec2Client, maxWaitTime: 3800 },
|
|
530
498
|
{ ImageIds: [amiId] }
|
|
531
499
|
);
|
|
532
500
|
|
|
@@ -579,45 +547,10 @@ class EC2AMIBuilder {
|
|
|
579
547
|
const deleteKeyPairCommand = new DeleteKeyPairCommand({
|
|
580
548
|
KeyName: this.createdResources.keyPairName
|
|
581
549
|
});
|
|
582
|
-
|
|
550
|
+
|
|
583
551
|
await this.ec2Client.send(deleteKeyPairCommand);
|
|
584
552
|
}
|
|
585
|
-
|
|
586
|
-
// Clean up IAM resources
|
|
587
|
-
if (this.createdResources.instanceProfileName && this.createdResources.iamRoleName) {
|
|
588
|
-
console.log('🗑️ Cleaning up IAM resources...');
|
|
589
|
-
|
|
590
|
-
// Remove role from instance profile
|
|
591
|
-
const removeRoleCommand = new RemoveRoleFromInstanceProfileCommand({
|
|
592
|
-
InstanceProfileName: this.createdResources.instanceProfileName,
|
|
593
|
-
RoleName: this.createdResources.iamRoleName
|
|
594
|
-
});
|
|
595
|
-
|
|
596
|
-
await this.iamClient.send(removeRoleCommand);
|
|
597
|
-
|
|
598
|
-
// Delete instance profile
|
|
599
|
-
const deleteProfileCommand = new DeleteInstanceProfileCommand({
|
|
600
|
-
InstanceProfileName: this.createdResources.instanceProfileName
|
|
601
|
-
});
|
|
602
|
-
|
|
603
|
-
await this.iamClient.send(deleteProfileCommand);
|
|
604
|
-
|
|
605
|
-
// Delete role policy
|
|
606
|
-
const deletePolicyCommand = new DeleteRolePolicyCommand({
|
|
607
|
-
RoleName: this.createdResources.iamRoleName,
|
|
608
|
-
PolicyName: 'S3AccessPolicy'
|
|
609
|
-
});
|
|
610
|
-
|
|
611
|
-
await this.iamClient.send(deletePolicyCommand);
|
|
612
|
-
|
|
613
|
-
// Delete role
|
|
614
|
-
const deleteRoleCommand = new DeleteRoleCommand({
|
|
615
|
-
RoleName: this.createdResources.iamRoleName
|
|
616
|
-
});
|
|
617
|
-
|
|
618
|
-
await this.iamClient.send(deleteRoleCommand);
|
|
619
|
-
}
|
|
620
|
-
|
|
553
|
+
|
|
621
554
|
console.log('✅ Cleanup completed');
|
|
622
555
|
|
|
623
556
|
} catch (error) {
|
|
@@ -632,18 +565,18 @@ class EC2AMIBuilder {
|
|
|
632
565
|
await this.launchInstance();
|
|
633
566
|
await this.connectSSH();
|
|
634
567
|
await this.setupSystem();
|
|
635
|
-
await this.
|
|
636
|
-
console.log('Build complete after', Math.ceil((Date.now() - start)/1000/60));
|
|
568
|
+
await this.uploadAndSetupApp();
|
|
569
|
+
console.log('Build complete after', Math.ceil((Date.now() - start)/1000/60), 'minutes');
|
|
637
570
|
const amiId = await this.createAMI();
|
|
638
|
-
console.log('AMI Created after', Math.ceil((Date.now() - start)/1000/60));
|
|
571
|
+
console.log('AMI Created after', Math.ceil((Date.now() - start)/1000/60), 'minutes');
|
|
639
572
|
console.log(`📋 Summary:`);
|
|
640
573
|
console.log(` - AMI ID: ${amiId}`);
|
|
641
574
|
console.log(` - AMI Name: ${this.amiName}`);
|
|
642
575
|
console.log(` - Instance Type Used: ${this.instanceType}`);
|
|
643
|
-
console.log(` -
|
|
576
|
+
console.log(` - Local Package: ${this.localZipPath}`);
|
|
644
577
|
|
|
645
578
|
return amiId;
|
|
646
|
-
|
|
579
|
+
|
|
647
580
|
} catch (error) {
|
|
648
581
|
console.error('❌ AMI Build failed:', error.message);
|
|
649
582
|
throw error;
|
|
@@ -653,8 +586,8 @@ class EC2AMIBuilder {
|
|
|
653
586
|
}
|
|
654
587
|
}
|
|
655
588
|
|
|
656
|
-
async function sshAMI(amiName,
|
|
657
|
-
const builder = new EC2AMIBuilder(amiName, instanceType,
|
|
589
|
+
async function sshAMI(amiName, localZipPath, instanceType){
|
|
590
|
+
const builder = new EC2AMIBuilder(amiName, instanceType, localZipPath);
|
|
658
591
|
const result = await builder.build();
|
|
659
592
|
console.log('AMI ID:', result);
|
|
660
593
|
return result;
|
|
@@ -664,8 +597,6 @@ async function sshAMI(amiName,s3PackageUrl,instanceType){
|
|
|
664
597
|
// Convenience function for direct usage
|
|
665
598
|
module.exports.buildAMI = sshAMI;
|
|
666
599
|
|
|
667
|
-
sshAMI('test-ami-2', 's3://coreinfra-infrabucket-qtfrahre6vbl/apps/theorim/3.6/app.zip')
|
|
668
|
-
|
|
669
600
|
// // CLI usage if called directly
|
|
670
601
|
// if (require.main === module) {
|
|
671
602
|
// const [,, amiName, instanceType, s3PackageUrl] = process.argv;
|
package/commands/update_app.js
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const fs = require('fs');
|
|
3
|
-
const EC2 = require('./helpers/ec2');
|
|
4
|
-
const { EC2Client, RunInstancesCommand,CreateImageCommand,TerminateInstancesCommand,DescribeInstanceStatusCommand,DeregisterImageCommand,DescribeImagesCommand,CopyImageCommand } = require("@aws-sdk/client-ec2");
|
|
5
|
-
|
|
6
3
|
const AdmZip = require("adm-zip");
|
|
7
4
|
|
|
8
5
|
const Params = require('./helpers/params')
|
|
9
6
|
const S3 = require('./helpers/s3');
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const INSTANCE_TYPE="t2.micro"
|
|
7
|
+
const { buildAMI } = require('./ssh_build');
|
|
13
8
|
|
|
14
9
|
exports.main = async function(args){
|
|
15
10
|
console.log(`Updating ${args.app} v${args.v}`);
|
|
@@ -52,42 +47,19 @@ exports.main = async function(args){
|
|
|
52
47
|
}
|
|
53
48
|
|
|
54
49
|
// --- III BUILD IMAGE ---
|
|
55
|
-
// Launch ec2
|
|
56
|
-
const orgParams = await Params.getOrgConfig();
|
|
57
|
-
|
|
58
|
-
// const awsLinuxAMI = await findLinuxAMI(process.env.orgRegion);
|
|
59
|
-
const awsLinuxAMI = EC2.awsLinuxAMI(process.env.orgRegion);
|
|
60
|
-
const instance_id = await launchInstance({
|
|
61
|
-
app: app.name,
|
|
62
|
-
linuxAMI: awsLinuxAMI,
|
|
63
|
-
version: args.v,
|
|
64
|
-
sec_group: orgParams.buildSecGroup,
|
|
65
|
-
iam: orgParams.buildInstanceProfile,
|
|
66
|
-
node: app.nodeV,
|
|
67
|
-
py: app.pyV
|
|
68
|
-
});
|
|
69
|
-
console.log('Instance Launched:',instance_id);
|
|
70
|
-
console.log('Waiting 60s to initiate checks');
|
|
71
|
-
await sleep(60*1000);
|
|
72
|
-
console.log('Checking Instance Status');
|
|
73
|
-
await waitUntilInstanceReady(instance_id,process.env.orgRegion);
|
|
74
|
-
console.log('Waiting 5m for app to be ready');
|
|
75
|
-
await sleep(300*1000);
|
|
76
|
-
|
|
77
|
-
// Create AMI
|
|
78
50
|
const buildNumber = (app.versions[args.v]?.currentBuild || 0) + 1;
|
|
79
51
|
const appVID = `${app.name.toLowerCase()}-v${args.v}.${buildNumber}`;
|
|
80
52
|
|
|
81
|
-
|
|
53
|
+
console.log(`Building AMI: ${appVID}`);
|
|
54
|
+
console.log(`Using local zip: ${zipFilePath}`);
|
|
55
|
+
|
|
82
56
|
let ami_id;
|
|
83
57
|
try {
|
|
84
|
-
ami_id = await
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
58
|
+
ami_id = await buildAMI(appVID, zipFilePath);
|
|
59
|
+
} catch(e) {
|
|
60
|
+
console.log("Error Creating AMI:" + e);
|
|
61
|
+
throw new Error("Error - Build Not Complete");
|
|
88
62
|
}
|
|
89
|
-
await terminateInstance(instance_id,process.env.orgRegion)
|
|
90
|
-
if (success === false){ throw new Error("Error - Build Not Complete") }
|
|
91
63
|
|
|
92
64
|
// --- IV UPDATE PARAMS ---
|
|
93
65
|
const versionInfo = {
|
|
@@ -132,121 +104,3 @@ async function prepZip(appPath){
|
|
|
132
104
|
process.on('exit', function(){ fs.unlinkSync(zipPath) });
|
|
133
105
|
return zipPath;
|
|
134
106
|
}
|
|
135
|
-
|
|
136
|
-
async function launchInstance(launchParams){
|
|
137
|
-
console.log('Launching Instance in ' + process.env.orgRegion);
|
|
138
|
-
const nodeRepo = launchParams.node === '' ? 'echo default_version' : `https://rpm.nodesource.com/setup_${launchParams.node}.x | sudo bash -`;
|
|
139
|
-
const user_data = [
|
|
140
|
-
`#!/bin/bash -xe`,
|
|
141
|
-
nodeRepo,
|
|
142
|
-
`yum -y install nodejs`,
|
|
143
|
-
`yum install -y amazon-cloudwatch-agent`,
|
|
144
|
-
`yum -y install python3`,
|
|
145
|
-
`yum -y install unzip`,
|
|
146
|
-
`npm install -g pm2`,
|
|
147
|
-
`cd /home/ec2-user`,
|
|
148
|
-
`aws s3 cp s3://${process.env.orgBucket}/apps/${launchParams.app.toLowerCase()}/${launchParams.version}/app.zip .`,
|
|
149
|
-
`sleep 10`,
|
|
150
|
-
`unzip app.zip -d app`,
|
|
151
|
-
`touch app/ami_ok.txt`,
|
|
152
|
-
`rm -r app.zip`
|
|
153
|
-
].join('\n')
|
|
154
|
-
|
|
155
|
-
const ud_b64 = Buffer.from(user_data).toString('base64');
|
|
156
|
-
|
|
157
|
-
const client = new EC2Client({region: process.env.orgRegion });
|
|
158
|
-
|
|
159
|
-
const createInstanceParams = {
|
|
160
|
-
ImageId: launchParams.linuxAMI,
|
|
161
|
-
InstanceType: INSTANCE_TYPE,
|
|
162
|
-
SecurityGroupIds: [
|
|
163
|
-
launchParams.sec_group
|
|
164
|
-
],
|
|
165
|
-
MinCount: 1,
|
|
166
|
-
MaxCount: 1,
|
|
167
|
-
UserData: ud_b64,
|
|
168
|
-
IamInstanceProfile: {
|
|
169
|
-
Arn: launchParams.iam
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
const command = new RunInstancesCommand(createInstanceParams);
|
|
173
|
-
const response = await client.send(command);
|
|
174
|
-
const instance_id = response.Instances[0].InstanceId
|
|
175
|
-
|
|
176
|
-
console.log('Instance Launched:',instance_id);
|
|
177
|
-
return instance_id;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
async function waitUntilInstanceReady(instance_id,region){
|
|
181
|
-
console.log(`Awaiting ${instance_id} status of ok`)
|
|
182
|
-
const client = new EC2Client({region});
|
|
183
|
-
const input = { // DescribeInstanceStatusRequest
|
|
184
|
-
InstanceIds: [ // InstanceIdStringList
|
|
185
|
-
instance_id
|
|
186
|
-
],
|
|
187
|
-
DryRun: false,
|
|
188
|
-
IncludeAllInstances: true
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
let totalSleepTime = 0;
|
|
192
|
-
let ok = false;
|
|
193
|
-
const command = new DescribeInstanceStatusCommand(input);
|
|
194
|
-
for (let i=0; i<100; i++){
|
|
195
|
-
const response = await client.send(command);
|
|
196
|
-
const status = response.InstanceStatuses[0].InstanceStatus.Status;
|
|
197
|
-
console.log(`\tCheck ${i+1} @ ${totalSleepTime}s: EC2 Status is ${status}`)
|
|
198
|
-
if (status !== 'ok'){
|
|
199
|
-
await sleep(10000);
|
|
200
|
-
totalSleepTime += 10;
|
|
201
|
-
} else {
|
|
202
|
-
console.log('Ec2 Instance Ready:' + status);
|
|
203
|
-
ok = true;
|
|
204
|
-
break;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (ok === false){
|
|
209
|
-
console.log('ERR:::', `Ec2 Instance Not Ready After ${totalSleepTime}s`)
|
|
210
|
-
throw `Ec2 Instance Not Ready After ${totalSleepTime}s`
|
|
211
|
-
} else {
|
|
212
|
-
console.log(`Instance Ready After ${totalSleepTime}s. Waiting 5m to Proceed`);
|
|
213
|
-
await sleep(300000);
|
|
214
|
-
}
|
|
215
|
-
return true;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
async function createAMI(instance_id,image_name,region){
|
|
219
|
-
console.log(`Building ${image_name} in ${region}`)
|
|
220
|
-
const client = new EC2Client({region});
|
|
221
|
-
const input = { // CreateImageRequest
|
|
222
|
-
Description: `Base Application Image`,
|
|
223
|
-
DryRun: false,
|
|
224
|
-
InstanceId: instance_id, // required
|
|
225
|
-
Name: image_name, // required
|
|
226
|
-
NoReboot: true
|
|
227
|
-
};
|
|
228
|
-
const command = new CreateImageCommand(input);
|
|
229
|
-
const response = await client.send(command);
|
|
230
|
-
console.log(`Created Image ${image_name} ID:${response.ImageId}`)
|
|
231
|
-
return response.ImageId;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
async function terminateInstance(instance_id,region){
|
|
235
|
-
console.log('Terminating Instance ' + instance_id)
|
|
236
|
-
const client = new EC2Client({region});
|
|
237
|
-
const input = { // TerminateInstancesRequest
|
|
238
|
-
InstanceIds: [ instance_id ],
|
|
239
|
-
DryRun: false,
|
|
240
|
-
};
|
|
241
|
-
const command = new TerminateInstancesCommand(input);
|
|
242
|
-
const response = await client.send(command);
|
|
243
|
-
return true;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
async function sleep(time){
|
|
248
|
-
return new Promise(function (resolve, reject) {
|
|
249
|
-
setTimeout(function () { resolve(true);
|
|
250
|
-
}, time);
|
|
251
|
-
});
|
|
252
|
-
}
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"cloudmason","version":"
|
|
1
|
+
{"name":"cloudmason","version":"2.0.36","description":"","main":"main.js","scripts":{"build":"node build.js"},"bin":{"mason":"./main.js"},"repository":{"type":"git","url":"https://github.com/kai-harvey/cloudmason.git"},"author":"Kai Harvey","license":"ISC","dependencies":{"@aws-sdk/client-acm":"^3.418.0","@aws-sdk/client-auto-scaling":"^3.470.0","@aws-sdk/client-cloudformation":"^3.418.0","@aws-sdk/client-ec2":"^3.864.0","@aws-sdk/client-iam":"^3.864.0","@aws-sdk/client-marketplace-catalog":"^3.716.0","@aws-sdk/client-route-53":"^3.425.0","@aws-sdk/client-s3":"^3.418.0","@aws-sdk/client-ssm":"^3.421.0","adm-zip":"^0.5.10","ssh2":"^1.16.0","yaml":"^2.6.1"}}
|