Skip to content

Commit c95a4e3

Browse files
authored
Merge pull request #60 from cloudgraphdev/feature/CG-1192
feat: Add apiGatewayHttpApi (apiGatewayV2) and apiGatewayDomainName services
2 parents 052bc70 + 313d9f4 commit c95a4e3

26 files changed

Lines changed: 799 additions & 104 deletions

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ CloudGraph AWS Provider will ask you what regions you would like to crawl and wi
7070
| Service | Relations |
7171
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
7272
| alb | ec2, elasticBeanstalkEnv, route53Record, securityGroup, subnet, vpc, wafV2WebAcl |
73-
| apiGatewayRestApi | apiGatewayResource, apiGatewayStage, route53Record |
73+
| apiGatewayDomainName | apiGatewayHttpApi, apiGatewayRestApi |
74+
| apiGatewayHttpApi | apiGatewayDomainName |
75+
| apiGatewayRestApi | apiGatewayDomainName, apiGatewayResource, apiGatewayStage, route53Record |
7476
| apiGatewayStage | apiGatewayRestApi, wafV2WebAcl |
7577
| apiGatewayResource | apiGatewayRestApi |
7678
| appSync | cognitoUserPool, dynamodb, iamRole, lambda, rdsCluster, wafV2WebAcl |

src/enums/relations.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ export default {
99
emrCluster: ['emrInstance', 'emrStep'],
1010
ecsService: ['ecsTaskSet', 'ecsTaskDefinition'],
1111
iamInstanceProfile: ['ec2Instance'],
12+
apiGatewayDomainName: ['apiGatewayRestApi'],
1213
}

src/enums/schemasMap.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import services from './services'
66
export default {
77
account: 'awsAccount',
88
[services.alb]: 'awsAlb',
9+
[services.apiGatewayDomainName]: 'awsApiGatewayDomainName',
10+
[services.apiGatewayHttpApi]: 'awsApiGatewayHttpApi',
911
[services.apiGatewayResource]: 'awsApiGatewayResource',
1012
[services.apiGatewayRestApi]: 'awsApiGatewayRestApi',
1113
[services.apiGatewayStage]: 'awsApiGatewayStage',

src/enums/serviceAliases.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import services from './services'
22

33
export default {
44
[services.alb]: 'albs',
5+
[services.apiGatewayDomainName]: 'apiGatewayDomainNames',
6+
[services.apiGatewayHttpApi]: 'apiGatewayHttpApis',
57
[services.apiGatewayResource]: 'apiGatewayResources',
68
[services.apiGatewayRestApi]: 'apiGatewayRestApis',
79
[services.apiGatewayStage]: 'apiGatewayStages',

src/enums/serviceMap.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ import SageMakerNotebookInstance from '../services/sageMakerNotebookInstance'
9696
import SystemsManagerInstance from '../services/systemsManagerInstance'
9797
import SystemsManagerDocument from '../services/systemsManagerDocument'
9898
import RdsClusterSnapshot from '../services/rdsClusterSnapshot'
99+
import APIGatewayDomainName from '../services/apiGatewayDomainName'
100+
import APIGatewayHttpApi from '../services/apiGatewayHttpApi'
99101

100102
/**
101103
* serviceMap is an object that contains all currently supported services for AWS
@@ -105,6 +107,8 @@ export default {
105107
account: Account,
106108
[services.appSync]: AppSync,
107109
[services.alb]: ALB,
110+
[services.apiGatewayDomainName]: APIGatewayDomainName,
111+
[services.apiGatewayHttpApi]: APIGatewayHttpApi,
108112
[services.apiGatewayResource]: APIGatewayResource,
109113
[services.apiGatewayRestApi]: APIGatewayRestApi,
110114
[services.apiGatewayStage]: APIGatewayStage,

src/enums/services.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
export default {
22
alb: 'alb',
3+
apiGatewayDomainName: 'apiGatewayDomainName',
4+
apiGatewayHttpApi: 'apiGatewayHttpApi',
35
apiGatewayResource: 'apiGatewayResource',
46
apiGatewayRestApi: 'apiGatewayRestApi',
57
apiGatewayStage: 'apiGatewayStage',

src/services/account/schema.graphql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
type awsAccount implements awsOptionalService @key(fields: "id") {
22
regions: [String] @search(by: [hash])
33
albs: [awsAlb]
4+
apiGatewayDomainNames: [awsApiGatewayDomainName]
5+
apiGatewayHttpApis: [awsApiGatewayHttpApi]
46
apiGatewayResources: [awsApiGatewayResource]
57
apiGatewayRestApis: [awsApiGatewayRestApi]
68
apiGatewayStages: [awsApiGatewayStage]
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import CloudGraph from '@cloudgraph/sdk'
2+
import APIGW, {
3+
ApiMapping,
4+
DomainName,
5+
GetDomainNamesRequest,
6+
GetDomainNamesResponse,
7+
GetApiMappingsRequest,
8+
GetApiMappingsResponse,
9+
} from 'aws-sdk/clients/apigatewayv2'
10+
import { AWSError } from 'aws-sdk/lib/error'
11+
import { Config } from 'aws-sdk/lib/config'
12+
import isEmpty from 'lodash/isEmpty'
13+
import groupBy from 'lodash/groupBy'
14+
import awsLoggerText from '../../properties/logger'
15+
import { initTestEndpoint, setAwsRetryOptions } from '../../utils'
16+
import AwsErrorLog from '../../utils/errorLog'
17+
import { API_GATEWAY_CUSTOM_DELAY } from '../../config/constants'
18+
import { TagMap } from '../../types'
19+
20+
const lt = { ...awsLoggerText }
21+
const { logger } = CloudGraph
22+
const MAX_DOMAIN_NAMES = '500'
23+
const serviceName = 'API Gateway Domain Name'
24+
const errorLog = new AwsErrorLog(serviceName)
25+
const endpoint = initTestEndpoint(serviceName)
26+
const customRetrySettings = setAwsRetryOptions({
27+
baseDelay: API_GATEWAY_CUSTOM_DELAY,
28+
})
29+
30+
export const getDomainNamesForRegion = async (
31+
apiGw: APIGW
32+
): Promise<DomainName[]> =>
33+
new Promise(async resolve => {
34+
const domainNameList: DomainName[] = []
35+
const getDomainNamesOpts: GetDomainNamesRequest = {}
36+
const listAllDomainNames = (token?: string): void => {
37+
getDomainNamesOpts.MaxResults = MAX_DOMAIN_NAMES
38+
if (token) {
39+
getDomainNamesOpts.NextToken = token
40+
}
41+
try {
42+
apiGw.getDomainNames(
43+
getDomainNamesOpts,
44+
(err: AWSError, data: GetDomainNamesResponse) => {
45+
if (err) {
46+
errorLog.generateAwsErrorLog({
47+
functionName: 'apiGw:getDomainNames',
48+
err,
49+
})
50+
}
51+
52+
if (isEmpty(data)) {
53+
return resolve([])
54+
}
55+
56+
const { NextToken: nextToken, Items: items = [] } = data || {}
57+
58+
if (isEmpty(items)) {
59+
return resolve([])
60+
}
61+
62+
logger.debug(lt.fetchedApiGwDomainNames(items.length))
63+
64+
domainNameList.push(...items)
65+
66+
if (nextToken) {
67+
listAllDomainNames(nextToken)
68+
} else {
69+
resolve(domainNameList)
70+
}
71+
}
72+
)
73+
} catch (error) {
74+
resolve([])
75+
}
76+
}
77+
listAllDomainNames()
78+
})
79+
80+
const getAPIMappings = (
81+
apiGw: APIGW,
82+
domainName: string
83+
): Promise<{ domainName: string; apiMappings: ApiMapping[] }> =>
84+
new Promise<{ domainName: string; apiMappings: ApiMapping[] }>(resolve => {
85+
const apiMappingList: ApiMapping[] = []
86+
const args: GetApiMappingsRequest = { DomainName: domainName }
87+
const listAllAPIMappings = (token?: string): void => {
88+
if (token) {
89+
args.NextToken = token
90+
}
91+
try {
92+
apiGw.getApiMappings(
93+
args,
94+
(err: AWSError, data: GetApiMappingsResponse) => {
95+
if (err) {
96+
errorLog.generateAwsErrorLog({
97+
functionName: 'apiGw:getApiMappings',
98+
err,
99+
})
100+
}
101+
102+
if (isEmpty(data)) {
103+
return resolve({
104+
domainName,
105+
apiMappings: [],
106+
})
107+
}
108+
109+
const { NextToken: nextToken, Items: apiMappings = [] } = data || {}
110+
111+
if (isEmpty(apiMappings)) {
112+
return resolve({
113+
domainName,
114+
apiMappings: [],
115+
})
116+
}
117+
118+
apiMappingList.push(...apiMappings)
119+
120+
if (nextToken) {
121+
listAllAPIMappings(nextToken)
122+
} else {
123+
resolve({ domainName, apiMappings: apiMappingList })
124+
}
125+
}
126+
)
127+
} catch (error) {
128+
resolve({
129+
domainName,
130+
apiMappings: [],
131+
})
132+
}
133+
}
134+
listAllAPIMappings()
135+
})
136+
137+
export interface RawAwsApiGatewayDomainName extends Omit<DomainName, 'tags'> {
138+
region: string
139+
Tags: TagMap
140+
ApiMappings: ApiMapping[]
141+
account
142+
}
143+
144+
export default async ({
145+
regions,
146+
config,
147+
account,
148+
}: {
149+
account: string
150+
regions: string
151+
config: Config
152+
}): Promise<{
153+
[region: string]: RawAwsApiGatewayDomainName[]
154+
}> =>
155+
new Promise(async resolve => {
156+
const domainNamesResult: RawAwsApiGatewayDomainName[] = []
157+
158+
const regionPromises = regions.split(',').map(region => {
159+
const apiGw = new APIGW({
160+
...config,
161+
region,
162+
endpoint,
163+
...customRetrySettings,
164+
})
165+
166+
return new Promise<void>(async resolveTransitGatewayData => {
167+
// Get Custom Domains Data
168+
const customDomains = await getDomainNamesForRegion(apiGw)
169+
170+
const mappingPromises = customDomains.map(
171+
({ DomainName: domainName }) => getAPIMappings(apiGw, domainName)
172+
)
173+
174+
const mappingData = await Promise.all(mappingPromises)
175+
176+
if (!isEmpty(customDomains)) {
177+
for (const domain of customDomains) {
178+
domainNamesResult.push({
179+
...domain,
180+
ApiMappings:
181+
mappingData?.find(m => m.domainName === domain.DomainName)
182+
?.apiMappings || [],
183+
region,
184+
Tags: domain.Tags,
185+
account,
186+
})
187+
}
188+
}
189+
190+
resolveTransitGatewayData()
191+
})
192+
})
193+
194+
await Promise.all(regionPromises)
195+
errorLog.reset()
196+
197+
resolve(groupBy(domainNamesResult, 'region'))
198+
})
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import cuid from 'cuid'
2+
import { RawAwsApiGatewayDomainName } from './data'
3+
import { AwsApiGatewayDomainName } from '../../types/generated'
4+
import { domainNameArn } from '../../utils/generateArns'
5+
import { formatTagsFromMap } from '../../utils/format'
6+
7+
export default ({
8+
service,
9+
account: accountId,
10+
region,
11+
}: {
12+
service: RawAwsApiGatewayDomainName
13+
account: string
14+
region: string
15+
}): AwsApiGatewayDomainName => {
16+
const {
17+
DomainName: domainName,
18+
ApiMappingSelectionExpression: apiMappingSelectionExpression,
19+
DomainNameConfigurations: domainNameConfigurations = [],
20+
Tags: tags = {},
21+
} = service
22+
23+
const arn = domainNameArn({ region, account: accountId, name: domainName })
24+
25+
return {
26+
id: arn,
27+
accountId,
28+
arn,
29+
region,
30+
domainName,
31+
apiMappingSelectionExpression,
32+
configurations: domainNameConfigurations?.map(dn => ({
33+
id: cuid(),
34+
apiGatewayDomainName: dn.ApiGatewayDomainName,
35+
certificateArn: dn.CertificateArn,
36+
certificateName: dn.CertificateName,
37+
certificateUploadDate: dn.CertificateUploadDate?.toDateString(),
38+
domainNameStatus: dn.DomainNameStatus,
39+
domainNameStatusMessage: dn.DomainNameStatusMessage,
40+
endpointType: dn.EndpointType,
41+
securityPolicy: dn.SecurityPolicy,
42+
ownershipVerificationCertificateArn:
43+
dn.OwnershipVerificationCertificateArn,
44+
})) || [],
45+
tags: formatTagsFromMap(tags),
46+
}
47+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Service } from '@cloudgraph/sdk'
2+
import BaseService from '../base'
3+
import format from './format'
4+
import getData from './data'
5+
import mutation from './mutation'
6+
7+
export default class APIGatewayDomainName extends BaseService implements Service {
8+
format = format.bind(this)
9+
10+
getData = getData.bind(this)
11+
12+
mutation = mutation
13+
}

0 commit comments

Comments
 (0)