Search code examples
aws-step-functions

How to decrement an int value and put the result back into a specific property in AWS Step Functions


Given this input:

{
    "foo": "bar",
    "ttl": 100
}

I'm using AWS step functions to wait until something is ready but I don't want to wait for ever so one of my "states" is a time-to-live value that gets decremented. When the TTL reaches 0 the execution stops:

{
  "Comment": "State machine to check the status of something until it's ready or we waiting long enough",
  "StartAt": "get-status",
  "States": {
    "get-status": {
        "Type": "Task",
        "Resource": "arn:aws:lambda:region:account:function:get-status-lambda",
        "ResultPath": "$.status",
        "Next" : "is-ready"
    },
    "is-ready": {
        "Type": "Choice",
        "Choices": [
        {
            "Variable": "$.status",
            "StringEquals": "CREATE_COMPLETE",
            "Next": "ready"
        },
        {
            "Variable": "$.status",
            "StringMatches": "*ROLLBACK_COMPLETE*",
            "Next": "creation-failed"
        },
        {
            "Variable": "$.ttl",
            "NumericEquals": 0,
            "Next": "ttl-expired"
        }
        ],
        "Default": "wait-a-bit"
    },
    "wait-a-bit": {
        "Type": "Wait",
        "Seconds": 10,
        "Next": "decrement-ttl"
    },
    "decrement-ttl": {
        "Type": "Task",
        "Resource": "arn:aws:lambda:region:account:function:decrement-ttl-lambda",
        "InputPath": "$.ttl",
        "ResultPath": "$.ttl",
        "Next": "get-status"
    },
    "ttl-expired": {
        "Type": "Fail",
        "Cause":  "Create dumper stack TTL reached 0!"
    },
    "creation-failed": {
        "Type": "Fail",
        "Cause":  "A error during the cloudformation stack prevented its creation."
    },
    "ready": {
        "Type": "Pass",
        "End": true
    }
  }
}

enter image description here

The state machine above works. It adds a status property to the JSON and it does decrement the ttl value. That said it seems overkill to have to use a lambda function just to decrement an int value!

Could Intrinsic Functions come to the rescue, namely the States.MathAdd one?

If I modify the decrement-ttl state in the definition above with this...

    "decrement-ttl": {
        "Type": "Pass",
        "Parameters": {
            "ttl.$": "States.MathAdd($.ttl, -1)"
        },
        "ResultPath": "$.ttl",
        "Next": "get-status"
    },

... my json payload becomes and the execution eventually breaks.

{
    "foo": "bar",
    "ttl": {
        "ttl": 9
    },
    "status": "meh"
}

My question is: is it possible to use an intrinsic function to increase or decrease an int value and the result put it back into a property?

The problem I have is an object { "ttl": 9} is put back, not the value 9.

I tried using an OutputPath selector but it didn't work...


Solution

  • Here are two options that decrement the TTL while retaining the same shape as the input:

    Case #1: Input has a known and constant shape - 1 Pass

    A single Pass state works if your input keys are known and fixed. List all the input keys in the Parameters field explicitly:

    "decrement-ttl": {
        "Type": "Pass",
        "Parameters": {
            "ttl.$": "States.MathAdd($.ttl, -1)",
            "foo.$": "$.foo",
            },
        "Next": "get-status"
        },
    

    Case #2: Input has an unknown or variable shape - 2 Pass

    Case #1 won't work if your input keys are not known in advance or change between iterations. A generic solution requires two Pass states. The first decrements the counter. But it outputs the wrong shape: {ttl: {ttl: 0}}. The second Pass fixes the shape to match the input. It merges the TTL with the original payload using the JsonMerge intrinsic function.

    "decrement-ttl": {
        "Type": "Pass",
        "Parameters": {
            "ttl.$": "States.MathAdd($.ttl, -1)"
        },
        "ResultPath": "$.ttl",
        "Next": "merge"
    },
    
    "merge": {
        "Type": "Pass",
        "Parameters": {
        "merged.$": "States.JsonMerge($, $.ttl, false)"
        },
        "OutputPath": "$.merged",
        "Next": "get-status"
    }