Skip to content

Commit

Permalink
feat(location): PlaceIndex (aws#22853)
Browse files Browse the repository at this point in the history
Add a L2 for the place index resource.


----

### All Submissions:

* [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md)

### Adding new Unconventional Dependencies:

* [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies)

### New Features

* [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)?
	* [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)?

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
jogold authored and Brennan Ho committed Dec 9, 2022
1 parent 1c2f95f commit bd567a8
Show file tree
Hide file tree
Showing 16 changed files with 736 additions and 22 deletions.
48 changes: 33 additions & 15 deletions packages/@aws-cdk/aws-location/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,49 @@
>
> [CFN Resources]: https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib
![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge)

> The APIs of higher level constructs in this module are experimental and under active development.
> They are subject to non-backward compatible changes or removal in any future version. These are
> not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be
> announced in the release notes. This means that while you may use them, you may need to update
> your source code when upgrading to a newer version of this package.
---

<!--END STABILITY BANNER-->

This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project.

```ts nofixture
import * as location from '@aws-cdk/aws-location';
```

<!--BEGIN CFNONLY DISCLAIMER-->
Amazon Location Service lets you add location data and functionality to applications, which
includes capabilities such as maps, points of interest, geocoding, routing, geofences, and
tracking. Amazon Location provides location-based services (LBS) using high-quality data from
global, trusted providers Esri and HERE. With affordable data, tracking and geofencing
capabilities, and built-in metrics for health monitoring, you can build sophisticated
location-enabled applications.

There are no official hand-written ([L2](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) constructs for this service yet. Here are some suggestions on how to proceed:
## Place Index

- Search [Construct Hub for Location construct libraries](https://constructs.dev/search?q=location)
- Use the automatically generated [L1](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_l1_using) constructs, in the same way you would use [the CloudFormation AWS::Location resources](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_Location.html) directly.
A key function of Amazon Location Service is the ability to search the geolocation information.
Amazon Location provides this functionality via the Place index resource. The place index includes
which [data provider](https://docs.aws.amazon.com/location/latest/developerguide/what-is-data-provider.html)
to use for the search.

To create a place index, define a `PlaceIndex`:

<!--BEGIN CFNONLY DISCLAIMER-->

There are no hand-written ([L2](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) constructs for this service yet.
However, you can still use the automatically generated [L1](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_l1_using) constructs, and use this service exactly as you would using CloudFormation directly.
```ts
new location.PlaceIndex(this, 'PlaceIndex', {
placeIndexName: 'MyPlaceIndex', // optional, defaults to a generated name
dataSource: location.DataSource.HERE, // optional, defaults to Esri
});
```

For more information on the resources and properties available for this service, see the [CloudFormation documentation for AWS::Location](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_Location.html).
Use the `grant()` or `grantSearch()` method to grant the given identity permissions to perform actions
on the place index:

(Read the [CDK Contributing Guide](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and submit an RFC if you are interested in contributing to this construct library.)
```ts
declare const role: iam.Role;

<!--END CFNONLY DISCLAIMER-->
const placeIndex = new location.PlaceIndex(this, 'PlaceIndex');
placeIndex.grantSearch(role);
```
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-location/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from './place-index';

// AWS::Location CloudFormation Resources:
export * from './location.generated';
209 changes: 209 additions & 0 deletions packages/@aws-cdk/aws-location/lib/place-index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import * as iam from '@aws-cdk/aws-iam';
import { ArnFormat, IResource, Lazy, Names, Resource, Stack, Token } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CfnPlaceIndex } from './location.generated';

/**
* A Place Index
*/
export interface IPlaceIndex extends IResource {
/**
* The name of the place index
*
* @attribute
*/
readonly placeIndexName: string;

/**
* The Amazon Resource Name (ARN) of the place index resource
*
* @attribute Arn,IndexArn
*/
readonly placeIndexArn: string;
}

/**
* Properties for a place index
*/
export interface PlaceIndexProps {
/**
* A name for the place index
*
* @default - A name is automatically generated
*/
readonly placeIndexName?: string;

/**
* Data source for the place index
*
* @default DataSource.ESRI
*/
readonly dataSource?: DataSource;

/**
* Intend use for the results of an operation
*
* @default IntendedUse.SINGLE_USE
*/
readonly intendedUse?: IntendedUse;

/**
* A description for the place index
*
* @default - no description
*/
readonly description?: string;
}

/**
* Data source for a place index
*/
export enum DataSource {
/**
* Esri
*
* @see https://docs.aws.amazon.com/location/latest/developerguide/esri.html
*/
ESRI = 'Esri',

/**
* HERE
*
* @see https://docs.aws.amazon.com/location/latest/developerguide/HERE.html
*/
HERE = 'Here'
}

/**
* Intend use for the results of an operation
*/
export enum IntendedUse {
/**
* The results won't be stored
*/
SINGLE_USE = 'SingleUse',

/**
* The result can be cached or stored in a database
*/
STORAGE = 'Storage'
}

abstract class PlaceIndexBase extends Resource implements IPlaceIndex {
public abstract readonly placeIndexName: string;
public abstract readonly placeIndexArn: string;

/**
* Grant the given principal identity permissions to perform the actions on this place index.
*/
public grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant {
return iam.Grant.addToPrincipal({
grantee: grantee,
actions: actions,
resourceArns: [this.placeIndexArn],
});
}

/**
* Grant the given identity permissions to search using this index
*/
public grantSearch(grantee: iam.IGrantable): iam.Grant {
return this.grant(grantee,
'geo:SearchPlaceIndexForPosition',
'geo:SearchPlaceIndexForSuggestions',
'geo:SearchPlaceIndexForText',
);
}
}

/**
* A Place Index
*
* @see https://docs.aws.amazon.com/location/latest/developerguide/places-concepts.html
*/
export class PlaceIndex extends PlaceIndexBase {
/**
* Use an existing place index by name
*/
public static fromPlaceIndexName(scope: Construct, id: string, placeIndexName: string): IPlaceIndex {
const placeIndexArn = Stack.of(scope).formatArn({
service: 'geo',
resource: 'place-index',
resourceName: placeIndexName,
});

return PlaceIndex.fromPlaceIndexArn(scope, id, placeIndexArn);
}

/**
* Use an existing place index by ARN
*/
public static fromPlaceIndexArn(scope: Construct, id: string, placeIndexArn: string): IPlaceIndex {
const parsedArn = Stack.of(scope).splitArn(placeIndexArn, ArnFormat.SLASH_RESOURCE_NAME);

if (!parsedArn.resourceName) {
throw new Error(`Place Index Arn ${placeIndexArn} does not have a resource name.`);
}

class Import extends PlaceIndexBase {
public readonly placeIndexName = parsedArn.resourceName!;
public readonly placeIndexArn = placeIndexArn;
}

return new Import(scope, id, {
account: parsedArn.account,
region: parsedArn.region,
});
}

public readonly placeIndexName: string;

public readonly placeIndexArn: string;

/**
* The timestamp for when the place index resource was created in ISO 8601 forma
*
* @attribute
*/
public readonly placeIndexCreateTime: string;

/**
* The timestamp for when the place index resource was last updated in ISO 8601 format
*
* @attribute
*/
public readonly placeIndexUpdateTime: string;


constructor(scope: Construct, id: string, props: PlaceIndexProps = {}) {
if (props.placeIndexName && !Token.isUnresolved(props.placeIndexName) && !/^[-.\w]{1,100}$/.test(props.placeIndexName)) {
throw new Error(`Invalid place index name. The place index name must be between 1 and 100 characters and contain only alphanumeric characters, hyphens, periods and underscores. Received: ${props.placeIndexName}`);
}

super(scope, id, {
physicalName: props.placeIndexName ?? Lazy.string({ produce: () => this.generateUniqueId() }),
});

const placeIndex = new CfnPlaceIndex(this, 'Resource', {
indexName: this.physicalName,
dataSource: props.dataSource ?? DataSource.ESRI,
dataSourceConfiguration: props.intendedUse
? { intendedUse: props.intendedUse }
: undefined,
description: props.description,
});

this.placeIndexName = placeIndex.ref;
this.placeIndexArn = placeIndex.attrArn;
this.placeIndexCreateTime = placeIndex.attrCreateTime;
this.placeIndexUpdateTime = placeIndex.attrUpdateTime;
}

private generateUniqueId(): string {
const name = Names.uniqueId(this);
if (name.length > 100) {
return name.substring(0, 50) + name.substring(name.length - 50);
}
return name;
}
}
6 changes: 5 additions & 1 deletion packages/@aws-cdk/aws-location/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,23 +85,27 @@
"devDependencies": {
"@aws-cdk/assertions": "0.0.0",
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/integ-runner": "0.0.0",
"@aws-cdk/integ-tests": "0.0.0",
"@aws-cdk/cfn2ts": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
"@types/jest": "^27.5.2"
},
"dependencies": {
"@aws-cdk/core": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"constructs": "^10.0.0"
},
"peerDependencies": {
"@aws-cdk/core": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"constructs": "^10.0.0"
},
"engines": {
"node": ">= 14.15.0"
},
"stability": "experimental",
"maturity": "cfn-only",
"maturity": "experimental",
"awscdkio": {
"announce": false
},
Expand Down
13 changes: 13 additions & 0 deletions packages/@aws-cdk/aws-location/rosetta/default.ts-fixture
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Fixture with packages imported, but nothing else
import { Stack } from '@aws-cdk/core';
import { Construct } from 'constructs';
import * as location from '@aws-cdk/aws-location';
import * as iam from '@aws-cdk/aws-iam';

class Fixture extends Stack {
constructor(scope: Construct, id: string) {
super(scope, id);

/// here
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"version": "21.0.0",
"files": {
"21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": {
"source": {
"path": "PlaceIndexTestDefaultTestDeployAssert3F96C508.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
}
},
"dockerImages": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"Parameters": {
"BootstrapVersion": {
"Type": "AWS::SSM::Parameter::Value<String>",
"Default": "/cdk-bootstrap/hnb659fds/version",
"Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
}
},
"Rules": {
"CheckBootstrapVersion": {
"Assertions": [
{
"Assert": {
"Fn::Not": [
{
"Fn::Contains": [
[
"1",
"2",
"3",
"4",
"5"
],
{
"Ref": "BootstrapVersion"
}
]
}
]
},
"AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
}
]
}
}
}
Loading

0 comments on commit bd567a8

Please sign in to comment.