我一直在寻找相同的功能。我想到了使用SpoonMeiser建议的嵌套堆栈,但是后来我意识到我真正需要的是自定义函数。幸运的是,CloudFormation允许使用AWS :: CloudFormation :: CustomResource,并且只需做一些工作,就可以做到这一点。这似乎只是变量的过大杀伤力(我认为这本来应该放在CloudFormation中),但是它可以完成工作,此外,还提供了所有的灵活性(请选择python / node) / java)。应该注意的是,lambda函数要花钱,但除非您每小时创建/删除堆栈数多次,否则我们在这里只说几分钱。
第一步是在此页面上创建一个lambda函数,该函数除了获取输入值并将其复制到输出外什么也不做。我们可以让lambda函数做各种疯狂的事情,但是一旦有了identity函数,其他任何事情都很容易。或者,我们可以在堆栈本身中创建lambda函数。由于我在1个帐户中使用了许多堆栈,因此我将有一堆剩余的lambda函数和角色(并且所有堆栈都需要使用创建--capabilities=CAPABILITY_IAM
,因为它也需要一个角色。
创建lambda函数
- 转到lambda主页,然后选择您喜欢的区域
- 选择“空白功能”作为模板
- 单击“下一步”(不配置任何触发器)
- 填写:
- 名称:CloudFormationIdentity
- 描述:返回得到的信息,Cloud Form中的可变支持
- 运行时:python2.7
- 代码输入类型:内联编辑代码
- 代码:见下文
- 处理程序:
index.handler
- 角色:创建自定义角色。此时,将打开一个弹出窗口,允许您创建一个新角色。接受此页面上的所有内容,然后单击“允许”。它将创建一个具有发布到Cloudwatch日志的权限的角色。
- 内存:128(最小值)
- 超时:3秒(应该足够)
- VPC:无VPC
然后在代码字段中复制粘贴下面的代码。该函数的顶部是cfn-response python模块中的代码,仅出于某些奇怪的原因,如果通过CloudFormation创建了lambda函数,该模块才自动安装。该handler
功能非常不言自明。
from __future__ import print_function
import json
try:
from urllib2 import HTTPError, build_opener, HTTPHandler, Request
except ImportError:
from urllib.error import HTTPError
from urllib.request import build_opener, HTTPHandler, Request
SUCCESS = "SUCCESS"
FAILED = "FAILED"
def send(event, context, response_status, reason=None, response_data=None, physical_resource_id=None):
response_data = response_data or {}
response_body = json.dumps(
{
'Status': response_status,
'Reason': reason or "See the details in CloudWatch Log Stream: " + context.log_stream_name,
'PhysicalResourceId': physical_resource_id or context.log_stream_name,
'StackId': event['StackId'],
'RequestId': event['RequestId'],
'LogicalResourceId': event['LogicalResourceId'],
'Data': response_data
}
)
if event["ResponseURL"] == "http://pre-signed-S3-url-for-response":
print("Would send back the following values to Cloud Formation:")
print(response_data)
return
opener = build_opener(HTTPHandler)
request = Request(event['ResponseURL'], data=response_body)
request.add_header('Content-Type', '')
request.add_header('Content-Length', len(response_body))
request.get_method = lambda: 'PUT'
try:
response = opener.open(request)
print("Status code: {}".format(response.getcode()))
print("Status message: {}".format(response.msg))
return True
except HTTPError as exc:
print("Failed executing HTTP request: {}".format(exc.code))
return False
def handler(event, context):
responseData = event['ResourceProperties']
send(event, context, SUCCESS, None, responseData, "CustomResourcePhysicalID")
现在,您可以通过选择“测试”按钮来测试lambda函数,然后选择“ CloudFormation Create Request”作为示例模板。您应该在日志中看到返回给它的变量。
在CloudFormation模板中使用变量
现在我们有了此lambda函数,我们可以在CloudFormation模板中使用它了。首先记下lambda函数Arn(转到lambda主页,单击刚刚创建的函数,Arn应该在右上角,类似arn:aws:lambda:region:12345:function:CloudFormationIdentity
)。
现在,在模板的“资源”部分中,指定变量,例如:
Identity:
Type: "Custom::Variable"
Properties:
ServiceToken: "arn:aws:lambda:region:12345:function:CloudFormationIdentity"
Arn: "arn:aws:lambda:region:12345:function:CloudFormationIdentity"
ClientBucketVar:
Type: "Custom::Variable"
Properties:
ServiceToken: !GetAtt [Identity, Arn]
Name: !Join ["-", [my-client-bucket, !Ref ClientName]]
Arn: !Join [":", [arn, aws, s3, "", "", !Join ["-", [my-client-bucket, !Ref ClientName]]]]
ClientBackupBucketVar:
Type: "Custom::Variable"
Properties:
ServiceToken: !GetAtt [Identity, Arn]
Name: !Join ["-", [my-client-bucket, !Ref ClientName, backup]]
Arn: !Join [":", [arn, aws, s3, "", "", !Join ["-", [my-client-bucket, !Ref ClientName, backup]]]]
首先,我指定一个Identity
包含lambda函数的Arn 的变量。将其放在此处的变量中,意味着我只需指定一次即可。我将所有变量设置为type Custom::Variable
。CloudFormation允许您将任何类型名称以开头Custom::
用于自定义资源。
请注意,该Identity
变量两次包含了lambda函数的Arn。一次指定要使用的lambda函数。第二次作为变量的值。
现在有了Identity
变量,我可以使用定义新变量了ServiceToken: !GetAtt [Identity, Arn]
(我认为JSON代码应该类似于"ServiceToken": {"Fn::GetAtt": ["Identity", "Arn"]}
)。我创建了2个新变量,每个变量都有2个字段:Name和Arn。在我的模板的其余部分,我可以使用!GetAtt [ClientBucketVar, Name]
或!GetAtt [ClientBucketVar, Arn]
每当我需要它。
注意事项
使用自定义资源时,如果lambda函数崩溃,则您将停留1到2个小时,因为CloudFormation在放弃之前会等待(崩溃)函数的答复一个小时。因此,在开发lambda函数时,为堆栈指定较短的超时时间可能会很好。