@raindancers/raindancers-crew 0.0.1 → 0.0.2
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 +1026 -3
- package/API.md +1459 -326
- package/README.md +58 -0
- package/docs/spec-ecs-crew-construct.md +200 -0
- package/lib/crew-backup-bucket.js +1 -1
- package/lib/crew-webhook-ingress.d.ts +104 -0
- package/lib/crew-webhook-ingress.js +111 -0
- package/lib/ecs-crew-host.d.ts +247 -0
- package/lib/ecs-crew-host.js +383 -0
- package/lib/fargate-crew-base.js +1 -1
- package/lib/fargate-crew.js +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.js +3 -1
- package/lib/remote-crew-instance.js +1 -1
- package/package.json +2 -2
- package/src/assets/ecs-host-bootstrap.sh +205 -0
- package/src/crew-webhook-ingress.ts +175 -0
- package/src/ecs-crew-host.ts +616 -0
- package/src/index.ts +2 -0
package/README.md
CHANGED
|
@@ -418,6 +418,64 @@ Security invariants preserved from upstream: task role has no policies and never
|
|
|
418
418
|
|
|
419
419
|
**EC2 vs Fargate:** EC2 gives a persistent box with local disk (the crew's memory/knowledge DBs live on the instance) and is the native launcher's default; Fargate is more ephemeral and expects external persistence. For a remote crew that remembers across sessions, EC2 is usually the better fit.
|
|
420
420
|
|
|
421
|
+
## ECS-on-EC2 multi-crew host
|
|
422
|
+
|
|
423
|
+
`EcsCrewHost` runs several isolated Kiro Crew instances on ONE self-provisioned EC2 host, each crew as its own ECS-on-EC2 container task. It builds on the same `FargateCrewBase` cluster and `FargateCrew` per-crew identity, adds the EC2 capacity, and wires the two-subnet host-NAT networking. It is strictly additive: `crewCount` defaults to 1, so a plain instantiation behaves like a single crew and existing consumers gain nothing new.
|
|
424
|
+
|
|
425
|
+
```ts
|
|
426
|
+
import { EcsCrewHost, CrewBackupBucket } from '@raindancers/raindancers-crew';
|
|
427
|
+
import * as ec2 from 'aws-cdk-lib/aws-ec2';
|
|
428
|
+
|
|
429
|
+
const backup = new CrewBackupBucket(this, 'CrewBackup');
|
|
430
|
+
|
|
431
|
+
new EcsCrewHost(this, 'CrewFleet', {
|
|
432
|
+
vpc, // you supply the two-subnet host-NAT VPC
|
|
433
|
+
permissionsBoundaryArn: '...', // host role boundary (required)
|
|
434
|
+
crewPermissionsBoundaryArn: '...', // kirocrew-crew-boundary for the crew roles
|
|
435
|
+
instanceType: new ec2.InstanceType('m9g.xlarge'),
|
|
436
|
+
crewCount: 3,
|
|
437
|
+
crewDataVolumeSizeGb: 20,
|
|
438
|
+
backupBucket: backup,
|
|
439
|
+
stackTag: 'fiftyfive',
|
|
440
|
+
});
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
What it creates:
|
|
444
|
+
|
|
445
|
+
| Resource | Notes |
|
|
446
|
+
|---|---|
|
|
447
|
+
| ECS cluster + egress-only task SG | Reused from `FargateCrewBase` (one per account/region) |
|
|
448
|
+
| Size-1 Auto Scaling Group + capacity provider | ONE self-provisioned host, arch-aware ECS-optimized AL2023 AMI, **IMDSv2 enforced**, **encrypted gp3** root. A size-1 ASG is still one EC2 host; the capacity provider is what registers it with the cluster so the L2 `Ec2Service` can schedule onto it |
|
|
449
|
+
| Per-crew EC2 task definition + service | `awsvpc` network mode (each task its own ENI + private IP), `desiredCount: 1`, egress-only task SG, **no public IP**. Reuses each crew's `FargateCrew` task/execution role and `/kirocrew/crew/<crew>` log group |
|
|
450
|
+
| Per-crew durable EBS | one encrypted **gp3** volume per crew, **`deleteOnTermination: false`** so a crew's `~/.kiro/crew` outlives an instance replacement. Resolved in the bootstrap by a stable filesystem **label**, never `/dev/sdf` (the Nitro NVMe layer renames it) |
|
|
451
|
+
| Host role grant | scoped `ec2:ModifyInstanceAttribute` (tag-conditioned to KiroCrew ECS hosts) so the host disables its own source/dest check for NAT |
|
|
452
|
+
|
|
453
|
+
Networking model (you supply the VPC; the construct does NOT create a NAT Gateway, fck-nat, VPC endpoints, or an ALB):
|
|
454
|
+
|
|
455
|
+
- **PUBLIC subnet** holds the host ENI + Elastic IP + the IGW route. The host egresses directly via the IGW.
|
|
456
|
+
- **PRIVATE subnet** holds the crew task ENIs. Its `0.0.0.0/0` route points at the host ENI. The host does the NAT: the bootstrap sets `net.ipv4.ip_forward=1` and an iptables `MASQUERADE` rule, and the host disables source/dest check on its own ENI. Tasks have no public IPs.
|
|
457
|
+
|
|
458
|
+
Isolation is container-level (shared kernel), accepted as sufficient: Graviton Nitro protects the box from other AWS tenants, and containers cover crew-to-crew separation. Two cautions this bakes into the props and tests:
|
|
459
|
+
|
|
460
|
+
- **Host-OOM cross-crew blast radius.** Each crew container carries a SOFT `memoryReservationMiB` (for scheduling) and a HARD `memoryLimitMiB` (its ceiling). Hard caps bound each crew, but because the kernel is shared, if the sum of ACTUAL usage exceeds physical RAM the host OOM-killer can hit any crew. For a hard guarantee set the sum of hard caps at or below (physical RAM minus host + ECS-agent headroom). This is the accepted cost of container isolation versus microVMs.
|
|
461
|
+
- **awsvpc ENI budget.** Each task takes one ENI, plus the host ENI, so you need at least `crewCount + 1` ENIs on the instance type. `crewCount` is validated 1..8. The bootstrap enables ENI trunking (`ECS_ENABLE_AWSVPC_TRUNKING`) which raises the per-instance task-ENI budget on smaller sizes; confirm the chosen `m9g` size carries `crewCount + 1` ENIs before you build.
|
|
462
|
+
|
|
463
|
+
### Optional webhook ingress (`CrewWebhookIngress`)
|
|
464
|
+
|
|
465
|
+
Because each task has a real private VPC IP, an in-VPC Lambda can POST to a crew's `POST /api/hooks/agent` directly, with no VPC endpoint. `CrewWebhookIngress` is an OPTIONAL composable sub-construct (mirroring `CrewBackupBucket`): OFF by default, so an existing consumer gains no new resources. When instantiated it creates an SNS topic, an in-VPC arm64 Lambda under the boundary, and the ONE controlled inbound exception to the egress-only task model: a scoped ingress rule on the crew task SG from the Lambda's SG on the crew port only, never a CIDR. It ships no webhook-to-crew routing opinion; you supply the Lambda `code`.
|
|
466
|
+
|
|
467
|
+
```ts
|
|
468
|
+
import { CrewWebhookIngress } from '@raindancers/raindancers-crew';
|
|
469
|
+
import * as lambda from 'aws-cdk-lib/aws-lambda';
|
|
470
|
+
|
|
471
|
+
new CrewWebhookIngress(this, 'Webhook', {
|
|
472
|
+
vpc,
|
|
473
|
+
host, // the EcsCrewHost above
|
|
474
|
+
permissionsBoundaryArn: '...',
|
|
475
|
+
code: lambda.Code.fromAsset('path/to/ingress-lambda'),
|
|
476
|
+
});
|
|
477
|
+
```
|
|
478
|
+
|
|
421
479
|
## Backing up the crew's learnings
|
|
422
480
|
|
|
423
481
|
A remote crew's value is its accumulated memory, lessons, and knowledge — which on the EC2 lane live on the instance's local disk. KiroCrew's built-in backup (`kirocrew snapshot`) produces a redaction-scrubbed bundle (the signing key, `.env`, and execution logs never ship), but writes it **locally** — so it survives corruption, not instance loss. These constructs add the missing **off-box durability**.
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# Build spec: three isolated Kiro Crew instances on one m9g, as ECS-on-EC2 container tasks, via the raindancers-crew construct
|
|
2
|
+
|
|
3
|
+
Status: build spec for review. Nothing here is applied. This is a spec only: no git, no AWS calls, no installs. It lists the concrete bits to ADD to the raindancers-crew CDK construct so ONE self-provisioned EC2 (m9g, Graviton5, arm64) runs three isolated Kiro Crew instances as ECS-on-EC2 container tasks, and how the 55minutes repo instantiates that construct as the consumer.
|
|
4
|
+
|
|
5
|
+
This spec supersedes the microVM shape in `/home/frazera/.kiro/crew/scratch/spec-firecracker-crew-construct.md` for this workload. It DELETES the whole Firecracker/jailer/microVM layer (see section 5). That older spec is left in place untouched; this is a separate, alternative build.
|
|
6
|
+
|
|
7
|
+
Convention used throughout: VERIFIED means read directly from source in `/home/frazera/.kiro/crew/scratch/rc-repo`. PROPOSED means new work this spec is asking for. ESTIMATE means a number to tune, not a measured fact.
|
|
8
|
+
|
|
9
|
+
Real files this spec touches (all confirmed present):
|
|
10
|
+
- `src/remote-crew-instance-props.ts` (VERIFIED, 5825 bytes)
|
|
11
|
+
- `src/remote-crew-instance.ts` (VERIFIED, 12220 bytes)
|
|
12
|
+
- `src/fargate-crew-base.ts` (VERIFIED, exists, holds `ecs.Cluster` + egress-only SG)
|
|
13
|
+
- `src/fargate-crew.ts` (VERIFIED, exists, holds per-crew task role, execution role, log group)
|
|
14
|
+
- `src/crew-backup-bucket.ts` (VERIFIED, `RemovalPolicy.RETAIN` default)
|
|
15
|
+
- `src/index.ts` (VERIFIED, barrel exporting all of the above)
|
|
16
|
+
- `src/assets/bootstrap.sh` (VERIFIED), `src/assets/backup.sh`, `src/assets/restore-from-s3.sh`
|
|
17
|
+
|
|
18
|
+
## 1. Goal
|
|
19
|
+
|
|
20
|
+
Add an ECS-on-EC2 lane to the raindancers-crew construct so one operator-owned m9g EC2 instance, running the ECS agent and registered to an ECS cluster, hosts `crewCount` Kiro Crew instances as ECS tasks (one task/service per crew, default 3 for the 55minutes consumer). Each task runs in awsvpc mode with its own ENI and private VPC IP; the host does the NAT so tasks stay fully private with no public IPs; each crew keeps its durable `~/.kiro/crew` state on its own encrypted EBS volume that survives instance replacement. Isolation is container-level (shared kernel), accepted as sufficient: Graviton5 Nitro protects the box from other AWS tenants, containers cover crew-to-crew. The construct default stays a single crew so existing consumers are untouched; everything here is strictly additive.
|
|
21
|
+
|
|
22
|
+
## 2. VERIFIED current-construct baseline
|
|
23
|
+
|
|
24
|
+
Read directly from source. Nothing in this section is proposed; it is what the construct does today.
|
|
25
|
+
|
|
26
|
+
### 2a. RemoteCrewInstance (EC2 lane) - `src/remote-crew-instance.ts`, `src/remote-crew-instance-props.ts`
|
|
27
|
+
|
|
28
|
+
- `vpc: ec2.IVpc` is a REQUIRED prop (consumer passes it). VERIFIED.
|
|
29
|
+
- `permissionsBoundaryArn: string` is REQUIRED and applied to the instance role via `iam.ManagedPolicy.fromManagedPolicyArn` (`assumedBy: ec2.amazonaws.com`, managed policy `AmazonSSMManagedInstanceCore`). VERIFIED.
|
|
30
|
+
- `instanceType?: ec2.InstanceType`, default `m7g.2xlarge` for ARM64 (or `m7i.2xlarge` for x86_64). VERIFIED. So the instance type IS already a settable parameter; the m9g size is a consumer choice, not a new construct default.
|
|
31
|
+
- `architecture?: CrewArchitecture` default `ARM64`. The ARM64 branch selects `ec2.AmazonLinuxCpuType.ARM_64` and `ec2.MachineImage.latestAmazonLinux2023({ cpuType })` (no hardcoded AMI id). VERIFIED.
|
|
32
|
+
- `volumeSizeGb?: number` default 60. The single root block device is `/dev/xvda`, `ec2.BlockDeviceVolume.ebs(...)` with `volumeType: GP3`, `encrypted: true`, `deleteOnTermination: true`. VERIFIED. (The old Firecracker spec called the root default 20 GiB in one place; the real default is 60. See DIVERGENCE list.)
|
|
33
|
+
- `requireImdsv2: true` on the instance (IMDSv2 enforced so the prompt-injectable agent cannot read role creds via IMDSv1). VERIFIED.
|
|
34
|
+
- Security group is SSM-only: `allowAllOutbound: true`, no inbound unless `allowSshCidr` is set (validated by a CIDR regex no wider than /16). VERIFIED.
|
|
35
|
+
- `associatePublicIp?: boolean` default `true`; `vpcSubnets?: ec2.SubnetSelection` default `{ subnetType: ec2.SubnetType.PUBLIC }`. VERIFIED.
|
|
36
|
+
- Backup wiring: `backupBucket?: ICrewBackupBucket`, `backupSchedule?` (default `daily`), `backupPrefix?` (default `crew-snapshots/`). When set, grants write+read, writes `/etc/kirocrew/backup.env`, installs `/usr/local/sbin/kirocrew-backup` and `kirocrew-restore-from-s3`, plus `kirocrew-backup.service` and `.timer`. VERIFIED.
|
|
37
|
+
- `CfnWaitConditionHandle` + `CfnWaitCondition` health gate, `bootstrapTimeoutMinutes?` default 25, `count: 1`, depends on the CfnInstance. VERIFIED.
|
|
38
|
+
- Stack outputs `kirocrew-<stackTag>-instance-id` and `kirocrew-<stackTag>-public-dns` (public DNS is diagnostics only). VERIFIED.
|
|
39
|
+
- `source?: CrewSource` (S3 tarball via `sourceBucket`/`sourceKey`, else git clone of `kirocrewRepo`@`kirocrewRef`, default `kirodotdev/KiroCrew`@`main`), `stackTag?` (default `kirocrew`, regex `^[a-zA-Z0-9-]{1,51}$`), `dashboardPort?` (default 5476). VERIFIED.
|
|
40
|
+
- UserData mechanism: the construct reads `src/assets/bootstrap.sh` at synth (via a `resolveAsset` helper that walks `src/` for tests and `../src/assets` for compiled consumers), prepends a header of `WAIT_HANDLE`/`DASHBOARD_PORT`/`SOURCE_*`/`KIROCREW_*` env vars, then appends the script body. VERIFIED.
|
|
41
|
+
|
|
42
|
+
### 2b. Existing ECS lane (this is the important baseline for this spec)
|
|
43
|
+
|
|
44
|
+
The construct ALREADY has a container/ECS lane. This is the biggest divergence from the old Firecracker spec, which described it only as "the Fargate lane, untouched, simply not used". For an ECS-on-EC2 build it is a reusable foundation, not dead weight.
|
|
45
|
+
|
|
46
|
+
- `src/fargate-crew-base.ts` defines `FargateCrewBase`: creates an `ecs.Cluster` (`clusterName: kirocrew-crew-<stackTag>`, `containerInsightsV2: DISABLED`) in the passed `vpc`, plus an egress-only task security group (`allowAllOutbound: true`, NO inbound). It documents itself as "ONE per account and region" and notes awsvpc placement (`vpcSubnets?: ec2.SubnetSelection`, "each must be able to reach the container registry"). VERIFIED.
|
|
47
|
+
- `src/fargate-crew.ts` defines `FargateCrew`: one per crew. Creates a per-crew execution role (`kirocrew-crew-<crew>-exec`, scoped secret read `kirocrew/crew/<crew>/*`, `logs:CreateLogStream`+`PutLogEvents`, optional scoped ECR pull), a per-crew task role (`kirocrew-crew-<crew>-task`, created with NO policies, MUST NEVER get `secretsmanager:GetSecretValue`), and a per-crew log group (`/kirocrew/crew/<crew>`, retention prop). Both roles carry an optional crew boundary and an `aws:SourceAccount` assume condition. `crew` name regex `^[a-z0-9]([a-z0-9-]{0,30}[a-z0-9])?$`. VERIFIED.
|
|
48
|
+
- `FargateCpuArchitecture` enum (`X86_64` default, `ARM64`) on `FargateCrewBase`. VERIFIED.
|
|
49
|
+
|
|
50
|
+
What is NOT present today (VERIFIED by reading the files): neither ECS construct creates a `TaskDefinition`, a container definition, an `Ec2Service`/`FargateService`, an ASG or `AsgCapacityProvider`, or any EC2 capacity for the cluster. The cluster exists but has no capacity registered and no task definition to run. The per-crew IAM + log-group scaffolding exists; the "actually run a container" resources do not. That gap is exactly what this spec fills for the EC2 case.
|
|
51
|
+
|
|
52
|
+
### 2c. CrewBackupBucket - `src/crew-backup-bucket.ts`
|
|
53
|
+
|
|
54
|
+
- Hardened S3 bucket: SSE-KMS (rotating key), `BLOCK_ALL` public access, `enforceSSL: true`, versioned, lifecycle expiry of noncurrent versions (default 90 days). `removalPolicy` default `RemovalPolicy.RETAIN` (and the KMS key RETAIN too). Implements `ICrewBackupBucket` with `grantWrite`/`grantRead`. VERIFIED. This RETAIN intent is the model for the per-crew EBS `deleteOnTermination: false` decision in section 3.
|
|
55
|
+
|
|
56
|
+
## 3. Additive changes: CONSTRUCT (heavy lifting) vs 55minutes CONSUMER (thin)
|
|
57
|
+
|
|
58
|
+
The construct does the heavy lifting; the 55minutes repo is a thin consumer that supplies only its own facts (VPC, subnets, permissions boundary ARN, region, instance type, crew count) as props and CDK context and holds no ECS, networking, or provisioning logic of its own. No AWS resource ID is hardcoded inside the construct (hard rule): every account-specific or environment-specific value is a prop the consumer passes.
|
|
59
|
+
|
|
60
|
+
### 3a. CONSTRUCT changes
|
|
61
|
+
|
|
62
|
+
New or extended construct surface. A new dedicated construct is cleaner than overloading `RemoteCrewInstance`, but it reuses the verified EC2 provisioning (boundary, IMDSv2, encrypted gp3 root, SSM-only SG, bootstrap-asset mechanism, WaitCondition) and the verified ECS scaffolding (`FargateCrewBase` cluster, `FargateCrew` roles/log groups).
|
|
63
|
+
|
|
64
|
+
PROPOSED new construct: `EcsCrewHost` in a new file `src/ecs-crew-host.ts`, exported from `src/index.ts`. It provisions the single ECS-registered m9g and, per crew, one EC2 task definition + service + EBS data volume, plus the two-subnet host-NAT networking. Rationale for a new construct rather than a `RemoteCrewInstance` flag: the EC2 lane today is "one box runs the gateway directly", and ECS-on-EC2 is "one box runs the ECS agent and hosts N task ENIs behind host NAT". Those are different enough shapes that a variant flag on `RemoteCrewInstance` would be a maze of `if (ecs)` branches. This mirrors the existing choice to keep `FargateCrewBase`/`FargateCrew` separate from `RemoteCrewInstance`. (OPEN, section 5: confirm new-construct vs extend-RemoteCrewInstance.)
|
|
65
|
+
|
|
66
|
+
New props (PROPOSED) on `EcsCrewHostProps`:
|
|
67
|
+
|
|
68
|
+
- `vpc: ec2.IVpc` (required). Passed in, never created here.
|
|
69
|
+
- `permissionsBoundaryArn: string` (required). Applied to the instance role AND every per-crew task/execution role, matching the existing mandatory-boundary discipline (VERIFIED that `RemoteCrewInstanceProps.permissionsBoundaryArn` is required; the crew boundary on `FargateCrew` is optional today, but this spec passes it since the consumer has one).
|
|
70
|
+
- `instanceType?: ec2.InstanceType` (default `m7g.2xlarge` to match `RemoteCrewInstance`; the 55minutes consumer passes `new ec2.InstanceType('m9g.large')` or `m9g.xlarge`). The instance type STAYS a CDK parameter.
|
|
71
|
+
- `crewCount?: number` (CONSTRUCT DEFAULT = 1). One is identical to today's single-crew behaviour and protects existing consumers. The 55minutes consumer passes 3. Validate 1 to a small max (say 8). Strictly additive.
|
|
72
|
+
- `crews?: string[]` or a derived list. Per-crew names feed the existing `FargateCrew` `crew` regex (`^[a-z0-9]([a-z0-9-]{0,30}[a-z0-9])?$`) and derive log group / role / secret namespace. Default `['crew-1', 'crew-2', 'crew-3']` sliced to `crewCount`.
|
|
73
|
+
- `crewDataVolumeSizeGb?: number` (default 20 ESTIMATE). Per-crew durable EBS size, always encrypted.
|
|
74
|
+
- `crewDataVolumeType?: ec2.EbsDeviceVolumeType` (default `GP3`).
|
|
75
|
+
- `crewMemoryHardLimitMiB?: number` (ESTIMATE, see section 3a task-def) and `crewMemoryReservationMiB?: number` (soft) and optional `crewCpuShares?: number` (soft).
|
|
76
|
+
- `ecrRepositoryArn?: string` (optional; passed through to per-crew execution roles for a scoped pull grant, else public registry).
|
|
77
|
+
- `backupBucket?: ICrewBackupBucket`, `backupSchedule?`, `backupPrefix?` (reuse the existing types/wiring per crew).
|
|
78
|
+
- `stackTag?: string` (default `kirocrew`, same regex).
|
|
79
|
+
- `region`/`account` are NOT props: they come from the consumer's `Stack` env (VERIFIED that `RemoteCrewInstance` reads `Stack.of(this).region`, never a region prop).
|
|
80
|
+
|
|
81
|
+
Resources the construct creates (PROPOSED, reusing verified pieces):
|
|
82
|
+
|
|
83
|
+
1. ECS cluster: reuse `FargateCrewBase` (its `ecs.Cluster`), or create an `ecs.Cluster` directly in `EcsCrewHost`. Since `FargateCrewBase` disables container insights and names the cluster from the tag, reusing it keeps one code path. VERIFIED `FargateCrewBase` already builds exactly this cluster.
|
|
84
|
+
2. EC2 capacity: ONE self-provisioned m9g. Two shapes to pick from (OPEN, section 5):
|
|
85
|
+
- (a) A minimal Auto Scaling Group of size 1 with an `ecs.AsgCapacityProvider`, ECS-optimized arm64 AL2023 AMI (`ecs.EcsOptimizedImage.amazonLinux2023(ecs.AmiHardwareType.ARM)`), the ECS agent joining the cluster automatically. This is the CDK-idiomatic path and gives the WaitCondition-equivalent via ECS capacity provider health.
|
|
86
|
+
- (b) A single `ec2.Instance` (mirroring `RemoteCrewInstance`) whose UserData joins the ECS cluster (`echo ECS_CLUSTER=<name> >> /etc/ecs/ecs.config`) plus the host-NAT bootstrap. This reuses the verified `RemoteCrewInstance` provisioning shape (IMDSv2, encrypted gp3 root, SSM-only SG, boundary, bootstrap asset) but must self-register to ECS.
|
|
87
|
+
RECOMMENDED default: (b) single `ec2.Instance`, because it reuses the whole verified EC2 hardening and bootstrap-asset mechanism and matches "ONE self-provisioned EC2 instance the user owns" literally, and because a size-1 ASG buys little here (no scaling intended). Keep the instance's root volume the verified encrypted gp3 `/dev/xvda`.
|
|
88
|
+
3. Per-crew EBS data volumes: `crewCount` extra `ec2.CfnVolume` (or `blockDevices` entries on the instance), each `encrypted: true`, `volumeType: GP3`, `deleteOnTermination: false` (matching the `CrewBackupBucket` RETAIN intent so crew memory outlives an instance replacement). Attached to the host; the bootstrap mounts each under a per-crew path and each ECS task bind-mounts its own crew directory. Resolve devices by a stable attribute (NVMe volume mapping or a filesystem label written at provision time), NOT by assuming `/dev/sdf` equals the kernel device name (see cautions, section 6).
|
|
89
|
+
4. Per-crew ECS task definition + service:
|
|
90
|
+
- `ecs.Ec2TaskDefinition` per crew, `networkMode: ecs.NetworkMode.AWS_VPC` (awsvpc, so each task gets its own ENI + private VPC IP), `taskRole` and `executionRole` from a reused `FargateCrew` (VERIFIED those roles exist), `runtimePlatform` arm64.
|
|
91
|
+
- One container definition per task: the Kiro Crew image (public registry or scoped ECR), `memoryReservationMiB` (soft, so idle crews cost little), a hard `memory` cap (so no crew OOMs the others past its ceiling), optional soft `cpu` shares, log driver to the per-crew `/kirocrew/crew/<crew>` log group (VERIFIED that log group already exists in `FargateCrew`).
|
|
92
|
+
- A bind mount / host volume mapping the crew's mounted EBS path into the container so `~/.kiro/crew` is durable.
|
|
93
|
+
- `ecs.Ec2Service` per crew, `desiredCount: 1`, placed on the awsvpc private subnet with the egress-only task SG (VERIFIED `FargateCrewBase` builds that SG), `assignPublicIp` NOT set (tasks are fully private).
|
|
94
|
+
5. Networking: ONE small VPC is the consumer's to pass, but the construct creates/asserts the two-subnet host-NAT topology and the host-NAT bootstrap:
|
|
95
|
+
- PUBLIC subnet: holds the host ENI + Elastic IP + a route to the IGW.
|
|
96
|
+
- PRIVATE subnet: holds the crew task ENIs, route `0.0.0.0/0` -> the host ENI (the host does NAT). NO IGW route.
|
|
97
|
+
- Host NAT ("option B", host iptables NAT): the bootstrap sets `net.ipv4.ip_forward=1`, adds an iptables `MASQUERADE` rule on the host egress interface, and the construct DISABLES source/dest check on the host ENI (`ec2.CfnInstance` `sourceDestCheck: false`, or a `CfnNetworkInterface` with `SourceDestCheck: false`). Tasks have NO public IPs.
|
|
98
|
+
- EXPLICITLY no NAT Gateway, no fck-nat service, no VPC endpoints, no ALB, no PrivateLink.
|
|
99
|
+
6. Host-NAT + ECS-join bootstrap: extend the existing bootstrap-asset mechanism (VERIFIED: construct reads an asset, prepends a header, appends the body). Add a new asset (for example `src/assets/ecs-host-bootstrap.sh`) or extend `bootstrap.sh` with the ECS-host steps: write `/etc/ecs/ecs.config` with `ECS_CLUSTER=<name>` and awsvpc trunking enabled, enable IP forwarding, install the MASQUERADE iptables rule (persisted), mount each per-crew EBS volume (resolve-by-label), and keep the resumable-systemd discipline (VERIFIED the bootstrap already installs resumable oneshot units). The header gains `ECS_CLUSTER`, `CREW_COUNT`, and the per-crew volume-label list.
|
|
100
|
+
7. Optional `CrewWebhookIngress` sub-construct (PROPOSED, composable, mirrors `CrewBackupBucket`): each task has a real private VPC IP, so a Lambda-in-VPC can POST to a crew's `POST /api/hooks/agent` directly with NO VPC endpoint. Keep it a separate, optional construct the consumer instantiates and wires, exactly as `CrewBackupBucket` is optional and passed in. It creates (or references) an SNS topic and an in-VPC arm64 Lambda under the same boundary, plus a scoped task-SG ingress rule from the Lambda SG on the crew port only (the one controlled inbound exception to the egress-only model; state it in the README). NOTE: an earlier grep found NO RC2/RC3 webhook-token handling in the construct; confirmed still absent in this read (no hook/webhook/sns references in `src/`). Treat ingress as greenfield and say so; do not build it speculatively.
|
|
101
|
+
8. Backup per crew: reuse `src/assets/backup.sh` and `restore-from-s3.sh` (VERIFIED) with a per-crew prefix, or grant the per-crew task role write to the backup bucket so the running container pushes its own snapshots (VERIFIED `FargateCrew` already grants the TASK role backup write when `backupBucket` is set).
|
|
102
|
+
|
|
103
|
+
### 3b. 55minutes CONSUMER changes (thin)
|
|
104
|
+
|
|
105
|
+
A concrete stack in the 55minutes repo that imports the construct and passes real values. No AWS IDs hardcoded in the construct; the consumer supplies them here from CDK context or a config file it owns.
|
|
106
|
+
|
|
107
|
+
Facts the consumer supplies:
|
|
108
|
+
- Workload account 641001211783 via the `crew` profile; deploy account 969169896298 via `crew-deploy`.
|
|
109
|
+
- Region: Sydney `ap-southeast-2` (default here). Control plane is Auckland `ap-southeast-6`. Auckland has NO m9g, so the crews run in Sydney: this is a deliberate, flagged cross-region choice, added as a stack comment so it is visible, not accidental. `us-east-1` is the alternative if larger capacity is wanted.
|
|
110
|
+
- VPC, the two subnets (public host + private tasks), and the permissions boundary ARN for account 641001211783.
|
|
111
|
+
- `instanceType: new ec2.InstanceType('m9g.large')` (or `m9g.xlarge`), `crewCount: 3`.
|
|
112
|
+
|
|
113
|
+
Illustrative sketch (NOT final code):
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
const app = new App();
|
|
117
|
+
const stack = new Stack(app, 'FiftyFiveCrewFleet', {
|
|
118
|
+
env: { account: '641001211783', region: 'ap-southeast-2' }, // Sydney; control plane is Auckland ap-southeast-6 (cross-region, deliberate: no m9g in Auckland)
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const vpc = ec2.Vpc.fromLookup(stack, 'Vpc', { vpcId: /* from context */ });
|
|
122
|
+
const backup = new CrewBackupBucket(stack, 'Backup', {});
|
|
123
|
+
|
|
124
|
+
const host = new EcsCrewHost(stack, 'CrewFleet', {
|
|
125
|
+
vpc,
|
|
126
|
+
permissionsBoundaryArn: /* account-641001211783 boundary ARN, from context */,
|
|
127
|
+
instanceType: new ec2.InstanceType('m9g.large'),
|
|
128
|
+
crewCount: 3,
|
|
129
|
+
crewDataVolumeSizeGb: 20,
|
|
130
|
+
backupBucket: backup,
|
|
131
|
+
stackTag: 'fiftyfive',
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Optional, greenfield, only if inbound webhooks are needed:
|
|
135
|
+
// new CrewWebhookIngress(stack, 'Webhook', { vpc, host, crewCount: 3, permissionsBoundaryArn: /* same, from context */ });
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Every value marked "from context" is read from CDK context or a config file the 55minutes repo owns, not baked into the construct. Region is set on the stack env, not the construct.
|
|
139
|
+
|
|
140
|
+
## 4. Removed vs the old Firecracker spec
|
|
141
|
+
|
|
142
|
+
Nothing is removed from the EXISTING construct. The single-crew EC2 shape and the ECS scaffolding both stay; a `crewCount` of 1 with no webhook ingress is behaviourally what exists today.
|
|
143
|
+
|
|
144
|
+
What this spec DELETES relative to the old Firecracker spec: the entire microVM layer. No Firecracker, no jailer, no microVMs, no tap-per-VM + bridge, no per-VMM systemd jailer units, no cgroup-v2 slices around VMMs, no shared guest kernel/rootfs build, no host-side webhook adapter forwarding to guest gateway ports. ECS-on-EC2 replaces all of it: container isolation (shared kernel) is accepted as sufficient because Graviton5 Nitro Isolation protects the box from other AWS tenants and containers cover crew-to-crew separation. The old spec's sections 3.1-3.7 (Firecracker install, guest image, jailer units, tap/bridge, cgroup slices, host adapter) are all gone.
|
|
145
|
+
|
|
146
|
+
## 5. Additive-guard note
|
|
147
|
+
|
|
148
|
+
The construct default MUST NOT change behaviour for existing consumers. `crewCount` defaults to 1 at the construct level; the 55minutes consumer passes 3. A plain instantiation with no new props behaves as today. The old Firecracker spec's "default 3" was the consumer's value, not the construct default; this spec keeps 1 as the construct default and states it plainly.
|
|
149
|
+
|
|
150
|
+
## 6. Ordered task list (bounded steps)
|
|
151
|
+
|
|
152
|
+
Construct first, then consumer.
|
|
153
|
+
|
|
154
|
+
1. Add `EcsCrewHostProps` and the `EcsCrewHost` skeleton in `src/ecs-crew-host.ts`; export from `src/index.ts`. Add prop validation (`crewCount` 1..8, crew-name regex reuse, boundary required).
|
|
155
|
+
2. Create the ECS cluster (reuse `FargateCrewBase` or create `ecs.Cluster` in the new construct).
|
|
156
|
+
3. Create the single ECS-registered m9g EC2 capacity: recommended path is one `ec2.Instance` mirroring `RemoteCrewInstance` hardening (IMDSv2, encrypted gp3 `/dev/xvda`, SSM-only SG, boundary, resumable bootstrap) whose UserData joins the cluster via `/etc/ecs/ecs.config`; instance type stays a prop. (Alternative: size-1 ASG + `AsgCapacityProvider`.)
|
|
157
|
+
4. Per-crew EC2 task definition (`ecs.Ec2TaskDefinition`, `networkMode: AWS_VPC`, arm64 runtime, reused `FargateCrew` task+execution roles and `/kirocrew/crew/<crew>` log group) and container definition.
|
|
158
|
+
5. awsvpc networking + two subnets + host-NAT bootstrap: assert/consume PUBLIC subnet (host ENI + EIP + IGW route) and PRIVATE subnet (task ENIs, `0.0.0.0/0` -> host ENI, no IGW); disable source/dest check on the host ENI; bootstrap sets `ip_forward=1` and iptables `MASQUERADE`.
|
|
159
|
+
6. Per-crew EBS data volume (`encrypted`, `GP3`, `deleteOnTermination: false`) + host mount (resolve-by-label) + container bind mount for `~/.kiro/crew`.
|
|
160
|
+
7. Soft `memoryReservationMiB` + optional soft `cpu`, and a hard per-task `memory` cap, per crew.
|
|
161
|
+
8. Per-crew `ecs.Ec2Service` (`desiredCount: 1`, private subnet, egress-only task SG, no public IP).
|
|
162
|
+
9. Reuse per-crew backup (per-crew prefix, or task-role write to the backup bucket, both already supported by `FargateCrew`/the backup assets).
|
|
163
|
+
10. Optional `CrewWebhookIngress` sub-construct (`src/crew-webhook-ingress.ts`, greenfield: SNS + in-VPC arm64 Lambda under boundary + scoped task-SG ingress rule), export from `src/index.ts`. Build only if inbound webhooks are needed.
|
|
164
|
+
11. Add/extend jest tests (`test/ecs-crew-host.test.ts`, and a new `test/crew-webhook-ingress.test.ts` if built) asserting the cluster, capacity, per-crew task defs/services, EBS volumes, subnets, host-NAT source/dest-check, soft/hard limits, and that `crewCount` default 1 synthesizes one crew.
|
|
165
|
+
12. Run the full `npx projen build` (eslint + test + synth + package) as the done gate. Green there is the bar (the subset jest+synth is not enough: eslint runs in CI's build).
|
|
166
|
+
13. Update `README.md` and `API.md` for the new construct, props, the ECS-on-EC2 shape, the two-subnet host-NAT model, and the one scoped inbound SG exception (webhook, if built).
|
|
167
|
+
|
|
168
|
+
Consumer (55minutes):
|
|
169
|
+
|
|
170
|
+
14. Create the `FiftyFiveCrewFleet` stack importing `EcsCrewHost`, `CrewBackupBucket`, and (optionally) `CrewWebhookIngress`, in account 641001211783 via the `crew` profile.
|
|
171
|
+
15. Supply VPC, the two subnets, permissions boundary ARN, region (Sydney), instance type (m9g.large/xlarge), and crew count (3) from context.
|
|
172
|
+
16. Add the cross-region comment (crews Sydney, control plane Auckland).
|
|
173
|
+
17. Synth and review the consumer stack (no deploy in this spec's scope).
|
|
174
|
+
|
|
175
|
+
Ordered task-step count: 17 (13 construct steps, 4 consumer steps).
|
|
176
|
+
|
|
177
|
+
## 7. Open decisions (with recommended defaults)
|
|
178
|
+
|
|
179
|
+
1. New construct vs extend `RemoteCrewInstance`. RECOMMENDED: new `EcsCrewHost` construct (reuses `FargateCrewBase`/`FargateCrew` + `RemoteCrewInstance` hardening), because ECS-on-EC2 is a different enough shape that a variant flag would branch heavily. Confirm.
|
|
180
|
+
2. Capacity shape: single `ec2.Instance` (RECOMMENDED, reuses verified hardening, matches "one self-provisioned EC2") vs size-1 ASG + `AsgCapacityProvider` (CDK-idiomatic but adds an ASG for no scaling benefit). Confirm.
|
|
181
|
+
3. Region: Sydney `ap-southeast-2` (RECOMMENDED, nearest m9g to the Auckland control plane) vs `us-east-1` (largest capacity). Auckland is OUT (no m9g).
|
|
182
|
+
4. Start size: m9g.large (cheapest, tight for three tasks + host ENIs, confirm ENI budget in section 8) vs m9g.xlarge (comfortable). Instance type is a prop either way.
|
|
183
|
+
5. Overcommit vs hard caps: soft `memoryReservationMiB` allowing the sum of reservations to exceed nothing while hard `memory` caps sum near or below physical RAM minus host headroom (RECOMMENDED: soft reservation for scheduling, hard `memory` per task so a runaway crew cannot exceed its ceiling; accept the host OOM cross-crew blast-radius caveat in section 8).
|
|
184
|
+
6. Per-crew durable data: EBS (RECOMMENDED default, small gp3 per crew, `deleteOnTermination: false`) vs EFS (alternative, one filesystem with per-crew access points, survives instance loss without re-attach but adds an NFS mount and cost). Default EBS.
|
|
185
|
+
7. Webhook reuse: no RC2/RC3 webhook-token code found in the construct. Confirm whether that handling exists elsewhere (another repo or the upstream KiroCrew runtime) to reuse, rather than building the Lambda + host handler fresh.
|
|
186
|
+
|
|
187
|
+
## 8. Honest cautions
|
|
188
|
+
|
|
189
|
+
- Host OOM cross-crew blast radius: containers share the host kernel, so if the sum of actual usage exceeds physical RAM the host OOM-killer can kill a process in ANY crew's container, not only the crew that overcommitted. Hard per-task `memory` caps bound each task's ceiling but do NOT prevent the aggregate from exceeding RAM when reservations are soft. Set the sum of hard `memory` caps at or below (physical RAM minus host + ECS-agent headroom) if a hard guarantee is wanted; otherwise accept that a busy crew can pressure siblings. This is real and cannot be designed away at the container layer; it is the accepted cost of shared-kernel isolation vs microVMs.
|
|
190
|
+
- awsvpc ENI-per-task limit: awsvpc mode gives each task its own ENI, which counts against the instance's ENI limit. The host itself uses one ENI (public subnet); three crew task ENIs plus the host ENI is four ENIs. Confirm the chosen m9g size supports at least (crewCount + 1) ENIs (m9g.large and m9g.xlarge ENI limits are ESTIMATE until checked against the current EC2 ENI-per-instance-type table; ECS "ENI trunking"/awsvpcTrunking raises the per-instance task-ENI budget and may be needed on smaller sizes). This is a hard capacity gate: if the size cannot carry crewCount + 1 ENIs, either enable ENI trunking or step up the instance size. Mark VERIFY-BEFORE-BUILD.
|
|
191
|
+
- EBS NVMe device naming: on Nitro (all Graviton), a CDK/EBS device name like `/dev/sdf` surfaces as `/dev/nvme1n1`, and the number is not stable across reattach. The bootstrap MUST resolve each per-crew volume by a stable attribute (a filesystem label written at first provision, or the NVMe volume-id mapping), NOT by hardcoding `/dev/sdf`. Getting this wrong mounts the wrong crew's state into a container. Flagged as an implementation detail for the bootstrap, ESTIMATE on exact resolve mechanism.
|
|
192
|
+
- ECS agent + host NAT interaction: the host runs both the ECS agent (managing task ENIs) and iptables MASQUERADE for task egress. Confirm during build that awsvpc task ENIs route through the host's MASQUERADE correctly and that the ECS agent's own CNI plumbing does not conflict with the host NAT rules. Mark VERIFY-BEFORE-BUILD.
|
|
193
|
+
|
|
194
|
+
## 9. Where the real construct source DIVERGED from the old Firecracker spec's claims
|
|
195
|
+
|
|
196
|
+
- BIGGEST: the old spec described the ECS/Fargate lane as merely "untouched and simply not used". In fact `src/fargate-crew-base.ts` and `src/fargate-crew.ts` are a substantial, verified ECS foundation (an `ecs.Cluster`, an egress-only awsvpc task SG, per-crew task role, execution role with scoped secret read, and per-crew log group). For an ECS-on-EC2 build this is reusable baseline, not dead weight. This spec builds ON it.
|
|
197
|
+
- The old spec's section 2a said the per-crew data-volume default was 20 GiB and, separately, referenced the root volume; the REAL root-volume default in `remote-crew-instance-props.ts` is `volumeSizeGb` default 60 (not 20). The 20 was the proposed per-crew data volume, kept as an ESTIMATE here.
|
|
198
|
+
- The old spec proposed a `crewCount` default of 3 "in the design doc" and had to add a guard note that the CONSTRUCT default must be 1. Confirmed here: nothing in the current source has any `crewCount`, so it is genuinely new and this spec sets the construct default to 1 from the outset.
|
|
199
|
+
- The old spec's `CrewWebhookIngress` assumed a possible per-crew VPC endpoint. For ECS-on-EC2 with awsvpc, each task already has a real private VPC IP, so a Lambda-in-VPC reaches it directly and NO VPC endpoint is needed. Simpler than the microVM host-adapter fan-out the old spec described.
|
|
200
|
+
- Confirmed still true: no `hook`/`webhook`/`sns`/`firecracker`/`jailer` references anywhere in `src/` (the old spec's grep finding holds), so webhook ingress remains greenfield.
|
|
@@ -53,7 +53,7 @@ const constructs_1 = require("constructs");
|
|
|
53
53
|
* {@link grantRead} to let a replacement instance pull them for restore.
|
|
54
54
|
*/
|
|
55
55
|
class CrewBackupBucket extends constructs_1.Construct {
|
|
56
|
-
static [JSII_RTTI_SYMBOL_1] = { fqn: "@raindancers/raindancers-crew.CrewBackupBucket", version: "0.0.
|
|
56
|
+
static [JSII_RTTI_SYMBOL_1] = { fqn: "@raindancers/raindancers-crew.CrewBackupBucket", version: "0.0.2" };
|
|
57
57
|
/** The backup bucket. */
|
|
58
58
|
bucket;
|
|
59
59
|
/** The KMS key encrypting the bucket. */
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Duration } from 'aws-cdk-lib';
|
|
2
|
+
import * as ec2 from 'aws-cdk-lib/aws-ec2';
|
|
3
|
+
import * as lambda from 'aws-cdk-lib/aws-lambda';
|
|
4
|
+
import * as sns from 'aws-cdk-lib/aws-sns';
|
|
5
|
+
import { Construct } from 'constructs';
|
|
6
|
+
import { EcsCrewHost } from './ecs-crew-host';
|
|
7
|
+
/**
|
|
8
|
+
* Properties for {@link CrewWebhookIngress}.
|
|
9
|
+
*/
|
|
10
|
+
export interface CrewWebhookIngressProps {
|
|
11
|
+
/**
|
|
12
|
+
* VPC to place the ingress Lambda in. Must be the same VPC as the crew tasks
|
|
13
|
+
* so the Lambda can reach a task's private ENI IP directly with NO VPC
|
|
14
|
+
* endpoint.
|
|
15
|
+
*/
|
|
16
|
+
readonly vpc: ec2.IVpc;
|
|
17
|
+
/**
|
|
18
|
+
* The crew host whose tasks receive webhooks. The task security group is
|
|
19
|
+
* granted a scoped inbound rule from this ingress Lambda's SG on the crew
|
|
20
|
+
* port only — the one controlled inbound exception to the egress-only model.
|
|
21
|
+
*/
|
|
22
|
+
readonly host: EcsCrewHost;
|
|
23
|
+
/**
|
|
24
|
+
* ARN of an IAM permissions boundary applied to the Lambda's execution role.
|
|
25
|
+
* The ingress Lambda runs consumer-triggered code, so it carries a boundary
|
|
26
|
+
* matching the rest of the construct.
|
|
27
|
+
*/
|
|
28
|
+
readonly permissionsBoundaryArn: string;
|
|
29
|
+
/**
|
|
30
|
+
* The code the ingress Lambda runs. The consumer supplies it: this construct
|
|
31
|
+
* wires the plumbing (SNS -> in-VPC Lambda -> task private IP) but does not
|
|
32
|
+
* ship an opinion about how a webhook payload maps to a crew.
|
|
33
|
+
*/
|
|
34
|
+
readonly code: lambda.Code;
|
|
35
|
+
/**
|
|
36
|
+
* The Lambda handler entry point.
|
|
37
|
+
*
|
|
38
|
+
* @default 'index.handler'
|
|
39
|
+
*/
|
|
40
|
+
readonly handler?: string;
|
|
41
|
+
/**
|
|
42
|
+
* The Lambda runtime. Must be an arm64-compatible runtime to match the
|
|
43
|
+
* arm64 default host.
|
|
44
|
+
*
|
|
45
|
+
* @default lambda.Runtime.NODEJS_20_X
|
|
46
|
+
*/
|
|
47
|
+
readonly runtime?: lambda.Runtime;
|
|
48
|
+
/**
|
|
49
|
+
* Lambda architecture. Defaults to arm64 to match the Graviton host.
|
|
50
|
+
*
|
|
51
|
+
* @default lambda.Architecture.ARM_64
|
|
52
|
+
*/
|
|
53
|
+
readonly architecture?: lambda.Architecture;
|
|
54
|
+
/**
|
|
55
|
+
* Subnets to place the ingress Lambda's ENIs in. Should be the same private
|
|
56
|
+
* subnet the crew tasks run in so the Lambda reaches task IPs directly.
|
|
57
|
+
*
|
|
58
|
+
* @default - the VPC's private-with-egress subnets
|
|
59
|
+
*/
|
|
60
|
+
readonly vpcSubnets?: ec2.SubnetSelection;
|
|
61
|
+
/**
|
|
62
|
+
* TCP port on the crew task the Lambda reaches (the gateway/webhook port).
|
|
63
|
+
*
|
|
64
|
+
* @default 5476
|
|
65
|
+
*/
|
|
66
|
+
readonly crewPort?: number;
|
|
67
|
+
/**
|
|
68
|
+
* An existing SNS topic to subscribe the Lambda to. Omit to create one.
|
|
69
|
+
*
|
|
70
|
+
* @default - a new topic is created
|
|
71
|
+
*/
|
|
72
|
+
readonly topic?: sns.ITopic;
|
|
73
|
+
/**
|
|
74
|
+
* Lambda timeout.
|
|
75
|
+
*
|
|
76
|
+
* @default Duration.seconds(30)
|
|
77
|
+
*/
|
|
78
|
+
readonly timeout?: Duration;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* OPTIONAL, composable webhook-ingress path for {@link EcsCrewHost}, mirroring
|
|
82
|
+
* the optional {@link CrewBackupBucket} shape. Nothing is created unless the
|
|
83
|
+
* consumer instantiates it, so an existing consumer gains no new resources.
|
|
84
|
+
*
|
|
85
|
+
* Because each crew task runs in awsvpc mode with a real private VPC IP, an
|
|
86
|
+
* in-VPC Lambda can POST to a crew's `POST /api/hooks/agent` DIRECTLY — no VPC
|
|
87
|
+
* endpoint, no PrivateLink, no ALB. This construct wires: an SNS topic, an
|
|
88
|
+
* in-VPC Lambda under the permissions boundary, and a scoped ingress rule on
|
|
89
|
+
* the crew task security group from the Lambda's SG on the crew port ONLY.
|
|
90
|
+
* That ingress rule is the single controlled inbound exception to the
|
|
91
|
+
* egress-only task model.
|
|
92
|
+
*
|
|
93
|
+
* Greenfield: the construct wires the plumbing but ships no webhook-to-crew
|
|
94
|
+
* routing opinion — the consumer supplies the Lambda {@link CrewWebhookIngressProps.code}.
|
|
95
|
+
*/
|
|
96
|
+
export declare class CrewWebhookIngress extends Construct {
|
|
97
|
+
/** The SNS topic that fans events into the ingress Lambda. */
|
|
98
|
+
readonly topic: sns.ITopic;
|
|
99
|
+
/** The in-VPC ingress Lambda. */
|
|
100
|
+
readonly function: lambda.Function;
|
|
101
|
+
/** The ingress Lambda's security group (the source of the scoped task ingress rule). */
|
|
102
|
+
readonly securityGroup: ec2.SecurityGroup;
|
|
103
|
+
constructor(scope: Construct, id: string, props: CrewWebhookIngressProps);
|
|
104
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.CrewWebhookIngress = void 0;
|
|
37
|
+
const JSII_RTTI_SYMBOL_1 = Symbol.for("jsii.rtti");
|
|
38
|
+
const aws_cdk_lib_1 = require("aws-cdk-lib");
|
|
39
|
+
const ec2 = __importStar(require("aws-cdk-lib/aws-ec2"));
|
|
40
|
+
const iam = __importStar(require("aws-cdk-lib/aws-iam"));
|
|
41
|
+
const lambda = __importStar(require("aws-cdk-lib/aws-lambda"));
|
|
42
|
+
const sns = __importStar(require("aws-cdk-lib/aws-sns"));
|
|
43
|
+
const subs = __importStar(require("aws-cdk-lib/aws-sns-subscriptions"));
|
|
44
|
+
const constructs_1 = require("constructs");
|
|
45
|
+
const DEFAULT_CREW_PORT = 5476;
|
|
46
|
+
/**
|
|
47
|
+
* OPTIONAL, composable webhook-ingress path for {@link EcsCrewHost}, mirroring
|
|
48
|
+
* the optional {@link CrewBackupBucket} shape. Nothing is created unless the
|
|
49
|
+
* consumer instantiates it, so an existing consumer gains no new resources.
|
|
50
|
+
*
|
|
51
|
+
* Because each crew task runs in awsvpc mode with a real private VPC IP, an
|
|
52
|
+
* in-VPC Lambda can POST to a crew's `POST /api/hooks/agent` DIRECTLY — no VPC
|
|
53
|
+
* endpoint, no PrivateLink, no ALB. This construct wires: an SNS topic, an
|
|
54
|
+
* in-VPC Lambda under the permissions boundary, and a scoped ingress rule on
|
|
55
|
+
* the crew task security group from the Lambda's SG on the crew port ONLY.
|
|
56
|
+
* That ingress rule is the single controlled inbound exception to the
|
|
57
|
+
* egress-only task model.
|
|
58
|
+
*
|
|
59
|
+
* Greenfield: the construct wires the plumbing but ships no webhook-to-crew
|
|
60
|
+
* routing opinion — the consumer supplies the Lambda {@link CrewWebhookIngressProps.code}.
|
|
61
|
+
*/
|
|
62
|
+
class CrewWebhookIngress extends constructs_1.Construct {
|
|
63
|
+
static [JSII_RTTI_SYMBOL_1] = { fqn: "@raindancers/raindancers-crew.CrewWebhookIngress", version: "0.0.2" };
|
|
64
|
+
/** The SNS topic that fans events into the ingress Lambda. */
|
|
65
|
+
topic;
|
|
66
|
+
/** The in-VPC ingress Lambda. */
|
|
67
|
+
function;
|
|
68
|
+
/** The ingress Lambda's security group (the source of the scoped task ingress rule). */
|
|
69
|
+
securityGroup;
|
|
70
|
+
constructor(scope, id, props) {
|
|
71
|
+
super(scope, id);
|
|
72
|
+
const crewPort = props.crewPort ?? DEFAULT_CREW_PORT;
|
|
73
|
+
this.securityGroup = new ec2.SecurityGroup(this, 'IngressLambdaSg', {
|
|
74
|
+
vpc: props.vpc,
|
|
75
|
+
description: 'KiroCrew webhook-ingress Lambda - reaches crew task private IPs',
|
|
76
|
+
allowAllOutbound: true,
|
|
77
|
+
});
|
|
78
|
+
const executionRole = new iam.Role(this, 'IngressLambdaRole', {
|
|
79
|
+
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
|
|
80
|
+
permissionsBoundary: iam.ManagedPolicy.fromManagedPolicyArn(this, 'Boundary', props.permissionsBoundaryArn),
|
|
81
|
+
managedPolicies: [
|
|
82
|
+
// The Lambda runs in a VPC, so it needs the ENI-management actions.
|
|
83
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaVPCAccessExecutionRole'),
|
|
84
|
+
],
|
|
85
|
+
});
|
|
86
|
+
this.function = new lambda.Function(this, 'IngressLambda', {
|
|
87
|
+
runtime: props.runtime ?? lambda.Runtime.NODEJS_20_X,
|
|
88
|
+
architecture: props.architecture ?? lambda.Architecture.ARM_64,
|
|
89
|
+
handler: props.handler ?? 'index.handler',
|
|
90
|
+
code: props.code,
|
|
91
|
+
role: executionRole,
|
|
92
|
+
vpc: props.vpc,
|
|
93
|
+
vpcSubnets: props.vpcSubnets ?? {
|
|
94
|
+
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
|
|
95
|
+
},
|
|
96
|
+
securityGroups: [this.securityGroup],
|
|
97
|
+
timeout: props.timeout ?? aws_cdk_lib_1.Duration.seconds(30),
|
|
98
|
+
environment: {
|
|
99
|
+
CREW_PORT: String(crewPort),
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
this.topic = props.topic ?? new sns.Topic(this, 'IngressTopic');
|
|
103
|
+
this.topic.addSubscription(new subs.LambdaSubscription(this.function));
|
|
104
|
+
// The one controlled inbound exception to the egress-only task model: the
|
|
105
|
+
// crew task SG accepts the crew port FROM the ingress Lambda SG only, never
|
|
106
|
+
// a CIDR. Documented in the README.
|
|
107
|
+
props.host.base.securityGroup.addIngressRule(ec2.Peer.securityGroupId(this.securityGroup.securityGroupId), ec2.Port.tcp(crewPort), 'Webhook ingress from the in-VPC ingress Lambda SG only (no CIDR)');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
exports.CrewWebhookIngress = CrewWebhookIngress;
|
|
111
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3Jldy13ZWJob29rLWluZ3Jlc3MuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvY3Jldy13ZWJob29rLWluZ3Jlc3MudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLDZDQUF1QztBQUN2Qyx5REFBMkM7QUFDM0MseURBQTJDO0FBQzNDLCtEQUFpRDtBQUNqRCx5REFBMkM7QUFDM0Msd0VBQTBEO0FBQzFELDJDQUF1QztBQUd2QyxNQUFNLGlCQUFpQixHQUFHLElBQUksQ0FBQztBQXNGL0I7Ozs7Ozs7Ozs7Ozs7OztHQWVHO0FBQ0gsTUFBYSxrQkFBbUIsU0FBUSxzQkFBUzs7SUFDL0MsOERBQThEO0lBQzlDLEtBQUssQ0FBYTtJQUNsQyxpQ0FBaUM7SUFDakIsUUFBUSxDQUFrQjtJQUMxQyx3RkFBd0Y7SUFDeEUsYUFBYSxDQUFvQjtJQUVqRCxZQUFZLEtBQWdCLEVBQUUsRUFBVSxFQUFFLEtBQThCO1FBQ3RFLEtBQUssQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFFakIsTUFBTSxRQUFRLEdBQUcsS0FBSyxDQUFDLFFBQVEsSUFBSSxpQkFBaUIsQ0FBQztRQUVyRCxJQUFJLENBQUMsYUFBYSxHQUFHLElBQUksR0FBRyxDQUFDLGFBQWEsQ0FBQyxJQUFJLEVBQUUsaUJBQWlCLEVBQUU7WUFDbEUsR0FBRyxFQUFFLEtBQUssQ0FBQyxHQUFHO1lBQ2QsV0FBVyxFQUFFLGlFQUFpRTtZQUM5RSxnQkFBZ0IsRUFBRSxJQUFJO1NBQ3ZCLENBQUMsQ0FBQztRQUVILE1BQU0sYUFBYSxHQUFHLElBQUksR0FBRyxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsbUJBQW1CLEVBQUU7WUFDNUQsU0FBUyxFQUFFLElBQUksR0FBRyxDQUFDLGdCQUFnQixDQUFDLHNCQUFzQixDQUFDO1lBQzNELG1CQUFtQixFQUFFLEdBQUcsQ0FBQyxhQUFhLENBQUMsb0JBQW9CLENBQ3pELElBQUksRUFDSixVQUFVLEVBQ1YsS0FBSyxDQUFDLHNCQUFzQixDQUM3QjtZQUNELGVBQWUsRUFBRTtnQkFDZixvRUFBb0U7Z0JBQ3BFLEdBQUcsQ0FBQyxhQUFhLENBQUMsd0JBQXdCLENBQ3hDLDhDQUE4QyxDQUMvQzthQUNGO1NBQ0YsQ0FBQyxDQUFDO1FBRUgsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLE1BQU0sQ0FBQyxRQUFRLENBQUMsSUFBSSxFQUFFLGVBQWUsRUFBRTtZQUN6RCxPQUFPLEVBQUUsS0FBSyxDQUFDLE9BQU8sSUFBSSxNQUFNLENBQUMsT0FBTyxDQUFDLFdBQVc7WUFDcEQsWUFBWSxFQUFFLEtBQUssQ0FBQyxZQUFZLElBQUksTUFBTSxDQUFDLFlBQVksQ0FBQyxNQUFNO1lBQzlELE9BQU8sRUFBRSxLQUFLLENBQUMsT0FBTyxJQUFJLGVBQWU7WUFDekMsSUFBSSxFQUFFLEtBQUssQ0FBQyxJQUFJO1lBQ2hCLElBQUksRUFBRSxhQUFhO1lBQ25CLEdBQUcsRUFBRSxLQUFLLENBQUMsR0FBRztZQUNkLFVBQVUsRUFBRSxLQUFLLENBQUMsVUFBVSxJQUFJO2dCQUM5QixVQUFVLEVBQUUsR0FBRyxDQUFDLFVBQVUsQ0FBQyxtQkFBbUI7YUFDL0M7WUFDRCxjQUFjLEVBQUUsQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDO1lBQ3BDLE9BQU8sRUFBRSxLQUFLLENBQUMsT0FBTyxJQUFJLHNCQUFRLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztZQUM5QyxXQUFXLEVBQUU7Z0JBQ1gsU0FBUyxFQUFFLE1BQU0sQ0FBQyxRQUFRLENBQUM7YUFDNUI7U0FDRixDQUFDLENBQUM7UUFFSCxJQUFJLENBQUMsS0FBSyxHQUFHLEtBQUssQ0FBQyxLQUFLLElBQUksSUFBSSxHQUFHLENBQUMsS0FBSyxDQUFDLElBQUksRUFBRSxjQUFjLENBQUMsQ0FBQztRQUNoRSxJQUFJLENBQUMsS0FBSyxDQUFDLGVBQWUsQ0FBQyxJQUFJLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQztRQUV2RSwwRUFBMEU7UUFDMUUsNEVBQTRFO1FBQzVFLG9DQUFvQztRQUNwQyxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsY0FBYyxDQUMxQyxHQUFHLENBQUMsSUFBSSxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLGVBQWUsQ0FBQyxFQUM1RCxHQUFHLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUMsRUFDdEIsa0VBQWtFLENBQ25FLENBQUM7SUFDSixDQUFDOztBQTlESCxnREErREMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBEdXJhdGlvbiB9IGZyb20gJ2F3cy1jZGstbGliJztcbmltcG9ydCAqIGFzIGVjMiBmcm9tICdhd3MtY2RrLWxpYi9hd3MtZWMyJztcbmltcG9ydCAqIGFzIGlhbSBmcm9tICdhd3MtY2RrLWxpYi9hd3MtaWFtJztcbmltcG9ydCAqIGFzIGxhbWJkYSBmcm9tICdhd3MtY2RrLWxpYi9hd3MtbGFtYmRhJztcbmltcG9ydCAqIGFzIHNucyBmcm9tICdhd3MtY2RrLWxpYi9hd3Mtc25zJztcbmltcG9ydCAqIGFzIHN1YnMgZnJvbSAnYXdzLWNkay1saWIvYXdzLXNucy1zdWJzY3JpcHRpb25zJztcbmltcG9ydCB7IENvbnN0cnVjdCB9IGZyb20gJ2NvbnN0cnVjdHMnO1xuaW1wb3J0IHsgRWNzQ3Jld0hvc3QgfSBmcm9tICcuL2Vjcy1jcmV3LWhvc3QnO1xuXG5jb25zdCBERUZBVUxUX0NSRVdfUE9SVCA9IDU0NzY7XG5cbi8qKlxuICogUHJvcGVydGllcyBmb3Ige0BsaW5rIENyZXdXZWJob29rSW5ncmVzc30uXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgQ3Jld1dlYmhvb2tJbmdyZXNzUHJvcHMge1xuICAvKipcbiAgICogVlBDIHRvIHBsYWNlIHRoZSBpbmdyZXNzIExhbWJkYSBpbi4gTXVzdCBiZSB0aGUgc2FtZSBWUEMgYXMgdGhlIGNyZXcgdGFza3NcbiAgICogc28gdGhlIExhbWJkYSBjYW4gcmVhY2ggYSB0YXNrJ3MgcHJpdmF0ZSBFTkkgSVAgZGlyZWN0bHkgd2l0aCBOTyBWUENcbiAgICogZW5kcG9pbnQuXG4gICAqL1xuICByZWFkb25seSB2cGM6IGVjMi5JVnBjO1xuXG4gIC8qKlxuICAgKiBUaGUgY3JldyBob3N0IHdob3NlIHRhc2tzIHJlY2VpdmUgd2ViaG9va3MuIFRoZSB0YXNrIHNlY3VyaXR5IGdyb3VwIGlzXG4gICAqIGdyYW50ZWQgYSBzY29wZWQgaW5ib3VuZCBydWxlIGZyb20gdGhpcyBpbmdyZXNzIExhbWJkYSdzIFNHIG9uIHRoZSBjcmV3XG4gICAqIHBvcnQgb25seSDigJQgdGhlIG9uZSBjb250cm9sbGVkIGluYm91bmQgZXhjZXB0aW9uIHRvIHRoZSBlZ3Jlc3Mtb25seSBtb2RlbC5cbiAgICovXG4gIHJlYWRvbmx5IGhvc3Q6IEVjc0NyZXdIb3N0O1xuXG4gIC8qKlxuICAgKiBBUk4gb2YgYW4gSUFNIHBlcm1pc3Npb25zIGJvdW5kYXJ5IGFwcGxpZWQgdG8gdGhlIExhbWJkYSdzIGV4ZWN1dGlvbiByb2xlLlxuICAgKiBUaGUgaW5ncmVzcyBMYW1iZGEgcnVucyBjb25zdW1lci10cmlnZ2VyZWQgY29kZSwgc28gaXQgY2FycmllcyBhIGJvdW5kYXJ5XG4gICAqIG1hdGNoaW5nIHRoZSByZXN0IG9mIHRoZSBjb25zdHJ1Y3QuXG4gICAqL1xuICByZWFkb25seSBwZXJtaXNzaW9uc0JvdW5kYXJ5QXJuOiBzdHJpbmc7XG5cbiAgLyoqXG4gICAqIFRoZSBjb2RlIHRoZSBpbmdyZXNzIExhbWJkYSBydW5zLiBUaGUgY29uc3VtZXIgc3VwcGxpZXMgaXQ6IHRoaXMgY29uc3RydWN0XG4gICAqIHdpcmVzIHRoZSBwbHVtYmluZyAoU05TIC0+IGluLVZQQyBMYW1iZGEgLT4gdGFzayBwcml2YXRlIElQKSBidXQgZG9lcyBub3RcbiAgICogc2hpcCBhbiBvcGluaW9uIGFib3V0IGhvdyBhIHdlYmhvb2sgcGF5bG9hZCBtYXBzIHRvIGEgY3Jldy5cbiAgICovXG4gIHJlYWRvbmx5IGNvZGU6IGxhbWJkYS5Db2RlO1xuXG4gIC8qKlxuICAgKiBUaGUgTGFtYmRhIGhhbmRsZXIgZW50cnkgcG9pbnQuXG4gICAqXG4gICAqIEBkZWZhdWx0ICdpbmRleC5oYW5kbGVyJ1xuICAgKi9cbiAgcmVhZG9ubHkgaGFuZGxlcj86IHN0cmluZztcblxuICAvKipcbiAgICogVGhlIExhbWJkYSBydW50aW1lLiBNdXN0IGJlIGFuIGFybTY0LWNvbXBhdGlibGUgcnVudGltZSB0byBtYXRjaCB0aGVcbiAgICogYXJtNjQgZGVmYXVsdCBob3N0LlxuICAgKlxuICAgKiBAZGVmYXVsdCBsYW1iZGEuUnVudGltZS5OT0RFSlNfMjBfWFxuICAgKi9cbiAgcmVhZG9ubHkgcnVudGltZT86IGxhbWJkYS5SdW50aW1lO1xuXG4gIC8qKlxuICAgKiBMYW1iZGEgYXJjaGl0ZWN0dXJlLiBEZWZhdWx0cyB0byBhcm02NCB0byBtYXRjaCB0aGUgR3Jhdml0b24gaG9zdC5cbiAgICpcbiAgICogQGRlZmF1bHQgbGFtYmRhLkFyY2hpdGVjdHVyZS5BUk1fNjRcbiAgICovXG4gIHJlYWRvbmx5IGFyY2hpdGVjdHVyZT86IGxhbWJkYS5BcmNoaXRlY3R1cmU7XG5cbiAgLyoqXG4gICAqIFN1Ym5ldHMgdG8gcGxhY2UgdGhlIGluZ3Jlc3MgTGFtYmRhJ3MgRU5JcyBpbi4gU2hvdWxkIGJlIHRoZSBzYW1lIHByaXZhdGVcbiAgICogc3VibmV0IHRoZSBjcmV3IHRhc2tzIHJ1biBpbiBzbyB0aGUgTGFtYmRhIHJlYWNoZXMgdGFzayBJUHMgZGlyZWN0bHkuXG4gICAqXG4gICAqIEBkZWZhdWx0IC0gdGhlIFZQQydzIHByaXZhdGUtd2l0aC1lZ3Jlc3Mgc3VibmV0c1xuICAgKi9cbiAgcmVhZG9ubHkgdnBjU3VibmV0cz86IGVjMi5TdWJuZXRTZWxlY3Rpb247XG5cbiAgLyoqXG4gICAqIFRDUCBwb3J0IG9uIHRoZSBjcmV3IHRhc2sgdGhlIExhbWJkYSByZWFjaGVzICh0aGUgZ2F0ZXdheS93ZWJob29rIHBvcnQpLlxuICAgKlxuICAgKiBAZGVmYXVsdCA1NDc2XG4gICAqL1xuICByZWFkb25seSBjcmV3UG9ydD86IG51bWJlcjtcblxuICAvKipcbiAgICogQW4gZXhpc3RpbmcgU05TIHRvcGljIHRvIHN1YnNjcmliZSB0aGUgTGFtYmRhIHRvLiBPbWl0IHRvIGNyZWF0ZSBvbmUuXG4gICAqXG4gICAqIEBkZWZhdWx0IC0gYSBuZXcgdG9waWMgaXMgY3JlYXRlZFxuICAgKi9cbiAgcmVhZG9ubHkgdG9waWM/OiBzbnMuSVRvcGljO1xuXG4gIC8qKlxuICAgKiBMYW1iZGEgdGltZW91dC5cbiAgICpcbiAgICogQGRlZmF1bHQgRHVyYXRpb24uc2Vjb25kcygzMClcbiAgICovXG4gIHJlYWRvbmx5IHRpbWVvdXQ/OiBEdXJhdGlvbjtcbn1cblxuLyoqXG4gKiBPUFRJT05BTCwgY29tcG9zYWJsZSB3ZWJob29rLWluZ3Jlc3MgcGF0aCBmb3Ige0BsaW5rIEVjc0NyZXdIb3N0fSwgbWlycm9yaW5nXG4gKiB0aGUgb3B0aW9uYWwge0BsaW5rIENyZXdCYWNrdXBCdWNrZXR9IHNoYXBlLiBOb3RoaW5nIGlzIGNyZWF0ZWQgdW5sZXNzIHRoZVxuICogY29uc3VtZXIgaW5zdGFudGlhdGVzIGl0LCBzbyBhbiBleGlzdGluZyBjb25zdW1lciBnYWlucyBubyBuZXcgcmVzb3VyY2VzLlxuICpcbiAqIEJlY2F1c2UgZWFjaCBjcmV3IHRhc2sgcnVucyBpbiBhd3N2cGMgbW9kZSB3aXRoIGEgcmVhbCBwcml2YXRlIFZQQyBJUCwgYW5cbiAqIGluLVZQQyBMYW1iZGEgY2FuIFBPU1QgdG8gYSBjcmV3J3MgYFBPU1QgL2FwaS9ob29rcy9hZ2VudGAgRElSRUNUTFkg4oCUIG5vIFZQQ1xuICogZW5kcG9pbnQsIG5vIFByaXZhdGVMaW5rLCBubyBBTEIuIFRoaXMgY29uc3RydWN0IHdpcmVzOiBhbiBTTlMgdG9waWMsIGFuXG4gKiBpbi1WUEMgTGFtYmRhIHVuZGVyIHRoZSBwZXJtaXNzaW9ucyBib3VuZGFyeSwgYW5kIGEgc2NvcGVkIGluZ3Jlc3MgcnVsZSBvblxuICogdGhlIGNyZXcgdGFzayBzZWN1cml0eSBncm91cCBmcm9tIHRoZSBMYW1iZGEncyBTRyBvbiB0aGUgY3JldyBwb3J0IE9OTFkuXG4gKiBUaGF0IGluZ3Jlc3MgcnVsZSBpcyB0aGUgc2luZ2xlIGNvbnRyb2xsZWQgaW5ib3VuZCBleGNlcHRpb24gdG8gdGhlXG4gKiBlZ3Jlc3Mtb25seSB0YXNrIG1vZGVsLlxuICpcbiAqIEdyZWVuZmllbGQ6IHRoZSBjb25zdHJ1Y3Qgd2lyZXMgdGhlIHBsdW1iaW5nIGJ1dCBzaGlwcyBubyB3ZWJob29rLXRvLWNyZXdcbiAqIHJvdXRpbmcgb3BpbmlvbiDigJQgdGhlIGNvbnN1bWVyIHN1cHBsaWVzIHRoZSBMYW1iZGEge0BsaW5rIENyZXdXZWJob29rSW5ncmVzc1Byb3BzLmNvZGV9LlxuICovXG5leHBvcnQgY2xhc3MgQ3Jld1dlYmhvb2tJbmdyZXNzIGV4dGVuZHMgQ29uc3RydWN0IHtcbiAgLyoqIFRoZSBTTlMgdG9waWMgdGhhdCBmYW5zIGV2ZW50cyBpbnRvIHRoZSBpbmdyZXNzIExhbWJkYS4gKi9cbiAgcHVibGljIHJlYWRvbmx5IHRvcGljOiBzbnMuSVRvcGljO1xuICAvKiogVGhlIGluLVZQQyBpbmdyZXNzIExhbWJkYS4gKi9cbiAgcHVibGljIHJlYWRvbmx5IGZ1bmN0aW9uOiBsYW1iZGEuRnVuY3Rpb247XG4gIC8qKiBUaGUgaW5ncmVzcyBMYW1iZGEncyBzZWN1cml0eSBncm91cCAodGhlIHNvdXJjZSBvZiB0aGUgc2NvcGVkIHRhc2sgaW5ncmVzcyBydWxlKS4gKi9cbiAgcHVibGljIHJlYWRvbmx5IHNlY3VyaXR5R3JvdXA6IGVjMi5TZWN1cml0eUdyb3VwO1xuXG4gIGNvbnN0cnVjdG9yKHNjb3BlOiBDb25zdHJ1Y3QsIGlkOiBzdHJpbmcsIHByb3BzOiBDcmV3V2ViaG9va0luZ3Jlc3NQcm9wcykge1xuICAgIHN1cGVyKHNjb3BlLCBpZCk7XG5cbiAgICBjb25zdCBjcmV3UG9ydCA9IHByb3BzLmNyZXdQb3J0ID8/IERFRkFVTFRfQ1JFV19QT1JUO1xuXG4gICAgdGhpcy5zZWN1cml0eUdyb3VwID0gbmV3IGVjMi5TZWN1cml0eUdyb3VwKHRoaXMsICdJbmdyZXNzTGFtYmRhU2cnLCB7XG4gICAgICB2cGM6IHByb3BzLnZwYyxcbiAgICAgIGRlc2NyaXB0aW9uOiAnS2lyb0NyZXcgd2ViaG9vay1pbmdyZXNzIExhbWJkYSAtIHJlYWNoZXMgY3JldyB0YXNrIHByaXZhdGUgSVBzJyxcbiAgICAgIGFsbG93QWxsT3V0Ym91bmQ6IHRydWUsXG4gICAgfSk7XG5cbiAgICBjb25zdCBleGVjdXRpb25Sb2xlID0gbmV3IGlhbS5Sb2xlKHRoaXMsICdJbmdyZXNzTGFtYmRhUm9sZScsIHtcbiAgICAgIGFzc3VtZWRCeTogbmV3IGlhbS5TZXJ2aWNlUHJpbmNpcGFsKCdsYW1iZGEuYW1hem9uYXdzLmNvbScpLFxuICAgICAgcGVybWlzc2lvbnNCb3VuZGFyeTogaWFtLk1hbmFnZWRQb2xpY3kuZnJvbU1hbmFnZWRQb2xpY3lBcm4oXG4gICAgICAgIHRoaXMsXG4gICAgICAgICdCb3VuZGFyeScsXG4gICAgICAgIHByb3BzLnBlcm1pc3Npb25zQm91bmRhcnlBcm4sXG4gICAgICApLFxuICAgICAgbWFuYWdlZFBvbGljaWVzOiBbXG4gICAgICAgIC8vIFRoZSBMYW1iZGEgcnVucyBpbiBhIFZQQywgc28gaXQgbmVlZHMgdGhlIEVOSS1tYW5hZ2VtZW50IGFjdGlvbnMuXG4gICAgICAgIGlhbS5NYW5hZ2VkUG9saWN5LmZyb21Bd3NNYW5hZ2VkUG9saWN5TmFtZShcbiAgICAgICAgICAnc2VydmljZS1yb2xlL0FXU0xhbWJkYVZQQ0FjY2Vzc0V4ZWN1dGlvblJvbGUnLFxuICAgICAgICApLFxuICAgICAgXSxcbiAgICB9KTtcblxuICAgIHRoaXMuZnVuY3Rpb24gPSBuZXcgbGFtYmRhLkZ1bmN0aW9uKHRoaXMsICdJbmdyZXNzTGFtYmRhJywge1xuICAgICAgcnVudGltZTogcHJvcHMucnVudGltZSA/PyBsYW1iZGEuUnVudGltZS5OT0RFSlNfMjBfWCxcbiAgICAgIGFyY2hpdGVjdHVyZTogcHJvcHMuYXJjaGl0ZWN0dXJlID8/IGxhbWJkYS5BcmNoaXRlY3R1cmUuQVJNXzY0LFxuICAgICAgaGFuZGxlcjogcHJvcHMuaGFuZGxlciA/PyAnaW5kZXguaGFuZGxlcicsXG4gICAgICBjb2RlOiBwcm9wcy5jb2RlLFxuICAgICAgcm9sZTogZXhlY3V0aW9uUm9sZSxcbiAgICAgIHZwYzogcHJvcHMudnBjLFxuICAgICAgdnBjU3VibmV0czogcHJvcHMudnBjU3VibmV0cyA/PyB7XG4gICAgICAgIHN1Ym5ldFR5cGU6IGVjMi5TdWJuZXRUeXBlLlBSSVZBVEVfV0lUSF9FR1JFU1MsXG4gICAgICB9LFxuICAgICAgc2VjdXJpdHlHcm91cHM6IFt0aGlzLnNlY3VyaXR5R3JvdXBdLFxuICAgICAgdGltZW91dDogcHJvcHMudGltZW91dCA/PyBEdXJhdGlvbi5zZWNvbmRzKDMwKSxcbiAgICAgIGVudmlyb25tZW50OiB7XG4gICAgICAgIENSRVdfUE9SVDogU3RyaW5nKGNyZXdQb3J0KSxcbiAgICAgIH0sXG4gICAgfSk7XG5cbiAgICB0aGlzLnRvcGljID0gcHJvcHMudG9waWMgPz8gbmV3IHNucy5Ub3BpYyh0aGlzLCAnSW5ncmVzc1RvcGljJyk7XG4gICAgdGhpcy50b3BpYy5hZGRTdWJzY3JpcHRpb24obmV3IHN1YnMuTGFtYmRhU3Vic2NyaXB0aW9uKHRoaXMuZnVuY3Rpb24pKTtcblxuICAgIC8vIFRoZSBvbmUgY29udHJvbGxlZCBpbmJvdW5kIGV4Y2VwdGlvbiB0byB0aGUgZWdyZXNzLW9ubHkgdGFzayBtb2RlbDogdGhlXG4gICAgLy8gY3JldyB0YXNrIFNHIGFjY2VwdHMgdGhlIGNyZXcgcG9ydCBGUk9NIHRoZSBpbmdyZXNzIExhbWJkYSBTRyBvbmx5LCBuZXZlclxuICAgIC8vIGEgQ0lEUi4gRG9jdW1lbnRlZCBpbiB0aGUgUkVBRE1FLlxuICAgIHByb3BzLmhvc3QuYmFzZS5zZWN1cml0eUdyb3VwLmFkZEluZ3Jlc3NSdWxlKFxuICAgICAgZWMyLlBlZXIuc2VjdXJpdHlHcm91cElkKHRoaXMuc2VjdXJpdHlHcm91cC5zZWN1cml0eUdyb3VwSWQpLFxuICAgICAgZWMyLlBvcnQudGNwKGNyZXdQb3J0KSxcbiAgICAgICdXZWJob29rIGluZ3Jlc3MgZnJvbSB0aGUgaW4tVlBDIGluZ3Jlc3MgTGFtYmRhIFNHIG9ubHkgKG5vIENJRFIpJyxcbiAgICApO1xuICB9XG59XG4iXX0=
|