Skip to content

Commit b9886ce

Browse files
committed
feat: add geofence collection
1 parent afe69b8 commit b9886ce

14 files changed

+796
-0
lines changed

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

+26
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,29 @@ declare const role: iam.Role;
4949
const placeIndex = new location.PlaceIndex(this, 'PlaceIndex');
5050
placeIndex.grantSearch(role);
5151
```
52+
53+
## Geofence Collection
54+
55+
Geofence collection resources allow you to store and manage geofences—virtual boundaries on a map. You can evaluate locations against a geofence collection resource and get notifications when the location update crosses the boundary of any of the geofences in the geofence collection.
56+
57+
```ts
58+
declare const key: kms.Key;
59+
60+
new location.GeofenceCollection(this, 'GeofenceCollection', {
61+
placeIndexName: 'MyGeofenceCollection', // optional, defaults to a generated name
62+
kmsKey: Key, // optional, defaults to use an AWS managed key
63+
});
64+
```
65+
66+
Use the `grant()` or `grantRead()` method to grant the given identity permissions to perform actions
67+
on the geofence collection:
68+
69+
```ts
70+
declare const role: iam.Role;
71+
72+
const geofenceCollection = new location.GeofenceCollection(this, 'GeofenceCollection', {
73+
placeIndexName: 'MyGeofenceCollection',
74+
});
75+
76+
geofenceCollection.grantRead(role);
77+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
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 { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location';
6+
import { generateUniqueId } from './util';
7+
8+
/**
9+
* A Geofence Collection
10+
*/
11+
export interface IGeofenceCollection extends IResource {
12+
/**
13+
* The name of the geofence collection
14+
*
15+
* @attribute
16+
*/
17+
readonly geofenceCollectionName: string;
18+
19+
/**
20+
* The Amazon Resource Name (ARN) of the geofence collection resource
21+
*
22+
* @attribute Arn, CollectionArn
23+
*/
24+
readonly geofenceCollectionArn: string;
25+
}
26+
27+
/**
28+
* Properties for a geofence collection
29+
*/
30+
export interface GeofenceCollectionProps {
31+
/**
32+
* A name for the geofence collection
33+
*
34+
* @default - A name is automatically generated
35+
*/
36+
readonly geofenceCollectionName?: string;
37+
38+
/**
39+
* A description for the geofence collection
40+
*
41+
* @default - no description
42+
*/
43+
readonly description?: string;
44+
45+
/**
46+
* The customer managed to encrypt your data.
47+
*
48+
* @default - Use an AWS managed key
49+
* @see https://docs.aws.amazon.com/location/latest/developerguide/encryption-at-rest.html
50+
*/
51+
readonly kmsKey?: kms.IKey;
52+
}
53+
54+
abstract class GeofenceCollectionBase extends Resource implements IGeofenceCollection {
55+
public abstract readonly geofenceCollectionName: string;
56+
public abstract readonly geofenceCollectionArn: string;
57+
58+
/**
59+
* Grant the given principal identity permissions to perform the actions on this geofence collection.
60+
*/
61+
public grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant {
62+
return iam.Grant.addToPrincipal({
63+
grantee: grantee,
64+
actions: actions,
65+
resourceArns: [this.geofenceCollectionArn],
66+
});
67+
}
68+
69+
/**
70+
* Grant the given identity permissions to read this geofence collection
71+
*
72+
* See https://docs.aws.amazon.com/location/latest/developerguide/security_iam_id-based-policy-examples.html#security_iam_id-based-policy-examples-read-only-geofences
73+
*/
74+
public grantRead(grantee: iam.IGrantable): iam.Grant {
75+
return this.grant(grantee,
76+
'geo:ListGeofences',
77+
'geo:GetGeofence',
78+
);
79+
}
80+
}
81+
82+
/**
83+
* A Geofence Collection
84+
*
85+
* @see https://docs.aws.amazon.com/location/latest/developerguide/geofence-tracker-concepts.html#geofence-overview
86+
*/
87+
export class GeofenceCollection extends GeofenceCollectionBase {
88+
/**
89+
* Use an existing geofence collection by name
90+
*/
91+
public static fromGeofenceCollectionName(scope: Construct, id: string, geofenceCollectionName: string): IGeofenceCollection {
92+
const geofenceCollectionArn = Stack.of(scope).formatArn({
93+
service: 'geo',
94+
resource: 'geofence-collection',
95+
resourceName: geofenceCollectionName,
96+
});
97+
98+
return GeofenceCollection.fromGeofenceCollectionArn(scope, id, geofenceCollectionArn);
99+
}
100+
101+
/**
102+
* Use an existing geofence collection by ARN
103+
*/
104+
public static fromGeofenceCollectionArn(scope: Construct, id: string, geofenceCollectionArn: string): IGeofenceCollection {
105+
const parsedArn = Stack.of(scope).splitArn(geofenceCollectionArn, ArnFormat.SLASH_RESOURCE_NAME);
106+
107+
if (!parsedArn.resourceName) {
108+
throw new Error(`Geofence Collection Arn ${geofenceCollectionArn} does not have a resource name.`);
109+
}
110+
111+
class Import extends GeofenceCollectionBase {
112+
public readonly geofenceCollectionName = parsedArn.resourceName!;
113+
public readonly geofenceCollectionArn = geofenceCollectionArn;
114+
}
115+
116+
return new Import(scope, id, {
117+
account: parsedArn.account,
118+
region: parsedArn.region,
119+
});
120+
}
121+
122+
public readonly geofenceCollectionName: string;
123+
124+
public readonly geofenceCollectionArn: string;
125+
126+
/**
127+
* The timestamp for when the geofence collection resource was created in ISO 8601 forma
128+
*
129+
* @attribute
130+
*/
131+
public readonly geofenceCollectionCreateTime: string;
132+
133+
/**
134+
* The timestamp for when the geofence collection resource was last updated in ISO 8601 format
135+
*
136+
* @attribute
137+
*/
138+
public readonly geofenceCollectionUpdateTime: string;
139+
140+
constructor(scope: Construct, id: string, props: GeofenceCollectionProps = {}) {
141+
if (props.geofenceCollectionName && !Token.isUnresolved(props.geofenceCollectionName) && !/^[-.\w]{1,100}$/.test(props.geofenceCollectionName)) {
142+
throw new Error(`Invalid geofence collection name. The geofence collection name must be between 1 and 100 characters and contain only alphanumeric characters, hyphens, periods and underscores. Received: ${props.geofenceCollectionName}`);
143+
}
144+
145+
super(scope, id, {
146+
physicalName: props.geofenceCollectionName ?? Lazy.string({ produce: () => generateUniqueId(this) }),
147+
});
148+
149+
const geofenceCollection = new CfnGeofenceCollection(this, 'Resource', {
150+
collectionName: this.physicalName,
151+
description: props.description,
152+
kmsKeyId: props.kmsKey?.keyArn,
153+
});
154+
155+
this.geofenceCollectionName = geofenceCollection.ref;
156+
this.geofenceCollectionArn = geofenceCollection.attrArn;
157+
this.geofenceCollectionCreateTime = geofenceCollection.attrCreateTime;
158+
this.geofenceCollectionUpdateTime = geofenceCollection.attrUpdateTime;
159+
}
160+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Names } from 'aws-cdk-lib/core';
2+
3+
export function generateUniqueId(context: any): string {
4+
const name = Names.uniqueId(context);
5+
if (name.length > 100) {
6+
return name.substring(0, 50) + name.substring(name.length - 50);
7+
}
8+
return name;
9+
}

packages/@aws-cdk/aws-location-alpha/rosetta/default.ts-fixture

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Stack } from 'aws-cdk-lib';
33
import { Construct } from 'constructs';
44
import * as location from '@aws-cdk/aws-location-alpha';
55
import * as iam from 'aws-cdk-lib/aws-iam';
6+
import * as kms from 'aws-cdk-lib/aws-kms';
67

78
class Fixture extends Stack {
89
constructor(scope: Construct, id: string) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { Match, Template } from 'aws-cdk-lib/assertions';
2+
import * as iam from 'aws-cdk-lib/aws-iam';
3+
import * as kms from 'aws-cdk-lib/aws-kms';
4+
import { Stack } from 'aws-cdk-lib';
5+
import { GeofenceCollection } from '../lib/geofence-collection';
6+
7+
let stack: Stack;
8+
beforeEach(() => {
9+
stack = new Stack();
10+
});
11+
12+
test('create a geofence collecction', () => {
13+
new GeofenceCollection(stack, 'GeofenceCollection', { description: 'test' });
14+
15+
Template.fromStack(stack).hasResourceProperties('AWS::Location::GeofenceCollection', {
16+
CollectionName: 'GeofenceCollection',
17+
Description: 'test',
18+
});
19+
});
20+
21+
test('throws with invalid name', () => {
22+
expect(() => new GeofenceCollection(stack, 'GeofenceCollection', {
23+
geofenceCollectionName: 'inv@lid',
24+
})).toThrow('Invalid geofence collection name. The geofence collection name must be between 1 and 100 characters and contain only alphanumeric characters, hyphens, periods and underscores. Received: inv@lid');
25+
});
26+
27+
test('grant read actions', () => {
28+
const geofenceCollection = new GeofenceCollection(stack, 'GeofenceCollection', {
29+
});
30+
31+
const role = new iam.Role(stack, 'Role', {
32+
assumedBy: new iam.ServicePrincipal('foo'),
33+
});
34+
35+
geofenceCollection.grantRead(role);
36+
37+
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', Match.objectLike({
38+
PolicyDocument: Match.objectLike({
39+
Statement: [
40+
{
41+
Action: [
42+
'geo:ListGeofences',
43+
'geo:GetGeofence',
44+
],
45+
Effect: 'Allow',
46+
Resource: {
47+
'Fn::GetAtt': [
48+
'GeofenceCollection6FAC681F',
49+
'Arn',
50+
],
51+
},
52+
},
53+
],
54+
}),
55+
}));
56+
});
57+
58+
test('import from arn', () => {
59+
const geofenceCollectionArn = stack.formatArn({
60+
service: 'geo',
61+
resource: 'geofence-collection',
62+
resourceName: 'MyGeofenceCollection',
63+
});
64+
const geofenceCollection = GeofenceCollection.fromGeofenceCollectionArn(stack, 'GeofenceCollection', geofenceCollectionArn);
65+
66+
// THEN
67+
expect(geofenceCollection.geofenceCollectionName).toEqual('MyGeofenceCollection');
68+
expect(geofenceCollection.geofenceCollectionArn).toEqual(geofenceCollectionArn);
69+
});
70+
71+
test('import from name', () => {
72+
// WHEN
73+
const geofenceCollectionName = 'MyGeofenceCollection';
74+
const geofenceCollection = GeofenceCollection.fromGeofenceCollectionName(stack, 'GeofenceCollection', geofenceCollectionName);
75+
76+
// THEN
77+
expect(geofenceCollection.geofenceCollectionName).toEqual(geofenceCollectionName);
78+
expect(geofenceCollection.geofenceCollectionArn).toEqual(stack.formatArn({
79+
service: 'geo',
80+
resource: 'geofence-collection',
81+
resourceName: 'MyGeofenceCollection',
82+
}));
83+
});
84+
85+
test('create a geofence collection with a customer managed key)', () => {
86+
// GIVEN
87+
const kmsKey = new kms.Key(stack, 'Key');
88+
89+
// WHEN
90+
new GeofenceCollection(stack, 'GeofenceCollection',
91+
{ kmsKey },
92+
);
93+
94+
// THEN
95+
Template.fromStack(stack).hasResourceProperties('AWS::Location::GeofenceCollection', {
96+
KmsKeyId: stack.resolve(kmsKey.keyArn),
97+
});
98+
});

packages/@aws-cdk/aws-location-alpha/test/integ.geofence-collection.js.snapshot/GeofenceCollectionTestDefaultTestDeployAssert44609017.assets.json

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

packages/@aws-cdk/aws-location-alpha/test/integ.geofence-collection.js.snapshot/GeofenceCollectionTestDefaultTestDeployAssert44609017.template.json

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

packages/@aws-cdk/aws-location-alpha/test/integ.geofence-collection.js.snapshot/cdk-integ-location-geofence-collection.assets.json

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

0 commit comments

Comments
 (0)