CloudFormation Macro - Example Random MD5 Macro#
lambda_function.py
1# -*- coding: utf-8 -*-
2
3"""
4这个 Lambda Function 实现了一个 AWS CloudFormation Macro, 用来生成一个随机的 MD5 字符串.
5"""
6
7import typing as T
8import uuid
9import traceback
10import dataclasses
11
12T_DATA = T.Dict[str, T.Any]
13
14
15@dataclasses.dataclass
16class Base:
17 @classmethod
18 def from_dict(cls, dct: T_DATA):
19 kwargs = {}
20 for field in dataclasses.fields(cls):
21 if field.name in dct:
22 kwargs[field.name] = dct[field.name]
23 return cls(**kwargs)
24
25 def to_dict(self) -> T_DATA:
26 return dataclasses.asdict(self)
27
28
29@dataclasses.dataclass
30class Request(Base):
31 region: str = dataclasses.field(default=None)
32 accountId: str = dataclasses.field(default=None)
33 fragment: dict = dataclasses.field(default=None)
34 transformId: str = dataclasses.field(default=None)
35 params: str = dataclasses.field(default=None)
36 requestId: str = dataclasses.field(default=None)
37 templateParameterValues: dict = dataclasses.field(default=None)
38
39
40SUCCESS = "success"
41
42
43@dataclasses.dataclass
44class Response(Base):
45 requestId: str = dataclasses.field(default=None)
46 status: str = dataclasses.field(default=None)
47 fragment: dict = dataclasses.field(default=None)
48 errorMessage: str = dataclasses.field(default=None)
49
50
51def lambda_handler(event, context):
52 print("========== Start CloudFormation Macro Lambda Function ==========")
53 print("event: " + str(event))
54
55 request = Request.from_dict(event)
56 try:
57 response = process(request)
58 except Exception as e:
59 traceback.print_exc()
60 response = Response(
61 requestId=request.requestId,
62 status="failure",
63 errorMessage=str(e),
64 )
65
66 return response.to_dict()
67
68
69def process(request: Request) -> Response:
70 return Response(
71 requestId=request.requestId,
72 status=SUCCESS,
73 # --- 方法 1. 返回一个简单的字符串本身
74 # fragment=uuid.uuid4().hex,
75 # --- 方法 2. 返回一个 CloudFormation JSON 中的一个 object 对象, 这个例子中是一个简单的
76 # intrinsic function Fn::Sub, 只是用来演示. 实际上你可以返回任何复杂的 JSON 对象.
77 # 甚至定义一个 Resource, 或是 nested stack 都可以
78 fragment={
79 "Fn::Sub": [
80 "${value}",
81 {"value": uuid.uuid4().hex},
82 ],
83 },
84 )
s1_deploy_macro.py
1# -*- coding: utf-8 -*-
2
3"""
4在这个 CloudFormation Template 中, 我们定义了一个 CloudFormation Macro 和其所需的
5Lambda Function, 其功能是生成一个随机的 MD5 字符串.
6"""
7
8import json
9from shared import dir_here, bsm, s3dir
10import aws_cloudformation as aws_cf
11
12# ------------------------------------------------------------------------------
13# Put your settings here
14# ------------------------------------------------------------------------------
15iam_role_arn = "arn:aws:iam::{aws_account_id}:role/lambda-power-user-role"
16name_prefix = "cfn_macro_poc_01_deploy_macro"
17
18# ------------------------------------------------------------------------------
19# Don't change the code below
20# ------------------------------------------------------------------------------
21iam_role_arn = iam_role_arn.format(aws_account_id=bsm.aws_account_id)
22
23path_lbd_func = dir_here / "lambda_function.py"
24path_lbd_deploy_package = dir_here / "lambda.zip"
25path_lbd_func.make_zip_archive(path_lbd_deploy_package, overwrite=True)
26
27s3path_lbd_deploy_package = s3dir / f"{path_lbd_func.md5}.zip"
28s3path_lbd_deploy_package.upload_file(path_lbd_deploy_package, overwrite=True)
29
30
31template = {
32 "AWSTemplateFormatVersion": "2010-09-09",
33 "Resources": {
34 "CfnMacroMd5LambdaFunction": {
35 "Type": "AWS::Lambda::Function",
36 "Properties": {
37 "FunctionName": "cfn_macro-md5",
38 "Handler": "lambda_function.lambda_handler",
39 "Role": iam_role_arn,
40 "Code": {
41 "S3Bucket": s3path_lbd_deploy_package.bucket,
42 "S3Key": s3path_lbd_deploy_package.key,
43 },
44 "Runtime": "python3.11",
45 "MemorySize": 128,
46 "Timeout": 30,
47 "Environment": {
48 "Variables": {"CODE_MD5": str(hash(path_lbd_func.read_text()))}
49 },
50 },
51 },
52 "CfnMacroMd5": {
53 "Type": "AWS::CloudFormation::Macro",
54 "Properties": {
55 "Name": "Md5",
56 "Description": "Generate random md5",
57 "FunctionName": {"Fn::GetAtt": ["CfnMacroMd5LambdaFunction", "Arn"]},
58 },
59 },
60 },
61}
62stack_name = name_prefix.replace("_", "-")
63
64
65def deploy_stack():
66 aws_cf.deploy_stack(
67 bsm=bsm,
68 stack_name=stack_name,
69 template=json.dumps(template),
70 include_named_iam=True,
71 skip_prompt=True,
72 )
73
74
75def delete_stack():
76 aws_cf.remove_stack(
77 bsm=bsm,
78 stack_name=stack_name,
79 skip_prompt=True,
80 )
81
82
83if __name__ == "__main__":
84 # deploy_stack()
85 delete_stack()
86 # pass
s2_test_macro.py
1# -*- coding: utf-8 -*-
2
3"""
4测试 s1_deploy_macro.py 中部署的 Macro.
5"""
6
7import json
8from shared import bsm
9import aws_cloudformation as aws_cf
10
11# ------------------------------------------------------------------------------
12# Put your settings here
13# ------------------------------------------------------------------------------
14name_prefix = "cfn_macro_poc_01_test_macro"
15
16# ------------------------------------------------------------------------------
17# Don't change the code below
18# ------------------------------------------------------------------------------
19template = {
20 "AWSTemplateFormatVersion": "2010-09-09",
21 "Resources": {
22 "IamGroup": {
23 "Type": "AWS::IAM::Group",
24 "Properties": {
25 "GroupName": {
26 "Fn::Transform": {
27 # this value has to match the value at
28 # s1_deploy_macro.py -> CfnMacroMd5 -> Properties -> Name
29 "Name": "Md5",
30 "Parameters": {},
31 }
32 },
33 },
34 },
35 },
36}
37stack_name = name_prefix.replace("_", "-")
38
39
40def deploy_stack():
41 aws_cf.deploy_stack(
42 bsm=bsm,
43 stack_name=stack_name,
44 template=json.dumps(template),
45 include_named_iam=True,
46 skip_prompt=True,
47 )
48
49
50def delete_stack():
51 aws_cf.remove_stack(
52 bsm=bsm,
53 stack_name=stack_name,
54 skip_prompt=True,
55 )
56
57
58if __name__ == "__main__":
59 # deploy_stack()
60 delete_stack()
61 # pass
s3_try_def_and_use_in_one_stack.py
1# -*- coding: utf-8 -*-
2
3"""
4有了 deploy_macro.py 和 test_macro.py 两个成功例子, 我们来尝试将 Macro 的定义和 Macro
5的使用放在一个 CloudFormation 中可不可以 (**结论, 不可以直接放**). 在 AWS 官方文档中我们已经
6知道可以将 Macro 的定义和 Macro 的使用分别放在两个不同的 CloudFormation Stack 中,
7然后使用 Nested Stack 的方式将两个 Stack 放在一个 Stack 中, 并且定义依赖关系, 这个是确定
8可行的. 但我们还是想要动手尝试一下, 看看可不可以直接放在一个 CloudFormation 中.
9"""
10
11import json
12from shared import dir_here, bsm, s3dir
13import aws_cloudformation as aws_cf
14
15# ------------------------------------------------------------------------------
16# Put your settings here
17# ------------------------------------------------------------------------------
18iam_role_arn = "arn:aws:iam::{aws_account_id}:role/lambda-power-user-role"
19name_prefix = "cfn_macro_poc_01_try_def_and_use_in_one_stack"
20
21# ------------------------------------------------------------------------------
22# Don't change the code below
23# ------------------------------------------------------------------------------
24iam_role_arn = iam_role_arn.format(aws_account_id=bsm.aws_account_id)
25
26path_lbd_func = dir_here / "lambda_function.py"
27path_lbd_deploy_package = dir_here / "lambda.zip"
28path_lbd_func.make_zip_archive(path_lbd_deploy_package, overwrite=True)
29
30s3path_lbd_deploy_package = s3dir / f"{path_lbd_func.md5}.zip"
31s3path_lbd_deploy_package.upload_file(path_lbd_deploy_package, overwrite=True)
32
33
34template = {
35 "AWSTemplateFormatVersion": "2010-09-09",
36 "Resources": {
37 "CfnMacroMd5LambdaFunction": {
38 "Type": "AWS::Lambda::Function",
39 "Properties": {
40 "FunctionName": "cfn_macro-md5",
41 "Handler": "lambda_function.lambda_handler",
42 "Role": iam_role_arn,
43 "Code": {
44 "S3Bucket": s3path_lbd_deploy_package.bucket,
45 "S3Key": s3path_lbd_deploy_package.key,
46 },
47 "Runtime": "python3.11",
48 "MemorySize": 128,
49 "Timeout": 30,
50 "Environment": {
51 "Variables": {"CODE_MD5": str(hash(path_lbd_func.read_text()))}
52 },
53 },
54 },
55 "CfnMacroMd5": {
56 "Type": "AWS::CloudFormation::Macro",
57 "Properties": {
58 "Name": "Md5",
59 "Description": "Generate random md5",
60 "FunctionName": {
61 "Fn::GetAtt": ["CfnMacroMd5LambdaFunction", "Arn"],
62 },
63 },
64 },
65 "IamGroup": {
66 "Type": "AWS::IAM::Group",
67 "Properties": {
68 "GroupName": {
69 "Fn::Transform": {
70 "Name": "Md5",
71 "Parameters": {},
72 }
73 },
74 },
75 "DependsOn": "CfnMacroMd5",
76 },
77 },
78}
79stack_name = name_prefix.replace("_", "-")
80
81
82def deploy_stack():
83 aws_cf.deploy_stack(
84 bsm=bsm,
85 stack_name=stack_name,
86 template=json.dumps(template),
87 include_named_iam=True,
88 skip_prompt=True,
89 )
90
91
92def delete_stack():
93 aws_cf.remove_stack(
94 bsm=bsm,
95 stack_name=stack_name,
96 skip_prompt=True,
97 )
98
99
100if __name__ == "__main__":
101 deploy_stack()
102 # delete_stack()
103 # pass
s4_try_def_and_use_in_one_stack_the_right_way.py
1# -*- coding: utf-8 -*-
2
3"""
4有了 deploy_macro.py 和 test_macro.py 两个成功例子, 我们来尝试将 Macro 的定义和 Macro
5的使用放在一个 CloudFormation 中可不可以 (**结论, 不可以直接放**). 在 AWS 官方文档中我们已经
6知道可以将 Macro 的定义和 Macro 的使用分别放在两个不同的 CloudFormation Stack 中,
7然后使用 Nested Stack 的方式将两个 Stack 放在一个 Stack 中, 并且定义依赖关系, 这个是确定
8可行的. 但我们还是想要动手尝试一下, 看看可不可以直接放在一个 CloudFormation 中.
9"""
10
11import json
12from shared import dir_here, bsm, s3dir
13import aws_cloudformation as aws_cf
14
15# ------------------------------------------------------------------------------
16# Put your settings here
17# ------------------------------------------------------------------------------
18iam_role_arn = "arn:aws:iam::{aws_account_id}:role/lambda-power-user-role"
19name_prefix = "cfn_macro_poc_01_one_stack_two_nested_stack"
20
21# ------------------------------------------------------------------------------
22# Don't change the code below
23# ------------------------------------------------------------------------------
24iam_role_arn = iam_role_arn.format(aws_account_id=bsm.aws_account_id)
25
26path_lbd_func = dir_here / "lambda_function.py"
27path_lbd_deploy_package = dir_here / "lambda.zip"
28path_lbd_func.make_zip_archive(path_lbd_deploy_package, overwrite=True)
29
30s3path_lbd_deploy_package = s3dir / f"{path_lbd_func.md5}.zip"
31s3path_lbd_deploy_package.upload_file(path_lbd_deploy_package, overwrite=True)
32
33macro_template = {
34 "AWSTemplateFormatVersion": "2010-09-09",
35 "Resources": {
36 "CfnMacroMd5LambdaFunction": {
37 "Type": "AWS::Lambda::Function",
38 "Metadata": {
39 "Description": f"This Lambda Function allow CloudFormation to generate random md5 string, You can exam the source code at {s3path_lbd_deploy_package.get_regional_console_url(bsm.aws_region)}",
40 "PreviewLambdaFunctionSourceCode": s3path_lbd_deploy_package.get_regional_console_url(bsm.aws_region),
41 },
42 "Properties": {
43 "FunctionName": "cfn_macro-md5",
44 "Handler": "lambda_function.lambda_handler",
45 "Role": iam_role_arn,
46 "Code": {
47 "S3Bucket": s3path_lbd_deploy_package.bucket,
48 "S3Key": s3path_lbd_deploy_package.key,
49 },
50 "Runtime": "python3.11",
51 "MemorySize": 128,
52 "Timeout": 30,
53 "Environment": {
54 "Variables": {"CODE_MD5": str(hash(path_lbd_func.read_text()))}
55 },
56 },
57 },
58 "CfnMacroMd5": {
59 "Type": "AWS::CloudFormation::Macro",
60 "Properties": {
61 "Name": "Md5",
62 "Description": "Generate random md5",
63 "FunctionName": {
64 "Fn::GetAtt": ["CfnMacroMd5LambdaFunction", "Arn"],
65 },
66 },
67 },
68 },
69}
70test_template = {
71 "AWSTemplateFormatVersion": "2010-09-09",
72 "Resources": {
73 "IamGroup": {
74 "Type": "AWS::IAM::Group",
75 "Properties": {
76 "GroupName": {
77 "Fn::Transform": {
78 "Name": "Md5",
79 "Parameters": {},
80 }
81 },
82 },
83 },
84 },
85}
86
87macro_template_json = json.dumps(macro_template)
88s3path_macro_template = s3dir / f"{abs(hash(macro_template_json))}_macro.json"
89s3path_macro_template.write_text(macro_template_json, content_type="application/json")
90
91test_template_json = json.dumps(test_template)
92s3path_test_template = s3dir / f"{abs(hash(test_template_json))}_macro.json"
93s3path_test_template.write_text(test_template_json, content_type="application/json")
94
95
96def get_object_url(s3path) -> str:
97 return f"https://{s3path.bucket}.s3.amazonaws.com/{s3path.key}"
98
99
100master_template = {
101 "AWSTemplateFormatVersion": "2010-09-09",
102 "Resources": {
103 # NOTE, you have to deploy MacroTemplate first in one API run
104 # then you can deploy TestTemplate in another API run
105 # You CANNOT deploy both in one API run, even with "DependsOn".
106 "MacroTemplate": {
107 "Type": "AWS::CloudFormation::Stack",
108 "Properties": {
109 "TemplateURL": get_object_url(s3path_macro_template),
110 },
111 },
112 "TestTemplate": {
113 "Type": "AWS::CloudFormation::Stack",
114 "Properties": {
115 "TemplateURL": get_object_url(s3path_test_template),
116 },
117 "DependsOn": "MacroTemplate",
118 },
119 },
120}
121stack_name = name_prefix.replace("_", "-")
122
123
124def deploy_stack():
125 aws_cf.deploy_stack(
126 bsm=bsm,
127 stack_name=stack_name,
128 template=json.dumps(master_template),
129 include_named_iam=True,
130 include_macro=True,
131 skip_prompt=True,
132 )
133
134
135def delete_stack():
136 aws_cf.remove_stack(
137 bsm=bsm,
138 stack_name=stack_name,
139 skip_prompt=True,
140 )
141
142
143if __name__ == "__main__":
144 deploy_stack()
145 # delete_stack()
146 # pass