6

I'm having trouble passing one of the values returned by my AWS state machine as input to a Lambda using the EventBridge service.

I created a state machine in AWS Step Functions to model a specific problem in our domain. Once the state machine finishes, I want to perform another operation from inside one of my Lambdas. To make that work, I created a new rule using EventBridge: whenever the state machine finishes, it triggers my lambda with a specific Json input.

My problem is in how to extract properties from the state machine output and pass them as properties of the lambda input.

Say my state machine returns this:

{
    "usefulObject":{
        "usefulProperty":"value"
    },
    "anotherProperty":"anotherValue"
}

I want to receive the following payload in my lambda:

{
    "property":"value"
}

Initially, I thought I would be able to do this using the "Input Transformation" option on the EventBridge rule, something like:

  • InputTransformer Path:
{"propertyValue":"$.usefulObject.usefulProperty"}
  • InputTransformer Template:
{"property":<propertyValue>}

However, during testing, I realized that the event payload contains a lot more data than my state machine output. In reality, the state machine output is wrapped in this "event container" object, something like this:

{
    "version": "0",
    "id": "...",
    "detail-type": "Step Functions Execution Status Change",
    "source": "aws.states",
    "account": "...",
    "time": "2020-11-10T13:59:57Z",
    "region": "us-east-1",
    "resources": [
        "...myStateMachineArn..."
    ],
    "detail": {
        "executionArn": "...myStateMachineExecutionArn...",
        "stateMachineArn": "...myStateMachineArn...",
        "name": "ff72036a-2917-c657-80e7-2589b7b76d59",
        "status": "SUCCEEDED",
        "startDate": 1605016794597,
        "stopDate": 1605016797936,
        "input": "{\n  \"usefulObject\":{\n    \"usefulProperty\": \"value\"\n  },\n  \"anotherProperty\": \"anotherValue\"\n}",
        "inputDetails": {
            "included": true
        },
        "output": "{\n  \"usefulObject\":{\n    \"usefulProperty\": \"value\"\n  },\n  \"anotherProperty\": \"anotherValue\"\n}",
        "outputDetails": {
            "included": true
        }
    }
}

As you can see, in the actual event payload, my statemachine data is stored as a stringified Json value inside the output node. If I then change my Input Transformation path to something like:

{"propertyValue":"$.detail.output.usefulObject.usefulProperty"}

I get an empty result for property in the transformed input. Turns out, JsonPath is unable to traverse the stringified value as part of the Json payload and will fail on the search.

How can I extract the usefulProperty value out of that Json string in the event payload so that I can pass it to my lambda function? Is there a way to do it using purely JsonPath that I'm missing? Perhaps there is a way to configure AWS to not convert the payload into a string and just make it part of the whole event payload? Any other alternatives?

julealgon
  • 7,072
  • 3
  • 32
  • 77
  • 2
    I don't have the answer for your question, but it's not easier to add that Lambda as the last step to your State Machine? – Pooya Paridel Nov 13 '20 at 00:03
  • Putting the call to the lambda in the state machine itself will for sure work @PooyaParidel , however this solves the problem but doesn't answer the actual question here. I'll probably not use EventBridge anymore for this one (and will probably go with a nested async workflow) but I'll leave this open in the chance that it is still possible to do using EventBridge. – julealgon Nov 13 '20 at 18:49
  • 1
    There's a good reason why you wouldn't want to go for integrating the lambda: scalability. Imagine that you want to trigger not 1, but 100, all listening to the same event. It's very bad to create a parallel stage with 100 branches. It would be much better to manage and capture this with EventBridge – justHelloWorld Dec 02 '21 at 16:23

1 Answers1

2

There are a couple of things you can try to keep your input and output from being stringified by the interpreter:

  1. Instead of input :
{"propertyValue":"$.usefulObject.usefulProperty"}

try

{"propertyValue.$":"$.usefulObject.usefulProperty"}

or

{"propertyValue": $.usefulObject.usefulProperty}
  1. Make sure your lambda does not return a stringified version of the result i.e: return JSON.stringify(usefulObject)

  2. If none of these help you can try converting the output string in your step function template with:

{
    "propertyValue.$": "States.StringToJson($.detail.output.usefulObject.usefulProperty)"
}
  1. longshot: make sure you are not deliberately converting it to string, in your step function template with States.JsonToString somewhere... :)

as per the docs on https://states-language.net/

  1. Try pulling out the value from the event object within the Lambda, and then use it:
exports.lambdaHandler = async (event) => {
  // parse your input or output string and use it as an object;
  let receivedParams = JSON.parse(event.detail.input);
  let propertyValueObject = {
    propertyValue: receivedParams.usefulObject.usefulProperty,
  };

  // do something in your lambda with propertValueObject 
  // which is equal to: { propertyValue: 'value' }
  console.log(propertyValueObject);
};

let event = {
    "version": "0",
    "id": "...",
    "detail-type": "Step Functions Execution Status Change",
    "source": "aws.states",
    "account": "...",
    "time": "2020-11-10T13:59:57Z",
    "region": "us-east-1",
    "resources": [
        "...myStateMachineArn..."
    ],
    "detail": {
        "executionArn": "...myStateMachineExecutionArn...",
        "stateMachineArn": "...myStateMachineArn...",
        "name": "ff72036a-2917-c657-80e7-2589b7b76d59",
        "status": "SUCCEEDED",
        "startDate": 1605016794597,
        "stopDate": 1605016797936,
        "input": "{\n  \"usefulObject\":{\n    \"usefulProperty\": \"value\"\n  },\n  \"anotherProperty\": \"anotherValue\"\n}",
        "inputDetails": {
            "included": true
        },
        "output": "{\n  \"usefulObject\":{\n    \"usefulProperty\": \"value\"\n  },\n  \"anotherProperty\": \"anotherValue\"\n}",
        "outputDetails": {
            "included": true
        }
    }
}

const lambdaHandler = (event) => {
  // parse your input or output string and use it as an object;
  let receivedParams = JSON.parse(event.detail.input);
  let propertyValueObject = {
    propertyValue: receivedParams.usefulObject.usefulProperty,
  };

  // do something in your lambda with propertValueObject 
  // which is equal to: { propertyValue: 'value' }
  console.log(propertyValueObject);
};

lambdaHandler(event)
Herald Smit
  • 2,342
  • 1
  • 22
  • 28
  • I'm not sure how your suggestions regarding converting from JSON would help, since I have no control on how the event container is generated and _that's_ where the stringification is happening. I'm not returning a stringified Json from my state machine, it is returning proper JSON. When the event notification comes however, that Json is presented in string form in the payload and I can't manipulate it further after that. – julealgon Sep 21 '21 at 20:11
  • Also, you suggestion about using `States.StringToJson` does not apply: I don't want to limit the output of my state machine. The output is used somewhere else. I only want to read the specific property from it from inside the EventBridge mapping. Simplifying the output before the state machine finishes is just not possible for me and not part of the question. – julealgon Sep 21 '21 at 20:13
  • ahh ok. The only other way then would be to pull the value out from the event object within the lambda before using it somewhere else... or will that not work for you? I've added a code snippet to the answer at no 5. – Herald Smit Sep 22 '21 at 09:25
  • That would either couple the lambda considerably to the state machine (something I don't want) or it would require a "conversion lambda" in the middle which would be responsible just for parsing and extracting the value, then potentially publishing a brand new event with only that value or directly calling the target lambda. Obviously, I would also not want that due to the drastic increase in complexity and even costs (having to have a lambda just to perform this parsing and redirection would be very wasteful). – julealgon Sep 23 '21 at 16:42
  • You can reuse the lambda for other similar functions, just filter on event properties like : executionArn, stateMachineArn etc. Good luck. – Herald Smit Sep 23 '21 at 17:31
  • I definitely could, but that again would be far from ideal. While it can certainly be made to work, I explicitly requested a way using the Event Bridge's Input Transformer capability, so I don't think your answer applies. It is a useful post though so +1. – julealgon Sep 23 '21 at 20:30
  • Yeah, to do pattern matching, through the AWS console or through a template, that state machine output needs to be converted to JSON somewhere before the event gets triggered. So double check that your State Machine returns actual JSON. That usefulObject should not arrive stringified. It's difficult to reproduce and test this issue. – Herald Smit Sep 24 '21 at 08:49
  • I'm definitely returning json from the state machine. There is nothing being done to stringify the results. Keep in mind that this post is somewhat old by now and that I'm not currently working on the same project. If you can prove that this is not an issue anymore and that the state machine payload shows up as raw json in the transformer now, it would be a valid answer. It definitely wasn't the case when I posted this question. – julealgon Sep 27 '21 at 16:34