【问题标题】:AWS S3 Lambda event - get, update, then put JSON file from same bucketAWS S3 Lambda 事件 - 从同一个存储桶获取、更新然后放入 JSON 文件
【发布时间】:2019-06-01 15:41:20
【问题描述】:

我正在使用 here 找到的 Amazon S3 教程修改 AWS Lambda,以便它能够简单地--

  • 读取已驻留在该存储桶中的 JSON 文件(内容索引),
  • 使用新创建的密钥(该存储桶中触发此 Lambda 的新“文件夹”)对其进行更新,
  • 然后将更新的 JSON 文件保存(放置)回去。

澄清: 只有在其中创建文件夹对象时,存储桶才应触发 Lambda 函数。内容索引 (index.json) 已驻留在存储桶中。因此,bucket 将包含文件夹(例如,{folder-1、folder-2、folder-n})和 index.json。每次添加新文件夹时,都会将其添加到 JSON 数组中。

具体来说,在我的例子中,我有一个根(目标)存储桶,其中包含由 Elemental MediaConvert 创建的一系列文件夹。每个文件夹代表一个新视频;每个文件夹中都有可以提供给不同设备的不同格式。回到根级别,我有 index.json,它是这些视频文件夹的数组;它是内容索引。现在,我可以修改作为 MediaConvert 流一部分的 Lambda。但我会在其他时间考虑。在这里,我只想在每次 MediaConvert 写入一个新的视频文件夹时触发一个新的 S3 Lambda……这只是一些随机的 GUID。

我正在学习 Node JS,这是我第一次使用一些结构并调用你在这里看到的。 (我至少知道这种形式比使用回调更清晰。)

由于将其作为 Lambda 函数进行测试会很棘手(对我来说!),有人会指出任何明显的错误吗??

另外,有人会告诉我如何使用 Amazon S3 事件数据手动测试这个 Lambda 函数(在配置实际存储桶以发布所需事件之前)吗?我想我需要 event.json 来指定新创建的文件夹的名称,这样它就可以添加到我的 index.json 中,它也位于同一个存储桶中。

这是我的代码:

// dependencies
var async = require('async');
var AWS = require('aws-sdk');
var util = require('util');

// constants
//const DEST_FOLDER = 'my-triggering-bucket';
const CONTENT_INDEX_FILENAME = 'index.json';

// get reference to S3 client
var s3 = new AWS.S3();

exports.handler = function(event, context, callback) {
    // Read options from the event.
    // Need the new folder (key) that's been added to the bucket...
    console.log("Reading options from event:\n", util.inspect(event, {depth: 5}));

    // I assume this is the triggering bucket...
    var triggerBucket = event.Records[0].s3.bucket.name;

    // And I assume this is the folder that was added and caused the trigger
    var newKey    = decodeURIComponent(event.Records[0].s3.object.key);

    const indexKey = CONTENT_INDEX_FILENAME;

    // Get the content index and add the newly created dir to it
    async.waterfall([
        function download(next) {
            // Download the content index from S3 into a buffer.
            s3.getObject({
                    Bucket: triggerBucket,
                    Key: indexKey
                },
                next);
        },
        function update(response, next) {
          // Update the content index with the new dir that was added
          console.log('UPDATE...');
          var obj = JSON.parse(response);
          obj.push(newKey);
          var jsonStr = JSON.stringify(obj);
          next(null, jsonStr.ContentType, jsonStr);
        },
        function upload(contentType, data, next) {
            // Stream the updated content index back
            s3.putObject({
                    Bucket: triggerBucket,
                    Key: indexKey,
                    Body: data,
                    ContentType: contentType
                },
                next);
            }
        ], function (err) {
            if (err) {
                console.error('error: ' + err);
            } else {
                console.log('Success);
            }

            callback(null, "message");
        }
    );
};

更新 我已经放弃了这种方法,转而通过另一种方式更新内容索引,这种方式不会冒着我的 Lambda 执行失控的风险。我亲身发现,当一个人的设计不提供可靠的事件通知过滤器时,尝试在存储桶中触发特定的 createObject 事件并不是一个好主意。 (我无法过滤简单的/ 的后缀。)此外,我期待单个文件夹键对象创建事件来触发我的 Lambda,但实际上其他文件夹和键在 内部创建新的根级文件夹最终也触发了我的 Lambda。因此,这让我进入了视频转换工作流程,以修改通知工作流程成功完成的 Lambda,从而更新我的内容索引。

【问题讨论】:

  • 注意#1:使用两个存储桶会更安全,否则如果此 Lambda 函数正在写入触发它的同一个存储桶,以及新的或覆盖的对象(由 Lambda 函数创建)还会触发 Lambda 函数,您有一个无限循环的 Lambda 调用和存储桶PUT 操作,如果不及时检测到,可能会花费真正的美元。至少确保新对象不会触发包含自身。
  • 注意 #2:此代码将错误放置(偶尔省略)文件以包含在索引中,因为有一个隐含的假设,即当并行上传发生时 Lambda 函数不会同时运行 - 它会 -并且 s3.getObject 始终获取已被覆盖的(索引)对象的最新副本 - 它可能不会。这是不能保证的。快速读取后的覆盖可能会返回旧对象或新对象。它总是会返回一个完整的对象,但 S3 只保证覆盖的最终一致性。
  • 感谢您的提醒。不,我没有考虑过这两种情况..但我想当我接近时第一个会突然出现在我身上!这很容易防止,因为触发操作将是一个新的“文件夹”出现在存储桶中......而不是更新的 JSON 对象。我需要考虑@athar-kahn 在下面的回复,因为他似乎表示创建新文件夹不会触发我的 Lambda。至于第 2 点,在这个阶段不可能有并发的可能性.. 但这是需要牢记的。谢谢!

标签: javascript node.js amazon-web-services amazon-s3 aws-lambda


【解决方案1】:

只有在存储桶中创建/删除/修改新对象时才会触发您的 Lambda,具体取决于您的配置。如果您在存储桶中创建一个新文件夹,它不会触发您的 Lambda。您可以通过在 Lambda Node.js 8.1 运行时中使用 async/await 语法来简化代码。

Lambda 处理程序

var AWSS3 = require('aws-sdk/clients/s3');
const CONTENT_INDEX_FILENAME = 'index.json';
var s3 = new AWSS3();

exports.handler = async (event) => {

  try {
    console.log('Event', JSON.stringify(event));

    // Bucket name.
    const triggerBucket = event.Records[0].s3.bucket.name;

    // New key added.
    const newKey = event.Records[0].s3.object.key;

    // Assuming only folder name is to be added in the list. If object 
    // is added in the bucket root then it will be ignored.
    if (newKey.indexOf('/') > -1) {

      // Get existing data.
      let existing = await s3.getObject({
        Bucket: triggerBucket,
        Key: CONTENT_INDEX_FILENAME
      }).promise();

      // Parse JSON object.
      let existingData = JSON.parse(existing.Body);

      // Get the folder name.
      const folderName = newKey.substring(0, newKey.indexOf("/"));

      // Check if we have an array.
      if (!Array.isArray(existingData)) {
        // Create array.
        existingData = [];
      }

      existingData.push(folderName);

      await s3.putObject({
        Bucket: triggerBucket,
        Key: CONTENT_INDEX_FILENAME,
        Body: JSON.stringify(existingData),
        ContentType: 'application/json'
      }).promise();

      console.log(`Added new folder name ${folderName}`);

      return folderName;

     } else {
         console.log('Key was added in bucket root.');
         return 'Ignored';
     }
    };
  }
  catch(err) {
    return err;
  }

在本地运行:

在项目的根目录中创建一个 event.json 文件。在 event.json 中添加以下内容。

{
  "Records":[
    {
      "s3":{
        "bucket":{
        "name": "your_bucket_name"
    },
    "object":{
      "key": "your_folder/your_file.json"
    }
   }
  }
 ]
}

全局下载 lambda 本地包。

npm install -g lambda-local

最后测试一下:

通过传递上面创建的 event.json 文件在本地运行函数。

lambda-local -l path/to/function.js -e event.json

【讨论】:

  • “如果您在存储桶中创建一个新文件夹,它不会触发您的 Lambda。” 根据术语,这不是真的。使用控制台创建文件夹会创建一个名称以/ 结尾的零字节对象,并将触发任何匹配事件。
  • 我认为 event.json 可能不正确,即使我认为您的 Lambda 与我的用例完美匹配。尽管如此,非常感谢您让我清楚如何测试。除非我弄错了,否则 event.json 必须指定存储桶和 新文件夹(不是 json 文件)。我要更新的内容索引(即index.json)直接位于存储桶中。所以,我认为“对象”需要只是“your_folder”......首先触发事件的东西。我说的对吗?
  • @Michael-sqlbot 你是绝对正确的。由于 OP 没有说明 S3 触发器的设置位置,所以我试图通过创建一个新文件夹来明确表示不要指望 Lambda 触发器。
  • @motivus event.json 对我来说看起来是正确的。 s3.bucket.name 是您的存储桶的名称,s3.object.key 是要上传的对象的路径。如果对象在存储桶根目录中上传,则路径为filename.json,如果在文件夹中上传,则路径为folder_name/file_name.json
  • 另外,正如@michael-sqlbot 指出的那样,创建一个文件夹将根据您的配置触发您的 Lambda,因此您需要确保您的 index.json 未设置为触发,否则它将最终在一个无限循环。
【解决方案2】:

至于本地测试,我之前使用过https://www.npmjs.com/package/aws-lambda-local,对我来说效果很好。

只需查看 AWS 文档,例如 S3 事件 json 数据

【讨论】:

  • 谢谢安迪。我会安装那个包!
  • 我看到这个资源将帮助我解决我在尝试全局安装 aws-lambda-local 时遇到的错误...docs.npmjs.com/…
猜你喜欢
  • 2016-05-15
  • 1970-01-01
  • 1970-01-01
  • 2020-09-15
  • 2020-12-27
  • 2018-09-04
  • 2023-03-31
  • 2015-03-21
  • 2019-11-12
相关资源
最近更新 更多