quidproquo-deploy-awscdk 0.1.13 → 0.1.15
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/lib/commonjs/constructs/feature/core/keyValueStore/QpqApiCoreKeyValueStoreStreamConstruct.js +13 -4
- package/lib/commonjs/constructs/feature/core/keyValueStore/QpqApiCoreKeyValueStoreStreamConstruct.js.map +1 -1
- package/lib/commonjs/constructs/feature/core/keyValueStore/QpqCoreKeyValueStoreConstruct.js +1 -1
- package/lib/commonjs/constructs/feature/core/keyValueStore/QpqCoreKeyValueStoreConstruct.js.map +1 -1
- package/lib/esm/constructs/feature/core/keyValueStore/QpqApiCoreKeyValueStoreStreamConstruct.js +13 -4
- package/lib/esm/constructs/feature/core/keyValueStore/QpqApiCoreKeyValueStoreStreamConstruct.js.map +1 -1
- package/lib/esm/constructs/feature/core/keyValueStore/QpqCoreKeyValueStoreConstruct.js +1 -1
- package/lib/esm/constructs/feature/core/keyValueStore/QpqCoreKeyValueStoreConstruct.js.map +1 -1
- package/package.json +7 -7
- package/src/constructs/feature/core/keyValueStore/QpqApiCoreKeyValueStoreStreamConstruct.ts +13 -4
- package/src/constructs/feature/core/keyValueStore/QpqCoreKeyValueStoreConstruct.ts +1 -1
package/lib/commonjs/constructs/feature/core/keyValueStore/QpqApiCoreKeyValueStoreStreamConstruct.js
CHANGED
|
@@ -36,9 +36,7 @@ class QpqApiCoreKeyValueStoreStreamConstruct extends QpqConstructBlock_1.QpqCons
|
|
|
36
36
|
if (!streamConfig) {
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
|
-
const isOwnedHere = quidproquo_core_1.qpqCoreUtils
|
|
40
|
-
.getOwnedKeyValueStores(props.qpqConfig)
|
|
41
|
-
.some((kvs) => kvs.uniqueKey === props.keyValueStoreConfig.uniqueKey);
|
|
39
|
+
const isOwnedHere = quidproquo_core_1.qpqCoreUtils.getOwnedKeyValueStores(props.qpqConfig).some((kvs) => kvs.uniqueKey === props.keyValueStoreConfig.uniqueKey);
|
|
42
40
|
if (!isOwnedHere) {
|
|
43
41
|
return;
|
|
44
42
|
}
|
|
@@ -54,7 +52,18 @@ class QpqApiCoreKeyValueStoreStreamConstruct extends QpqConstructBlock_1.QpqCons
|
|
|
54
52
|
});
|
|
55
53
|
const func = new Function_1.Function(this, 'stream', {
|
|
56
54
|
reacreateOnFunctionNameChange: true,
|
|
57
|
-
|
|
55
|
+
// Two characters, and that is not a style choice - it is the whole budget.
|
|
56
|
+
//
|
|
57
|
+
// A Lambda name is capped at 64 characters, and this resource carries the longest
|
|
58
|
+
// prefix of anything qpq names: the store name already has `EventLog` appended before
|
|
59
|
+
// the app-service-environment-feature decoration and the `-qpq` marker are added. The
|
|
60
|
+
// longest today is `qpq-admin-sessionsEventLog`, which leaves exactly two characters
|
|
61
|
+
// for the type. `kvsStream` overran by four; even `str` overruns by one.
|
|
62
|
+
//
|
|
63
|
+
// So this sits at exactly 64 with no headroom: a longer store name, service, feature
|
|
64
|
+
// or actor name will fail synth again, and the fix at that point is a general
|
|
65
|
+
// shortening rule in Function.ts rather than more characters shaved off here.
|
|
66
|
+
functionName: this.qpqResourceName(props.keyValueStoreConfig.keyValueStoreName, 'ks'),
|
|
58
67
|
functionType: 'dynamoStreamEvent_kvsStreamEvent',
|
|
59
68
|
executorName: 'dynamoStreamEvent_kvsStreamEvent',
|
|
60
69
|
qpqConfig: props.qpqConfig,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QpqApiCoreKeyValueStoreStreamConstruct.js","sourceRoot":"","sources":["../../../../../../src/constructs/feature/core/keyValueStore/QpqApiCoreKeyValueStoreStreamConstruct.ts"],"names":[],"mappings":";;;AAAA,qDAAyF;AAEzF,6CAAoG;AAGpG,uEAA4F;AAC5F,sDAAmD;AACnD,mFAA+E;AAO/E,uFAAuF;AACvF,yFAAyF;AACzF,wFAAwF;AACxF,6DAA6D;AAC7D,EAAE;AACF,4FAA4F;AAC5F,6CAA6C;AAC7C,sEAAsE;AACtE,0FAA0F;AAC1F,4CAA4C;AAC5C,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAEhC,2FAA2F;AAC3F,qFAAqF;AACrF,yFAAyF;AACzF,+DAA+D;AAC/D,MAAM,yBAAyB,GAAG,GAAG,CAAC;AACtC,MAAM,sCAAsC,GAAG,CAAC,CAAC;AAEjD;;;;GAIG;AACH,MAAa,sCAAuC,SAAQ,qCAAiB;IAC3E,YAAY,KAAgB,EAAE,EAAU,EAAE,KAAkD;;QAC1F,KAAK,CAAC,KAAK,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC;QAExB,MAAM,YAAY,GAAG,KAAK,CAAC,mBAAmB,CAAC,QAAQ,CAAC;QACxD,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QAED,MAAM,WAAW,GAAG,8BAAY
|
|
1
|
+
{"version":3,"file":"QpqApiCoreKeyValueStoreStreamConstruct.js","sourceRoot":"","sources":["../../../../../../src/constructs/feature/core/keyValueStore/QpqApiCoreKeyValueStoreStreamConstruct.ts"],"names":[],"mappings":"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","sourcesContent":["import { KeyValueStoreQPQConfigSetting, QPQConfig, qpqCoreUtils } from 'quidproquo-core';\n\nimport { aws_dynamodb, aws_lambda, aws_lambda_event_sources, aws_ssm, Duration } from 'aws-cdk-lib';\nimport { Construct } from 'constructs';\n\nimport { QpqConstructBlock, QpqConstructBlockProps } from '../../../base/QpqConstructBlock';\nimport { Function } from '../../../basic/Function';\nimport { kvsStreamArnSsmParameterName } from './QpqCoreKeyValueStoreConstruct';\n\nexport interface QpqApiCoreKeyValueStoreStreamConstructProps extends QpqConstructBlockProps {\n keyValueStoreConfig: KeyValueStoreQPQConfigSetting;\n apiLayerVersions?: aws_lambda.ILayerVersion[];\n}\n\n// A failing batch is retried, and by DEFAULT a DynamoDB stream keeps retrying the SAME\n// batch until it succeeds or the records expire (24 hours), which stalls the whole shard\n// behind one poison record. That is the single sharpest edge of using streams, so these\n// are set deliberately rather than left to the AWS defaults:\n//\n// - bisectBatchOnError narrows a failing batch down to the individual bad record instead of\n// condemning everything it travelled with.\n// - retryAttempts bounds how long a bad record can hold up the shard.\n// - onFailure sends what could not be processed somewhere visible, so a skipped change is\n// an item in a queue rather than silence.\nconst STREAM_RETRY_ATTEMPTS = 5;\n\n// Records per invocation, and how long to wait accumulating them. The window is what makes\n// coalescing worth having: a burst of writes to one item arrives as one batch, and a\n// projection collapses it to a single rebuild. It costs projection latency, which is the\n// trade an eventually-consistent read model is already making.\nconst DEFAULT_STREAM_BATCH_SIZE = 100;\nconst DEFAULT_STREAM_BATCHING_WINDOW_SECONDS = 5;\n\n/**\n * Wires a key-value store's stream to its handler. Created only for stores declaring\n * `onStream`, and only on the owning service's stack — attaching an event source to\n * another service's table would have two services processing the same records.\n */\nexport class QpqApiCoreKeyValueStoreStreamConstruct extends QpqConstructBlock {\n constructor(scope: Construct, id: string, props: QpqApiCoreKeyValueStoreStreamConstructProps) {\n super(scope, id, props);\n\n const streamConfig = props.keyValueStoreConfig.onStream;\n if (!streamConfig) {\n return;\n }\n\n const isOwnedHere = qpqCoreUtils.getOwnedKeyValueStores(props.qpqConfig).some((kvs) => kvs.uniqueKey === props.keyValueStoreConfig.uniqueKey);\n\n if (!isOwnedHere) {\n return;\n }\n\n // fromOtherStack imports by NAME, which carries no stream ARN, and a stream ARN ends\n // in a creation timestamp so it cannot be recomputed. The Inf stack publishes it to a\n // deterministically-named SSM parameter; this resolves at deploy time, so synth stays\n // independent of the other stack.\n const tableName = this.qpqResourceName(props.keyValueStoreConfig.keyValueStoreName, 'kvs');\n const tableStreamArn = aws_ssm.StringParameter.valueForStringParameter(this, kvsStreamArnSsmParameterName(tableName));\n\n const table = aws_dynamodb.Table.fromTableAttributes(this, 'stream-table', {\n tableName,\n tableStreamArn,\n });\n\n const func = new Function(this, 'stream', {\n reacreateOnFunctionNameChange: true,\n // Two characters, and that is not a style choice - it is the whole budget.\n //\n // A Lambda name is capped at 64 characters, and this resource carries the longest\n // prefix of anything qpq names: the store name already has `EventLog` appended before\n // the app-service-environment-feature decoration and the `-qpq` marker are added. The\n // longest today is `qpq-admin-sessionsEventLog`, which leaves exactly two characters\n // for the type. `kvsStream` overran by four; even `str` overruns by one.\n //\n // So this sits at exactly 64 with no headroom: a longer store name, service, feature\n // or actor name will fail synth again, and the fix at that point is a general\n // shortening rule in Function.ts rather than more characters shaved off here.\n functionName: this.qpqResourceName(props.keyValueStoreConfig.keyValueStoreName, 'ks'),\n\n functionType: 'dynamoStreamEvent_kvsStreamEvent',\n executorName: 'dynamoStreamEvent_kvsStreamEvent',\n\n qpqConfig: props.qpqConfig,\n\n apiLayerVersions: props.apiLayerVersions,\n\n environment: {\n // Never change: keyValueStoreName ~ hard coded to stop logs being recursive,\n // matching the storage drive event lambda's storageDriveName.\n keyValueStoreName: props.keyValueStoreConfig.keyValueStoreName,\n kvsStreamEntry: JSON.stringify(streamConfig.runtime),\n kvsStreamCoalesceByPartitionKey: String(!!streamConfig.coalesceByPartitionKey),\n kvsStreamPartitionKey: props.keyValueStoreConfig.partitionKey.key,\n },\n\n role: this.getServiceRole(),\n });\n\n func.lambdaFunction.addEventSource(\n new aws_lambda_event_sources.DynamoEventSource(table, {\n startingPosition: aws_lambda.StartingPosition.TRIM_HORIZON,\n\n batchSize: streamConfig.batchSize ?? DEFAULT_STREAM_BATCH_SIZE,\n maxBatchingWindow: Duration.seconds(streamConfig.maximumBatchingWindowInSeconds ?? DEFAULT_STREAM_BATCHING_WINDOW_SECONDS),\n\n // See the note above: without these a single bad record stalls the shard until the\n // records age out, and nothing records that it happened.\n bisectBatchOnError: true,\n retryAttempts: STREAM_RETRY_ATTEMPTS,\n reportBatchItemFailures: true,\n }),\n );\n }\n}\n"]}
|
|
@@ -194,7 +194,7 @@ class QpqCoreKeyValueStoreConstruct extends QpqCoreKeyValueStoreConstructBase {
|
|
|
194
194
|
const resources = [...ownedArns, ...foreignArns];
|
|
195
195
|
if (resources.length === 0)
|
|
196
196
|
return;
|
|
197
|
-
qpqDeployAwsCdkUtils.attachManagedResourcePolicies(scope, role, 'webserverKeyValueStoreAccess', ['dynamodb:GetItem', 'dynamodb:PutItem', 'dynamodb:Query', 'dynamodb:Scan', 'dynamodb:UpdateItem', 'dynamodb:DeleteItem'], resources);
|
|
197
|
+
qpqDeployAwsCdkUtils.attachManagedResourcePolicies(scope, role, 'webserverKeyValueStoreAccess', ['dynamodb:GetItem', 'dynamodb:PutItem', 'dynamodb:BatchWriteItem', 'dynamodb:Query', 'dynamodb:Scan', 'dynamodb:UpdateItem', 'dynamodb:DeleteItem'], resources);
|
|
198
198
|
// Grant KMS permissions for any encrypted KVSs (owned or foreign) whose
|
|
199
199
|
// customer-managed key is declared in this service's config.
|
|
200
200
|
const kmsArns = [...ownedKvsConfigs, ...foreignKvsConfigs]
|
package/lib/commonjs/constructs/feature/core/keyValueStore/QpqCoreKeyValueStoreConstruct.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QpqCoreKeyValueStoreConstruct.js","sourceRoot":"","sources":["../../../../../../src/constructs/feature/core/keyValueStore/QpqCoreKeyValueStoreConstruct.ts"],"names":[],"mappings":"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","sourcesContent":["import { awsNamingUtils } from 'quidproquo-actionprocessor-awslambda';\nimport { AwsDataStoreRemovalPolicy, qpqConfigAwsUtils } from 'quidproquo-config-aws';\nimport { KeyValueStoreQPQConfigSetting, KvsKey, QPQConfig, qpqCoreUtils } from 'quidproquo-core';\n\nimport { aws_dynamodb, aws_iam, aws_kms, aws_ssm } from 'aws-cdk-lib';\nimport * as cdk from 'aws-cdk-lib';\nimport { Construct } from 'constructs';\n\nimport * as qpqDeployAwsCdkUtils from '../../../../utils/qpqDeployAwsCdkUtils';\nimport { createDefaultResourceAlarm } from '../../../base/createDefaultResourceAlarm';\nimport { QpqConstructBlock, QpqConstructBlockProps } from '../../../base/QpqConstructBlock';\n\nexport interface QpqCoreKeyValueStoreConstructProps extends QpqConstructBlockProps {\n keyValueStoreConfig: KeyValueStoreQPQConfigSetting;\n}\n\nexport abstract class QpqCoreKeyValueStoreConstructBase extends QpqConstructBlock {\n abstract table: aws_dynamodb.ITable;\n\n public grantRead(grantee: aws_iam.IGrantable) {\n this.table.grantReadData(grantee);\n }\n\n public grantWrite(grantee: aws_iam.IGrantable) {\n this.table.grantWriteData(grantee);\n }\n\n public grantAll(grantee: aws_iam.IGrantable) {\n this.table.grantFullAccess(grantee);\n }\n}\n\n// Deterministic SSM location for a store's stream ARN, derived from the table's own\n// resource name so both stacks compute it without talking to each other.\nexport const kvsStreamArnSsmParameterName = (tableResourceName: string): string => `/qpq/kvs-stream-arn/${tableResourceName}`;\n\nexport const convertKvsKeyTypeToDynamodbAttributeType = (kvsKeyType: KvsKey['type']): aws_dynamodb.AttributeType => {\n switch (kvsKeyType) {\n case 'string':\n return aws_dynamodb.AttributeType.STRING;\n case 'number':\n return aws_dynamodb.AttributeType.NUMBER;\n case 'binary':\n return aws_dynamodb.AttributeType.BINARY;\n }\n\n throw new Error(`Unknown KvsKeyType: ${kvsKeyType}`);\n};\n\nexport const convertKvsKeyToDynamodbAttribute = (kvsKey: KvsKey): aws_dynamodb.Attribute => ({\n name: kvsKey.key,\n type: convertKvsKeyTypeToDynamodbAttributeType(kvsKey.type),\n});\n\nexport class QpqCoreKeyValueStoreConstruct extends QpqCoreKeyValueStoreConstructBase {\n table: aws_dynamodb.ITable;\n\n static fromOtherStack(scope: Construct, id: string, qpqConfig: QPQConfig, keyValueStoreName: string): QpqCoreKeyValueStoreConstructBase {\n const tableNameOverride = qpqConfigAwsUtils.getDynamoTableNameOverrride(keyValueStoreName, qpqConfig);\n\n class Import extends QpqCoreKeyValueStoreConstructBase {\n table = aws_dynamodb.Table.fromTableName(this, 'table', tableNameOverride || this.qpqResourceName(keyValueStoreName, 'kvs'));\n }\n\n return new Import(scope, id, { qpqConfig });\n }\n\n constructor(scope: Construct, id: string, props: QpqCoreKeyValueStoreConstructProps) {\n super(scope, id, props);\n\n const dataStoreRemovalPolicy = qpqConfigAwsUtils.getAwsDataStoreRemovalPolicy(props.qpqConfig);\n\n const [primarySortKey] = props.keyValueStoreConfig.sortKeys;\n\n let tableEncryption: aws_dynamodb.TableEncryption | undefined;\n let encryptionKey: aws_kms.IKey | undefined;\n if (props.keyValueStoreConfig.encryption) {\n const kmsCfg = qpqConfigAwsUtils.getAwsKmsKeyForKeyValueStore(props.qpqConfig, props.keyValueStoreConfig);\n if (kmsCfg) {\n encryptionKey = aws_kms.Key.fromKeyArn(this, 'enc-key', kmsCfg.arn);\n tableEncryption = aws_dynamodb.TableEncryption.CUSTOMER_MANAGED;\n } else {\n tableEncryption = aws_dynamodb.TableEncryption.AWS_MANAGED;\n }\n }\n\n const table = new aws_dynamodb.Table(this, 'table', {\n tableName: this.qpqResourceName(props.keyValueStoreConfig.keyValueStoreName, 'kvs'),\n partitionKey: convertKvsKeyToDynamodbAttribute(props.keyValueStoreConfig.partitionKey),\n sortKey: primarySortKey && convertKvsKeyToDynamodbAttribute(primarySortKey),\n billingMode: aws_dynamodb.BillingMode.PAY_PER_REQUEST,\n // Retain data stores by default; dev configs opt into full teardown via defineAwsDataStoreRemovalPolicy(destroy).\n // When retained, deletionProtection also blocks deletes/replacements outright (RETAIN alone only orphans the table).\n removalPolicy: dataStoreRemovalPolicy === AwsDataStoreRemovalPolicy.destroy ? cdk.RemovalPolicy.DESTROY : cdk.RemovalPolicy.RETAIN,\n deletionProtection: dataStoreRemovalPolicy === AwsDataStoreRemovalPolicy.retain,\n timeToLiveAttribute: props.keyValueStoreConfig.ttlAttribute,\n // PITR on by default (35-day continuous backups / point-in-time restore); opt out per-table via config.\n pointInTimeRecoverySpecification: { pointInTimeRecoveryEnabled: !props.keyValueStoreConfig.disablePointInTimeRecovery },\n encryption: tableEncryption,\n encryptionKey,\n // Change data capture, only when a handler is configured — a stream on a table\n // nothing consumes is pure cost. NEW_AND_OLD_IMAGES so a handler can see what a\n // change actually did, including on a delete, where the new image does not exist.\n stream: props.keyValueStoreConfig.onStream ? aws_dynamodb.StreamViewType.NEW_AND_OLD_IMAGES : undefined,\n });\n\n qpqDeployAwsCdkUtils.applyEnvironmentTags(table, props.qpqConfig);\n\n // Default alarms (opt-in via defineNotifyError): read/write throttling means\n // the table is rejecting requests (hot partition / capacity), user-visible.\n createDefaultResourceAlarm(this, props.qpqConfig, {\n id: 'default-alarm-read-throttle',\n alarmName: this.resourceName(`${props.keyValueStoreConfig.keyValueStoreName}-read-throttle`),\n metric: table.metric('ReadThrottleEvents', { period: cdk.Duration.minutes(1), statistic: 'Sum' }),\n threshold: 1,\n evaluationPeriods: 1,\n });\n createDefaultResourceAlarm(this, props.qpqConfig, {\n id: 'default-alarm-write-throttle',\n alarmName: this.resourceName(`${props.keyValueStoreConfig.keyValueStoreName}-write-throttle`),\n metric: table.metric('WriteThrottleEvents', { period: cdk.Duration.minutes(1), statistic: 'Sum' }),\n threshold: 1,\n evaluationPeriods: 1,\n });\n\n // Do local secondary indexes\n for (let i = 1; i < props.keyValueStoreConfig.sortKeys.length; i++) {\n let sortKey = props.keyValueStoreConfig.sortKeys[i];\n\n table.addLocalSecondaryIndex({\n indexName: sortKey.key,\n sortKey: convertKvsKeyToDynamodbAttribute(sortKey),\n });\n }\n\n // Do global secondary indexes\n for (const index of props.keyValueStoreConfig.indexes) {\n table.addGlobalSecondaryIndex({\n indexName: index.partitionKey.key,\n partitionKey: convertKvsKeyToDynamodbAttribute(index.partitionKey),\n sortKey: index.sortKey && convertKvsKeyToDynamodbAttribute(index.sortKey),\n });\n }\n\n this.table = table;\n\n // Publish the stream ARN for the API stack's handler to pick up.\n //\n // A DynamoDB stream ARN ends in a creation timestamp, so unlike every other resource\n // here it CANNOT be recomputed from naming conventions — which is how the API stack\n // reaches every other table (fromOtherStack). Passing the table object across stacks\n // would work but creates a CloudFormation export, the coupling this codebase avoids\n // everywhere else. So it goes through SSM and is resolved at deploy time, the same\n // trick DomainCertificateLookup uses for cert ARNs.\n if (props.keyValueStoreConfig.onStream) {\n new aws_ssm.StringParameter(this, 'stream-arn', {\n parameterName: kvsStreamArnSsmParameterName(this.qpqResourceName(props.keyValueStoreConfig.keyValueStoreName, 'kvs')),\n stringValue: table.tableStreamArn!,\n });\n }\n\n // If we have an override, lets use it instead\n // we still want to keep the above table created.\n const tableNameOverride = qpqConfigAwsUtils.getDynamoTableNameOverrride(props.keyValueStoreConfig.keyValueStoreName, props.qpqConfig);\n if (tableNameOverride) {\n console.log('tableNameOverride', tableNameOverride);\n this.table = aws_dynamodb.Table.fromTableName(this, 'tablelink', tableNameOverride);\n }\n }\n\n public static authorizeActionsForRole(scope: Construct, role: aws_iam.IRole, qpqConfig: QPQConfig, ownedKvsList: QpqCoreKeyValueStoreConstruct[]) {\n // CDK-known ARNs for tables created in this stack (+ GSI ARNs).\n const ownedArns = ownedKvsList.flatMap((kvs) => [kvs.table.tableArn, `${kvs.table.tableArn}/index/*`]);\n\n // Deterministically-computed ARNs for KVSs declared in this service's\n // config but owned by another service. Uses the same naming path as\n // `getKvsDynamoTableNameFromConfig` so no CDK cross-stack ref is created.\n const allKvsConfigs = qpqCoreUtils.getAllKeyValueStores(qpqConfig);\n const ownedKvsConfigs = qpqCoreUtils.getOwnedKeyValueStores(qpqConfig);\n const foreignKvsConfigs = allKvsConfigs.filter((cfg) => !ownedKvsConfigs.includes(cfg));\n\n const foreignArns = foreignKvsConfigs.flatMap((cfg) => {\n const tableName = awsNamingUtils.getKvsDynamoTableNameFromConfig(cfg.keyValueStoreName, qpqConfig, 'kvs');\n const tableArn = `arn:aws:dynamodb:*:*:table/${tableName}`;\n return [tableArn, `${tableArn}/index/*`];\n });\n\n const resources = [...ownedArns, ...foreignArns];\n if (resources.length === 0) return;\n\n qpqDeployAwsCdkUtils.attachManagedResourcePolicies(\n scope,\n role,\n 'webserverKeyValueStoreAccess',\n ['dynamodb:GetItem', 'dynamodb:PutItem', 'dynamodb:Query', 'dynamodb:Scan', 'dynamodb:UpdateItem', 'dynamodb:DeleteItem'],\n resources,\n );\n\n // Grant KMS permissions for any encrypted KVSs (owned or foreign) whose\n // customer-managed key is declared in this service's config.\n const kmsArns = [...ownedKvsConfigs, ...foreignKvsConfigs]\n .filter((cfg) => cfg.encryption)\n .map((cfg) => qpqConfigAwsUtils.getAwsKmsKeyForKeyValueStore(qpqConfig, cfg)?.arn)\n .filter((arn): arn is string => Boolean(arn));\n\n if (kmsArns.length > 0) {\n role.addToPrincipalPolicy(\n new aws_iam.PolicyStatement({\n effect: aws_iam.Effect.ALLOW,\n actions: ['kms:Decrypt', 'kms:GenerateDataKey*', 'kms:DescribeKey', 'kms:Encrypt', 'kms:ReEncrypt*'],\n resources: [...new Set(kmsArns)],\n }),\n );\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"QpqCoreKeyValueStoreConstruct.js","sourceRoot":"","sources":["../../../../../../src/constructs/feature/core/keyValueStore/QpqCoreKeyValueStoreConstruct.ts"],"names":[],"mappings":"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","sourcesContent":["import { awsNamingUtils } from 'quidproquo-actionprocessor-awslambda';\nimport { AwsDataStoreRemovalPolicy, qpqConfigAwsUtils } from 'quidproquo-config-aws';\nimport { KeyValueStoreQPQConfigSetting, KvsKey, QPQConfig, qpqCoreUtils } from 'quidproquo-core';\n\nimport { aws_dynamodb, aws_iam, aws_kms, aws_ssm } from 'aws-cdk-lib';\nimport * as cdk from 'aws-cdk-lib';\nimport { Construct } from 'constructs';\n\nimport * as qpqDeployAwsCdkUtils from '../../../../utils/qpqDeployAwsCdkUtils';\nimport { createDefaultResourceAlarm } from '../../../base/createDefaultResourceAlarm';\nimport { QpqConstructBlock, QpqConstructBlockProps } from '../../../base/QpqConstructBlock';\n\nexport interface QpqCoreKeyValueStoreConstructProps extends QpqConstructBlockProps {\n keyValueStoreConfig: KeyValueStoreQPQConfigSetting;\n}\n\nexport abstract class QpqCoreKeyValueStoreConstructBase extends QpqConstructBlock {\n abstract table: aws_dynamodb.ITable;\n\n public grantRead(grantee: aws_iam.IGrantable) {\n this.table.grantReadData(grantee);\n }\n\n public grantWrite(grantee: aws_iam.IGrantable) {\n this.table.grantWriteData(grantee);\n }\n\n public grantAll(grantee: aws_iam.IGrantable) {\n this.table.grantFullAccess(grantee);\n }\n}\n\n// Deterministic SSM location for a store's stream ARN, derived from the table's own\n// resource name so both stacks compute it without talking to each other.\nexport const kvsStreamArnSsmParameterName = (tableResourceName: string): string => `/qpq/kvs-stream-arn/${tableResourceName}`;\n\nexport const convertKvsKeyTypeToDynamodbAttributeType = (kvsKeyType: KvsKey['type']): aws_dynamodb.AttributeType => {\n switch (kvsKeyType) {\n case 'string':\n return aws_dynamodb.AttributeType.STRING;\n case 'number':\n return aws_dynamodb.AttributeType.NUMBER;\n case 'binary':\n return aws_dynamodb.AttributeType.BINARY;\n }\n\n throw new Error(`Unknown KvsKeyType: ${kvsKeyType}`);\n};\n\nexport const convertKvsKeyToDynamodbAttribute = (kvsKey: KvsKey): aws_dynamodb.Attribute => ({\n name: kvsKey.key,\n type: convertKvsKeyTypeToDynamodbAttributeType(kvsKey.type),\n});\n\nexport class QpqCoreKeyValueStoreConstruct extends QpqCoreKeyValueStoreConstructBase {\n table: aws_dynamodb.ITable;\n\n static fromOtherStack(scope: Construct, id: string, qpqConfig: QPQConfig, keyValueStoreName: string): QpqCoreKeyValueStoreConstructBase {\n const tableNameOverride = qpqConfigAwsUtils.getDynamoTableNameOverrride(keyValueStoreName, qpqConfig);\n\n class Import extends QpqCoreKeyValueStoreConstructBase {\n table = aws_dynamodb.Table.fromTableName(this, 'table', tableNameOverride || this.qpqResourceName(keyValueStoreName, 'kvs'));\n }\n\n return new Import(scope, id, { qpqConfig });\n }\n\n constructor(scope: Construct, id: string, props: QpqCoreKeyValueStoreConstructProps) {\n super(scope, id, props);\n\n const dataStoreRemovalPolicy = qpqConfigAwsUtils.getAwsDataStoreRemovalPolicy(props.qpqConfig);\n\n const [primarySortKey] = props.keyValueStoreConfig.sortKeys;\n\n let tableEncryption: aws_dynamodb.TableEncryption | undefined;\n let encryptionKey: aws_kms.IKey | undefined;\n if (props.keyValueStoreConfig.encryption) {\n const kmsCfg = qpqConfigAwsUtils.getAwsKmsKeyForKeyValueStore(props.qpqConfig, props.keyValueStoreConfig);\n if (kmsCfg) {\n encryptionKey = aws_kms.Key.fromKeyArn(this, 'enc-key', kmsCfg.arn);\n tableEncryption = aws_dynamodb.TableEncryption.CUSTOMER_MANAGED;\n } else {\n tableEncryption = aws_dynamodb.TableEncryption.AWS_MANAGED;\n }\n }\n\n const table = new aws_dynamodb.Table(this, 'table', {\n tableName: this.qpqResourceName(props.keyValueStoreConfig.keyValueStoreName, 'kvs'),\n partitionKey: convertKvsKeyToDynamodbAttribute(props.keyValueStoreConfig.partitionKey),\n sortKey: primarySortKey && convertKvsKeyToDynamodbAttribute(primarySortKey),\n billingMode: aws_dynamodb.BillingMode.PAY_PER_REQUEST,\n // Retain data stores by default; dev configs opt into full teardown via defineAwsDataStoreRemovalPolicy(destroy).\n // When retained, deletionProtection also blocks deletes/replacements outright (RETAIN alone only orphans the table).\n removalPolicy: dataStoreRemovalPolicy === AwsDataStoreRemovalPolicy.destroy ? cdk.RemovalPolicy.DESTROY : cdk.RemovalPolicy.RETAIN,\n deletionProtection: dataStoreRemovalPolicy === AwsDataStoreRemovalPolicy.retain,\n timeToLiveAttribute: props.keyValueStoreConfig.ttlAttribute,\n // PITR on by default (35-day continuous backups / point-in-time restore); opt out per-table via config.\n pointInTimeRecoverySpecification: { pointInTimeRecoveryEnabled: !props.keyValueStoreConfig.disablePointInTimeRecovery },\n encryption: tableEncryption,\n encryptionKey,\n // Change data capture, only when a handler is configured — a stream on a table\n // nothing consumes is pure cost. NEW_AND_OLD_IMAGES so a handler can see what a\n // change actually did, including on a delete, where the new image does not exist.\n stream: props.keyValueStoreConfig.onStream ? aws_dynamodb.StreamViewType.NEW_AND_OLD_IMAGES : undefined,\n });\n\n qpqDeployAwsCdkUtils.applyEnvironmentTags(table, props.qpqConfig);\n\n // Default alarms (opt-in via defineNotifyError): read/write throttling means\n // the table is rejecting requests (hot partition / capacity), user-visible.\n createDefaultResourceAlarm(this, props.qpqConfig, {\n id: 'default-alarm-read-throttle',\n alarmName: this.resourceName(`${props.keyValueStoreConfig.keyValueStoreName}-read-throttle`),\n metric: table.metric('ReadThrottleEvents', { period: cdk.Duration.minutes(1), statistic: 'Sum' }),\n threshold: 1,\n evaluationPeriods: 1,\n });\n createDefaultResourceAlarm(this, props.qpqConfig, {\n id: 'default-alarm-write-throttle',\n alarmName: this.resourceName(`${props.keyValueStoreConfig.keyValueStoreName}-write-throttle`),\n metric: table.metric('WriteThrottleEvents', { period: cdk.Duration.minutes(1), statistic: 'Sum' }),\n threshold: 1,\n evaluationPeriods: 1,\n });\n\n // Do local secondary indexes\n for (let i = 1; i < props.keyValueStoreConfig.sortKeys.length; i++) {\n let sortKey = props.keyValueStoreConfig.sortKeys[i];\n\n table.addLocalSecondaryIndex({\n indexName: sortKey.key,\n sortKey: convertKvsKeyToDynamodbAttribute(sortKey),\n });\n }\n\n // Do global secondary indexes\n for (const index of props.keyValueStoreConfig.indexes) {\n table.addGlobalSecondaryIndex({\n indexName: index.partitionKey.key,\n partitionKey: convertKvsKeyToDynamodbAttribute(index.partitionKey),\n sortKey: index.sortKey && convertKvsKeyToDynamodbAttribute(index.sortKey),\n });\n }\n\n this.table = table;\n\n // Publish the stream ARN for the API stack's handler to pick up.\n //\n // A DynamoDB stream ARN ends in a creation timestamp, so unlike every other resource\n // here it CANNOT be recomputed from naming conventions — which is how the API stack\n // reaches every other table (fromOtherStack). Passing the table object across stacks\n // would work but creates a CloudFormation export, the coupling this codebase avoids\n // everywhere else. So it goes through SSM and is resolved at deploy time, the same\n // trick DomainCertificateLookup uses for cert ARNs.\n if (props.keyValueStoreConfig.onStream) {\n new aws_ssm.StringParameter(this, 'stream-arn', {\n parameterName: kvsStreamArnSsmParameterName(this.qpqResourceName(props.keyValueStoreConfig.keyValueStoreName, 'kvs')),\n stringValue: table.tableStreamArn!,\n });\n }\n\n // If we have an override, lets use it instead\n // we still want to keep the above table created.\n const tableNameOverride = qpqConfigAwsUtils.getDynamoTableNameOverrride(props.keyValueStoreConfig.keyValueStoreName, props.qpqConfig);\n if (tableNameOverride) {\n console.log('tableNameOverride', tableNameOverride);\n this.table = aws_dynamodb.Table.fromTableName(this, 'tablelink', tableNameOverride);\n }\n }\n\n public static authorizeActionsForRole(scope: Construct, role: aws_iam.IRole, qpqConfig: QPQConfig, ownedKvsList: QpqCoreKeyValueStoreConstruct[]) {\n // CDK-known ARNs for tables created in this stack (+ GSI ARNs).\n const ownedArns = ownedKvsList.flatMap((kvs) => [kvs.table.tableArn, `${kvs.table.tableArn}/index/*`]);\n\n // Deterministically-computed ARNs for KVSs declared in this service's\n // config but owned by another service. Uses the same naming path as\n // `getKvsDynamoTableNameFromConfig` so no CDK cross-stack ref is created.\n const allKvsConfigs = qpqCoreUtils.getAllKeyValueStores(qpqConfig);\n const ownedKvsConfigs = qpqCoreUtils.getOwnedKeyValueStores(qpqConfig);\n const foreignKvsConfigs = allKvsConfigs.filter((cfg) => !ownedKvsConfigs.includes(cfg));\n\n const foreignArns = foreignKvsConfigs.flatMap((cfg) => {\n const tableName = awsNamingUtils.getKvsDynamoTableNameFromConfig(cfg.keyValueStoreName, qpqConfig, 'kvs');\n const tableArn = `arn:aws:dynamodb:*:*:table/${tableName}`;\n return [tableArn, `${tableArn}/index/*`];\n });\n\n const resources = [...ownedArns, ...foreignArns];\n if (resources.length === 0) return;\n\n qpqDeployAwsCdkUtils.attachManagedResourcePolicies(\n scope,\n role,\n 'webserverKeyValueStoreAccess',\n ['dynamodb:GetItem', 'dynamodb:PutItem', 'dynamodb:BatchWriteItem', 'dynamodb:Query', 'dynamodb:Scan', 'dynamodb:UpdateItem', 'dynamodb:DeleteItem'],\n resources,\n );\n\n // Grant KMS permissions for any encrypted KVSs (owned or foreign) whose\n // customer-managed key is declared in this service's config.\n const kmsArns = [...ownedKvsConfigs, ...foreignKvsConfigs]\n .filter((cfg) => cfg.encryption)\n .map((cfg) => qpqConfigAwsUtils.getAwsKmsKeyForKeyValueStore(qpqConfig, cfg)?.arn)\n .filter((arn): arn is string => Boolean(arn));\n\n if (kmsArns.length > 0) {\n role.addToPrincipalPolicy(\n new aws_iam.PolicyStatement({\n effect: aws_iam.Effect.ALLOW,\n actions: ['kms:Decrypt', 'kms:GenerateDataKey*', 'kms:DescribeKey', 'kms:Encrypt', 'kms:ReEncrypt*'],\n resources: [...new Set(kmsArns)],\n }),\n );\n }\n }\n}\n"]}
|
package/lib/esm/constructs/feature/core/keyValueStore/QpqApiCoreKeyValueStoreStreamConstruct.js
CHANGED
|
@@ -32,9 +32,7 @@ export class QpqApiCoreKeyValueStoreStreamConstruct extends QpqConstructBlock {
|
|
|
32
32
|
if (!streamConfig) {
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
|
-
const isOwnedHere = qpqCoreUtils
|
|
36
|
-
.getOwnedKeyValueStores(props.qpqConfig)
|
|
37
|
-
.some((kvs) => kvs.uniqueKey === props.keyValueStoreConfig.uniqueKey);
|
|
35
|
+
const isOwnedHere = qpqCoreUtils.getOwnedKeyValueStores(props.qpqConfig).some((kvs) => kvs.uniqueKey === props.keyValueStoreConfig.uniqueKey);
|
|
38
36
|
if (!isOwnedHere) {
|
|
39
37
|
return;
|
|
40
38
|
}
|
|
@@ -50,7 +48,18 @@ export class QpqApiCoreKeyValueStoreStreamConstruct extends QpqConstructBlock {
|
|
|
50
48
|
});
|
|
51
49
|
const func = new Function(this, 'stream', {
|
|
52
50
|
reacreateOnFunctionNameChange: true,
|
|
53
|
-
|
|
51
|
+
// Two characters, and that is not a style choice - it is the whole budget.
|
|
52
|
+
//
|
|
53
|
+
// A Lambda name is capped at 64 characters, and this resource carries the longest
|
|
54
|
+
// prefix of anything qpq names: the store name already has `EventLog` appended before
|
|
55
|
+
// the app-service-environment-feature decoration and the `-qpq` marker are added. The
|
|
56
|
+
// longest today is `qpq-admin-sessionsEventLog`, which leaves exactly two characters
|
|
57
|
+
// for the type. `kvsStream` overran by four; even `str` overruns by one.
|
|
58
|
+
//
|
|
59
|
+
// So this sits at exactly 64 with no headroom: a longer store name, service, feature
|
|
60
|
+
// or actor name will fail synth again, and the fix at that point is a general
|
|
61
|
+
// shortening rule in Function.ts rather than more characters shaved off here.
|
|
62
|
+
functionName: this.qpqResourceName(props.keyValueStoreConfig.keyValueStoreName, 'ks'),
|
|
54
63
|
functionType: 'dynamoStreamEvent_kvsStreamEvent',
|
|
55
64
|
executorName: 'dynamoStreamEvent_kvsStreamEvent',
|
|
56
65
|
qpqConfig: props.qpqConfig,
|
package/lib/esm/constructs/feature/core/keyValueStore/QpqApiCoreKeyValueStoreStreamConstruct.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QpqApiCoreKeyValueStoreStreamConstruct.js","sourceRoot":"","sources":["../../../../../../src/constructs/feature/core/keyValueStore/QpqApiCoreKeyValueStoreStreamConstruct.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4C,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEzF,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,wBAAwB,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGpG,OAAO,EAAE,iBAAiB,EAA0B,MAAM,iCAAiC,CAAC;AAC5F,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,EAAE,4BAA4B,EAAE,MAAM,iCAAiC,CAAC;AAO/E,uFAAuF;AACvF,yFAAyF;AACzF,wFAAwF;AACxF,6DAA6D;AAC7D,EAAE;AACF,4FAA4F;AAC5F,6CAA6C;AAC7C,sEAAsE;AACtE,0FAA0F;AAC1F,4CAA4C;AAC5C,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAEhC,2FAA2F;AAC3F,qFAAqF;AACrF,yFAAyF;AACzF,+DAA+D;AAC/D,MAAM,yBAAyB,GAAG,GAAG,CAAC;AACtC,MAAM,sCAAsC,GAAG,CAAC,CAAC;AAEjD;;;;GAIG;AACH,MAAM,OAAO,sCAAuC,SAAQ,iBAAiB;IAC3E,YAAY,KAAgB,EAAE,EAAU,EAAE,KAAkD;QAC1F,KAAK,CAAC,KAAK,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC;QAExB,MAAM,YAAY,GAAG,KAAK,CAAC,mBAAmB,CAAC,QAAQ,CAAC;QACxD,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QAED,MAAM,WAAW,GAAG,YAAY
|
|
1
|
+
{"version":3,"file":"QpqApiCoreKeyValueStoreStreamConstruct.js","sourceRoot":"","sources":["../../../../../../src/constructs/feature/core/keyValueStore/QpqApiCoreKeyValueStoreStreamConstruct.ts"],"names":[],"mappings":"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","sourcesContent":["import { KeyValueStoreQPQConfigSetting, QPQConfig, qpqCoreUtils } from 'quidproquo-core';\n\nimport { aws_dynamodb, aws_lambda, aws_lambda_event_sources, aws_ssm, Duration } from 'aws-cdk-lib';\nimport { Construct } from 'constructs';\n\nimport { QpqConstructBlock, QpqConstructBlockProps } from '../../../base/QpqConstructBlock';\nimport { Function } from '../../../basic/Function';\nimport { kvsStreamArnSsmParameterName } from './QpqCoreKeyValueStoreConstruct';\n\nexport interface QpqApiCoreKeyValueStoreStreamConstructProps extends QpqConstructBlockProps {\n keyValueStoreConfig: KeyValueStoreQPQConfigSetting;\n apiLayerVersions?: aws_lambda.ILayerVersion[];\n}\n\n// A failing batch is retried, and by DEFAULT a DynamoDB stream keeps retrying the SAME\n// batch until it succeeds or the records expire (24 hours), which stalls the whole shard\n// behind one poison record. That is the single sharpest edge of using streams, so these\n// are set deliberately rather than left to the AWS defaults:\n//\n// - bisectBatchOnError narrows a failing batch down to the individual bad record instead of\n// condemning everything it travelled with.\n// - retryAttempts bounds how long a bad record can hold up the shard.\n// - onFailure sends what could not be processed somewhere visible, so a skipped change is\n// an item in a queue rather than silence.\nconst STREAM_RETRY_ATTEMPTS = 5;\n\n// Records per invocation, and how long to wait accumulating them. The window is what makes\n// coalescing worth having: a burst of writes to one item arrives as one batch, and a\n// projection collapses it to a single rebuild. It costs projection latency, which is the\n// trade an eventually-consistent read model is already making.\nconst DEFAULT_STREAM_BATCH_SIZE = 100;\nconst DEFAULT_STREAM_BATCHING_WINDOW_SECONDS = 5;\n\n/**\n * Wires a key-value store's stream to its handler. Created only for stores declaring\n * `onStream`, and only on the owning service's stack — attaching an event source to\n * another service's table would have two services processing the same records.\n */\nexport class QpqApiCoreKeyValueStoreStreamConstruct extends QpqConstructBlock {\n constructor(scope: Construct, id: string, props: QpqApiCoreKeyValueStoreStreamConstructProps) {\n super(scope, id, props);\n\n const streamConfig = props.keyValueStoreConfig.onStream;\n if (!streamConfig) {\n return;\n }\n\n const isOwnedHere = qpqCoreUtils.getOwnedKeyValueStores(props.qpqConfig).some((kvs) => kvs.uniqueKey === props.keyValueStoreConfig.uniqueKey);\n\n if (!isOwnedHere) {\n return;\n }\n\n // fromOtherStack imports by NAME, which carries no stream ARN, and a stream ARN ends\n // in a creation timestamp so it cannot be recomputed. The Inf stack publishes it to a\n // deterministically-named SSM parameter; this resolves at deploy time, so synth stays\n // independent of the other stack.\n const tableName = this.qpqResourceName(props.keyValueStoreConfig.keyValueStoreName, 'kvs');\n const tableStreamArn = aws_ssm.StringParameter.valueForStringParameter(this, kvsStreamArnSsmParameterName(tableName));\n\n const table = aws_dynamodb.Table.fromTableAttributes(this, 'stream-table', {\n tableName,\n tableStreamArn,\n });\n\n const func = new Function(this, 'stream', {\n reacreateOnFunctionNameChange: true,\n // Two characters, and that is not a style choice - it is the whole budget.\n //\n // A Lambda name is capped at 64 characters, and this resource carries the longest\n // prefix of anything qpq names: the store name already has `EventLog` appended before\n // the app-service-environment-feature decoration and the `-qpq` marker are added. The\n // longest today is `qpq-admin-sessionsEventLog`, which leaves exactly two characters\n // for the type. `kvsStream` overran by four; even `str` overruns by one.\n //\n // So this sits at exactly 64 with no headroom: a longer store name, service, feature\n // or actor name will fail synth again, and the fix at that point is a general\n // shortening rule in Function.ts rather than more characters shaved off here.\n functionName: this.qpqResourceName(props.keyValueStoreConfig.keyValueStoreName, 'ks'),\n\n functionType: 'dynamoStreamEvent_kvsStreamEvent',\n executorName: 'dynamoStreamEvent_kvsStreamEvent',\n\n qpqConfig: props.qpqConfig,\n\n apiLayerVersions: props.apiLayerVersions,\n\n environment: {\n // Never change: keyValueStoreName ~ hard coded to stop logs being recursive,\n // matching the storage drive event lambda's storageDriveName.\n keyValueStoreName: props.keyValueStoreConfig.keyValueStoreName,\n kvsStreamEntry: JSON.stringify(streamConfig.runtime),\n kvsStreamCoalesceByPartitionKey: String(!!streamConfig.coalesceByPartitionKey),\n kvsStreamPartitionKey: props.keyValueStoreConfig.partitionKey.key,\n },\n\n role: this.getServiceRole(),\n });\n\n func.lambdaFunction.addEventSource(\n new aws_lambda_event_sources.DynamoEventSource(table, {\n startingPosition: aws_lambda.StartingPosition.TRIM_HORIZON,\n\n batchSize: streamConfig.batchSize ?? DEFAULT_STREAM_BATCH_SIZE,\n maxBatchingWindow: Duration.seconds(streamConfig.maximumBatchingWindowInSeconds ?? DEFAULT_STREAM_BATCHING_WINDOW_SECONDS),\n\n // See the note above: without these a single bad record stalls the shard until the\n // records age out, and nothing records that it happened.\n bisectBatchOnError: true,\n retryAttempts: STREAM_RETRY_ATTEMPTS,\n reportBatchItemFailures: true,\n }),\n );\n }\n}\n"]}
|
|
@@ -152,7 +152,7 @@ export class QpqCoreKeyValueStoreConstruct extends QpqCoreKeyValueStoreConstruct
|
|
|
152
152
|
const resources = [...ownedArns, ...foreignArns];
|
|
153
153
|
if (resources.length === 0)
|
|
154
154
|
return;
|
|
155
|
-
qpqDeployAwsCdkUtils.attachManagedResourcePolicies(scope, role, 'webserverKeyValueStoreAccess', ['dynamodb:GetItem', 'dynamodb:PutItem', 'dynamodb:Query', 'dynamodb:Scan', 'dynamodb:UpdateItem', 'dynamodb:DeleteItem'], resources);
|
|
155
|
+
qpqDeployAwsCdkUtils.attachManagedResourcePolicies(scope, role, 'webserverKeyValueStoreAccess', ['dynamodb:GetItem', 'dynamodb:PutItem', 'dynamodb:BatchWriteItem', 'dynamodb:Query', 'dynamodb:Scan', 'dynamodb:UpdateItem', 'dynamodb:DeleteItem'], resources);
|
|
156
156
|
// Grant KMS permissions for any encrypted KVSs (owned or foreign) whose
|
|
157
157
|
// customer-managed key is declared in this service's config.
|
|
158
158
|
const kmsArns = [...ownedKvsConfigs, ...foreignKvsConfigs]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QpqCoreKeyValueStoreConstruct.js","sourceRoot":"","sources":["../../../../../../src/constructs/feature/core/keyValueStore/QpqCoreKeyValueStoreConstruct.ts"],"names":[],"mappings":"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","sourcesContent":["import { awsNamingUtils } from 'quidproquo-actionprocessor-awslambda';\nimport { AwsDataStoreRemovalPolicy, qpqConfigAwsUtils } from 'quidproquo-config-aws';\nimport { KeyValueStoreQPQConfigSetting, KvsKey, QPQConfig, qpqCoreUtils } from 'quidproquo-core';\n\nimport { aws_dynamodb, aws_iam, aws_kms, aws_ssm } from 'aws-cdk-lib';\nimport * as cdk from 'aws-cdk-lib';\nimport { Construct } from 'constructs';\n\nimport * as qpqDeployAwsCdkUtils from '../../../../utils/qpqDeployAwsCdkUtils';\nimport { createDefaultResourceAlarm } from '../../../base/createDefaultResourceAlarm';\nimport { QpqConstructBlock, QpqConstructBlockProps } from '../../../base/QpqConstructBlock';\n\nexport interface QpqCoreKeyValueStoreConstructProps extends QpqConstructBlockProps {\n keyValueStoreConfig: KeyValueStoreQPQConfigSetting;\n}\n\nexport abstract class QpqCoreKeyValueStoreConstructBase extends QpqConstructBlock {\n abstract table: aws_dynamodb.ITable;\n\n public grantRead(grantee: aws_iam.IGrantable) {\n this.table.grantReadData(grantee);\n }\n\n public grantWrite(grantee: aws_iam.IGrantable) {\n this.table.grantWriteData(grantee);\n }\n\n public grantAll(grantee: aws_iam.IGrantable) {\n this.table.grantFullAccess(grantee);\n }\n}\n\n// Deterministic SSM location for a store's stream ARN, derived from the table's own\n// resource name so both stacks compute it without talking to each other.\nexport const kvsStreamArnSsmParameterName = (tableResourceName: string): string => `/qpq/kvs-stream-arn/${tableResourceName}`;\n\nexport const convertKvsKeyTypeToDynamodbAttributeType = (kvsKeyType: KvsKey['type']): aws_dynamodb.AttributeType => {\n switch (kvsKeyType) {\n case 'string':\n return aws_dynamodb.AttributeType.STRING;\n case 'number':\n return aws_dynamodb.AttributeType.NUMBER;\n case 'binary':\n return aws_dynamodb.AttributeType.BINARY;\n }\n\n throw new Error(`Unknown KvsKeyType: ${kvsKeyType}`);\n};\n\nexport const convertKvsKeyToDynamodbAttribute = (kvsKey: KvsKey): aws_dynamodb.Attribute => ({\n name: kvsKey.key,\n type: convertKvsKeyTypeToDynamodbAttributeType(kvsKey.type),\n});\n\nexport class QpqCoreKeyValueStoreConstruct extends QpqCoreKeyValueStoreConstructBase {\n table: aws_dynamodb.ITable;\n\n static fromOtherStack(scope: Construct, id: string, qpqConfig: QPQConfig, keyValueStoreName: string): QpqCoreKeyValueStoreConstructBase {\n const tableNameOverride = qpqConfigAwsUtils.getDynamoTableNameOverrride(keyValueStoreName, qpqConfig);\n\n class Import extends QpqCoreKeyValueStoreConstructBase {\n table = aws_dynamodb.Table.fromTableName(this, 'table', tableNameOverride || this.qpqResourceName(keyValueStoreName, 'kvs'));\n }\n\n return new Import(scope, id, { qpqConfig });\n }\n\n constructor(scope: Construct, id: string, props: QpqCoreKeyValueStoreConstructProps) {\n super(scope, id, props);\n\n const dataStoreRemovalPolicy = qpqConfigAwsUtils.getAwsDataStoreRemovalPolicy(props.qpqConfig);\n\n const [primarySortKey] = props.keyValueStoreConfig.sortKeys;\n\n let tableEncryption: aws_dynamodb.TableEncryption | undefined;\n let encryptionKey: aws_kms.IKey | undefined;\n if (props.keyValueStoreConfig.encryption) {\n const kmsCfg = qpqConfigAwsUtils.getAwsKmsKeyForKeyValueStore(props.qpqConfig, props.keyValueStoreConfig);\n if (kmsCfg) {\n encryptionKey = aws_kms.Key.fromKeyArn(this, 'enc-key', kmsCfg.arn);\n tableEncryption = aws_dynamodb.TableEncryption.CUSTOMER_MANAGED;\n } else {\n tableEncryption = aws_dynamodb.TableEncryption.AWS_MANAGED;\n }\n }\n\n const table = new aws_dynamodb.Table(this, 'table', {\n tableName: this.qpqResourceName(props.keyValueStoreConfig.keyValueStoreName, 'kvs'),\n partitionKey: convertKvsKeyToDynamodbAttribute(props.keyValueStoreConfig.partitionKey),\n sortKey: primarySortKey && convertKvsKeyToDynamodbAttribute(primarySortKey),\n billingMode: aws_dynamodb.BillingMode.PAY_PER_REQUEST,\n // Retain data stores by default; dev configs opt into full teardown via defineAwsDataStoreRemovalPolicy(destroy).\n // When retained, deletionProtection also blocks deletes/replacements outright (RETAIN alone only orphans the table).\n removalPolicy: dataStoreRemovalPolicy === AwsDataStoreRemovalPolicy.destroy ? cdk.RemovalPolicy.DESTROY : cdk.RemovalPolicy.RETAIN,\n deletionProtection: dataStoreRemovalPolicy === AwsDataStoreRemovalPolicy.retain,\n timeToLiveAttribute: props.keyValueStoreConfig.ttlAttribute,\n // PITR on by default (35-day continuous backups / point-in-time restore); opt out per-table via config.\n pointInTimeRecoverySpecification: { pointInTimeRecoveryEnabled: !props.keyValueStoreConfig.disablePointInTimeRecovery },\n encryption: tableEncryption,\n encryptionKey,\n // Change data capture, only when a handler is configured — a stream on a table\n // nothing consumes is pure cost. NEW_AND_OLD_IMAGES so a handler can see what a\n // change actually did, including on a delete, where the new image does not exist.\n stream: props.keyValueStoreConfig.onStream ? aws_dynamodb.StreamViewType.NEW_AND_OLD_IMAGES : undefined,\n });\n\n qpqDeployAwsCdkUtils.applyEnvironmentTags(table, props.qpqConfig);\n\n // Default alarms (opt-in via defineNotifyError): read/write throttling means\n // the table is rejecting requests (hot partition / capacity), user-visible.\n createDefaultResourceAlarm(this, props.qpqConfig, {\n id: 'default-alarm-read-throttle',\n alarmName: this.resourceName(`${props.keyValueStoreConfig.keyValueStoreName}-read-throttle`),\n metric: table.metric('ReadThrottleEvents', { period: cdk.Duration.minutes(1), statistic: 'Sum' }),\n threshold: 1,\n evaluationPeriods: 1,\n });\n createDefaultResourceAlarm(this, props.qpqConfig, {\n id: 'default-alarm-write-throttle',\n alarmName: this.resourceName(`${props.keyValueStoreConfig.keyValueStoreName}-write-throttle`),\n metric: table.metric('WriteThrottleEvents', { period: cdk.Duration.minutes(1), statistic: 'Sum' }),\n threshold: 1,\n evaluationPeriods: 1,\n });\n\n // Do local secondary indexes\n for (let i = 1; i < props.keyValueStoreConfig.sortKeys.length; i++) {\n let sortKey = props.keyValueStoreConfig.sortKeys[i];\n\n table.addLocalSecondaryIndex({\n indexName: sortKey.key,\n sortKey: convertKvsKeyToDynamodbAttribute(sortKey),\n });\n }\n\n // Do global secondary indexes\n for (const index of props.keyValueStoreConfig.indexes) {\n table.addGlobalSecondaryIndex({\n indexName: index.partitionKey.key,\n partitionKey: convertKvsKeyToDynamodbAttribute(index.partitionKey),\n sortKey: index.sortKey && convertKvsKeyToDynamodbAttribute(index.sortKey),\n });\n }\n\n this.table = table;\n\n // Publish the stream ARN for the API stack's handler to pick up.\n //\n // A DynamoDB stream ARN ends in a creation timestamp, so unlike every other resource\n // here it CANNOT be recomputed from naming conventions — which is how the API stack\n // reaches every other table (fromOtherStack). Passing the table object across stacks\n // would work but creates a CloudFormation export, the coupling this codebase avoids\n // everywhere else. So it goes through SSM and is resolved at deploy time, the same\n // trick DomainCertificateLookup uses for cert ARNs.\n if (props.keyValueStoreConfig.onStream) {\n new aws_ssm.StringParameter(this, 'stream-arn', {\n parameterName: kvsStreamArnSsmParameterName(this.qpqResourceName(props.keyValueStoreConfig.keyValueStoreName, 'kvs')),\n stringValue: table.tableStreamArn!,\n });\n }\n\n // If we have an override, lets use it instead\n // we still want to keep the above table created.\n const tableNameOverride = qpqConfigAwsUtils.getDynamoTableNameOverrride(props.keyValueStoreConfig.keyValueStoreName, props.qpqConfig);\n if (tableNameOverride) {\n console.log('tableNameOverride', tableNameOverride);\n this.table = aws_dynamodb.Table.fromTableName(this, 'tablelink', tableNameOverride);\n }\n }\n\n public static authorizeActionsForRole(scope: Construct, role: aws_iam.IRole, qpqConfig: QPQConfig, ownedKvsList: QpqCoreKeyValueStoreConstruct[]) {\n // CDK-known ARNs for tables created in this stack (+ GSI ARNs).\n const ownedArns = ownedKvsList.flatMap((kvs) => [kvs.table.tableArn, `${kvs.table.tableArn}/index/*`]);\n\n // Deterministically-computed ARNs for KVSs declared in this service's\n // config but owned by another service. Uses the same naming path as\n // `getKvsDynamoTableNameFromConfig` so no CDK cross-stack ref is created.\n const allKvsConfigs = qpqCoreUtils.getAllKeyValueStores(qpqConfig);\n const ownedKvsConfigs = qpqCoreUtils.getOwnedKeyValueStores(qpqConfig);\n const foreignKvsConfigs = allKvsConfigs.filter((cfg) => !ownedKvsConfigs.includes(cfg));\n\n const foreignArns = foreignKvsConfigs.flatMap((cfg) => {\n const tableName = awsNamingUtils.getKvsDynamoTableNameFromConfig(cfg.keyValueStoreName, qpqConfig, 'kvs');\n const tableArn = `arn:aws:dynamodb:*:*:table/${tableName}`;\n return [tableArn, `${tableArn}/index/*`];\n });\n\n const resources = [...ownedArns, ...foreignArns];\n if (resources.length === 0) return;\n\n qpqDeployAwsCdkUtils.attachManagedResourcePolicies(\n scope,\n role,\n 'webserverKeyValueStoreAccess',\n ['dynamodb:GetItem', 'dynamodb:PutItem', 'dynamodb:Query', 'dynamodb:Scan', 'dynamodb:UpdateItem', 'dynamodb:DeleteItem'],\n resources,\n );\n\n // Grant KMS permissions for any encrypted KVSs (owned or foreign) whose\n // customer-managed key is declared in this service's config.\n const kmsArns = [...ownedKvsConfigs, ...foreignKvsConfigs]\n .filter((cfg) => cfg.encryption)\n .map((cfg) => qpqConfigAwsUtils.getAwsKmsKeyForKeyValueStore(qpqConfig, cfg)?.arn)\n .filter((arn): arn is string => Boolean(arn));\n\n if (kmsArns.length > 0) {\n role.addToPrincipalPolicy(\n new aws_iam.PolicyStatement({\n effect: aws_iam.Effect.ALLOW,\n actions: ['kms:Decrypt', 'kms:GenerateDataKey*', 'kms:DescribeKey', 'kms:Encrypt', 'kms:ReEncrypt*'],\n resources: [...new Set(kmsArns)],\n }),\n );\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"QpqCoreKeyValueStoreConstruct.js","sourceRoot":"","sources":["../../../../../../src/constructs/feature/core/keyValueStore/QpqCoreKeyValueStoreConstruct.ts"],"names":[],"mappings":"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","sourcesContent":["import { awsNamingUtils } from 'quidproquo-actionprocessor-awslambda';\nimport { AwsDataStoreRemovalPolicy, qpqConfigAwsUtils } from 'quidproquo-config-aws';\nimport { KeyValueStoreQPQConfigSetting, KvsKey, QPQConfig, qpqCoreUtils } from 'quidproquo-core';\n\nimport { aws_dynamodb, aws_iam, aws_kms, aws_ssm } from 'aws-cdk-lib';\nimport * as cdk from 'aws-cdk-lib';\nimport { Construct } from 'constructs';\n\nimport * as qpqDeployAwsCdkUtils from '../../../../utils/qpqDeployAwsCdkUtils';\nimport { createDefaultResourceAlarm } from '../../../base/createDefaultResourceAlarm';\nimport { QpqConstructBlock, QpqConstructBlockProps } from '../../../base/QpqConstructBlock';\n\nexport interface QpqCoreKeyValueStoreConstructProps extends QpqConstructBlockProps {\n keyValueStoreConfig: KeyValueStoreQPQConfigSetting;\n}\n\nexport abstract class QpqCoreKeyValueStoreConstructBase extends QpqConstructBlock {\n abstract table: aws_dynamodb.ITable;\n\n public grantRead(grantee: aws_iam.IGrantable) {\n this.table.grantReadData(grantee);\n }\n\n public grantWrite(grantee: aws_iam.IGrantable) {\n this.table.grantWriteData(grantee);\n }\n\n public grantAll(grantee: aws_iam.IGrantable) {\n this.table.grantFullAccess(grantee);\n }\n}\n\n// Deterministic SSM location for a store's stream ARN, derived from the table's own\n// resource name so both stacks compute it without talking to each other.\nexport const kvsStreamArnSsmParameterName = (tableResourceName: string): string => `/qpq/kvs-stream-arn/${tableResourceName}`;\n\nexport const convertKvsKeyTypeToDynamodbAttributeType = (kvsKeyType: KvsKey['type']): aws_dynamodb.AttributeType => {\n switch (kvsKeyType) {\n case 'string':\n return aws_dynamodb.AttributeType.STRING;\n case 'number':\n return aws_dynamodb.AttributeType.NUMBER;\n case 'binary':\n return aws_dynamodb.AttributeType.BINARY;\n }\n\n throw new Error(`Unknown KvsKeyType: ${kvsKeyType}`);\n};\n\nexport const convertKvsKeyToDynamodbAttribute = (kvsKey: KvsKey): aws_dynamodb.Attribute => ({\n name: kvsKey.key,\n type: convertKvsKeyTypeToDynamodbAttributeType(kvsKey.type),\n});\n\nexport class QpqCoreKeyValueStoreConstruct extends QpqCoreKeyValueStoreConstructBase {\n table: aws_dynamodb.ITable;\n\n static fromOtherStack(scope: Construct, id: string, qpqConfig: QPQConfig, keyValueStoreName: string): QpqCoreKeyValueStoreConstructBase {\n const tableNameOverride = qpqConfigAwsUtils.getDynamoTableNameOverrride(keyValueStoreName, qpqConfig);\n\n class Import extends QpqCoreKeyValueStoreConstructBase {\n table = aws_dynamodb.Table.fromTableName(this, 'table', tableNameOverride || this.qpqResourceName(keyValueStoreName, 'kvs'));\n }\n\n return new Import(scope, id, { qpqConfig });\n }\n\n constructor(scope: Construct, id: string, props: QpqCoreKeyValueStoreConstructProps) {\n super(scope, id, props);\n\n const dataStoreRemovalPolicy = qpqConfigAwsUtils.getAwsDataStoreRemovalPolicy(props.qpqConfig);\n\n const [primarySortKey] = props.keyValueStoreConfig.sortKeys;\n\n let tableEncryption: aws_dynamodb.TableEncryption | undefined;\n let encryptionKey: aws_kms.IKey | undefined;\n if (props.keyValueStoreConfig.encryption) {\n const kmsCfg = qpqConfigAwsUtils.getAwsKmsKeyForKeyValueStore(props.qpqConfig, props.keyValueStoreConfig);\n if (kmsCfg) {\n encryptionKey = aws_kms.Key.fromKeyArn(this, 'enc-key', kmsCfg.arn);\n tableEncryption = aws_dynamodb.TableEncryption.CUSTOMER_MANAGED;\n } else {\n tableEncryption = aws_dynamodb.TableEncryption.AWS_MANAGED;\n }\n }\n\n const table = new aws_dynamodb.Table(this, 'table', {\n tableName: this.qpqResourceName(props.keyValueStoreConfig.keyValueStoreName, 'kvs'),\n partitionKey: convertKvsKeyToDynamodbAttribute(props.keyValueStoreConfig.partitionKey),\n sortKey: primarySortKey && convertKvsKeyToDynamodbAttribute(primarySortKey),\n billingMode: aws_dynamodb.BillingMode.PAY_PER_REQUEST,\n // Retain data stores by default; dev configs opt into full teardown via defineAwsDataStoreRemovalPolicy(destroy).\n // When retained, deletionProtection also blocks deletes/replacements outright (RETAIN alone only orphans the table).\n removalPolicy: dataStoreRemovalPolicy === AwsDataStoreRemovalPolicy.destroy ? cdk.RemovalPolicy.DESTROY : cdk.RemovalPolicy.RETAIN,\n deletionProtection: dataStoreRemovalPolicy === AwsDataStoreRemovalPolicy.retain,\n timeToLiveAttribute: props.keyValueStoreConfig.ttlAttribute,\n // PITR on by default (35-day continuous backups / point-in-time restore); opt out per-table via config.\n pointInTimeRecoverySpecification: { pointInTimeRecoveryEnabled: !props.keyValueStoreConfig.disablePointInTimeRecovery },\n encryption: tableEncryption,\n encryptionKey,\n // Change data capture, only when a handler is configured — a stream on a table\n // nothing consumes is pure cost. NEW_AND_OLD_IMAGES so a handler can see what a\n // change actually did, including on a delete, where the new image does not exist.\n stream: props.keyValueStoreConfig.onStream ? aws_dynamodb.StreamViewType.NEW_AND_OLD_IMAGES : undefined,\n });\n\n qpqDeployAwsCdkUtils.applyEnvironmentTags(table, props.qpqConfig);\n\n // Default alarms (opt-in via defineNotifyError): read/write throttling means\n // the table is rejecting requests (hot partition / capacity), user-visible.\n createDefaultResourceAlarm(this, props.qpqConfig, {\n id: 'default-alarm-read-throttle',\n alarmName: this.resourceName(`${props.keyValueStoreConfig.keyValueStoreName}-read-throttle`),\n metric: table.metric('ReadThrottleEvents', { period: cdk.Duration.minutes(1), statistic: 'Sum' }),\n threshold: 1,\n evaluationPeriods: 1,\n });\n createDefaultResourceAlarm(this, props.qpqConfig, {\n id: 'default-alarm-write-throttle',\n alarmName: this.resourceName(`${props.keyValueStoreConfig.keyValueStoreName}-write-throttle`),\n metric: table.metric('WriteThrottleEvents', { period: cdk.Duration.minutes(1), statistic: 'Sum' }),\n threshold: 1,\n evaluationPeriods: 1,\n });\n\n // Do local secondary indexes\n for (let i = 1; i < props.keyValueStoreConfig.sortKeys.length; i++) {\n let sortKey = props.keyValueStoreConfig.sortKeys[i];\n\n table.addLocalSecondaryIndex({\n indexName: sortKey.key,\n sortKey: convertKvsKeyToDynamodbAttribute(sortKey),\n });\n }\n\n // Do global secondary indexes\n for (const index of props.keyValueStoreConfig.indexes) {\n table.addGlobalSecondaryIndex({\n indexName: index.partitionKey.key,\n partitionKey: convertKvsKeyToDynamodbAttribute(index.partitionKey),\n sortKey: index.sortKey && convertKvsKeyToDynamodbAttribute(index.sortKey),\n });\n }\n\n this.table = table;\n\n // Publish the stream ARN for the API stack's handler to pick up.\n //\n // A DynamoDB stream ARN ends in a creation timestamp, so unlike every other resource\n // here it CANNOT be recomputed from naming conventions — which is how the API stack\n // reaches every other table (fromOtherStack). Passing the table object across stacks\n // would work but creates a CloudFormation export, the coupling this codebase avoids\n // everywhere else. So it goes through SSM and is resolved at deploy time, the same\n // trick DomainCertificateLookup uses for cert ARNs.\n if (props.keyValueStoreConfig.onStream) {\n new aws_ssm.StringParameter(this, 'stream-arn', {\n parameterName: kvsStreamArnSsmParameterName(this.qpqResourceName(props.keyValueStoreConfig.keyValueStoreName, 'kvs')),\n stringValue: table.tableStreamArn!,\n });\n }\n\n // If we have an override, lets use it instead\n // we still want to keep the above table created.\n const tableNameOverride = qpqConfigAwsUtils.getDynamoTableNameOverrride(props.keyValueStoreConfig.keyValueStoreName, props.qpqConfig);\n if (tableNameOverride) {\n console.log('tableNameOverride', tableNameOverride);\n this.table = aws_dynamodb.Table.fromTableName(this, 'tablelink', tableNameOverride);\n }\n }\n\n public static authorizeActionsForRole(scope: Construct, role: aws_iam.IRole, qpqConfig: QPQConfig, ownedKvsList: QpqCoreKeyValueStoreConstruct[]) {\n // CDK-known ARNs for tables created in this stack (+ GSI ARNs).\n const ownedArns = ownedKvsList.flatMap((kvs) => [kvs.table.tableArn, `${kvs.table.tableArn}/index/*`]);\n\n // Deterministically-computed ARNs for KVSs declared in this service's\n // config but owned by another service. Uses the same naming path as\n // `getKvsDynamoTableNameFromConfig` so no CDK cross-stack ref is created.\n const allKvsConfigs = qpqCoreUtils.getAllKeyValueStores(qpqConfig);\n const ownedKvsConfigs = qpqCoreUtils.getOwnedKeyValueStores(qpqConfig);\n const foreignKvsConfigs = allKvsConfigs.filter((cfg) => !ownedKvsConfigs.includes(cfg));\n\n const foreignArns = foreignKvsConfigs.flatMap((cfg) => {\n const tableName = awsNamingUtils.getKvsDynamoTableNameFromConfig(cfg.keyValueStoreName, qpqConfig, 'kvs');\n const tableArn = `arn:aws:dynamodb:*:*:table/${tableName}`;\n return [tableArn, `${tableArn}/index/*`];\n });\n\n const resources = [...ownedArns, ...foreignArns];\n if (resources.length === 0) return;\n\n qpqDeployAwsCdkUtils.attachManagedResourcePolicies(\n scope,\n role,\n 'webserverKeyValueStoreAccess',\n ['dynamodb:GetItem', 'dynamodb:PutItem', 'dynamodb:BatchWriteItem', 'dynamodb:Query', 'dynamodb:Scan', 'dynamodb:UpdateItem', 'dynamodb:DeleteItem'],\n resources,\n );\n\n // Grant KMS permissions for any encrypted KVSs (owned or foreign) whose\n // customer-managed key is declared in this service's config.\n const kmsArns = [...ownedKvsConfigs, ...foreignKvsConfigs]\n .filter((cfg) => cfg.encryption)\n .map((cfg) => qpqConfigAwsUtils.getAwsKmsKeyForKeyValueStore(qpqConfig, cfg)?.arn)\n .filter((arn): arn is string => Boolean(arn));\n\n if (kmsArns.length > 0) {\n role.addToPrincipalPolicy(\n new aws_iam.PolicyStatement({\n effect: aws_iam.Effect.ALLOW,\n actions: ['kms:Decrypt', 'kms:GenerateDataKey*', 'kms:DescribeKey', 'kms:Encrypt', 'kms:ReEncrypt*'],\n resources: [...new Set(kmsArns)],\n }),\n );\n }\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quidproquo-deploy-awscdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./lib/commonjs/index.js",
|
|
6
6
|
"module": "./lib/esm/index.js",
|
|
@@ -38,11 +38,11 @@
|
|
|
38
38
|
"constructs": "^10.7.1",
|
|
39
39
|
"esbuild": "^0.25.8",
|
|
40
40
|
"liquidjs": "^10.7.0",
|
|
41
|
-
"quidproquo-actionprocessor-awslambda": "0.1.
|
|
42
|
-
"quidproquo-actionprocessor-node": "0.1.
|
|
43
|
-
"quidproquo-config-aws": "0.1.
|
|
44
|
-
"quidproquo-core": "0.1.
|
|
45
|
-
"quidproquo-webserver": "0.1.
|
|
41
|
+
"quidproquo-actionprocessor-awslambda": "0.1.15",
|
|
42
|
+
"quidproquo-actionprocessor-node": "0.1.15",
|
|
43
|
+
"quidproquo-config-aws": "0.1.15",
|
|
44
|
+
"quidproquo-core": "0.1.15",
|
|
45
|
+
"quidproquo-webserver": "0.1.15",
|
|
46
46
|
"upath": "^2.0.1"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@types/babel__traverse": "7.18.2",
|
|
51
51
|
"@types/node": "^22.13.13",
|
|
52
52
|
"@types/prettier": "2.6.0",
|
|
53
|
-
"quidproquo-tsconfig": "0.1.
|
|
53
|
+
"quidproquo-tsconfig": "0.1.15",
|
|
54
54
|
"typescript": "^5.8.2"
|
|
55
55
|
}
|
|
56
56
|
}
|
|
@@ -45,9 +45,7 @@ export class QpqApiCoreKeyValueStoreStreamConstruct extends QpqConstructBlock {
|
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
const isOwnedHere = qpqCoreUtils
|
|
49
|
-
.getOwnedKeyValueStores(props.qpqConfig)
|
|
50
|
-
.some((kvs) => kvs.uniqueKey === props.keyValueStoreConfig.uniqueKey);
|
|
48
|
+
const isOwnedHere = qpqCoreUtils.getOwnedKeyValueStores(props.qpqConfig).some((kvs) => kvs.uniqueKey === props.keyValueStoreConfig.uniqueKey);
|
|
51
49
|
|
|
52
50
|
if (!isOwnedHere) {
|
|
53
51
|
return;
|
|
@@ -67,7 +65,18 @@ export class QpqApiCoreKeyValueStoreStreamConstruct extends QpqConstructBlock {
|
|
|
67
65
|
|
|
68
66
|
const func = new Function(this, 'stream', {
|
|
69
67
|
reacreateOnFunctionNameChange: true,
|
|
70
|
-
|
|
68
|
+
// Two characters, and that is not a style choice - it is the whole budget.
|
|
69
|
+
//
|
|
70
|
+
// A Lambda name is capped at 64 characters, and this resource carries the longest
|
|
71
|
+
// prefix of anything qpq names: the store name already has `EventLog` appended before
|
|
72
|
+
// the app-service-environment-feature decoration and the `-qpq` marker are added. The
|
|
73
|
+
// longest today is `qpq-admin-sessionsEventLog`, which leaves exactly two characters
|
|
74
|
+
// for the type. `kvsStream` overran by four; even `str` overruns by one.
|
|
75
|
+
//
|
|
76
|
+
// So this sits at exactly 64 with no headroom: a longer store name, service, feature
|
|
77
|
+
// or actor name will fail synth again, and the fix at that point is a general
|
|
78
|
+
// shortening rule in Function.ts rather than more characters shaved off here.
|
|
79
|
+
functionName: this.qpqResourceName(props.keyValueStoreConfig.keyValueStoreName, 'ks'),
|
|
71
80
|
|
|
72
81
|
functionType: 'dynamoStreamEvent_kvsStreamEvent',
|
|
73
82
|
executorName: 'dynamoStreamEvent_kvsStreamEvent',
|
|
@@ -192,7 +192,7 @@ export class QpqCoreKeyValueStoreConstruct extends QpqCoreKeyValueStoreConstruct
|
|
|
192
192
|
scope,
|
|
193
193
|
role,
|
|
194
194
|
'webserverKeyValueStoreAccess',
|
|
195
|
-
['dynamodb:GetItem', 'dynamodb:PutItem', 'dynamodb:Query', 'dynamodb:Scan', 'dynamodb:UpdateItem', 'dynamodb:DeleteItem'],
|
|
195
|
+
['dynamodb:GetItem', 'dynamodb:PutItem', 'dynamodb:BatchWriteItem', 'dynamodb:Query', 'dynamodb:Scan', 'dynamodb:UpdateItem', 'dynamodb:DeleteItem'],
|
|
196
196
|
resources,
|
|
197
197
|
);
|
|
198
198
|
|