Skip to content

Commit ce4d33d

Browse files
committed
🔥 cdk-python >> ServerlessWidgetService
1 parent 5412afe commit ce4d33d

File tree

15 files changed

+416
-4
lines changed

15 files changed

+416
-4
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,5 @@ dmypy.json
131131

132132
# Pyre type checker
133133
.pyre/
134+
cdk-python/ServerlessWidgetService/cdk.out/
135+
cdk

README.md

+23-3
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,35 @@ To get started with the Social Listening application, you can deploy into your A
3535

3636
## 3. Environment Setup & Deployment
3737

38-
### 3.1. Deploy fargate-twitter-reader on ECS Fargate
38+
### 3.1. Deploy Bootstrap DOcker
3939

4040
```bash
4141
./bootstrap.sh
4242
```
4343

4444
* [ ] ECS/EKS Container - CI/CD pipeline
4545

46-
### 3.2. Building Lambda Package
46+
### 3.2. CDK - Infrastructure is Code
47+
48+
```
49+
cd cdk-python/ServerlessWidgetService/
50+
51+
pip install -r requirements.txt # Best to do this in a virtualenv
52+
53+
. ../../.env.sh # social-listening/.env.sh
54+
cdk deploy # Deploys the CloudFormation template
55+
56+
## Afterwards
57+
cdk destroy
58+
```
59+
60+
### 3.3. Deploy fargate-twitter-reader on ECS Fargate
61+
62+
```bash
63+
./bootstrap.sh
64+
```
65+
66+
### 3.4. Building Lambda Package
4767

4868
```bash
4969
cd deployment
@@ -58,5 +78,5 @@ The template will then expect the source code to be located in the solutions-[re
5878

5979
* [ ] Serverless (Lambda, API-Gateway) - CI/CD pipeline
6080

61-
### 3.3. Cloudformation template and Lambda function
81+
### 3.4. Cloudformation template and Lambda function
6282
Located in deployment/dist
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
*.swp
2+
package-lock.json
3+
__pycache__
4+
.pytest_cache
5+
.env
6+
*.egg-info
7+
8+
# CDK asset staging directory
9+
.cdk.staging
10+
cdk.
11+
12+
cdk.context.json
13+
**/cdk-out/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Creating a Serverless Application Using the AWS CDK
2+
3+
4+
### 1. Create a AWS CDK App
5+
6+
```
7+
mkdir ServerlessWidgetService && cd ServerlessWidgetService
8+
cdk init --language python
9+
source .env/bin/activate
10+
pip install -r requirements.txt
11+
12+
cdk synth
13+
14+
mkdir resources
15+
16+
```
17+
18+
### 2. Create a Lambda Function
19+
20+
### 3. Creating a Widget Service
21+
22+
```
23+
pip3 install aws_cdk.aws_apigateway aws_cdk.aws_lambda aws_cdk.aws_s3
24+
```
25+
26+
### 4. Add the Service to the App
27+
28+
### 5. Deploy and Test the App
29+
30+
```
31+
export GUID="ct5o1hvsh9"
32+
export REGION="ap-southeast-2"
33+
34+
curl -X GET 'https://ct5o1hvsh9.execute-api.ap-southeast-2.amazonaws.com/prod/'
35+
curl -X POST 'https://${GUID}.execute-api-REGION.amazonaws.com/prod/example'
36+
curl -X GET 'https://${GUID}.execute-api-REGION.amazonaws.com/prod'
37+
curl -X GET 'https://${GUID}.execute-api-REGION.amazonaws.com/prod/example'
38+
curl -X DELETE 'https://${GUID}.execute-api-REGION.amazonaws.com/prod/example'
39+
curl -X GET 'https://${GUID}.execute-api-REGION.amazonaws.com/prod'
40+
```
41+
42+
### 6. Add the Individual Widget Functions
43+
44+
### 7. Clean Up
45+
46+
```
47+
48+
The initialization process also creates a virtualenv within this project, stored under the .env directory.
49+
To create the virtualenv it assumes that there is a `python3`
50+
(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.
51+
52+
To manually create a virtualenv on MacOS and Linux:
53+
54+
```
55+
$ python3 -m venv .env
56+
```
57+
58+
After the init process completes and the virtualenv is created, you can use the following
59+
step to activate your virtualenv.
60+
61+
```
62+
$ source .env/bin/activate
63+
```
64+
65+
If you are a Windows platform, you would activate the virtualenv like this:
66+
67+
```
68+
% .env\Scripts\activate.bat
69+
```
70+
71+
Once the virtualenv is activated, you can install the required dependencies.
72+
73+
```
74+
$ pip install -r requirements.txt
75+
```
76+
77+
At this point you can now synthesize the CloudFormation template for this code.
78+
79+
```
80+
$ cdk synth
81+
```
82+
83+
To add additional dependencies, for example other CDK libraries, just add
84+
them to your `setup.py` file and rerun the `pip install -r requirements.txt`
85+
command.
86+
87+
## Useful commands
88+
89+
* `cdk ls` list all stacks in the app
90+
* `cdk synth` emits the synthesized CloudFormation template
91+
* `cdk deploy` deploy this stack to your default AWS account/region
92+
* `cdk diff` compare deployed stack with current state
93+
* `cdk docs` open CDK documentation
94+
95+
Enjoy!
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env python3
2+
3+
from aws_cdk import core
4+
5+
from serverless_widget_service.serverless_widget_service_stack import ServerlessWidgetServiceStack
6+
7+
8+
app = core.App()
9+
ServerlessWidgetServiceStack(app, "serverless-widget-service")
10+
11+
app.synth()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"app": "python3 app.py"
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-e .
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
const AWS = require('aws-sdk');
2+
const S3 = new AWS.S3();
3+
4+
const bucketName = process.env.BUCKET;
5+
6+
exports.main = async function(event, context) {
7+
try {
8+
var method = event.httpMethod;
9+
// Get name, if present
10+
var widgetName = event.path.startsWith('/') ? event.path.substring(1) : event.path;
11+
12+
if (method === "GET") {
13+
// GET / to get the names of all widgets
14+
if (event.path === "/") {
15+
const data = await S3.listObjectsV2({ Bucket: bucketName }).promise();
16+
var body = {
17+
widgets: data.Contents.map(function(e) { return e.Key })
18+
};
19+
return {
20+
statusCode: 200,
21+
headers: {},
22+
body: JSON.stringify(body)
23+
};
24+
}
25+
26+
if (widgetName) {
27+
// GET /name to get info on widget name
28+
const data = await S3.getObject({ Bucket: bucketName, Key: widgetName}).promise();
29+
var body = data.Body.toString('utf-8');
30+
31+
return {
32+
statusCode: 200,
33+
headers: {},
34+
body: JSON.stringify(body)
35+
};
36+
}
37+
}
38+
39+
if (method === "POST") {
40+
// POST /name
41+
// Return error if we do not have a name
42+
if (!widgetName) {
43+
return {
44+
statusCode: 400,
45+
headers: {},
46+
body: "Widget name missing"
47+
};
48+
}
49+
50+
// Create some dummy data to populate object
51+
const now = new Date();
52+
var data = widgetName + " created: " + now;
53+
54+
var base64data = new Buffer(data, 'binary');
55+
56+
await S3.putObject({
57+
Bucket: bucketName,
58+
Key: widgetName,
59+
Body: base64data,
60+
ContentType: 'application/json'
61+
}).promise();
62+
63+
return {
64+
statusCode: 200,
65+
headers: {},
66+
body: JSON.stringify(event.widgets)
67+
};
68+
}
69+
70+
if (method === "DELETE") {
71+
// DELETE /name
72+
// Return an error if we do not have a name
73+
if (!widgetName) {
74+
return {
75+
statusCode: 400,
76+
headers: {},
77+
body: "Widget name missing"
78+
};
79+
}
80+
81+
await S3.deleteObject({
82+
Bucket: bucketName, Key: widgetName
83+
}).promise();
84+
85+
return {
86+
statusCode: 200,
87+
headers: {},
88+
body: "Successfully deleted widget " + widgetName
89+
};
90+
}
91+
92+
// We got something besides a GET, POST, or DELETE
93+
return {
94+
statusCode: 400,
95+
headers: {},
96+
body: "We only accept GET, POST, and DELETE, not " + method
97+
};
98+
} catch(error) {
99+
var body = error.stack || JSON.stringify(error, null, 2);
100+
return {
101+
statusCode: 400,
102+
headers: {},
103+
body: body
104+
}
105+
}
106+
}
107+
108+
109+
// exports.main = async function(event, context) {
110+
// try {
111+
// var method = event.httpMethod;
112+
113+
// if (method === "GET") {
114+
// if (event.path === "/") {
115+
// const data = await S3.listObjectsV2({ Bucket: bucketName }).promise();
116+
// var body = {
117+
// widgets: data.Contents.map(function(e) { return e.Key })
118+
// };
119+
// return {
120+
// statusCode: 200,
121+
// headers: {},
122+
// body: JSON.stringify(body)
123+
// };
124+
// }
125+
// }
126+
127+
// // We only accept GET for now
128+
// return {
129+
// statusCode: 400,
130+
// headers: {},
131+
// body: "We only accept GET /"
132+
// };
133+
// } catch(error) {
134+
// var body = error.stack || JSON.stringify(error, null, 2);
135+
// return {
136+
// statusCode: 400,
137+
// headers: {},
138+
// body: JSON.stringify(body)
139+
// }
140+
// }
141+
// }

cdk-python/ServerlessWidgetService/serverless_widget_service/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from aws_cdk import core
2+
from . import widget_service
3+
4+
5+
class ServerlessWidgetServiceStack(core.Stack):
6+
7+
def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
8+
super().__init__(scope, id, **kwargs)
9+
10+
# The code that defines your stack goes here
11+
widget_service.WidgetService(self, "Widgets")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from aws_cdk import (core,
2+
aws_apigateway as apigateway,
3+
aws_s3 as s3,
4+
aws_lambda as lambda_)
5+
6+
class WidgetService(core.Construct):
7+
def __init__(self, scope: core.Construct, id: str):
8+
super().__init__(scope, id)
9+
10+
bucket = s3.Bucket(self, "WidgetStore")
11+
12+
handler = lambda_.Function(self, "WidgetHandler",
13+
runtime=lambda_.Runtime.PYTHON_3_6,
14+
code=lambda_.Code.asset("resources"),
15+
handler="widgets.main",
16+
environment=dict(
17+
BUCKET=bucket.bucket_name)
18+
)
19+
20+
bucket.grant_read_write(handler)
21+
22+
api = apigateway.RestApi(self, "widgets-api",
23+
rest_api_name="Widget Service",
24+
description="This service serves widgets.")
25+
26+
get_widgets_integration = apigateway.LambdaIntegration(handler,
27+
request_templates={"application/json": '{ "statusCode": "200" }'})
28+
29+
api.root.add_method("GET", get_widgets_integration) # GET /
30+
31+
32+
widget = api.root.add_resource("{id}")
33+
34+
# Add new widget to bucket with: POST /{id}
35+
post_widget_integration = apigateway.LambdaIntegration(handler)
36+
37+
# Get a specific widget from bucket with: GET /{id}
38+
get_widget_integration = apigateway.LambdaIntegration(handler)
39+
40+
# Remove a specific widget from the bucket with: DELETE /{id}
41+
delete_widget_integration = apigateway.LambdaIntegration(handler)
42+
43+
widget.add_method("POST", post_widget_integration); # POST /{id}
44+
widget.add_method("GET", get_widget_integration); # GET /{id}
45+
widget.add_method("DELETE", delete_widget_integration); # DELETE /{id}

0 commit comments

Comments
 (0)