spense-core 0.1.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.
- package/dist/aws-resources.d.ts +16 -0
- package/dist/aws-resources.js +41 -0
- package/dist/aws-validator.d.ts +12 -0
- package/dist/aws-validator.js +76 -0
- package/dist/conversation.d.ts +2 -0
- package/dist/conversation.js +293 -0
- package/dist/deploy.d.ts +2 -0
- package/dist/deploy.js +30 -0
- package/dist/detector.d.ts +3 -0
- package/dist/detector.js +72 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +28 -0
- package/dist/infra.d.ts +6 -0
- package/dist/infra.js +189 -0
- package/dist/types.d.ts +45 -0
- package/dist/types.js +26 -0
- package/package.json +31 -0
- package/src/aws-resources.ts +57 -0
- package/src/aws-validator.ts +83 -0
- package/src/conversation.ts +339 -0
- package/src/deploy.ts +29 -0
- package/src/detector.ts +43 -0
- package/src/index.ts +5 -0
- package/src/infra.ts +315 -0
- package/src/types.ts +59 -0
- package/tsconfig.json +13 -0
package/dist/infra.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.deployInfra = deployInfra;
|
|
37
|
+
const child_process_1 = require("child_process");
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
async function deployInfra(project, config) {
|
|
41
|
+
const stackDir = path.join(process.cwd(), '.spense');
|
|
42
|
+
fs.mkdirSync(stackDir, { recursive: true });
|
|
43
|
+
// Set AWS credentials as env vars for CDK
|
|
44
|
+
process.env.AWS_ACCESS_KEY_ID = config.credentials.accessKeyId;
|
|
45
|
+
process.env.AWS_SECRET_ACCESS_KEY = config.credentials.secretAccessKey;
|
|
46
|
+
process.env.AWS_REGION = config.credentials.region;
|
|
47
|
+
const stackCode = generateStack(project, config);
|
|
48
|
+
fs.writeFileSync(path.join(stackDir, 'stack.ts'), stackCode);
|
|
49
|
+
fs.writeFileSync(path.join(stackDir, 'cdk.json'), '{"app":"npx ts-node stack.ts"}');
|
|
50
|
+
(0, child_process_1.execSync)('npx cdk bootstrap --require-approval never', { cwd: stackDir, stdio: 'inherit' });
|
|
51
|
+
(0, child_process_1.execSync)('npx cdk deploy --require-approval never --outputs-file outputs.json', { cwd: stackDir, stdio: 'inherit' });
|
|
52
|
+
const outputs = JSON.parse(fs.readFileSync(path.join(stackDir, 'outputs.json'), 'utf-8'));
|
|
53
|
+
const stackOutputs = outputs[`${project.name}-stack`] || {};
|
|
54
|
+
return {
|
|
55
|
+
url: stackOutputs.URL || stackOutputs.LoadBalancerDNS,
|
|
56
|
+
dbEndpoint: stackOutputs.DatabaseEndpoint
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function generateStack(project, config) {
|
|
60
|
+
const stackName = `${project.name}-stack`;
|
|
61
|
+
const region = config.credentials.region;
|
|
62
|
+
return `import * as cdk from 'aws-cdk-lib';
|
|
63
|
+
import * as ec2 from 'aws-cdk-lib/aws-ec2';
|
|
64
|
+
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
|
|
65
|
+
import * as asg from 'aws-cdk-lib/aws-autoscaling';
|
|
66
|
+
import * as rds from 'aws-cdk-lib/aws-rds';
|
|
67
|
+
import * as iam from 'aws-cdk-lib/aws-iam';
|
|
68
|
+
${config.useHttps ? "import * as acm from 'aws-cdk-lib/aws-certificatemanager';" : ''}
|
|
69
|
+
${config.domain ? "import * as route53 from 'aws-cdk-lib/aws-route53';\nimport * as targets from 'aws-cdk-lib/aws-route53-targets';" : ''}
|
|
70
|
+
|
|
71
|
+
const app = new cdk.App();
|
|
72
|
+
const stack = new cdk.Stack(app, '${stackName}', { env: { region: '${region}' }});
|
|
73
|
+
|
|
74
|
+
const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 2, natGateways: 1 });
|
|
75
|
+
|
|
76
|
+
${generateDbCode(config)}
|
|
77
|
+
|
|
78
|
+
const role = new iam.Role(stack, 'InstanceRole', {
|
|
79
|
+
assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
|
|
80
|
+
managedPolicies: [
|
|
81
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'),
|
|
82
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonS3ReadOnlyAccess')
|
|
83
|
+
]
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
${generateAsgCode(project, config)}
|
|
87
|
+
|
|
88
|
+
const alb = new elbv2.ApplicationLoadBalancer(stack, 'ALB', { vpc, internetFacing: true });
|
|
89
|
+
|
|
90
|
+
${generateListenerCode(config)}
|
|
91
|
+
|
|
92
|
+
listener.addTargets('App', {
|
|
93
|
+
port: ${project.port},
|
|
94
|
+
targets: [scaling],
|
|
95
|
+
healthCheck: { path: '/health', healthyHttpCodes: '200-399' }
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
${generateDomainCode(config)}
|
|
99
|
+
|
|
100
|
+
new cdk.CfnOutput(stack, 'URL', { value: '${config.useHttps ? 'https' : 'http'}://' + ${config.domain ? `'${config.domain}'` : 'alb.loadBalancerDnsName'} });
|
|
101
|
+
${config.database !== 'none' ? "new cdk.CfnOutput(stack, 'DatabaseEndpoint', { value: db.instanceEndpoint.hostname });" : ''}
|
|
102
|
+
|
|
103
|
+
app.synth();
|
|
104
|
+
`;
|
|
105
|
+
}
|
|
106
|
+
function generateDbCode(config) {
|
|
107
|
+
if (config.database === 'none')
|
|
108
|
+
return '';
|
|
109
|
+
const engine = config.database === 'postgresql'
|
|
110
|
+
? 'rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_15 })'
|
|
111
|
+
: 'rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0 })';
|
|
112
|
+
return `const db = new rds.DatabaseInstance(stack, 'Database', {
|
|
113
|
+
vpc,
|
|
114
|
+
engine: ${engine},
|
|
115
|
+
instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.${config.dbInstanceClass?.split('.')[2].toUpperCase() || 'MICRO'}),
|
|
116
|
+
credentials: rds.Credentials.fromGeneratedSecret('dbadmin'),
|
|
117
|
+
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
|
|
118
|
+
removalPolicy: cdk.RemovalPolicy.SNAPSHOT
|
|
119
|
+
});`;
|
|
120
|
+
}
|
|
121
|
+
function generateAsgCode(project, config) {
|
|
122
|
+
if (config.ec2.useExisting) {
|
|
123
|
+
// For existing instances, we'd need different approach - skip ASG
|
|
124
|
+
return `// Using existing instances: ${config.ec2.existingInstanceIds?.join(', ')}`;
|
|
125
|
+
}
|
|
126
|
+
const instanceSize = config.ec2.instanceType?.split('.')[1].toUpperCase() || 'SMALL';
|
|
127
|
+
return `const scaling = new asg.AutoScalingGroup(stack, 'ASG', {
|
|
128
|
+
vpc,
|
|
129
|
+
instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.${instanceSize}),
|
|
130
|
+
machineImage: ec2.MachineImage.latestAmazonLinux2023(),
|
|
131
|
+
role,
|
|
132
|
+
minCapacity: ${config.ec2.minInstances || 1},
|
|
133
|
+
maxCapacity: ${config.ec2.maxInstances || 3},
|
|
134
|
+
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
|
|
135
|
+
${config.useElasticIp ? 'associatePublicIpAddress: true,' : ''}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
scaling.addUserData(\`${generateUserData(project)}\`);
|
|
139
|
+
|
|
140
|
+
scaling.scaleOnCpuUtilization('CpuScaling', { targetUtilizationPercent: 70 });`;
|
|
141
|
+
}
|
|
142
|
+
function generateUserData(project) {
|
|
143
|
+
if (project.type === 'nodejs') {
|
|
144
|
+
return `#!/bin/bash
|
|
145
|
+
set -e
|
|
146
|
+
yum install -y nodejs npm
|
|
147
|
+
mkdir -p /app && cd /app
|
|
148
|
+
# App code will be deployed via CodeDeploy or S3
|
|
149
|
+
${project.buildCommand || ''}
|
|
150
|
+
${project.startCommand}
|
|
151
|
+
`;
|
|
152
|
+
}
|
|
153
|
+
return `#!/bin/bash
|
|
154
|
+
set -e
|
|
155
|
+
yum install -y java-17-amazon-corretto
|
|
156
|
+
mkdir -p /app && cd /app
|
|
157
|
+
# JAR will be deployed via CodeDeploy or S3
|
|
158
|
+
java -jar app.jar
|
|
159
|
+
`;
|
|
160
|
+
}
|
|
161
|
+
function generateListenerCode(config) {
|
|
162
|
+
if (config.useHttps && config.domain) {
|
|
163
|
+
return `const cert = new acm.Certificate(stack, 'Cert', {
|
|
164
|
+
domainName: '${config.domain}',
|
|
165
|
+
validation: acm.CertificateValidation.fromDns()
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const listener = alb.addListener('HTTPS', {
|
|
169
|
+
port: 443,
|
|
170
|
+
certificates: [cert]
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
alb.addListener('HTTP', { port: 80 }).addAction('Redirect', {
|
|
174
|
+
action: elbv2.ListenerAction.redirect({ protocol: 'HTTPS', port: '443', permanent: true })
|
|
175
|
+
});`;
|
|
176
|
+
}
|
|
177
|
+
return `const listener = alb.addListener('HTTP', { port: 80 });`;
|
|
178
|
+
}
|
|
179
|
+
function generateDomainCode(config) {
|
|
180
|
+
if (!config.domain)
|
|
181
|
+
return '';
|
|
182
|
+
const baseDomain = config.domain.split('.').slice(-2).join('.');
|
|
183
|
+
return `const zone = route53.HostedZone.fromLookup(stack, 'Zone', { domainName: '${baseDomain}' });
|
|
184
|
+
new route53.ARecord(stack, 'DNS', {
|
|
185
|
+
zone,
|
|
186
|
+
recordName: '${config.domain}',
|
|
187
|
+
target: route53.RecordTarget.fromAlias(new targets.LoadBalancerTarget(alb))
|
|
188
|
+
});`;
|
|
189
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type ProjectType = 'nodejs' | 'springboot';
|
|
2
|
+
export type DatabaseType = 'postgresql' | 'mysql' | 'none';
|
|
3
|
+
export interface ProjectInfo {
|
|
4
|
+
type: ProjectType;
|
|
5
|
+
name: string;
|
|
6
|
+
port: number;
|
|
7
|
+
buildCommand: string;
|
|
8
|
+
startCommand: string;
|
|
9
|
+
artifactPath: string;
|
|
10
|
+
}
|
|
11
|
+
export interface AwsCredentials {
|
|
12
|
+
accessKeyId: string;
|
|
13
|
+
secretAccessKey: string;
|
|
14
|
+
region: string;
|
|
15
|
+
}
|
|
16
|
+
export interface Ec2Choice {
|
|
17
|
+
useExisting: boolean;
|
|
18
|
+
existingInstanceIds?: string[];
|
|
19
|
+
instanceType?: string;
|
|
20
|
+
minInstances?: number;
|
|
21
|
+
maxInstances?: number;
|
|
22
|
+
}
|
|
23
|
+
export interface DeployConfig {
|
|
24
|
+
credentials: AwsCredentials;
|
|
25
|
+
ec2: Ec2Choice;
|
|
26
|
+
useElasticIp: boolean;
|
|
27
|
+
database: DatabaseType;
|
|
28
|
+
dbInstanceClass?: string;
|
|
29
|
+
domain?: string;
|
|
30
|
+
useHttps: boolean;
|
|
31
|
+
}
|
|
32
|
+
export declare const PRICING: {
|
|
33
|
+
ec2: Record<string, number>;
|
|
34
|
+
elasticIp: {
|
|
35
|
+
attached: number;
|
|
36
|
+
detached: number;
|
|
37
|
+
perHour: number;
|
|
38
|
+
note: string;
|
|
39
|
+
};
|
|
40
|
+
rds: Record<string, number>;
|
|
41
|
+
alb: {
|
|
42
|
+
perHour: number;
|
|
43
|
+
perLcu: number;
|
|
44
|
+
};
|
|
45
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PRICING = void 0;
|
|
4
|
+
exports.PRICING = {
|
|
5
|
+
ec2: {
|
|
6
|
+
't3.micro': 0.0104,
|
|
7
|
+
't3.small': 0.0208,
|
|
8
|
+
't3.medium': 0.0416,
|
|
9
|
+
't3.large': 0.0832,
|
|
10
|
+
},
|
|
11
|
+
elasticIp: {
|
|
12
|
+
attached: 0,
|
|
13
|
+
detached: 0.005,
|
|
14
|
+
perHour: 0.005,
|
|
15
|
+
note: 'Free when attached to running instance, $0.005/hr when detached'
|
|
16
|
+
},
|
|
17
|
+
rds: {
|
|
18
|
+
'db.t3.micro': 0.017,
|
|
19
|
+
'db.t3.small': 0.034,
|
|
20
|
+
'db.t3.medium': 0.068,
|
|
21
|
+
},
|
|
22
|
+
alb: {
|
|
23
|
+
perHour: 0.0225,
|
|
24
|
+
perLcu: 0.008,
|
|
25
|
+
}
|
|
26
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "spense-core",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Core deployment logic for Spense",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@aws-sdk/client-auto-scaling": "^3.400.0",
|
|
12
|
+
"@aws-sdk/client-ec2": "^3.400.0",
|
|
13
|
+
"@aws-sdk/client-elastic-load-balancing-v2": "^3.400.0",
|
|
14
|
+
"@aws-sdk/client-iam": "^3.400.0",
|
|
15
|
+
"@aws-sdk/client-rds": "^3.400.0",
|
|
16
|
+
"@aws-sdk/client-route-53": "^3.400.0",
|
|
17
|
+
"@aws-sdk/client-s3": "^3.400.0",
|
|
18
|
+
"archiver": "^6.0.1",
|
|
19
|
+
"aws-cdk-lib": "^2.170.0",
|
|
20
|
+
"chalk": "^4.1.2",
|
|
21
|
+
"constructs": "^10.4.2",
|
|
22
|
+
"inquirer": "^8.2.6",
|
|
23
|
+
"ora": "^5.4.1"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/archiver": "^6.0.2",
|
|
27
|
+
"@types/inquirer": "^8.2.10",
|
|
28
|
+
"@types/node": "^20.10.0",
|
|
29
|
+
"typescript": "^5.3.0"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { AwsCredentials } from './types';
|
|
2
|
+
import { EC2Client, DescribeInstancesCommand, Instance } from '@aws-sdk/client-ec2';
|
|
3
|
+
import { RDSClient, DescribeDBInstancesCommand, DBInstance } from '@aws-sdk/client-rds';
|
|
4
|
+
|
|
5
|
+
export interface ExistingInstance {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
type: string;
|
|
9
|
+
state: string;
|
|
10
|
+
publicIp?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ExistingDatabase {
|
|
14
|
+
id: string;
|
|
15
|
+
engine: string;
|
|
16
|
+
instanceClass: string;
|
|
17
|
+
endpoint?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function listEc2Instances(creds: AwsCredentials): Promise<ExistingInstance[]> {
|
|
21
|
+
const ec2 = new EC2Client({
|
|
22
|
+
region: creds.region,
|
|
23
|
+
credentials: { accessKeyId: creds.accessKeyId, secretAccessKey: creds.secretAccessKey }
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const res = await ec2.send(new DescribeInstancesCommand({}));
|
|
27
|
+
const instances: ExistingInstance[] = [];
|
|
28
|
+
|
|
29
|
+
for (const reservation of res.Reservations || []) {
|
|
30
|
+
for (const inst of reservation.Instances || []) {
|
|
31
|
+
if (inst.State?.Name === 'terminated') continue;
|
|
32
|
+
instances.push({
|
|
33
|
+
id: inst.InstanceId || '',
|
|
34
|
+
name: inst.Tags?.find(t => t.Key === 'Name')?.Value || 'Unnamed',
|
|
35
|
+
type: inst.InstanceType || '',
|
|
36
|
+
state: inst.State?.Name || '',
|
|
37
|
+
publicIp: inst.PublicIpAddress,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return instances;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function listRdsInstances(creds: AwsCredentials): Promise<ExistingDatabase[]> {
|
|
45
|
+
const rds = new RDSClient({
|
|
46
|
+
region: creds.region,
|
|
47
|
+
credentials: { accessKeyId: creds.accessKeyId, secretAccessKey: creds.secretAccessKey }
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const res = await rds.send(new DescribeDBInstancesCommand({}));
|
|
51
|
+
return (res.DBInstances || []).map(db => ({
|
|
52
|
+
id: db.DBInstanceIdentifier || '',
|
|
53
|
+
engine: db.Engine || '',
|
|
54
|
+
instanceClass: db.DBInstanceClass || '',
|
|
55
|
+
endpoint: db.Endpoint?.Address,
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { AwsCredentials } from './types';
|
|
2
|
+
import { EC2Client, DescribeInstancesCommand } from '@aws-sdk/client-ec2';
|
|
3
|
+
import { RDSClient, DescribeDBInstancesCommand } from '@aws-sdk/client-rds';
|
|
4
|
+
import { ElasticLoadBalancingV2Client, DescribeLoadBalancersCommand } from '@aws-sdk/client-elastic-load-balancing-v2';
|
|
5
|
+
import { Route53Client, ListHostedZonesCommand } from '@aws-sdk/client-route-53';
|
|
6
|
+
import { AutoScalingClient, DescribeAutoScalingGroupsCommand } from '@aws-sdk/client-auto-scaling';
|
|
7
|
+
import { IAMClient, GetUserCommand } from '@aws-sdk/client-iam';
|
|
8
|
+
|
|
9
|
+
export interface PermissionCheck {
|
|
10
|
+
service: string;
|
|
11
|
+
hasAccess: boolean;
|
|
12
|
+
error?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function validateCredentials(creds: AwsCredentials): Promise<{ valid: boolean; username?: string; error?: string }> {
|
|
16
|
+
const iam = new IAMClient({
|
|
17
|
+
region: creds.region,
|
|
18
|
+
credentials: { accessKeyId: creds.accessKeyId, secretAccessKey: creds.secretAccessKey }
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const res = await iam.send(new GetUserCommand({}));
|
|
23
|
+
return { valid: true, username: res.User?.UserName };
|
|
24
|
+
} catch (err: any) {
|
|
25
|
+
return { valid: false, error: err.message };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function checkPermissions(creds: AwsCredentials): Promise<PermissionCheck[]> {
|
|
30
|
+
const config = {
|
|
31
|
+
region: creds.region,
|
|
32
|
+
credentials: { accessKeyId: creds.accessKeyId, secretAccessKey: creds.secretAccessKey }
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const checks: PermissionCheck[] = [];
|
|
36
|
+
|
|
37
|
+
// EC2
|
|
38
|
+
try {
|
|
39
|
+
const ec2 = new EC2Client(config);
|
|
40
|
+
await ec2.send(new DescribeInstancesCommand({ MaxResults: 5 }));
|
|
41
|
+
checks.push({ service: 'EC2', hasAccess: true });
|
|
42
|
+
} catch (err: any) {
|
|
43
|
+
checks.push({ service: 'EC2', hasAccess: false, error: err.message });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// RDS
|
|
47
|
+
try {
|
|
48
|
+
const rds = new RDSClient(config);
|
|
49
|
+
await rds.send(new DescribeDBInstancesCommand({}));
|
|
50
|
+
checks.push({ service: 'RDS', hasAccess: true });
|
|
51
|
+
} catch (err: any) {
|
|
52
|
+
checks.push({ service: 'RDS', hasAccess: false, error: err.message });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ALB
|
|
56
|
+
try {
|
|
57
|
+
const elb = new ElasticLoadBalancingV2Client(config);
|
|
58
|
+
await elb.send(new DescribeLoadBalancersCommand({}));
|
|
59
|
+
checks.push({ service: 'ALB', hasAccess: true });
|
|
60
|
+
} catch (err: any) {
|
|
61
|
+
checks.push({ service: 'ALB', hasAccess: false, error: err.message });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Route53
|
|
65
|
+
try {
|
|
66
|
+
const r53 = new Route53Client(config);
|
|
67
|
+
await r53.send(new ListHostedZonesCommand({}));
|
|
68
|
+
checks.push({ service: 'Route53', hasAccess: true });
|
|
69
|
+
} catch (err: any) {
|
|
70
|
+
checks.push({ service: 'Route53', hasAccess: false, error: err.message });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Auto Scaling
|
|
74
|
+
try {
|
|
75
|
+
const asg = new AutoScalingClient(config);
|
|
76
|
+
await asg.send(new DescribeAutoScalingGroupsCommand({ MaxRecords: 1 }));
|
|
77
|
+
checks.push({ service: 'AutoScaling', hasAccess: true });
|
|
78
|
+
} catch (err: any) {
|
|
79
|
+
checks.push({ service: 'AutoScaling', hasAccess: false, error: err.message });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return checks;
|
|
83
|
+
}
|