@go-to-k/cdkd 0.194.0 → 0.196.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
|
@@ -242,6 +242,7 @@ maintain, no `cdk synth | sam ...` round-trip.
|
|
|
242
242
|
| `cdkd local run-task <target>` | ECS RunTask — every container in a task definition started on a per-task docker network |
|
|
243
243
|
| `cdkd local start-service <target>` | Long-running ECS Service emulator — `DesiredCount` replicas with restart-on-exit (no local load balancer in v1) |
|
|
244
244
|
| `cdkd local invoke-agentcore <target>` | One-shot Bedrock AgentCore Runtime invoke (HTTP `/invocations` / MCP `/mcp` / A2A `/a2a` / AGUI / WebSocket `--ws`) |
|
|
245
|
+
| `cdkd local start-alb <targets...>` | Long-running local ALB front-door (HTTP + HTTPS listeners, path / host / header / weighted / redirect / fixed-response routing, authenticate-cognito / authenticate-oidc) for ECS / Lambda backing services |
|
|
245
246
|
|
|
246
247
|
Requires Docker. Pass `--from-state` (cdkd-deployed) or
|
|
247
248
|
`--from-cfn-stack` (cdk-deployed / CFn-managed) to substitute deployed
|
|
@@ -302,6 +303,21 @@ restart-on-exit, cross-service Service Connect / Cloud Map DNS
|
|
|
302
303
|
discovery (peer containers reach each other by `<discoveryName>.<namespace>`).
|
|
303
304
|
No local load-balancer in v1.
|
|
304
305
|
|
|
306
|
+
### `local start-alb`
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
cdkd local start-alb MyStack/MyAlb --lb-port 80=8080 # remap privileged listener port
|
|
310
|
+
cdkd local start-alb MyStack/MyAlb --from-state # OR --from-cfn-stack
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Long-running local ALB front-door: names an `AWS::ElasticLoadBalancingV2::LoadBalancer`,
|
|
314
|
+
boots every ECS service behind its listeners, and stands up a local
|
|
315
|
+
HTTP / HTTPS front-door on each listener port that round-robins across
|
|
316
|
+
the running replicas and routes its listener rules across the backing
|
|
317
|
+
services. Forward / redirect / fixed-response actions; ECS or Lambda
|
|
318
|
+
targets; authenticate-cognito / authenticate-oidc via a local Bearer-JWT
|
|
319
|
+
check.
|
|
320
|
+
|
|
305
321
|
See **[docs/local-emulation.md](docs/local-emulation.md)** for the
|
|
306
322
|
full reference — runtimes, target resolution, every flag, integration
|
|
307
323
|
and authorizer detail, route precedence, container pool, networking,
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { _ as withSkipPrefix, a as runDockerStreaming, c as getLogger, d as getLiveRenderer, f as PATTERN_B_NAME_PROPERTIES, g as generateResourceNameWithFallback, h as generateResourceName, i as runDockerForeground, n as formatDockerLoginError, p as PATTERN_B_RESOURCE_TYPES, r as getDockerCmd, u as runStackBuffered, v as withStackName } from "./docker-cmd-iDMcWcre.js";
|
|
3
|
-
import { A as S3StateBackend, B as resolveCaptureObservedState, C as assertRegionMatch, D as DagBuilder, E as DiffCalculator, F as buildDockerImage, G as CFN_TEMPLATE_BODY_LIMIT, H as resolveStateBucketWithDefault, I as Synthesizer, J as findLargeInlineResources, K as CFN_TEMPLATE_URL_LIMIT, L as getDefaultStateBucketName, M as AssetPublisher, N as stringifyValue, O as TemplateParser, P as WorkGraph, Q as resolveBucketRegion, R as getLegacyStateBucketName, S as CloudControlProvider, T as applyRoleArnIfSet, U as resolveStateBucketWithDefaultAndSource, V as resolveSkipPrefix, W as warnDeprecatedNoPrefixCliFlag, X as AssemblyReader, Y as uploadCfnTemplate, _ as matchesCdkPath, a as withRetry, at as LocalStartServiceError, b as ProviderRegistry, bt as withErrorHandling, c as bold, ct as NestedStackChildDirectDestroyError, d as green, dt as ResourceTimeoutError, et as CdkdError, f as red, ft as ResourceUpdateNotSupportedError, g as CDK_PATH_TAG, h as collectInlinePolicyNamesManagedBySiblings, i as withResourceDeadline, it as LocalMigrateError, j as shouldRetainResource, k as LockManager, l as cyan, lt as PartialFailureError, m as IAMRoleProvider, mt as StackTerminationProtectionError, n as DEFAULT_RESOURCE_WARN_AFTER_MS, o as IMPLICIT_DELETE_DEPENDENCIES, p as yellow, pt as StackHasActiveImportsError, q as MIGRATE_TMP_PREFIX, r as DeployEngine, rt as LocalInvokeBuildError$1, s as formatResourceLine, st as MissingCdkCliError, t as DEFAULT_RESOURCE_TIMEOUT_MS, u as gray, ut as ProvisioningError, v as normalizeAwsTagsToCfn, w as IntrinsicFunctionResolver, x as findActionableSilentDrops, y as resolveExplicitPhysicalId, yt as normalizeAwsError, z as resolveApp } from "./deploy-engine-
|
|
3
|
+
import { A as S3StateBackend, B as resolveCaptureObservedState, C as assertRegionMatch, D as DagBuilder, E as DiffCalculator, F as buildDockerImage, G as CFN_TEMPLATE_BODY_LIMIT, H as resolveStateBucketWithDefault, I as Synthesizer, J as findLargeInlineResources, K as CFN_TEMPLATE_URL_LIMIT, L as getDefaultStateBucketName, M as AssetPublisher, N as stringifyValue, O as TemplateParser, P as WorkGraph, Q as resolveBucketRegion, R as getLegacyStateBucketName, S as CloudControlProvider, T as applyRoleArnIfSet, U as resolveStateBucketWithDefaultAndSource, V as resolveSkipPrefix, W as warnDeprecatedNoPrefixCliFlag, X as AssemblyReader, Y as uploadCfnTemplate, _ as matchesCdkPath, a as withRetry, at as LocalStartServiceError, b as ProviderRegistry, bt as withErrorHandling, c as bold, ct as NestedStackChildDirectDestroyError, d as green, dt as ResourceTimeoutError, et as CdkdError, f as red, ft as ResourceUpdateNotSupportedError, g as CDK_PATH_TAG, h as collectInlinePolicyNamesManagedBySiblings, i as withResourceDeadline, it as LocalMigrateError, j as shouldRetainResource, k as LockManager, l as cyan, lt as PartialFailureError, m as IAMRoleProvider, mt as StackTerminationProtectionError, n as DEFAULT_RESOURCE_WARN_AFTER_MS, o as IMPLICIT_DELETE_DEPENDENCIES, p as yellow, pt as StackHasActiveImportsError, q as MIGRATE_TMP_PREFIX, r as DeployEngine, rt as LocalInvokeBuildError$1, s as formatResourceLine, st as MissingCdkCliError, t as DEFAULT_RESOURCE_TIMEOUT_MS, u as gray, ut as ProvisioningError, v as normalizeAwsTagsToCfn, w as IntrinsicFunctionResolver, x as findActionableSilentDrops, y as resolveExplicitPhysicalId, yt as normalizeAwsError, z as resolveApp } from "./deploy-engine-C6v_fcDw.js";
|
|
4
4
|
import { a as setAwsClients, i as resetAwsClients, r as getAwsClients, t as AwsClients } from "./aws-clients-B15NAPbL.js";
|
|
5
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
6
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
@@ -63,7 +63,7 @@ import { CreateNamespaceCommand, CreateTableBucketCommand, CreateTableCommand as
|
|
|
63
63
|
import { AttachLoadBalancerTargetGroupsCommand, AttachLoadBalancersCommand, AttachTrafficSourcesCommand, AutoScalingClient, CreateAutoScalingGroupCommand, CreateOrUpdateTagsCommand, DeleteAutoScalingGroupCommand, DeleteLifecycleHookCommand, DeleteNotificationConfigurationCommand, DeleteTagsCommand as DeleteTagsCommand$1, DescribeAutoScalingGroupsCommand, DescribeLifecycleHooksCommand, DescribeNotificationConfigurationsCommand, DescribeTrafficSourcesCommand, DetachLoadBalancerTargetGroupsCommand, DetachLoadBalancersCommand, DetachTrafficSourcesCommand, DisableMetricsCollectionCommand, EnableMetricsCollectionCommand, PutLifecycleHookCommand, PutNotificationConfigurationCommand, UpdateAutoScalingGroupCommand } from "@aws-sdk/client-auto-scaling";
|
|
64
64
|
import { Document, Pair, Scalar, YAMLMap, YAMLSeq, parse as parse$1, stringify } from "yaml";
|
|
65
65
|
import { createLocalStateProvider, getEmbedConfig, isCfnFlagPresent, listTargets, rejectExplicitCfnStackWithMultipleStacks, resolveCfnFallbackRegion, setEmbedConfig, substituteAgainstState, substituteAgainstStateAsync, substituteEnvVarsFromState, substituteEnvVarsFromStateAsync } from "cdk-local";
|
|
66
|
-
import { A2A_CONTAINER_PORT, A2A_PATH, AGENTCORE_A2A_PROTOCOL, AGENTCORE_AGUI_PROTOCOL, AGENTCORE_MCP_PROTOCOL, CloudMapRegistry, ConnectionRegistry, EcsTaskResolutionError, HOST_GATEWAY_MIN_VERSION, LocalInvokeBuildError, MCP_CONTAINER_PORT, MCP_PATH, a2aInvokeOnce, architectureToPlatform, attachAuthorizers, attachStageContext, availableApiIdentifiers, bufferToBody, buildAgentCoreCodeImage, buildCloudMapIndex, buildCognitoJwksUrl, buildConnectEvent, buildContainerImage, buildCorsConfigByApiId, buildCorsConfigFromCloudFrontChain, buildDisconnectEvent, buildJwksUrlFromIssuer, buildMessageEvent, buildMgmtEndpointEnvUrl, buildStageMap, createAuthorizerCache, createFileWatcher, createJwksCache, createWatchPredicates, defaultCredentialsLoader, derivePseudoParametersFromRegion, discoverRoutes, discoverWebSocketApis, downloadAndExtractS3Bundle, filterRoutesByApiIdentifier, getContainerNetworkIp, groupRoutesByServer, handleConnectionsRequest, invokeAgentCore, invokeAgentCoreWs, materializeLayerFromArn, mcpInvokeOnce, parseConnectionsPath, parseSelectionExpressionPath, pickAgentCoreCandidateStack, probeHostGatewaySupport, readMtlsMaterialsFromDisk, resolveAgentCoreTarget, resolveEnvVars, resolveRuntimeCodeMountPath, resolveRuntimeFileExtension, resolveRuntimeImage, resolveSingleTarget, resolveWatchConfig, signAgentCoreInvocation, startApiServer, substituteImagePlaceholders, tryResolveImageFnJoin, verifyJwtViaDiscovery, waitForAgentCorePing } from "cdk-local/internal";
|
|
66
|
+
import { A2A_CONTAINER_PORT, A2A_PATH, AGENTCORE_A2A_PROTOCOL, AGENTCORE_AGUI_PROTOCOL, AGENTCORE_MCP_PROTOCOL, CloudMapRegistry, ConnectionRegistry, EcsTaskResolutionError, HOST_GATEWAY_MIN_VERSION, LocalInvokeBuildError, MCP_CONTAINER_PORT, MCP_PATH, a2aInvokeOnce, addCommonEcsServiceOptions, architectureToPlatform, attachAuthorizers, attachStageContext, availableApiIdentifiers, bufferToBody, buildAgentCoreCodeImage, buildCloudMapIndex, buildCognitoJwksUrl, buildConnectEvent, buildContainerImage, buildCorsConfigByApiId, buildCorsConfigFromCloudFrontChain, buildDisconnectEvent, buildJwksUrlFromIssuer, buildMessageEvent, buildMgmtEndpointEnvUrl, buildStageMap, createAuthorizerCache, createFileWatcher, createJwksCache, createWatchPredicates, defaultCredentialsLoader, derivePseudoParametersFromRegion, discoverRoutes, discoverWebSocketApis, downloadAndExtractS3Bundle, filterRoutesByApiIdentifier, getContainerNetworkIp, groupRoutesByServer, handleConnectionsRequest, invokeAgentCore, invokeAgentCoreWs, isApplicationLoadBalancer, materializeLayerFromArn, mcpInvokeOnce, parseConnectionsPath, parseSelectionExpressionPath, pickAgentCoreCandidateStack, probeHostGatewaySupport, readMtlsMaterialsFromDisk, resolveAgentCoreTarget, resolveAlbFrontDoor, resolveEnvVars, resolveRuntimeCodeMountPath, resolveRuntimeFileExtension, resolveRuntimeImage, resolveSingleTarget, resolveWatchConfig, runEcsServiceEmulator, signAgentCoreInvocation, startApiServer, substituteImagePlaceholders, tryResolveImageFnJoin, verifyJwtViaDiscovery, waitForAgentCorePing } from "cdk-local/internal";
|
|
67
67
|
import { createServer } from "node:net";
|
|
68
68
|
import { promisify } from "node:util";
|
|
69
69
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
@@ -31342,7 +31342,11 @@ var S3VectorsProvider = class {
|
|
|
31342
31342
|
client;
|
|
31343
31343
|
providerRegion = process.env["AWS_REGION"];
|
|
31344
31344
|
logger = getLogger().child("S3VectorsProvider");
|
|
31345
|
-
handledProperties = new Map([["AWS::S3Vectors::VectorBucket", new Set([
|
|
31345
|
+
handledProperties = new Map([["AWS::S3Vectors::VectorBucket", new Set([
|
|
31346
|
+
"VectorBucketName",
|
|
31347
|
+
"EncryptionConfiguration",
|
|
31348
|
+
"Tags"
|
|
31349
|
+
])]]);
|
|
31346
31350
|
getClient() {
|
|
31347
31351
|
if (!this.client) this.client = new S3VectorsClient(this.providerRegion ? { region: this.providerRegion } : {});
|
|
31348
31352
|
return this.client;
|
|
@@ -31373,13 +31377,19 @@ var S3VectorsProvider = class {
|
|
|
31373
31377
|
const vectorBucketName = properties["VectorBucketName"];
|
|
31374
31378
|
if (!vectorBucketName) throw new ProvisioningError(`VectorBucketName is required for S3 VectorBucket ${logicalId}`, resourceType, logicalId);
|
|
31375
31379
|
const encryptionConfiguration = properties["EncryptionConfiguration"];
|
|
31380
|
+
const tagsArray = properties["Tags"];
|
|
31381
|
+
const tags = tagsArray && tagsArray.length > 0 ? tagsArray.reduce((acc, t) => {
|
|
31382
|
+
if (t.Key !== void 0 && t.Value !== void 0) acc[t.Key] = t.Value;
|
|
31383
|
+
return acc;
|
|
31384
|
+
}, {}) : void 0;
|
|
31376
31385
|
try {
|
|
31377
31386
|
const vectorBucketArn = (await this.getClient().send(new CreateVectorBucketCommand({
|
|
31378
31387
|
vectorBucketName,
|
|
31379
31388
|
encryptionConfiguration: encryptionConfiguration ? {
|
|
31380
31389
|
sseType: encryptionConfiguration["SSEType"],
|
|
31381
31390
|
kmsKeyArn: encryptionConfiguration["KMSKeyArn"]
|
|
31382
|
-
} : void 0
|
|
31391
|
+
} : void 0,
|
|
31392
|
+
...tags && Object.keys(tags).length > 0 ? { tags } : {}
|
|
31383
31393
|
}))).vectorBucketArn ?? "";
|
|
31384
31394
|
this.logger.debug(`Successfully created S3 VectorBucket ${logicalId}: ${vectorBucketName}`);
|
|
31385
31395
|
return {
|
|
@@ -31460,6 +31470,13 @@ var S3VectorsProvider = class {
|
|
|
31460
31470
|
if (sseType === "aws:kms" && bucket.encryptionConfiguration.kmsKeyArn !== void 0) enc["KMSKeyArn"] = bucket.encryptionConfiguration.kmsKeyArn;
|
|
31461
31471
|
if (Object.keys(enc).length > 0) result["EncryptionConfiguration"] = enc;
|
|
31462
31472
|
}
|
|
31473
|
+
if (bucket?.vectorBucketArn) try {
|
|
31474
|
+
result["Tags"] = normalizeAwsTagsToCfn((await this.getClient().send(new ListTagsForResourceCommand$18({ resourceArn: bucket.vectorBucketArn }))).tags);
|
|
31475
|
+
} catch (err) {
|
|
31476
|
+
this.logger.debug(`S3Vectors ListTagsForResource(${bucket.vectorBucketArn}) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
31477
|
+
result["Tags"] = [];
|
|
31478
|
+
}
|
|
31479
|
+
else this.logger.debug(`S3Vectors GetVectorBucket(${physicalId}) returned no vectorBucketArn; skipping Tags readback`);
|
|
31463
31480
|
return result;
|
|
31464
31481
|
}
|
|
31465
31482
|
/**
|
|
@@ -43048,6 +43065,14 @@ const fromStateFactory = (options) => {
|
|
|
43048
43065
|
});
|
|
43049
43066
|
};
|
|
43050
43067
|
/**
|
|
43068
|
+
* Cdkd's `extraStateProviders` map for cdk-local's engine entry points
|
|
43069
|
+
* (e.g. `runEcsServiceEmulator`) that accept a state-source factory
|
|
43070
|
+
* registry directly instead of going through `createLocalStateProvider`.
|
|
43071
|
+
* The engine calls `createLocalStateProvider` internally with this map,
|
|
43072
|
+
* so cdkd's `--from-state` flow is wired in transparently.
|
|
43073
|
+
*/
|
|
43074
|
+
const cdkdExtraStateProviders = { fromState: fromStateFactory };
|
|
43075
|
+
/**
|
|
43051
43076
|
* Pick and construct the right `LocalStateProvider` for the supplied
|
|
43052
43077
|
* flag set. Delegates to cdk-local's dispatcher with cdkd's
|
|
43053
43078
|
* `--from-state` factory wired in. Returns `undefined` when neither
|
|
@@ -43066,7 +43091,7 @@ const fromStateFactory = (options) => {
|
|
|
43066
43091
|
* per-stack loop — see that helper's docstring for the rationale.
|
|
43067
43092
|
*/
|
|
43068
43093
|
function createLocalStateProvider$1(options, cdkdStackName, synthRegion) {
|
|
43069
|
-
return createLocalStateProvider(options, cdkdStackName, synthRegion,
|
|
43094
|
+
return createLocalStateProvider(options, cdkdStackName, synthRegion, cdkdExtraStateProviders);
|
|
43070
43095
|
}
|
|
43071
43096
|
|
|
43072
43097
|
//#endregion
|
|
@@ -43111,7 +43136,7 @@ function parseTarget(target) {
|
|
|
43111
43136
|
function resolveLambdaTarget(target, stacks) {
|
|
43112
43137
|
if (stacks.length === 0) throw new LocalInvokeResolutionError("No stacks found in the synthesized assembly.");
|
|
43113
43138
|
const parsed = parseTarget(target);
|
|
43114
|
-
const stack = pickStack$
|
|
43139
|
+
const stack = pickStack$3(parsed, stacks);
|
|
43115
43140
|
const template = stack.template;
|
|
43116
43141
|
const resources = template.Resources ?? {};
|
|
43117
43142
|
let match;
|
|
@@ -43145,7 +43170,7 @@ function resolveLambdaTarget(target, stacks) {
|
|
|
43145
43170
|
* user may omit the stack prefix. Otherwise an explicit stack pattern is
|
|
43146
43171
|
* required.
|
|
43147
43172
|
*/
|
|
43148
|
-
function pickStack$
|
|
43173
|
+
function pickStack$3(parsed, stacks) {
|
|
43149
43174
|
if (parsed.stackPattern === null) {
|
|
43150
43175
|
if (stacks.length === 1) return stacks[0];
|
|
43151
43176
|
throw new LocalInvokeResolutionError(`Multiple stacks in app, target '${parsed.pathOrId}' is missing a stack prefix. Use 'StackName:${parsed.pathOrId}' or 'StackName/...' (path form). Available stacks: ${stacks.map((s) => s.stackName).join(", ")}.`);
|
|
@@ -43768,7 +43793,7 @@ function parseEcsTarget(target) {
|
|
|
43768
43793
|
function resolveEcsTaskTarget(target, stacks, context) {
|
|
43769
43794
|
if (stacks.length === 0) throw new EcsTaskResolutionError("No stacks found in the synthesized assembly.");
|
|
43770
43795
|
const parsed = parseEcsTarget(target);
|
|
43771
|
-
const stack = pickStack$
|
|
43796
|
+
const stack = pickStack$2(parsed, stacks);
|
|
43772
43797
|
const resources = stack.template.Resources ?? {};
|
|
43773
43798
|
let logicalId;
|
|
43774
43799
|
let resource;
|
|
@@ -43789,7 +43814,7 @@ function resolveEcsTaskTarget(target, stacks, context) {
|
|
|
43789
43814
|
if (resource.Type !== "AWS::ECS::TaskDefinition") throw new EcsTaskResolutionError(`Resource '${logicalId}' in ${stack.stackName} is ${resource.Type}, not an AWS::ECS::TaskDefinition.`);
|
|
43790
43815
|
return extractTaskDefinitionProperties(stack, logicalId, resource, context);
|
|
43791
43816
|
}
|
|
43792
|
-
function pickStack$
|
|
43817
|
+
function pickStack$2(parsed, stacks) {
|
|
43793
43818
|
if (parsed.stackPattern === null) {
|
|
43794
43819
|
if (stacks.length === 1) return stacks[0];
|
|
43795
43820
|
throw new EcsTaskResolutionError(`Multiple stacks in app, target '${parsed.pathOrId}' is missing a stack prefix. Use 'StackName:${parsed.pathOrId}' or 'StackName/...' (path form). Available stacks: ${stacks.map((s) => s.stackName).join(", ")}.`);
|
|
@@ -48531,7 +48556,7 @@ async function localRunTaskCommand(target, options) {
|
|
|
48531
48556
|
const { stacks } = await synthesizer.synthesize(synthOpts);
|
|
48532
48557
|
const candidate = pickCandidateStack$1(parseEcsTarget(target).stackPattern, stacks);
|
|
48533
48558
|
stateProvider = createLocalStateProvider$1(options, candidate?.stackName ?? "", candidate?.region);
|
|
48534
|
-
const imageContext = await buildEcsImageResolutionContext$
|
|
48559
|
+
const imageContext = await buildEcsImageResolutionContext$2(candidate, stateProvider, options);
|
|
48535
48560
|
const task = resolveEcsTaskTarget(target, stacks, imageContext);
|
|
48536
48561
|
logger.info(`Target: ${task.stack.stackName}/${task.taskDefinitionLogicalId} (family=${task.family}, containers=${task.containers.length})`);
|
|
48537
48562
|
const taskNeeds = detectEcsImageResolutionNeeds(stacks.find((s) => s.stackName === task.stack.stackName) ?? task.stack);
|
|
@@ -48655,7 +48680,7 @@ async function assumeTaskRole$1(roleArn, region) {
|
|
|
48655
48680
|
* `--from-state` and `--from-cfn-stack` produce the same downstream
|
|
48656
48681
|
* context shape (issue #606).
|
|
48657
48682
|
*/
|
|
48658
|
-
async function buildEcsImageResolutionContext$
|
|
48683
|
+
async function buildEcsImageResolutionContext$2(candidate, stateProvider, options) {
|
|
48659
48684
|
const logger = getLogger();
|
|
48660
48685
|
if (!candidate) return void 0;
|
|
48661
48686
|
const needs = detectEcsImageResolutionNeeds(candidate);
|
|
@@ -48783,7 +48808,7 @@ function createLocalRunTaskCommand() {
|
|
|
48783
48808
|
function resolveEcsServiceTarget(target, stacks, context) {
|
|
48784
48809
|
if (stacks.length === 0) throw new EcsTaskResolutionError("No stacks found in the synthesized assembly.");
|
|
48785
48810
|
const parsed = parseEcsTarget(target);
|
|
48786
|
-
const stack = pickStack(parsed, stacks);
|
|
48811
|
+
const stack = pickStack$1(parsed, stacks);
|
|
48787
48812
|
const resources = stack.template.Resources ?? {};
|
|
48788
48813
|
let serviceLogicalId;
|
|
48789
48814
|
let serviceResource;
|
|
@@ -48985,7 +49010,7 @@ function parseServiceName(raw, serviceLogicalId) {
|
|
|
48985
49010
|
* service-specific extensions (e.g. cross-stack service-to-task refs)
|
|
48986
49011
|
* can diverge without breaking the run-task code path.
|
|
48987
49012
|
*/
|
|
48988
|
-
function pickStack(parsed, stacks) {
|
|
49013
|
+
function pickStack$1(parsed, stacks) {
|
|
48989
49014
|
if (parsed.stackPattern === null) {
|
|
48990
49015
|
if (stacks.length === 1) return stacks[0];
|
|
48991
49016
|
throw new EcsTaskResolutionError(`Target has no stack prefix, and the assembly contains ${stacks.length} stacks: ${stacks.map((s) => s.stackName).join(", ")}. Pass the target as 'Stack/Path' or 'Stack:LogicalId'.`);
|
|
@@ -49539,7 +49564,7 @@ async function localStartServiceCommand(targets, options) {
|
|
|
49539
49564
|
for (const w of index.warnings) logger.warn(w);
|
|
49540
49565
|
}
|
|
49541
49566
|
const registry = new CloudMapRegistry();
|
|
49542
|
-
const sidecarCredentials = await resolveSharedSidecarCredentials(options);
|
|
49567
|
+
const sidecarCredentials = await resolveSharedSidecarCredentials$1(options);
|
|
49543
49568
|
try {
|
|
49544
49569
|
sharedNetwork = await createSharedSvcNetwork({
|
|
49545
49570
|
prefix: options.cluster,
|
|
@@ -49596,7 +49621,7 @@ async function bootOneTarget(target, runState, stacks, options, discovery, skipP
|
|
|
49596
49621
|
}
|
|
49597
49622
|
async function runOneTarget(target, runState, stacks, options, discovery, skipPull, stateProvider, profileCredsFile) {
|
|
49598
49623
|
const logger = getLogger();
|
|
49599
|
-
const imageContext = await buildEcsImageResolutionContext(target, stacks, options, stateProvider);
|
|
49624
|
+
const imageContext = await buildEcsImageResolutionContext$1(target, stacks, options, stateProvider);
|
|
49600
49625
|
const service = resolveEcsServiceTarget(target, stacks, imageContext);
|
|
49601
49626
|
logger.info(`Target: ${service.stack.stackName}/${service.serviceLogicalId} (service=${service.serviceName}, desiredCount=${service.desiredCount}, task=${service.task.taskDefinitionLogicalId})`);
|
|
49602
49627
|
for (const w of service.warnings) logger.warn(w);
|
|
@@ -49689,7 +49714,7 @@ async function assumeTaskRole(roleArn, region) {
|
|
|
49689
49714
|
* the candidate stack picker differs because services and tasks share
|
|
49690
49715
|
* the same stack-pattern grammar.
|
|
49691
49716
|
*/
|
|
49692
|
-
async function buildEcsImageResolutionContext(target, stacks, options, stateProvider) {
|
|
49717
|
+
async function buildEcsImageResolutionContext$1(target, stacks, options, stateProvider) {
|
|
49693
49718
|
const logger = getLogger();
|
|
49694
49719
|
const candidate = pickCandidateStack(parseEcsTarget(target).stackPattern, stacks);
|
|
49695
49720
|
if (!candidate) return void 0;
|
|
@@ -49781,13 +49806,13 @@ function parsePositiveInt(raw, flagName) {
|
|
|
49781
49806
|
* Raising this requires extending the allocator to walk a different
|
|
49782
49807
|
* IP range.
|
|
49783
49808
|
*/
|
|
49784
|
-
const MAX_TASKS_SUBNET_RANGE_CAP = 83;
|
|
49785
|
-
function parseMaxTasks(raw) {
|
|
49809
|
+
const MAX_TASKS_SUBNET_RANGE_CAP$1 = 83;
|
|
49810
|
+
function parseMaxTasks$1(raw) {
|
|
49786
49811
|
const parsed = parsePositiveInt(raw, "--max-tasks");
|
|
49787
49812
|
if (parsed > 83) throw new LocalStartServiceError(`--max-tasks ${parsed} exceeds the per-replica link-local /24 subnet allocator's range (${83}). The allocator in ecs-service-runner.ts assigns each replica its own 169.254.x.0/24 from the range 169.254.170.0..169.254.253.0; replica indices >= ${83} would collide with earlier replicas via modulo wrap. Lower --max-tasks to <= ${83}, or accept reduced local concurrency for high-DesiredCount services.`);
|
|
49788
49813
|
return parsed;
|
|
49789
49814
|
}
|
|
49790
|
-
function parseRestartPolicy(raw) {
|
|
49815
|
+
function parseRestartPolicy$1(raw) {
|
|
49791
49816
|
if (raw === "on-failure" || raw === "always" || raw === "none") return raw;
|
|
49792
49817
|
throw new LocalStartServiceError(`--restart-policy must be one of 'on-failure', 'always', or 'none' (got '${raw}').`);
|
|
49793
49818
|
}
|
|
@@ -49816,11 +49841,11 @@ function parseRestartPolicy(raw) {
|
|
|
49816
49841
|
* branches without having to mock the full Synth + Docker + AWS
|
|
49817
49842
|
* pipeline (the strategy PR #655 used for the Lambda container path).
|
|
49818
49843
|
*/
|
|
49819
|
-
async function resolveSharedSidecarCredentials(options) {
|
|
49844
|
+
async function resolveSharedSidecarCredentials$1(options) {
|
|
49820
49845
|
if (options.profile) return resolveProfileCredentials(options.profile);
|
|
49821
49846
|
}
|
|
49822
49847
|
function createLocalStartServiceCommand() {
|
|
49823
|
-
const cmd = new Command("start-service").description("Run one or more AWS::ECS::Service resources locally as a long-running emulator. Spins up DesiredCount task replicas per service (clamped by --max-tasks) using the same per-task docker network + metadata sidecar pattern as `cdkd local run-task`, then keeps each replica running and restarts it on exit per --restart-policy. ^C tears every replica + sidecar + network down. Each <target> accepts a CDK display path (MyStack/MyService) or stack-qualified logical ID (MyStack:MyServiceXYZ); single-stack apps may omit the stack prefix. When two or more <target>s are supplied, every service is booted into a shared Cloud Map / Service Connect registry so peer services discover each other via docker --add-host overlay (Issue #460).").argument("<targets...>", "One or more CDK display paths or stack-qualified logical IDs of the AWS::ECS::Service resources to run").addOption(new Option("--cluster <name>", "Cluster name surfaced to ECS_CONTAINER_METADATA_URI_V4 and used as the docker network prefix").default("cdkd-local")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"ContainerName\":{\"KEY\":\"VALUE\"}, \"Parameters\":{}})")).addOption(new Option("--container-host <ip>", "Host IP to bind published container ports to. Must be a numeric IP (Docker rejects hostnames here)").default("127.0.0.1")).addOption(new Option("--assume-task-role [arn]", "Assume the task definition's TaskRoleArn (or the supplied ARN) and forward STS-issued temp credentials via the metadata sidecar so containers run with the deployed task role. Bare flag uses the template's TaskRoleArn; pass an explicit ARN to override.")).addOption(new Option("--no-pull", "Skip docker pull for every container image and the metadata sidecar")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries.")).addOption(new Option("--platform <platform>", "Force docker --platform (linux/amd64 or linux/arm64). Default: inferred from task RuntimePlatform.CpuArchitecture")).addOption(new Option("--max-tasks <n>", `Hard cap on local replica count. Caps the template DesiredCount so local dev machines don't run an unbounded number of containers. Cannot exceed ${83} due to the per-replica link-local /24 subnet allocator's range.`).default(3).argParser(parseMaxTasks)).addOption(new Option("--restart-policy <policy>", "How to react when an essential container exits. 'on-failure' (default) restarts only on non-zero exit; 'always' restarts on every exit; 'none' shuts the replica down and runs the service degraded.").default("on-failure").argParser(parseRestartPolicy)).addOption(new Option("--from-state", "Read cdkd S3 state for the target stack and substitute Fn::Sub / Fn::GetAtt / Fn::ImportValue / Fn::GetStackOutput intrinsics in container images, environment variables, secrets, role ARNs, and volumes.").default(false)).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via DescribeStackResources and substitute Ref / Fn::ImportValue in container env vars / secrets / image URIs with the deployed physical IDs / exports. Use for CDK apps deployed via the upstream CDK CLI (`cdk deploy`). Bare form uses the cdkd stack name; pass an explicit value when the CFn stack name differs. Mutually exclusive with --from-state. Fn::GetAtt is warn-and-dropped in v1 (CFn DescribeStackResources does not return per-attribute values).")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-state when the same stack name has state in multiple regions, and with --from-cfn-stack as the CFn client region (cdkd does not have a separate --cfn-stack-region flag).")).action(withErrorHandling(localStartServiceCommand));
|
|
49848
|
+
const cmd = new Command("start-service").description("Run one or more AWS::ECS::Service resources locally as a long-running emulator. Spins up DesiredCount task replicas per service (clamped by --max-tasks) using the same per-task docker network + metadata sidecar pattern as `cdkd local run-task`, then keeps each replica running and restarts it on exit per --restart-policy. ^C tears every replica + sidecar + network down. Each <target> accepts a CDK display path (MyStack/MyService) or stack-qualified logical ID (MyStack:MyServiceXYZ); single-stack apps may omit the stack prefix. When two or more <target>s are supplied, every service is booted into a shared Cloud Map / Service Connect registry so peer services discover each other via docker --add-host overlay (Issue #460).").argument("<targets...>", "One or more CDK display paths or stack-qualified logical IDs of the AWS::ECS::Service resources to run").addOption(new Option("--cluster <name>", "Cluster name surfaced to ECS_CONTAINER_METADATA_URI_V4 and used as the docker network prefix").default("cdkd-local")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"ContainerName\":{\"KEY\":\"VALUE\"}, \"Parameters\":{}})")).addOption(new Option("--container-host <ip>", "Host IP to bind published container ports to. Must be a numeric IP (Docker rejects hostnames here)").default("127.0.0.1")).addOption(new Option("--assume-task-role [arn]", "Assume the task definition's TaskRoleArn (or the supplied ARN) and forward STS-issued temp credentials via the metadata sidecar so containers run with the deployed task role. Bare flag uses the template's TaskRoleArn; pass an explicit ARN to override.")).addOption(new Option("--no-pull", "Skip docker pull for every container image and the metadata sidecar")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries.")).addOption(new Option("--platform <platform>", "Force docker --platform (linux/amd64 or linux/arm64). Default: inferred from task RuntimePlatform.CpuArchitecture")).addOption(new Option("--max-tasks <n>", `Hard cap on local replica count. Caps the template DesiredCount so local dev machines don't run an unbounded number of containers. Cannot exceed ${83} due to the per-replica link-local /24 subnet allocator's range.`).default(3).argParser(parseMaxTasks$1)).addOption(new Option("--restart-policy <policy>", "How to react when an essential container exits. 'on-failure' (default) restarts only on non-zero exit; 'always' restarts on every exit; 'none' shuts the replica down and runs the service degraded.").default("on-failure").argParser(parseRestartPolicy$1)).addOption(new Option("--from-state", "Read cdkd S3 state for the target stack and substitute Fn::Sub / Fn::GetAtt / Fn::ImportValue / Fn::GetStackOutput intrinsics in container images, environment variables, secrets, role ARNs, and volumes.").default(false)).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via DescribeStackResources and substitute Ref / Fn::ImportValue in container env vars / secrets / image URIs with the deployed physical IDs / exports. Use for CDK apps deployed via the upstream CDK CLI (`cdk deploy`). Bare form uses the cdkd stack name; pass an explicit value when the CFn stack name differs. Mutually exclusive with --from-state. Fn::GetAtt is warn-and-dropped in v1 (CFn DescribeStackResources does not return per-attribute values).")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-state when the same stack name has state in multiple regions, and with --from-cfn-stack as the CFn client region (cdkd does not have a separate --cfn-stack-region flag).")).action(withErrorHandling(localStartServiceCommand));
|
|
49824
49849
|
[
|
|
49825
49850
|
...commonOptions,
|
|
49826
49851
|
...appOptions,
|
|
@@ -50637,6 +50662,189 @@ function createLocalInvokeAgentCoreCommand() {
|
|
|
50637
50662
|
return cmd;
|
|
50638
50663
|
}
|
|
50639
50664
|
|
|
50665
|
+
//#endregion
|
|
50666
|
+
//#region src/cli/commands/local-start-alb.ts
|
|
50667
|
+
/**
|
|
50668
|
+
* Issue #86 v1 — parse `--lb-port <listenerPort>=<hostPort>` overrides into a
|
|
50669
|
+
* `listenerPort -> hostPort` map. The local ALB front-door binds the listener
|
|
50670
|
+
* port on the host by default, but a privileged listener port (e.g. 80 / 443)
|
|
50671
|
+
* fails to bind as non-root on macOS, so the user opts in to a non-privileged
|
|
50672
|
+
* host port (e.g. `--lb-port 80=8080`). Repeatable; each value is
|
|
50673
|
+
* `<listenerPort>=<hostPort>` with both in 1-65535.
|
|
50674
|
+
*/
|
|
50675
|
+
function parseLbPortOverrides(values) {
|
|
50676
|
+
const out = {};
|
|
50677
|
+
for (const raw of values ?? []) {
|
|
50678
|
+
const m = /^(\d+)=(\d+)$/.exec(raw.trim());
|
|
50679
|
+
if (!m) throw new LocalStartServiceError(`Invalid --lb-port '${raw}'. Expected <listenerPort>=<hostPort> (e.g. 80=8080).`);
|
|
50680
|
+
const listenerPort = Number(m[1]);
|
|
50681
|
+
const hostPort = Number(m[2]);
|
|
50682
|
+
for (const [label, p] of [["listener", listenerPort], ["host", hostPort]]) if (p < 1 || p > 65535) throw new LocalStartServiceError(`Invalid --lb-port '${raw}': ${label} port must be 1-65535.`);
|
|
50683
|
+
out[listenerPort] = hostPort;
|
|
50684
|
+
}
|
|
50685
|
+
return out;
|
|
50686
|
+
}
|
|
50687
|
+
/**
|
|
50688
|
+
* Resolve an ALB target string (`Stack/Path` display path or `Stack:LogicalId`)
|
|
50689
|
+
* to its stack + `AWS::ElasticLoadBalancingV2::LoadBalancer` logical id. Mirrors
|
|
50690
|
+
* the ECS service resolver's target grammar.
|
|
50691
|
+
*/
|
|
50692
|
+
function resolveAlbTarget(target, stacks) {
|
|
50693
|
+
if (stacks.length === 0) throw new LocalStartServiceError("No stacks found in the synthesized assembly.");
|
|
50694
|
+
const parsed = parseEcsTarget(target);
|
|
50695
|
+
const stack = pickStack(parsed.stackPattern, stacks, target);
|
|
50696
|
+
const resources = stack.template.Resources ?? {};
|
|
50697
|
+
if (parsed.isPath) {
|
|
50698
|
+
const index = buildCdkPathIndex(stack.template);
|
|
50699
|
+
const albs = resolveCdkPathToLogicalIds(parsed.pathOrId, index).filter(({ logicalId }) => {
|
|
50700
|
+
const r = resources[logicalId];
|
|
50701
|
+
return r !== void 0 && isApplicationLoadBalancer(r);
|
|
50702
|
+
});
|
|
50703
|
+
if (albs.length === 0) throw notFound(target, stack, resources);
|
|
50704
|
+
if (albs.length > 1) throw new LocalStartServiceError(`Target '${target}' matches ${albs.length} load balancers in ${stack.stackName}: ${albs.map((a) => a.logicalId).join(", ")}. Refine the path or use the stack:LogicalId form.`);
|
|
50705
|
+
return {
|
|
50706
|
+
stack,
|
|
50707
|
+
albLogicalId: albs[0].logicalId
|
|
50708
|
+
};
|
|
50709
|
+
}
|
|
50710
|
+
const res = resources[parsed.pathOrId];
|
|
50711
|
+
if (!res || !isApplicationLoadBalancer(res)) throw notFound(target, stack, resources);
|
|
50712
|
+
return {
|
|
50713
|
+
stack,
|
|
50714
|
+
albLogicalId: parsed.pathOrId
|
|
50715
|
+
};
|
|
50716
|
+
}
|
|
50717
|
+
function pickStack(stackPattern, stacks, target) {
|
|
50718
|
+
if (stackPattern === null) {
|
|
50719
|
+
if (stacks.length === 1) return stacks[0];
|
|
50720
|
+
throw new LocalStartServiceError(`Target '${target}' has no stack prefix, and the assembly contains ${stacks.length} stacks: ${stacks.map((s) => s.stackName).join(", ")}. Pass it as 'Stack/Path' or 'Stack:LogicalId'.`);
|
|
50721
|
+
}
|
|
50722
|
+
const matched = matchStacks(stacks, [stackPattern]);
|
|
50723
|
+
if (matched.length === 0) throw new LocalStartServiceError(`No stack matches '${stackPattern}'. Available stacks: ${stacks.map((s) => s.stackName).join(", ")}.`);
|
|
50724
|
+
if (matched.length > 1) throw new LocalStartServiceError(`Multiple stacks match '${stackPattern}': ${matched.map((s) => s.stackName).join(", ")}. Refine the pattern.`);
|
|
50725
|
+
return matched[0];
|
|
50726
|
+
}
|
|
50727
|
+
function notFound(target, stack, resources) {
|
|
50728
|
+
const albs = Object.entries(resources).filter(([, r]) => r.Type === "AWS::ElasticLoadBalancingV2::LoadBalancer").map(([logicalId]) => logicalId);
|
|
50729
|
+
const available = albs.length > 0 ? ` Available load balancers in ${stack.stackName}: ${albs.join(", ")}.` : ` ${stack.stackName} declares no AWS::ElasticLoadBalancingV2::LoadBalancer resources.`;
|
|
50730
|
+
return new LocalStartServiceError(`Target '${target}' did not match an application Load Balancer in ${stack.stackName}.${available}`);
|
|
50731
|
+
}
|
|
50732
|
+
/**
|
|
50733
|
+
* `cdkl start-alb` strategy — name the ALB, boot the ECS service(s) behind it,
|
|
50734
|
+
* and expose each listener via a local front-door. Mirrors how `start-api`
|
|
50735
|
+
* names the API and serves its backing Lambdas.
|
|
50736
|
+
*/
|
|
50737
|
+
function albStrategy(options) {
|
|
50738
|
+
const lbPortOverrides = parseLbPortOverrides(options.lbPort);
|
|
50739
|
+
return {
|
|
50740
|
+
pickEntries: (stacks) => listTargets(stacks).loadBalancers,
|
|
50741
|
+
pickerMessage: "Select one or more Application Load Balancers to run",
|
|
50742
|
+
pickerNoun: "Application Load Balancers",
|
|
50743
|
+
onMissing: () => new LocalStartServiceError(`${getEmbedConfig().cliName} start-alb requires at least one <target>. Pass one or more ALB paths like 'Stack/MyAlb', or run it in a TTY to pick interactively.`),
|
|
50744
|
+
resolveBoots: (stacks, chosenTargets) => {
|
|
50745
|
+
const warnings = [];
|
|
50746
|
+
const serviceTargets = /* @__PURE__ */ new Set();
|
|
50747
|
+
const listeners = [];
|
|
50748
|
+
const claimedHostPorts = /* @__PURE__ */ new Map();
|
|
50749
|
+
for (const albTarget of chosenTargets) {
|
|
50750
|
+
const { stack, albLogicalId } = resolveAlbTarget(albTarget, stacks);
|
|
50751
|
+
const resolution = resolveAlbFrontDoor(stack, albLogicalId);
|
|
50752
|
+
warnings.push(...resolution.warnings);
|
|
50753
|
+
const qualifyTarget = (t) => {
|
|
50754
|
+
if (t.kind === "lambda") return {
|
|
50755
|
+
kind: "lambda",
|
|
50756
|
+
lambda: resolveLambdaTarget(`${stack.stackName}:${t.lambdaLogicalId}`, stacks),
|
|
50757
|
+
targetGroupArn: `${stack.stackName}:${t.targetGroupLogicalId}`,
|
|
50758
|
+
multiValueHeaders: t.multiValueHeaders,
|
|
50759
|
+
weight: t.weight
|
|
50760
|
+
};
|
|
50761
|
+
const serviceTarget = `${stack.stackName}:${t.serviceLogicalId}`;
|
|
50762
|
+
serviceTargets.add(serviceTarget);
|
|
50763
|
+
return {
|
|
50764
|
+
kind: "ecs",
|
|
50765
|
+
serviceTarget,
|
|
50766
|
+
targetContainerName: t.targetContainerName,
|
|
50767
|
+
targetContainerPort: t.targetContainerPort,
|
|
50768
|
+
weight: t.weight
|
|
50769
|
+
};
|
|
50770
|
+
};
|
|
50771
|
+
const qualify = (action) => {
|
|
50772
|
+
if (action.kind === "forward") return {
|
|
50773
|
+
kind: "forward",
|
|
50774
|
+
targets: action.targets.map(qualifyTarget)
|
|
50775
|
+
};
|
|
50776
|
+
if (action.kind === "redirect") return {
|
|
50777
|
+
kind: "redirect",
|
|
50778
|
+
statusCode: action.statusCode,
|
|
50779
|
+
...action.protocol !== void 0 && { protocol: action.protocol },
|
|
50780
|
+
...action.host !== void 0 && { host: action.host },
|
|
50781
|
+
...action.port !== void 0 && { port: action.port },
|
|
50782
|
+
...action.path !== void 0 && { path: action.path },
|
|
50783
|
+
...action.query !== void 0 && { query: action.query }
|
|
50784
|
+
};
|
|
50785
|
+
return {
|
|
50786
|
+
kind: "fixed-response",
|
|
50787
|
+
statusCode: action.statusCode,
|
|
50788
|
+
...action.contentType !== void 0 && { contentType: action.contentType },
|
|
50789
|
+
...action.messageBody !== void 0 && { messageBody: action.messageBody }
|
|
50790
|
+
};
|
|
50791
|
+
};
|
|
50792
|
+
for (const listener of resolution.listeners) {
|
|
50793
|
+
const hostPort = lbPortOverrides[listener.listenerPort] ?? listener.listenerPort;
|
|
50794
|
+
const claimedBy = claimedHostPorts.get(hostPort);
|
|
50795
|
+
if (claimedBy !== void 0) {
|
|
50796
|
+
warnings.push(`Listener port ${listener.listenerPort} would bind host port ${hostPort}, already claimed by listener port ${claimedBy}; the local front-door fronts only the first. Use --lb-port to remap one of them.`);
|
|
50797
|
+
continue;
|
|
50798
|
+
}
|
|
50799
|
+
claimedHostPorts.set(hostPort, listener.listenerPort);
|
|
50800
|
+
listeners.push({
|
|
50801
|
+
listenerPort: listener.listenerPort,
|
|
50802
|
+
hostPort,
|
|
50803
|
+
protocol: listener.listenerProtocol,
|
|
50804
|
+
...listener.defaultAction ? { defaultAction: qualify(listener.defaultAction) } : {},
|
|
50805
|
+
...listener.defaultAuthGuard ? { defaultAuthGuard: listener.defaultAuthGuard } : {},
|
|
50806
|
+
rules: listener.rules.map((r) => ({
|
|
50807
|
+
priority: r.priority,
|
|
50808
|
+
pathPatterns: r.pathPatterns,
|
|
50809
|
+
hostPatterns: r.hostPatterns,
|
|
50810
|
+
httpHeaderConditions: r.httpHeaderConditions,
|
|
50811
|
+
httpRequestMethods: r.httpRequestMethods,
|
|
50812
|
+
queryStringConditions: r.queryStringConditions,
|
|
50813
|
+
sourceIpCidrs: r.sourceIpCidrs,
|
|
50814
|
+
action: qualify(r.action),
|
|
50815
|
+
...r.authGuard ? { authGuard: r.authGuard } : {}
|
|
50816
|
+
}))
|
|
50817
|
+
});
|
|
50818
|
+
}
|
|
50819
|
+
}
|
|
50820
|
+
const boots = [...serviceTargets].map((target) => ({ target }));
|
|
50821
|
+
const resolvedPorts = new Set(listeners.map((l) => l.listenerPort));
|
|
50822
|
+
for (const portStr of Object.keys(lbPortOverrides)) {
|
|
50823
|
+
const port = Number(portStr);
|
|
50824
|
+
if (!resolvedPorts.has(port)) warnings.push(`--lb-port override for listener port ${port} matched no ALB listener resolved for the named target(s); it was ignored.`);
|
|
50825
|
+
}
|
|
50826
|
+
return {
|
|
50827
|
+
boots,
|
|
50828
|
+
...listeners.length > 0 ? { frontDoor: { listeners } } : {},
|
|
50829
|
+
warnings
|
|
50830
|
+
};
|
|
50831
|
+
},
|
|
50832
|
+
lbPortOverrides
|
|
50833
|
+
};
|
|
50834
|
+
}
|
|
50835
|
+
/**
|
|
50836
|
+
* `cdkl start-alb <Stack/Alb>` — Issue #86 v1. Names an
|
|
50837
|
+
* `AWS::ElasticLoadBalancingV2::LoadBalancer`, discovers the ECS service(s)
|
|
50838
|
+
* behind its HTTP `forward` listeners, boots their replicas, and stands up a
|
|
50839
|
+
* local front-door on each listener port that round-robins across the replicas.
|
|
50840
|
+
* The symmetric ALB counterpart of `start-api`.
|
|
50841
|
+
*/
|
|
50842
|
+
function createLocalStartAlbCommand() {
|
|
50843
|
+
return addCommonEcsServiceOptions(new Command("start-alb").description("Run an Application Load Balancer locally: name the ALB, and cdk-local boots the ECS service(s) behind its listeners and stands up a local front-door on each listener port that round-robins across the running replicas and routes its listener rules across the backing services — a stable host endpoint, like behind a real load balancer. The symmetric ALB counterpart of `start-api`. Each <target> accepts a CDK display path (MyStack/MyAlb) or stack-qualified logical ID; single-stack apps may omit the stack prefix. Supports HTTP and HTTPS listeners (TLS terminated locally with --tls-cert/--tls-key or an auto-generated self-signed cert); all six ALB rule-condition fields (path-pattern / host-header / http-header / http-request-method / query-string / source-ip); forward (single and weighted), redirect, and fixed-response actions; and ECS or Lambda targets (a Lambda target group is invoked locally via the Lambda RIE). authenticate-cognito / authenticate-oidc actions enforce a local Bearer-JWT check (or AWSELBAuthSessionCookie pass-through) against the same JWKS / OIDC discovery URL the deployed ALB would; use --bearer-token <jwt> to inject a default token or --no-verify-auth to disable the guard. Omit <targets> in an interactive terminal to multi-select the load balancers from a list.").argument("[targets...]", "One or more CDK display paths or stack-qualified logical IDs of the AWS::ElasticLoadBalancingV2::LoadBalancer resources to run (omit to multi-select interactively in a TTY)").addOption(new Option("--lb-port <listenerPort=hostPort...>", "Bind the local front-door on a specific host port (e.g. 80=8080); repeatable. Default: host port == ALB listener port. Use this on macOS to remap a privileged listener port (< 1024) to a non-privileged host port.")).addOption(new Option("--tls-cert <path>", "PEM-encoded server certificate for HTTPS front-door listeners. Must be set together with --tls-key. Omit both flags to auto-generate a self-signed cert (cached under $XDG_CACHE_HOME/cdk-local/alb-https/, default ~/.cache/cdk-local/alb-https/); requires openssl on PATH. The deployed Listener Certificates[] are NOT fetched (ACM private keys are not retrievable by design). The auto-generated cert lists DNS:localhost,IP:127.0.0.1 as SubjectAltName, so a client validating a non-loopback --container-host will fail the SAN check — pass --tls-cert / --tls-key with a SAN covering that host instead.")).addOption(new Option("--tls-key <path>", "PEM-encoded server private key matching --tls-cert. Must be set together with --tls-cert.")).addOption(new Option("--no-verify-auth", "Disable local enforcement of authenticate-cognito / authenticate-oidc actions. Every request is served as if the auth check passed. Useful for local dev where you do not want to mint a Bearer token at all.")).addOption(new Option("--bearer-token <jwt>", "Default Bearer JWT injected as Authorization: Bearer <jwt> when the inbound request has none. Verified against the same JWKS / OIDC discovery URL the deployed ALB would (signature + iss + aud + exp). Local-dev convenience; cookie pass-through (AWSELBAuthSessionCookie-*) also works.")).addOption(new Option("--from-state", "Read cdkd's S3 state for the target stack and substitute Ref / Fn::GetAtt / Fn::Sub / Fn::ImportValue / Fn::GetStackOutput intrinsics in container images, environment variables, secrets, role ARNs, and volumes of the ECS services behind the ALB. Mutually exclusive with --from-cfn-stack.").default(false)).addOption(new Option("--state-bucket <bucket>", "S3 bucket for --from-state. Falls back to CDKD_STATE_BUCKET env or cdk.json context.cdkd.stateBucket.")).addOption(new Option("--state-prefix <prefix>", "S3 key prefix for --from-state state files.").default("cdkd")).action(withErrorHandling(async (targets, options) => {
|
|
50844
|
+
await runEcsServiceEmulator(targets, options, albStrategy(options), cdkdExtraStateProviders);
|
|
50845
|
+
})));
|
|
50846
|
+
}
|
|
50847
|
+
|
|
50640
50848
|
//#endregion
|
|
50641
50849
|
//#region src/cli/commands/local-invoke.ts
|
|
50642
50850
|
/**
|
|
@@ -51440,6 +51648,7 @@ function createLocalCommand() {
|
|
|
51440
51648
|
local.addCommand(createLocalRunTaskCommand());
|
|
51441
51649
|
local.addCommand(createLocalStartServiceCommand());
|
|
51442
51650
|
local.addCommand(createLocalInvokeAgentCoreCommand());
|
|
51651
|
+
local.addCommand(createLocalStartAlbCommand());
|
|
51443
51652
|
return local;
|
|
51444
51653
|
}
|
|
51445
51654
|
|
|
@@ -52552,7 +52761,7 @@ function reorderArgs(argv) {
|
|
|
52552
52761
|
*/
|
|
52553
52762
|
async function main() {
|
|
52554
52763
|
const program = new Command();
|
|
52555
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
52764
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.196.0");
|
|
52556
52765
|
program.addCommand(createBootstrapCommand());
|
|
52557
52766
|
program.addCommand(createSynthCommand());
|
|
52558
52767
|
program.addCommand(createListCommand());
|