我可以强制 CloudFormation 删除非空 S3 存储桶吗?
Can I force CloudFormation to delete non-empty S3 Bucket?
有没有办法强制 CloudFormation 删除非空的 S3 存储桶?
您可以创建一个 lambda 函数 来清理您的存储桶并使用 CustomResource 从您的 CloudFormation 堆栈调用您的 lambda。
下面是清理存储桶的 lambda 示例:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import json
import boto3
from botocore.vendored import requests
def lambda_handler(event, context):
try:
bucket = event['ResourceProperties']['BucketName']
if event['RequestType'] == 'Delete':
s3 = boto3.resource('s3')
bucket = s3.Bucket(bucket)
for obj in bucket.objects.filter():
s3.Object(bucket.name, obj.key).delete()
sendResponseCfn(event, context, "SUCCESS")
except Exception as e:
print(e)
sendResponseCfn(event, context, "FAILED")
def sendResponseCfn(event, context, responseStatus):
response_body = {'Status': responseStatus,
'Reason': 'Log stream name: ' + context.log_stream_name,
'PhysicalResourceId': context.log_stream_name,
'StackId': event['StackId'],
'RequestId': event['RequestId'],
'LogicalResourceId': event['LogicalResourceId'],
'Data': json.loads("{}")}
requests.put(event['ResponseURL'], data=json.dumps(response_body))
在您创建上面的 lambda 之后,只需将 CustomResource 放入您的 CloudFormation 堆栈中:
---
AWSTemplateFormatVersion: '2010-09-09'
Resources:
myBucketResource:
Type: AWS::S3::Bucket
Properties:
BucketName: my-test-bucket-cleaning-on-delete
cleanupBucketOnDelete:
Type: Custom::cleanupbucket
Properties:
ServiceToken: arn:aws:lambda:eu-west-1:123456789012:function:clean-bucket-lambda
BucketName: !Ref myBucketResource
Remember to attach a role to your lambda that has permission to remove objects from your bucket.
此外请记住,您可以使用 lambda 函数 cli2cloudformation 创建一个接受 CLI 命令行的 lambda 函数。您可以从 here 下载并安装。使用它,您只需要像下面这样创建一个 CustomResource:
"removeBucket": {
"Type": "Custom::cli2cloudformation",
"Properties": {
"ServiceToken": "arn:aws:lambda:eu-west-1:123456789000:function:custom-lambda-name",
"CliCommandDelete": "aws s3 rb s3://bucket-name --force",
}
}
我认为您的 DependsOn 在错误的资源中,至少它对我来说不能正常工作,因为在堆栈 deletion 上(通过控制台),它会首先尝试强制删除存储桶这将失败,然后将尝试删除自定义资源,这会触发 lambda 清空存储桶。这将清空桶,但堆栈删除将失败,因为它试图在桶为空之前删除桶。我们想先启动自定义资源删除,然后在删除自定义资源后尝试删除存储桶,所以我这样做了并且对我有用:
myBucketResource:
Type: AWS::S3::Bucket
Properties:
BucketName: my-test-bucket-cleaning-on-delete
cleanupBucketOnDelete:
Type: Custom::cleanupbucket
Properties:
ServiceToken: arn:aws:lambda:eu-west-1:123456789012:function:clean-bucket-lambda
BucketName: my-test-bucket-cleaning-on-delete
DependsOn: myBucketResource
通过这种方式,您可以确保不会先删除存储桶,因为还有另一个资源依赖于它,因此首先删除依赖资源(这会触发 lambda 清空存储桶),然后再删除存储桶。
希望有人觉得它有用。
你应该清空桶:
$ aws s3 rm s3://bucket-name --recursive
然后删除Bucket
$ aws cloudformation delete-stack --stack-name mys3stack
botocore.vendored 已弃用,将于 2021/01/30 后从 Lambda 中删除。
这是一个更新版本
Type: AWS::Lambda::Function
Properties:
Code:
ZipFile:
!Sub |
import json, boto3, logging
import cfnresponse
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
logger.info("event: {}".format(event))
try:
bucket = event['ResourceProperties']['BucketName']
logger.info("bucket: {}, event['RequestType']: {}".format(bucket,event['RequestType']))
if event['RequestType'] == 'Delete':
s3 = boto3.resource('s3')
bucket = s3.Bucket(bucket)
for obj in bucket.objects.filter():
logger.info("delete obj: {}".format(obj))
s3.Object(bucket.name, obj.key).delete()
sendResponseCfn(event, context, cfnresponse.SUCCESS)
except Exception as e:
logger.info("Exception: {}".format(e))
sendResponseCfn(event, context, cfnresponse.FAILED)
def sendResponseCfn(event, context, responseStatus):
responseData = {}
responseData['Data'] = {}
cfnresponse.send(event, context, responseStatus, responseData, "CustomResourcePhysicalID")
Handler: "index.lambda_handler"
Runtime: python3.7
MemorySize: 128
Timeout: 60
Role: !GetAtt TSIemptyBucketOnDeleteFunctionRole.Arn
它在 python 3.8 上运行有点费时,所以我与社区分享它。
Python 3.8 lambda 不再支持 from botocore.vendored import requests
.
您可以使用以下代码通知 Cloudformation。
import urllib
request = urllib.request.Request(event['ResponseURL'],
method="PUT",
data=json.dumps(response_body).encode('utf-8'),
headers={'Content-Type': "''"})
with urllib.request.urlopen(request) as response:
print("Status code: " + response.reason)
另一个小提示:当 lambda 收到来自 cloudformation 的 create
请求时,您可以将任何唯一值放入 PhysicalResourceId 响应中。当它是 UPDATE/DELETE 时,您还会从 CloudFormation 收到该参数,并且您必须在响应中重用输入中的值。
有没有办法强制 CloudFormation 删除非空的 S3 存储桶?
您可以创建一个 lambda 函数 来清理您的存储桶并使用 CustomResource 从您的 CloudFormation 堆栈调用您的 lambda。
下面是清理存储桶的 lambda 示例:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import json
import boto3
from botocore.vendored import requests
def lambda_handler(event, context):
try:
bucket = event['ResourceProperties']['BucketName']
if event['RequestType'] == 'Delete':
s3 = boto3.resource('s3')
bucket = s3.Bucket(bucket)
for obj in bucket.objects.filter():
s3.Object(bucket.name, obj.key).delete()
sendResponseCfn(event, context, "SUCCESS")
except Exception as e:
print(e)
sendResponseCfn(event, context, "FAILED")
def sendResponseCfn(event, context, responseStatus):
response_body = {'Status': responseStatus,
'Reason': 'Log stream name: ' + context.log_stream_name,
'PhysicalResourceId': context.log_stream_name,
'StackId': event['StackId'],
'RequestId': event['RequestId'],
'LogicalResourceId': event['LogicalResourceId'],
'Data': json.loads("{}")}
requests.put(event['ResponseURL'], data=json.dumps(response_body))
在您创建上面的 lambda 之后,只需将 CustomResource 放入您的 CloudFormation 堆栈中:
---
AWSTemplateFormatVersion: '2010-09-09'
Resources:
myBucketResource:
Type: AWS::S3::Bucket
Properties:
BucketName: my-test-bucket-cleaning-on-delete
cleanupBucketOnDelete:
Type: Custom::cleanupbucket
Properties:
ServiceToken: arn:aws:lambda:eu-west-1:123456789012:function:clean-bucket-lambda
BucketName: !Ref myBucketResource
Remember to attach a role to your lambda that has permission to remove objects from your bucket.
此外请记住,您可以使用 lambda 函数 cli2cloudformation 创建一个接受 CLI 命令行的 lambda 函数。您可以从 here 下载并安装。使用它,您只需要像下面这样创建一个 CustomResource:
"removeBucket": {
"Type": "Custom::cli2cloudformation",
"Properties": {
"ServiceToken": "arn:aws:lambda:eu-west-1:123456789000:function:custom-lambda-name",
"CliCommandDelete": "aws s3 rb s3://bucket-name --force",
}
}
我认为您的 DependsOn 在错误的资源中,至少它对我来说不能正常工作,因为在堆栈 deletion 上(通过控制台),它会首先尝试强制删除存储桶这将失败,然后将尝试删除自定义资源,这会触发 lambda 清空存储桶。这将清空桶,但堆栈删除将失败,因为它试图在桶为空之前删除桶。我们想先启动自定义资源删除,然后在删除自定义资源后尝试删除存储桶,所以我这样做了并且对我有用:
myBucketResource:
Type: AWS::S3::Bucket
Properties:
BucketName: my-test-bucket-cleaning-on-delete
cleanupBucketOnDelete:
Type: Custom::cleanupbucket
Properties:
ServiceToken: arn:aws:lambda:eu-west-1:123456789012:function:clean-bucket-lambda
BucketName: my-test-bucket-cleaning-on-delete
DependsOn: myBucketResource
通过这种方式,您可以确保不会先删除存储桶,因为还有另一个资源依赖于它,因此首先删除依赖资源(这会触发 lambda 清空存储桶),然后再删除存储桶。 希望有人觉得它有用。
你应该清空桶:
$ aws s3 rm s3://bucket-name --recursive
然后删除Bucket
$ aws cloudformation delete-stack --stack-name mys3stack
botocore.vendored 已弃用,将于 2021/01/30 后从 Lambda 中删除。
这是一个更新版本
Type: AWS::Lambda::Function
Properties:
Code:
ZipFile:
!Sub |
import json, boto3, logging
import cfnresponse
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
logger.info("event: {}".format(event))
try:
bucket = event['ResourceProperties']['BucketName']
logger.info("bucket: {}, event['RequestType']: {}".format(bucket,event['RequestType']))
if event['RequestType'] == 'Delete':
s3 = boto3.resource('s3')
bucket = s3.Bucket(bucket)
for obj in bucket.objects.filter():
logger.info("delete obj: {}".format(obj))
s3.Object(bucket.name, obj.key).delete()
sendResponseCfn(event, context, cfnresponse.SUCCESS)
except Exception as e:
logger.info("Exception: {}".format(e))
sendResponseCfn(event, context, cfnresponse.FAILED)
def sendResponseCfn(event, context, responseStatus):
responseData = {}
responseData['Data'] = {}
cfnresponse.send(event, context, responseStatus, responseData, "CustomResourcePhysicalID")
Handler: "index.lambda_handler"
Runtime: python3.7
MemorySize: 128
Timeout: 60
Role: !GetAtt TSIemptyBucketOnDeleteFunctionRole.Arn
它在 python 3.8 上运行有点费时,所以我与社区分享它。
Python 3.8 lambda 不再支持 from botocore.vendored import requests
.
您可以使用以下代码通知 Cloudformation。
import urllib
request = urllib.request.Request(event['ResponseURL'],
method="PUT",
data=json.dumps(response_body).encode('utf-8'),
headers={'Content-Type': "''"})
with urllib.request.urlopen(request) as response:
print("Status code: " + response.reason)
另一个小提示:当 lambda 收到来自 cloudformation 的 create
请求时,您可以将任何唯一值放入 PhysicalResourceId 响应中。当它是 UPDATE/DELETE 时,您还会从 CloudFormation 收到该参数,并且您必须在响应中重用输入中的值。