cloudmason 0.0.1

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.
@@ -0,0 +1,61 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { S3Client,HeadBucketCommand } = require("@aws-sdk/client-s3");
4
+ const { EC2Client, DescribeVpcsCommand } = require("@aws-sdk/client-ec2");
5
+
6
+ const CF = require('./helpers/cf');
7
+
8
+
9
+ exports.main = async function(args){
10
+ console.log(`Setting up ${args.name}@ ${args.domain} in ${args.region}`)
11
+
12
+ // Check if bucket exists
13
+ const isBucket = await bucketExists(`infra.${args.domain}`);
14
+ if (isBucket.name){
15
+ console.log('ERR: Bucket infra.${args.domain} already exists in ${existingBucket.region}')
16
+ console.log('Org already set up or rollback still in progress')
17
+ throw {message: 'Org exists', at: 'init_org check for existing org'}
18
+ }
19
+
20
+ // Get VPC ID
21
+ const VpcId = await getDefaultVPC(args.region);
22
+
23
+ // Deploy Stack
24
+ const success = await CF.deployOrgStack('infra', 'infra',args.region, {orgName: args.name, orgDomain: args.domain, VpcId: VpcId}, {purpose: 'infra'})
25
+ if (success === false){
26
+ console.log('ERR: Org already exists. Only one org permitted per account');
27
+ throw new Error('Org already exists')
28
+ }
29
+
30
+ // Set org.txt
31
+ const orgPath = path.resolve(__dirname,'..','org.txt');
32
+ const orgData = `${args.name},${args.region},infra.${args.domain}`;
33
+ fs.writeFileSync(orgPath,orgData,'utf-8')
34
+ console.log('Set up org:',orgData)
35
+ return true;
36
+ }
37
+
38
+
39
+ /////////////////////////////////////////
40
+ ////////////// FUNCS ////////////////////
41
+ ////////////////////////////////////////
42
+
43
+ async function bucketExists(bucketName,region){
44
+ const client = new S3Client({region});
45
+ try {
46
+ await client.send(new HeadBucketCommand({ Bucket: bucketName }));
47
+ return true;
48
+ } catch (e){
49
+ return false;
50
+ }
51
+ }
52
+
53
+ async function getDefaultVPC(region){
54
+ const ec2Client = new EC2Client({ region });
55
+ const response = await ec2Client.send(new DescribeVpcsCommand({ Filters: [{ Name: "isDefault", Values: ["true"] }] }));
56
+ return response.Vpcs[0].VpcId;
57
+ }
58
+
59
+
60
+
61
+
@@ -0,0 +1,57 @@
1
+ const Params = require('./helpers/params');
2
+ const EC2 = require('./helpers/ec2');
3
+ const CF = require('./helpers/cf')
4
+
5
+ exports.main = async function(args){
6
+ const app = await Params.getApp(args.app);
7
+ if (!app){ console.log('No app named ' + args.app); throw new Error('Invalid App Name')}
8
+
9
+ const targetInstance = app.instances.find(ins=>{ return ins.name.toLowerCase() == args.i.toLowerCase() });
10
+ const targetVersion = app.versions[args.v];
11
+
12
+ if (!targetInstance){ console.log(`No instance of ${args.app} named ${args.i}`); throw new Error('Invalid Instance')}
13
+ if (!targetVersion){ console.log(`No version ${args.v} of ${args.app}`); throw new Error('Invalid Version')}
14
+
15
+ // --- I GET AMI ---
16
+ // Get Instance Region
17
+ const targetRegion = targetInstance.region;
18
+ // Get Latest AMI Build
19
+ const latestBuild = targetVersion.baseAMI_Name;
20
+ // Check for AMI Build in Instance Region
21
+ let targetAMI = await EC2.findAMI(latestBuild,targetRegion);
22
+ // Copy AMI from Org Region to Instance Region
23
+ if (!targetAMI){
24
+ console.log(`Copying ${latestBuild} from ${process.env.orgRegion} to ${targetRegion}`);
25
+ targetAMI = await EC2.copyAMI(latestBuild,targetVersion.baseAMI_Id,process.env.orgRegion,targetRegion)
26
+ } else {
27
+ console.log('Found existing image ' + targetAMI)
28
+ }
29
+ // Update Instance CF Params to Instance Region AMI ID
30
+ targetInstance.cfParams.AmiId = targetAMI;
31
+ await Params.editInstance(args.app,args.i,latestBuild,targetInstance.cfParams);
32
+ // console.log(targetInstance.cfParams);
33
+
34
+ // --- II DEPLOY CF STACK ---
35
+ // Get Stack URL
36
+ // https://s3.us-east-2.amazonaws.com/infra.orgtheorem.xyz/apps/ssdemo/1/stack.json
37
+ const stackName = (`${args.app}-${args.i}`).toLowerCase();
38
+ const stackURL = `https://s3.${process.env.orgRegion}.amazonaws.com/${process.env.orgBucket}/apps/${args.app.toLowerCase()}/${args.v}/stack.yaml`
39
+
40
+ // Check whether stack exists
41
+ const stackExists = await CF.stackExists(stackName,targetRegion);
42
+ // console.log(stackName,stackURL,targetInstance.cfParams,targetRegion);
43
+
44
+ // Update Stack
45
+ let stackId;
46
+ if (stackExists){
47
+ console.log(`Updating stack ${stackName} in ${targetRegion}`);
48
+ stackId = await CF.updateStack(stackName,stackURL,targetInstance.cfParams,targetRegion);
49
+ // Deploy Stack
50
+ } else {
51
+ console.log(`Deploying stack ${stackName} in ${targetRegion}`);
52
+ const tags = {purpose: 'app', app: args.app, instance: args.i};
53
+ stackId = await CF.deployS3Stack(stackName,stackURL,targetInstance.cfParams,tags,targetRegion)
54
+ }
55
+ console.log('Deployed stack ' + stackId)
56
+ return true;
57
+ }
@@ -0,0 +1,22 @@
1
+ const Params = require('./helpers/params');
2
+
3
+
4
+ exports.main = async function(){
5
+ const apps = await Params.listApps();
6
+ for(let i=0; i<apps.length; i++){
7
+ const app = apps[i];
8
+ console.log(`${app.name} <${app.stack}>`);
9
+ console.log('\tVERSIONS')
10
+ const spaces = 10;
11
+ Object.entries(app.versions).forEach((k)=>{
12
+ const s = k[0].length > 5 ? 1 : (5 - k[0].length);
13
+ console.log(`\t\t[${k[0]}]${' '.repeat(s)}ami:${k[1].baseAMI_Name} | build: ${k[1].currentBuild} `)
14
+ })
15
+ console.log('\tINSTANCES')
16
+ app.instances.forEach(ins=>{
17
+ const s = ins.name.length > 7 ? 1 : (7 - ins.name.length);
18
+ console.log(`\t\t[${ins.name}]${' '.repeat(s)}domain:${ins.domain} | region:${ins.region} | version:${ins.version}`)
19
+ })
20
+ console.log('--------------')
21
+ }
22
+ }
@@ -0,0 +1,50 @@
1
+ const { S3Client,PutObjectCommand } = require("@aws-sdk/client-s3");
2
+ const fs = require('fs');
3
+ const path = require('path')
4
+ const Params = require('./helpers/params');
5
+ const S3 = require('./helpers/s3');
6
+
7
+ const nodeScript = `npm run start .`
8
+ const pyScript = `py`
9
+
10
+ exports.main = async function(args){
11
+ // Check for existing app
12
+ const existingApp = await Params.getApp(args.name);
13
+ if (existingApp){
14
+ console.log('Err: App already exists ' + args.name);
15
+ throw new Error('App exists')
16
+ }
17
+
18
+ // Check Lang Selection
19
+ if (args.py && args.node){
20
+ console.log('Cannot set both nodejs and python. Select one.');
21
+ throw new Error('Invalid language selection')
22
+ }
23
+
24
+ // Prep Stack
25
+ let bootScript = `#!/bin/bash -xe\ncd /home/ec2-user/app\n`;
26
+ let startScript = args.py ? pyScript : nodeScript;
27
+ bootScript += startScript;
28
+ const stackText = prepStack(args.type,bootScript)
29
+
30
+ // Upload Stack
31
+ console.log(`Uploading ${args.type} to ${process.env.orgBucket}`)
32
+ const stackKey = `apps/${args.name.toLowerCase()}/default_stack.yaml`
33
+ await S3.uploadInfraText(stackKey,stackText);
34
+
35
+ // Update app config
36
+ console.log('Adding app params');
37
+ const nodev = args.node || '';
38
+ const pyv = args.py || '';
39
+ await Params.addApp(args.name,args.type,stackKey,nodev,pyv);
40
+ console.log(`Added ${args.name} with ${args.type} stack`)
41
+ }
42
+
43
+ function prepStack(stackType,bootScript){
44
+ const stackPath = path.resolve(__dirname, 'helpers', 'stacks', `${stackType}.yaml`);
45
+ if (!fs.existsSync(stackPath)){ throw new Error('Invalid stack ' + args.type); }
46
+
47
+ const b64Script = Buffer.from(bootScript, 'utf8').toString('base64');
48
+ const stackTxt = fs.readFileSync(stackPath, 'utf-8').replace(/{{user_data}}/, b64Script)
49
+ return stackTxt;
50
+ }
@@ -0,0 +1,186 @@
1
+ const { EC2Client, DescribeVpcsCommand, DescribeSubnetsCommand } = require("@aws-sdk/client-ec2");
2
+ const { ACMClient, ListCertificatesCommand,RequestCertificateCommand } = require("@aws-sdk/client-acm");
3
+ const { Route53Client, ListHostedZonesByNameCommand } = require("@aws-sdk/client-route-53");
4
+
5
+ const Params = require('./helpers/params')
6
+
7
+ exports.main = async function(args){
8
+ console.log(`Adding ${args.app} instance ${args.name} in ${args.region} @${args.domain}`)
9
+ // Get App
10
+ const existingApp = await Params.getApp(args.app);
11
+ if (!existingApp){
12
+ console.log('Err: No app named ' + args.app);
13
+ throw new Error('Err: No app named ' + args.app)
14
+ }
15
+ // Get Instance
16
+ const existingInstance = existingApp.instances.find(ins=>{ return ins.name.toLowerCase() === args.name.toLowerCase() })
17
+ if (existingInstance){
18
+ console.log('Err: Existing Intance with name ' + args.name);
19
+ throw new Error('Existing Instance')
20
+ }
21
+ // Get Hosted Zone
22
+ const rootDomain = parseDomain(args.domain);
23
+ const hostedZoneId = await getHostedZoneId(rootDomain);
24
+ if (!hostedZoneId){
25
+ console.log('Err: Hosted zone/domain not found ' + rootDomain);
26
+ throw new Error('Domain not found')
27
+ }
28
+
29
+ const instanceParams = {
30
+ name: args.name,
31
+ domain: args.domain,
32
+ region: args.region,
33
+ version: null,
34
+ cfParams: {
35
+ AmiId: null,
36
+ ACMDomainCert: null,
37
+ VpcId: null,
38
+ InstanceSubnets: [],
39
+ InstanceRootDomain: hostedZoneId,
40
+ InstanceDomain: args.domain,
41
+ MaxEc2Instances: args.max || 2
42
+ }
43
+ }
44
+
45
+ // Certificate
46
+ let cert = await getCertificate(args.domain,rootDomain,args.region);
47
+ if (cert === 'none'){
48
+ cert = await requestCert(rootDomain,args.region)
49
+ }
50
+ instanceParams.cfParams.ACMDomainCert = cert;
51
+
52
+ // Get VPC & Subnets
53
+ instanceParams.cfParams.VpcId = await getVPC(args.region);
54
+ instanceParams.cfParams.InstanceSubnets = await getSubnets(args.region)
55
+
56
+ // Update SSM
57
+ console.log('Updating instance params')
58
+ await Params.addInstance(args.app,args.name,instanceParams);
59
+
60
+ console.log(`Added instance ${args.name}`);
61
+ return true;
62
+ }
63
+
64
+ //////////////////////////////////////////////////
65
+ //////////////////////////////////////////////////
66
+ /////////////////////////////////////////////////
67
+
68
+
69
+ function parseDomain(domain){
70
+ const domainArray = domain.split('.');
71
+ const rootDomainName = domainArray.length === 2 ? domainArray[0] : domainArray[domainArray.length-2]
72
+ const rootDomain = rootDomainName + '.'+ domainArray[domainArray.length-1];
73
+ return rootDomain;
74
+ }
75
+
76
+ async function getCertificate(domain,rootDomain,region){
77
+ const acmClient = new ACMClient({ region }); // Replace with your desired region.
78
+
79
+ // Get All Certs
80
+ const certificates = [];
81
+ let params = {};
82
+ do {
83
+ // Call ListCertificatesCommand to list certificates.
84
+ const data = await acmClient.send(new ListCertificatesCommand(params));
85
+
86
+ // Add the listed certificates to the array.
87
+ certificates.push(...(data.CertificateSummaryList || []));
88
+
89
+ // If NextToken is present, set it in params for the next iteration.
90
+ params.NextToken = data.NextToken;
91
+ } while (params.NextToken);
92
+
93
+ // Find Cert
94
+ let arn = null;
95
+ const cert = certificates.find(c=>{ return c.DomainName == rootDomain && c.Status == 'ISSUED'})
96
+ if (cert){
97
+ console.log('Found certificate');
98
+ if (domain == rootDomain){
99
+ arn = cert.CertificateArn;
100
+ } else if (cert.SubjectAlternativeNameSummaries && cert.SubjectAlternativeNameSummaries.includes(`*.${rootDomain}`)){
101
+ arn = cert.CertificateArn;
102
+ } else {
103
+ console.log('Certificate does not cover subdomain ' + domain);
104
+ arn = 'none';
105
+ }
106
+ } else {
107
+ const pendingCert = certificates.find(c=>{ return c.DomainName == rootDomain && c.Status.toLowerCase().includes('validation')});
108
+ if (pendingCert){
109
+ console.log('WARNING: Cert still pending validation ' + pendingCert.Status)
110
+ arn = pendingCert.CertificateArn;
111
+ } else {
112
+ const invalidCert = certificates.find(c=>{ return c.DomainName == rootDomain });
113
+ if (invalidCert){
114
+ console.log('WARNING: Certificate not in valid status: ' + invalidCert.Status)
115
+ console.log('Use renew-certs command to renew')
116
+ arn = invalidCert.CertificateArn;
117
+ } else {
118
+ console.log('No certificate found for ' + domain);
119
+ arn = 'none';
120
+ }
121
+ }
122
+ }
123
+
124
+ return arn;
125
+ }
126
+
127
+ async function requestCert(rootDomain,region){
128
+ console.log(`Requesting new certificate for ${rootDomain} in ${region}`)
129
+ const acmClient = new ACMClient({ region }); // Replace with your desired region.
130
+
131
+ const params = {
132
+ DomainName: rootDomain,
133
+ ValidationMethod: "DNS",
134
+ SubjectAlternativeNames: [ `*.${rootDomain}` ],
135
+ };
136
+
137
+ const data = await acmClient.send(new RequestCertificateCommand(params));
138
+ return data.CertificateArn;
139
+ }
140
+
141
+ async function getVPC(region){
142
+ console.log('Getting default VPC ID')
143
+ // Initialize an Amazon EC2 client object.
144
+ const ec2Client = new EC2Client({ region }); // replace with your desired region
145
+ const data = await ec2Client.send(new DescribeVpcsCommand({}));
146
+
147
+ // Find the default VPC from the list of VPCs.
148
+ const defaultVpc = data.Vpcs.find(vpc => vpc.IsDefault);
149
+
150
+ // Return the VPC ID if the default VPC is found.
151
+ return defaultVpc.VpcId;
152
+ }
153
+
154
+ async function getSubnets(region){
155
+ console.log('Retrieving Subnets')
156
+ const ec2Client = new EC2Client({ region });
157
+ const subnetsData = await ec2Client.send(new DescribeSubnetsCommand({}));
158
+ const subNets = subnetsData.Subnets.filter(s=>{ return s.DefaultForAz === true });
159
+ const subnetList = subNets.map(subnet => subnet.SubnetId );
160
+ return subnetList;
161
+ }
162
+
163
+ async function getHostedZoneId(hostedZoneName){
164
+ hostedZoneName += '.';
165
+ const client = new Route53Client({ region: "us-east-1" });
166
+ const command = new ListHostedZonesByNameCommand({ DNSName: hostedZoneName });
167
+ let response;
168
+ try {
169
+ response = await client.send(command);
170
+ } catch(e){
171
+ console.log(e);
172
+ return false;
173
+ }
174
+
175
+
176
+ // Check if we have the desired hosted zone in the response
177
+ if (response.HostedZones && response.HostedZones.length > 0) {
178
+ for (let zone of response.HostedZones) {
179
+ if (zone.Name === hostedZoneName) {
180
+ return zone.Id.split("/").pop(); // Extracting the ID part from the full ARN
181
+ }
182
+ }
183
+ } else {
184
+ return false;
185
+ }
186
+ }
@@ -0,0 +1,28 @@
1
+ const S3 = require('./helpers/s3')
2
+ const Params= require('./helpers/params');
3
+ const Stacks = require('./helpers/stacks')
4
+
5
+ exports.main = async function(args){
6
+ // Get App
7
+ const app = await Params.getApp(args.app);
8
+ if (!app){
9
+ console.log('Err: No app named ' + args.app);
10
+ throw new Error('Err: No app named ' + args.app)
11
+ }
12
+ // Get Stack
13
+ const stackText = Stacks.get('asg', {lang: 'node'});
14
+ const stackKey = `apps/${args.app.toLowerCase()}/default_stack.yaml`
15
+
16
+ // Reset Default
17
+ console.log('Resetting default stack @ ', stackKey)
18
+ await S3.uploadInfraText(stackKey,stackText);
19
+
20
+ if (!app.versions){ return }
21
+ const versions = Object.keys(app.versions);
22
+ for (let i=0; i<versions.length; i++){
23
+ let k = versions[i]
24
+ let vStackPath = `apps/${args.app.toLowerCase()}/${k}/stack.yaml`
25
+ console.log('Resetting v',k, '@', vStackPath);
26
+ await S3.uploadInfraText(vStackPath,stackText);
27
+ }
28
+ }
@@ -0,0 +1,272 @@
1
+ const path = require('path');
2
+ const fs = require('fs')
3
+ const { EC2Client, RunInstancesCommand,CreateImageCommand,TerminateInstancesCommand,DescribeInstanceStatusCommand,DeregisterImageCommand,DescribeImagesCommand,CopyImageCommand } = require("@aws-sdk/client-ec2");
4
+
5
+ const AdmZip = require("adm-zip");
6
+
7
+ const Params = require('./helpers/params')
8
+ const S3 = require('./helpers/s3');
9
+
10
+
11
+ const INSTANCE_TYPE="t2.micro"
12
+
13
+ exports.main = async function(args){
14
+ console.log(`Updating ${args.app} v${args.v}`)
15
+
16
+ // Get App
17
+ const app = await Params.getApp(args.app);
18
+ if (!app){
19
+ console.log('Err: No app named ' + args.app);
20
+ throw new Error('Err: No app named ' + args.app)
21
+ }
22
+
23
+ // --- I PREP ZIP ---
24
+ const zipPath = path.resolve(args.path);
25
+ if (!fs.existsSync(zipPath)){ throw new Error("Path not found:" + args.path)}
26
+ const zipFilePath = await prepZip(zipPath);
27
+ await S3.uploadInfraFile(`apps/${args.app}/${args.v}/app.zip`,zipFilePath);
28
+
29
+ // --- II UPDATE STACK ---
30
+ // If stack arg, upload stack
31
+ const stackKey = `apps/${args.app}/${args.v}/stack.yaml`;
32
+ if (args.stack){
33
+ console.log(`Upating stack: ${stackPath} to ${stackKey}`)
34
+ const stackPath = path.resolve(args.stack);
35
+ if (!fs.existsSync(stackPath)){ throw new Error("Stack file not found:" + args.stack)}
36
+ await S3.uploadInfraFile(stackKey,stackPath)
37
+ } else {
38
+ // If no stack arg, upload default stack if none exists
39
+ const stackExists = await S3.infraFileExists(stackKey)
40
+ if (!stackExists){
41
+ console.log('Using default stack');
42
+ await S3.copyInfraFile(app.stackKey,stackKey)
43
+ } else {
44
+ console.log('Skipping stack update')
45
+ }
46
+ }
47
+
48
+ // --- III BUILD IMAGE ---
49
+ // Launch ec2
50
+ const orgParams = await Params.getOrgConfig();
51
+
52
+ const awsLinuxAMI = await findLinuxAMI(process.env.orgRegion);
53
+ const instance_id = await launchInstance({
54
+ app: app.name,
55
+ linuxAMI: awsLinuxAMI,
56
+ version: args.v,
57
+ sec_group: orgParams.buildSecGroup,
58
+ iam: orgParams.buildInstanceProfile
59
+ });
60
+
61
+ await waitUntilInstanceReady(instance_id,process.env.orgRegion);
62
+
63
+ // Create AMI
64
+ const buildNumber = (app.versions[args.v]?.currentBuild || 0) + 1;
65
+ const appVID = `${app.name}-v${args.v}.${buildNumber}`;
66
+ console.log(appVID);
67
+
68
+ var success = false;
69
+ let ami_id;
70
+ try {
71
+ ami_id = await createAMI(instance_id, appVID,process.env.orgRegion)
72
+ success = true;
73
+ } catch(e){
74
+ console.log("Error Creating AMI:" + e)
75
+ }
76
+ await terminateInstance(instance_id,process.env.orgRegion)
77
+ if (success === false){ throw new Error("Error - Build Not Complete") }
78
+
79
+ // --- IV UPDATE PARAMS ---
80
+ const versionInfo = {
81
+ baseAMI_Name: appVID,
82
+ baseAMI_Id: ami_id,
83
+ currentBuild: buildNumber,
84
+ updated: Date.now()
85
+ }
86
+ await Params.updateAppV(app.name,args.v,versionInfo);
87
+ return true;
88
+ }
89
+
90
+
91
+
92
+ ///////////////////////////////////////////////
93
+ ///////////////////////////////////////////////
94
+ ///////////////////////////////////////////////
95
+
96
+ async function prepZip(appPath){
97
+ console.log('Zipping ' + appPath);
98
+ const inPath = path.resolve(appPath);
99
+ let zipPath = path.resolve(`./app.zip`);
100
+
101
+ const pathStat = fs.statSync(inPath);
102
+ // If dir, zip
103
+ if (!pathStat.isFile()){
104
+ const zip = new AdmZip();
105
+ zip.addLocalFolder(inPath);
106
+ zip.writeZip(zipPath);
107
+ } else {
108
+ // If not zip, throw error
109
+ if (path.extname(inPath) !== '.zip'){
110
+ console.log('ERROR:Not a .zip file >>' + inPath)
111
+ throw 'ERROR:Not a .zip file >>' + inPath;
112
+ }
113
+ // Copy .zip file
114
+ fs.copyFileSync(inPath,zipPath);
115
+ }
116
+ process.on('exit', function(){ fs.unlinkSync(zipPath) });
117
+ return zipPath;
118
+ }
119
+
120
+ async function findLinuxAMI(region){
121
+ // aws ec2 describe-images --owners amazon --filters "Name=name,Values=amzn*" --query 'sort_by(Images, &CreationDate)[].Name'
122
+ console.log('Locating AMI for AWS Linux in ' + region)
123
+ const client = new EC2Client({region});
124
+ const input = { // DescribeImagesRequest
125
+ Owners: ['amazon'],
126
+ Filters: [
127
+ {
128
+ Name: "name",
129
+ Values: ["al2023-ami-2023.2.20230920.1-kernel-6.1-x86_64"], // Pattern for Amazon Linux 2 AMI
130
+ },
131
+ {
132
+ Name: "state",
133
+ Values: ["available"],
134
+ },
135
+ {
136
+ Name: "architecture",
137
+ Values: [ "x86_64" ],
138
+ },
139
+ {
140
+ Name: "virtualization-type",
141
+ Values: ["hvm"]
142
+ }
143
+ ],
144
+ // al2023-ami-2023.2.20230920.1-kernel-6.1-x86_64
145
+ // ImageIds: ['ami-03a6eaae9938c858c'],
146
+ IncludeDeprecated: false,
147
+ DryRun: false,
148
+ // MaxResults: 16
149
+ };
150
+ const command = new DescribeImagesCommand(input);
151
+ const response = await client.send(command);
152
+ const images = response.Images;
153
+
154
+ if (!images[0]){
155
+ console.log('Could not find AWS Linux 2023 base image')
156
+ throw 'Could not find AWS Linux 2023 base image'
157
+ };
158
+
159
+ return images[0].ImageId;
160
+ }
161
+
162
+ async function launchInstance(launchParams){
163
+ console.log('Launching Instance in ' + process.env.orgRegion);
164
+
165
+ const user_data = [
166
+ `#!/bin/bash -xe`,
167
+ `yum -y install nodejs`,
168
+ `yum -y install unzip`,
169
+ `cd /home/ec2-user`,
170
+ `aws s3 cp s3://${process.env.orgBucket}/apps/${launchParams.app.toLowerCase()}/${launchParams.version}/app.zip .`,
171
+ `unzip app.zip -d app`,
172
+ `rm -r app.zip`
173
+ ].join('\n')
174
+
175
+ const ud_b64 = Buffer.from(user_data).toString('base64');
176
+
177
+ const client = new EC2Client({region: process.env.orgRegion });
178
+
179
+ const createInstanceParams = {
180
+ ImageId: launchParams.linuxAMI,
181
+ InstanceType: INSTANCE_TYPE,
182
+ SecurityGroupIds: [
183
+ launchParams.sec_group
184
+ ],
185
+ MinCount: 1,
186
+ MaxCount: 1,
187
+ UserData: ud_b64,
188
+ IamInstanceProfile: {
189
+ Arn: launchParams.iam
190
+ }
191
+ };
192
+ const command = new RunInstancesCommand(createInstanceParams);
193
+ const response = await client.send(command);
194
+ const instance_id = response.Instances[0].InstanceId
195
+
196
+ console.log('Instance Launched:',instance_id);
197
+ return instance_id;
198
+ }
199
+
200
+ async function waitUntilInstanceReady(instance_id,region){
201
+ console.log(`Awaiting ${instance_id} status of ok`)
202
+ const client = new EC2Client({region});
203
+ const input = { // DescribeInstanceStatusRequest
204
+ InstanceIds: [ // InstanceIdStringList
205
+ instance_id
206
+ ],
207
+ DryRun: false,
208
+ IncludeAllInstances: true
209
+ };
210
+
211
+ let totalSleepTime = 0;
212
+ let ok = false;
213
+ const command = new DescribeInstanceStatusCommand(input);
214
+ for (let i=0; i<50; i++){
215
+ const response = await client.send(command);
216
+ const status = response.InstanceStatuses[0].InstanceStatus.Status;
217
+ console.log(`\tCheck ${i+1} @ ${totalSleepTime}s: EC2 Status is ${status}`)
218
+ if (status !== 'ok'){
219
+ await sleep(10000);
220
+ totalSleepTime += 10;
221
+ } else {
222
+ console.log('Ec2 Instance Ready:' + status);
223
+ ok = true;
224
+ break;
225
+ }
226
+ }
227
+
228
+ if (ok === false){
229
+ console.log('ERR:::', `Ec2 Instance Not Ready After ${totalSleepTime}s`)
230
+ throw `Ec2 Instance Not Ready After ${totalSleepTime}s`
231
+ } else {
232
+ console.log(`Instance Ready After ${totalSleepTime}s. Waiting 30s to Proceed`);
233
+ await sleep(30);
234
+ }
235
+ return true;
236
+ }
237
+
238
+ async function createAMI(instance_id,image_name,region){
239
+ console.log(`Building ${image_name} in ${region}`)
240
+ const client = new EC2Client({region});
241
+ const input = { // CreateImageRequest
242
+ Description: `Base Application Image`,
243
+ DryRun: false,
244
+ InstanceId: instance_id, // required
245
+ Name: image_name, // required
246
+ NoReboot: true
247
+ };
248
+ const command = new CreateImageCommand(input);
249
+ const response = await client.send(command);
250
+ console.log(`Created Image ${image_name} ID:${response.ImageId}`)
251
+ return response.ImageId;
252
+ }
253
+
254
+ async function terminateInstance(instance_id,region){
255
+ console.log('Terminating Instance ' + instance_id)
256
+ const client = new EC2Client({region});
257
+ const input = { // TerminateInstancesRequest
258
+ InstanceIds: [ instance_id ],
259
+ DryRun: false,
260
+ };
261
+ const command = new TerminateInstancesCommand(input);
262
+ const response = await client.send(command);
263
+ return true;
264
+ }
265
+
266
+
267
+ async function sleep(time){
268
+ return new Promise(function (resolve, reject) {
269
+ setTimeout(function () { resolve(true);
270
+ }, time);
271
+ });
272
+ }