Serverless-offline: Ability to run lambda functions offline "locally"

Created on 4 Jun 2016  路  7Comments  路  Source: dherault/serverless-offline

Sorry for empty post in your emails, ill edit this now.

is there a way to use lambda functions that are not deployed, via invoke in serverless offline mode?

eg .then(() => invoke('timeout', {user, delay: 70})), would execute the lambda function. But it would call it in AWS and not try running it in offline mode.

like theres a command to run sls function run timeout that runs the function locally
so could this be abstracted to be used inside the code so that when you run serverless offline it would run the functions called in the code locally.

enhancement help wanted

Most helpful comment

Hi, serverless-offline does not have a local "invoke" method at the moment.
If you want to use "invoke" within a handler to call another local handler,
I suggest you mock the function invoke:

const invoke = process.env.IS_OFFLINE ? require('my-fake-local-invoke') : require('the-real-invoke');

or wrap it around a function that checks for process.env.IS_OFFLINE.

const wrappedInvoke = (...args) => {
  if (process.env.IS_OFFLINE) return myLocalInvoke(...args);

  return invoke(...args);
};

All 7 comments

https://github.com/SC5/lambda-wrapper
This is probably close to what I'm looking for

Hi @Aciid, this feature is interesting, can you point me to the invoke API? Is it a AWS thing?

Hey @dherault here is invoke.js from serverless-graphql project

'use strict';

const Promise = require('bluebird');
const _ = require('lodash');
const Lambda = require('aws-sdk').Lambda;

const lambda = new Lambda({
  region: process.env.SERVERLESS_REGION,
  sessionToken: process.env.AWS_SESSION_TOKEN
});

/**
 * Invokes the project functions
 * @param {string} name The function name
 * @param {object} data Optional event data
 * @param {function} responseHandler Optional callback, if specified the invocation type
 * will be RequestResponse (synchronous), otherwise it will be 'Event' (async).
 */

module.exports = (name, data, responseHandler) => {
  if (arguments.length === 2 && _.isFunction(arguments[1])) {
    responseHandler = arguments[1];
    data = {};
  }

  const FunctionName = process.env.SERVERLESS_PROJECT + '-' + name;
  const InvocationType = responseHandler ? 'RequestResponse' : 'Event';

  const params = {
    FunctionName,
    InvocationType,
    LogType: 'None',
    Payload: new Buffer(JSON.stringify(data)),
    Qualifier: process.env.SERVERLESS_STAGE
  };

  return Promise
    .fromCallback(cb => lambda.invoke(params, cb))
    .then(reply => reply.Payload ? JSON.parse(reply.Payload) : {})
    .then(responseHandler);
}

And heres how serverless-graphql uses it in collection/users/resolve.js

module.exports = {
  create(user) {
    user.id = uuid.v1();
    user.permissions = ['UPDATE_USER', 'CREATE_RESERVATION'];

    // generated salted hash with bcryptjs with 10 work factor
    user.password_hash = bcryptjs.hashSync(user.password, 10);

    delete user.password; // don't save plain password!

    return db('put', {
      TableName: usersTable,
      Item: user
    })
    // let's invoke another lambda asynchronously (don't wait till it finished)!
    .then(() => invoke('timeout', {user, delay: 70}))  // no actual delay here
    // if we pass a callback it will run synchronously, so we'll get a response
    .then(() => invoke('timeout', {user, delay: 50}, (response) => {
      // this should be delayed for 50ms
      // let's do something with the response
      if (response.result === 'success') {
        console.log("response data:", response);
      } else {
        return Promise.reject(new Error("Something went wrong :("));
      }
    }))
    // finally return the user record
    .then(() => user);
  },

And this is timeout function's handler.js that is invoked in the users collection's create function as an example.
Serverless will try to invoke it from from deployed AWS functions, even when running serverless-offline

'use strict';

module.exports.handler = (event, context, cb) => {
  event.delay = event.delay || 100;
  event.result = 'success';

  setTimeout((() => cb(null, event)), event.delay);
};

Here's Lambda-wrapper that I figured using with env.IS_OFFLINE, that does the same thing as invoke but from local code. This is handler.js for a test function

'use strict';

// For offline usage
const lambdaFunc = require('../getlatestposition/handler.js');
const lambda = require('lambda-wrapper').wrap(lambdaFunc);


module.exports.respond = (event, cb, context) => {

  var event_1 = { deviceId: 1 };
  lambda.run({Payload: event_1}, function(err, data) {
    if (err) {
      console.log(err)
    }
    console.log(data)
    return context.done(err,data)

  })

};


It runs just my basic another handlers function, could run timeout handler as well.

'use strict';

// Require Serverless ENV vars
//var ServerlessHelpers = require('serverless-helpers-js').loadEnv();

const db = require('../../api/lib/dynamodb');

const stage = process.env.SERVERLESS_STAGE;
const projectName = process.env.SERVERLESS_PROJECT;
const reservationsTable = projectName + '-reservations-' + stage;
const vehiclesTable = projectName + '-vehicles-' + stage;
const request = require('request');
/**
 * Get latest device position
 * @param event
 * @param context
 * @param cb
 */
module.exports.handler = (event, context, cb) => {

  // query parameter for deviceId ?deviceId=1 also event = { deviceId: 1 }
  event.deviceId = event.deviceId || 1;

  var options = {
    method: 'GET',
    json: true,
    url: "http://xxxxxxxxxxxxxxxxxxx:8082/api/positions?_dc=1464888263330&deviceId="+event.deviceId
  };
  request(options, function(error, response, body){
    if(error) console.log(error);
    else console.log(body);
    if (body[0] == null) {
      event.result = {status: "error", msg: "FATAL: deviceId doesn't exist"}
    }
    if (body[0].deviceId != event.deviceId) {
      event.result = {status: "error", msg: "FATAL: deviceId doesn't match returned."}
    }
    event.result = body;
    cb(null, event)
  });

  /*
  reservation.id = uuid.v1(); // Created

  return db('put', {
    TableName: reservationsTable,
    Item: reservation
  })
  */
};

Example of a complex invokation chain that requires callbacks to write to database from the first function that makes the selective database query.

  var event_1 = { reservationId: "76d823c0-2a9c-11e6-909d-73a9b178868a", deviceId: "1" };
  var w1 = lambda.wrap(latestFunc);

  w1.run({Payload: event_1}, function(err, data) {
    if (err) {
      console.log(err)
    }
    console.log("Data received: "+data)

    var w2 = lambda.wrap(updateFunc);
    w2.run(data, function(err, data) {
      if (err) {
        console.log(err)
      }
      console.log("Data received: " + data)
    });

    return context.done(err,data)

  });

This is actually nice feature to have as I have same problem when I invoke lambda function it calls AWS lambda an not local lambda...

Hi all, maybe this PR fixed this issue for some edge cases: #74

Is there any documentation that shows how to use serverless-offline with a lambda function that is invoked within another lambda function using AWS.Lambda?

Hi, serverless-offline does not have a local "invoke" method at the moment.
If you want to use "invoke" within a handler to call another local handler,
I suggest you mock the function invoke:

const invoke = process.env.IS_OFFLINE ? require('my-fake-local-invoke') : require('the-real-invoke');

or wrap it around a function that checks for process.env.IS_OFFLINE.

const wrappedInvoke = (...args) => {
  if (process.env.IS_OFFLINE) return myLocalInvoke(...args);

  return invoke(...args);
};
Was this page helpful?
0 / 5 - 0 ratings