diff --git a/csharp/random-writer/.gitignore b/csharp/random-writer/.gitignore new file mode 100644 index 000000000..dbd103469 --- /dev/null +++ b/csharp/random-writer/.gitignore @@ -0,0 +1,2 @@ +src/*/bin/ +src/*/obj/ diff --git a/csharp/random-writer/README.md b/csharp/random-writer/README.md new file mode 100644 index 000000000..71f5dc6ae --- /dev/null +++ b/csharp/random-writer/README.md @@ -0,0 +1,29 @@ +# RandomWriter + +## Overview +This sample application demonstrates some essential mechanisms of the *AWS Cloud +Development Kit* (*AWS CDK*) for .NET (in this case, using **C#**): +- Creating a user-defined *construct* +- Having this *construct* implement a feature interface + (`Amazon.CDK.AWS.Events.IRuleTarget`) to bring specific capabilities to the + *construct* +- Granting permissions (for a *Lambda* function to write into a *DynamoDB* + table) using the `.Grant*` methods. + +## Application Resources +> All resources provisioned by this application are *[free-tier] eligible* or +> generally free to provision and use. + +The application provisions the following elements (shown in their hierarchical +presentation withing the *CDK construct tree*): + +- A *stack* named `RandomWriterStack` + - A user-defined *construct* named `RandomWriter` + - A *DynamoDB* table (the physical name of which is to be determined by + *AWS CloudFormation* upon creation) + - A *Lambda* function + * It writes a random hash into the *DynamoDB* table when invoked + - A *CloudWatch* event rule that triggers an execution of the *Lambda* + function *every minute*. + +[free-tier]: https://aws.amazon.com/free/ diff --git a/csharp/random-writer/cdk.json b/csharp/random-writer/cdk.json new file mode 100644 index 000000000..ac6692e9a --- /dev/null +++ b/csharp/random-writer/cdk.json @@ -0,0 +1,3 @@ +{ + "app": "dotnet run --project src/RandomWriter/RandomWriter.csproj" +} diff --git a/csharp/random-writer/src/RandomWriter.sln b/csharp/random-writer/src/RandomWriter.sln new file mode 100644 index 000000000..692f6b306 --- /dev/null +++ b/csharp/random-writer/src/RandomWriter.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26124.0 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RandomWriter", "RandomWriter\RandomWriter.csproj", "{0B87A53B-C322-4A64-9C52-7A047E296339}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0B87A53B-C322-4A64-9C52-7A047E296339}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0B87A53B-C322-4A64-9C52-7A047E296339}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B87A53B-C322-4A64-9C52-7A047E296339}.Debug|x64.ActiveCfg = Debug|Any CPU + {0B87A53B-C322-4A64-9C52-7A047E296339}.Debug|x64.Build.0 = Debug|Any CPU + {0B87A53B-C322-4A64-9C52-7A047E296339}.Debug|x86.ActiveCfg = Debug|Any CPU + {0B87A53B-C322-4A64-9C52-7A047E296339}.Debug|x86.Build.0 = Debug|Any CPU + {0B87A53B-C322-4A64-9C52-7A047E296339}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0B87A53B-C322-4A64-9C52-7A047E296339}.Release|Any CPU.Build.0 = Release|Any CPU + {0B87A53B-C322-4A64-9C52-7A047E296339}.Release|x64.ActiveCfg = Release|Any CPU + {0B87A53B-C322-4A64-9C52-7A047E296339}.Release|x64.Build.0 = Release|Any CPU + {0B87A53B-C322-4A64-9C52-7A047E296339}.Release|x86.ActiveCfg = Release|Any CPU + {0B87A53B-C322-4A64-9C52-7A047E296339}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/csharp/random-writer/src/RandomWriter/Program.cs b/csharp/random-writer/src/RandomWriter/Program.cs new file mode 100644 index 000000000..e4bde383e --- /dev/null +++ b/csharp/random-writer/src/RandomWriter/Program.cs @@ -0,0 +1,14 @@ +using Amazon.CDK; + +namespace RandomWriter +{ + public sealed class Program + { + public static void Main(string[] args) + { + var app = new App(); + new RandomWriterStack(app, "RandomWriterStack"); + app.Synth(); + } + } +} diff --git a/csharp/random-writer/src/RandomWriter/RandomWriter.csproj b/csharp/random-writer/src/RandomWriter/RandomWriter.csproj new file mode 100644 index 000000000..975e31751 --- /dev/null +++ b/csharp/random-writer/src/RandomWriter/RandomWriter.csproj @@ -0,0 +1,17 @@ + + + + Exe + netcoreapp3.0 + + + + + + + + + + + + diff --git a/csharp/random-writer/src/RandomWriter/RandomWriterStack.cs b/csharp/random-writer/src/RandomWriter/RandomWriterStack.cs new file mode 100644 index 000000000..1c9e5cb0d --- /dev/null +++ b/csharp/random-writer/src/RandomWriter/RandomWriterStack.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using Amazon.CDK; +using Amazon.CDK.AWS.DynamoDB; +using Amazon.CDK.AWS.Events; +using Amazon.CDK.AWS.Events.Targets; +using Amazon.CDK.AWS.Lambda; + +namespace RandomWriter +{ + internal sealed class RandomWriterStack : Stack + { + public RandomWriterStack(Construct parent, string id, IStackProps props = null) : base(parent, id, props) + { + // The code that defines your stack goes here + var randomWriter = new RandomWriter(this, "RandomWriter"); + new Rule(this, "Trigger", new RuleProps() + { + Description = "Triggers a RandomWrite every minute", + Schedule = Schedule.Rate(Duration.Minutes(1)), + Targets = new [] { randomWriter } + }); + } + } + + internal sealed class RandomWriter : Construct, IRuleTarget + { + private IFunction Function { get; } + + public RandomWriter(Construct scope, string id): base(scope, id) + { + var table = new Table(this, "Table", new TableProps + { + PartitionKey = new Attribute + { + Name = "ID", + Type = AttributeType.STRING + } + }); + + Function = new Function(this, "Lambda", new FunctionProps + { + Runtime = Runtime.NODEJS_10_X, + Handler = "index.handler", + Code = Code.FromAsset("src/RandomWriter/resources"), + Environment = new Dictionary + { + { "TABLE_NAME", table.TableName } + } + }); + + table.GrantReadWriteData(Function); + } + + public IRuleTargetConfig Bind(IRule rule, string id = null) + { + return new LambdaFunction(Function).Bind(rule, id); + } + } +} diff --git a/csharp/random-writer/src/RandomWriter/resources/index.js b/csharp/random-writer/src/RandomWriter/resources/index.js new file mode 100644 index 000000000..b16cfad14 --- /dev/null +++ b/csharp/random-writer/src/RandomWriter/resources/index.js @@ -0,0 +1,26 @@ +const { DynamoDB } = require('aws-sdk'); +const crypto = require('crypto'); + +/** + * This Lambda event handler expects the name of a DynamoDB table to be passed + * in the `TABLE_NAME` environment variable. The Lambda function must be granted + * WRITE permissions on the DynamoDB table (for it will add new items in the + * table). + * + * The DynamoDB table must have a hash-only primary key, where the hash key is + * named `ID` and is of type STRING. + */ +exports.handler = async function handler(event, context) { + console.log(JSON.stringify(event, undefined, 2)); + + var seed = `${Date.now}${Math.random()}`; + const id = crypto.createHash('sha1').update(seed).digest('hex'); + + const ddb = new DynamoDB(); + await ddb.putItem({ + TableName: process.env.TABLE_NAME, + Item: { + ID: { S: id } + } + }).promise(); +};