ec2-instance-running-scheduler 0.2.6 → 0.3.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/README.md CHANGED
@@ -6,18 +6,19 @@
6
6
  [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/gammarers-aws-cdk-constructs/ec2-instance-running-scheduler?sort=semver&style=flat-square)](https://github.com/gammarers-aws-cdk-constructs/ec2-instance-running-scheduler/releases)
7
7
  [![View on Construct Hub](https://constructs.dev/badge?package=ec2-instance-running-scheduler)](https://constructs.dev/packages/ec2-instance-running-scheduler)
8
8
 
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.
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, **waits 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.
10
10
 
11
11
  ## Features
12
12
 
13
13
  - **Tag-based targeting** – Select EC2 instances by tag key and values (e.g. `Schedule` / `YES`) via `tag:GetResources`.
14
14
  - **EventBridge Scheduler** – Separate cron rules for start and stop, with per-rule timezone (`aws-cdk-lib` `TimeZone`).
15
15
  - **Durable Lambda** – One Lambda with AWS Lambda Durable Execution (`step`, `wait`, `map`, child contexts per instance) for long-running workflows without Step Functions.
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`).
16
+ - **Stable-state waiting** – After start/stop, the function waits (20 seconds between attempts) and re-describes instances until `running` (start mode) or `stopped` (stop mode).
17
+ - **Configurable wait limits** – Per-instance **max loop count** and **max elapsed time** via `resourceWait` (default: 90 loops / 1800 seconds). Failures use explicit `ResourceWaitFailed:*` messages instead of running until the Durable execution timeout (construct default: 2 hours).
18
+ - **Validated environment variables** – The bundled handler parses env vars with **strict-env-resolver** (`StrictEnvResolver`). `SLACK_SECRET_NAME` is required; wait limits must be **finite positive numbers** (`> 0`).
19
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
+ - **Structured logging** – Durable execution **`ctx.logger`** for traceable JSON application logs (invocation, describe/start/stop/wait loops, wait limit errors, Slack steps, completion).
21
+ - **Optional failure detection** – CloudWatch alarms and log-based metrics for Lambda errors, instance wait failures (`ResourceWaitFailed`), Slack post failures, and other handler `ERROR` logs. Optional SNS notifications via a caller-supplied topic (`failureDetection.alarmTopic`).
21
22
  - **Scheduling toggle** – Enable or disable both schedules without removing the stack (`enableScheduling`).
22
23
  - **Configurable schedules** – Optional cron overrides for start and stop (`minute`, `hour`, `week`, `timezone`); sensible defaults if omitted.
23
24
  - **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).
@@ -49,11 +50,14 @@ Use the **construct** `EC2InstanceRunningScheduler` when embedding the scheduler
49
50
  ```typescript
50
51
  import * as cdk from 'aws-cdk-lib';
51
52
  import { TimeZone } from 'aws-cdk-lib';
53
+ import * as sns from 'aws-cdk-lib/aws-sns';
52
54
  import { EC2InstanceRunningScheduler } from 'ec2-instance-running-scheduler';
53
55
 
54
56
  const app = new cdk.App();
55
57
  const stack = new cdk.Stack(app, 'MyStack');
56
58
 
59
+ const alarmTopic = new sns.Topic(stack, 'OpsAlerts');
60
+
57
61
  new EC2InstanceRunningScheduler(stack, 'EC2InstanceRunningScheduler', {
58
62
  targetResource: {
59
63
  tagKey: 'Schedule',
@@ -75,22 +79,33 @@ new EC2InstanceRunningScheduler(stack, 'EC2InstanceRunningScheduler', {
75
79
  week: 'MON-FRI',
76
80
  },
77
81
  enableScheduling: true,
78
- resourcePolling: {
82
+ resourceWait: {
79
83
  maxLoopCount: 120,
80
84
  maxElapsedSeconds: 3600,
81
85
  },
86
+ failureDetection: {
87
+ enabled: true,
88
+ alarmTopic,
89
+ },
82
90
  });
83
91
  ```
84
92
 
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.
93
+ Use the **stack** `EC2InstanceRunningScheduleStack` when deploying the scheduler as its own stack. It accepts the same **targeting, schedules, secrets, enable flag, and failure detection** as the construct (plus standard `StackProps` such as `env`). For **`resourceWait`**, use the construct directly or extend the stack in your app.
86
94
 
87
95
  ```typescript
88
96
  import * as cdk from 'aws-cdk-lib';
89
97
  import { TimeZone } from 'aws-cdk-lib';
98
+ import * as sns from 'aws-cdk-lib/aws-sns';
90
99
  import { EC2InstanceRunningScheduleStack } from 'ec2-instance-running-scheduler';
91
100
 
92
101
  const app = new cdk.App();
93
102
 
103
+ const alarmTopic = sns.Topic.fromTopicArn(
104
+ app,
105
+ 'OpsAlerts',
106
+ 'arn:aws:sns:ap-northeast-1:123456789012:ops-alerts',
107
+ );
108
+
94
109
  new EC2InstanceRunningScheduleStack(app, 'EC2InstanceRunningScheduleStack', {
95
110
  targetResource: {
96
111
  tagKey: 'Schedule',
@@ -112,6 +127,10 @@ new EC2InstanceRunningScheduleStack(app, 'EC2InstanceRunningScheduleStack', {
112
127
  week: 'MON-FRI',
113
128
  },
114
129
  enableScheduling: true,
130
+ failureDetection: {
131
+ enabled: true,
132
+ alarmTopic,
133
+ },
115
134
  });
116
135
  ```
117
136
 
@@ -120,10 +139,10 @@ EventBridge Scheduler invokes the Lambda with `Params.TagKey`, `Params.TagValues
120
139
  | Variable | Source | Purpose |
121
140
  |----------|--------|---------|
122
141
  | `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 |
142
+ | `PROCESS_RESOURCE_MAX_LOOP_COUNT` | `resourceWait.maxLoopCount` (default `90`) | Max describe/wait iterations per instance |
143
+ | `PROCESS_RESOURCE_MAX_ELAPSED_SECONDS` | `resourceWait.maxElapsedSeconds` (default `1800`) | Max wall-clock seconds waiting for one instance |
125
144
 
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.
145
+ When you set wait limits via `resourceWait`, the construct writes them as decimal integer strings. At invocation the handler parses them with **strict-env-resolver**; each value must be a **finite number greater than zero**. Missing `SLACK_SECRET_NAME` or invalid env values cause `StrictEnvValidationError` at the start of an invocation.
127
146
 
128
147
  ## Options
129
148
 
@@ -136,11 +155,12 @@ When you set polling limits via `resourcePolling`, the construct writes them as
136
155
  | `startSchedule` | `Schedule` | No | Cron for starting instances (default: `50 7 ? * MON-FRI *` in `Etc/UTC`). |
137
156
  | `stopSchedule` | `Schedule` | No | Cron for stopping instances (default: `5 19 ? * MON-FRI *` in `Etc/UTC`). |
138
157
  | `enableScheduling` | `boolean` | No | Whether both scheduler rules are enabled (default: `true`). |
139
- | `resourcePolling` | `ResourcePollingLimits` | No | Per-instance polling caps (see below). |
158
+ | `resourceWait` | `ResourceWaitLimits` | No | Per-instance wait caps (see below). |
159
+ | `failureDetection` | `FailureDetectionAlarms` | No | Optional CloudWatch alarms and log-based metrics (see below). |
140
160
 
141
161
  ### EC2InstanceRunningScheduleStack
142
162
 
143
- Includes `targetResource`, `secrets`, `startSchedule`, `stopSchedule`, `enableScheduling`, and standard `StackProps`. Does **not** expose `resourcePolling`; use `EC2InstanceRunningScheduler` when you need custom polling limits.
163
+ Includes `targetResource`, `secrets`, `startSchedule`, `stopSchedule`, `enableScheduling`, `failureDetection`, and standard `StackProps`. Does **not** expose `resourceWait`; use `EC2InstanceRunningScheduler` when you need custom wait limits.
144
164
 
145
165
  ### TargetResource
146
166
 
@@ -158,20 +178,38 @@ Includes `targetResource`, `secrets`, `startSchedule`, `stopSchedule`, `enableSc
158
178
 
159
179
  - `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
180
 
161
- ### ResourcePollingLimits
181
+ ### ResourceWaitLimits
162
182
 
163
183
  Written to `PROCESS_RESOURCE_MAX_LOOP_COUNT` and `PROCESS_RESOURCE_MAX_ELAPSED_SECONDS` on the running scheduler Lambda.
164
184
 
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.
185
+ - `maxLoopCount` – Maximum describe/wait loop iterations per instance (default: **90**). Must be a positive integer when set.
186
+ - `maxElapsedSeconds` – Maximum wall-clock seconds spent waiting for one instance to stabilize (default: **1800**, 30 minutes). Must be a positive integer when set.
187
+
188
+ When a limit is exceeded during waiting, the handler throws an error with prefix `ResourceWaitFailed:` (`MaxLoopCountExceeded`, `MaxElapsedTimeExceeded`, or `UnexpectedInstanceState` for unknown EC2 states).
189
+
190
+ ### FailureDetectionAlarms
191
+
192
+ Optional operational failure detection. Alarms are created only when `enabled` is `true`.
193
+
194
+ - `enabled` – When `true`, creates four CloudWatch alarms and three log metric filters (default: disabled when omitted).
195
+ - `alarmTopic` – Optional `sns.ITopic` for alarm actions. The construct does **not** create an SNS topic; pass an existing or imported topic. When omitted, alarms are created without SNS actions.
196
+
197
+ When enabled, the construct creates:
198
+
199
+ | Alarm | Trigger |
200
+ |-------|---------|
201
+ | Lambda errors | `AWS/Lambda` `Errors` metric |
202
+ | Instance status failure | Log filter: `ResourceWaitFailed` |
203
+ | Slack post failure | Log filter: `running-scheduler: Slack post failed` |
204
+ | Durable execution failure | Other handler `ERROR` logs (excluding the above) |
167
205
 
168
- When a limit is exceeded during polling, the handler throws an error with prefix `ResourcePollingFailed:` (`MaxLoopCountExceeded`, `MaxElapsedTimeExceeded`, or `UnexpectedInstanceState` for unknown EC2 states).
206
+ Custom metrics are published under the `EC2InstanceRunningScheduler` namespace. Access the created alarms via `EC2InstanceRunningScheduler.failureDetection` when enabled.
169
207
 
170
208
  ## Requirements
171
209
 
172
210
  - **Node.js** ≥ 20.0.0 (for developing or synthesizing CDK apps that depend on this package).
173
211
  - **aws-cdk-lib** ^2.232.0 and **constructs** ^10.5.1 (peer dependencies).
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**.
212
+ - **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** (^0.6) and parses env vars via **strict-env-resolver** (^0.5). Secret fetch runs only inside Lambda (requires runtime `AWS_SESSION_TOKEN` and the extension layer); the library retries transient extension errors including cold-start "not ready" responses.
175
213
 
176
214
  ## License
177
215