Skip to content
This repository was archived by the owner on Jul 9, 2024. It is now read-only.

Commit 2709522

Browse files
committed
feat(cdk): unify the way APIs are configured
Previously there were two ways to configure APIs (one for UnwiredLabs and one for GitHub). This is now unified and there is a CLI to set these configuration settings as well. In addition all SSM Parameters are now prefixed with the stack name so multiple instances can exist in the same account. This is not marked as a breaking change because the documentation was not yet published
1 parent 5387a64 commit 2709522

13 files changed

Lines changed: 254 additions & 105 deletions

File tree

cdk/apps/AssetTracker.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ export class AssetTrackerApp extends App {
2020
sourceCodeBucketName: string
2121
packedLambdas: PackedLambdas<AssetTrackerLambdas>
2222
packedCDKLambdas: PackedLambdas<CDKLambdas>
23-
enableUnwiredApi: boolean
2423
context?: Record<string, any>
2524
}) {
2625
super({ context: args.context })

cdk/apps/Test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ export class TestApp extends App {
2121
super({ context: args.context })
2222
new AssetTrackerStack(this, {
2323
...args,
24-
enableUnwiredApi: false, // FIXME: implement e2e test
2524
})
2625
new FirmwareCIStack(this, args)
2726
}

cdk/cloudformation.ts

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,18 @@ import {
55
prepareCDKLambdas,
66
} from './prepare-resources'
77
import { SSMClient } from '@aws-sdk/client-ssm'
8-
import { getApiSettings } from '../cellGeolocation/stepFunction/unwiredlabs'
8+
import { getUnwiredLabsApiSettings } from '../cellGeolocation/stepFunction/unwiredlabs'
99
import { warn } from './helper/note'
1010
import { STSClient } from '@aws-sdk/client-sts'
1111
import { loadContext } from './helper/loadContext'
12+
import { CORE_STACK_NAME } from './stacks/stackName'
13+
import { getApiSettings } from '../util/apiConfiguration'
14+
import * as chalk from 'chalk'
1215

13-
const fetchUnwiredLabsApiSettings = getApiSettings({
14-
ssm: new SSMClient({}),
16+
const ssm = new SSMClient({})
17+
const fetchUnwiredLabsApiSettings = getUnwiredLabsApiSettings({
18+
ssm,
19+
stackName: CORE_STACK_NAME,
1520
})
1621

1722
const rootDir = process.cwd()
@@ -30,25 +35,47 @@ Promise.all([
3035
rootDir,
3136
}),
3237
})),
33-
fetchUnwiredLabsApiSettings({ api: 'unwiredlabs' }).catch(() => {
34-
warn(
35-
'Cell Geolocation',
36-
'No UnwiredLabs API key configured. Feature will be disabled.',
37-
)
38-
return {}
39-
}),
38+
fetchUnwiredLabsApiSettings().catch(() => ({})),
39+
getApiSettings({
40+
ssm,
41+
stackName: CORE_STACK_NAME,
42+
scope: 'codebuild',
43+
api: 'github',
44+
})().catch(() => ({})),
4045
loadContext({ sts: new STSClient({}) }),
4146
])
42-
.then(([args, ulApiSettings, context]) =>
43-
new AssetTrackerApp({
47+
.then(([args, ulApiSettings, codebuildSettings, context]) => {
48+
const ctx = {
49+
version: process.env.VERSION ?? '0.0.0-development',
50+
...context,
51+
} as Record<string, any>
52+
const enableUnwiredApi = 'apiKey' in ulApiSettings
53+
if (!enableUnwiredApi) {
54+
warn(
55+
'Cell Geolocation',
56+
'No UnwiredLabs API key configured. Feature will be disabled.',
57+
)
58+
warn(
59+
'Cell Geolocation',
60+
`Use ${chalk.greenBright(
61+
`node cli configure-api codebuild github token <token>`,
62+
)} to set the token`,
63+
)
64+
ctx.unwiredlabs = '0'
65+
}
66+
const enableCD = 'token' in codebuildSettings
67+
if (!enableCD) {
68+
warn(
69+
'Continuous Deployment',
70+
'No GitHub API key configured. Continuous deployment will be disabled.',
71+
)
72+
ctx.cd = '0'
73+
}
74+
return new AssetTrackerApp({
4475
...args,
45-
enableUnwiredApi: 'apiKey' in ulApiSettings,
46-
context: {
47-
version: process.env.VERSION ?? '0.0.0-development',
48-
...context,
49-
},
50-
}).synth(),
51-
)
76+
context: ctx,
77+
}).synth()
78+
})
5279
.catch((err) => {
5380
console.error(err)
5481
process.exit(1)

cdk/helper/note.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import * as chalk from 'chalk'
22

33
export const warn = (category: string, note: string): void => {
4-
console.warn('', chalk.magenta(''), chalk.cyan(category), chalk.grey(note))
4+
console.warn('', chalk.magenta(''), chalk.cyan(category), chalk.grey(note))
55
}
66
export const info = (category: string, note: string): void => {
77
console.debug(
88
'',
9-
chalk.blue(''),
9+
chalk.blue(''),
1010
chalk.white.dim(category),
1111
chalk.gray(note),
1212
)

cdk/resources/CellGeolocation.ts

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { StateMachineType } from '@aws-cdk/aws-stepfunctions'
1212
import { Role } from '@aws-cdk/aws-iam'
1313
import * as SQS from '@aws-cdk/aws-sqs'
1414
import { LambdasWithLayer } from './LambdasWithLayer'
15+
import { CORE_STACK_NAME } from '../stacks/stackName'
16+
import { enabledInContext } from '../helper/enabledInContext'
1517

1618
/**
1719
* Provides the resources for geolocating LTE/NB-IoT network cells
@@ -28,10 +30,8 @@ export class CellGeolocation extends CloudFormation.Resource {
2830
id: string,
2931
{
3032
lambdas,
31-
enableUnwiredApi,
3233
}: {
3334
lambdas: LambdasWithLayer<AssetTrackerLambdas>
34-
enableUnwiredApi: boolean
3535
},
3636
) {
3737
super(parent, id)
@@ -165,31 +165,37 @@ export class CellGeolocation extends CloudFormation.Resource {
165165

166166
// Optional step
167167
let fromUnwiredLabs: Lambda.IFunction | undefined = undefined
168-
if (enableUnwiredApi) {
169-
fromUnwiredLabs = new Lambda.Function(this, 'fromUnwiredLabs', {
170-
layers: lambdas.layers,
171-
handler: 'index.handler',
172-
runtime: Lambda.Runtime.NODEJS_12_X,
173-
timeout: CloudFormation.Duration.seconds(10),
174-
memorySize: 1792,
175-
code: lambdas.lambdas.geolocateCellFromUnwiredLabsStepFunction,
176-
description: 'Resolve cell geolocation using the UnwiredLabs API',
177-
initialPolicy: [
178-
logToCloudWatch,
179-
new IAM.PolicyStatement({
180-
actions: ['ssm:GetParametersByPath'],
181-
resources: [
182-
`arn:aws:ssm:${parent.region}:${parent.account}:parameter/asset-tracker/cellGeoLocation/unwiredlabs`,
183-
],
184-
}),
185-
],
186-
environment: {
187-
VERSION: this.node.tryGetContext('version'),
188-
},
189-
})
168+
const checkFlag = enabledInContext(this.node)
169+
const unwiredLabsEnabled = checkFlag({
170+
key: 'unwiredlabs',
171+
component: 'UnwiredLabs API',
172+
onUndefined: 'disabled',
173+
onEnabled: () => {
174+
fromUnwiredLabs = new Lambda.Function(this, 'fromUnwiredLabs', {
175+
layers: lambdas.layers,
176+
handler: 'index.handler',
177+
runtime: Lambda.Runtime.NODEJS_12_X,
178+
timeout: CloudFormation.Duration.seconds(10),
179+
memorySize: 1792,
180+
code: lambdas.lambdas.geolocateCellFromUnwiredLabsStepFunction,
181+
description: 'Resolve cell geolocation using the UnwiredLabs API',
182+
initialPolicy: [
183+
logToCloudWatch,
184+
new IAM.PolicyStatement({
185+
actions: ['ssm:GetParametersByPath'],
186+
resources: [
187+
`arn:aws:ssm:${parent.region}:${parent.account}:parameter/${CORE_STACK_NAME}/cellGeoLocation/unwiredlabs`,
188+
],
189+
}),
190+
],
191+
environment: {
192+
VERSION: this.node.tryGetContext('version'),
193+
},
194+
})
190195

191-
new LambdaLogGroup(this, 'fromUnwiredLabsLogs', fromUnwiredLabs)
192-
}
196+
new LambdaLogGroup(this, 'fromUnwiredLabsLogs', fromUnwiredLabs)
197+
},
198+
})
193199

194200
const isGeolocated = StepFunctions.Condition.booleanEquals(
195201
'$.cellgeo.located',
@@ -243,7 +249,7 @@ export class CellGeolocation extends CloudFormation.Resource {
243249
)
244250
.otherwise(
245251
(() => {
246-
if (!fromUnwiredLabs) {
252+
if (!unwiredLabsEnabled) {
247253
return new StepFunctions.Fail(this, 'Failed (No API)', {
248254
error: 'NO_API',
249255
cause:
@@ -255,7 +261,7 @@ export class CellGeolocation extends CloudFormation.Resource {
255261
'Resolve using UnwiredLabs API',
256262
{
257263
task: new StepFunctionTasks.InvokeFunction(
258-
fromUnwiredLabs,
264+
(fromUnwiredLabs as unknown) as Lambda.IFunction,
259265
),
260266
resultPath: '$.cellgeo',
261267
},

cdk/stacks/AssetTracker.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,11 @@ export class AssetTrackerStack extends CloudFormation.Stack {
3030
sourceCodeBucketName,
3131
packedLambdas,
3232
packedCDKLambdas,
33-
enableUnwiredApi,
3433
}: {
3534
mqttEndpoint: string
3635
sourceCodeBucketName: string
3736
packedLambdas: PackedLambdas<AssetTrackerLambdas>
3837
packedCDKLambdas: PackedLambdas<CDKLambdas>
39-
enableUnwiredApi: boolean
4038
},
4139
) {
4240
super(parent, CORE_STACK_NAME)
@@ -435,7 +433,6 @@ export class AssetTrackerStack extends CloudFormation.Stack {
435433

436434
const cellgeo = new CellGeolocation(this, 'cellGeolocation', {
437435
lambdas,
438-
enableUnwiredApi,
439436
})
440437

441438
cellgeo.stateMachine.grantStartExecution(userRole)

cdk/stacks/ContinuousDeployment.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export class ContinuousDeploymentStack extends CloudFormation.Stack {
106106
this,
107107
'ghtoken',
108108
{
109-
parameterName: '/codebuild/github-token',
109+
parameterName: `/${CORE_STACK_NAME}/codebuild/github/token`,
110110
version: 1,
111111
},
112112
)

cellGeolocation/stepFunction/unwiredlabs.ts

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,43 @@
1-
import { GetParametersByPathCommand, SSMClient } from '@aws-sdk/client-ssm'
1+
import { SSMClient } from '@aws-sdk/client-ssm'
22
import { request as nodeRequest } from 'https'
3-
import { parse } from 'url'
3+
import { URL } from 'url'
44
import { MaybeCellGeoLocation } from './types'
55
import { Cell } from '../geolocateCell'
6+
import { fromEnv } from '../../util/fromEnv'
7+
import { getApiSettings } from '../../util/apiConfiguration'
68

7-
export const getApiSettings = ({ ssm }: { ssm: SSMClient }) => async ({
8-
api,
9+
export const getUnwiredLabsApiSettings = ({
10+
ssm,
11+
stackName,
912
}: {
10-
api: 'unwiredlabs'
11-
}): Promise<{ apiKey: string; endpoint: string }> => {
12-
const Path = `/asset-tracker/cellGeoLocation/${api}`
13-
const { Parameters } = await ssm.send(
14-
new GetParametersByPathCommand({
15-
Path,
16-
Recursive: true,
17-
}),
18-
)
19-
20-
const apiKey = Parameters?.find(
21-
({ Name }) => Name?.replace(`${Path}/`, '') === 'apiKey',
22-
)?.Value
13+
ssm: SSMClient
14+
stackName: string
15+
}) => async (): Promise<{ apiKey: string; endpoint: string }> => {
16+
const p = await getApiSettings({
17+
ssm,
18+
stackName,
19+
scope: 'cellGeoLocation',
20+
api: 'unwiredlabs',
21+
})()
22+
const { apiKey, endpoint } = p
2323
if (apiKey === undefined) throw new Error('No API key configured!')
24-
const endpoint =
25-
Parameters?.find(({ Name }) => Name?.replace(`${Path}/`, '') === 'endpoint')
26-
?.Value ?? 'https://eu1.unwiredlabs.com/'
27-
2824
return {
2925
apiKey,
30-
endpoint,
26+
endpoint: endpoint ?? 'https://eu1.unwiredlabs.com/',
3127
}
3228
}
3329

34-
const fetchSettings = getApiSettings({ ssm: new SSMClient({}) })
30+
const { stackName } = fromEnv({ stackName: 'STACK_NAME' })(process.env)
31+
32+
const fetchSettings = getUnwiredLabsApiSettings({
33+
ssm: new SSMClient({}),
34+
stackName,
35+
})
3536

3637
export const handler = async (cell: Cell): Promise<MaybeCellGeoLocation> => {
3738
try {
38-
const { apiKey, endpoint } = await fetchSettings({
39-
api: 'unwiredlabs',
40-
})
41-
const { hostname, path } = parse(endpoint)
39+
const { apiKey, endpoint } = await fetchSettings()
40+
const { hostname, pathname } = new URL(endpoint)
4241

4342
// See https://eu1.unwiredlabs.com/docs-html/index.html#response
4443
const {
@@ -61,7 +60,7 @@ export const handler = async (cell: Cell): Promise<MaybeCellGeoLocation> => {
6160
} = await new Promise((resolve, reject) => {
6261
const options = {
6362
host: hostname,
64-
path: `${path?.replace(/\/*$/, '') ?? ''}/v2/process.php`,
63+
path: `${pathname?.replace(/\/*$/, '') ?? ''}/v2/process.php`,
6564
method: 'POST',
6665
agent: false,
6766
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { firmwareCICommand } from './commands/firmware-ci'
2222
import { certsDir as provideCertsDir } from './jitp/certsDir'
2323
import { flashCommand } from './commands/flash'
2424
import { deviceUIConfigCommand } from './commands/device-ui-config'
25+
import { configureAPICommand } from './commands/configure-api'
2526

2627
const iot = new IoTClient({})
2728
const version = JSON.parse(
@@ -79,6 +80,7 @@ const assetTrackerCLI = async ({ isCI }: { isCI: boolean }) => {
7980
cdCommand(),
8081
purgeIotUserPolicyPrincipals(),
8182
logsCommand(),
83+
configureAPICommand(),
8284
]
8385

8486
if (isCI) {

cli/commands/cd-update-token.ts

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,27 @@ import {
33
GetPipelineCommand,
44
UpdatePipelineCommand,
55
} from '@aws-sdk/client-codepipeline'
6-
import {
7-
DeleteParameterCommand,
8-
PutParameterCommand,
9-
SSMClient,
10-
} from '@aws-sdk/client-ssm'
6+
import { SSMClient } from '@aws-sdk/client-ssm'
117
import { CommandDefinition } from './CommandDefinition'
128
import * as chalk from 'chalk'
139
import { listPipelines } from '../cd/listPipelines'
10+
import { CORE_STACK_NAME } from '../../cdk/stacks/stackName'
11+
import { putApiSetting } from '../../util/apiConfiguration'
1412

1513
export const cdUpdateTokenCommand = (): CommandDefinition => ({
1614
command: 'cd-update-token <token>',
1715
action: async (token: string) => {
1816
const ssm = new SSMClient({})
19-
try {
20-
await ssm.send(
21-
new DeleteParameterCommand({
22-
Name: '/codebuild/github-token',
23-
}),
24-
)
25-
} catch {
26-
// pass
27-
}
28-
await ssm.send(
29-
new PutParameterCommand({
30-
Name: '/codebuild/github-token',
31-
Value: token,
32-
Type: 'String',
33-
}),
34-
)
17+
18+
await putApiSetting({
19+
ssm,
20+
stackName: CORE_STACK_NAME,
21+
scope: 'codebuild',
22+
api: 'github',
23+
})({
24+
property: 'token',
25+
value: token,
26+
})
3527

3628
const cp = new CodePipelineClient({})
3729
const pipelines = await listPipelines()

0 commit comments

Comments
 (0)