@maestro-js/aws-cdk-recipes 1.0.0-alpha.23 → 1.0.0-alpha.25
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 +113 -6
- package/dist/index.d.ts +3330 -495
- package/dist/index.js +2942 -752
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -27,18 +27,75 @@ Package dependencies:
|
|
|
27
27
|
## Available Constructs
|
|
28
28
|
|
|
29
29
|
### Vpc
|
|
30
|
-
Multi-AZ VPC with public and private subnets, configurable NAT strategy (none, single, onePerAz, ec2), optional WireGuard
|
|
30
|
+
Multi-AZ VPC with public and private subnets, configurable NAT strategy (none, single, onePerAz, ec2), an optional VPN (self-hosted WireGuard or Tailscale), and VPC Flow Logs.
|
|
31
31
|
|
|
32
32
|
```ts
|
|
33
33
|
const network = new Vpc(this, 'Network', {
|
|
34
34
|
natGateways: 'single',
|
|
35
|
-
|
|
35
|
+
vpn: { type: 'tailscale', auth: { method: 'authKey' } }
|
|
36
36
|
})
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
Key props: `cidr`, `maxAzs`, `natGateways`, `subnetConfiguration`, `
|
|
39
|
+
Key props: `cidr`, `maxAzs`, `natGateways`, `subnetConfiguration`, `vpn`, `flowLogs`.
|
|
40
40
|
|
|
41
|
-
Exposes: `vpc`, `publicSubnets`, `privateSubnets`, `securityGroup`, `wireguard`, `wireguardPublicKeyParam`.
|
|
41
|
+
Exposes: `vpc`, `publicSubnets`, `privateSubnets`, `securityGroup`, `wireguard`, `wireguardPublicKeyParam`, `tailscale`, `tailscaleRoutes`.
|
|
42
|
+
|
|
43
|
+
#### VPN: WireGuard vs Tailscale
|
|
44
|
+
|
|
45
|
+
`vpn` selects the backend: `{ type: 'wireguard' }` or `{ type: 'tailscale', auth: ... }`. Omit it for no VPN.
|
|
46
|
+
|
|
47
|
+
- **WireGuard** — a self-hosted server in a public subnet with an Elastic IP and inbound UDP 51820. Server keys and peer config persist in SSM; provision clients with `maestro aws:make-wireguard-client`.
|
|
48
|
+
- **Tailscale** — a subnet router that joins your tailnet **outbound-only** (no public ingress, no Elastic IP) and advertises the VPC CIDR so tailnet members can reach private resources. Clients onboard themselves with `tailscale up` — there's no per-client CLI.
|
|
49
|
+
|
|
50
|
+
Either way the VPN node carries the VPC's `securityGroup`, so you grant it access to private resources the same way:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
db.connections.allowDefaultPortFrom(network.securityGroup, 'VPN access')
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
#### Tailscale setup
|
|
57
|
+
|
|
58
|
+
1. **Create the auth secret in SSM** (one-time, out-of-band). CloudFormation can't create `SecureString` parameters, so the construct only *reads* a pre-existing one:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Auth key (tskey-auth-...), short-lived; or OAuth client secret (tskey-client-...), long-lived
|
|
62
|
+
aws ssm put-parameter --name /tailscale/Network/auth-key --type SecureString --value tskey-auth-...
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The default param name is `/tailscale/<id>/auth-key` (authKey) or `/tailscale/<id>/oauth-secret` (oauth); override with `tailscale.auth.param`.
|
|
66
|
+
|
|
67
|
+
2. **Choose an auth method.** Supplying `tags` registers the node under the `tagged-devices` system user (rather than the human who minted the credential), which also disables key expiry on the node:
|
|
68
|
+
|
|
69
|
+
- **`authKey`** (`tskey-auth-...`) — short-lived. Set `auth.tags` to advertise tags; the SSM key should itself be created as a tagged key. Without tags the node registers personally-owned and inherits the tailnet's key expiry.
|
|
70
|
+
- **`oauth`** (`tskey-client-...`) — long-lived and mints ephemeral keys on demand (so it survives instance replacement without key rotation); `tags` is **required**.
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
new Vpc(this, 'Network', {
|
|
74
|
+
vpn: {
|
|
75
|
+
type: 'tailscale',
|
|
76
|
+
auth: { method: 'oauth', tags: ['tag:subnet-router'] },
|
|
77
|
+
advertiseRoutes: ['10.0.0.0/16'], // defaults to the VPC CIDR
|
|
78
|
+
exitNode: false, // also route clients' internet traffic out AWS
|
|
79
|
+
ephemeral: true, // auto-remove from tailnet when offline
|
|
80
|
+
placement: 'public' // 'private' requires natGateways !== 'none'
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
3. **Approve the advertised routes.** Advertising a route doesn't make it usable — approve it once in the admin console, or auto-approve via your tailnet ACL policy:
|
|
86
|
+
|
|
87
|
+
```jsonc
|
|
88
|
+
// tailnet ACL policy
|
|
89
|
+
"autoApprovers": {
|
|
90
|
+
"routes": {
|
|
91
|
+
"10.0.0.0/16": ["tag:subnet-router"]
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The construct emits a `TailscaleRouteApproval` CfnOutput naming the exact routes and tag to approve.
|
|
97
|
+
|
|
98
|
+
4. **Onboard clients** with the standard Tailscale flow (`tailscale up` on each device); the control plane handles IP assignment, key exchange, and ACL-based access.
|
|
42
99
|
|
|
43
100
|
### Cluster
|
|
44
101
|
ECS cluster inside a VPC with optional Fargate capacity providers for Spot pricing.
|
|
@@ -137,7 +194,9 @@ new EcsFargateService(this, 'Api', {
|
|
|
137
194
|
})
|
|
138
195
|
```
|
|
139
196
|
|
|
140
|
-
Key props: `cluster`, `architecture`, `cpu`, `memoryLimitMiB`, `containers` (array with `name`, `image`, `port`, `environment`, `ssm`, `secrets`, `health`, `logging`), `loadBalancer` (with `domain`, `health`, `rules`), `scaling`, `capacity` (`'spot'` or mixed), `permissions`, `transform`.
|
|
197
|
+
Key props: `cluster`, `architecture`, `cpu`, `memoryLimitMiB`, `ephemeralStorageGiB`, `desiredCount`, `volumes`, `containers` (array with `name`, `image`, `port`, `command`, `workingDirectory`, `essential`, `mountPoints`, `dependsOn`, `environment`, `ssm`, `secrets`, `health`, `logging`), `loadBalancer` (with `domain`, `health`, `healthCheckGracePeriodSeconds`, `rules`), `scaling`, `capacity` (`'spot'` or mixed), `permissions`, `transform`.
|
|
198
|
+
|
|
199
|
+
Sidecar/init-container primitives: declare task-scoped `volumes`, mount them per container via `mountPoints`, and order startup with `dependsOn` (`'start' | 'complete' | 'success' | 'healthy'`). Mark short-lived init containers `essential: false`.
|
|
141
200
|
|
|
142
201
|
Exposes: `service`, `taskDefinition`, `containers`, `logGroups`, `securityGroup`, `alb`, `listener`, `targetGroups`.
|
|
143
202
|
|
|
@@ -199,6 +258,27 @@ Key props: `cluster`, `path`, `buildDirectory`, `image`, `port`, `environment`,
|
|
|
199
258
|
|
|
200
259
|
Exposes: `bucket`, `distribution`, `ecsService`.
|
|
201
260
|
|
|
261
|
+
### ArtifactEcsFargateService
|
|
262
|
+
Framework-agnostic Fargate service that runs an application **artifact downloaded at boot** instead of a baked image — so a new version ships **without a Docker build or `cdk deploy`**. A single container runs a boot-time Node bootstrap that downloads the tarball from S3 (SigV4 with the task role's credentials), verifies its SHA-256, and extracts it to `/app`, then `exec`s the app in place — no init sidecar, no shared volume.
|
|
263
|
+
|
|
264
|
+
CDK owns the durable infrastructure (ALB, roles, buckets, CloudFront, autoscaling, the service, and a single bootstrap task definition); CI owns every subsequent task-def revision. On `cdk deploy` the service comes up on a sentinel artifact key with no pinned `DesiredCount` — its create-time task serves a healthy placeholder (200 on the health path, 503 elsewhere) so the stack reaches a healthy state without running the real app. The app comes alive on the first CI deploy. See [docs/artifact-deploy-runbook.md](docs/artifact-deploy-runbook.md) for the deploy / promote / rollback procedure.
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
// React Router SSR app — command points at the artifact's react-router-serve binary.
|
|
268
|
+
new ArtifactEcsFargateService(this, 'App', {
|
|
269
|
+
app: 'my-app',
|
|
270
|
+
cluster: cluster.cluster,
|
|
271
|
+
command: ['./node_modules/.bin/react-router-serve', './build/server/index.js'],
|
|
272
|
+
environment: { ENV_NAME: 'production' },
|
|
273
|
+
ssm: { MAESTRO_ENV_ENCRYPTION_KEY: '/my-app/production/env-key' },
|
|
274
|
+
domain: { name: 'app.example.com', certificateArn: '...' }
|
|
275
|
+
})
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Key props: `app`, `cluster`, `command`, `port`, `architecture`, `cpu`, `memoryLimitMiB`, `ephemeralStorageGiB`, `runtimeImage`, `mountPath`, `workingDirectory`, `environment` (set `ENV_NAME` here), `ssm`, `secrets`, `artifact` (`bucket` for cross-account), `cdn`, `assets` (`lifecycleDays`, `behaviors`), `domain`, `edge`, `loadBalancer`, `healthCheck` (with `gracePeriodSeconds`), `scaling`, `permissions`, `discovery` (`namespace`), `taskFamily`, `s3GatewayEndpoint`, `logging`, `transform`.
|
|
279
|
+
|
|
280
|
+
Exposes: `ecsService`, `artifactBucket`, `assetBucket`, `distribution`, `deployPolicy` (managed policy to attach to your CI principal), `taskFamily`. Writes discovery values to SSM under `/maestro/<app>/{region,cluster,service,task-family,artifact-bucket,asset-bucket}`.
|
|
281
|
+
|
|
202
282
|
### ReactRouterEcrImage
|
|
203
283
|
Builds a Docker image for a React Router app and pushes it to ECR. Auto-generates a Dockerfile when none is provided. Use to pre-build an image shared across multiple ECS services.
|
|
204
284
|
|
|
@@ -226,6 +306,7 @@ export { Cluster } from './cluster.ts'
|
|
|
226
306
|
export { StaticSite } from './static-site.ts'
|
|
227
307
|
export { ReactRouterSsrSiteLambda } from './react-router-ssr-site-lambda.ts'
|
|
228
308
|
export { ReactRouterSsrSiteEcsFargateService } from './react-router-ssr-site-ecs-fargate-service.ts'
|
|
309
|
+
export { ArtifactEcsFargateService } from './artifact-ecs-fargate-service.ts'
|
|
229
310
|
export { ReactRouterEcrImage } from './react-router-ecr-image.ts'
|
|
230
311
|
export { MySqlDatabase } from './mysql-database.ts'
|
|
231
312
|
export { AuroraDatabase } from './aurora-database.ts'
|
|
@@ -247,6 +328,7 @@ export { EcsFargateService } from './ecs-fargate-service.ts'
|
|
|
247
328
|
| `StaticSite` | `new StaticSite(scope, id, props: StaticSite.Props)` |
|
|
248
329
|
| `ReactRouterSsrSiteLambda` | `new ReactRouterSsrSiteLambda(scope, id, props: ReactRouterSsrSiteLambda.Props)` |
|
|
249
330
|
| `ReactRouterSsrSiteEcsFargateService` | `new ReactRouterSsrSiteEcsFargateService(scope, id, props: ReactRouterSsrSiteEcsFargateService.Props)` |
|
|
331
|
+
| `ArtifactEcsFargateService` | `new ArtifactEcsFargateService(scope, id, props: ArtifactEcsFargateService.Props)` |
|
|
250
332
|
| `ReactRouterEcrImage` | `new ReactRouterEcrImage(scope, id, props?: ReactRouterEcrImage.Props)` |
|
|
251
333
|
|
|
252
334
|
## Common Patterns
|
|
@@ -311,6 +393,29 @@ new ReactRouterSsrSiteEcsFargateService(this, 'App', {
|
|
|
311
393
|
})
|
|
312
394
|
```
|
|
313
395
|
|
|
396
|
+
### Download-on-boot deploys without `cdk deploy`
|
|
397
|
+
|
|
398
|
+
Ship new app versions by uploading an artifact and rolling the service — no Docker build, no CDK run.
|
|
399
|
+
CDK provisions the durable infrastructure once; CI owns subsequent task-def revisions.
|
|
400
|
+
|
|
401
|
+
```ts
|
|
402
|
+
const app = new ArtifactEcsFargateService(this, 'App', {
|
|
403
|
+
app: 'my-app',
|
|
404
|
+
cluster: cluster.cluster,
|
|
405
|
+
command: ['./node_modules/.bin/react-router-serve', './build/server/index.js'],
|
|
406
|
+
environment: { ENV_NAME: 'production' },
|
|
407
|
+
ssm: { MAESTRO_ENV_ENCRYPTION_KEY: '/my-app/production/env-key' },
|
|
408
|
+
domain: { name: 'app.example.com', certificateArn: '...' }
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
// Attach the emitted, tightly-scoped policy to your CI OIDC principal.
|
|
412
|
+
ciPrincipal.addManagedPolicy(app.deployPolicy)
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
After `cdk deploy`, CI discovers its targets from SSM and runs the deploy flow in
|
|
416
|
+
[docs/artifact-deploy-runbook.md](docs/artifact-deploy-runbook.md). Rollback is one call to a prior
|
|
417
|
+
task-def revision.
|
|
418
|
+
|
|
314
419
|
### Transform escape hatch for customizing underlying CDK props
|
|
315
420
|
|
|
316
421
|
Every construct supports a `transform` prop that receives default CDK props and returns modified versions:
|
|
@@ -359,7 +464,7 @@ Values starting with `arn:aws:secretsmanager:` are resolved as Secrets Manager s
|
|
|
359
464
|
const network = new Vpc(this, 'Network', {
|
|
360
465
|
natGateways: 'ec2',
|
|
361
466
|
maxAzs: 2,
|
|
362
|
-
|
|
467
|
+
vpn: { type: 'wireguard' },
|
|
363
468
|
flowLogs: { retention: RetentionDays.ONE_WEEK }
|
|
364
469
|
})
|
|
365
470
|
```
|
|
@@ -383,7 +488,9 @@ The `'ec2'` NAT option uses a `t4g.nano` instance running the fck-nat AMI (~$3/m
|
|
|
383
488
|
- `packages/aws-cdk-recipes/src/static-site.ts` - StaticSite construct
|
|
384
489
|
- `packages/aws-cdk-recipes/src/react-router-ssr-site-lambda.ts` - ReactRouterSsrSiteLambda construct
|
|
385
490
|
- `packages/aws-cdk-recipes/src/react-router-ssr-site-ecs-fargate-service.ts` - ReactRouterSsrSiteEcsFargateService construct
|
|
491
|
+
- `packages/aws-cdk-recipes/src/artifact-ecs-fargate-service.ts` - ArtifactEcsFargateService construct (download-on-boot deploys)
|
|
386
492
|
- `packages/aws-cdk-recipes/src/react-router-ecr-image.ts` - ReactRouterEcrImage construct
|
|
493
|
+
- `packages/aws-cdk-recipes/docs/artifact-deploy-runbook.md` - Artifact deploy / promote / rollback runbook
|
|
387
494
|
- `packages/aws-cdk-recipes/src/helpers/cloudfront.ts` - Shared CloudFront/S3 deployment helpers
|
|
388
495
|
- `packages/aws-cdk-recipes/src/helpers/ecs.ts` - Shared ECS helpers (log retention, secrets)
|
|
389
496
|
- `packages/aws-cdk-recipes/src/helpers/docker.ts` - React Router Dockerfile generation
|