15

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?

Thales Minussi
  • 6,965
  • 1
  • 30
  • 48
rlcrews
  • 3,482
  • 19
  • 66
  • 116

3 Answers3

19

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!

Thales Minussi
  • 6,965
  • 1
  • 30
  • 48
  • Thanks Thales, I've updated my question. Changing my implementation to use async/await is still returning an empty array. – rlcrews Feb 21 '19 at 15:48
  • Could you please update the question with your handler? It may now be something to do with the scopes. – Thales Minussi Feb 21 '19 at 15:50
  • @rlcrews I have updated my answer with a working example. Hope this helps! – Thales Minussi Feb 21 '19 at 17:33
  • 1
    That seems to have been the issue of omitting the {Name: param} in the call. Thanks for the guidance – rlcrews Feb 21 '19 at 18:08
  • 1
    You can also use the built-in promise API `parameterStore.getParameter(...).promise()`, instead of handling the `new Promise()` by yourself. (I've also written a microframework to help developers retrieve secrets without dealing with the low level API directly: https://laconiajs.io/docs/guides/retrieving-secrets. Disclaimer: I'm the author). – ceilfors Apr 23 '19 at 05:27
  • even though this is not part of the OP's question - This may help someone looking to get decrypted values for secure string parameters: parameterStore.getParameter({ Name: param, WithDecryption: true } .... – kiran01bm Mar 28 '20 at 11:40
1

If your lambda is deployed on VPC, make sure that Security Group is attached to it and outbound traffic is allowed. It will be able to access parameter store automatically.

https://aws.amazon.com/premiumsupport/knowledge-center/lambda-vpc-parameter-store/

Aziz Zoaib
  • 661
  • 8
  • 21
  • is there a way to do it without the async nature of the getparameter call i want it to be sync so the next line of code can get the data and store the secrets in its variable which can be accessed later when an api call requires secret key . – karthik rao May 26 '22 at 09:56
  • 1
    I think it does not matter whether its sync or async, if the lambda is deployed on VPC and SG is attached with outbound allowed - it should be able to access parameter store. – Aziz Zoaib May 27 '22 at 11:16
1

A simpler solution would be:

const getParameterFromStore = (params) => servmgr.getParametersByPath(params).promise();

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

As you can see, the SDK itself provides utility functinality that you can use depending on your needs to use in an async or syncronious fashion.

Hope it helps.

patrick.1729
  • 4,222
  • 2
  • 20
  • 29
mim
  • 1,301
  • 14
  • 24