Search code examples
amazon-web-servicespromiseaws-lambdaaws-parameter-store

How to access the aws parameter store from a lambda using node.js and aws-sdk


I've created a lambda and cloud formation template which grants a lambda access to the parameter store and secrets manager. When I test the lambda I have the following functions outside of the export.handler function:

function getParameterFromStore(param){
    let promise = new Promise(function(resolve, reject){
        console.log('++ ' + param.Path);
        servmgr.getParametersByPath(param, function(err, data){
            if(err){
                reject(console.log('Error getting parameter: ' + err, err.stack));
            } else {
                resolve(data);
            }
        });
    });

   let parameterResult = promise.then(function(result){
    console.log('---- result: '+ JSON.stringify(result));
        return result;
    });
   return parameterResult;
};

servmgr is instantiated as var servmgr = new AWS.SSM();

When I call this function from the export.handler function I do so as:

myFirstParam =  { Path : '/myPath/Service/servicesEndpoint'};

let endpointResult = getParameterFromStore(myFirstParam);

In the lambda I have the function retrieve the parameter defined outside of the export.handler function bt wrapped in a promise.

When I run/test this lambda the object returned is always undefined... I get Parameters[] back but no values.

2019-02-20T21:42:41.340Z    2684fe88-d552-4560-a477-6761f2de6717    ++ /myPath/Service/serviceEndpoint
2019-02-20T21:42:41.452Z    2684fe88-d552-4560-a477-6761f2de6717    ---- result: {"Parameters":[]}

How do you get parameter values returned back to a lambda at run time?

update

based upon the suggestion/answer from Thales I've simplified the lambda to just this:

const getParameterFromStoreAsync = (param) => {
    return new Promise((resolve, reject) => {
        servmgr.getParametersByPath(param, (err, data) => {
            if(err){
                reject(console.log('Error getting parameter: ' + err, err.stack));
            } 
            return resolve(data);
        });
    });
};

exports.handler = async(event, ctx, callback) => {

console.log('INFO[lambda]: Event: [' + JSON.stringify(event, null, 2) + ']');

    console.log('this is the event' + JSON.stringify(event));
    sfdcEndPointParam =  { Path : '/PartnerBanking/Service/SfdcEndpoint'};
    let myendpoint = await getParameterFromStoreAsync(sfdcEndPointParam);

    console.log('### endpoint path: ' + JSON.stringify(myendpoint));

done = ()=>{}
callback(null, done());
};

I am still seeing an empty array being returned in my tests:

### endpoint path: {"Parameters":[]}

I've also moved the function into the callback as

exports.handler = (event,ctx, callback){
done = async()=>{
 console.log('this is the event' + JSON.stringify(event));
    sfdcEndPointParam =  { Path : '/PartnerBanking/Service/SfdcEndpoint'};
    let myendpoint = await getParameterFromStoreAsync(sfdcEndPointParam);

    console.log('### endpoint path: ' + JSON.stringify(myendpoint));}
}
callback(null, done());

Same result ... empty array. Any additional things to try?


Solution

  • This is because your getParameterFromStore returns before your then() code is executed, thus parameterResult is undefined. If you don't want to change your code too much, I would return the Promise you create, like this:

    function getParameterFromStore(param){
    return new Promise(function(resolve, reject){
        console.log('++ ' + param.Path);
        servmgr.getParametersByPath(param, function(err, data){
            if(err){
                reject(console.log('Error getting parameter: ' + err, err.stack));
            } else {
                resolve(data);
            }
        });
    });
    

    };

    And finally, on your function's client, you can get the result like this:

    const myFirstParam =  { Path : '/myPath/Service/servicesEndpoint'}
    getParameterFromStore(myFirstParam).then(console.log)
    

    When coding in NodeJS, however, I highly recommend you use async/await instead, so you'll be able to escape the Promise Hell (chaninig Promise after Promise in order to achieve something "synchronously")

    When using async/await, you can design your code as though it was synchronous. Here's a refactored version of your example, using async/await as well as arrow functions:

    const getParameterFromStore = param => {
        return new Promise((resolve, reject) => {
            console.log('++ ' + param.Path);
            servmgr.getParametersByPath(param, (err, data) => {
                if (err) {
                    console.log('Error getting parameter: ' + err, err.stack)
                    return reject(err);
                }
                return resolve(data);
            });
        })
    }
    
    exports.handler = async (event) => {
       const endpointResult = await getParameterFromStore(event.someAttributeFromTheEventThatYouWantToUse)
    
       console.log(endpointResult)
    };
    

    EDIT: After the OP fixed the first issue, I created a working example on my own. It turned out that the way the OP was invoking the API was incorrect.

    Here's the full working example:

    'use strict';
    
    const AWS = require('aws-sdk')
    
    AWS.config.update({
      region: 'us-east-1'
    })
    
    const parameterStore = new AWS.SSM()
    
    const getParam = param => {
      return new Promise((res, rej) => {
        parameterStore.getParameter({
          Name: param
        }, (err, data) => {
            if (err) {
              return rej(err)
            }
            return res(data)
        })
      })
    }
    
    module.exports.get = async (event, context) => {
      const param = await getParam('MyTestParameter')
      console.log(param);
      return {
        statusCode: 200,
        body: JSON.stringify(param)
      };
    };
    

    Mind the Name attribute which must be provided as part of the API call to the ServiceManager.getAttribute method.

    This attribute is stated in the official docs

    I have run this myself and here's the output in CloudWatch Logs:

    enter image description here

    As you can see, the value was returned successfully.

    Hope this helps!