如何从 CloudFormation 模板中获取 AWS IOT 端点 URL?

How to obtain AWS IOT endpoint URL from within a CloudFormation template?

我希望我的一些 Lambda 资源使用 aws-sdkAWS.IotData({ endpoint: url }) 函数推送到 AWS IOT 端点 - 其中端点是必需参数。

现在,我正在通过环境变量将端点 URL 传递到我的 Lambda。但是,当放入 SAM/CF 模板时,我找不到检索 IOT 端点 URL 的方法,因此我只能 !Ref 它。

浏览 AWS resource type reference 我没有找到与 IOT 端点对应的任何资源。

似乎只能通过 AWS 控制台(启用/禁用)手动配置 IOT 端点,如下面的屏幕截图所示:

关于如何控制配置 IOT 端点或至少从 SAM/CF 模板中读取 IOT URL 而不使用 aws-cli 编写脚本的任何建议?

恐怕您无法配置 IoT 端点,因为与 IoT 端点相关的唯一 API 调用是 DescribeEndpoint

您可以做的是创建一个 Lambda 支持的 CloudFormation 自定义资源。 Lambda 函数将执行 DescribeEndpoint 调用(根据 Lambda 的运行时使用您选择的 AWS SDK)和 return 端点的 URL 以便您的其他 CloudFormation 资源可以使用它。

这是一个关于 Lambda 支持的自定义资源的好例子:http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources-lambda.html

对于任何对使用 CloudFormation 自定义资源的解决方案感兴趣的人,我编写了一个简单的 Lambda 和一个 CF 模板,为其他 CF 堆栈提供 IOT 端点地址。

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Resources:
  IotEndpointProvider:
    Type: 'AWS::Serverless::Function'
    Properties:
      FunctionName: IotEndpointProvider
      Handler: iotEndpointProvider.handler
      Runtime: nodejs6.10
      CodeUri: .
      MemorySize: 128
      Timeout: 3
      Policies:
        - Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action: 
              - iot:DescribeEndpoint
            Resource:
              - '*'
  IotEndpoint:
    Type: 'Custom::IotEndpoint'
    Properties:
      ServiceToken: !GetAtt IotEndpointProvider.Arn
Outputs:
  IotEndpointAddress:
    Value: !GetAtt IotEndpoint.IotEndpointAddress
    Export:
      Name: IotEndpointAddress

iotEndpointProvider.js

var aws = require("aws-sdk");

exports.handler = function(event, context) {
    console.log("REQUEST RECEIVED:\n" + JSON.stringify(event));

    // For Delete requests, immediately send a SUCCESS response.
    if (event.RequestType == "Delete") {
        sendResponse(event, context, "SUCCESS");
        return;
    }

    const iot = new aws.Iot();
    iot.describeEndpoint({}, (err, data) => {
    let responseData, responseStatus;
        if (err) {
            responseStatus = "FAILED";
            responseData = { Error: "describeEndpoint call failed" };
            console.log(responseData.Error + ":\n", err);
        } else  {
            responseStatus = "SUCCESS";
            responseData = { IotEndpointAddress: data.endpointAddress };
            console.log('response data: ' + JSON.stringify(responseData));
        }

        sendResponse(event, context, responseStatus, responseData);
    });
};

// Send response to the pre-signed S3 URL 
function 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();
}