cloudmason 0.0.1 → 1.0.2

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.
Files changed (46) hide show
  1. package/.github/workflows/CODEOWNERS +1 -0
  2. package/.github/workflows/main.yml +27 -27
  3. package/README.md +373 -25
  4. package/build.js +20 -20
  5. package/commands/delete.js +67 -28
  6. package/commands/helpers/cf.js +181 -117
  7. package/commands/helpers/common.js +82 -0
  8. package/commands/helpers/ec2.js +154 -40
  9. package/commands/helpers/params.js +231 -178
  10. package/commands/helpers/s3.js +186 -67
  11. package/commands/helpers/stacks/asg.yaml +420 -224
  12. package/commands/helpers/stacks/infra.yaml +102 -106
  13. package/commands/helpers/stacks.js +25 -25
  14. package/commands/index.html +22 -0
  15. package/commands/init_org.js +54 -61
  16. package/commands/inspect.js +40 -0
  17. package/commands/launch_app.js +80 -57
  18. package/commands/list_apps.js +21 -21
  19. package/commands/new_app.js +44 -50
  20. package/commands/new_instance.js +133 -186
  21. package/commands/reset_stack.js +27 -27
  22. package/commands/starter.js +21 -0
  23. package/commands/starters/asg_node/index.js +62 -0
  24. package/commands/starters/asg_node/mason.txt +1 -0
  25. package/commands/starters/asg_node/modules/appConfig.js +131 -0
  26. package/commands/starters/asg_node/package-lock.json +5877 -0
  27. package/commands/starters/asg_node/package.json +23 -0
  28. package/commands/starters/asg_node/public/css/favicon-16x16.png +0 -0
  29. package/commands/starters/asg_node/public/css/fonts/Lato-Bold.ttf +0 -0
  30. package/commands/starters/asg_node/public/css/fonts/Lato-Regular.ttf +0 -0
  31. package/commands/starters/asg_node/public/css/fonts/Montserrat-Var.ttf +0 -0
  32. package/commands/starters/asg_node/public/css/fonts/OpenSans.ttf +0 -0
  33. package/commands/starters/asg_node/public/css/fonts/bpmn.woff2 +0 -0
  34. package/commands/starters/asg_node/public/css/fonts/fonts.css +17 -0
  35. package/commands/starters/asg_node/public/css/index.css +9 -0
  36. package/commands/starters/asg_node/public/index.html +15 -0
  37. package/commands/starters/asg_node/public/js/index.js +5 -0
  38. package/commands/starters/asg_node/start.sh +4 -0
  39. package/commands/update_app.js +235 -272
  40. package/commands/update_stack.js +27 -0
  41. package/commands/utils.js +32 -32
  42. package/main.js +262 -220
  43. package/package.json +1 -28
  44. package/test.bat +16 -9
  45. package/commands/delete_app.js +0 -28
  46. package/commands/helpers/stacks/asg_draft.json +0 -321
@@ -1,118 +1,182 @@
1
- const { CloudFormationClient, CreateStackCommand,UpdateStackCommand, DeleteStackCommand, ValidateTemplateCommand,DescribeStacksCommand } = require('@aws-sdk/client-cloudformation');
2
-
3
- const fs = require('fs');
4
- const path = require('path')
5
-
6
-
7
- exports.deployOrgStack = async function(stackName,stackType,region,params,tag){
8
- // Read Stack
9
- const stackPath = path.resolve(__dirname,'stacks',`${stackType.toLowerCase()}.yaml`);
10
- if (!fs.existsSync(stackPath)){
11
- console.log('Invalid stack type:' + stackType);
12
- throw { message: 'Invalid stack type', at: 'deployStack'}
13
- }
14
- const stackYML = fs.readFileSync(stackPath,'utf-8');
15
- console.log
16
- // Build Params
17
- const cfParams = Object.keys(params).map(k=>{ return { ParameterKey: k, ParameterValue: params[k] } })
18
- const cfTags = Object.keys(tag).map(k=>{ return { Key: k, Value: tag[k] } })
19
- // Deploy Stack
20
- const client = new CloudFormationClient({ region });
21
- const input = {
22
- StackName: stackName,
23
- TemplateBody: stackYML,
24
- Capabilities: [
25
- "CAPABILITY_IAM" || "CAPABILITY_NAMED_IAM" || "CAPABILITY_AUTO_EXPAND",
26
- ],
27
- Parameters: cfParams,
28
- Tags: cfTags
29
- };
30
- try{
31
- const result = await client.send(new CreateStackCommand(input));
32
- return result.StackId;
33
- } catch (e){
34
- if (/AlreadyExistsException/.test(e)){
35
- console.log('Stack exists ' + stackName);
36
- return false;
37
- } else {
38
- throw new Error(e.message)
39
- }
40
- }
41
- }
42
-
43
- exports.deployS3Stack = async function(stackName,s3Url,params,tag,region){
44
- // Build Params
45
- const cfParams = Object.keys(params).map(k=>{ return { ParameterKey: k, ParameterValue: params[k] } })
46
- const cfTags = Object.keys(tag).map(k=>{ return { Key: k, Value: tag[k] } })
47
- // Deploy Stack
48
- const client = new CloudFormationClient({ region });
49
- const input = {
50
- StackName: stackName,
51
- TemplateURL: s3Url,
52
- Capabilities: [
53
- "CAPABILITY_IAM" || "CAPABILITY_NAMED_IAM" || "CAPABILITY_AUTO_EXPAND",
54
- ],
55
- Parameters: cfParams,
56
- Tags: cfTags
57
- };
58
-
59
- const result = await client.send(new CreateStackCommand(input));
60
- return result.StackId;
61
- }
62
-
63
- exports.updateStack = async function(stackName,s3Url,params,region){
64
- const client = new CloudFormationClient({ region });
65
- const cfParams = Object.keys(params).map(k=>{ return { ParameterKey: k, ParameterValue: params[k] } })
66
- console.log(s3Url)
67
- const cmd = {
68
- StackName: stackName,
69
- TemplateURL: s3Url,
70
- Parameters: cfParams,
71
- Capabilities: ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"]
72
- };
73
- const command = new UpdateStackCommand(cmd);
74
- const response = await client.send(command);
75
- return true;
76
- }
77
-
78
- exports.validateStack = async function(stack){
79
- const client = new CloudFormationClient({ region: process.env.orgRegion });
80
-
81
- const command = new ValidateTemplateCommand({
82
- TemplateBody: stack,
83
- });
84
-
85
- try{
86
- const data = await client.send(command);
87
- return { ok: true, data: data }
88
- } catch (e){
89
- return {ok: false, data: e}
90
- }
91
- }
92
-
93
- exports.stackExists = async function(stackName,region){
94
- const client = new CloudFormationClient({ region });
95
- const command = new DescribeStacksCommand({ StackName: stackName });
96
- try {
97
-
98
- const response = await client.send(command);
99
- return true;
100
- } catch (error) {
101
- if (error.name === 'ValidationError') {
102
- return false;
103
- }
104
- throw error;
105
- }
106
- }
107
-
108
- exports.delete = async function(stackName,region){
109
- const client = new CloudFormationClient({ region });
110
- try {
111
- const command = new DeleteStackCommand({ StackName: stackName });
112
- const response = await client.send(command);
113
- return true;
114
- } catch (error) {
115
- console.error("Error deleting the stack:", error);
116
- return false;
117
- }
1
+ const { CloudFormationClient,ListStackResourcesCommand, CreateStackCommand,UpdateStackCommand, DeleteStackCommand, ValidateTemplateCommand,DescribeStacksCommand } = require('@aws-sdk/client-cloudformation');
2
+
3
+ const fs = require('fs');
4
+ const path = require('path')
5
+
6
+
7
+ exports.deployOrgStack = async function(region,params){
8
+ // Read Stack
9
+ const stackPath = path.resolve(__dirname,'stacks',`infra.yaml`);
10
+ if (!fs.existsSync(stackPath)){
11
+ console.log('Invalid stack type:' + stackType);
12
+ throw { message: 'Invalid stack type', at: 'deployStack'}
13
+ }
14
+ const stackYML = fs.readFileSync(stackPath,'utf-8');
15
+
16
+ // Build Params
17
+ const cfParams = Object.keys(params).map(k=>{ return { ParameterKey: k, ParameterValue: params[k] } })
18
+
19
+ // Deploy Stack
20
+ const client = new CloudFormationClient({ region });
21
+ const input = {
22
+ StackName: `CoreInfra`,
23
+ TemplateBody: stackYML,
24
+ Capabilities: [
25
+ "CAPABILITY_IAM" || "CAPABILITY_NAMED_IAM" || "CAPABILITY_AUTO_EXPAND",
26
+ ],
27
+ Parameters: cfParams,
28
+ Tags: [{ Key: 'purpose', Value: 'infra' }]
29
+ };
30
+ try{
31
+ const result = await client.send(new CreateStackCommand(input));
32
+ return result.StackId;
33
+ } catch (e){
34
+ if (/AlreadyExistsException/.test(e)){
35
+ console.log('Stack exists ' + stackName);
36
+ return false;
37
+ } else {
38
+ throw new Error(e.message)
39
+ }
40
+ }
41
+ }
42
+
43
+ exports.deployS3Stack = async function(stackName,s3Url,params,tag,region){
44
+ // Build Params
45
+ const cfParams = Object.keys(params).map(k=>{ return { ParameterKey: k, ParameterValue: params[k] } })
46
+ const cfTags = Object.keys(tag).map(k=>{ return { Key: k, Value: tag[k] } })
47
+
48
+ // Deploy Stack
49
+ const client = new CloudFormationClient({ region });
50
+ const input = {
51
+ StackName: stackName,
52
+ TemplateURL: s3Url,
53
+ OnFailure: 'DELETE',
54
+ Capabilities: [
55
+ "CAPABILITY_IAM" || "CAPABILITY_NAMED_IAM" || "CAPABILITY_AUTO_EXPAND",
56
+ ],
57
+ Parameters: cfParams,
58
+ Tags: cfTags
59
+ };
60
+
61
+ const result = await client.send(new CreateStackCommand(input));
62
+ return result.StackId;
63
+ }
64
+
65
+ exports.updateStack = async function(stackName,s3Url,params,region){
66
+ const client = new CloudFormationClient({ region });
67
+ const cfParams = Object.keys(params).map(k=>{ return { ParameterKey: k, ParameterValue: params[k] } })
68
+ const cmd = {
69
+ StackName: stackName,
70
+ TemplateURL: s3Url,
71
+ Parameters: cfParams,
72
+ Capabilities: ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"]
73
+ };
74
+ const command = new UpdateStackCommand(cmd);
75
+ const response = await client.send(command);
76
+ return response.StackId;
77
+ }
78
+
79
+ exports.validateStack = async function(stack){
80
+ const client = new CloudFormationClient({ region: process.env.orgRegion });
81
+
82
+ const command = new ValidateTemplateCommand({
83
+ TemplateBody: stack,
84
+ });
85
+
86
+ try{
87
+ const data = await client.send(command);
88
+ return { ok: true, data: data }
89
+ } catch (e){
90
+ return {ok: false, data: e}
91
+ }
92
+ }
93
+
94
+ exports.stackExists = async function(stackName,region){
95
+ const client = new CloudFormationClient({ region });
96
+ const command = new DescribeStacksCommand({ StackName: stackName });
97
+ try {
98
+
99
+ const response = await client.send(command);
100
+ return true;
101
+ } catch (error) {
102
+ if (error.name === 'ValidationError') {
103
+ return false;
104
+ }
105
+ throw error;
106
+ }
107
+ }
108
+
109
+ exports.stackStatus = async function(stackName, region) {
110
+ const client = new CloudFormationClient({ region }); // Replace with your region
111
+ try {
112
+ const command = new DescribeStacksCommand({ StackName: stackName });
113
+ const response = await client.send(command);
114
+
115
+ if (response.Stacks && response.Stacks.length > 0) {
116
+ const stack = response.Stacks[0];
117
+ const status = stack.StackStatus;
118
+
119
+ // Check if status indicates failure
120
+ if (status.includes("FAIL") || status === "DELETE_FAILED") {
121
+ return {
122
+ status: status,
123
+ ok: false,
124
+ failureReason: stack.StackStatusReason
125
+ };
126
+ } else if (status.includes("CREATE_IN_PROGRESS")){
127
+ return {
128
+ status: status,
129
+ ok: null
130
+ };
131
+ } else {
132
+ return {
133
+ status: status,
134
+ ok: true
135
+ };
136
+ }
137
+ } else {
138
+ throw new Error("No stack found with the specified name.");
139
+ }
140
+ } catch (error) {
141
+ console.error("Error fetching CloudFormation stack status:", error);
142
+ throw error;
143
+ }
144
+ }
145
+
146
+ exports.getStackResource = async function(resourceType,stackName,region){
147
+ const res = {
148
+ 's3': "AWS::S3::Bucket",
149
+ 'asg': "AWS::AutoScaling::AutoScalingGroup",
150
+ 'ec2': "AWS::EC2::Instance"
151
+ }[resourceType];
152
+ const client = new CloudFormationClient({ region }); // specify your region
153
+
154
+ try {
155
+ const command = new ListStackResourcesCommand({ StackName: stackName });
156
+ const response = await client.send(command);
157
+
158
+ // Filter the results to find the Auto Scaling group
159
+ const asgResource = response.StackResourceSummaries.find(resource => resource.ResourceType === res);
160
+
161
+ if (asgResource) {
162
+ return asgResource.PhysicalResourceId;
163
+ } else {
164
+ throw new Error("Auto Scaling Group not found in the specified stack.");
165
+ }
166
+ } catch (error) {
167
+ console.error("Error fetching Auto Scaling Group ID:", error);
168
+ throw error;
169
+ }
170
+ }
171
+
172
+ exports.delete = async function(stackName,region){
173
+ const client = new CloudFormationClient({ region });
174
+ try {
175
+ const command = new DeleteStackCommand({ StackName: stackName });
176
+ const response = await client.send(command);
177
+ return true;
178
+ } catch (error) {
179
+ console.error("Error deleting the stack:", error);
180
+ return false;
181
+ }
118
182
  }
@@ -0,0 +1,82 @@
1
+ const Params = require('./params');
2
+ const EC2 = require('./ec2');
3
+
4
+
5
+ exports.prune_amis = async function(appName,version,region,removeLatest){
6
+ // Removal Func
7
+ const removeUnused = async function(aN,v,r){
8
+ const app = await Params.getApp(appName);
9
+ const latestBuildAMI = app.versions[version].baseAMI_Name;
10
+
11
+ // All AMIs
12
+ const AMIs = await EC2.listAMIs(`${appName}-v${version}`,region);
13
+ // console.log(`Found ${AMIs.length} AMIs in ${region}`)
14
+
15
+ // Used AMIs
16
+ const regionInstances = app.instances.filter(i=>{ return i.region === region });
17
+ const usedAMIs = regionInstances.map(i=>{ return i.amiName });
18
+ // console.log(`Found ${usedAMIs.length} used AMIs in ${region}`)
19
+
20
+
21
+ // Remove Unused AMI from Target Region
22
+ let unusedAMIs = AMIs.filter(ami=>{ return !usedAMIs.includes(ami.Name) });
23
+ if (removeLatest !== true){
24
+ console.log('Retaining latest build ', latestBuildAMI, 'in ', region)
25
+ unusedAMIs = unusedAMIs.filter(ami=>{ return ami.Name !== latestBuildAMI })
26
+ } else {
27
+ console.log('Removing latest build ', latestBuildAMI, 'in ', region)
28
+ }
29
+ // console.log(`Found ${unusedAMIs.length} unused AMIs in ${region}`);
30
+
31
+ for (const ami of unusedAMIs){
32
+ console.log(`Deregistering ${ami.Name} in ${region}`)
33
+ await EC2.deleteAMI(ami.ImageId,region);
34
+ }
35
+ }
36
+ // Remove from target region
37
+ await removeUnused(appName,version,region);
38
+ // Remove from org region
39
+ await removeUnused(appName,version,process.env.orgRegion);
40
+ }
41
+
42
+ exports.remove_app_amis = async function(appName,region){
43
+ // All AMIs
44
+ const AMIs = await EC2.listAMIs(`${appName}-v`,region);
45
+ for (const ami of AMIs){
46
+ console.log(`Deregistering ${ami.Name} in ${region}`)
47
+ await EC2.deleteAMI(ami.ImageId,region);
48
+ }
49
+ }
50
+
51
+ exports.sleep = async function(s){
52
+ s = s*1000;
53
+ return new Promise(function (resolve, reject) {
54
+ setTimeout(function () { resolve(true);
55
+ }, s);
56
+ });
57
+ }
58
+
59
+ exports.formatDate = function(utcMillis){
60
+ let date;
61
+ if (typeof(utcMillis) === 'string'){
62
+ date = new Date(utcMillis);
63
+ } else {
64
+ date = new Date(utcMillis);
65
+ }
66
+
67
+ // Extracting individual components in local time
68
+ const month = date.getMonth() + 1; // getMonth returns month from 0-11
69
+ const day = date.getDate();
70
+ const year = date.getFullYear().toString().substr(-2); // Get last two digits of the year
71
+ const hours = date.getHours();
72
+ const minutes = date.getMinutes();
73
+
74
+ // Formatting each component to ensure two digits
75
+ const formattedMonth = month.toString().padStart(2, '0');
76
+ const formattedDay = day.toString().padStart(2, '0');
77
+ const formattedHours = hours.toString().padStart(2, '0');
78
+ const formattedMinutes = minutes.toString().padStart(2, '0');
79
+
80
+ // Constructing the final string
81
+ return `${formattedMonth}-${formattedDay}-${year} ${formattedHours}:${formattedMinutes}`;
82
+ }
@@ -1,40 +1,154 @@
1
- const { EC2Client, RunInstancesCommand,CreateImageCommand,TerminateInstancesCommand,DescribeInstanceStatusCommand,DeregisterImageCommand,DescribeImagesCommand,CopyImageCommand } = require("@aws-sdk/client-ec2");
2
-
3
- exports.findAMI = async function(image_name,region){
4
- const client = new EC2Client({ region });
5
- const input = { // DescribeImagesRequest
6
- Filters: [ // FilterList
7
- { // Filter
8
- Name: "name",
9
- Values: [ // ValueStringList
10
- image_name,
11
- ],
12
- },
13
- ],
14
- IncludeDeprecated: true,
15
- DryRun: false,
16
- MaxResults: 6
17
- };
18
- const command = new DescribeImagesCommand(input);
19
- const response = await client.send(command);
20
- const images = response.Images;
21
- if (!images[0]){
22
- console.log('No existing image with name:' + image_name)
23
- return false;
24
- };
25
- return images[0].ImageId;
26
- }
27
-
28
- exports.copyAMI = async function(image_name,src_ami,src_region,dest_region){
29
-
30
- const destinationEc2Client = new EC2Client({ region: dest_region });
31
-
32
- const copyImageCommand = new CopyImageCommand({
33
- SourceRegion: src_region,
34
- SourceImageId: src_ami,
35
- Name: image_name,
36
- });
37
-
38
- const { ImageId } = await destinationEc2Client.send(copyImageCommand);
39
- return ImageId;
40
- }
1
+ const { EC2Client, GetConsoleOutputCommand, RunInstancesCommand,CreateImageCommand,TerminateInstancesCommand,DescribeInstanceStatusCommand,DeregisterImageCommand,DescribeImagesCommand,CopyImageCommand } = require("@aws-sdk/client-ec2");
2
+ const { AutoScalingClient, DescribeAutoScalingGroupsCommand } = require("@aws-sdk/client-auto-scaling");
3
+
4
+ exports.findAMI = async function(image_name,region){
5
+ const client = new EC2Client({ region });
6
+ const input = { // DescribeImagesRequest
7
+ Filters: [ // FilterList
8
+ { // Filter
9
+ Name: "name",
10
+ Values: [ // ValueStringList
11
+ image_name,
12
+ ],
13
+ },
14
+ ],
15
+ IncludeDeprecated: true,
16
+ DryRun: false,
17
+ MaxResults: 6
18
+ };
19
+ const command = new DescribeImagesCommand(input);
20
+ const response = await client.send(command);
21
+ const images = response.Images;
22
+ if (!images[0]){
23
+ console.log('No existing image with name:' + image_name)
24
+ return false;
25
+ };
26
+ return images[0].ImageId;
27
+ }
28
+
29
+ exports.awsLinuxAMI = function(region){
30
+ const ami = {
31
+ "us-east-1": "ami-0759f51a90924c166",
32
+ "us-east-2": "ami-048e636f368eb3006",
33
+ "us-west-1": "ami-0a07b0077b66673f1",
34
+ "us-west-2": "ami-0c00eacddaea828c6",
35
+ "ap-east-1": "",
36
+ "ap-south-1": "",
37
+ "ap-northeast-2": "",
38
+ "ap-southeast-1": "",
39
+ "ap-southeast-2": "",
40
+ "ap-northeast-1": "",
41
+ "ca-central-1": "ami-02d34aedb8fa9c346",
42
+ "eu-central-1": "",
43
+ "eu-west-1": "",
44
+ "eu-west-2": "",
45
+ "eu-west-3": "",
46
+ "eu-north-1": "",
47
+ "me-south-1": "",
48
+ "sa-east-1": "ami-0f4e579ad17e32ab7"
49
+ }[region];
50
+ if (!ami){ throw 'No AMI found for region ' + region };
51
+ return ami;
52
+ }
53
+
54
+ exports.listAMIs = async function(image_name,region){
55
+ const client = new EC2Client({ region });
56
+ image_name = image_name + '*';
57
+ const input = { // DescribeImagesRequest
58
+ Filters: [ // FilterList
59
+ { // Filter
60
+ Name: "name",
61
+ Values: [ // ValueStringList
62
+ image_name,
63
+ ],
64
+ }
65
+ ],
66
+ Owners: [ // OwnerStringList
67
+ "self",
68
+ ],
69
+ IncludeDeprecated: false,
70
+ IncludeDisabled: false,
71
+ DryRun: false,
72
+ MaxResults: 6
73
+ };
74
+ const command = new DescribeImagesCommand(input);
75
+ const response = await client.send(command);
76
+ if (response.Images){
77
+ return response.Images.map(i=>{ return { ImageId: i.ImageId, Name: i.Name } });
78
+ } else {
79
+ return [];
80
+ }
81
+ }
82
+
83
+ exports.copyAMI = async function(image_name,src_ami,src_region,dest_region){
84
+
85
+ const destinationEc2Client = new EC2Client({ region: dest_region });
86
+
87
+ const copyImageCommand = new CopyImageCommand({
88
+ SourceRegion: src_region,
89
+ SourceImageId: src_ami,
90
+ Name: image_name,
91
+ });
92
+
93
+ const { ImageId } = await destinationEc2Client.send(copyImageCommand);
94
+ return ImageId;
95
+ }
96
+
97
+ exports.deleteAMI = async function(image_id,region){
98
+ const client = new EC2Client({ region });
99
+ const input = {
100
+ ImageId: image_id,
101
+ DryRun: false
102
+ };
103
+ const command = new DeregisterImageCommand(input);
104
+ const response = await client.send(command);
105
+ return response;
106
+ }
107
+
108
+ exports.checkAMIStatus = async function(image_id,region){
109
+ const client = new EC2Client({ region }); // Replace 'your-region' with your AWS region
110
+
111
+ const command = new DescribeImagesCommand({
112
+ ImageIds: [image_id]
113
+ });
114
+
115
+ const response = await client.send(command);
116
+ if (response.Images && response.Images.length > 0 && response.Images[0].State.toLowerCase() === 'available') {
117
+ return true;
118
+ } else {
119
+ return false;
120
+ }
121
+
122
+ }
123
+
124
+ exports.getConsoleOutput = async function(autoScalingGroupName,region,latest=true){
125
+ const autoScalingClient = new AutoScalingClient({ region }); // specify your region
126
+ const ec2Client = new EC2Client({ region });
127
+
128
+ try {
129
+ // Get instance IDs from Auto Scaling group
130
+ const describeGroupsCommand = new DescribeAutoScalingGroupsCommand({
131
+ AutoScalingGroupNames: [autoScalingGroupName]
132
+ });
133
+ const groupResponse = await autoScalingClient.send(describeGroupsCommand);
134
+ const instances = groupResponse.AutoScalingGroups[0].Instances;
135
+ const instanceIds = instances.map(instance => instance.InstanceId);
136
+
137
+ // Get console output for each instance
138
+ const consoleOutput = [];
139
+
140
+ for (let i=0;i<instanceIds.length;i++){
141
+ const consoleOutputCommand = new GetConsoleOutputCommand({ InstanceId: instanceIds[i],Latest: latest });
142
+ const outputResponse = await ec2Client.send(consoleOutputCommand);
143
+ const output = outputResponse.Output ? Buffer.from(outputResponse.Output, "base64").toString("ascii") : '';
144
+ consoleOutput.push({
145
+ instanceId: instanceIds[i],
146
+ output: output
147
+ });
148
+ }
149
+ return consoleOutput;
150
+ } catch (error) {
151
+ console.error("Error fetching EC2 console outputs:", error);
152
+ throw error;
153
+ }
154
+ }