Skip to content

Commit 0c0e317

Browse files
authored
Merge branch 'staging' into fix-asset-uploader-error-logging
2 parents b6a7c85 + 9a4a214 commit 0c0e317

File tree

10 files changed

+263
-44
lines changed

10 files changed

+263
-44
lines changed

README.md

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,23 +30,37 @@ If you have previously set up a v1 developer portal (non-SAM deployed), you will
3030
#### Deploy
3131

3232
Run:
33+
3334
>In the command below, replace the `your-lambda-artifacts-bucket-name` with the name of a bucket that you manage and that already exists. Then, run:
35+
3436
```bash
35-
sam package --template-file ./cloudformation/template.yaml --output-template-file ./cloudformation/packaged.yaml --s3-bucket your-lambda-artifacts-bucket-name
37+
sam package --template-file ./cloudformation/template.yaml \
38+
--output-template-file ./cloudformation/packaged.yaml \
39+
--s3-bucket your-lambda-artifacts-bucket-name
3640
```
3741

38-
Then run:
42+
Then run:
43+
3944
>In the command below, replace the `your-lambda-artifacts-bucket-name` with the name of a bucket that you manage and that already exists, and replace `custom-prefix` with some prefix that is globally unique, like your org name or username. Then, run:
45+
4046
```bash
41-
sam deploy --template-file ./cloudformation/packaged.yaml --stack-name "dev-portal" --s3-bucket your-lambda-artifacts-bucket-name --capabilities CAPABILITY_NAMED_IAM --parameter-overrides DevPortalSiteS3BucketName="custom-prefix-dev-portal-static-assets" ArtifactsS3BucketName="custom-prefix-dev-portal-artifacts" CognitoDomainNameOrPrefix="custom-prefix"
47+
sam deploy --template-file ./cloudformation/packaged.yaml \
48+
--stack-name "dev-portal" \
49+
--s3-bucket your-lambda-artifacts-bucket-name \
50+
--capabilities CAPABILITY_NAMED_IAM \
51+
--parameter-overrides \
52+
DevPortalSiteS3BucketName="custom-prefix-dev-portal-static-assets" \
53+
ArtifactsS3BucketName="custom-prefix-dev-portal-artifacts" \
54+
CognitoDomainNameOrPrefix="custom-prefix"
4255
```
4356

4457
The command will exit when the stack creation is successful. If you'd like to watch it create in real-time, you can log into the cloudformation console.
4558

4659
To get the URL for the newly created developer portal instance, find the websiteURL field in the cloudformation console's outputs or run this command:
4760

4861
```bash
49-
aws cloudformation describe-stacks --query "Stacks[?StackName=='dev-portal'][Outputs[?OutputKey=='WebsiteURL']][][].OutputValue"
62+
aws cloudformation describe-stacks --query \
63+
"Stacks[?StackName=='dev-portal'][Outputs[?OutputKey=='WebsiteURL']][][].OutputValue"
5064
```
5165

5266
You can override any of the parameters in the template using the `--parameter-overrides key="value"` format. This will be necessary if you intend to deploy several instances of the developer portal or customize some of the features. You can see a full list of overridable parameters in `cloudformation/template.yaml` under the `Parameters` section.

cloudformation/template.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ Parameters:
7474
Type: String
7575
Description: By default, a static asset rebuild doesn't overwrite custom-content. Provide the value `overwrite-content` to replace the custom-content with your local version. Don't do this unless you know what you're doing -- all custom changes in your s3 bucket will be lost.
7676
Default: ''
77+
AllowedValues:
78+
- 'overwrite-content'
79+
- ''
80+
ConstraintDescription: Malformed input - Parameter StaticAssetRebuildMode value must be either 'overwrite-content' or left blank.
7781

7882
MarketplaceSubscriptionTopicProductCode:
7983
Type: String
@@ -108,11 +112,19 @@ Parameters:
108112
Type: String
109113
Description: Only applicable if creating a custom domain name for your dev portal. Defaults to false, and you'll need to provide your own nameserver hosting. If set to true, a Route53 HostedZone and RecordSet are created for you.
110114
Default: 'false'
115+
AllowedValues:
116+
- 'false'
117+
- 'true'
118+
ConstraintDescription: Malformed input - Parameter UseRoute53Nameservers value must be either 'true' or 'false'
111119

112120
DevelopmentMode:
113121
Type: String
114122
Description: Enabling this weakens security features (OAI, SSL, site S3 bucket with public read ACLs, Cognito callback verification, CORS, etc.) for easier development. It also breaks frontend routing (except to /index.html), including deep linking and page refresh. Do not enable this in production! Additionally, do not update a stack that was previously in development mode to be a production stack; instead, make a new stack that has never been in development mode.
115123
Default: 'false'
124+
AllowedValues:
125+
- 'false'
126+
- 'true'
127+
ConstraintDescription: Malformed input - Parameter DevelopmentMode value must be either 'true' or 'false'
116128

117129
Conditions:
118130
UseCustomDomainName: !And [!And [!Not [!Equals [!Ref CustomDomainName, '']], !Not [!Equals [!Ref CustomDomainNameAcmCertArn, '']]], !Condition NotDevelopmentMode]

lambdas/backend/_common/customers-controller.js

Lines changed: 18 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
'use strict'
55
const AWS = require('aws-sdk')
6+
const { getAllUsagePlans } = require('../shared/get-all-usage-plans')
67

78
const dynamoDb = new AWS.DynamoDB.DocumentClient()
89
const apigateway = new AWS.APIGateway()
@@ -195,10 +196,9 @@ function getUsagePlansForCustomer(cognitoIdentityId, error, callback) {
195196
keyId,
196197
limit: 1000
197198
}
198-
apigateway.getUsagePlans(params, (err, usagePlansData) => {
199-
if (err) error(err)
200-
else callback(usagePlansData)
201-
})
199+
getAllUsagePlans(apigateway, params)
200+
.then(usagePlansData => callback({ items: usagePlansData }))
201+
.catch(err => error(err))
202202
}
203203
})
204204
}
@@ -209,26 +209,22 @@ function getUsagePlanForProductCode(productCode, error, callback) {
209209
// do a linear scan of usage plans for name matching productCode
210210
var params = {
211211
limit: 1000
212-
};
213-
apigateway.getUsagePlans(params, function(err, data) {
214-
if (err) {
215-
error(err)
216-
} else {
217-
console.log(`Got usage plans ${JSON.stringify(data.items)}`)
212+
}
213+
getAllUsagePlans(apigateway, params).then(usagePlans => {
214+
console.log(`Got usage plans ${JSON.stringify(usagePlans)}`)
218215

219-
// note: ensure that only one usage plan maps to a given marketplace product code
220-
const usageplan = data.items.find(function (item) {
221-
return item.productCode !== undefined && item.productCode === productCode
222-
})
223-
if (usageplan !== undefined) {
224-
console.log(`Found usage plan matching ${productCode}`)
225-
callback(usageplan)
226-
} else {
227-
console.log(`Couldn't find usageplan matching product code ${productCode}`)
228-
error(`Couldn't find usageplan matching product code ${productCode}`)
229-
}
216+
// note: ensure that only one usage plan maps to a given marketplace product code
217+
const usageplan = usagePlans.find(function (item) {
218+
return item.productCode !== undefined && item.productCode === productCode
219+
})
220+
if (usageplan !== undefined) {
221+
console.log(`Found usage plan matching ${productCode}`)
222+
callback(usageplan)
223+
} else {
224+
console.log(`Couldn't find usageplan matching product code ${productCode}`)
225+
error(`Couldn't find usageplan matching product code ${productCode}`)
230226
}
231-
});
227+
}).catch(err => error(err))
232228
}
233229

234230
function updateCustomerMarketplaceId(cognitoIdentityId, marketplaceCustomerId, error, success) {
@@ -323,16 +319,6 @@ function updateCustomerApiKeyId(cognitoIdentityId, apiKeyId, error, success) {
323319
})
324320
}
325321

326-
// function getUsagePlans(error, callback) {
327-
// const params = {
328-
// limit: 1000
329-
// }
330-
// apigateway.getUsagePlans(params, (err, data) => {
331-
// if (err) error(err)
332-
// else callback(data)
333-
// })
334-
// }
335-
336322
module.exports = {
337323
ensureCustomerItem,
338324
subscribe,

lambdas/backend/express-route-handlers.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const feedbackController = require('./_common/feedback-controller.js')
33
const AWS = require('aws-sdk')
44
const catalog = require('./catalog/index')
55
const hash = require('object-hash')
6+
const { getAllUsagePlans } = require('./shared/get-all-usage-plans')
67

78
const Datauri = require('datauri')
89

@@ -421,7 +422,7 @@ async function getAdminCatalogVisibility(req, res) {
421422
})
422423
})
423424

424-
let usagePlans = await exports.apigateway.getUsagePlans().promise()
425+
let usagePlans = await getAllUsagePlans(exports.apigateway)
425426

426427
// In the case of apiGateway APIs, the client doesn't know if there are usage plan associated or not
427428
// so we need to provide that information. This can't be merged with the above loop:
@@ -431,7 +432,7 @@ async function getAdminCatalogVisibility(req, res) {
431432
visibility.apiGateway.map((apiEntry) => {
432433
apiEntry.subscribable = false
433434

434-
usagePlans.items.forEach((usagePlan) => {
435+
usagePlans.forEach((usagePlan) => {
435436
usagePlan.apiStages.forEach((apiStage) => {
436437
if(apiEntry.id === apiStage.apiId && apiEntry.stage === apiStage.stage) {
437438
apiEntry.subscribable = true
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* Fetches all usage plans, combining all pages into a single array.
3+
*
4+
* @param apiGateway
5+
* an instance of `AWS.APIGateway` to use for API calls
6+
*
7+
* @param paramOverrides
8+
* a parameter object passed in calls to `APIGateway.getUsagePlans`
9+
*
10+
* @returns
11+
* a Promise resolving with an array of items returned from
12+
* `APIGateway.getUsagePlans` calls
13+
*/
14+
async function getAllUsagePlans(apiGateway, paramOverrides = {}) {
15+
// The maximum allowed value of `limit` is 500 according to
16+
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/APIGateway.html#getUsagePlans-property
17+
const defaultParams = {limit: 500, ...paramOverrides}
18+
19+
console.log('Fetching first page of usage plans')
20+
let response = await apiGateway.getUsagePlans(defaultParams).promise()
21+
const usagePlans = response.items
22+
23+
while (response.position) {
24+
console.log(`Fetching next page of usage plans, at position=[${response.position}]`)
25+
const nextParams = {...defaultParams, position: response.position}
26+
response = await apiGateway.getUsagePlans(nextParams).promise()
27+
usagePlans.push(...response.items)
28+
}
29+
30+
return usagePlans
31+
}
32+
33+
module.exports = { getAllUsagePlans }

lambdas/catalog-updater/index.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ let AWS = require('aws-sdk'),
1212
bucketName = '',
1313
hash = require('object-hash')
1414

15+
const { getAllUsagePlans } = require('./shared/get-all-usage-plans')
16+
1517
/**
1618
* Takes in an s3 listObjectsV2 object and returns whether it's a "swagger file" (one ending in .JSON, .YAML, or .YML),
1719
* and whether it's in the catalog folder (S3 Key starts with "catalog/").
@@ -185,10 +187,9 @@ function buildCatalog(swaggerFiles, sdkGeneration) {
185187
generic: []
186188
}
187189

188-
return exports.gateway.getUsagePlans({}).promise()
189-
.then((result) => {
190-
console.log(`usagePlans: ${JSON.stringify(result.items, null, 4)}`)
191-
let usagePlans = result.items
190+
return getAllUsagePlans(exports.gateway)
191+
.then(usagePlans => {
192+
console.log(`usagePlans: ${JSON.stringify(usagePlans, null, 4)}`)
192193
for (let i = 0; i < usagePlans.length; i++) {
193194
catalog.apiGateway[i] = usagePlanToCatalogObject(usagePlans[i], swaggerFiles, sdkGeneration)
194195
}
@@ -252,4 +253,4 @@ exports = module.exports = {
252253
gateway: new AWS.APIGateway(),
253254
handler,
254255
hash
255-
}
256+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* Fetches all usage plans, combining all pages into a single array.
3+
*
4+
* @param apiGateway
5+
* an instance of `AWS.APIGateway` to use for API calls
6+
*
7+
* @param paramOverrides
8+
* a parameter object passed in calls to `APIGateway.getUsagePlans`
9+
*
10+
* @returns
11+
* a Promise resolving with an array of items returned from
12+
* `APIGateway.getUsagePlans` calls
13+
*/
14+
async function getAllUsagePlans(apiGateway, paramOverrides = {}) {
15+
// The maximum allowed value of `limit` is 500 according to
16+
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/APIGateway.html#getUsagePlans-property
17+
const defaultParams = {limit: 500, ...paramOverrides}
18+
19+
console.log('Fetching first page of usage plans')
20+
let response = await apiGateway.getUsagePlans(defaultParams).promise()
21+
const usagePlans = response.items
22+
23+
while (response.position) {
24+
console.log(`Fetching next page of usage plans, at position=[${response.position}]`)
25+
const nextParams = {...defaultParams, position: response.position}
26+
response = await apiGateway.getUsagePlans(nextParams).promise()
27+
usagePlans.push(...response.items)
28+
}
29+
30+
return usagePlans
31+
}
32+
33+
module.exports = { getAllUsagePlans }
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
const { getAllUsagePlans } = require('../get-all-usage-plans')
2+
3+
const promiser = require('../../setup-jest').promiser
4+
5+
const mockUsagePlanItem = () => ({
6+
id: '1a2b3c',
7+
name: '1a2b3c',
8+
apiStages: [
9+
{
10+
apiId: 'anmlcrckrs',
11+
stage: 'prod',
12+
},
13+
{
14+
apiId: 'jlpnochips',
15+
stage: 'beta',
16+
},
17+
],
18+
throttle: {
19+
burstLimit: 10,
20+
rateLimit: 10,
21+
},
22+
quota: {
23+
limit: 10000,
24+
offset: 0,
25+
period: 'DAY',
26+
}
27+
})
28+
29+
describe('getAllUsagePlans', () => {
30+
test('returns all usage plans, when none exist', async () => {
31+
const mockApiGateway = {
32+
getUsagePlans: jest.fn().mockReturnValueOnce(promiser({
33+
items: []
34+
}))
35+
}
36+
37+
const result = await getAllUsagePlans(mockApiGateway)
38+
const mocked = mockApiGateway.getUsagePlans.mock
39+
expect(mocked.calls.length).toBe(1)
40+
expect(mocked.calls[0][0]).not.toHaveProperty('position')
41+
expect(result).toHaveLength(0)
42+
})
43+
44+
test('returns all usage plans, when only one page of usage plans exists', async () => {
45+
const mockApiGateway = {
46+
getUsagePlans: jest.fn().mockReturnValueOnce(promiser({
47+
items: [
48+
mockUsagePlanItem(),
49+
mockUsagePlanItem(),
50+
mockUsagePlanItem(),
51+
mockUsagePlanItem(),
52+
]
53+
}))
54+
}
55+
56+
const result = await getAllUsagePlans(mockApiGateway)
57+
const mocked = mockApiGateway.getUsagePlans.mock
58+
expect(mocked.calls.length).toBe(1)
59+
expect(mocked.calls[0][0]).not.toHaveProperty('position')
60+
expect(result).toHaveLength(4)
61+
})
62+
63+
test('returns all usage plans, when multiple pages of usage plans exist', async () => {
64+
const mockApiGateway = {
65+
getUsagePlans: jest.fn().mockReturnValueOnce(promiser({
66+
items: [
67+
mockUsagePlanItem(),
68+
mockUsagePlanItem(),
69+
mockUsagePlanItem(),
70+
mockUsagePlanItem(),
71+
],
72+
position: 'qwertyuiopasdf%3D%3D',
73+
})).mockReturnValueOnce(promiser({
74+
items: [
75+
mockUsagePlanItem(),
76+
mockUsagePlanItem(),
77+
mockUsagePlanItem(),
78+
mockUsagePlanItem(),
79+
],
80+
position: 'zxcvbnm1234567%3D%3D',
81+
})).mockReturnValueOnce(promiser({
82+
items: [
83+
mockUsagePlanItem(),
84+
mockUsagePlanItem(),
85+
],
86+
}))
87+
}
88+
89+
const result = await getAllUsagePlans(mockApiGateway)
90+
const mocked = mockApiGateway.getUsagePlans.mock
91+
expect(mocked.calls.length).toBe(3)
92+
expect(mocked.calls[0][0]).not.toHaveProperty('position')
93+
expect(mocked.calls[1][0]).toHaveProperty('position', 'qwertyuiopasdf%3D%3D')
94+
expect(mocked.calls[2][0]).toHaveProperty('position', 'zxcvbnm1234567%3D%3D')
95+
expect(result).toHaveLength(10)
96+
})
97+
98+
test('passes through an API Gateway request error', async () => {
99+
const expectedError = {}
100+
const mockApiGateway = {
101+
getUsagePlans: jest.fn().mockReturnValueOnce(promiser(null, expectedError))
102+
}
103+
104+
await expect(getAllUsagePlans(mockApiGateway)).rejects.toStrictEqual(expectedError)
105+
})
106+
})

0 commit comments

Comments
 (0)