-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added the AWS CDK code for the patterns - first version
- Loading branch information
Mohanna Shahrad
committed
Aug 16, 2022
1 parent
1e35703
commit 59e1e20
Showing
48 changed files
with
3,847 additions
and
0 deletions.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
*.swp | ||
__pycache__ | ||
.pytest_cache | ||
.venv | ||
*.egg-info | ||
|
||
# CDK asset staging directory | ||
.cdk.staging | ||
cdk.out |
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,105 @@ | ||
|
||
# Welcome to your CDK project! | ||
# IoT Data visulaization with AWS IoT Analytics | ||
|
||
The `cdk.json` file tells the CDK Toolkit how to execute your app. | ||
|
||
This project is set up like a standard Python project. The initialization | ||
process also creates a virtualenv within this project, stored under the `.venv` | ||
directory. To create the virtualenv it assumes that there is a `python3` | ||
(or `python` for Windows) executable in your path with access to the `venv` | ||
package. If for any reason the automatic creation of the virtualenv fails, | ||
you can create the virtualenv manually. | ||
|
||
To manually create a virtualenv on MacOS and Linux: | ||
|
||
``` | ||
$ python3 -m venv .venv | ||
``` | ||
|
||
After the init process completes and the virtualenv is created, you can use the following | ||
step to activate your virtualenv. | ||
|
||
``` | ||
$ source .venv/bin/activate | ||
``` | ||
|
||
If you are a Windows platform, you would activate the virtualenv like this: | ||
|
||
``` | ||
% .venv\Scripts\activate.bat | ||
``` | ||
|
||
Once the virtualenv is activated, you can install the required dependencies. | ||
|
||
``` | ||
$ pip install -r requirements.txt | ||
``` | ||
|
||
At this point you can now synthesize the CloudFormation template for this code. | ||
|
||
``` | ||
$ cdk synth | ||
``` | ||
|
||
To add additional dependencies, for example other CDK libraries, just add | ||
them to your `setup.py` file and rerun the `pip install -r requirements.txt` | ||
command. | ||
|
||
## Useful commands | ||
|
||
* `cdk ls` list all stacks in the app | ||
* `cdk synth` emits the synthesized CloudFormation template | ||
* `cdk deploy` deploy this stack to your default AWS account/region | ||
* `cdk diff` compare deployed stack with current state | ||
* `cdk docs` open CDK documentation | ||
|
||
## Context parameters | ||
There are multiple context parameters that you need to set before synthesizing or delpoying this CDK stack. You can specify a context variable either as part of an AWS CDK CLI command, or in `cdk.json`. | ||
To create a command line context variable, use the __--context (-c) option__, as shown in the following example. | ||
|
||
``` | ||
$ cdk cdk synth -c bucket_name=mybucket | ||
``` | ||
|
||
To specify the same context variable and value in the cdk.json file, use the following code. | ||
|
||
``` | ||
{ | ||
"context": { | ||
"bucket_name": "mybucket" | ||
} | ||
} | ||
``` | ||
|
||
In this project, these are the following parameters to be set: | ||
|
||
* `topic_sql` | ||
<br>It is required for IoT Core rule creation to add a simplified SQL syntax to filter messages received on an MQTT topic and push the data elsewhere. | ||
<br> __Format__: Enter an SQL statement using the following: ```SELECT <Attribute> FROM <Topic Filter> WHERE <Condition>```. For example: ```SELECT temperature FROM 'iot/topic' WHERE temperature > 50```. To learn more, see AWS IoT SQL Reference. | ||
|
||
* `analytics_channel_name` `<Optional>` | ||
<br> The name of the IoT Analytics channel that will get connected to the IoT Core to get your data. | ||
<br> __Format__: Choose a unique name that you can easily identify. The channel name must contain 1-128 characters. Valid characters are a-z, A-Z, 0-9, and _ (underscore). | ||
|
||
* `analytics_datastore_name` `<Optional>` | ||
<br> The name of the IoT Analytics datastore that will get connected to the IoT Core to store your data. | ||
<br> __Format__: A unique ID identifies your data store. You can't change this ID after you create it. Valid characters: a-z, A-Z, 0-9, and _ (underscore). | ||
|
||
* `analytics_dataset_name` `<Optional>` | ||
<br> The name of the IoT Analytics SQL dataset that will get connected to the IoT Core. A SQL dataset is a materialized view from a data store. | ||
<br> __Format__: Choose a unique name that you can easily identify. The dataset name must contain 1-128 characters. Valid characters are a-z, A-Z, 0-9, and _ (underscore). | ||
|
||
* `analytics_pipeline_name` `<Optional>` | ||
<br> The name of the IoT Analytics pipeline that will read messages from the channel and write processed data to the datastore. | ||
<br> __Format__: Valid characters: a-z, A-Z, 0-9, and _ (underscore). | ||
|
||
* `analytics_iot_rule_name` `<Optional>` | ||
<br> The name of the IoT Core rule that is going to be created. | ||
<br> __Format__: Should be an alphanumeric string that can also contain underscore (_) characters, but no spaces. | ||
|
||
* `analytics_iot_role_name` `<Optional>` | ||
<br> An IAM role should be created to grant AWS IoT access to your endpoint. This parameter is for setting the name of this role. | ||
<br> __Format__: Enter a unique role name that contains alphanumeric characters, hyphens, and underscores. A role name can't contain any spaces. | ||
|
||
Enjoy! |
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,27 @@ | ||
import os | ||
|
||
import aws_cdk as cdk | ||
|
||
from io_t_analytics_pattern.io_t_analytics_pattern_stack import IoTAnalyticsPatternStack | ||
|
||
|
||
app = cdk.App() | ||
IoTAnalyticsPatternStack(app, "IoTAnalyticsPatternStack", | ||
# If you don't specify 'env', this stack will be environment-agnostic. | ||
# Account/Region-dependent features and context lookups will not work, | ||
# but a single synthesized template can be deployed anywhere. | ||
|
||
# Uncomment the next line to specialize this stack for the AWS Account | ||
# and Region that are implied by the current CLI configuration. | ||
|
||
#env=cdk.Environment(account=os.getenv('CDK_DEFAULT_ACCOUNT'), region=os.getenv('CDK_DEFAULT_REGION')), | ||
|
||
# Uncomment the next line if you know exactly what Account and Region you | ||
# want to deploy the stack to. */ | ||
|
||
#env=cdk.Environment(account='123456789012', region='us-east-1'), | ||
|
||
# For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html | ||
) | ||
|
||
app.synth() |
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,45 @@ | ||
{ | ||
"app": "python3 app.py", | ||
"watch": { | ||
"include": [ | ||
"**" | ||
], | ||
"exclude": [ | ||
"README.md", | ||
"cdk*.json", | ||
"requirements*.txt", | ||
"source.bat", | ||
"**/__init__.py", | ||
"python/__pycache__", | ||
"tests" | ||
] | ||
}, | ||
"context": { | ||
"@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, | ||
"@aws-cdk/core:stackRelativeExports": true, | ||
"@aws-cdk/aws-rds:lowercaseDbIdentifier": true, | ||
"@aws-cdk/aws-lambda:recognizeVersionProps": true, | ||
"@aws-cdk/aws-lambda:recognizeLayerVersion": true, | ||
"@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, | ||
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, | ||
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, | ||
"@aws-cdk/core:checkSecretUsage": true, | ||
"@aws-cdk/aws-iam:minimizePolicies": true, | ||
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, | ||
"@aws-cdk/core:validateSnapshotRemovalPolicy": true, | ||
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, | ||
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, | ||
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, | ||
"@aws-cdk/core:target-partitions": [ | ||
"aws", | ||
"aws-cn" | ||
], | ||
"topic_sql": "SELECT * FROM 'IoT_Analytics_demo'", | ||
"analytics_channel_name": "demo_iot_channel", | ||
"analytics_datastore_name": "demo_iot_datastore", | ||
"analytics_dataset_name": "demo_iot_dataset", | ||
"analytics_pipeline_name": "demo_iot_pipeline", | ||
"analytics_iot_role_name": "demo_iot_iotanalytics_role", | ||
"analytics_iot_rule_name": "demo_to_iotanalytics_rule" | ||
} | ||
} |
Empty file.
201 changes: 201 additions & 0 deletions
201
...plates/AWS_CDK/IoTAnalyticsPattern/io_t_analytics_pattern/io_t_analytics_pattern_stack.py
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,201 @@ | ||
from aws_cdk import ( | ||
Stack, | ||
aws_iam as iam, | ||
aws_iot as iot, | ||
aws_iotanalytics as iotanalytics, | ||
aws_s3 as s3, | ||
aws_logs as logs | ||
) | ||
from constructs import Construct | ||
import aws_cdk as cdk | ||
import re | ||
import sys | ||
|
||
sys.path.append('../') | ||
|
||
from customExceptions import * | ||
|
||
class IoTAnalyticsPatternStack(Stack): | ||
|
||
# Defining the class variables | ||
topic_sql = "" | ||
analytics_channel_name = "" | ||
analytics_datastore_name = "" | ||
analytics_dataset_name = "" | ||
analytics_pipeline_name = "" | ||
analytics_iot_role_name = "" | ||
analytics_iot_rule_name = "" | ||
|
||
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: | ||
super().__init__(scope, construct_id, **kwargs) | ||
|
||
# Getting the context parameters | ||
|
||
# Required parameters for users to set in the CLI command or cdk.json | ||
self.topic_sql = self.node.try_get_context("topic_sql") | ||
|
||
# Optional parameters for users to set in the CLI command or cdk.json | ||
self.analytics_channel_name = self.node.try_get_context("analytics_channel_name") | ||
self.analytics_datastore_name = self.node.try_get_context("analytics_datastore_name") | ||
self.analytics_dataset_name = self.node.try_get_context("analytics_dataset_name") | ||
self.analytics_pipeline_name = self.node.try_get_context("analytics_pipeline_name") | ||
self.analytics_iot_role_name = self.node.try_get_context("analytics_iot_role_name") | ||
self.analytics_iot_rule_name = self.node.try_get_context("analytics_iot_rule_name") | ||
|
||
# Perform input validation | ||
self.performInputValidation() | ||
|
||
# Creating an IoT Analytics Channel | ||
analytics_channel = iotanalytics.CfnChannel(self, self.analytics_channel_name, channel_name=self.analytics_channel_name) | ||
analytics_channel.apply_removal_policy(policy=cdk.RemovalPolicy.DESTROY) | ||
|
||
# Creating an IoT Analytics Datastore | ||
analytics_datastore = iotanalytics.CfnDatastore(self, self.analytics_datastore_name, datastore_name=self.analytics_datastore_name, | ||
datastore_storage=iotanalytics.CfnDatastore.DatastoreStorageProperty( | ||
service_managed_s3={} | ||
), | ||
retention_period=iotanalytics.CfnDatastore.RetentionPeriodProperty( | ||
number_of_days=30, | ||
unlimited=False | ||
)) | ||
analytics_datastore.apply_removal_policy(policy=cdk.RemovalPolicy.DESTROY) | ||
|
||
# Creating an IoT Analytics Dataset | ||
analytics_dataset = iotanalytics.CfnDataset(self, self.analytics_dataset_name, actions=[iotanalytics.CfnDataset.ActionProperty( | ||
action_name="QueryDatastoreCDK", | ||
query_action=iotanalytics.CfnDataset.QueryActionProperty( | ||
sql_query= f'''SELECT * FROM {analytics_datastore.datastore_name}''' | ||
) | ||
)]) | ||
analytics_dataset.node.add_dependency(analytics_datastore) | ||
analytics_dataset.apply_removal_policy(policy=cdk.RemovalPolicy.DESTROY) | ||
|
||
# Creating an Iot Analytics Pipeline | ||
analytics_pipeline = iotanalytics.CfnPipeline(self, self.analytics_pipeline_name, pipeline_name=self.analytics_pipeline_name, pipeline_activities=[ | ||
iotanalytics.CfnPipeline.ActivityProperty( | ||
channel=iotanalytics.CfnPipeline.ChannelProperty( | ||
channel_name=analytics_channel.channel_name, | ||
name=analytics_channel.channel_name, | ||
next=analytics_datastore.datastore_name | ||
), | ||
datastore=iotanalytics.CfnPipeline.DatastoreProperty( | ||
datastore_name=analytics_datastore.datastore_name, | ||
name=analytics_datastore.datastore_name | ||
) | ||
)]) | ||
analytics_pipeline.node.add_dependency(analytics_datastore) | ||
analytics_pipeline.node.add_dependency(analytics_channel) | ||
analytics_pipeline.apply_removal_policy(policy=cdk.RemovalPolicy.DESTROY) | ||
|
||
|
||
# Creating the role for the IoT-Analytics rule | ||
channel_arn = f"arn:aws:iotanalytics:{self.region}:{self.account}:channel/{analytics_channel.channel_name}" | ||
iot_analytics_role = iam.Role(self, self.analytics_iot_role_name, assumed_by=iam.ServicePrincipal("iot.amazonaws.com")) | ||
iot_analytics_role.add_to_policy(iam.PolicyStatement(effect=iam.Effect.ALLOW, resources=[channel_arn], actions=["iotanalytics:BatchPutMessage"])) | ||
iot_analytics_role.node.add_dependency(analytics_channel) | ||
iot_analytics_role.apply_removal_policy(policy=cdk.RemovalPolicy.DESTROY) | ||
|
||
# Creating a cloudwatch log group for topic rule's error action | ||
log_group = logs.LogGroup(self, "iot_to_analytics_log_group" , log_group_name="iot_to_analytics_log_group", removal_policy=cdk.RemovalPolicy.DESTROY) | ||
|
||
iot_to_cloudwatch_logs_role = iam.Role(self, "iot_to_analytics_log_group_role", assumed_by=iam.ServicePrincipal("iot.amazonaws.com")) | ||
iot_to_cloudwatch_logs_role.add_to_policy(iam.PolicyStatement( | ||
effect=iam.Effect.ALLOW, resources=[log_group.log_group_arn], | ||
actions=["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents", "logs:PutMetricFilter", "logs:PutRetentionPolicy"])) | ||
iot_to_cloudwatch_logs_role.node.add_dependency(log_group) | ||
iot_to_cloudwatch_logs_role.apply_removal_policy(policy=cdk.RemovalPolicy.DESTROY) | ||
|
||
|
||
# Creating the IoT Core Rule | ||
topic_rule = iot.CfnTopicRule(self, self.analytics_iot_rule_name, topic_rule_payload=iot.CfnTopicRule.TopicRulePayloadProperty( | ||
actions=[iot.CfnTopicRule.ActionProperty( iot_analytics=iot.CfnTopicRule.IotAnalyticsActionProperty( | ||
channel_name=analytics_channel.channel_name, | ||
role_arn=iot_analytics_role.role_arn, | ||
) | ||
)], | ||
sql=self.topic_sql, | ||
error_action= iot.CfnTopicRule.ActionProperty( | ||
cloudwatch_logs=iot.CfnTopicRule.CloudwatchLogsActionProperty( | ||
log_group_name=log_group.log_group_name, | ||
role_arn=iot_to_cloudwatch_logs_role.role_arn | ||
) | ||
))) | ||
|
||
topic_rule.node.add_dependency(analytics_channel) | ||
topic_rule.node.add_dependency(iot_analytics_role) | ||
topic_rule.apply_removal_policy(policy=cdk.RemovalPolicy.DESTROY) | ||
|
||
|
||
def performInputValidation(self): | ||
self.validateTopicSQL(self.topic_sql) | ||
self.validateAnalyticsChannelName(self.analytics_channel_name) | ||
self.validateAnalyticsDatasetName(self.analytics_dataset_name) | ||
self.validateAnalyticsDatastoreName(self.analytics_datastore_name) | ||
self.validateAnalyticsPipelineName(self.analytics_pipeline_name) | ||
self.validateRoleName(self.analytics_iot_role_name) | ||
self.validateIoTRuleName(self.analytics_iot_rule_name) | ||
|
||
def validateTopicSQL(self, sqlStatement): | ||
if not sqlStatement: | ||
raise NoSQL | ||
elif type(sqlStatement) != str: | ||
raise WrongFormattedInput("The input sql statement does not have a right format. Please refer to README.md for more information.") | ||
return | ||
|
||
def validateAnalyticsChannelName(self, channelName): | ||
if not channelName: | ||
self.analytics_channel_name = "demo_iot_channel" | ||
else: | ||
if len(channelName) < 1 or len(channelName) > 128: | ||
raise WrongLengthForInput("Not a valid input for channel name: The channel name must contain 1-128 characters.") | ||
elif not re.match(r'^[a-zA-Z0-9_]+$', channelName): | ||
raise WrongFormattedInput("String format error for channel name: Valid characters are a-z, A-Z, 0-9, and _ (underscore)") | ||
return | ||
|
||
def validateAnalyticsDatasetName(self, datasetName): | ||
if not datasetName: | ||
self.analytics_dataset_name = "demo_iot_dataset" | ||
else: | ||
if len(datasetName) < 1 or len(datasetName) > 128: | ||
raise WrongLengthForInput("Not a valid input for dataset name: The dataset name must contain 1-128 characters.") | ||
elif not re.match(r'^[a-zA-Z0-9_]+$', datasetName): | ||
raise WrongFormattedInput("String format error for dataset name: Valid characters are a-z, A-Z, 0-9, and _ (underscore)") | ||
return | ||
|
||
def validateAnalyticsDatastoreName(self, datastoreName): | ||
if not datastoreName: | ||
self.analytics_datastore_name = "demo_iot_datastore" | ||
else: | ||
if not re.match(r'^[a-zA-Z0-9_]+$', datastoreName): | ||
raise WrongFormattedInput("String format error for datastore name: Valid characters are a-z, A-Z, 0-9, and _ (underscore)") | ||
return | ||
|
||
def validateAnalyticsPipelineName(self, pipelineName): | ||
if not pipelineName: | ||
self.analytics_pipeline_name = "demo_iot_pipeline" | ||
else: | ||
if not re.match(r'^[a-zA-Z0-9_]+$', pipelineName): | ||
raise WrongFormattedInput("String format error for pipeline name: Valid characters are a-z, A-Z, 0-9, and _ (underscore)") | ||
return | ||
|
||
def validateRoleName(self, roleName): | ||
if not roleName: | ||
self.analytics_iot_role_name = "demo_iot_iotanalytics_role" | ||
elif type(roleName) != str: | ||
raise WrongFormattedInput("The provided input for the IAM role name is not of type string") | ||
elif len(roleName) > 64: | ||
raise WrongLengthForInput("The length of the IAM role name string should not exceed 64 characters.") | ||
elif not re.match(r'^[a-zA-Z0-9+=,@-_\.]+$', roleName): | ||
raise WrongFormattedInput("String format error: The IAM role name should be an alphanumeric string that can also contain '+=,.@-_' characters.") | ||
else: | ||
return | ||
|
||
def validateIoTRuleName(self, ruleName): | ||
if not ruleName: | ||
self.analytics_iot_rule_name = "demo_to_iotanalytics_rule" | ||
elif type(ruleName) != str: | ||
raise WrongFormattedInput("The provided input for topic rule name is not of type string") | ||
elif not re.match(r'^[a-zA-Z0-9_]+$', ruleName): | ||
raise WrongFormattedInput("String format error: The topic rule name should be an alphanumeric string that can also contain underscore (_) characters, but no spaces.") | ||
else: | ||
return |
1 change: 1 addition & 0 deletions
1
Cloud_Templates/AWS_CDK/IoTAnalyticsPattern/requirements-dev.txt
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 @@ | ||
pytest==6.2.5 |
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,2 @@ | ||
aws-cdk-lib==2.37.1 | ||
constructs>=10.0.0,<11.0.0 |
Oops, something went wrong.