-
Notifications
You must be signed in to change notification settings - Fork 3.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(location): support GeofenceCollection #30711
Merged
Merged
Changes from 11 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
4b80527
feat: add geofence collection
mazyu36 5e5d121
Merge branch 'main' into location-geofence
mazyu36 4b22485
Update packages/@aws-cdk/aws-location-alpha/README.md
mazyu36 9324d26
incorporate review comments
mazyu36 baf15dd
Merge branch 'location-geofence' of https://github.com/mazyu36/aws-cd…
mazyu36 1a715e2
Update packages/@aws-cdk/aws-location-alpha/lib/geofence-collection.ts
mazyu36 df19851
Update packages/@aws-cdk/aws-location-alpha/lib/geofence-collection.ts
mazyu36 bb82a5e
incorporate review comments
mazyu36 1eb8ff9
update validation
mazyu36 ff62d88
Merge branch 'main' into location-geofence
mazyu36 a063feb
update docs
mazyu36 9068e77
incorporate review comments
mazyu36 a9eb231
Merge branch 'main' into location-geofence
mazyu36 a624296
remove base class
mazyu36 0270867
Merge branch 'main' into location-geofence
mergify[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
168 changes: 168 additions & 0 deletions
168
packages/@aws-cdk/aws-location-alpha/lib/geofence-collection.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
import * as iam from 'aws-cdk-lib/aws-iam'; | ||
import * as kms from 'aws-cdk-lib/aws-kms'; | ||
import { ArnFormat, IResource, Lazy, Resource, Stack, Token } from 'aws-cdk-lib/core'; | ||
import { Construct } from 'constructs'; | ||
import { CfnGeofenceCollection } from 'aws-cdk-lib/aws-location'; | ||
import { generateUniqueId } from './util'; | ||
|
||
/** | ||
* A Geofence Collection | ||
*/ | ||
export interface IGeofenceCollection extends IResource { | ||
/** | ||
* The name of the geofence collection | ||
* | ||
* @attribute | ||
*/ | ||
readonly geofenceCollectionName: string; | ||
|
||
/** | ||
* The Amazon Resource Name (ARN) of the geofence collection resource | ||
* | ||
* @attribute Arn, CollectionArn | ||
*/ | ||
readonly geofenceCollectionArn: string; | ||
} | ||
|
||
/** | ||
* Properties for a geofence collection | ||
*/ | ||
export interface GeofenceCollectionProps { | ||
/** | ||
* A name for the geofence collection | ||
* | ||
* Must be between 1 and 100 characters and contain only alphanumeric characters, | ||
* hyphens, periods and underscores. | ||
* | ||
* @default - A name is automatically generated | ||
*/ | ||
readonly geofenceCollectionName?: string; | ||
|
||
/** | ||
* A description for the geofence collection | ||
* | ||
* @default - no description | ||
*/ | ||
readonly description?: string; | ||
|
||
/** | ||
* The customer managed to encrypt your data. | ||
* | ||
* @default - Use an AWS managed key | ||
* @see https://docs.aws.amazon.com/location/latest/developerguide/encryption-at-rest.html | ||
*/ | ||
readonly kmsKey?: kms.IKey; | ||
} | ||
|
||
abstract class GeofenceCollectionBase extends Resource implements IGeofenceCollection { | ||
public abstract readonly geofenceCollectionName: string; | ||
public abstract readonly geofenceCollectionArn: string; | ||
|
||
/** | ||
* Grant the given principal identity permissions to perform the actions on this geofence collection. | ||
*/ | ||
public grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant { | ||
return iam.Grant.addToPrincipal({ | ||
grantee: grantee, | ||
actions: actions, | ||
resourceArns: [this.geofenceCollectionArn], | ||
}); | ||
} | ||
|
||
/** | ||
* Grant the given identity permissions to read this geofence collection | ||
* | ||
* @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 | ||
*/ | ||
public grantRead(grantee: iam.IGrantable): iam.Grant { | ||
return this.grant(grantee, | ||
'geo:ListGeofences', | ||
'geo:GetGeofence', | ||
); | ||
} | ||
} | ||
|
||
/** | ||
* A Geofence Collection | ||
* | ||
* @see https://docs.aws.amazon.com/location/latest/developerguide/geofence-tracker-concepts.html#geofence-overview | ||
*/ | ||
export class GeofenceCollection extends GeofenceCollectionBase { | ||
/** | ||
* Use an existing geofence collection by name | ||
*/ | ||
public static fromGeofenceCollectionName(scope: Construct, id: string, geofenceCollectionName: string): IGeofenceCollection { | ||
const geofenceCollectionArn = Stack.of(scope).formatArn({ | ||
service: 'geo', | ||
resource: 'geofence-collection', | ||
resourceName: geofenceCollectionName, | ||
}); | ||
|
||
return GeofenceCollection.fromGeofenceCollectionArn(scope, id, geofenceCollectionArn); | ||
} | ||
|
||
/** | ||
* Use an existing geofence collection by ARN | ||
*/ | ||
public static fromGeofenceCollectionArn(scope: Construct, id: string, geofenceCollectionArn: string): IGeofenceCollection { | ||
const parsedArn = Stack.of(scope).splitArn(geofenceCollectionArn, ArnFormat.SLASH_RESOURCE_NAME); | ||
|
||
if (!parsedArn.resourceName) { | ||
throw new Error(`Geofence Collection Arn ${geofenceCollectionArn} does not have a resource name.`); | ||
} | ||
|
||
class Import extends GeofenceCollectionBase { | ||
public readonly geofenceCollectionName = parsedArn.resourceName!; | ||
public readonly geofenceCollectionArn = geofenceCollectionArn; | ||
} | ||
|
||
return new Import(scope, id, { | ||
account: parsedArn.account, | ||
region: parsedArn.region, | ||
}); | ||
} | ||
|
||
public readonly geofenceCollectionName: string; | ||
|
||
public readonly geofenceCollectionArn: string; | ||
|
||
/** | ||
* The timestamp for when the geofence collection resource was created in ISO 8601 format | ||
* | ||
* @attribute | ||
*/ | ||
public readonly geofenceCollectionCreateTime: string; | ||
|
||
/** | ||
* The timestamp for when the geofence collection resource was last updated in ISO 8601 format | ||
* | ||
* @attribute | ||
*/ | ||
public readonly geofenceCollectionUpdateTime: string; | ||
|
||
constructor(scope: Construct, id: string, props: GeofenceCollectionProps = {}) { | ||
|
||
if (props.description && !Token.isUnresolved(props.description) && props.description.length > 1000) { | ||
throw new Error(`\`description\` must be between 0 and 1000 characters. Received: ${props.description.length} characters`); | ||
} | ||
|
||
if (props.geofenceCollectionName && !Token.isUnresolved(props.geofenceCollectionName) && !/^[-.\w]{1,100}$/.test(props.geofenceCollectionName)) { | ||
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}`); | ||
} | ||
|
||
mazyu36 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
super(scope, id, { | ||
physicalName: props.geofenceCollectionName ?? Lazy.string({ produce: () => generateUniqueId(this) }), | ||
}); | ||
|
||
const geofenceCollection = new CfnGeofenceCollection(this, 'Resource', { | ||
collectionName: this.physicalName, | ||
description: props.description, | ||
kmsKeyId: props.kmsKey?.keyArn, | ||
}); | ||
|
||
this.geofenceCollectionName = geofenceCollection.ref; | ||
this.geofenceCollectionArn = geofenceCollection.attrArn; | ||
this.geofenceCollectionCreateTime = geofenceCollection.attrCreateTime; | ||
this.geofenceCollectionUpdateTime = geofenceCollection.attrUpdateTime; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export * from './geofence-collection'; | ||
export * from './place-index'; | ||
|
||
// AWS::Location CloudFormation Resources: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { Names } from 'aws-cdk-lib/core'; | ||
|
||
export function generateUniqueId(context: any): string { | ||
mazyu36 marked this conversation as resolved.
Show resolved
Hide resolved
GavinZZ marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const name = Names.uniqueId(context); | ||
if (name.length > 100) { | ||
return name.substring(0, 50) + name.substring(name.length - 50); | ||
} | ||
return name; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
112 changes: 112 additions & 0 deletions
112
packages/@aws-cdk/aws-location-alpha/test/geofence-collection.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import { Match, Template } from 'aws-cdk-lib/assertions'; | ||
import * as iam from 'aws-cdk-lib/aws-iam'; | ||
import * as kms from 'aws-cdk-lib/aws-kms'; | ||
import { Stack } from 'aws-cdk-lib'; | ||
import { GeofenceCollection } from '../lib/geofence-collection'; | ||
|
||
let stack: Stack; | ||
beforeEach(() => { | ||
stack = new Stack(); | ||
}); | ||
|
||
test('create a geofence collection', () => { | ||
new GeofenceCollection(stack, 'GeofenceCollection', { description: 'test' }); | ||
|
||
Template.fromStack(stack).hasResourceProperties('AWS::Location::GeofenceCollection', { | ||
CollectionName: 'GeofenceCollection', | ||
Description: 'test', | ||
}); | ||
}); | ||
|
||
test('creates geofence collection with empty description', () => { | ||
new GeofenceCollection(stack, 'GeofenceCollection', { description: '' }); | ||
|
||
Template.fromStack(stack).hasResourceProperties('AWS::Location::GeofenceCollection', { | ||
Description: '', | ||
}); | ||
}); | ||
|
||
test('throws with invalid description', () => { | ||
expect(() => new GeofenceCollection(stack, 'GeofenceCollection', { | ||
description: 'a'.repeat(1001), | ||
})).toThrow('`description` must be between 0 and 1000 characters. Received: 1001 characters'); | ||
}); | ||
|
||
test('throws with invalid name', () => { | ||
expect(() => new GeofenceCollection(stack, 'GeofenceCollection', { | ||
geofenceCollectionName: 'inv@lid', | ||
})).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'); | ||
}); | ||
|
||
test('grant read actions', () => { | ||
const geofenceCollection = new GeofenceCollection(stack, 'GeofenceCollection', { | ||
}); | ||
|
||
const role = new iam.Role(stack, 'Role', { | ||
assumedBy: new iam.ServicePrincipal('foo'), | ||
}); | ||
|
||
geofenceCollection.grantRead(role); | ||
|
||
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', Match.objectLike({ | ||
PolicyDocument: Match.objectLike({ | ||
Statement: [ | ||
{ | ||
Action: [ | ||
'geo:ListGeofences', | ||
'geo:GetGeofence', | ||
], | ||
Effect: 'Allow', | ||
Resource: { | ||
'Fn::GetAtt': [ | ||
'GeofenceCollection6FAC681F', | ||
'Arn', | ||
], | ||
}, | ||
}, | ||
], | ||
}), | ||
})); | ||
}); | ||
|
||
test('import from arn', () => { | ||
const geofenceCollectionArn = stack.formatArn({ | ||
service: 'geo', | ||
resource: 'geofence-collection', | ||
resourceName: 'MyGeofenceCollection', | ||
}); | ||
const geofenceCollection = GeofenceCollection.fromGeofenceCollectionArn(stack, 'GeofenceCollection', geofenceCollectionArn); | ||
|
||
// THEN | ||
expect(geofenceCollection.geofenceCollectionName).toEqual('MyGeofenceCollection'); | ||
expect(geofenceCollection.geofenceCollectionArn).toEqual(geofenceCollectionArn); | ||
}); | ||
|
||
test('import from name', () => { | ||
// WHEN | ||
const geofenceCollectionName = 'MyGeofenceCollection'; | ||
const geofenceCollection = GeofenceCollection.fromGeofenceCollectionName(stack, 'GeofenceCollection', geofenceCollectionName); | ||
|
||
// THEN | ||
expect(geofenceCollection.geofenceCollectionName).toEqual(geofenceCollectionName); | ||
expect(geofenceCollection.geofenceCollectionArn).toEqual(stack.formatArn({ | ||
service: 'geo', | ||
resource: 'geofence-collection', | ||
resourceName: 'MyGeofenceCollection', | ||
})); | ||
}); | ||
|
||
test('create a geofence collection with a customer managed key)', () => { | ||
// GIVEN | ||
const kmsKey = new kms.Key(stack, 'Key'); | ||
|
||
// WHEN | ||
new GeofenceCollection(stack, 'GeofenceCollection', | ||
{ kmsKey }, | ||
); | ||
|
||
// THEN | ||
Template.fromStack(stack).hasResourceProperties('AWS::Location::GeofenceCollection', { | ||
KmsKeyId: stack.resolve(kmsKey.keyArn), | ||
}); | ||
}); |
19 changes: 19 additions & 0 deletions
19
...-collection.js.snapshot/GeofenceCollectionTestDefaultTestDeployAssert44609017.assets.json
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reasoning behind creating a base class when it's only inherited by one class? Do you foresee another class extending this abstract class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is because the already implemented PlaceIndex class has a base class. I don't know if more classes will be added in the future, but I'm doing this to maintain consistency within the package.
I'd appreciate your opinion on this if you have any.
https://github.com/aws/aws-cdk/blob/main/packages/%40aws-cdk/aws-location-alpha/lib/place-index.ts#L95
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally don't see the purpose of having a separate base class. I would recommend to just use one class instead. Otherwise the PR LGTM.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@GavinZZ
Thanks. I've removed the based class.
Would you please review again?