Skip to content

Commit ac2b830

Browse files
authored
Merge pull request #156 from cloudgraphdev/feature/EP-3205-support-missing-ecs-services
feat(aws): support missing ecs services
2 parents d1c09b6 + 87865d2 commit ac2b830

File tree

7 files changed

+170
-102
lines changed

7 files changed

+170
-102
lines changed

src/enums/relations.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export default {
77
apiGatewayRestApi: ['apiGatewayResource', 'apiGatewayStage'],
88
route53HostedZone: ['route53Record'],
99
emrCluster: ['emrInstance', 'emrStep'],
10+
ecsCluster: ['ecsService'],
1011
ecsService: ['ecsTaskSet', 'ecsTaskDefinition'],
1112
iamInstanceProfile: ['ec2Instance'],
1213
apiGatewayDomainName: ['apiGatewayRestApi'],

src/services/ecsCluster/data.ts

Lines changed: 56 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,121 +1,97 @@
1+
import CloudGraph from '@cloudgraph/sdk'
12
import { Config } from 'aws-sdk'
2-
import { AWSError } from 'aws-sdk/lib/error'
33
import ECS, {
44
Cluster,
55
ListClustersRequest,
66
ListClustersResponse,
77
} from 'aws-sdk/clients/ecs'
8-
import CloudGraph from '@cloudgraph/sdk'
8+
import { AWSError } from 'aws-sdk/lib/error'
99
import groupBy from 'lodash/groupBy'
1010
import isEmpty from 'lodash/isEmpty'
1111
import awsLoggerText from '../../properties/logger'
1212
import { AwsTag, TagMap } from '../../types'
13-
import { convertAwsTagsToTagMap } from '../../utils/format'
14-
import AwsErrorLog from '../../utils/errorLog'
1513
import { initTestEndpoint } from '../../utils'
14+
import AwsErrorLog from '../../utils/errorLog'
15+
import { convertAwsTagsToTagMap } from '../../utils/format'
1616

1717
const lt = { ...awsLoggerText }
1818
const { logger } = CloudGraph
1919
const serviceName = 'ECS cluster'
2020
const errorLog = new AwsErrorLog(serviceName)
2121
const endpoint = initTestEndpoint(serviceName)
22-
22+
const MAX_ITEMS = 100
2323
export interface RawAwsEcsCluster extends Omit<Cluster, 'Tags'> {
2424
region: string
2525
Tags?: TagMap
2626
}
2727

28-
export default async ({
29-
regions,
30-
config,
31-
}: {
32-
regions: string
33-
config: Config
34-
}): Promise<{ [region: string]: RawAwsEcsCluster[] }> =>
35-
new Promise(async resolve => {
36-
/**
37-
* Get the arns of all the ECS Clusters
38-
*/
39-
const ecsClusterData: RawAwsEcsCluster[] = []
40-
const ecsClusterArns: Array<{ region: string; clusterArns: string[] }> = []
41-
const regionPromises = []
42-
43-
const listClusterArns = async ({
44-
ecs,
45-
region,
46-
token: nextToken = '',
47-
resolveRegion,
48-
}: {
49-
ecs: ECS
50-
region: string
51-
token?: string
52-
resolveRegion: Function
53-
}) => {
54-
let args: ListClustersRequest = {}
55-
56-
if (nextToken) {
57-
args = { ...args, nextToken }
28+
export const listClusterArnsForRegion = async (ecs: ECS): Promise<string[]> =>
29+
new Promise<string[]>(resolve => {
30+
const clusterArnList: string[] = []
31+
const args: ListClustersRequest = {}
32+
const listAllClusterArns = (token?: string): void => {
33+
args.maxResults = MAX_ITEMS
34+
if (token) {
35+
args.nextToken = token
5836
}
59-
60-
return ecs.listClusters(
61-
args,
62-
(err: AWSError, data: ListClustersResponse) => {
37+
try {
38+
ecs.listClusters(args, (err: AWSError, data: ListClustersResponse) => {
6339
if (err) {
6440
errorLog.generateAwsErrorLog({
6541
functionName: 'ecs:listClusters',
6642
err,
6743
})
6844
}
6945

70-
/**
71-
* No Cluster data for this region
72-
*/
7346
if (isEmpty(data)) {
74-
return resolveRegion()
47+
return resolve([])
7548
}
7649

77-
const { clusterArns, nextToken: token } = data
78-
79-
logger.debug(lt.fetchedEcsClusters(clusterArns.length))
50+
const { clusterArns = [], nextToken } = data || {}
8051

81-
/**
82-
* No Clusters Found
83-
*/
52+
clusterArnList.push(...clusterArns)
8453

85-
if (isEmpty(clusterArns)) {
86-
return resolveRegion()
54+
if (nextToken) {
55+
listAllClusterArns(nextToken)
56+
} else {
57+
resolve(clusterArnList)
8758
}
59+
})
60+
} catch (error) {
61+
resolve([])
62+
}
63+
}
64+
listAllClusterArns()
65+
})
8866

89-
/**
90-
* Check to see if there are more
91-
*/
92-
93-
if (token) {
94-
listClusterArns({ region, token, ecs, resolveRegion })
95-
}
96-
97-
/**
98-
* Add the found Clusters to the ecsClusterArns
99-
*/
100-
101-
ecsClusterArns.push({ region, clusterArns })
102-
103-
/**
104-
* If this is the last page of data then return
105-
*/
67+
export default async ({
68+
regions,
69+
config,
70+
}: {
71+
regions: string
72+
config: Config
73+
}): Promise<{ [region: string]: RawAwsEcsCluster[] }> =>
74+
new Promise(async resolve => {
75+
/**
76+
* Get all ECS Clusters Arns
77+
*/
78+
const ecsClusterData: RawAwsEcsCluster[] = []
79+
const ecsClusterArns: Array<{ region: string; clusterArns: string[] }> = []
80+
const regionPromises = []
10681

107-
if (!token) {
108-
resolveRegion()
109-
}
82+
regions.split(',').forEach(region => {
83+
const ecs = new ECS({ ...config, region, endpoint })
84+
const regionPromise = new Promise<void>(async resolveRegion => {
85+
const clusterArnsList = await listClusterArnsForRegion(ecs)
86+
if (!isEmpty(clusterArnsList)) {
87+
ecsClusterArns.push({
88+
clusterArns: clusterArnsList,
89+
region,
90+
})
11091
}
111-
)
112-
}
92+
resolveRegion()
93+
})
11394

114-
regions.split(',').map(region => {
115-
const ecs = new ECS({ ...config, region, endpoint })
116-
const regionPromise = new Promise<void>(resolveRegion =>
117-
listClusterArns({ ecs, region, resolveRegion })
118-
)
11995
regionPromises.push(regionPromise)
12096
})
12197

@@ -126,10 +102,10 @@ export default async ({
126102
*/
127103

128104
const clusterPromises = ecsClusterArns.map(
129-
async ({ region, clusterArns: clusters }) =>
105+
async ({ region, clusterArns }) =>
130106
new Promise<void>(resolveEcsData =>
131107
new ECS({ ...config, region, endpoint }).describeClusters(
132-
{ clusters },
108+
{ clusters: clusterArns },
133109
(err, data) => {
134110
if (err) {
135111
errorLog.generateAwsErrorLog({

src/services/ecsService/data.ts

Lines changed: 102 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,132 @@
1-
import { Config } from 'aws-sdk'
2-
import ECS, { Service } from 'aws-sdk/clients/ecs'
31
import CloudGraph from '@cloudgraph/sdk'
4-
import flatMap from 'lodash/flatMap'
2+
import { Config } from 'aws-sdk'
3+
import ECS, { ListServicesRequest, Service } from 'aws-sdk/clients/ecs'
54
import groupBy from 'lodash/groupBy'
65
import isEmpty from 'lodash/isEmpty'
6+
import services from '../../enums/services'
77
import awsLoggerText from '../../properties/logger'
88
import { AwsTag, TagMap } from '../../types'
9-
import { convertAwsTagsToTagMap } from '../../utils/format'
10-
import AwsErrorLog from '../../utils/errorLog'
119
import { initTestEndpoint } from '../../utils'
12-
import EcsClusterClass from '../ecsCluster'
13-
import { RawAwsEcsCluster } from '../ecsCluster/data'
10+
import AwsErrorLog from '../../utils/errorLog'
11+
import { convertAwsTagsToTagMap } from '../../utils/format'
12+
import { listClusterArnsForRegion, RawAwsEcsCluster } from '../ecsCluster/data'
1413

1514
const lt = { ...awsLoggerText }
1615
const { logger } = CloudGraph
1716
const serviceName = 'ECS service'
1817
const errorLog = new AwsErrorLog(serviceName)
1918
const endpoint = initTestEndpoint(serviceName)
20-
19+
const MAX_ITEMS = 100
2120
export interface RawAwsEcsService extends Service {
2221
region: string
2322
Tags?: TagMap
23+
ClusterArn: string
2424
}
2525

26+
export const listServicesArnForRegion = async (
27+
ecs: ECS,
28+
cluster: string
29+
): Promise<string[]> =>
30+
new Promise<string[]>(resolve => {
31+
const serviceArnList: string[] = []
32+
const getRestApisOpts: ListServicesRequest = {
33+
cluster,
34+
maxResults: MAX_ITEMS,
35+
}
36+
const listAllServiceArns = (token?: string): void => {
37+
if (token) {
38+
getRestApisOpts.nextToken = token
39+
}
40+
try {
41+
ecs.listServices({ cluster }, (err, data) => {
42+
if (err) {
43+
errorLog.generateAwsErrorLog({
44+
functionName: 'ecs:listServices',
45+
err,
46+
})
47+
}
48+
49+
if (isEmpty(data)) {
50+
return resolve([])
51+
}
52+
53+
const { serviceArns = [], nextToken } = data
54+
55+
serviceArnList.push(...serviceArns)
56+
57+
if (nextToken) {
58+
listAllServiceArns(nextToken)
59+
} else {
60+
resolve(serviceArnList)
61+
}
62+
})
63+
} catch (error) {
64+
resolve([])
65+
}
66+
}
67+
listAllServiceArns()
68+
})
69+
2670
export default async ({
2771
regions,
2872
config,
73+
rawData,
2974
}: {
3075
regions: string
3176
config: Config
77+
rawData: any
3278
}): Promise<{
3379
[region: string]: RawAwsEcsService[]
3480
}> =>
3581
new Promise(async resolve => {
3682
const ecsServices: RawAwsEcsService[] = []
37-
const ecsClusterClass = new EcsClusterClass({ logger: CloudGraph.logger })
38-
const clusterResult = await ecsClusterClass.getData({
39-
...config,
40-
regions,
41-
})
42-
const ecsClusters: RawAwsEcsCluster[] = flatMap(clusterResult)
83+
const regionPromises = []
84+
const ecsClusterData: Array<{ region: string; clusterArn: string }> = []
85+
86+
const existingData: { [property: string]: RawAwsEcsCluster[] } =
87+
rawData?.find(({ name }) => name === services.ecsCluster)?.data || {}
88+
89+
if (isEmpty(existingData)) {
90+
// Refresh data
91+
regions.split(',').map(region => {
92+
const ecsClient = new ECS({ ...config, region, endpoint })
93+
const regionPromise = new Promise<void>(async resolveRegion => {
94+
const clusterArnList = await listClusterArnsForRegion(ecsClient)
95+
if (!isEmpty(clusterArnList)) {
96+
ecsClusterData.push(
97+
...clusterArnList.map(arn => ({
98+
clusterArn: arn,
99+
region,
100+
}))
101+
)
102+
}
103+
resolveRegion()
104+
})
105+
regionPromises.push(regionPromise)
106+
return null
107+
})
108+
await Promise.all(regionPromises)
109+
} else {
110+
// Uses existing data
111+
regions.split(',').map(region => {
112+
if (!isEmpty(existingData[region])) {
113+
ecsClusterData.push(
114+
...existingData[region].map(ecsCluster => ({
115+
clusterArn: ecsCluster.clusterArn,
116+
region,
117+
}))
118+
)
119+
}
120+
return null
121+
})
122+
}
43123

44124
/**
45125
* Get the arns of all the services
46126
*/
47127
let ecsServiceArns: any = await Promise.all(
48-
ecsClusters.map(
49-
async ({ clusterName: cluster, region }) =>
128+
ecsClusterData.map(
129+
async ({ clusterArn: cluster, region }) =>
50130
new Promise(resolveEcsData =>
51131
new ECS({ ...config, region, endpoint }).listServices(
52132
{ cluster },
@@ -81,10 +161,10 @@ export default async ({
81161
* Get all the details for each service
82162
*/
83163
const ecsServicePromises = ecsServiceArns.map(
84-
async ({ region, serviceArns: services, cluster }) =>
164+
async ({ region, serviceArns, cluster }) =>
85165
new Promise<void>(resolveEcsData =>
86166
new ECS({ ...config, region, endpoint }).describeServices(
87-
{ services, cluster },
167+
{ services: serviceArns, cluster },
88168
(err, data) => {
89169
if (err) {
90170
errorLog.generateAwsErrorLog({
@@ -97,14 +177,15 @@ export default async ({
97177
return resolveEcsData()
98178
}
99179

100-
const { services = [] } = data
180+
const { services: servicesList = [] } = data
101181

102-
logger.debug(lt.fetchedEcsServices(services.length))
182+
logger.debug(lt.fetchedEcsServices(servicesList.length))
103183

104184
ecsServices.push(
105-
...services.map(service => ({
185+
...servicesList.map(service => ({
106186
region,
107187
...service,
188+
ClusterArn: cluster,
108189
Tags: convertAwsTagsToTagMap(service.tags as AwsTag[]),
109190
}))
110191
)

0 commit comments

Comments
 (0)