140 lines
11 KiB
TypeScript
140 lines
11 KiB
TypeScript
import {describe, expect, test} from 'bun:test'
|
|
import {CreateClusterCommand, CreateServiceCommand, DeleteServiceCommand, DescribeClustersCommand, DescribeServicesCommand, DescribeTaskDefinitionCommand, DescribeTasksCommand, ListClustersCommand, ListServicesCommand, ListTaskDefinitionsCommand, ListTasksCommand, RegisterTaskDefinitionCommand, type ECSClient} from '@aws-sdk/client-ecs'
|
|
import {DescribeLoadBalancersCommand, DescribeTargetGroupsCommand, DescribeTargetHealthCommand, type ElasticLoadBalancingV2Client} from '@aws-sdk/client-elastic-load-balancing-v2'
|
|
import {AwsEcsAdapter} from './AwsEcsAdapter'
|
|
import {AwsFargateAdapter} from './AwsFargateAdapter'
|
|
|
|
const cluster = 'orders-cluster'
|
|
|
|
describe('AwsEcsAdapter', () => {
|
|
test('lists EC2 and Fargate services with task, IP target, and ALB health', async () => {
|
|
const ecs = {async send(command: object) {
|
|
if (command instanceof ListClustersCommand) return {clusterArns: [cluster]}
|
|
if (command instanceof ListServicesCommand) return {serviceArns: ['orders', 'fargate']}
|
|
if (command instanceof DescribeServicesCommand) return {services: [
|
|
{serviceName: 'orders', serviceArn: 'arn:aws:ecs:us-east-1:000000000000:service/orders', launchType: 'EC2', status: 'ACTIVE', desiredCount: 2, runningCount: 1, pendingCount: 1, taskDefinition: 'orders:4'},
|
|
{serviceName: 'fargate', serviceArn: 'arn:aws:ecs:us-east-1:000000000000:service/fargate', launchType: 'FARGATE', status: 'ACTIVE', desiredCount: 1, runningCount: 1, taskDefinition: 'fargate:1', networkConfiguration: {awsvpcConfiguration: {subnets: ['subnet-1'], securityGroups: ['sg-1'], assignPublicIp: 'ENABLED'}}, loadBalancers: [{targetGroupArn: 'tg-1', containerName: 'app', containerPort: 8080}]},
|
|
]}
|
|
if (command instanceof ListTasksCommand) return {taskArns: ['task-1']}
|
|
if (command instanceof DescribeTasksCommand) return {tasks: [{taskArn: 'task-1', lastStatus: 'RUNNING', healthStatus: 'HEALTHY', connectivity: 'CONNECTED'}]}
|
|
return {}
|
|
}} as unknown as ECSClient
|
|
const elb = {async send(command: object) {
|
|
if (command instanceof DescribeTargetGroupsCommand) return {TargetGroups: [{TargetType: 'ip', LoadBalancerArns: ['alb-1']}]}
|
|
if (command instanceof DescribeTargetHealthCommand) return {TargetHealthDescriptions: [{Target: {Id: '10.0.1.5', Port: 8080}, TargetHealth: {State: 'healthy'}}]}
|
|
if (command instanceof DescribeLoadBalancersCommand) return {LoadBalancers: [{DNSName: 'orders.example.test', Scheme: 'internet-facing', State: {Code: 'active'}}]}
|
|
return {}
|
|
}} as unknown as ElasticLoadBalancingV2Client
|
|
|
|
const services = await new AwsEcsAdapter(ecs, elb).list()
|
|
|
|
expect(services).toHaveLength(2)
|
|
expect(services[0]).toMatchObject({metadata: {launchType: 'EC2', tasks: [{healthStatus: 'HEALTHY'}]}})
|
|
expect(services[1]).toMatchObject({metadata: {launchType: 'FARGATE', networkConfiguration: {subnets: ['subnet-1']}, loadBalancers: [{targetType: 'ip', targets: [{state: 'healthy'}], endpoints: [{dnsName: 'orders.example.test', state: 'active'}]}]}})
|
|
})
|
|
|
|
test('creates an awsvpc Fargate service and force deletes it', async () => {
|
|
const sent: object[] = []
|
|
const ecs = {async send(command: object) {
|
|
sent.push(command)
|
|
if (command instanceof CreateServiceCommand) return {service: {serviceName: 'orders', serviceArn: 'arn:aws:ecs:us-east-1:000000000000:service/orders', launchType: 'FARGATE', status: 'ACTIVE', desiredCount: 1, runningCount: 0, taskDefinition: 'orders:1'}}
|
|
if (command instanceof ListTasksCommand) return {taskArns: []}
|
|
return {}
|
|
}} as unknown as ECSClient
|
|
const elb = {async send() { return {} }} as unknown as ElasticLoadBalancingV2Client
|
|
const adapter = new AwsEcsAdapter(ecs, elb)
|
|
|
|
await adapter.create({values: {cluster, serviceName: 'orders', taskDefinition: 'orders:1', launchType: 'FARGATE', deploymentController: 'CODE_DEPLOY', subnets: 'subnet-1, subnet-2', securityGroups: 'sg-1', assignPublicIp: 'ENABLED', targetGroupArn: 'tg-1', containerName: 'app', containerPort: '8080'}})
|
|
await adapter.delete(`${cluster}|orders`)
|
|
|
|
expect((sent[0] as CreateServiceCommand).input).toMatchObject({cluster, serviceName: 'orders', launchType: 'FARGATE', deploymentController: {type: 'CODE_DEPLOY'}, networkConfiguration: {awsvpcConfiguration: {subnets: ['subnet-1', 'subnet-2'], securityGroups: ['sg-1'], assignPublicIp: 'ENABLED'}}, loadBalancers: [{targetGroupArn: 'tg-1', containerName: 'app', containerPort: 8080}]})
|
|
expect((sent.at(-1) as DeleteServiceCommand).input).toEqual({cluster, service: 'orders', force: true})
|
|
})
|
|
|
|
test('creates and lists clusters through ECS APIs', async () => {
|
|
const sent: object[] = []
|
|
const clusterArn = 'arn:aws:ecs:us-east-1:000000000000:cluster/orders'
|
|
const ecs = {async send(command: object) {
|
|
sent.push(command)
|
|
if (command instanceof CreateClusterCommand) return {cluster: {clusterName: 'orders', clusterArn, status: 'ACTIVE'}}
|
|
if (command instanceof ListClustersCommand) return {clusterArns: [clusterArn]}
|
|
if (command instanceof DescribeClustersCommand) return {clusters: [{clusterName: 'orders', clusterArn, status: 'ACTIVE', runningTasksCount: 2}]}
|
|
if (command instanceof ListServicesCommand || command instanceof ListTaskDefinitionsCommand) return {}
|
|
return {}
|
|
}} as unknown as ECSClient
|
|
const adapter = new AwsEcsAdapter(ecs, {async send() { return {} }} as unknown as ElasticLoadBalancingV2Client)
|
|
|
|
const created = await adapter.create({values: {resourceKind: 'cluster', clusterName: 'orders'}})
|
|
const resources = await adapter.list()
|
|
|
|
expect(sent[0]).toBeInstanceOf(CreateClusterCommand)
|
|
expect((sent[0] as CreateClusterCommand).input).toEqual({clusterName: 'orders'})
|
|
expect(created).toMatchObject({id: `cluster:${clusterArn}`, type: 'ecs-cluster', name: 'orders'})
|
|
expect(resources).toContainEqual(expect.objectContaining({type: 'ecs-cluster', name: 'orders', metadata: expect.objectContaining({runningTasksCount: 2})}))
|
|
})
|
|
|
|
test('registers and lists only awsvpc Fargate task definitions', async () => {
|
|
const sent: object[] = []
|
|
const fargateArn = 'arn:aws:ecs:us-east-1:000000000000:task-definition/orders:1'
|
|
const ec2Arn = 'arn:aws:ecs:us-east-1:000000000000:task-definition/worker:1'
|
|
const fargate = {taskDefinitionArn: fargateArn, family: 'orders', revision: 1, status: 'ACTIVE', cpu: '256', memory: '512', networkMode: 'awsvpc', requiresCompatibilities: ['FARGATE'], containerDefinitions: [{name: 'web', image: 'orders:latest', portMappings: [{containerPort: 8080}]}]}
|
|
const ecs = {async send(command: object) {
|
|
sent.push(command)
|
|
if (command instanceof RegisterTaskDefinitionCommand) return {taskDefinition: fargate}
|
|
if (command instanceof ListClustersCommand) return {}
|
|
if (command instanceof ListTaskDefinitionsCommand) return {taskDefinitionArns: [fargateArn, ec2Arn]}
|
|
if (command instanceof DescribeTaskDefinitionCommand) return {taskDefinition: command.input.taskDefinition === fargateArn ? fargate : {taskDefinitionArn: ec2Arn, family: 'worker', networkMode: 'bridge', requiresCompatibilities: ['EC2']}}
|
|
return {}
|
|
}} as unknown as ECSClient
|
|
const adapter = new AwsEcsAdapter(ecs, {async send() { return {} }} as unknown as ElasticLoadBalancingV2Client)
|
|
|
|
const created = await adapter.create({values: {resourceKind: 'taskDefinition', family: 'orders', image: 'orders:latest', containerName: 'web', containerPort: '8080', cpu: '256', memory: '512'}})
|
|
const resources = await adapter.list()
|
|
|
|
expect((sent[0] as RegisterTaskDefinitionCommand).input).toEqual({family: 'orders', cpu: '256', memory: '512', networkMode: 'awsvpc', requiresCompatibilities: ['FARGATE'], containerDefinitions: [{name: 'web', image: 'orders:latest', essential: true, portMappings: [{containerPort: 8080, protocol: 'tcp'}]}]})
|
|
expect(created).toMatchObject({type: 'ecs-task-definition', name: 'orders:1', metadata: {image: 'orders:latest', containerPort: 8080}})
|
|
expect(resources).toHaveLength(1)
|
|
expect(resources[0]).toMatchObject({id: `task-definition:${fargateArn}`, type: 'ecs-task-definition'})
|
|
})
|
|
|
|
test('does not present stale inactive counts as running', async () => {
|
|
const ecs = {async send(command: object) {
|
|
if (command instanceof DescribeServicesCommand) return {services: [{serviceName: 'orders', status: 'INACTIVE', desiredCount: 2, runningCount: 2, pendingCount: 1, deployments: [{desiredCount: 2, runningCount: 2}]}]}
|
|
return {}
|
|
}} as unknown as ECSClient
|
|
const elb = {async send() { return {} }} as unknown as ElasticLoadBalancingV2Client
|
|
|
|
const service = await new AwsEcsAdapter(ecs, elb).get(`${cluster}|orders`)
|
|
|
|
expect(service).toMatchObject({metadata: {inactive: true, desiredCount: 0, runningCount: 0, pendingCount: 0, deployments: [{desiredCount: 0, runningCount: 0}]}})
|
|
})
|
|
|
|
test('Fargate adapter lists and creates only Fargate services', async () => {
|
|
const sent: object[] = []
|
|
const ecs = {async send(command: object) {
|
|
sent.push(command)
|
|
if (command instanceof ListClustersCommand) return {clusterArns: [cluster]}
|
|
if (command instanceof ListServicesCommand) return {serviceArns: ['ec2', 'fargate']}
|
|
if (command instanceof DescribeServicesCommand) return {services: [
|
|
{serviceName: 'ec2', launchType: 'EC2', status: 'ACTIVE'},
|
|
{serviceName: 'fargate', launchType: 'FARGATE', status: 'ACTIVE'},
|
|
]}
|
|
if (command instanceof CreateServiceCommand) return {service: {serviceName: 'created', launchType: 'FARGATE', status: 'ACTIVE'}}
|
|
if (command instanceof ListTasksCommand) return {taskArns: []}
|
|
return {}
|
|
}} as unknown as ECSClient
|
|
const elb = {async send() { return {} }} as unknown as ElasticLoadBalancingV2Client
|
|
const adapter = new AwsFargateAdapter(new AwsEcsAdapter(ecs, elb))
|
|
|
|
const services = await adapter.list()
|
|
const created = await adapter.create({values: {cluster, serviceName: 'created', taskDefinition: 'created:1', launchType: 'EC2', subnets: 'subnet-1'}})
|
|
|
|
expect(services).toHaveLength(1)
|
|
expect(services[0]).toMatchObject({service: 'fargate', name: 'fargate', metadata: {launchType: 'FARGATE'}})
|
|
expect(created).toMatchObject({service: 'fargate', metadata: {launchType: 'FARGATE'}})
|
|
expect(sent.find((command) => command instanceof CreateServiceCommand)).toMatchObject({input: {launchType: 'FARGATE'}})
|
|
expect(adapter.schema().fields.find((field) => field.name === 'launchType')).toBeUndefined()
|
|
expect(adapter.schema().fields.find((field) => field.name === 'subnets')).toMatchObject({required: true})
|
|
})
|
|
})
|