ec2-instance-running-scheduler 0.1.9 → 0.2.0
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/.jsii +109 -35
- package/API.md +77 -7
- package/README.md +39 -12
- package/assets/funcs/running-scheduler.lambda/index.js +134 -13
- package/assets/funcs/running-scheduler.lambda/index.js.map +4 -4
- package/lib/constructs/ec2-instance-running-scheduler.d.ts +31 -1
- package/lib/constructs/ec2-instance-running-scheduler.js +10 -3
- package/lib/funcs/running-scheduler-polling-config.d.ts +17 -0
- package/lib/funcs/running-scheduler-polling-config.js +21 -0
- package/lib/funcs/running-scheduler-polling-env.d.ts +8 -0
- package/lib/funcs/running-scheduler-polling-env.js +53 -0
- package/lib/funcs/running-scheduler-predicates.d.ts +56 -0
- package/lib/funcs/running-scheduler-predicates.js +55 -2
- package/lib/funcs/running-scheduler.lambda.d.ts +12 -3
- package/lib/funcs/running-scheduler.lambda.js +53 -11
- package/lib/index.d.ts +3 -1
- package/lib/index.js +4 -2
- package/lib/stacks/ec2-instance-running-schedule-stack.d.ts +6 -4
- package/lib/stacks/ec2-instance-running-schedule-stack.js +6 -6
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,22 +4,23 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/ec2-instance-running-scheduler)
|
|
5
5
|
[](https://github.com/gammarers-aws-cdk-constructs/ec2-instance-running-scheduler/actions/workflows/release.yml)
|
|
6
6
|
[](https://github.com/gammarers-aws-cdk-constructs/ec2-instance-running-scheduler/releases)
|
|
7
|
-
|
|
8
7
|
[](https://constructs.dev/packages/ec2-instance-running-scheduler)
|
|
9
8
|
|
|
10
|
-
AWS CDK construct that starts and stops EC2 instances on a cron schedule using **EventBridge Scheduler** and a **Durable Execution Lambda**. The handler discovers instances with the **Resource Groups Tagging API**,
|
|
9
|
+
AWS CDK construct library that starts and stops EC2 instances on a cron schedule using **EventBridge Scheduler** and a **Durable Execution Lambda**. The handler discovers instances with the **Resource Groups Tagging API**, issues start/stop, **polls until each instance reaches a stable target state** (durable `step` / `wait`), processes **multiple instances in parallel** (bounded concurrency), and posts **Slack** summary and per-instance thread messages using a secret from **Secrets Manager**. The Lambda emits **structured application logs** alongside JSON platform logs.
|
|
11
10
|
|
|
12
11
|
## Features
|
|
13
12
|
|
|
14
13
|
- **Tag-based targeting** – Select EC2 instances by tag key and values (e.g. `Schedule` / `YES`) via `tag:GetResources`.
|
|
15
14
|
- **EventBridge Scheduler** – Separate cron rules for start and stop, with per-rule timezone (`aws-cdk-lib` `TimeZone`).
|
|
16
15
|
- **Durable Lambda** – One Lambda with AWS Lambda Durable Execution (`step`, `wait`, `map`, child contexts per instance) for long-running workflows without Step Functions.
|
|
17
|
-
- **Stable-state polling** – After start/stop, the function waits and re-describes instances until `running` (start mode) or `stopped` (stop mode)
|
|
18
|
-
- **
|
|
19
|
-
- **
|
|
16
|
+
- **Stable-state polling** – After start/stop, the function waits (20 seconds between attempts) and re-describes instances until `running` (start mode) or `stopped` (stop mode).
|
|
17
|
+
- **Configurable polling limits** – Per-instance **max loop count** and **max elapsed time** via `resourcePolling` (default: 90 loops / 1800 seconds). Failures use explicit `ResourcePollingFailed:*` messages instead of running until the Durable execution timeout (construct default: 2 hours).
|
|
18
|
+
- **Validated environment variables** – The bundled handler parses required and polling env vars at invocation using strict decimal integer rules; polling limits must be **positive integers** (`> 0`).
|
|
19
|
+
- **Slack notifications** – Parent message plus threaded updates per instance; credentials from Secrets Manager JSON (`token`, `channel`). The construct sets **`SLACK_SECRET_NAME`** on the function.
|
|
20
|
+
- **Structured logging** – Durable execution **`ctx.logger`** for traceable JSON application logs (invocation, describe/start/stop/wait loops, polling limit errors, Slack steps, completion).
|
|
20
21
|
- **Scheduling toggle** – Enable or disable both schedules without removing the stack (`enableScheduling`).
|
|
21
22
|
- **Configurable schedules** – Optional cron overrides for start and stop (`minute`, `hour`, `week`, `timezone`); sensible defaults if omitted.
|
|
22
|
-
- **IAM and observability** – EC2 and tagging API permissions, Slack secret read grant, **Parameters and Secrets Lambda Extension
|
|
23
|
+
- **IAM and observability** – EC2 and tagging API permissions, Slack secret read grant, **Parameters and Secrets Lambda Extension**, JSON logging, and a dedicated log group (construct defaults).
|
|
23
24
|
|
|
24
25
|
## Installation
|
|
25
26
|
|
|
@@ -74,10 +75,14 @@ new EC2InstanceRunningScheduler(stack, 'EC2InstanceRunningScheduler', {
|
|
|
74
75
|
week: 'MON-FRI',
|
|
75
76
|
},
|
|
76
77
|
enableScheduling: true,
|
|
78
|
+
resourcePolling: {
|
|
79
|
+
maxLoopCount: 120,
|
|
80
|
+
maxElapsedSeconds: 3600,
|
|
81
|
+
},
|
|
77
82
|
});
|
|
78
83
|
```
|
|
79
84
|
|
|
80
|
-
Use the **stack** `EC2InstanceRunningScheduleStack` when deploying the scheduler as its own stack. It accepts the
|
|
85
|
+
Use the **stack** `EC2InstanceRunningScheduleStack` when deploying the scheduler as its own stack. It accepts the same **targeting, schedules, secrets, and enable flag** as the construct (plus standard `StackProps` such as `env`). For **`resourcePolling`**, use the construct directly or extend the stack in your app.
|
|
81
86
|
|
|
82
87
|
```typescript
|
|
83
88
|
import * as cdk from 'aws-cdk-lib';
|
|
@@ -110,19 +115,32 @@ new EC2InstanceRunningScheduleStack(app, 'EC2InstanceRunningScheduleStack', {
|
|
|
110
115
|
});
|
|
111
116
|
```
|
|
112
117
|
|
|
113
|
-
EventBridge Scheduler invokes the Lambda with `Params.TagKey`, `Params.TagValues`, and `Params.Mode` (`Start` or `Stop`); the construct wires this for you. The function environment includes
|
|
118
|
+
EventBridge Scheduler invokes the Lambda with `Params.TagKey`, `Params.TagValues`, and `Params.Mode` (`Start` or `Stop`); the construct wires this for you. The function environment includes:
|
|
119
|
+
|
|
120
|
+
| Variable | Source | Purpose |
|
|
121
|
+
|----------|--------|---------|
|
|
122
|
+
| `SLACK_SECRET_NAME` | `secrets.slackSecretName` | Secrets Manager secret for Slack (required) |
|
|
123
|
+
| `PROCESS_RESOURCE_MAX_LOOP_COUNT` | `resourcePolling.maxLoopCount` (default `90`) | Max describe/poll iterations per instance |
|
|
124
|
+
| `PROCESS_RESOURCE_MAX_ELAPSED_SECONDS` | `resourcePolling.maxElapsedSeconds` (default `1800`) | Max wall-clock seconds polling one instance |
|
|
125
|
+
|
|
126
|
+
When you set polling limits via `resourcePolling`, the construct writes them as decimal integer strings. If you override these variables manually, each value must be a **strict decimal integer** (e.g. `"120"` is valid; `"0x10"`, `"3.14"`, or `"10abc"` are rejected) and **greater than zero**. Invalid or missing `SLACK_SECRET_NAME` causes the handler to fail at the start of an invocation.
|
|
114
127
|
|
|
115
128
|
## Options
|
|
116
129
|
|
|
117
|
-
|
|
130
|
+
### EC2InstanceRunningScheduler
|
|
118
131
|
|
|
119
132
|
| Option | Type | Required | Description |
|
|
120
133
|
|--------|------|----------|-------------|
|
|
121
134
|
| `targetResource` | `TargetResource` | Yes | Tag key and values used to select EC2 instances. |
|
|
122
|
-
| `secrets` | `Secrets` | Yes |
|
|
135
|
+
| `secrets` | `Secrets` | Yes | Secrets Manager secret for Slack (`slackSecretName`). |
|
|
123
136
|
| `startSchedule` | `Schedule` | No | Cron for starting instances (default: `50 7 ? * MON-FRI *` in `Etc/UTC`). |
|
|
124
137
|
| `stopSchedule` | `Schedule` | No | Cron for stopping instances (default: `5 19 ? * MON-FRI *` in `Etc/UTC`). |
|
|
125
138
|
| `enableScheduling` | `boolean` | No | Whether both scheduler rules are enabled (default: `true`). |
|
|
139
|
+
| `resourcePolling` | `ResourcePollingLimits` | No | Per-instance polling caps (see below). |
|
|
140
|
+
|
|
141
|
+
### EC2InstanceRunningScheduleStack
|
|
142
|
+
|
|
143
|
+
Includes `targetResource`, `secrets`, `startSchedule`, `stopSchedule`, `enableScheduling`, and standard `StackProps`. Does **not** expose `resourcePolling`; use `EC2InstanceRunningScheduler` when you need custom polling limits.
|
|
126
144
|
|
|
127
145
|
### TargetResource
|
|
128
146
|
|
|
@@ -138,13 +156,22 @@ These options apply to **`EC2InstanceRunningScheduler`** and to **`EC2InstanceRu
|
|
|
138
156
|
|
|
139
157
|
### Secrets
|
|
140
158
|
|
|
141
|
-
- `slackSecretName` – Name of the AWS Secrets Manager secret. The Lambda expects JSON with **`token`** (Slack bot token) and **`channel`** (channel ID or name for `chat.postMessage`).
|
|
159
|
+
- `slackSecretName` – Name of the AWS Secrets Manager secret. The Lambda expects JSON with **`token`** (Slack bot token) and **`channel`** (channel ID or name for `chat.postMessage`).
|
|
160
|
+
|
|
161
|
+
### ResourcePollingLimits
|
|
162
|
+
|
|
163
|
+
Written to `PROCESS_RESOURCE_MAX_LOOP_COUNT` and `PROCESS_RESOURCE_MAX_ELAPSED_SECONDS` on the running scheduler Lambda.
|
|
164
|
+
|
|
165
|
+
- `maxLoopCount` – Maximum describe/poll loop iterations per instance (default: **90**). Must be a positive integer when set.
|
|
166
|
+
- `maxElapsedSeconds` – Maximum wall-clock seconds spent polling one instance (default: **1800**, 30 minutes). Must be a positive integer when set.
|
|
167
|
+
|
|
168
|
+
When a limit is exceeded during polling, the handler throws an error with prefix `ResourcePollingFailed:` (`MaxLoopCountExceeded`, `MaxElapsedTimeExceeded`, or `UnexpectedInstanceState` for unknown EC2 states).
|
|
142
169
|
|
|
143
170
|
## Requirements
|
|
144
171
|
|
|
145
172
|
- **Node.js** ≥ 20.0.0 (for developing or synthesizing CDK apps that depend on this package).
|
|
146
173
|
- **aws-cdk-lib** ^2.232.0 and **constructs** ^10.5.1 (peer dependencies).
|
|
147
|
-
- **AWS** – EventBridge Scheduler; Lambda with **Durable Execution** (
|
|
174
|
+
- **AWS** – EventBridge Scheduler; Lambda with **Durable Execution** (Node.js **24.x** runtime in the construct; Durable Execution requires a supported Node.js runtime in your region), a **live alias**, **Parameters and Secrets Lambda Extension**; EC2 (`DescribeInstances`, `StartInstances`, `StopInstances`); Resource Groups Tagging API (`tag:GetResources`); Secrets Manager. The deployed function uses **arm64**, Durable Execution IAM policies, a 2-hour Durable execution timeout (construct default), and a bundled handler that loads secrets via **aws-lambda-secret-fetcher**.
|
|
148
175
|
|
|
149
176
|
## License
|
|
150
177
|
|
|
@@ -20017,7 +20017,22 @@ var require_lib3 = __commonJS({
|
|
|
20017
20017
|
"use strict";
|
|
20018
20018
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
20019
20019
|
exports2.SafeEnvGetter = exports2.SafeEnvType = exports2.SafeEnvGetterValidationError = exports2.SafeEnvGetterError = void 0;
|
|
20020
|
+
var STRICT_INTEGER_PATTERN = /^-?\d+$/;
|
|
20021
|
+
var parseStrictInteger = (raw) => {
|
|
20022
|
+
const trimmed = raw.trim();
|
|
20023
|
+
if (trimmed === "" || !STRICT_INTEGER_PATTERN.test(trimmed)) {
|
|
20024
|
+
return void 0;
|
|
20025
|
+
}
|
|
20026
|
+
const n = Number(trimmed);
|
|
20027
|
+
if (!Number.isFinite(n) || !Number.isInteger(n)) {
|
|
20028
|
+
return void 0;
|
|
20029
|
+
}
|
|
20030
|
+
return n;
|
|
20031
|
+
};
|
|
20020
20032
|
var SafeEnvGetterError = class extends Error {
|
|
20033
|
+
/**
|
|
20034
|
+
* @param message - Error message.
|
|
20035
|
+
*/
|
|
20021
20036
|
constructor(message) {
|
|
20022
20037
|
super(message);
|
|
20023
20038
|
this.name = "SafeEnvGetterError";
|
|
@@ -20027,6 +20042,10 @@ var require_lib3 = __commonJS({
|
|
|
20027
20042
|
var SafeEnvGetterValidationError = class _SafeEnvGetterValidationError extends SafeEnvGetterError {
|
|
20028
20043
|
/**
|
|
20029
20044
|
* Formats validation errors into a human-readable error message.
|
|
20045
|
+
*
|
|
20046
|
+
* @template K - Environment variable key type.
|
|
20047
|
+
* @param errors - Validation error entries to format.
|
|
20048
|
+
* @returns Multi-line summary listing each key and message.
|
|
20030
20049
|
*/
|
|
20031
20050
|
static format(errors) {
|
|
20032
20051
|
const lines = errors.map((e) => `- ${e.key}: ${e.message}${e.raw == null ? "" : ` (raw="${e.raw}")`}`);
|
|
@@ -20035,6 +20054,8 @@ ${lines.join("\n")}`;
|
|
|
20035
20054
|
}
|
|
20036
20055
|
/**
|
|
20037
20056
|
* Creates a new validation error from one or more `SafeEnvError` entries.
|
|
20057
|
+
*
|
|
20058
|
+
* @param errors - One or more structured validation errors.
|
|
20038
20059
|
*/
|
|
20039
20060
|
constructor(errors) {
|
|
20040
20061
|
const msg = _SafeEnvGetterValidationError.format(errors);
|
|
@@ -20046,9 +20067,12 @@ ${lines.join("\n")}`;
|
|
|
20046
20067
|
};
|
|
20047
20068
|
exports2.SafeEnvGetterValidationError = SafeEnvGetterValidationError;
|
|
20048
20069
|
exports2.SafeEnvType = {
|
|
20049
|
-
/** Spec for a string value. */
|
|
20070
|
+
/** Spec for a string value (returned as-is). */
|
|
20050
20071
|
String: { type: "string" },
|
|
20051
|
-
/**
|
|
20072
|
+
/**
|
|
20073
|
+
* Spec for a strict decimal integer.
|
|
20074
|
+
* Rejects whitespace-only, hex, `Infinity`, `NaN`, and non-integer forms.
|
|
20075
|
+
*/
|
|
20052
20076
|
Number: { type: "number" },
|
|
20053
20077
|
/** Spec for a boolean value (1/true/yes/on → true). */
|
|
20054
20078
|
Boolean: { type: "boolean" },
|
|
@@ -20073,8 +20097,8 @@ ${lines.join("\n")}`;
|
|
|
20073
20097
|
}
|
|
20074
20098
|
switch (spec.type) {
|
|
20075
20099
|
case "number": {
|
|
20076
|
-
const n =
|
|
20077
|
-
if (
|
|
20100
|
+
const n = parseStrictInteger(raw);
|
|
20101
|
+
if (n === void 0) {
|
|
20078
20102
|
throw new SafeEnvGetterValidationError([
|
|
20079
20103
|
{ key, message: `Env ${key}: expected number, got "${raw}"`, raw, kind: "invalid_number" }
|
|
20080
20104
|
]);
|
|
@@ -20113,8 +20137,8 @@ ${lines.join("\n")}`;
|
|
|
20113
20137
|
continue;
|
|
20114
20138
|
}
|
|
20115
20139
|
if (spec.type === "number") {
|
|
20116
|
-
const n =
|
|
20117
|
-
if (
|
|
20140
|
+
const n = parseStrictInteger(raw);
|
|
20141
|
+
if (n === void 0) {
|
|
20118
20142
|
errors.push({ key, message: `Env ${key}: expected number, got "${raw}"`, raw, kind: "invalid_number" });
|
|
20119
20143
|
continue;
|
|
20120
20144
|
}
|
|
@@ -20145,7 +20169,9 @@ ${lines.join("\n")}`;
|
|
|
20145
20169
|
return envs;
|
|
20146
20170
|
};
|
|
20147
20171
|
exports2.SafeEnvGetter = {
|
|
20172
|
+
/** Reads and parses a single environment variable. See {@link getEnv}. */
|
|
20148
20173
|
getEnv,
|
|
20174
|
+
/** Reads and parses multiple environment variables. See {@link getEnvs}. */
|
|
20149
20175
|
getEnvs
|
|
20150
20176
|
};
|
|
20151
20177
|
}
|
|
@@ -23677,10 +23703,73 @@ var import_client_ec2 = require("@aws-sdk/client-ec2");
|
|
|
23677
23703
|
var import_client_resource_groups_tagging_api = require("@aws-sdk/client-resource-groups-tagging-api");
|
|
23678
23704
|
var import_web_api = __toESM(require_dist5());
|
|
23679
23705
|
var import_aws_lambda_secret_fetcher = __toESM(require_lib2());
|
|
23706
|
+
var import_safe_env_getter2 = __toESM(require_lib3());
|
|
23707
|
+
|
|
23708
|
+
// src/funcs/running-scheduler-polling-env.ts
|
|
23680
23709
|
var import_safe_env_getter = __toESM(require_lib3());
|
|
23681
23710
|
|
|
23711
|
+
// src/funcs/running-scheduler-polling-config.ts
|
|
23712
|
+
var PROCESS_RESOURCE_MAX_LOOP_COUNT_ENV = "PROCESS_RESOURCE_MAX_LOOP_COUNT";
|
|
23713
|
+
var PROCESS_RESOURCE_MAX_ELAPSED_SECONDS_ENV = "PROCESS_RESOURCE_MAX_ELAPSED_SECONDS";
|
|
23714
|
+
|
|
23682
23715
|
// src/funcs/running-scheduler-predicates.ts
|
|
23716
|
+
var DEFAULT_RESOURCE_POLLING_LIMITS = {
|
|
23717
|
+
maxLoopCount: 90,
|
|
23718
|
+
maxElapsedSeconds: 1800
|
|
23719
|
+
};
|
|
23683
23720
|
var isDesiredStableState = (mode, currentState) => mode === "Start" && currentState === "running" || mode === "Stop" && currentState === "stopped";
|
|
23721
|
+
var isTransitioningState = (mode, currentState) => mode === "Start" && currentState === "pending" || mode === "Stop" && (currentState === "stopping" || currentState === "shutting-down");
|
|
23722
|
+
var getPollingAbortReason = (loopCount, startedAtMs, nowMs, limits) => {
|
|
23723
|
+
if (loopCount >= limits.maxLoopCount) {
|
|
23724
|
+
return "MaxLoopCountExceeded";
|
|
23725
|
+
}
|
|
23726
|
+
const elapsedSeconds = (nowMs - startedAtMs) / 1e3;
|
|
23727
|
+
if (elapsedSeconds >= limits.maxElapsedSeconds) {
|
|
23728
|
+
return "MaxElapsedTimeExceeded";
|
|
23729
|
+
}
|
|
23730
|
+
return void 0;
|
|
23731
|
+
};
|
|
23732
|
+
var formatResourcePollingFailure = (reason, context) => {
|
|
23733
|
+
const common = `identifier=${context.identifier} mode=${context.mode} currentState=${context.currentState} loopCount=${context.loopCount}`;
|
|
23734
|
+
if (reason === "MaxLoopCountExceeded") {
|
|
23735
|
+
return `ResourcePollingFailed:MaxLoopCountExceeded: ${common} maxLoopCount=${context.limits.maxLoopCount}`;
|
|
23736
|
+
}
|
|
23737
|
+
if (reason === "MaxElapsedTimeExceeded") {
|
|
23738
|
+
return `ResourcePollingFailed:MaxElapsedTimeExceeded: ${common} maxElapsedSeconds=${context.limits.maxElapsedSeconds}`;
|
|
23739
|
+
}
|
|
23740
|
+
return `ResourcePollingFailed:UnexpectedInstanceState: ${common}`;
|
|
23741
|
+
};
|
|
23742
|
+
|
|
23743
|
+
// src/funcs/running-scheduler-polling-env.ts
|
|
23744
|
+
var assertPositiveEnvInt = (key, value) => {
|
|
23745
|
+
if (value <= 0) {
|
|
23746
|
+
const raw = process.env[key];
|
|
23747
|
+
throw new Error(`Invalid ${key}: must be a positive integer (got "${raw ?? ""}")`);
|
|
23748
|
+
}
|
|
23749
|
+
return value;
|
|
23750
|
+
};
|
|
23751
|
+
var parseResourcePollingLimitsFromEnv = () => {
|
|
23752
|
+
const parsed = import_safe_env_getter.SafeEnvGetter.getEnvs({
|
|
23753
|
+
[PROCESS_RESOURCE_MAX_LOOP_COUNT_ENV]: [
|
|
23754
|
+
import_safe_env_getter.SafeEnvType.Number,
|
|
23755
|
+
{ default: DEFAULT_RESOURCE_POLLING_LIMITS.maxLoopCount }
|
|
23756
|
+
],
|
|
23757
|
+
[PROCESS_RESOURCE_MAX_ELAPSED_SECONDS_ENV]: [
|
|
23758
|
+
import_safe_env_getter.SafeEnvType.Number,
|
|
23759
|
+
{ default: DEFAULT_RESOURCE_POLLING_LIMITS.maxElapsedSeconds }
|
|
23760
|
+
]
|
|
23761
|
+
});
|
|
23762
|
+
return {
|
|
23763
|
+
maxLoopCount: assertPositiveEnvInt(
|
|
23764
|
+
PROCESS_RESOURCE_MAX_LOOP_COUNT_ENV,
|
|
23765
|
+
parsed[PROCESS_RESOURCE_MAX_LOOP_COUNT_ENV]
|
|
23766
|
+
),
|
|
23767
|
+
maxElapsedSeconds: assertPositiveEnvInt(
|
|
23768
|
+
PROCESS_RESOURCE_MAX_ELAPSED_SECONDS_ENV,
|
|
23769
|
+
parsed[PROCESS_RESOURCE_MAX_ELAPSED_SECONDS_ENV]
|
|
23770
|
+
)
|
|
23771
|
+
};
|
|
23772
|
+
};
|
|
23684
23773
|
|
|
23685
23774
|
// src/funcs/running-scheduler.lambda.ts
|
|
23686
23775
|
var STATE_LIST = [
|
|
@@ -23692,7 +23781,7 @@ var getStateDisplay = (current) => {
|
|
|
23692
23781
|
const found = STATE_LIST.find((s) => s.state === current);
|
|
23693
23782
|
return found ? { emoji: found.emoji, name: found.name } : void 0;
|
|
23694
23783
|
};
|
|
23695
|
-
var processOneResource = async (ctx, targetResource, params, resourceIndex) => {
|
|
23784
|
+
var processOneResource = async (ctx, targetResource, params, resourceIndex, pollingLimits) => {
|
|
23696
23785
|
const parts = targetResource.split("/");
|
|
23697
23786
|
const identifier = parts[parts.length - 1] ?? "unknown";
|
|
23698
23787
|
const arnParts = targetResource.split(":");
|
|
@@ -23704,11 +23793,36 @@ var processOneResource = async (ctx, targetResource, params, resourceIndex) => {
|
|
|
23704
23793
|
identifier,
|
|
23705
23794
|
region,
|
|
23706
23795
|
account,
|
|
23707
|
-
mode: params.Mode
|
|
23796
|
+
mode: params.Mode,
|
|
23797
|
+
maxLoopCount: pollingLimits.maxLoopCount,
|
|
23798
|
+
maxElapsedSeconds: pollingLimits.maxElapsedSeconds
|
|
23708
23799
|
});
|
|
23800
|
+
const startedAtMs = await ctx.step(`${stepPrefix}-polling-started-at`, async () => Date.now());
|
|
23709
23801
|
let loopCount = 0;
|
|
23710
23802
|
let currentState = "";
|
|
23711
23803
|
do {
|
|
23804
|
+
const abortReason = await ctx.step(
|
|
23805
|
+
`${stepPrefix}-poll-limit-check-${loopCount}`,
|
|
23806
|
+
async () => getPollingAbortReason(loopCount, startedAtMs, Date.now(), pollingLimits)
|
|
23807
|
+
);
|
|
23808
|
+
if (abortReason) {
|
|
23809
|
+
const message = formatResourcePollingFailure(abortReason, {
|
|
23810
|
+
identifier,
|
|
23811
|
+
mode: params.Mode,
|
|
23812
|
+
currentState: currentState || "unknown",
|
|
23813
|
+
loopCount,
|
|
23814
|
+
limits: pollingLimits
|
|
23815
|
+
});
|
|
23816
|
+
ctx.logger.error("processOneResource: polling limit exceeded", {
|
|
23817
|
+
identifier,
|
|
23818
|
+
abortReason,
|
|
23819
|
+
loopCount,
|
|
23820
|
+
currentState,
|
|
23821
|
+
maxLoopCount: pollingLimits.maxLoopCount,
|
|
23822
|
+
maxElapsedSeconds: pollingLimits.maxElapsedSeconds
|
|
23823
|
+
});
|
|
23824
|
+
throw new Error(message);
|
|
23825
|
+
}
|
|
23712
23826
|
currentState = await ctx.step(`${stepPrefix}-describe-${loopCount}`, async () => {
|
|
23713
23827
|
const ec2 = new import_client_ec2.EC2Client({});
|
|
23714
23828
|
const out = await ec2.send(new import_client_ec2.DescribeInstancesCommand({ InstanceIds: [identifier] }));
|
|
@@ -23750,8 +23864,7 @@ var processOneResource = async (ctx, targetResource, params, resourceIndex) => {
|
|
|
23750
23864
|
continue;
|
|
23751
23865
|
}
|
|
23752
23866
|
if (!isDesiredStableState(mode, currentState)) {
|
|
23753
|
-
|
|
23754
|
-
if (transitioning) {
|
|
23867
|
+
if (isTransitioningState(mode, currentState)) {
|
|
23755
23868
|
ctx.logger.info("processOneResource: wait while transitioning", {
|
|
23756
23869
|
identifier,
|
|
23757
23870
|
loopCount,
|
|
@@ -23763,13 +23876,20 @@ var processOneResource = async (ctx, targetResource, params, resourceIndex) => {
|
|
|
23763
23876
|
loopCount += 1;
|
|
23764
23877
|
continue;
|
|
23765
23878
|
}
|
|
23879
|
+
const message = formatResourcePollingFailure("UnexpectedInstanceState", {
|
|
23880
|
+
identifier,
|
|
23881
|
+
mode,
|
|
23882
|
+
currentState,
|
|
23883
|
+
loopCount,
|
|
23884
|
+
limits: pollingLimits
|
|
23885
|
+
});
|
|
23766
23886
|
ctx.logger.error("processOneResource: unexpected state", {
|
|
23767
23887
|
identifier,
|
|
23768
23888
|
mode,
|
|
23769
23889
|
currentState,
|
|
23770
23890
|
loopCount
|
|
23771
23891
|
});
|
|
23772
|
-
throw new Error(
|
|
23892
|
+
throw new Error(message);
|
|
23773
23893
|
}
|
|
23774
23894
|
} while (!isDesiredStableState(params.Mode, currentState));
|
|
23775
23895
|
ctx.logger.info("processOneResource: reached desired stable state", {
|
|
@@ -23796,7 +23916,8 @@ var handler = withDurableExecution(async (event, ctx) => {
|
|
|
23796
23916
|
if (!params?.TagKey || !params?.TagValues || !params?.Mode) {
|
|
23797
23917
|
throw new Error("Invalid event: Params.TagKey, Params.TagValues, Params.Mode are required.");
|
|
23798
23918
|
}
|
|
23799
|
-
const
|
|
23919
|
+
const pollingLimits = parseResourcePollingLimitsFromEnv();
|
|
23920
|
+
const slackSecretName = import_safe_env_getter2.SafeEnvGetter.getEnv("SLACK_SECRET_NAME");
|
|
23800
23921
|
const slackSecretValue = await ctx.step("fetch-slack-secret", async () => {
|
|
23801
23922
|
ctx.logger.info("running-scheduler: fetching Slack secret", { secretName: slackSecretName });
|
|
23802
23923
|
return import_aws_lambda_secret_fetcher.secretFetcher.getSecretValue(slackSecretName);
|
|
@@ -23847,7 +23968,7 @@ var handler = withDurableExecution(async (event, ctx) => {
|
|
|
23847
23968
|
// ),
|
|
23848
23969
|
async (mapCtx, targetResource, index) => {
|
|
23849
23970
|
return mapCtx.runInChildContext(`resource-${index}`, async (childCtx) => {
|
|
23850
|
-
const result = await processOneResource(childCtx, targetResource, params, index);
|
|
23971
|
+
const result = await processOneResource(childCtx, targetResource, params, index, pollingLimits);
|
|
23851
23972
|
childCtx.logger.info("running-scheduler: posting thread Slack message", {
|
|
23852
23973
|
index,
|
|
23853
23974
|
identifier: result.identifier,
|