first commit
This commit is contained in:
@@ -0,0 +1,583 @@
|
||||
import {describe, expect, test} from 'bun:test'
|
||||
import type {EC2Client} from '@aws-sdk/client-ec2'
|
||||
import {
|
||||
AllocateAddressCommand,
|
||||
AssociateAddressCommand,
|
||||
AssociateRouteTableCommand,
|
||||
AuthorizeSecurityGroupEgressCommand,
|
||||
AuthorizeSecurityGroupIngressCommand,
|
||||
CreateImageCommand,
|
||||
CreateInternetGatewayCommand,
|
||||
CreateKeyPairCommand,
|
||||
CreateNatGatewayCommand,
|
||||
CreateRouteCommand,
|
||||
CreateRouteTableCommand,
|
||||
CreateSecurityGroupCommand,
|
||||
CreateSubnetCommand,
|
||||
CreateTagsCommand,
|
||||
CreateVpcCommand,
|
||||
DeleteInternetGatewayCommand,
|
||||
DeleteKeyPairCommand,
|
||||
DeleteNatGatewayCommand,
|
||||
DeleteRouteCommand,
|
||||
DeleteSecurityGroupCommand,
|
||||
DeleteSubnetCommand,
|
||||
DeleteTagsCommand,
|
||||
DeleteVpcCommand,
|
||||
DeregisterImageCommand,
|
||||
DescribeAddressesCommand,
|
||||
DescribeAvailabilityZonesCommand,
|
||||
DescribeImagesCommand,
|
||||
DescribeInstanceTypesCommand,
|
||||
DescribeInternetGatewaysCommand,
|
||||
DescribeKeyPairsCommand,
|
||||
DescribeNatGatewaysCommand,
|
||||
DescribeRouteTablesCommand,
|
||||
DescribeSecurityGroupsCommand,
|
||||
DescribeSubnetsCommand,
|
||||
DescribeVpcAttributeCommand,
|
||||
DescribeVpcsCommand,
|
||||
DetachInternetGatewayCommand,
|
||||
DisassociateAddressCommand,
|
||||
DisassociateRouteTableCommand,
|
||||
GetConsoleOutputCommand,
|
||||
RebootInstancesCommand,
|
||||
ModifySubnetAttributeCommand,
|
||||
ModifyVpcAttributeCommand,
|
||||
ReleaseAddressCommand,
|
||||
RevokeSecurityGroupEgressCommand,
|
||||
RevokeSecurityGroupIngressCommand,
|
||||
RunInstancesCommand,
|
||||
StartInstancesCommand,
|
||||
StopInstancesCommand,
|
||||
TerminateInstancesCommand,
|
||||
} from '@aws-sdk/client-ec2'
|
||||
import {createEc2Service} from './ec2'
|
||||
|
||||
function fakeClient(responses: Record<string, unknown> = {}): EC2Client {
|
||||
return {
|
||||
send: async (cmd: {constructor: {name: string}}) => {
|
||||
const name = cmd.constructor.name
|
||||
if (name in responses) return responses[name]
|
||||
return {}
|
||||
},
|
||||
} as unknown as EC2Client
|
||||
}
|
||||
|
||||
describe('ec2Service.runInstance', () => {
|
||||
test('sends RunInstancesCommand and returns mapped instance', async () => {
|
||||
const sentCmds: unknown[] = []
|
||||
const client: EC2Client = {
|
||||
send: async (cmd: unknown) => {
|
||||
sentCmds.push(cmd)
|
||||
return {
|
||||
Instances: [{
|
||||
InstanceId: 'i-new123',
|
||||
Tags: [{Key: 'Name', Value: 'my-instance'}],
|
||||
State: {Name: 'pending'},
|
||||
}],
|
||||
}
|
||||
},
|
||||
} as unknown as EC2Client
|
||||
|
||||
const service = createEc2Service(client)
|
||||
const result = await service.runInstance({
|
||||
name: 'my-instance',
|
||||
imageId: 'ami-abc',
|
||||
instanceType: 't3.micro',
|
||||
})
|
||||
|
||||
expect(sentCmds[0]).toBeInstanceOf(RunInstancesCommand)
|
||||
expect(result.instanceId).toBe('i-new123')
|
||||
expect(result.name).toBe('my-instance')
|
||||
expect(result.state).toBe('pending')
|
||||
})
|
||||
})
|
||||
|
||||
describe('ec2Service lifecycle', () => {
|
||||
test('startInstance sends StartInstancesCommand with correct id', async () => {
|
||||
const sentCmds: unknown[] = []
|
||||
const client: EC2Client = {send: async (cmd: unknown) => { sentCmds.push(cmd); return {} }} as unknown as EC2Client
|
||||
await createEc2Service(client).startInstance('i-abc')
|
||||
expect(sentCmds[0]).toBeInstanceOf(StartInstancesCommand)
|
||||
})
|
||||
|
||||
test('stopInstance sends StopInstancesCommand with correct id', async () => {
|
||||
const sentCmds: unknown[] = []
|
||||
const client: EC2Client = {send: async (cmd: unknown) => { sentCmds.push(cmd); return {} }} as unknown as EC2Client
|
||||
await createEc2Service(client).stopInstance('i-abc')
|
||||
expect(sentCmds[0]).toBeInstanceOf(StopInstancesCommand)
|
||||
})
|
||||
|
||||
test('rebootInstance sends RebootInstancesCommand', async () => {
|
||||
const sentCmds: unknown[] = []
|
||||
const client: EC2Client = {send: async (cmd: unknown) => { sentCmds.push(cmd); return {} }} as unknown as EC2Client
|
||||
await createEc2Service(client).rebootInstance('i-abc')
|
||||
expect(sentCmds[0]).toBeInstanceOf(RebootInstancesCommand)
|
||||
})
|
||||
|
||||
test('terminateInstance sends TerminateInstancesCommand', async () => {
|
||||
const sentCmds: unknown[] = []
|
||||
const client: EC2Client = {send: async (cmd: unknown) => { sentCmds.push(cmd); return {} }} as unknown as EC2Client
|
||||
await createEc2Service(client).terminateInstance('i-abc')
|
||||
expect(sentCmds[0]).toBeInstanceOf(TerminateInstancesCommand)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ec2Service.updateInstanceTags', () => {
|
||||
test('calls CreateTags for additions and DeleteTags for removals', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
|
||||
const service = createEc2Service(client)
|
||||
await service.updateInstanceTags('i-abc', [{key: 'Env', value: 'prod'}], ['OldKey'])
|
||||
expect(sent[0]).toBeInstanceOf(CreateTagsCommand)
|
||||
expect(sent[1]).toBeInstanceOf(DeleteTagsCommand)
|
||||
})
|
||||
|
||||
test('skips CreateTags when nothing to add', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
|
||||
await createEc2Service(client).updateInstanceTags('i-abc', [], ['OldKey'])
|
||||
expect(sent[0]).toBeInstanceOf(DeleteTagsCommand)
|
||||
expect(sent).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ec2Service.createImage', () => {
|
||||
test('sends CreateImageCommand and returns imageId', async () => {
|
||||
const client = fakeClient({CreateImageCommand: {ImageId: 'ami-new'}})
|
||||
const result = await createEc2Service(client).createImage({instanceId: 'i-abc', name: 'snap-1'})
|
||||
expect(result.imageId).toBe('ami-new')
|
||||
})
|
||||
})
|
||||
|
||||
describe('ec2Service.deregisterImage', () => {
|
||||
test('sends DeregisterImageCommand', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
|
||||
await createEc2Service(client).deregisterImage('ami-old')
|
||||
expect(sent[0]).toBeInstanceOf(DeregisterImageCommand)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ec2Service.listAmis', () => {
|
||||
test('requests the complete image catalog and maps launchable AMIs', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {
|
||||
send: async (cmd: unknown) => {
|
||||
sent.push(cmd)
|
||||
return {
|
||||
Images: [{
|
||||
ImageId: 'ami-amazonlinux2023',
|
||||
Name: 'Amazon Linux 2023',
|
||||
}],
|
||||
}
|
||||
},
|
||||
} as unknown as EC2Client
|
||||
|
||||
const result = await createEc2Service(client).listAmis()
|
||||
|
||||
expect(sent[0]).toBeInstanceOf(DescribeImagesCommand)
|
||||
expect((sent[0] as DescribeImagesCommand).input).toEqual({})
|
||||
expect(result).toEqual([
|
||||
expect.objectContaining({
|
||||
imageId: 'ami-amazonlinux2023',
|
||||
name: 'Amazon Linux 2023',
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('ec2Service.getConsoleOutput', () => {
|
||||
test('decodes base64 output', async () => {
|
||||
const encoded = Buffer.from('hello console').toString('base64')
|
||||
const client = fakeClient({GetConsoleOutputCommand: {Output: encoded}})
|
||||
const result = await createEc2Service(client).getConsoleOutput('i-abc')
|
||||
expect(result.output).toBe('hello console')
|
||||
})
|
||||
|
||||
test('returns empty output when operation is unsupported', async () => {
|
||||
const client: EC2Client = {
|
||||
send: async () => { throw new Error('GetConsoleOutput is not supported') },
|
||||
} as unknown as EC2Client
|
||||
const result = await createEc2Service(client).getConsoleOutput('i-abc')
|
||||
expect(result.output).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('ec2Service key pairs', () => {
|
||||
test('listKeyPairs returns empty array when unsupported', async () => {
|
||||
const client: EC2Client = {
|
||||
send: async () => { throw new Error('DescribeKeyPairs is not supported') },
|
||||
} as unknown as EC2Client
|
||||
const result = await createEc2Service(client).listKeyPairs()
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
|
||||
test('createKeyPair sends CreateKeyPairCommand and returns material', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {
|
||||
send: async (cmd: unknown) => {
|
||||
sent.push(cmd)
|
||||
return {KeyPairId: 'kp-1', KeyName: 'my-key', KeyMaterial: '-----BEGIN RSA-----'}
|
||||
},
|
||||
} as unknown as EC2Client
|
||||
const result = await createEc2Service(client).createKeyPair('my-key')
|
||||
expect(sent[0]).toBeInstanceOf(CreateKeyPairCommand)
|
||||
expect(result.keyName).toBe('my-key')
|
||||
expect(result.keyMaterial).toBe('-----BEGIN RSA-----')
|
||||
})
|
||||
|
||||
test('deleteKeyPair sends DeleteKeyPairCommand', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
|
||||
await createEc2Service(client).deleteKeyPair('my-key')
|
||||
expect(sent[0]).toBeInstanceOf(DeleteKeyPairCommand)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ec2Service security groups', () => {
|
||||
test('listSecurityGroups returns empty array when unsupported', async () => {
|
||||
const client: EC2Client = {
|
||||
send: async () => { throw new Error('DescribeSecurityGroups is not supported') },
|
||||
} as unknown as EC2Client
|
||||
expect(await createEc2Service(client).listSecurityGroups()).toEqual([])
|
||||
})
|
||||
|
||||
test('createSecurityGroup sends CreateSecurityGroupCommand and returns groupId', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {
|
||||
send: async (cmd: unknown) => { sent.push(cmd); return {GroupId: 'sg-new'} },
|
||||
} as unknown as EC2Client
|
||||
const result = await createEc2Service(client).createSecurityGroup('web', 'Web SG', 'vpc-1')
|
||||
expect(sent[0]).toBeInstanceOf(CreateSecurityGroupCommand)
|
||||
expect(result.groupId).toBe('sg-new')
|
||||
})
|
||||
|
||||
test('deleteSecurityGroup sends DeleteSecurityGroupCommand', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
|
||||
await createEc2Service(client).deleteSecurityGroup('sg-1')
|
||||
expect(sent[0]).toBeInstanceOf(DeleteSecurityGroupCommand)
|
||||
})
|
||||
|
||||
test('authorizeSecurityGroupIngress sends correct command', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
|
||||
await createEc2Service(client).authorizeSecurityGroupIngress('sg-1', {protocol: 'tcp', fromPort: 80, toPort: 80, cidr: '0.0.0.0/0'})
|
||||
expect(sent[0]).toBeInstanceOf(AuthorizeSecurityGroupIngressCommand)
|
||||
})
|
||||
|
||||
test('revokeSecurityGroupIngress sends correct command', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
|
||||
await createEc2Service(client).revokeSecurityGroupIngress('sg-1', {protocol: 'tcp', fromPort: 80, toPort: 80, cidr: '0.0.0.0/0'})
|
||||
expect(sent[0]).toBeInstanceOf(RevokeSecurityGroupIngressCommand)
|
||||
})
|
||||
|
||||
test('authorizeSecurityGroupEgress sends correct command', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
|
||||
await createEc2Service(client).authorizeSecurityGroupEgress('sg-1', {protocol: 'tcp', fromPort: 443, toPort: 443, cidr: '0.0.0.0/0'})
|
||||
expect(sent[0]).toBeInstanceOf(AuthorizeSecurityGroupEgressCommand)
|
||||
})
|
||||
|
||||
test('revokeSecurityGroupEgress sends correct command', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
|
||||
await createEc2Service(client).revokeSecurityGroupEgress('sg-1', {protocol: 'tcp', fromPort: 443, toPort: 443, cidr: '0.0.0.0/0'})
|
||||
expect(sent[0]).toBeInstanceOf(RevokeSecurityGroupEgressCommand)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ec2Service VPCs and subnets', () => {
|
||||
test('listVpcs returns empty array when unsupported', async () => {
|
||||
const client: EC2Client = {
|
||||
send: async () => { throw new Error('DescribeVpcs is not supported') },
|
||||
} as unknown as EC2Client
|
||||
expect(await createEc2Service(client).listVpcs()).toEqual([])
|
||||
})
|
||||
|
||||
test('createVpc sends CreateVpcCommand and returns mapped vpc', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {
|
||||
send: async (cmd: unknown) => { sent.push(cmd); return {Vpc: {VpcId: 'vpc-new', CidrBlock: '10.0.0.0/16', IsDefault: false}} },
|
||||
} as unknown as EC2Client
|
||||
const result = await createEc2Service(client).createVpc('10.0.0.0/16')
|
||||
expect(sent[0]).toBeInstanceOf(CreateVpcCommand)
|
||||
expect(result.vpcId).toBe('vpc-new')
|
||||
expect(result.cidrBlock).toBe('10.0.0.0/16')
|
||||
})
|
||||
|
||||
test('deleteVpc sends DeleteVpcCommand as final command', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
|
||||
await createEc2Service(client).deleteVpc('vpc-1')
|
||||
// deleteVpc first cleans up SGs, subnets, IGWs, RTBs then sends DeleteVpcCommand last
|
||||
expect(sent[sent.length - 1]).toBeInstanceOf(DeleteVpcCommand)
|
||||
})
|
||||
|
||||
test('listSubnets returns empty array when unsupported', async () => {
|
||||
const client: EC2Client = {
|
||||
send: async () => { throw new Error('DescribeSubnets is not supported') },
|
||||
} as unknown as EC2Client
|
||||
expect(await createEc2Service(client).listSubnets()).toEqual([])
|
||||
})
|
||||
|
||||
test('createSubnet sends CreateSubnetCommand and returns mapped subnet', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {
|
||||
send: async (cmd: unknown) => {
|
||||
sent.push(cmd)
|
||||
return {Subnet: {SubnetId: 'subnet-new', VpcId: 'vpc-1', CidrBlock: '10.0.1.0/24', AvailabilityZone: 'us-east-1a'}}
|
||||
},
|
||||
} as unknown as EC2Client
|
||||
const result = await createEc2Service(client).createSubnet('vpc-1', '10.0.1.0/24', 'us-east-1a')
|
||||
expect(sent[0]).toBeInstanceOf(CreateSubnetCommand)
|
||||
expect(result.subnetId).toBe('subnet-new')
|
||||
expect(result.availabilityZone).toBe('us-east-1a')
|
||||
})
|
||||
|
||||
test('deleteSubnet sends DeleteSubnetCommand', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
|
||||
await createEc2Service(client).deleteSubnet('subnet-1')
|
||||
expect(sent[0]).toBeInstanceOf(DeleteSubnetCommand)
|
||||
})
|
||||
|
||||
test('modifySubnetMapPublicIp sends ModifySubnetAttributeCommand', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
|
||||
await createEc2Service(client).modifySubnetMapPublicIp('subnet-1', true)
|
||||
expect(sent[0]).toBeInstanceOf(ModifySubnetAttributeCommand)
|
||||
})
|
||||
|
||||
test('getVpcAttributes returns dns settings from two parallel describe calls', async () => {
|
||||
const client = fakeClient({
|
||||
DescribeVpcAttributeCommand: {AttributeValue: {Value: true}},
|
||||
})
|
||||
const result = await createEc2Service(client).getVpcAttributes('vpc-1')
|
||||
expect(result).toHaveProperty('enableDnsHostnames')
|
||||
expect(result).toHaveProperty('enableDnsSupport')
|
||||
})
|
||||
|
||||
test('modifyVpcAttribute sends ModifyVpcAttributeCommand', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
|
||||
await createEc2Service(client).modifyVpcAttribute('vpc-1', 'enableDnsHostnames', true)
|
||||
expect(sent[0]).toBeInstanceOf(ModifyVpcAttributeCommand)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ec2Service Internet Gateways', () => {
|
||||
test('listInternetGateways returns empty array when unsupported', async () => {
|
||||
const client: EC2Client = {send: async () => { throw new Error('DescribeInternetGateways is not supported') }} as unknown as EC2Client
|
||||
expect(await createEc2Service(client).listInternetGateways()).toEqual([])
|
||||
})
|
||||
|
||||
test('createInternetGateway sends CreateInternetGatewayCommand and returns mapped igw', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {
|
||||
send: async (cmd: unknown) => {
|
||||
sent.push(cmd)
|
||||
if (cmd instanceof CreateInternetGatewayCommand) return {InternetGateway: {InternetGatewayId: 'igw-0a1b2c3d', Attachments: [], Tags: []}}
|
||||
return {}
|
||||
},
|
||||
} as unknown as EC2Client
|
||||
const result = await createEc2Service(client).createInternetGateway()
|
||||
expect(sent[0]).toBeInstanceOf(CreateInternetGatewayCommand)
|
||||
expect(result.internetGatewayId).toBe('igw-0a1b2c3d')
|
||||
})
|
||||
|
||||
test('deleteInternetGateway detaches all VPCs then deletes', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {
|
||||
send: async (cmd: unknown) => {
|
||||
sent.push(cmd)
|
||||
if (cmd instanceof DescribeInternetGatewaysCommand) {
|
||||
return {InternetGateways: [{InternetGatewayId: 'igw-0a1b2c3d', Attachments: [{VpcId: 'vpc-0a1b2c3d', State: 'available'}]}]}
|
||||
}
|
||||
return {}
|
||||
},
|
||||
} as unknown as EC2Client
|
||||
await createEc2Service(client).deleteInternetGateway('igw-0a1b2c3d')
|
||||
expect(sent[0]).toBeInstanceOf(DescribeInternetGatewaysCommand)
|
||||
expect(sent[1]).toBeInstanceOf(DetachInternetGatewayCommand)
|
||||
expect(sent[2]).toBeInstanceOf(DeleteInternetGatewayCommand)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ec2Service NAT Gateways', () => {
|
||||
test('listNatGateways returns empty array when unsupported', async () => {
|
||||
const client: EC2Client = {send: async () => { throw new Error('DescribeNatGateways is not supported') }} as unknown as EC2Client
|
||||
expect(await createEc2Service(client).listNatGateways()).toEqual([])
|
||||
})
|
||||
|
||||
test('createNatGateway auto-allocates EIP when allocationId is absent', async () => {
|
||||
const sent: unknown[] = []
|
||||
let attempt = 0
|
||||
const client: EC2Client = {
|
||||
send: async (cmd: unknown) => {
|
||||
sent.push(cmd)
|
||||
if (cmd instanceof AllocateAddressCommand) return {AllocationId: 'eipalloc-0a1b2c3d', PublicIp: '1.2.3.4'}
|
||||
if (cmd instanceof CreateNatGatewayCommand) return {NatGateway: {NatGatewayId: 'nat-0a1b2c3d', SubnetId: 'subnet-1', VpcId: 'vpc-1'}}
|
||||
if (cmd instanceof DescribeNatGatewaysCommand) {
|
||||
attempt++
|
||||
return {NatGateways: [{NatGatewayId: 'nat-0a1b2c3d', SubnetId: 'subnet-1', VpcId: 'vpc-1', State: attempt >= 1 ? 'available' : 'pending'}]}
|
||||
}
|
||||
return {}
|
||||
},
|
||||
} as unknown as EC2Client
|
||||
const result = await createEc2Service(client).createNatGateway({subnetId: 'subnet-1'})
|
||||
expect(sent[0]).toBeInstanceOf(AllocateAddressCommand)
|
||||
expect(sent[1]).toBeInstanceOf(CreateNatGatewayCommand)
|
||||
expect(result.natGatewayId).toBe('nat-0a1b2c3d')
|
||||
})
|
||||
|
||||
test('deleteNatGateway sends DeleteNatGatewayCommand', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {
|
||||
send: async (cmd: unknown) => {
|
||||
sent.push(cmd)
|
||||
if (cmd instanceof DescribeNatGatewaysCommand) return {NatGateways: [{NatGatewayId: 'nat-0a1b2c3d', State: 'deleted'}]}
|
||||
return {}
|
||||
},
|
||||
} as unknown as EC2Client
|
||||
await createEc2Service(client).deleteNatGateway('nat-0a1b2c3d')
|
||||
expect(sent[0]).toBeInstanceOf(DeleteNatGatewayCommand)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ec2Service Route Tables', () => {
|
||||
test('listRouteTables returns empty array when unsupported', async () => {
|
||||
const client: EC2Client = {send: async () => { throw new Error('DescribeRouteTables is not supported') }} as unknown as EC2Client
|
||||
expect(await createEc2Service(client).listRouteTables()).toEqual([])
|
||||
})
|
||||
|
||||
test('createRoute sends CreateRouteCommand', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
|
||||
await createEc2Service(client).createRoute('rtb-0a1b2c3d', {destinationCidrBlock: '0.0.0.0/0', gatewayId: 'igw-0a1b2c3d'})
|
||||
expect(sent[0]).toBeInstanceOf(CreateRouteCommand)
|
||||
})
|
||||
|
||||
test('deleteRoute sends DeleteRouteCommand', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
|
||||
await createEc2Service(client).deleteRoute('rtb-0a1b2c3d', '0.0.0.0/0')
|
||||
expect(sent[0]).toBeInstanceOf(DeleteRouteCommand)
|
||||
})
|
||||
|
||||
test('associateRouteTable sends AssociateRouteTableCommand and returns associationId', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {
|
||||
send: async (cmd: unknown) => { sent.push(cmd); return {AssociationId: 'rtbassoc-0a1b2c3d'} },
|
||||
} as unknown as EC2Client
|
||||
const id = await createEc2Service(client).associateRouteTable('rtb-0a1b2c3d', 'subnet-0a1b2c3d')
|
||||
expect(sent[0]).toBeInstanceOf(AssociateRouteTableCommand)
|
||||
expect(id).toBe('rtbassoc-0a1b2c3d')
|
||||
})
|
||||
|
||||
test('disassociateRouteTable sends DisassociateRouteTableCommand', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
|
||||
await createEc2Service(client).disassociateRouteTable('rtbassoc-0a1b2c3d')
|
||||
expect(sent[0]).toBeInstanceOf(DisassociateRouteTableCommand)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ec2Service Elastic IPs', () => {
|
||||
test('listElasticIps returns empty array when unsupported', async () => {
|
||||
const client: EC2Client = {send: async () => { throw new Error('DescribeAddresses is not supported') }} as unknown as EC2Client
|
||||
expect(await createEc2Service(client).listElasticIps()).toEqual([])
|
||||
})
|
||||
|
||||
test('allocateElasticIp sends AllocateAddressCommand and returns eip', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {
|
||||
send: async (cmd: unknown) => {
|
||||
sent.push(cmd)
|
||||
if (cmd instanceof AllocateAddressCommand) return {AllocationId: 'eipalloc-0a1b2c3d', PublicIp: '54.0.0.1', Domain: 'vpc'}
|
||||
return {}
|
||||
},
|
||||
} as unknown as EC2Client
|
||||
const result = await createEc2Service(client).allocateElasticIp()
|
||||
expect(sent[0]).toBeInstanceOf(AllocateAddressCommand)
|
||||
expect(result.allocationId).toBe('eipalloc-0a1b2c3d')
|
||||
expect(result.publicIp).toBe('54.0.0.1')
|
||||
})
|
||||
|
||||
test('releaseElasticIp sends ReleaseAddressCommand', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
|
||||
await createEc2Service(client).releaseElasticIp('eipalloc-0a1b2c3d')
|
||||
expect(sent[0]).toBeInstanceOf(ReleaseAddressCommand)
|
||||
})
|
||||
|
||||
test('associateElasticIp sends AssociateAddressCommand and returns associationId', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {
|
||||
send: async (cmd: unknown) => {
|
||||
sent.push(cmd)
|
||||
if (cmd instanceof AssociateAddressCommand) return {AssociationId: 'eipassoc-test'}
|
||||
return {}
|
||||
},
|
||||
} as unknown as EC2Client
|
||||
const result = await createEc2Service(client).associateElasticIp('eipalloc-0a1b2c3d', 'i-test1')
|
||||
expect(sent[0]).toBeInstanceOf(AssociateAddressCommand)
|
||||
expect(result.associationId).toBe('eipassoc-test')
|
||||
})
|
||||
|
||||
test('disassociateElasticIp sends DisassociateAddressCommand', async () => {
|
||||
const sent: unknown[] = []
|
||||
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
|
||||
await createEc2Service(client).disassociateElasticIp('eipassoc-test')
|
||||
expect(sent[0]).toBeInstanceOf(DisassociateAddressCommand)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ec2Service metadata', () => {
|
||||
test('listAvailabilityZones returns empty array when unsupported', async () => {
|
||||
const client: EC2Client = {send: async () => { throw new Error('DescribeAvailabilityZones is not supported') }} as unknown as EC2Client
|
||||
expect(await createEc2Service(client).listAvailabilityZones()).toEqual([])
|
||||
})
|
||||
|
||||
test('listAvailabilityZones returns mapped zones', async () => {
|
||||
const client = fakeClient({
|
||||
DescribeAvailabilityZonesCommand: {AvailabilityZones: [{ZoneName: 'us-east-1a', ZoneId: 'use1-az1', State: 'available'}]},
|
||||
})
|
||||
const result = await createEc2Service(client).listAvailabilityZones()
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].zoneName).toBe('us-east-1a')
|
||||
expect(result[0].zoneId).toBe('use1-az1')
|
||||
})
|
||||
|
||||
test('listInstanceTypes returns empty array when unsupported', async () => {
|
||||
const client: EC2Client = {send: async () => { throw new Error('DescribeInstanceTypes is not supported') }} as unknown as EC2Client
|
||||
expect(await createEc2Service(client).listInstanceTypes()).toEqual([])
|
||||
})
|
||||
|
||||
test('listInstanceTypes returns mapped types', async () => {
|
||||
const client = fakeClient({
|
||||
DescribeInstanceTypesCommand: {InstanceTypes: [{InstanceType: 't3.micro', VCpuInfo: {DefaultVCpus: 2}, MemoryInfo: {SizeInMiB: 1024}}]},
|
||||
})
|
||||
const result = await createEc2Service(client).listInstanceTypes()
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].instanceType).toBe('t3.micro')
|
||||
expect(result[0].vcpu).toBe(2)
|
||||
expect(result[0].memoryMiB).toBe(1024)
|
||||
})
|
||||
})
|
||||
|
||||
// Suppress unused import warnings
|
||||
void DescribeKeyPairsCommand
|
||||
void DescribeSecurityGroupsCommand
|
||||
void DescribeSubnetsCommand
|
||||
void DescribeVpcAttributeCommand
|
||||
void DescribeVpcsCommand
|
||||
void DescribeInternetGatewaysCommand
|
||||
void DescribeNatGatewaysCommand
|
||||
void DescribeRouteTablesCommand
|
||||
void DescribeAddressesCommand
|
||||
void CreateRouteTableCommand
|
||||
void ReleaseAddressCommand
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,386 @@
|
||||
import {
|
||||
CreateFargateProfileCommand,
|
||||
CreateNodegroupCommand,
|
||||
DeleteFargateProfileCommand,
|
||||
DeleteNodegroupCommand,
|
||||
DescribeClusterCommand,
|
||||
DescribeFargateProfileCommand,
|
||||
DescribeNodegroupCommand,
|
||||
EKSClient,
|
||||
ListClustersCommand,
|
||||
ListFargateProfilesCommand,
|
||||
ListNodegroupsCommand,
|
||||
type Cluster,
|
||||
type FargateProfile,
|
||||
type FargateProfileSelector,
|
||||
type Nodegroup,
|
||||
} from "@aws-sdk/client-eks";
|
||||
import { awsClients } from "../aws";
|
||||
|
||||
export type EksVpcConfig = {
|
||||
subnetIds: string[];
|
||||
securityGroupIds: string[];
|
||||
clusterSecurityGroupId?: string;
|
||||
vpcId?: string;
|
||||
endpointPublicAccess?: boolean;
|
||||
endpointPrivateAccess?: boolean;
|
||||
publicAccessCidrs: string[];
|
||||
};
|
||||
|
||||
export type EksCluster = {
|
||||
name: string;
|
||||
arn?: string;
|
||||
createdAt?: string;
|
||||
version?: string;
|
||||
endpoint?: string;
|
||||
roleArn?: string;
|
||||
status?: string;
|
||||
platformVersion?: string;
|
||||
certificateAuthority?: {
|
||||
data?: string;
|
||||
};
|
||||
resourcesVpcConfig?: EksVpcConfig;
|
||||
tags: Record<string, string>;
|
||||
nodegroupCount?: number;
|
||||
fargateProfileCount?: number;
|
||||
};
|
||||
|
||||
export type EksNodegroup = {
|
||||
name: string;
|
||||
arn?: string;
|
||||
clusterName: string;
|
||||
version?: string;
|
||||
releaseVersion?: string;
|
||||
createdAt?: string;
|
||||
modifiedAt?: string;
|
||||
status?: string;
|
||||
capacityType?: string;
|
||||
instanceTypes: string[];
|
||||
subnets: string[];
|
||||
nodeRole?: string;
|
||||
scalingConfig?: {
|
||||
minSize?: number;
|
||||
maxSize?: number;
|
||||
desiredSize?: number;
|
||||
};
|
||||
labels: Record<string, string>;
|
||||
tags: Record<string, string>;
|
||||
};
|
||||
|
||||
export type EksFargateProfile = {
|
||||
name: string;
|
||||
arn?: string;
|
||||
clusterName: string;
|
||||
createdAt?: string;
|
||||
status?: string;
|
||||
podExecutionRoleArn?: string;
|
||||
subnets: string[];
|
||||
selectors: {
|
||||
namespace?: string;
|
||||
labels: Record<string, string>;
|
||||
}[];
|
||||
tags: Record<string, string>;
|
||||
};
|
||||
|
||||
export type CreateEksNodegroupInput = {
|
||||
name: string;
|
||||
nodeRole: string;
|
||||
subnets: string[];
|
||||
instanceTypes?: string[];
|
||||
scalingConfig?: {
|
||||
minSize?: number;
|
||||
maxSize?: number;
|
||||
desiredSize?: number;
|
||||
};
|
||||
labels?: Record<string, string>;
|
||||
tags?: Record<string, string>;
|
||||
};
|
||||
|
||||
export type CreateEksFargateProfileInput = {
|
||||
name: string;
|
||||
podExecutionRoleArn: string;
|
||||
subnets?: string[];
|
||||
selectors: {
|
||||
namespace?: string;
|
||||
labels?: Record<string, string>;
|
||||
}[];
|
||||
tags?: Record<string, string>;
|
||||
};
|
||||
|
||||
function toEksVpcConfig(
|
||||
config: Cluster["resourcesVpcConfig"],
|
||||
): EksVpcConfig | undefined {
|
||||
if (!config) return undefined;
|
||||
|
||||
return {
|
||||
subnetIds: config.subnetIds ?? [],
|
||||
securityGroupIds: config.securityGroupIds ?? [],
|
||||
clusterSecurityGroupId: config.clusterSecurityGroupId,
|
||||
vpcId: config.vpcId,
|
||||
endpointPublicAccess: config.endpointPublicAccess,
|
||||
endpointPrivateAccess: config.endpointPrivateAccess,
|
||||
publicAccessCidrs: config.publicAccessCidrs ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
function toEksCluster(cluster: Cluster, nodegroupCount?: number): EksCluster {
|
||||
return {
|
||||
name: cluster.name ?? "",
|
||||
arn: cluster.arn,
|
||||
createdAt: cluster.createdAt?.toISOString(),
|
||||
version: cluster.version,
|
||||
endpoint: cluster.endpoint,
|
||||
roleArn: cluster.roleArn,
|
||||
status: cluster.status,
|
||||
platformVersion: cluster.platformVersion,
|
||||
certificateAuthority: cluster.certificateAuthority
|
||||
? { data: cluster.certificateAuthority.data }
|
||||
: undefined,
|
||||
resourcesVpcConfig: toEksVpcConfig(cluster.resourcesVpcConfig),
|
||||
tags: cluster.tags ?? {},
|
||||
nodegroupCount,
|
||||
};
|
||||
}
|
||||
|
||||
function toEksNodegroup(nodegroup: Nodegroup): EksNodegroup {
|
||||
return {
|
||||
name: nodegroup.nodegroupName ?? "",
|
||||
arn: nodegroup.nodegroupArn,
|
||||
clusterName: nodegroup.clusterName ?? "",
|
||||
version: nodegroup.version,
|
||||
releaseVersion: nodegroup.releaseVersion,
|
||||
createdAt: nodegroup.createdAt?.toISOString(),
|
||||
modifiedAt: nodegroup.modifiedAt?.toISOString(),
|
||||
status: nodegroup.status,
|
||||
capacityType: nodegroup.capacityType,
|
||||
instanceTypes: nodegroup.instanceTypes ?? [],
|
||||
subnets: nodegroup.subnets ?? [],
|
||||
nodeRole: nodegroup.nodeRole,
|
||||
scalingConfig: nodegroup.scalingConfig
|
||||
? {
|
||||
minSize: nodegroup.scalingConfig.minSize,
|
||||
maxSize: nodegroup.scalingConfig.maxSize,
|
||||
desiredSize: nodegroup.scalingConfig.desiredSize,
|
||||
}
|
||||
: undefined,
|
||||
labels: nodegroup.labels ?? {},
|
||||
tags: nodegroup.tags ?? {},
|
||||
};
|
||||
}
|
||||
|
||||
function toEksFargateProfile(profile: FargateProfile): EksFargateProfile {
|
||||
return {
|
||||
name: profile.fargateProfileName ?? "",
|
||||
arn: profile.fargateProfileArn,
|
||||
clusterName: profile.clusterName ?? "",
|
||||
createdAt: profile.createdAt?.toISOString(),
|
||||
status: profile.status,
|
||||
podExecutionRoleArn: profile.podExecutionRoleArn,
|
||||
subnets: profile.subnets ?? [],
|
||||
selectors: (profile.selectors ?? []).map(toEksFargateProfileSelector),
|
||||
tags: profile.tags ?? {},
|
||||
};
|
||||
}
|
||||
|
||||
function toEksFargateProfileSelector(selector: FargateProfileSelector) {
|
||||
return {
|
||||
namespace: selector.namespace,
|
||||
labels: selector.labels ?? {},
|
||||
};
|
||||
}
|
||||
|
||||
function getHttpStatus(error: unknown) {
|
||||
if (typeof error !== "object" || error === null) return undefined;
|
||||
const metadata = (error as { $metadata?: { httpStatusCode?: number } })
|
||||
.$metadata;
|
||||
return metadata?.httpStatusCode;
|
||||
}
|
||||
|
||||
export function createEksService(client: EKSClient = awsClients.eks) {
|
||||
async function listClusterNames(): Promise<string[]> {
|
||||
const clusters: string[] = [];
|
||||
let nextToken: string | undefined;
|
||||
|
||||
do {
|
||||
const res = await client.send(new ListClustersCommand({ nextToken }));
|
||||
clusters.push(...(res.clusters ?? []));
|
||||
nextToken = res.nextToken;
|
||||
} while (nextToken);
|
||||
|
||||
return clusters;
|
||||
}
|
||||
|
||||
async function listNodegroupNames(clusterName: string): Promise<string[]> {
|
||||
const nodegroups: string[] = [];
|
||||
let nextToken: string | undefined;
|
||||
|
||||
try {
|
||||
do {
|
||||
const res = await client.send(
|
||||
new ListNodegroupsCommand({ clusterName, nextToken }),
|
||||
);
|
||||
nodegroups.push(...(res.nodegroups ?? []));
|
||||
nextToken = res.nextToken;
|
||||
} while (nextToken);
|
||||
} catch (error) {
|
||||
if (getHttpStatus(error) === 404) return [];
|
||||
throw error;
|
||||
}
|
||||
|
||||
return nodegroups;
|
||||
}
|
||||
|
||||
async function listFargateProfileNames(clusterName: string): Promise<string[]> {
|
||||
const profiles: string[] = [];
|
||||
let nextToken: string | undefined;
|
||||
|
||||
try {
|
||||
do {
|
||||
const res = await client.send(
|
||||
new ListFargateProfilesCommand({ clusterName, nextToken }),
|
||||
);
|
||||
profiles.push(...(res.fargateProfileNames ?? []));
|
||||
nextToken = res.nextToken;
|
||||
} while (nextToken);
|
||||
} catch (error) {
|
||||
if (getHttpStatus(error) === 404) return [];
|
||||
throw error;
|
||||
}
|
||||
|
||||
return profiles;
|
||||
}
|
||||
|
||||
return {
|
||||
async listClusters(): Promise<EksCluster[]> {
|
||||
const names = await listClusterNames();
|
||||
|
||||
return Promise.all(
|
||||
names.map(async (name) => {
|
||||
const [cluster, nodegroups, fargateProfiles] = await Promise.all([
|
||||
this.describeCluster(name),
|
||||
listNodegroupNames(name),
|
||||
listFargateProfileNames(name),
|
||||
]);
|
||||
|
||||
return {
|
||||
...cluster,
|
||||
nodegroupCount: nodegroups.length,
|
||||
fargateProfileCount: fargateProfiles.length,
|
||||
};
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
||||
async describeCluster(name: string): Promise<EksCluster> {
|
||||
const res = await client.send(new DescribeClusterCommand({ name }));
|
||||
return toEksCluster(res.cluster ?? {});
|
||||
},
|
||||
|
||||
async listNodegroups(clusterName: string): Promise<EksNodegroup[]> {
|
||||
const names = await listNodegroupNames(clusterName);
|
||||
|
||||
return Promise.all(
|
||||
names.map((nodegroupName) =>
|
||||
this.describeNodegroup(clusterName, nodegroupName),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
async describeNodegroup(
|
||||
clusterName: string,
|
||||
nodegroupName: string,
|
||||
): Promise<EksNodegroup> {
|
||||
const res = await client.send(
|
||||
new DescribeNodegroupCommand({ clusterName, nodegroupName }),
|
||||
);
|
||||
return toEksNodegroup(res.nodegroup ?? {});
|
||||
},
|
||||
|
||||
async createNodegroup(
|
||||
clusterName: string,
|
||||
input: CreateEksNodegroupInput,
|
||||
): Promise<EksNodegroup> {
|
||||
const res = await client.send(
|
||||
new CreateNodegroupCommand({
|
||||
clusterName,
|
||||
nodegroupName: input.name,
|
||||
nodeRole: input.nodeRole,
|
||||
subnets: input.subnets,
|
||||
instanceTypes: input.instanceTypes,
|
||||
scalingConfig: input.scalingConfig,
|
||||
labels: input.labels,
|
||||
tags: input.tags,
|
||||
}),
|
||||
);
|
||||
return toEksNodegroup(res.nodegroup ?? {});
|
||||
},
|
||||
|
||||
async deleteNodegroup(
|
||||
clusterName: string,
|
||||
nodegroupName: string,
|
||||
): Promise<EksNodegroup> {
|
||||
const res = await client.send(
|
||||
new DeleteNodegroupCommand({ clusterName, nodegroupName }),
|
||||
);
|
||||
return toEksNodegroup(res.nodegroup ?? {});
|
||||
},
|
||||
|
||||
async listFargateProfiles(clusterName: string): Promise<EksFargateProfile[]> {
|
||||
const names = await listFargateProfileNames(clusterName);
|
||||
|
||||
return Promise.all(
|
||||
names.map((profileName) =>
|
||||
this.describeFargateProfile(clusterName, profileName),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
async describeFargateProfile(
|
||||
clusterName: string,
|
||||
profileName: string,
|
||||
): Promise<EksFargateProfile> {
|
||||
const res = await client.send(
|
||||
new DescribeFargateProfileCommand({
|
||||
clusterName,
|
||||
fargateProfileName: profileName,
|
||||
}),
|
||||
);
|
||||
return toEksFargateProfile(res.fargateProfile ?? {});
|
||||
},
|
||||
|
||||
async createFargateProfile(
|
||||
clusterName: string,
|
||||
input: CreateEksFargateProfileInput,
|
||||
): Promise<EksFargateProfile> {
|
||||
const res = await client.send(
|
||||
new CreateFargateProfileCommand({
|
||||
clusterName,
|
||||
fargateProfileName: input.name,
|
||||
podExecutionRoleArn: input.podExecutionRoleArn,
|
||||
subnets: input.subnets,
|
||||
selectors: input.selectors.map((selector) => ({
|
||||
namespace: selector.namespace,
|
||||
labels: selector.labels,
|
||||
})),
|
||||
tags: input.tags,
|
||||
}),
|
||||
);
|
||||
return toEksFargateProfile(res.fargateProfile ?? {});
|
||||
},
|
||||
|
||||
async deleteFargateProfile(
|
||||
clusterName: string,
|
||||
profileName: string,
|
||||
): Promise<EksFargateProfile> {
|
||||
const res = await client.send(
|
||||
new DeleteFargateProfileCommand({
|
||||
clusterName,
|
||||
fargateProfileName: profileName,
|
||||
}),
|
||||
);
|
||||
return toEksFargateProfile(res.fargateProfile ?? {});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const eksService = createEksService();
|
||||
@@ -0,0 +1,249 @@
|
||||
import {
|
||||
CreateDBInstanceCommand,
|
||||
CreateDBSnapshotCommand,
|
||||
DeleteDBInstanceCommand,
|
||||
DescribeDBInstancesCommand,
|
||||
DescribeDBSnapshotsCommand,
|
||||
DescribeOrderableDBInstanceOptionsCommand,
|
||||
ModifyDBInstanceCommand,
|
||||
type CreateDBInstanceCommandInput,
|
||||
type DBInstance,
|
||||
type DBSnapshot,
|
||||
type ModifyDBInstanceCommandInput,
|
||||
type RDSClient,
|
||||
} from "@aws-sdk/client-rds";
|
||||
import { awsClients } from "../aws";
|
||||
|
||||
export type RdsEndpoint = {
|
||||
address?: string;
|
||||
port?: number;
|
||||
hostedZoneId?: string;
|
||||
};
|
||||
|
||||
export type RdsVpcSecurityGroup = {
|
||||
id?: string;
|
||||
status?: string;
|
||||
};
|
||||
|
||||
export type RdsSubnet = {
|
||||
identifier?: string;
|
||||
availabilityZone?: string;
|
||||
status?: string;
|
||||
};
|
||||
|
||||
export type RdsDbSubnetGroup = {
|
||||
name?: string;
|
||||
vpcId?: string;
|
||||
status?: string;
|
||||
subnets: RdsSubnet[];
|
||||
};
|
||||
|
||||
export type RdsInstance = {
|
||||
identifier: string;
|
||||
arn?: string;
|
||||
resourceId?: string;
|
||||
createdAt?: string;
|
||||
status?: string;
|
||||
engine?: string;
|
||||
engineVersion?: string;
|
||||
instanceClass?: string;
|
||||
dbName?: string;
|
||||
masterUsername?: string;
|
||||
allocatedStorage?: number;
|
||||
storageType?: string;
|
||||
availabilityZone?: string;
|
||||
multiAz?: boolean;
|
||||
publiclyAccessible?: boolean;
|
||||
iamDatabaseAuthenticationEnabled?: boolean;
|
||||
preferredBackupWindow?: string;
|
||||
preferredMaintenanceWindow?: string;
|
||||
endpoint?: RdsEndpoint;
|
||||
vpcSecurityGroups: RdsVpcSecurityGroup[];
|
||||
subnetGroup?: RdsDbSubnetGroup;
|
||||
optionGroupName?: string;
|
||||
autoMinorVersionUpgrade?: boolean;
|
||||
};
|
||||
|
||||
export type RdsSnapshot = {
|
||||
identifier: string;
|
||||
instanceIdentifier?: string;
|
||||
arn?: string;
|
||||
status?: string;
|
||||
engine?: string;
|
||||
engineVersion?: string;
|
||||
allocatedStorage?: number;
|
||||
snapshotType?: string;
|
||||
createdAt?: string;
|
||||
port?: number;
|
||||
availabilityZone?: string;
|
||||
vpcId?: string;
|
||||
};
|
||||
|
||||
function toRdsInstance(instance: DBInstance): RdsInstance {
|
||||
return {
|
||||
identifier: instance.DBInstanceIdentifier ?? "",
|
||||
arn: instance.DBInstanceArn,
|
||||
resourceId: instance.DbiResourceId,
|
||||
createdAt: instance.InstanceCreateTime?.toISOString(),
|
||||
status: instance.DBInstanceStatus,
|
||||
engine: instance.Engine,
|
||||
engineVersion: instance.EngineVersion,
|
||||
instanceClass: instance.DBInstanceClass,
|
||||
dbName: instance.DBName,
|
||||
masterUsername: instance.MasterUsername,
|
||||
allocatedStorage: instance.AllocatedStorage,
|
||||
storageType: instance.StorageType,
|
||||
availabilityZone: instance.AvailabilityZone,
|
||||
multiAz: instance.MultiAZ,
|
||||
publiclyAccessible: instance.PubliclyAccessible,
|
||||
iamDatabaseAuthenticationEnabled: instance.IAMDatabaseAuthenticationEnabled,
|
||||
preferredBackupWindow: instance.PreferredBackupWindow,
|
||||
preferredMaintenanceWindow: instance.PreferredMaintenanceWindow,
|
||||
endpoint: instance.Endpoint
|
||||
? {
|
||||
address: instance.Endpoint.Address,
|
||||
port: instance.Endpoint.Port,
|
||||
hostedZoneId: instance.Endpoint.HostedZoneId,
|
||||
}
|
||||
: undefined,
|
||||
vpcSecurityGroups: (instance.VpcSecurityGroups ?? []).map((group) => ({
|
||||
id: group.VpcSecurityGroupId,
|
||||
status: group.Status,
|
||||
})),
|
||||
subnetGroup: instance.DBSubnetGroup
|
||||
? {
|
||||
name: instance.DBSubnetGroup.DBSubnetGroupName,
|
||||
vpcId: instance.DBSubnetGroup.VpcId,
|
||||
status: instance.DBSubnetGroup.SubnetGroupStatus,
|
||||
subnets: (instance.DBSubnetGroup.Subnets ?? []).map((subnet) => ({
|
||||
identifier: subnet.SubnetIdentifier,
|
||||
availabilityZone: subnet.SubnetAvailabilityZone?.Name,
|
||||
status: subnet.SubnetStatus,
|
||||
})),
|
||||
}
|
||||
: undefined,
|
||||
optionGroupName: instance.OptionGroupMemberships?.[0]?.OptionGroupName,
|
||||
autoMinorVersionUpgrade: instance.AutoMinorVersionUpgrade,
|
||||
};
|
||||
}
|
||||
|
||||
function toRdsSnapshot(snapshot: DBSnapshot): RdsSnapshot {
|
||||
return {
|
||||
identifier: snapshot.DBSnapshotIdentifier ?? "",
|
||||
instanceIdentifier: snapshot.DBInstanceIdentifier,
|
||||
arn: snapshot.DBSnapshotArn,
|
||||
status: snapshot.Status,
|
||||
engine: snapshot.Engine,
|
||||
engineVersion: snapshot.EngineVersion,
|
||||
allocatedStorage: snapshot.AllocatedStorage,
|
||||
snapshotType: snapshot.SnapshotType,
|
||||
createdAt: snapshot.SnapshotCreateTime?.toISOString(),
|
||||
port: snapshot.Port,
|
||||
availabilityZone: snapshot.AvailabilityZone,
|
||||
vpcId: snapshot.VpcId,
|
||||
};
|
||||
}
|
||||
|
||||
export function createRdsService(client: RDSClient = awsClients.rds) {
|
||||
return {
|
||||
async listInstances(): Promise<RdsInstance[]> {
|
||||
const instances: RdsInstance[] = [];
|
||||
let marker: string | undefined;
|
||||
|
||||
do {
|
||||
const res = await client.send(
|
||||
new DescribeDBInstancesCommand({ Marker: marker }),
|
||||
);
|
||||
instances.push(...(res.DBInstances ?? []).map(toRdsInstance));
|
||||
marker = res.Marker;
|
||||
} while (marker);
|
||||
|
||||
return instances;
|
||||
},
|
||||
|
||||
async describeInstance(identifier: string): Promise<RdsInstance> {
|
||||
const res = await client.send(
|
||||
new DescribeDBInstancesCommand({ DBInstanceIdentifier: identifier }),
|
||||
);
|
||||
return toRdsInstance(res.DBInstances?.[0] ?? {});
|
||||
},
|
||||
|
||||
async createInstance(input: CreateDBInstanceCommandInput): Promise<RdsInstance> {
|
||||
const res = await client.send(new CreateDBInstanceCommand(input));
|
||||
return toRdsInstance(res.DBInstance ?? {});
|
||||
},
|
||||
|
||||
async modifyInstance(input: ModifyDBInstanceCommandInput): Promise<RdsInstance> {
|
||||
const res = await client.send(new ModifyDBInstanceCommand(input));
|
||||
return toRdsInstance(res.DBInstance ?? {});
|
||||
},
|
||||
|
||||
async deleteInstance(
|
||||
identifier: string,
|
||||
skipFinalSnapshot = true,
|
||||
finalDBSnapshotIdentifier?: string,
|
||||
): Promise<void> {
|
||||
await client.send(
|
||||
new DeleteDBInstanceCommand({
|
||||
DBInstanceIdentifier: identifier,
|
||||
SkipFinalSnapshot: skipFinalSnapshot,
|
||||
...(finalDBSnapshotIdentifier
|
||||
? { FinalDBSnapshotIdentifier: finalDBSnapshotIdentifier }
|
||||
: {}),
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
||||
async listSnapshots(instanceIdentifier?: string): Promise<RdsSnapshot[]> {
|
||||
const snapshots: RdsSnapshot[] = [];
|
||||
let marker: string | undefined;
|
||||
|
||||
do {
|
||||
const res = await client.send(
|
||||
new DescribeDBSnapshotsCommand({
|
||||
DBInstanceIdentifier: instanceIdentifier,
|
||||
Marker: marker,
|
||||
}),
|
||||
);
|
||||
snapshots.push(...(res.DBSnapshots ?? []).map(toRdsSnapshot));
|
||||
marker = res.Marker;
|
||||
} while (marker);
|
||||
|
||||
return snapshots;
|
||||
},
|
||||
|
||||
async createSnapshot(instanceIdentifier: string, snapshotIdentifier: string): Promise<RdsSnapshot> {
|
||||
const res = await client.send(
|
||||
new CreateDBSnapshotCommand({
|
||||
DBInstanceIdentifier: instanceIdentifier,
|
||||
DBSnapshotIdentifier: snapshotIdentifier,
|
||||
}),
|
||||
);
|
||||
return toRdsSnapshot(res.DBSnapshot ?? {});
|
||||
},
|
||||
|
||||
async listOrderableInstanceClasses(engine: string): Promise<string[]> {
|
||||
const classes = new Set<string>();
|
||||
let marker: string | undefined;
|
||||
|
||||
do {
|
||||
const res = await client.send(
|
||||
new DescribeOrderableDBInstanceOptionsCommand({
|
||||
Engine: engine,
|
||||
Marker: marker,
|
||||
}),
|
||||
);
|
||||
for (const option of res.OrderableDBInstanceOptions ?? []) {
|
||||
if (option.DBInstanceClass) {
|
||||
classes.add(option.DBInstanceClass);
|
||||
}
|
||||
}
|
||||
marker = res.Marker;
|
||||
} while (marker);
|
||||
|
||||
return Array.from(classes);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const rdsService = createRdsService();
|
||||
Reference in New Issue
Block a user