@cumulus/message 9.7.0 → 9.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/StepFunctions.js CHANGED
@@ -14,7 +14,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
14
14
  var __importStar = (this && this.__importStar) || function (mod) {
15
15
  if (mod && mod.__esModule) return mod;
16
16
  var result = {};
17
- if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
17
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
18
  __setModuleDefault(result, mod);
19
19
  return result;
20
20
  };
@@ -22,20 +22,28 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
22
22
  return (mod && mod.__esModule) ? mod : { "default": mod };
23
23
  };
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.parseStepMessage = exports.pullStepFunctionEvent = void 0;
26
- /**
27
- * Utility functions for working with AWS Step Function events/messages
28
- * @module StepFunctions
29
- *
30
- * @example
31
- * const StepFunctions = require('@cumulus/message/StepFunctions');
32
- */
25
+ exports.getCumulusMessageFromExecutionEvent = exports.getFailedExecutionMessage = exports.lastFailedEventStep = exports.getFailedStepName = exports.parseStepMessage = exports.pullStepFunctionEvent = void 0;
33
26
  const jsonpath_plus_1 = require("jsonpath-plus");
27
+ const get_1 = __importDefault(require("lodash/get"));
28
+ const set_1 = __importDefault(require("lodash/set"));
29
+ const StepFunctions_1 = require("@cumulus/aws-client/StepFunctions");
30
+ const execution_history_1 = require("@cumulus/common/execution-history");
34
31
  const s3Utils = __importStar(require("@cumulus/aws-client/S3"));
35
32
  const logger_1 = __importDefault(require("@cumulus/logger"));
33
+ const Executions_1 = require("./Executions");
36
34
  const log = new logger_1.default({
37
35
  sender: '@cumulus/message/StepFunctions',
38
36
  });
37
+ const executionStatusToWorkflowStatus = (executionStatus) => {
38
+ const statusMap = {
39
+ ABORTED: 'failed',
40
+ FAILED: 'failed',
41
+ RUNNING: 'running',
42
+ SUCCEEDED: 'completed',
43
+ TIMED_OUT: 'failed',
44
+ };
45
+ return statusMap[executionStatus];
46
+ };
39
47
  /**
40
48
  * Given a Step Function event, replace specified key in event with contents
41
49
  * of S3 remote message
@@ -47,13 +55,13 @@ const log = new logger_1.default({
47
55
  * @async
48
56
  * @alias module:StepFunctions
49
57
  */
50
- exports.pullStepFunctionEvent = async (event) => {
58
+ const pullStepFunctionEvent = async (event) => {
51
59
  if (!event.replace)
52
60
  return event;
53
61
  const remoteMsg = await s3Utils.getJsonS3Object(event.replace.Bucket, event.replace.Key);
54
62
  let returnEvent = remoteMsg;
55
63
  if (event.replace.TargetPath) {
56
- const replaceNodeSearch = jsonpath_plus_1.JSONPath({
64
+ const replaceNodeSearch = (0, jsonpath_plus_1.JSONPath)({
57
65
  path: event.replace.TargetPath,
58
66
  json: event,
59
67
  resultType: 'all',
@@ -69,6 +77,7 @@ exports.pullStepFunctionEvent = async (event) => {
69
77
  }
70
78
  return returnEvent;
71
79
  };
80
+ exports.pullStepFunctionEvent = pullStepFunctionEvent;
72
81
  /**
73
82
  * Parse step message with CMA keys and replace specified key in event with contents
74
83
  * of S3 remote message
@@ -80,7 +89,7 @@ exports.pullStepFunctionEvent = async (event) => {
80
89
  * @async
81
90
  * @alias module:StepFunctions
82
91
  */
83
- exports.parseStepMessage = async (stepMessage, stepName) => {
92
+ const parseStepMessage = async (stepMessage, stepName) => {
84
93
  let parsedMessage;
85
94
  if (stepMessage.cma) {
86
95
  const flattenedMessage = { ...stepMessage, ...stepMessage.cma, ...stepMessage.cma.event };
@@ -94,8 +103,120 @@ exports.parseStepMessage = async (stepMessage, stepName) => {
94
103
  if (parsedMessage.replace) {
95
104
  // Message was too large and output was written to S3
96
105
  log.info(`Retrieving ${stepName} output from ${JSON.stringify(parsedMessage.replace)}`);
97
- parsedMessage = await exports.pullStepFunctionEvent(parsedMessage);
106
+ parsedMessage = await (0, exports.pullStepFunctionEvent)(parsedMessage);
98
107
  }
99
108
  return parsedMessage;
100
109
  };
110
+ exports.parseStepMessage = parseStepMessage;
111
+ /**
112
+ * Searches the Execution step History for the TaskStateEntered pertaining to
113
+ * the failed task Id. HistoryEvent ids are numbered sequentially, starting at
114
+ * one.
115
+ *
116
+ * @param {HistoryEvent[]} events - Step Function events array
117
+ * @param {HistoryEvent} failedStepEvent - Step Function's failed event.
118
+ * @returns {string} name of the current stepfunction task or 'UnknownFailedStepName'.
119
+ */
120
+ const getFailedStepName = (events, failedStepEvent) => {
121
+ var _a, _b;
122
+ try {
123
+ const previousEvents = events.slice(0, failedStepEvent.id - 1);
124
+ const startEvents = previousEvents.filter((e) => e.type === 'TaskStateEntered');
125
+ const stateName = (_b = (_a = startEvents.pop()) === null || _a === void 0 ? void 0 : _a.stateEnteredEventDetails) === null || _b === void 0 ? void 0 : _b.name;
126
+ if (!stateName)
127
+ throw new Error('TaskStateEntered Event Object did not have `stateEnteredEventDetails.name`');
128
+ return stateName;
129
+ }
130
+ catch (error) {
131
+ log.info('Failed to determine a failed stepName from execution events.');
132
+ log.error(error);
133
+ }
134
+ return 'UnknownFailedStepName';
135
+ };
136
+ exports.getFailedStepName = getFailedStepName;
137
+ /**
138
+ * Finds all failed execution events and returns the last one in the list.
139
+ *
140
+ * @param {Array<HistoryEventList>} events - array of AWS Stepfunction execution HistoryEvents
141
+ * @returns {HistoryEvent[]} - the last lambda or activity that failed in the
142
+ * event array, or an empty array.
143
+ */
144
+ const lastFailedEventStep = (events) => {
145
+ const failures = events.filter((event) => ['LambdaFunctionFailed', 'ActivityFailed'].includes(event.type));
146
+ return failures.pop();
147
+ };
148
+ exports.lastFailedEventStep = lastFailedEventStep;
149
+ /**
150
+ * Get message to use for publishing failed execution notifications.
151
+ *
152
+ * Try to get the input to the last failed step in the execution so we can
153
+ * update the status of any granules/PDRs that don't exist in the initial execution
154
+ * input.
155
+ *
156
+ * Falls back to overall execution input.
157
+ *
158
+ * @param {Object} inputCumulusMessage - Workflow execution input message
159
+ * @param {Function} getExecutionHistoryFunction - Testing override for mock/etc of
160
+ * StepFunctions.getExecutionHistory
161
+ * @returns {Object} - CumulusMessage Execution step message or execution input message
162
+ */
163
+ const getFailedExecutionMessage = async (inputCumulusMessage, getExecutionHistoryFunction = StepFunctions_1.getExecutionHistory) => {
164
+ const amendedCumulusMessage = { ...inputCumulusMessage };
165
+ try {
166
+ const executionArn = (0, Executions_1.getMessageExecutionArn)(amendedCumulusMessage);
167
+ if (!executionArn) {
168
+ throw new Error('No execution arn found');
169
+ }
170
+ const { events } = await getExecutionHistoryFunction({ executionArn });
171
+ const lastFailedEvent = (0, exports.lastFailedEventStep)(events);
172
+ if (!lastFailedEvent) {
173
+ log.warn(`No failed step events found in execution ${executionArn}`);
174
+ return amendedCumulusMessage;
175
+ }
176
+ const failedExecutionStepName = (0, exports.getFailedStepName)(events, lastFailedEvent);
177
+ const failedStepExitedEvent = (0, execution_history_1.getStepExitedEvent)(events, lastFailedEvent);
178
+ if (!failedStepExitedEvent) {
179
+ log.info(`Could not retrieve output from last failed step in execution ${executionArn}, falling back to execution input`);
180
+ log.info(`Could not find TaskStateExited event after step ID ${lastFailedEvent.id} for execution ${executionArn}`);
181
+ return {
182
+ ...amendedCumulusMessage,
183
+ exception: {
184
+ ...lastFailedEvent.activityFailedEventDetails,
185
+ ...lastFailedEvent.lambdaFunctionFailedEventDetails,
186
+ failedExecutionStepName,
187
+ },
188
+ };
189
+ }
190
+ const taskExitedEventOutput = JSON.parse((0, execution_history_1.getTaskExitedEventOutput)(failedStepExitedEvent) || '{}');
191
+ taskExitedEventOutput.exception = {
192
+ ...taskExitedEventOutput.exception,
193
+ failedExecutionStepName,
194
+ };
195
+ return await (0, exports.parseStepMessage)(taskExitedEventOutput, failedExecutionStepName);
196
+ }
197
+ catch (error) {
198
+ log.error('getFailedExecution failed to retrieve failure:', error);
199
+ return amendedCumulusMessage;
200
+ }
201
+ };
202
+ exports.getFailedExecutionMessage = getFailedExecutionMessage;
203
+ const getCumulusMessageFromExecutionEvent = async (executionEvent) => {
204
+ let cumulusMessage;
205
+ if (executionEvent.detail.status === 'RUNNING') {
206
+ cumulusMessage = JSON.parse(executionEvent.detail.input);
207
+ }
208
+ else if (executionEvent.detail.status === 'SUCCEEDED') {
209
+ cumulusMessage = JSON.parse(executionEvent.detail.output);
210
+ }
211
+ else {
212
+ const inputMessage = JSON.parse((0, get_1.default)(executionEvent, 'detail.input', '{}'));
213
+ cumulusMessage = await (0, exports.getFailedExecutionMessage)(inputMessage);
214
+ }
215
+ const fullCumulusMessage = (await (0, exports.pullStepFunctionEvent)(cumulusMessage));
216
+ const workflowStatus = executionStatusToWorkflowStatus(executionEvent.detail.status);
217
+ (0, set_1.default)(fullCumulusMessage, 'meta.status', workflowStatus);
218
+ (0, set_1.default)(fullCumulusMessage, 'cumulus_meta.workflow_stop_time', executionEvent.detail.stopDate);
219
+ return fullCumulusMessage;
220
+ };
221
+ exports.getCumulusMessageFromExecutionEvent = getCumulusMessageFromExecutionEvent;
101
222
  //# sourceMappingURL=StepFunctions.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"StepFunctions.js","sourceRoot":"","sources":["src/StepFunctions.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;AAEb;;;;;;GAMG;AAEH,iDAAyC;AACzC,gEAAkD;AAClD,6DAAqC;AAGrC,MAAM,GAAG,GAAG,IAAI,gBAAM,CAAC;IACrB,MAAM,EAAE,gCAAgC;CACzC,CAAC,CAAC;AAEH;;;;;;;;;;GAUG;AACU,QAAA,qBAAqB,GAAG,KAAK,EACxC,KAEC,EACiB,EAAE;IACpB,IAAI,CAAC,KAAK,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IAEjC,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,eAAe,CAC7C,KAAK,CAAC,OAAO,CAAC,MAAM,EACpB,KAAK,CAAC,OAAO,CAAC,GAAG,CAClB,CAAC;IAEF,IAAI,WAAW,GAAG,SAAS,CAAC;IAC5B,IAAI,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE;QAC5B,MAAM,iBAAiB,GAAG,wBAAQ,CAAC;YACjC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,UAAU;YAC9B,IAAI,EAAE,KAAK;YACX,UAAU,EAAE,KAAK;SAClB,CAAC,CAAC;QACH,IAAI,iBAAiB,CAAC,MAAM,KAAK,CAAC,EAAE;YAClC,MAAM,IAAI,KAAK,CAAC,0BAA0B,KAAK,CAAC,OAAO,CAAC,UAAU,UAAU,CAAC,CAAC;SAC/E;QACD,IAAI,iBAAiB,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE;YAC/B,iBAAiB,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;YAC7E,WAAW,GAAG,KAAK,CAAC;YACpB,OAAO,WAAW,CAAC,OAAO,CAAC;SAC5B;KACF;IACD,OAAO,WAAW,CAAC;AACrB,CAAC,CAAC;AAEF;;;;;;;;;;GAUG;AACU,QAAA,gBAAgB,GAAG,KAAK,EACnC,WAA+B,EAC/B,QAAgB,EAChB,EAAE;IACF,IAAI,aAAa,CAAC;IAClB,IAAI,WAAW,CAAC,GAAG,EAAE;QACnB,MAAM,gBAAgB,GAAG,EAAE,GAAG,WAAW,EAAE,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,WAAW,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;QAC1F,OAAO,gBAAgB,CAAC,GAAG,CAAC;QAC5B,OAAO,gBAAgB,CAAC,KAAK,CAAC;QAC9B,aAAa,GAAG,gBAAgB,CAAC;KAClC;SAAM;QACL,aAAa,GAAG,WAAW,CAAC;KAC7B;IAED,IAAI,aAAa,CAAC,OAAO,EAAE;QACzB,qDAAqD;QACrD,GAAG,CAAC,IAAI,CAAC,cAAc,QAAQ,gBAAgB,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACxF,aAAa,GAAG,MAAM,6BAAqB,CAAC,aAAa,CAAC,CAAC;KAC5D;IACD,OAA+B,aAAa,CAAC;AAC/C,CAAC,CAAC"}
1
+ {"version":3,"file":"StepFunctions.js","sourceRoot":"","sources":["src/StepFunctions.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cumulus/message",
3
- "version": "9.7.0",
3
+ "version": "9.9.1",
4
4
  "description": "Utilities for building and parsing Cumulus messages",
5
5
  "keywords": [
6
6
  "GIBS",
@@ -38,11 +38,11 @@
38
38
  "author": "Cumulus Authors",
39
39
  "license": "Apache-2.0",
40
40
  "dependencies": {
41
- "@cumulus/aws-client": "9.7.0",
42
- "@cumulus/common": "9.7.0",
43
- "@cumulus/errors": "9.7.0",
44
- "@cumulus/logger": "9.7.0",
45
- "@cumulus/types": "9.7.0",
41
+ "@cumulus/aws-client": "9.9.1",
42
+ "@cumulus/common": "9.9.1",
43
+ "@cumulus/errors": "9.9.1",
44
+ "@cumulus/logger": "9.9.1",
45
+ "@cumulus/types": "9.9.1",
46
46
  "jsonpath-plus": "^3.0.0",
47
47
  "lodash": "^4.17.20",
48
48
  "uuid": "^8.2.0"
@@ -50,5 +50,5 @@
50
50
  "devDependencies": {
51
51
  "@types/uuid": "^8.0.0"
52
52
  },
53
- "gitHead": "08fb2c1d9113e7be465316f950d4ac9272bd3141"
53
+ "gitHead": "7a6a8ce08bf67e5174c1523b4af2e00e1a3c58cc"
54
54
  }
package/src/Build.ts CHANGED
@@ -10,9 +10,9 @@
10
10
  */
11
11
 
12
12
  import merge from 'lodash/merge';
13
+
13
14
  import { Message } from '@cumulus/types';
14
15
  import { v4 as uuidv4 } from 'uuid';
15
-
16
16
  import {
17
17
  WorkflowMessageTemplate,
18
18
  WorkflowMessageTemplateCumulusMeta,
@@ -0,0 +1,54 @@
1
+ import { SQSRecord, EventBridgeEvent } from 'aws-lambda';
2
+
3
+ import { parseSQSMessageBody } from '@cumulus/aws-client/SQS';
4
+ import { CumulusMessage } from '@cumulus/types/message';
5
+ import Logger from '@cumulus/logger';
6
+
7
+ import { getCumulusMessageFromExecutionEvent } from './StepFunctions';
8
+
9
+ const log = new Logger({ sender: '@cumulus/DeadLetterMessage' });
10
+
11
+ type StepFunctionEventBridgeEvent = EventBridgeEvent<'Step Functions Execution Status Change', { [key: string]: string }>;
12
+ type UnwrapDeadLetterCumulusMessageReturnType = (
13
+ StepFunctionEventBridgeEvent
14
+ | AWS.SQS.Message
15
+ | SQSRecord
16
+ | CumulusMessage
17
+ );
18
+
19
+ /**
20
+ * Unwrap dead letter Cumulus message, which may be wrapped in a
21
+ * States cloudwatch event, which is wrapped in an SQS message.
22
+ *
23
+ * @param {Object} messageBody - received SQS message
24
+ * @returns {Object} the cumulus message or nearest available object
25
+ */
26
+ export const unwrapDeadLetterCumulusMessage = async (
27
+ messageBody: UnwrapDeadLetterCumulusMessageReturnType
28
+ ): Promise<UnwrapDeadLetterCumulusMessageReturnType> => {
29
+ try {
30
+ if ('cumulus_meta' in messageBody) {
31
+ return messageBody;
32
+ }
33
+ if ('Body' in messageBody || 'body' in messageBody) {
34
+ // AWS.SQS.Message/SQS.Record case
35
+ const unwrappedMessageBody = parseSQSMessageBody(
36
+ messageBody
37
+ ) as CumulusMessage;
38
+ return await unwrapDeadLetterCumulusMessage(unwrappedMessageBody);
39
+ }
40
+ if (!('detail' in messageBody)) {
41
+ // Non-typed catchall
42
+ return messageBody;
43
+ }
44
+ // StepFunctionEventBridgeEvent case
45
+ const unwrappedMessageBody = await getCumulusMessageFromExecutionEvent(messageBody);
46
+ return await unwrapDeadLetterCumulusMessage(unwrappedMessageBody);
47
+ } catch (error) {
48
+ log.error(
49
+ 'Falling back to storing wrapped message after encountering unwrap error',
50
+ error
51
+ );
52
+ return messageBody;
53
+ }
54
+ };
@@ -8,14 +8,41 @@
8
8
  * const StepFunctions = require('@cumulus/message/StepFunctions');
9
9
  */
10
10
 
11
+ import { EventBridgeEvent } from 'aws-lambda';
11
12
  import { JSONPath } from 'jsonpath-plus';
13
+ import get from 'lodash/get';
14
+ import set from 'lodash/set';
15
+
16
+ import { getExecutionHistory } from '@cumulus/aws-client/StepFunctions';
17
+ import { getStepExitedEvent, getTaskExitedEventOutput } from '@cumulus/common/execution-history';
18
+ import { Message } from '@cumulus/types';
12
19
  import * as s3Utils from '@cumulus/aws-client/S3';
13
20
  import Logger from '@cumulus/logger';
14
- import { Message } from '@cumulus/types';
21
+
22
+ import { getMessageExecutionArn } from './Executions';
15
23
 
16
24
  const log = new Logger({
17
25
  sender: '@cumulus/message/StepFunctions',
18
26
  });
27
+ type ExecutionStatus = ('ABORTED' | 'RUNNING' | 'TIMED_OUT' | 'SUCCEEDED' | 'FAILED');
28
+
29
+ type ExecutionStatusToWorkflowStatusMap = {
30
+ [K in ExecutionStatus]: Message.WorkflowStatus;
31
+ };
32
+
33
+ const executionStatusToWorkflowStatus = (
34
+ executionStatus: ExecutionStatus
35
+ ): Message.WorkflowStatus => {
36
+ const statusMap: ExecutionStatusToWorkflowStatusMap = {
37
+ ABORTED: 'failed',
38
+ FAILED: 'failed',
39
+ RUNNING: 'running',
40
+ SUCCEEDED: 'completed',
41
+ TIMED_OUT: 'failed',
42
+ };
43
+
44
+ return statusMap[executionStatus];
45
+ };
19
46
 
20
47
  /**
21
48
  * Given a Step Function event, replace specified key in event with contents
@@ -91,3 +118,133 @@ export const parseStepMessage = async (
91
118
  }
92
119
  return <Message.CumulusMessage>parsedMessage;
93
120
  };
121
+
122
+ /**
123
+ * Searches the Execution step History for the TaskStateEntered pertaining to
124
+ * the failed task Id. HistoryEvent ids are numbered sequentially, starting at
125
+ * one.
126
+ *
127
+ * @param {HistoryEvent[]} events - Step Function events array
128
+ * @param {HistoryEvent} failedStepEvent - Step Function's failed event.
129
+ * @returns {string} name of the current stepfunction task or 'UnknownFailedStepName'.
130
+ */
131
+ export const getFailedStepName = (
132
+ events: AWS.StepFunctions.HistoryEvent[],
133
+ failedStepEvent: { id: number }
134
+ ) => {
135
+ try {
136
+ const previousEvents = events.slice(0, failedStepEvent.id - 1);
137
+ const startEvents = previousEvents.filter(
138
+ (e) => e.type === 'TaskStateEntered'
139
+ );
140
+ const stateName = startEvents.pop()?.stateEnteredEventDetails?.name;
141
+ if (!stateName) throw new Error('TaskStateEntered Event Object did not have `stateEnteredEventDetails.name`');
142
+ return stateName;
143
+ } catch (error) {
144
+ log.info('Failed to determine a failed stepName from execution events.');
145
+ log.error(error);
146
+ }
147
+ return 'UnknownFailedStepName';
148
+ };
149
+
150
+ /**
151
+ * Finds all failed execution events and returns the last one in the list.
152
+ *
153
+ * @param {Array<HistoryEventList>} events - array of AWS Stepfunction execution HistoryEvents
154
+ * @returns {HistoryEvent[]} - the last lambda or activity that failed in the
155
+ * event array, or an empty array.
156
+ */
157
+ export const lastFailedEventStep = (
158
+ events: AWS.StepFunctions.HistoryEvent[]
159
+ ): AWS.StepFunctions.HistoryEvent | undefined => {
160
+ const failures = events.filter((event) =>
161
+ ['LambdaFunctionFailed', 'ActivityFailed'].includes(event.type));
162
+ return failures.pop();
163
+ };
164
+
165
+ /**
166
+ * Get message to use for publishing failed execution notifications.
167
+ *
168
+ * Try to get the input to the last failed step in the execution so we can
169
+ * update the status of any granules/PDRs that don't exist in the initial execution
170
+ * input.
171
+ *
172
+ * Falls back to overall execution input.
173
+ *
174
+ * @param {Object} inputCumulusMessage - Workflow execution input message
175
+ * @param {Function} getExecutionHistoryFunction - Testing override for mock/etc of
176
+ * StepFunctions.getExecutionHistory
177
+ * @returns {Object} - CumulusMessage Execution step message or execution input message
178
+ */
179
+ export const getFailedExecutionMessage = async (
180
+ inputCumulusMessage: Message.CumulusMessage,
181
+ getExecutionHistoryFunction: typeof getExecutionHistory = getExecutionHistory
182
+ ) => {
183
+ const amendedCumulusMessage = { ...inputCumulusMessage };
184
+
185
+ try {
186
+ const executionArn = getMessageExecutionArn(amendedCumulusMessage);
187
+ if (!executionArn) { throw new Error('No execution arn found'); }
188
+ const { events } = await getExecutionHistoryFunction({ executionArn });
189
+
190
+ const lastFailedEvent = lastFailedEventStep(events);
191
+ if (!lastFailedEvent) {
192
+ log.warn(`No failed step events found in execution ${executionArn}`);
193
+ return amendedCumulusMessage;
194
+ }
195
+ const failedExecutionStepName = getFailedStepName(events, lastFailedEvent);
196
+ const failedStepExitedEvent = getStepExitedEvent(events, lastFailedEvent);
197
+
198
+ if (!failedStepExitedEvent) {
199
+ log.info(`Could not retrieve output from last failed step in execution ${executionArn}, falling back to execution input`);
200
+ log.info(`Could not find TaskStateExited event after step ID ${lastFailedEvent.id} for execution ${executionArn}`);
201
+ return {
202
+ ...amendedCumulusMessage,
203
+ exception: {
204
+ ...lastFailedEvent.activityFailedEventDetails,
205
+ ...lastFailedEvent.lambdaFunctionFailedEventDetails,
206
+ failedExecutionStepName,
207
+
208
+ },
209
+ };
210
+ }
211
+ const taskExitedEventOutput = JSON.parse(getTaskExitedEventOutput(failedStepExitedEvent) || '{}');
212
+ taskExitedEventOutput.exception = {
213
+ ...taskExitedEventOutput.exception,
214
+ failedExecutionStepName,
215
+ };
216
+ return await parseStepMessage(taskExitedEventOutput, failedExecutionStepName);
217
+ } catch (error) {
218
+ log.error('getFailedExecution failed to retrieve failure:', error);
219
+ return amendedCumulusMessage;
220
+ }
221
+ };
222
+
223
+ export const getCumulusMessageFromExecutionEvent = async (executionEvent: EventBridgeEvent<'Step Functions Execution Status Change', { [key: string]: string }>) => {
224
+ let cumulusMessage;
225
+ if (executionEvent.detail.status === 'RUNNING') {
226
+ cumulusMessage = JSON.parse(executionEvent.detail.input);
227
+ } else if (executionEvent.detail.status === 'SUCCEEDED') {
228
+ cumulusMessage = JSON.parse(executionEvent.detail.output);
229
+ } else {
230
+ const inputMessage = JSON.parse(get(executionEvent, 'detail.input', '{}'));
231
+ cumulusMessage = await getFailedExecutionMessage(inputMessage);
232
+ }
233
+
234
+ const fullCumulusMessage = (await pullStepFunctionEvent(
235
+ cumulusMessage
236
+ )) as Message.CumulusMessage;
237
+
238
+ const workflowStatus = executionStatusToWorkflowStatus(
239
+ executionEvent.detail.status as ExecutionStatus
240
+ );
241
+ set(fullCumulusMessage, 'meta.status', workflowStatus);
242
+
243
+ set(
244
+ fullCumulusMessage,
245
+ 'cumulus_meta.workflow_stop_time',
246
+ executionEvent.detail.stopDate
247
+ );
248
+
249
+ return fullCumulusMessage;
250
+ };