Skip to content

Commit 046f041

Browse files
authored
feat(location): support Tracker and TrackerConsumer (#31268)
### Issue # (if applicable) Closes #30712. ### Reason for this change To support tracker and tracker consumer. ### Description of changes Add `Tracker` and `TrackerConsumer` class. ### Description of how you validated changes Add unit tests and integ tests. ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 8c93291 commit 046f041

File tree

14 files changed

+34393
-0
lines changed

14 files changed

+34393
-0
lines changed

packages/@aws-cdk/aws-location-alpha/README.md

+45
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,48 @@ const routeCalculator = new location.RouteCalculator(this, 'RouteCalculator', {
104104
});
105105
routeCalculator.grantRead(role);
106106
```
107+
108+
## Tracker
109+
110+
A tracker stores position updates for a collection of devices. The tracker can be used to query the devices' current location or location history. It stores the updates, but reduces storage space and visual noise by filtering the locations before storing them.
111+
112+
For more information, see [Trackers](https://docs.aws.amazon.com/location/latest/developerguide/geofence-tracker-concepts.html#tracking-overview).
113+
114+
To create a tracker, define a `Tracker`:
115+
116+
```ts
117+
declare const key: kms.Key;
118+
119+
new location.Tracker(this, 'Tracker', {
120+
trackerName: 'MyTracker', // optional, defaults to a generated name
121+
kmsKey: key, // optional, defaults to use an AWS managed key
122+
});
123+
```
124+
125+
Use the `grant()`, `grantUpdateDevicePositions` or `grantRead()` method to grant the given identity permissions to perform actions
126+
on the geofence collection:
127+
128+
```ts
129+
declare const role: iam.Role;
130+
131+
const tracker = new location.Tracker(this, 'Tracker', {
132+
trackerName: 'MyTracker',
133+
});
134+
135+
tracker.grantRead(role);
136+
```
137+
138+
If you want to associate a tracker with geofence collections, define a `geofenceCollections` property or use `addGeofenceCollections` method.
139+
140+
```ts
141+
declare const geofenceCollection: location.GeofenceCollection;
142+
declare const geofenceCollectionForAdd: location.GeofenceCollection;
143+
declare const tracker: location.Tracker;
144+
145+
const tracker = new location.Tracker(this, 'Tracker', {
146+
trackerName: 'MyTracker',
147+
geofenceCollections: [geofenceCollection],
148+
});
149+
150+
tracker.addGeofenceCollections(geofenceCollectionForAdd);
151+
```
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export * from './geofence-collection';
22
export * from './place-index';
33
export * from './route-calculator';
4+
export * from './tracker';
45
export * from './util';
56

67
// AWS::Location CloudFormation Resources:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
import * as iam from 'aws-cdk-lib/aws-iam';
2+
import * as kms from 'aws-cdk-lib/aws-kms';
3+
import { ArnFormat, IResource, Lazy, Resource, Stack, Token } from 'aws-cdk-lib/core';
4+
import { Construct } from 'constructs';
5+
import { CfnTracker, CfnTrackerConsumer } from 'aws-cdk-lib/aws-location';
6+
import { generateUniqueId } from './util';
7+
import { IGeofenceCollection } from './geofence-collection';
8+
9+
/**
10+
* A Tracker
11+
*/
12+
export interface ITracker extends IResource {
13+
/**
14+
* The name of the tracker
15+
*
16+
* @attribute
17+
*/
18+
readonly trackerName: string;
19+
20+
/**
21+
* The Amazon Resource Name (ARN) of the tracker resource
22+
*
23+
* @attribute Arn, TrackerArn
24+
*/
25+
readonly trackerArn: string;
26+
}
27+
28+
/**
29+
* Properties for a tracker
30+
*/
31+
export interface TrackerProps {
32+
/**
33+
* A name for the tracker
34+
*
35+
* Must be between 1 and 100 characters and contain only alphanumeric characters,
36+
* hyphens, periods and underscores.
37+
*
38+
* @default - A name is automatically generated
39+
*/
40+
readonly trackerName?: string;
41+
42+
/**
43+
* A description for the tracker
44+
*
45+
* @default - no description
46+
*/
47+
readonly description?: string;
48+
49+
/**
50+
* Send filtered device position updates to default EventBridge bus.
51+
*
52+
* @default false
53+
*/
54+
readonly eventBridgeEnabled?: boolean;
55+
56+
/**
57+
* The customer managed key to encrypt data.
58+
* If you set customer managed key, the Bounding Polygon Queries feature will be disabled by default.
59+
* You can choose to opt-in to the Bounding Polygon Queries feature by setting the kmsKeyEnableGeospatialQueries parameter to true.
60+
*
61+
* @default - Use an AWS managed key
62+
*/
63+
readonly kmsKey?: kms.IKey;
64+
65+
/**
66+
* Whether to opt-in to the Bounding Polygon Queries feature with customer managed key
67+
*
68+
* @default false
69+
*/
70+
readonly kmsKeyEnableGeospatialQueries?: boolean;
71+
72+
/**
73+
* The position filtering for the tracker resource
74+
*
75+
* @default PositionFiltering.TIME_BASED
76+
*/
77+
readonly positionFiltering?: PositionFiltering;
78+
79+
/**
80+
* An optional list of geofence collections to associate with the tracker resource
81+
*
82+
* @default - no geofence collections are associated
83+
*/
84+
readonly geofenceCollections?: IGeofenceCollection[];
85+
}
86+
87+
/**
88+
* The position filtering for the tracker resource
89+
*/
90+
export enum PositionFiltering {
91+
/**
92+
* Location updates are evaluated against linked geofence collections, but not every location update is stored.
93+
* If your update frequency is more often than 30 seconds, only one update per 30 seconds is stored for each unique device ID.
94+
*/
95+
TIME_BASED = 'TimeBased',
96+
97+
/**
98+
* If the device has moved less than 30 m (98.4 ft), location updates are ignored.
99+
* Location updates within this area are neither evaluated against linked geofence collections, nor stored.
100+
* This helps control costs by reducing the number of geofence evaluations and historical device positions to paginate through.
101+
* Distance-based filtering can also reduce the effects of GPS noise when displaying device trajectories on a map.
102+
*/
103+
DISTANCE_BASED = 'DistanceBased',
104+
105+
/**
106+
* If the device has moved less than the measured accuracy, location updates are ignored.
107+
* For example, if two consecutive updates from a device have a horizontal accuracy of 5 m and 10 m,
108+
* the second update is ignored if the device has moved less than 15 m.
109+
* Ignored location updates are neither evaluated against linked geofence collections, nor stored.
110+
* This can reduce the effects of GPS noise when displaying device trajectories on a map,
111+
* and can help control your costs by reducing the number of geofence evaluations.
112+
*/
113+
ACCURACY_BASED = 'AccuracyBased',
114+
}
115+
116+
/**
117+
* A Tracker
118+
*
119+
* @see https://docs.aws.amazon.com/location/latest/developerguide/geofence-tracker-concepts.html#tracking-overview
120+
*/
121+
export class Tracker extends Resource implements ITracker {
122+
/**
123+
* Use an existing tracker by name
124+
*/
125+
public static fromTrackerName(scope: Construct, id: string, trackerName: string): ITracker {
126+
const trackerArn = Stack.of(scope).formatArn({
127+
service: 'geo',
128+
resource: 'tracker',
129+
resourceName: trackerName,
130+
});
131+
132+
return Tracker.fromTrackerArn(scope, id, trackerArn);
133+
}
134+
135+
/**
136+
* Use an existing tracker by ARN
137+
*/
138+
public static fromTrackerArn(scope: Construct, id: string, trackerArn: string): ITracker {
139+
const parsedArn = Stack.of(scope).splitArn(trackerArn, ArnFormat.SLASH_RESOURCE_NAME);
140+
141+
if (!parsedArn.resourceName) {
142+
throw new Error(`Tracker Arn ${trackerArn} does not have a resource name.`);
143+
}
144+
145+
class Import extends Resource implements ITracker {
146+
public readonly trackerName = parsedArn.resourceName!;
147+
public readonly trackerArn = trackerArn;
148+
}
149+
150+
return new Import(scope, id, {
151+
account: parsedArn.account,
152+
region: parsedArn.region,
153+
});
154+
}
155+
156+
public readonly trackerName: string;
157+
158+
public readonly trackerArn: string;
159+
160+
/**
161+
* The timestamp for when the tracker resource was created in ISO 8601 format
162+
*
163+
* @attribute
164+
*/
165+
public readonly trackerCreateTime: string;
166+
167+
/**
168+
* The timestamp for when the tracker resource was last updated in ISO 8601 format
169+
*
170+
* @attribute
171+
*/
172+
public readonly trackerUpdateTime: string;
173+
174+
constructor(scope: Construct, id: string, props: TrackerProps = {}) {
175+
176+
if (props.description && !Token.isUnresolved(props.description) && props.description.length > 1000) {
177+
throw new Error(`\`description\` must be between 0 and 1000 characters. Received: ${props.description.length} characters`);
178+
}
179+
180+
if (props.trackerName && !Token.isUnresolved(props.trackerName) && !/^[-.\w]{1,100}$/.test(props.trackerName)) {
181+
throw new Error(`Invalid tracker name. The tracker name must be between 1 and 100 characters and contain only alphanumeric characters, hyphens, periods and underscores. Received: ${props.trackerName}`);
182+
}
183+
184+
if (!Token.isUnresolved(props.kmsKey)
185+
&& !props.kmsKey
186+
&& props.kmsKeyEnableGeospatialQueries
187+
) {
188+
throw new Error('`kmsKeyEnableGeospatialQueries` can only be enabled that are configured to use an AWS KMS customer managed key');
189+
}
190+
191+
super(scope, id, {
192+
physicalName: props.trackerName ?? Lazy.string({ produce: () => generateUniqueId(this) }),
193+
});
194+
195+
const tracker = new CfnTracker(this, 'Resource', {
196+
trackerName: this.physicalName,
197+
description: props.description,
198+
eventBridgeEnabled: props.eventBridgeEnabled,
199+
kmsKeyEnableGeospatialQueries: props.kmsKeyEnableGeospatialQueries,
200+
kmsKeyId: props.kmsKey?.keyArn,
201+
positionFiltering: props.positionFiltering,
202+
});
203+
204+
props.geofenceCollections?.forEach((collection) => {
205+
new CfnTrackerConsumer(this, `TrackerConsumer${collection.node.id}`, {
206+
consumerArn: collection.geofenceCollectionArn,
207+
trackerName: Lazy.string({ produce: () => this.trackerName }),
208+
});
209+
});
210+
211+
this.trackerName = tracker.ref;
212+
this.trackerArn = tracker.attrArn;
213+
this.trackerCreateTime = tracker.attrCreateTime;
214+
this.trackerUpdateTime = tracker.attrUpdateTime;
215+
}
216+
217+
/**
218+
* Add Geofence Collections which are associated to the tracker resource.
219+
*/
220+
public addGeofenceCollections(...geofenceCollections: IGeofenceCollection[]) {
221+
geofenceCollections.forEach((collection) => {
222+
new CfnTrackerConsumer(this, `TrackerConsumer${collection.node.id}`, {
223+
consumerArn: collection.geofenceCollectionArn,
224+
trackerName: Lazy.string({ produce: () => this.trackerName }),
225+
});
226+
});
227+
}
228+
229+
/**
230+
* Grant the given principal identity permissions to perform the actions on this tracker.
231+
*/
232+
public grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant {
233+
return iam.Grant.addToPrincipal({
234+
grantee: grantee,
235+
actions: actions,
236+
resourceArns: [this.trackerArn],
237+
});
238+
}
239+
240+
/**
241+
* Grant the given identity permissions to update device positions for a tracker
242+
*
243+
* @see https://docs.aws.amazon.com/location/latest/developerguide/security_iam_id-based-policy-examples.html#security_iam_id-based-policy-examples-read-only-trackers
244+
*/
245+
public grantUpdateDevicePositions(grantee: iam.IGrantable): iam.Grant {
246+
return this.grant(grantee,
247+
'geo:BatchUpdateDevicePosition',
248+
);
249+
}
250+
251+
/**
252+
* Grant the given identity permissions to read device positions from a tracker
253+
*
254+
* @see https://docs.aws.amazon.com/location/latest/developerguide/security_iam_id-based-policy-examples.html#security_iam_id-based-policy-examples-read-only-trackers
255+
*/
256+
public grantRead(grantee: iam.IGrantable): iam.Grant {
257+
return iam.Grant.addToPrincipal({
258+
grantee,
259+
actions: [
260+
'geo:BatchGetDevicePosition',
261+
'geo:GetDevicePosition',
262+
'geo:GetDevicePositionHistory',
263+
],
264+
resourceArns: [`${this.trackerArn}/*`],
265+
});
266+
}
267+
}

packages/@aws-cdk/aws-location-alpha/test/integ.tracker.js.snapshot/TrackerTestDefaultTestDeployAssert0E17BB8B.assets.json

+32
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)