Skip to content

Commit e02c6cb

Browse files
committed
feat: Add apiGatewayHttpApi (apiGatewayV2) service
1 parent 13d0560 commit e02c6cb

26 files changed

Lines changed: 779 additions & 101 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
@@ -95,6 +95,8 @@ import SageMakerNotebookInstance from '../services/sageMakerNotebookInstance'
9595
import SystemsManagerInstance from '../services/systemsManagerInstance'
9696
import SystemsManagerDocument from '../services/systemsManagerDocument'
9797
import RdsClusterSnapshot from '../services/rdsClusterSnapshot'
98+
import APIGatewayDomainName from '../services/apiGatewayDomainName'
99+
import APIGatewayHttpApi from '../services/apiGatewayHttpApi'
98100

99101
/**
100102
* serviceMap is an object that contains all currently supported services for AWS
@@ -104,6 +106,8 @@ export default {
104106
account: Account,
105107
[services.appSync]: AppSync,
106108
[services.alb]: ALB,
109+
[services.apiGatewayDomainName]: APIGatewayDomainName,
110+
[services.apiGatewayHttpApi]: APIGatewayHttpApi,
107111
[services.apiGatewayResource]: APIGatewayResource,
108112
[services.apiGatewayRestApi]: APIGatewayRestApi,
109113
[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: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
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+
resolve([])
54+
}
55+
56+
const { NextToken: nextToken, Items: items = [] } = data || {}
57+
58+
logger.debug(lt.fetchedApiGwDomainNames(items.length))
59+
60+
domainNameList.push(...items)
61+
62+
if (nextToken) {
63+
listAllDomainNames(nextToken)
64+
} else {
65+
resolve(domainNameList)
66+
}
67+
}
68+
)
69+
} catch (error) {
70+
resolve([])
71+
}
72+
}
73+
listAllDomainNames()
74+
})
75+
76+
const getAPIMappings = (
77+
apiGw: APIGW,
78+
domainName: string
79+
): Promise<{ domainName: string; apiMappings: ApiMapping[] }> =>
80+
new Promise<{ domainName: string; apiMappings: ApiMapping[] }>(resolve => {
81+
const apiMappingList: ApiMapping[] = []
82+
const args: GetApiMappingsRequest = { DomainName: domainName }
83+
const listAllAPIMappings = (token?: string): void => {
84+
if (token) {
85+
args.NextToken = token
86+
}
87+
try {
88+
apiGw.getApiMappings(
89+
args,
90+
(err: AWSError, data: GetApiMappingsResponse) => {
91+
if (err) {
92+
errorLog.generateAwsErrorLog({
93+
functionName: 'apiGw:getApiMappings',
94+
err,
95+
})
96+
}
97+
98+
if (isEmpty(data)) {
99+
resolve({
100+
domainName,
101+
apiMappings: [],
102+
})
103+
}
104+
105+
const { NextToken: nextToken, Items: apiMappings = [] } = data || {}
106+
107+
apiMappingList.push(...apiMappings)
108+
109+
if (nextToken) {
110+
listAllAPIMappings(nextToken)
111+
} else {
112+
resolve({ domainName, apiMappings: apiMappingList })
113+
}
114+
}
115+
)
116+
} catch (error) {
117+
resolve({
118+
domainName,
119+
apiMappings: [],
120+
})
121+
}
122+
}
123+
listAllAPIMappings()
124+
})
125+
126+
export interface RawAwsApiGatewayDomainName extends Omit<DomainName, 'tags'> {
127+
region: string
128+
Tags: TagMap
129+
ApiMappings: ApiMapping[]
130+
account
131+
}
132+
133+
export default async ({
134+
regions,
135+
config,
136+
account,
137+
}: {
138+
account: string
139+
regions: string
140+
config: Config
141+
}): Promise<{
142+
[region: string]: RawAwsApiGatewayDomainName[]
143+
}> =>
144+
new Promise(async resolve => {
145+
const domainNamesResult: RawAwsApiGatewayDomainName[] = []
146+
147+
const regionPromises = regions.split(',').map(region => {
148+
const apiGw = new APIGW({
149+
...config,
150+
region,
151+
endpoint,
152+
...customRetrySettings,
153+
})
154+
155+
return new Promise<void>(async resolveTransitGatewayData => {
156+
// Get Custom Domains Data
157+
const customDomains = await getDomainNamesForRegion(apiGw)
158+
159+
const mappingPromises = customDomains.map(
160+
({ DomainName: domainName }) => getAPIMappings(apiGw, domainName)
161+
)
162+
163+
const mappingData = await Promise.all(mappingPromises)
164+
165+
if (!isEmpty(customDomains)) {
166+
for (const domain of customDomains) {
167+
domainNamesResult.push({
168+
...domain,
169+
ApiMappings:
170+
mappingData?.find(m => m.domainName === domain.DomainName)
171+
?.apiMappings || [],
172+
region,
173+
Tags: domain.Tags,
174+
account,
175+
})
176+
}
177+
}
178+
179+
resolveTransitGatewayData()
180+
})
181+
})
182+
183+
await Promise.all(regionPromises)
184+
errorLog.reset()
185+
186+
resolve(groupBy(domainNamesResult, 'region'))
187+
})
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)