Aws lambda CloudFormation自定义资源未完成删除

Aws lambda CloudFormation自定义资源未完成删除,aws-lambda,amazon-cloudformation,Aws Lambda,Amazon Cloudformation,我已将自定义资源设置为在删除时立即返回 const aws = require('aws-sdk') const util = require('util') exports.handler = (event, context) => { console.log('Event>>>') console.log(JSON.stringify(event)) aws.config.update({ region: event.ResourceProperties

我已将自定义资源设置为在删除时立即返回

const aws = require('aws-sdk')
const util = require('util')

exports.handler = (event, context) => {
  console.log('Event>>>')
  console.log(JSON.stringify(event))
  aws.config.update({ region: event.ResourceProperties.Region })

  if (event.RequestType === 'Delete') return ApiMethodCustom.sendResponse(event, context, 'SUCCESS') // HERE!

  ApiMethodCustom.setupIntegration(event, context)
}

  static async sendResponse(event, context, responseStatus, responseData = {}) {
    var responseBody = JSON.stringify({
      Status: responseStatus,
      Reason: "See the details in CloudWatch Log Stream: " + context.logStreamName,
      PhysicalResourceId: context.logStreamName,
      StackId: event.StackId,
      RequestId: event.RequestId,
      LogicalResourceId: event.LogicalResourceId,
      Data: responseData
    });

    console.log("RESPONSE BODY:\n", responseBody);

    var https = require("https");
    var url = require("url");

    var parsedUrl = url.parse(event.ResponseURL);
    var options = {
      hostname: parsedUrl.hostname,
      port: 443,
      path: parsedUrl.path,
      method: "PUT",
      headers: {
        "content-type": "",
        "content-length": responseBody.length
      }
    };

    console.log("SENDING RESPONSE...\n");

    var request = https.request(options, function (response) {
      console.log("STATUS: " + response.statusCode);
      console.log("HEADERS: " + JSON.stringify(response.headers));
      // Tell AWS Lambda that the function execution is done
      context.done();
    });

    request.on("error", function (error) {
      console.log("sendResponse Error:" + error);
      // Tell AWS Lambda that the function execution is done
      context.done();
    });

    // write data to request body
    request.write(responseBody);
    request.end();
  }
但CloudFormation似乎陷入了
DELETE\u-in\u-PROGRESS
。为什么呢

在我的日志中,Lambda似乎正确地完成了执行:

2018-09-09T01:52:06.913Z    f48808d0-b3d2-11e8-9e84-5b218cad3090
{
    "RequestType": "Delete",
    "ServiceToken": "arn:aws:lambda:ap-southeast-1:621567429603:function:income2-base-ApiVpcIntegration",
    "ResponseURL": "https://cloudformation-custom-resource-response-apsoutheast1.s3-ap-southeast-1.amazonaws.com/arn%3Aaws%3Acloudformation%3Aap-southeast-1%3A621567429603%3Astack/test/5a34d100-b370-11e8-b89d-503a138dba36%7CApiTestIntegration%7C979b1814-d94c-4a49-b9f7-2fa352ab88f5?AWSAccessKeyId=AKIAIKQZQ3QDXOJPHOPA&Expires=1536465125&Signature=O2O0entoTXHCYp5jbJehghtE9Ck%3D",
    "StackId": "arn:aws:cloudformation:ap-southeast-1:621567429603:stack/test/5a34d100-b370-11e8-b89d-503a138dba36",
    "RequestId": "979b1814-d94c-4a49-b9f7-2fa352ab88f5",
    "LogicalResourceId": "ApiTestIntegration",
    "PhysicalResourceId": "2018/09/08/[$LATEST]b8a3df0fca884fe3b8abdde3ab525ac0",
    "ResourceType": "Custom::ApiVpcIntegration",
    "ResourceProperties": {
        "ServiceToken": "arn:aws:lambda:ap-southeast-1:621567429603:function:income2-base-ApiVpcIntegration",
        "ConnectionId": "24lbti",
        "ResourceId": "x1gjyy",
        "RestApiId": "aaj0q4dbml",
        "Uri": "http://dropletapi-dev.2359media.net:3001/authentication",
        "HttpMethod": "GET"
    }
}

2018-09-09T01:52:06.914Z    f48808d0-b3d2-11e8-9e84-5b218cad3090    RESPONSE BODY:
{
    "Status": "SUCCESS",
    "Reason": "See the details in CloudWatch Log Stream: 2018/09/09/[$LATEST]29276598cb9c49c1b1da3672c8707c78",
    "PhysicalResourceId": "2018/09/09/[$LATEST]29276598cb9c49c1b1da3672c8707c78",
    "StackId": "arn:aws:cloudformation:ap-southeast-1:621567429603:stack/test/5a34d100-b370-11e8-b89d-503a138dba36",
    "RequestId": "979b1814-d94c-4a49-b9f7-2fa352ab88f5",
    "LogicalResourceId": "ApiTestIntegration",
    "Data": {}
}

我今天在使用cfn响应包时遇到了类似的问题,您的代码似乎基于此。cfn响应包基于回调,但您的代码似乎也部分使用了async/await(运行时选项:node.js8.10)

在您的例子中,我怀疑您从未看到“STATUS:”或“HEADERS:”消息,即使响应主体被转储到日志中(同步)。这反映了我在使用基于回调的cfn响应与异步/等待混合时的体验

换句话说,在所有情况下,您需要确保在Lambda终止之前向Cloudformation发送响应(放入事件S3 ResponseURL),或者在放弃和回滚之前模板可能会挂起长达一个小时(可能会出现如下行的Cloudformation错误)“无法稳定资源…”。依次回滚(删除)也可能需要一个小时,因为删除也没有正确响应。请参阅详细信息

我最终实现了与by(MIT许可证)非常相似的定制资源,但有两个不同之处;我没有设置单独的lambda来处理sendResponse功能,我减少了定制资源服务器(使用aws cloudformation包和aws cloudformation deploy命令)

您的ApiMethodCustom没有定义,因此我很难指导您实现,因此我将使用async/await包含node.js8.10代码以供参考

首先是Cloudformation模板中的自定义资源:

---
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: CustomResource Example Stack
Resources:
  CustomResource:
    Type: 'AWS::Serverless::Function'
    Properties:
      Runtime: nodejs8.10
      Handler: index.handler
      MemorySize: 128
      Timeout: 15
      Role: !GetAtt CustomResourceRole.Arn
      CodeUri: ./CustomResource/

  CustomResourceUser:
    Type: 'Custom::CustomResourceUser'
    Properties:
      ServiceToken: !GetAtt CustomResource.Arn
      ...
请注意,CodeUri是相对于模板路径的。您需要为CustomResourceRole定义IAM角色和策略

现在,对于CustomResource/index.js Lambda(您还需要在CustomResource目录中运行“npm安装--save axios”):

有关在AWS Lambda中使用回调与异步的更多信息,请查看

最后,注意.It的使用是基于承诺的,因此支持wait而不是回调

'use strict';
const AWS = require('aws-sdk');
const axios = require('axios');

exports.handler = async (event, context) => {
  try {

    switch (event.RequestType) {
      case 'Create':
        await ApiMethodCustom.create(...);
        break;
      case 'Update':
        await ApiMethodCustom.update(...);
        break;
      case 'Delete':
        await ApiMethodCustom.delete(...);
        break;
    }
    console.info('Success for request type ${event.RequestType}');
    await sendResponse(event, context, 'SUCCESS', { } );
  } catch (error) {
    console.error('Error for request type ${event.RequestType}: ', error);
    await sendResponse(event, context, 'FAILED', { } );
  }
}

async function sendResponse (event, context, responseStatus, responseData, physicalResourceId) {

  var reason = responseStatus == 'FAILED' ? ('See the details in CloudWatch Log Stream: ' + context.logStreamName) : undefined;

  var responseBody = JSON.stringify({
    StackId: event.StackId,
    RequestId: event.RequestId,
    Status: responseStatus,
    Reason: reason,
    PhysicalResourceId: physicalResourceId || context.logStreamName,
    LogicalResourceId: event.LogicalResourceId,
    Data: responseData
  });

  var responseOptions = {
    headers: {
      'Content-Type': '',
      'Content-Length': responseBody.length
    }
  };

  console.info('Response body:\n', responseBody);

  try {
    await axios.put(event.ResponseURL, responseBody, responseOptions);

    console.info('CloudFormationSendResponse Success');
  } catch (error) {
    console.error('CloudFormationSendResponse Error:');

    if (error.response) {
      console.error(error.response.data);
      console.error(error.response.status);
      console.error(error.response.headers);
    } else if (error.request) {
      console.error(error.request);
    } else {
      console.error('Error', error.message);
    }

    console.error(error.config);

    throw new Error('Could not send CloudFormation response');
  }
}