Skip to content

Commit 258fdc0

Browse files
authored
Merge pull request #1 from zAbuQasem/develop
feat: support tag-based targeting via new 'targets' input
2 parents 8c51d5d + 8554ed6 commit 258fdc0

16 files changed

Lines changed: 1285 additions & 191 deletions

.github/workflows/lint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ on:
66
- main
77

88
env:
9-
MINIMUM_PACKAGE_AGE_HOURS: 48
9+
MINIMUM_PACKAGE_AGE_HOURS: 0
1010
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
1111
SAFE_CHAIN_VERSION: "1.4.7"
1212
SAFE_CHAIN_SHA256: "54c750232d149106ecf4f5f28fee82ba49d2428f1e411e0ed961c0263ae19eaf"

.github/workflows/tester.yml

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,16 @@ name: ci
22

33
on:
44
workflow_dispatch:
5+
inputs:
6+
environment:
7+
description: "Tag-targeting environment (used by the tag-targeting job)"
8+
required: false
9+
default: production
10+
type: choice
11+
options: [staging, production]
512
push:
613
branches:
14+
- develop
715
- main
816

917
env:
@@ -17,8 +25,9 @@ permissions:
1725
contents: read
1826

1927
jobs:
20-
test:
21-
name: Integration test
28+
# ── Job 1: target by instance ID ─────────────────────────────────────────────
29+
test-instance-ids:
30+
name: Integration test — instance-ids
2231
runs-on: ubuntu-latest
2332

2433
steps:
@@ -46,14 +55,69 @@ jobs:
4655
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
4756
aws-region: ${{ secrets.AWS_REGION }}
4857

49-
- name: Run SSM command
58+
- name: Run SSM command (instance-ids)
59+
id: ssm
5060
uses: ./
5161
with:
5262
aws-region: ${{ secrets.AWS_REGION }}
5363
instance-ids: ${{ secrets.INSTANCE_ID }}
5464
working-directory: /home/ec2-user
55-
comment: aws-ssm-action CI test
65+
wait-for-output: true
66+
wait-timeout: 180
67+
comment: ci — instance-ids — ${{ github.sha }}
5668
command: |
5769
echo "Hello from GitHub Actions!" >> logs.txt
5870
echo $(date) >> logs.txt
5971
cat logs.txt
72+
73+
- name: Print output
74+
run: echo "${{ steps.ssm.outputs.output }}"
75+
76+
# ── Job 2: target by tag ──────────────────────────────────────────────────────
77+
test-tag-targeting:
78+
name: Integration test — tag targeting
79+
runs-on: ubuntu-latest
80+
81+
steps:
82+
- name: Checkout
83+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
84+
85+
- name: Setup Bun
86+
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
87+
88+
- name: Install safe-chain
89+
run: |
90+
curl -fsSL "https://github.com/AikidoSec/safe-chain/releases/download/${SAFE_CHAIN_VERSION}/install-safe-chain.sh" \
91+
-o install-safe-chain.sh
92+
echo "${SAFE_CHAIN_SHA256} install-safe-chain.sh" | sha256sum --check
93+
sh install-safe-chain.sh --ci
94+
95+
- name: Install dependencies
96+
run: bun install
97+
env:
98+
SAFE_CHAIN_MINIMUM_PACKAGE_AGE_HOURS: ${{ env.MINIMUM_PACKAGE_AGE_HOURS }}
99+
100+
- name: Configure AWS credentials (OIDC)
101+
uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6.1.0
102+
with:
103+
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
104+
aws-region: ${{ secrets.AWS_REGION }}
105+
106+
- name: Run SSM command (tag targeting)
107+
id: ssm
108+
uses: ./
109+
with:
110+
aws-region: ${{ secrets.AWS_REGION }}
111+
targets: |
112+
Key=tag:env,Values=${{ inputs.environment || 'production' }}
113+
Key=tag:role,Values=web
114+
working-directory: /home/ec2-user
115+
wait-for-output: true
116+
wait-timeout: 180
117+
comment: ci — tags — ${{ github.sha }}
118+
command: |
119+
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) Hello from tag-targeting CI" >> logs.txt
120+
cat logs.txt
121+
122+
- name: Print output
123+
run: echo "${{ steps.ssm.outputs.output }}"

CLAUDE.md

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,41 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
77
```bash
88
bun install # install dependencies
99
bun run typecheck # tsc --noEmit (type check only, no emit)
10+
bun test # run unit tests (bun:test) for input validation
1011
bun run build # bundle src/index.ts → dist/index.js
1112
```
1213

13-
Run both before committing any source change — the runner executes `dist/index.js` directly, so it must always be up to date.
14+
Run typecheck, tests, and build before committing any source change — the runner executes `dist/index.js` directly, so it must always be up to date.
1415

15-
There are no unit tests. The CI workflow (`tester.yml`) validates against a live AWS environment via OIDC.
16+
Unit tests live in `src/inputs.test.ts` and exercise `sanitizeInputs()` by setting `INPUT_*` env vars (the same mechanism `@actions/core` uses at runtime). Test files are excluded from `tsconfig.json` so bun test handles their typing directly. The CI workflow (`tester.yml`) additionally validates against a live AWS environment via OIDC.
1617

1718
## Architecture
1819

1920
Single-file GitHub Action (`src/index.ts`) that wraps the AWS SSM `SendCommand` API to run bash commands on EC2 instances without SSH.
2021

2122
**Entry points:**
2223
- `action.yml` — input/output schema and runtime declaration (`node24`)
23-
- `src/index.ts` — reads inputs, validates them, calls `SSMClient.send(SendCommandCommand(...))`
24+
- `src/index.ts``run()` entry; wires SDK client, calls `SendCommandCommand`, optionally calls `waitForOutput()` which polls `GetCommandInvocationCommand` / `ListCommandInvocationsCommand`
25+
- `src/inputs.ts``sanitizeInputs()` reads inputs via `@actions/core`, validates them, and returns a typed `SanitizedInputs` (or `null` on failure). Pure enough to unit-test by setting `INPUT_*` env vars.
26+
- `src/inputs.test.ts` — bun:test unit tests for `sanitizeInputs()`
2427
- `dist/index.js` — Bun-bundled self-contained output; **must be committed** (runners have no build step)
2528

2629
**Input validation in `sanitizeInputs()`** (returns `null` and calls `core.setFailed` on any error):
27-
- `aws-region` — validated against `/^[a-z]{2}-[a-z]+-\d+$/`
30+
- `aws-region` — validated against `/^[a-z]{2}-[a-z]+(-[a-z]+)?-\d+$/` (supports commercial, GovCloud, and ISO regions)
2831
- `instance-ids` — each line validated against `/^i-[0-9a-f]{8}([0-9a-f]{9})?$/`; max 50
32+
- `targets` — each line parsed as `Key=<k>,Values=<v1>[,<v2>...]`; key matched against `/^(InstanceIds|tag:[\w.\-:/+=@ ]{1,128}|resource-groups:(Name|ResourceTypeFilters))$/`; max 5 targets, 50 values per target, 256 chars per value (AWS SSM limits)
33+
- `document-name` — optional, defaults to `AWS-RunShellScript`. Validated as either a simple SSM document name (`[A-Za-z0-9._-]{3,128}`) or a full document ARN (commercial / cn / us-gov / iso / iso-b partitions)
34+
- `wait-for-output` — parsed via `core.getBooleanInput`; must be `"true"` or `"false"` (case-insensitive), defaults to `false`
35+
- `wait-timeout` — parsed as integer, must be 1–3600 inclusive; rejects floats and non-numeric strings; defaults to `60`
36+
37+
**Output polling (when `waitForOutput` is true) in `src/index.ts`:**
38+
- For `instance-ids` mode: polls `GetCommandInvocationCommand` directly for each known ID
39+
- For `targets` mode: calls `ListCommandInvocationsCommand` first to discover instance IDs (requires `ssm:ListCommandInvocations`), then polls each
40+
- Exponential backoff: starts at 3 s, caps at 15 s
41+
- Terminal statuses: `Success`, `Failed`, `TimedOut`, `Cancelled`, `DeliveryTimedOut`, `ExecutionTimedOut`
42+
- Logs per-instance stdout/stderr in collapsible groups; sets `output` action output; fails action on non-success or timeout
43+
- AWS truncates `StandardOutputContent` at 24 000 bytes per invocation
44+
- Exactly one of `instance-ids` / `targets` must be set — both or neither is rejected. In `SendCommand`, the action sends `InstanceIds` *or* `Targets`, never both
2945
- `working-directory` — validated against `/^\/[a-zA-Z0-9._\-/]*$/`; no `..`
3046
- `command` — split on newlines; empty result is rejected
3147
- `comment` — newlines stripped, truncated to 100 chars (AWS API limit)

0 commit comments

Comments
 (0)