Как я могу перечислить все экземпляры Amazon EC2 с помощью Node.js в AWS Lambda?

Я на AWS и AWS SDK для JavaScript в Node.js. Я пытаюсь создать функцию AWS Lambda, и внутри я хочу получить список всех моих экземпляров Amazon EC2, но я просто не могу заставить его работать. Может ли кто-нибудь определить, что я делаю неправильно?

Вот мой код функции Lambda:

var AWS = require('aws-sdk');
AWS.config.region = 'us-west-1';

exports.handler = function(event, context) {
    console.log("\n\nLoading handler\n\n");
    var ec2 = new AWS.EC2();
    ec2.describeInstances( function(err, data) {
        console.log("\nIn describe instances:\n");
      if (err) console.log(err, err.stack); // an error occurred
      else     console.log("\n\n" + data + "\n\n"); // successful response
    });
    context.done(null, 'Function Finished!');  
};

И это моя политика (я думаю, это правильно?)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:*"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    },
    {
    "Effect": "Allow",
    "Action": [
      "ec2:*"
    ],
    "Resource": "arn:aws:ec2:*"
  },
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject"
      ],
      "Resource": [
        "arn:aws:s3:::*"
      ]
    }
  ]
}

И если я сделаю console.log на "ec2", я получаю:

{ config: 
   { credentials: 
      { expired: false,
        expireTime: null,
        accessKeyId: 'XXXXXXXXXXXXXXXXXX',
        sessionToken: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
        envPrefix: 'AWS' },
     credentialProvider: { providers: [Object] },
     region: 'us-west-1',
     logger: null,
     apiVersions: {},
     apiVersion: null,
     endpoint: 'ec2.us-west-1.amazonaws.com',
     httpOptions: { timeout: 120000 },
     maxRetries: undefined,
     maxRedirects: 10,
     paramValidation: true,
     sslEnabled: true,
     s3ForcePathStyle: false,
     s3BucketEndpoint: false,
     computeChecksums: true,
     convertResponseTypes: true,
     dynamoDbCrc32: true,
     systemClockOffset: 0,
     signatureVersion: 'v4' },
  isGlobalEndpoint: false,
  endpoint: 
   { protocol: 'https:',
     host: 'ec2.us-west-1.amazonaws.com',
     port: 443,
     hostname: 'ec2.us-west-1.amazonaws.com',
     pathname: '/',
     path: '/',
     href: 'https://ec2.us-west-1.amazonaws.com/' } }

Ответ 1

Наиболее вероятной причиной является то, что вы явно завершаете свою функцию Lambda до того, как она завершит вызов API EC2 DescribeInstances.

Причина в том, что Lambda предполагает, что ваш код завершил выполнение, как только вы вызываете context.done(...). И это происходит до вызова console.log(... data ...).

Это странное упорядочение происходит из-за того, как работает NodeJS и как работает AWS SDK для JavaScript. В NodeJS вы никогда не должны блокировать выполнение. Вызов веб-службы (например, EC2) блокирует выполнение. Поэтому AWS SDK для JavaScript (как и большинство NodeJS-библиотек) работает путем асинхронного вызова.

Чаще всего, когда у вас асинхронный вызов, вы передаете функцию обратного вызова этому вызову. Когда результаты будут готовы, NodeJS выполнит функцию обратного вызова.

В вашем коде эта функция function(err, data) {...} является функцией обратного вызова. Это не выполняется немедленно, но будет запланировано на выполнение, когда NodeJS увидит, что вызов ec2.describeInstances получил свои результаты.

Как только вы запланируете выполнение своего обратного вызова, вы вызываете context.done(...), что говорит Лямбде: я закончил, вы можете убить меня. И он радостно подчиняется и прерывает вашу функцию, прежде чем вызов EC2 DescribeInstances получит свои данные и передаст их функции обратного вызова.

Как решить проблему?

Теперь ответ должен быть прояснен: просто переместите вызов context.done(...) внутри функции обратного вызова сразу после блока if/else, содержащего вызов console.log(...data...):

ec2.describeInstances( function(err, data) {
  console.log("\nIn describe instances:\n");
  if (err) console.log(err, err.stack); // an error occurred
  else     console.log("\n\n" + data + "\n\n"); // successful response
  context.done(null, 'Function Finished!');  
});