@kensio/yulin 1.21.31 → 1.21.33
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/dist/serve/message/sim-message-log-console.js +3 -3
- package/dist/service/apigateway/api/deployment/sim-rest-api-deployment.js +3 -3
- package/dist/service/apigateway/api/method/sim-rest-api-method.js +12 -12
- package/dist/service/apigateway/api/resource/sim-rest-api-resource.js +11 -9
- package/dist/service/apigateway/api/sim-rest-api-view.js +3 -3
- package/dist/service/apigateway/api/stage/sim-rest-api-stage.js +8 -10
- package/dist/service/apigatewayv2/api/integration/sim-http-api-integration.js +3 -3
- package/dist/service/apigatewayv2/api/route/sim-http-api-route-view.js +6 -6
- package/dist/service/apigatewayv2/api/sim-http-api-view.js +3 -3
- package/dist/service/apigatewayv2/api/stage/settings/sim-http-api-stage-route-settings.js +8 -7
- package/dist/service/apigatewayv2/api/stage/sim-http-api-stage.js +9 -9
- package/dist/service/athena/engine/sim-athena-partitioned-objects.js +7 -7
- package/dist/service/athena/sim-athena-engine.fixture.js +3 -0
- package/dist/service/athena/table/sim-athena-partition-filters.js +6 -5
- package/dist/service/backup/command/sim-backup-authorizer.js +5 -4
- package/dist/service/bedrock/response/sim-bedrock-declared-content.js +5 -10
- package/dist/service/cloudformation/stack/update/sim-cfn-stack-resource-changes.js +19 -10
- package/dist/service/cloudfront/cfn/response-headers-policy/sim-cfn-cf-rh-policy-security-values.js +2 -6
- package/dist/service/cognito/user-pool/trigger/sim-cognito-pre-sign-up-response.js +4 -7
- package/dist/service/dynamodb/command/transact/sim-dynamodb-transact-write.js +34 -25
- package/dist/service/dynamodb/secondary-index/sim-dynamodb-index-limits.js +5 -4
- package/dist/service/dynamodb/stream/sim-dynamodb-stream-shard-iterator.js +1 -3
- package/dist/service/dynamodb/time-to-live/sim-dynamodb-time-to-live.js +4 -3
- package/dist/service/glue/expression/sim-glue-partition-comparisons.js +1 -1
- package/dist/service/iam/authorize/context/sim-iam-auth-z-allow-requirement.js +6 -6
- package/dist/service/iam/authorize/match/condition/null/sim-iam-null.js +4 -1
- package/dist/service/kinesis/stream/sim-kinesis-shard-iterator.js +1 -3
- package/dist/service/lambda/command/create-function/create-function.handler.js +1 -0
- package/dist/service/lambda/destination/sim-lambda-destination-record.js +7 -7
- package/dist/service/lambda/function/environment/sim-lambda-environment-conflict.d.ts +22 -0
- package/dist/service/lambda/function/environment/sim-lambda-environment-conflict.js +33 -0
- package/dist/service/lambda/function/environment/sim-lambda-environment-conflicts.d.ts +53 -11
- package/dist/service/lambda/function/environment/sim-lambda-environment-conflicts.js +78 -24
- package/dist/service/lambda/function/policy/sim-lambda-permission.js +15 -14
- package/dist/service/lambda/function/sim-lambda-function.js +1 -0
- package/dist/service/lambda/function/sim-lambda-function.type.d.ts +8 -0
- package/dist/service/lambda/metric/sim-lambda-function-metrics.js +11 -9
- package/dist/service/lambda/sim-lambda-commands.js +3 -1
- package/dist/service/logs/metric/sim-logs-metric-filter.js +1 -8
- package/dist/service/route53/dns/answer/sim-route53-dns-zone-finder.js +5 -4
- package/dist/service/route53/resolve/sim-r53-zone-record-finder.js +8 -6
- package/dist/service/s3/bucket/notification/sim-s3-notification-filter.js +4 -7
- package/dist/service/s3/bucket/sim-s3-bucket.d.ts +8 -0
- package/dist/service/s3/bucket/sim-s3-bucket.js +11 -0
- package/dist/service/s3/bucket/sim-s3-key-writes.d.ts +16 -0
- package/dist/service/s3/bucket/sim-s3-key-writes.js +43 -0
- package/dist/service/s3/bucket/versioning/sim-s3-bucket-versioning.js +1 -3
- package/dist/service/s3/command/complete-multipart-upload/complete-multipart-upload.command.d.ts +2 -1
- package/dist/service/s3/command/complete-multipart-upload/complete-multipart-upload.handler.js +5 -1
- package/dist/service/s3/command/copy-object/copy-object-precondition.d.ts +23 -0
- package/dist/service/s3/command/copy-object/copy-object-precondition.js +34 -0
- package/dist/service/s3/command/copy-object/copy-object.command.d.ts +3 -1
- package/dist/service/s3/command/copy-object/copy-object.handler.d.ts +4 -0
- package/dist/service/s3/command/copy-object/copy-object.handler.js +11 -1
- package/dist/service/s3/command/object/sim-s3-write-precondition.d.ts +34 -0
- package/dist/service/s3/command/object/sim-s3-write-precondition.js +47 -0
- package/dist/service/s3/command/put-object/put-object.command.d.ts +2 -1
- package/dist/service/s3/command/put-object/put-object.handler.js +5 -1
- package/dist/service/s3/error/sim-s3.error.d.ts +10 -0
- package/dist/service/s3/error/sim-s3.error.js +12 -0
- package/dist/service/s3/object/s3-object-listing.js +4 -3
- package/dist/service/s3/serve/api/sim-s3-api-input.d.ts +4 -0
- package/dist/service/s3/serve/api/sim-s3-api-input.js +10 -0
- package/dist/service/s3/serve/api/sim-s3-api-multipart-input.js +2 -0
- package/dist/service/s3/serve/api/sim-s3-api-object-input.d.ts +6 -0
- package/dist/service/s3/serve/api/sim-s3-api-object-input.js +34 -2
- package/dist/service/s3/storage/filesystem/s3-filesystem-object-keys.js +9 -8
- package/dist/service/secretsmanager/cfn/secret/sim-cfn-secrets-manager-secret-template.test-support.js +15 -23
- package/dist/service/sqs/message/sim-sqs-message.js +3 -3
- package/dist/service/stepfunctions/machine/sim-state-machine.js +4 -3
- package/dist/util/process/sim-process-environment-reads.d.ts +52 -0
- package/dist/util/process/sim-process-environment-reads.js +94 -0
- package/dist/util/process/sim-process-environment.d.ts +32 -9
- package/dist/util/process/sim-process-environment.js +43 -17
- package/dist/util/schedule/cron/sim-cron-field-bounds.js +1 -1
- package/docs/services/lambda/README.md +6 -0
- package/docs/services/s3/README.md +84 -3
- package/package.json +3 -3
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The advice that follows each warning, given the name that was read early.
|
|
3
|
+
*/
|
|
4
|
+
function moveTheReadAdvice(name) {
|
|
5
|
+
return (`Code read process.env.${name} outside any invocation, as a read at ` +
|
|
6
|
+
"module scope does when the module is imported, and saw the host value " +
|
|
7
|
+
"instead. Move the read inside the handler function.");
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The conflict of a function declaring a name the host process also sets,
|
|
11
|
+
* with a different value.
|
|
12
|
+
*/
|
|
13
|
+
export function simLambdaHostConflict(environment, name) {
|
|
14
|
+
return {
|
|
15
|
+
key: `host:${name}`,
|
|
16
|
+
name,
|
|
17
|
+
message: `Sim Lambda function "${environment.functionName}" declares ` +
|
|
18
|
+
`environment variable ${name}, which the host process also sets ` +
|
|
19
|
+
`with a different value. ${moveTheReadAdvice(name)}`,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The conflict of two functions declaring different values for one name.
|
|
24
|
+
*/
|
|
25
|
+
export function simLambdaFunctionConflict(existing, environment, name) {
|
|
26
|
+
return {
|
|
27
|
+
key: `function:${name}`,
|
|
28
|
+
name,
|
|
29
|
+
message: `Sim Lambda functions "${existing.functionName}" and ` +
|
|
30
|
+
`"${environment.functionName}" declare different values for ` +
|
|
31
|
+
`environment variable ${name}. ${moveTheReadAdvice(name)}`,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -1,15 +1,32 @@
|
|
|
1
1
|
import type { SimLambdaEnvironment } from "./sim-lambda-environment.js";
|
|
2
|
+
interface SimLambdaEnvironmentConflictsProperties {
|
|
3
|
+
/**
|
|
4
|
+
* The environments of the functions the simulated Lambda holds when it is
|
|
5
|
+
* asked, which a held name is checked against again once it is read. A
|
|
6
|
+
* check built standalone has none.
|
|
7
|
+
*/
|
|
8
|
+
readonly currentEnvironments?: () => Iterable<SimLambdaEnvironment>;
|
|
9
|
+
}
|
|
2
10
|
/**
|
|
3
11
|
* Warns about declared environment variables whose value a handler module
|
|
4
|
-
*
|
|
12
|
+
* did not actually see.
|
|
5
13
|
*
|
|
6
14
|
* A sim Lambda function backed by a real in-process handler only gets its own
|
|
7
15
|
* process.env while the handler runs, so a variable read at module scope is
|
|
8
16
|
* read too early (see SimLambdaProcessEnv). That only changes what the code
|
|
9
|
-
* sees when the value read at module scope differs from the declared one
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
17
|
+
* sees when the value read at module scope differs from the declared one.
|
|
18
|
+
* There are two ways for it to differ. The host process sets the same name
|
|
19
|
+
* with a different value, or another simulated function declares the same
|
|
20
|
+
* name with a different value.
|
|
21
|
+
*
|
|
22
|
+
* A difference is reported only once code has read the name from process.env
|
|
23
|
+
* outside any invocation, which SimProcessEnvironment records. The name of a
|
|
24
|
+
* difference with no such read yet is held. If the read turns up before a
|
|
25
|
+
* later invocation (a handler module imported after the function was
|
|
26
|
+
* created), the name is checked again against the functions that exist then,
|
|
27
|
+
* so a function deleted or reconfigured in between leaves nothing stale to
|
|
28
|
+
* report. A read made before Yulin was imported is not on record, and leaves
|
|
29
|
+
* the difference unreported.
|
|
13
30
|
*
|
|
14
31
|
* Staying quiet when the values agree matters as much as warning when they do
|
|
15
32
|
* not. Test suites commonly export the same variables they configure their
|
|
@@ -18,30 +35,55 @@ import type { SimLambdaEnvironment } from "./sim-lambda-environment.js";
|
|
|
18
35
|
*/
|
|
19
36
|
export declare class SimLambdaEnvironmentConflicts {
|
|
20
37
|
/**
|
|
21
|
-
*
|
|
38
|
+
* The environments of the functions that exist now.
|
|
39
|
+
*/
|
|
40
|
+
private readonly currentEnvironments;
|
|
41
|
+
/**
|
|
42
|
+
* Conflict keys already reported, so repeated function creation does not
|
|
22
43
|
* repeat the same warning.
|
|
23
44
|
*/
|
|
24
45
|
private readonly warned;
|
|
46
|
+
/**
|
|
47
|
+
* Names in conflict that had not been read outside an invocation when the
|
|
48
|
+
* conflict was found.
|
|
49
|
+
*/
|
|
50
|
+
private readonly held;
|
|
51
|
+
constructor(properties?: SimLambdaEnvironmentConflictsProperties);
|
|
25
52
|
/**
|
|
26
53
|
* Check a new function's environment against the host process and against
|
|
27
54
|
* the functions that already exist.
|
|
28
55
|
*/
|
|
29
56
|
check(environment: SimLambdaEnvironment, existingEnvironments: Iterable<SimLambdaEnvironment>): void;
|
|
30
57
|
/**
|
|
31
|
-
*
|
|
58
|
+
* Check the held names that have since been read outside an invocation
|
|
59
|
+
* again, against the functions that exist now, and report what conflicts.
|
|
60
|
+
*
|
|
61
|
+
* Called as a function is invoked, before the handler runs.
|
|
32
62
|
*/
|
|
33
|
-
|
|
63
|
+
reportReads(): void;
|
|
64
|
+
/**
|
|
65
|
+
* Check one name across the given environments, each against the host
|
|
66
|
+
* process and against the environments before it.
|
|
67
|
+
*/
|
|
68
|
+
private checkName;
|
|
34
69
|
/**
|
|
35
|
-
*
|
|
70
|
+
* Find a conflict when the host process sets the same name with a
|
|
36
71
|
* different value.
|
|
37
72
|
*/
|
|
73
|
+
private checkHostProcess;
|
|
74
|
+
/**
|
|
75
|
+
* Find a conflict when another simulated function declares the same name
|
|
76
|
+
* with a different value.
|
|
77
|
+
*/
|
|
38
78
|
private checkOtherFunctions;
|
|
39
79
|
/**
|
|
40
|
-
*
|
|
80
|
+
* Warn about a conflict once per key if its name has been read outside an
|
|
81
|
+
* invocation, and hold the name otherwise.
|
|
41
82
|
*
|
|
42
83
|
* Warnings go to the console because that is where a test runner surfaces
|
|
43
84
|
* them next to the failing expectation they explain; the simulator has no
|
|
44
85
|
* logger of its own to route them through.
|
|
45
86
|
*/
|
|
46
|
-
private
|
|
87
|
+
private report;
|
|
47
88
|
}
|
|
89
|
+
export {};
|
|
@@ -1,18 +1,25 @@
|
|
|
1
|
+
import { simLambdaFunctionConflict, simLambdaHostConflict, } from "./sim-lambda-environment-conflict.js";
|
|
1
2
|
import { simProcessEnvironment } from "../../../../util/process/sim-process-environment.js";
|
|
2
|
-
const moveTheReadAdvice = "A read of process.env at module scope happens when the module is " +
|
|
3
|
-
"imported, before any invocation, so it sees the host value instead. " +
|
|
4
|
-
"Move the read inside the handler function.";
|
|
5
3
|
/**
|
|
6
4
|
* Warns about declared environment variables whose value a handler module
|
|
7
|
-
*
|
|
5
|
+
* did not actually see.
|
|
8
6
|
*
|
|
9
7
|
* A sim Lambda function backed by a real in-process handler only gets its own
|
|
10
8
|
* process.env while the handler runs, so a variable read at module scope is
|
|
11
9
|
* read too early (see SimLambdaProcessEnv). That only changes what the code
|
|
12
|
-
* sees when the value read at module scope differs from the declared one
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
10
|
+
* sees when the value read at module scope differs from the declared one.
|
|
11
|
+
* There are two ways for it to differ. The host process sets the same name
|
|
12
|
+
* with a different value, or another simulated function declares the same
|
|
13
|
+
* name with a different value.
|
|
14
|
+
*
|
|
15
|
+
* A difference is reported only once code has read the name from process.env
|
|
16
|
+
* outside any invocation, which SimProcessEnvironment records. The name of a
|
|
17
|
+
* difference with no such read yet is held. If the read turns up before a
|
|
18
|
+
* later invocation (a handler module imported after the function was
|
|
19
|
+
* created), the name is checked again against the functions that exist then,
|
|
20
|
+
* so a function deleted or reconfigured in between leaves nothing stale to
|
|
21
|
+
* report. A read made before Yulin was imported is not on record, and leaves
|
|
22
|
+
* the difference unreported.
|
|
16
23
|
*
|
|
17
24
|
* Staying quiet when the values agree matters as much as warning when they do
|
|
18
25
|
* not. Test suites commonly export the same variables they configure their
|
|
@@ -21,22 +28,68 @@ const moveTheReadAdvice = "A read of process.env at module scope happens when th
|
|
|
21
28
|
*/
|
|
22
29
|
export class SimLambdaEnvironmentConflicts {
|
|
23
30
|
/**
|
|
24
|
-
*
|
|
31
|
+
* The environments of the functions that exist now.
|
|
32
|
+
*/
|
|
33
|
+
currentEnvironments;
|
|
34
|
+
/**
|
|
35
|
+
* Conflict keys already reported, so repeated function creation does not
|
|
25
36
|
* repeat the same warning.
|
|
26
37
|
*/
|
|
27
38
|
warned = new Set();
|
|
39
|
+
/**
|
|
40
|
+
* Names in conflict that had not been read outside an invocation when the
|
|
41
|
+
* conflict was found.
|
|
42
|
+
*/
|
|
43
|
+
held = new Set();
|
|
44
|
+
constructor(properties = {}) {
|
|
45
|
+
this.currentEnvironments =
|
|
46
|
+
properties.currentEnvironments ?? (() => []);
|
|
47
|
+
}
|
|
28
48
|
/**
|
|
29
49
|
* Check a new function's environment against the host process and against
|
|
30
50
|
* the functions that already exist.
|
|
31
51
|
*/
|
|
32
52
|
check(environment, existingEnvironments) {
|
|
53
|
+
const existing = [...existingEnvironments];
|
|
33
54
|
for (const [name, value] of environment.declaredVariables) {
|
|
34
55
|
this.checkHostProcess(environment, name, value);
|
|
35
|
-
this.checkOtherFunctions(environment, name, value,
|
|
56
|
+
this.checkOtherFunctions(environment, name, value, existing);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Check the held names that have since been read outside an invocation
|
|
61
|
+
* again, against the functions that exist now, and report what conflicts.
|
|
62
|
+
*
|
|
63
|
+
* Called as a function is invoked, before the handler runs.
|
|
64
|
+
*/
|
|
65
|
+
reportReads() {
|
|
66
|
+
const read = [...this.held].filter((name) => simProcessEnvironment.wasReadOutsideRun(name));
|
|
67
|
+
if (read.length === 0) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const environments = [...this.currentEnvironments()];
|
|
71
|
+
for (const name of read) {
|
|
72
|
+
this.held.delete(name);
|
|
73
|
+
this.checkName(name, environments);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Check one name across the given environments, each against the host
|
|
78
|
+
* process and against the environments before it.
|
|
79
|
+
*/
|
|
80
|
+
checkName(name, environments) {
|
|
81
|
+
for (const [index, environment] of environments.entries()) {
|
|
82
|
+
const value = environment.declaredVariables.get(name);
|
|
83
|
+
if (value === undefined) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
this.checkHostProcess(environment, name, value);
|
|
87
|
+
this.checkOtherFunctions(environment, name, value, environments.slice(0, index));
|
|
36
88
|
}
|
|
37
89
|
}
|
|
38
90
|
/**
|
|
39
|
-
*
|
|
91
|
+
* Find a conflict when the host process sets the same name with a
|
|
92
|
+
* different value.
|
|
40
93
|
*/
|
|
41
94
|
checkHostProcess(environment, name, value) {
|
|
42
95
|
// Read-only lookup of a name the function itself declared.
|
|
@@ -45,13 +98,11 @@ export class SimLambdaEnvironmentConflicts {
|
|
|
45
98
|
if (hostValue === undefined || hostValue === value) {
|
|
46
99
|
return;
|
|
47
100
|
}
|
|
48
|
-
this.
|
|
49
|
-
`environment variable ${name}, which the host process also sets ` +
|
|
50
|
-
`with a different value. ${moveTheReadAdvice}`);
|
|
101
|
+
this.report(simLambdaHostConflict(environment, name));
|
|
51
102
|
}
|
|
52
103
|
/**
|
|
53
|
-
*
|
|
54
|
-
* different value.
|
|
104
|
+
* Find a conflict when another simulated function declares the same name
|
|
105
|
+
* with a different value.
|
|
55
106
|
*/
|
|
56
107
|
checkOtherFunctions(environment, name, value, existingEnvironments) {
|
|
57
108
|
for (const existing of existingEnvironments) {
|
|
@@ -59,25 +110,28 @@ export class SimLambdaEnvironmentConflicts {
|
|
|
59
110
|
if (existingValue === undefined || existingValue === value) {
|
|
60
111
|
continue;
|
|
61
112
|
}
|
|
62
|
-
this.
|
|
63
|
-
`"${environment.functionName}" declare different values for ` +
|
|
64
|
-
`environment variable ${name}. ${moveTheReadAdvice}`);
|
|
113
|
+
this.report(simLambdaFunctionConflict(existing, environment, name));
|
|
65
114
|
return;
|
|
66
115
|
}
|
|
67
116
|
}
|
|
68
117
|
/**
|
|
69
|
-
*
|
|
118
|
+
* Warn about a conflict once per key if its name has been read outside an
|
|
119
|
+
* invocation, and hold the name otherwise.
|
|
70
120
|
*
|
|
71
121
|
* Warnings go to the console because that is where a test runner surfaces
|
|
72
122
|
* them next to the failing expectation they explain; the simulator has no
|
|
73
123
|
* logger of its own to route them through.
|
|
74
124
|
*/
|
|
75
|
-
|
|
76
|
-
if (this.warned.has(key)) {
|
|
125
|
+
report(conflict) {
|
|
126
|
+
if (this.warned.has(conflict.key)) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (!simProcessEnvironment.wasReadOutsideRun(conflict.name)) {
|
|
130
|
+
this.held.add(conflict.name);
|
|
77
131
|
return;
|
|
78
132
|
}
|
|
79
|
-
this.warned.add(key);
|
|
133
|
+
this.warned.add(conflict.key);
|
|
80
134
|
// oxlint-disable-next-line no-console
|
|
81
|
-
console.warn(message);
|
|
135
|
+
console.warn(conflict.message);
|
|
82
136
|
}
|
|
83
137
|
}
|
|
@@ -82,20 +82,21 @@ export class SimLambdaPermission {
|
|
|
82
82
|
*/
|
|
83
83
|
condition() {
|
|
84
84
|
const stringEquals = this.stringEqualsConditions();
|
|
85
|
-
const condition = {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
85
|
+
const condition = {
|
|
86
|
+
...(Object.keys(stringEquals).length > 0 && {
|
|
87
|
+
StringEquals: stringEquals,
|
|
88
|
+
}),
|
|
89
|
+
...(this.sourceArn !== undefined && {
|
|
90
|
+
ArnLike: {
|
|
91
|
+
[simLambdaSourceArnConditionKey]: this.sourceArn,
|
|
92
|
+
},
|
|
93
|
+
}),
|
|
94
|
+
...(this.invokedViaFunctionUrl !== undefined && {
|
|
95
|
+
Bool: {
|
|
96
|
+
[simLambdaInvokedViaFunctionUrlConditionKey]: this.invokedViaFunctionUrl,
|
|
97
|
+
},
|
|
98
|
+
}),
|
|
99
|
+
};
|
|
99
100
|
return Object.keys(condition).length === 0 ? undefined : condition;
|
|
100
101
|
}
|
|
101
102
|
stringEqualsConditions() {
|
|
@@ -194,6 +194,7 @@ export class SimLambdaFunction {
|
|
|
194
194
|
logStreamName: this.logging.logStreamName(),
|
|
195
195
|
});
|
|
196
196
|
this.logging.recordFrom(this.#code);
|
|
197
|
+
this.properties.environmentConflicts?.reportReads();
|
|
197
198
|
return await this.metrics.around(String(this.name), async () => simAwsRunAsContext.run(this.runAsOwner, { kind: "arn", arn: this.roleArn }, async () => await this.environment.runWith(async () => await this.runInHostScope(async () => await this.logging.around(async () => await invocation.run(this.#code.handlerFunction(), event))))));
|
|
198
199
|
}
|
|
199
200
|
/**
|
|
@@ -8,6 +8,7 @@ import type { SimLambdaOutput } from "./logging/sim-lambda-output.js";
|
|
|
8
8
|
import type { SimLogsServiceWriter } from "../../logs/write/sim-logs-service-writer.js";
|
|
9
9
|
import type { SimLambdaExecutableCode } from "./code/sim-lambda-executable-code.js";
|
|
10
10
|
import type { SimLambdaEnvironment } from "./environment/sim-lambda-environment.js";
|
|
11
|
+
import type { SimLambdaEnvironmentConflicts } from "./environment/sim-lambda-environment-conflicts.js";
|
|
11
12
|
import type { SimLambdaOutboundHttp } from "./outbound/sim-lambda-outbound-http.js";
|
|
12
13
|
import type { SimLambdaFunctionState } from "./sim-lambda-function-configuration.js";
|
|
13
14
|
import type { SimLambdaHandler } from "./sim-lambda-handler.type.js";
|
|
@@ -27,6 +28,13 @@ export interface SimLambdaFunctionProperties {
|
|
|
27
28
|
timeoutSeconds?: number | undefined;
|
|
28
29
|
memorySizeMb?: number | undefined;
|
|
29
30
|
environment?: SimLambdaEnvironment | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* The check that warns about declared variables read too early, shared with
|
|
33
|
+
* every other function of the same simulated Lambda. It reports, as this
|
|
34
|
+
* function is invoked, a difference found earlier whose name has since been
|
|
35
|
+
* read outside an invocation. A function built standalone has none.
|
|
36
|
+
*/
|
|
37
|
+
environmentConflicts?: SimLambdaEnvironmentConflicts | undefined;
|
|
30
38
|
/**
|
|
31
39
|
* The queue or topic this function's abandoned asynchronous events are sent
|
|
32
40
|
* to, which is what `DeadLetterConfig.TargetArn` names.
|
|
@@ -69,15 +69,17 @@ function invocationData(outcome) {
|
|
|
69
69
|
value: outcome.durationMilliseconds,
|
|
70
70
|
unit: "Milliseconds",
|
|
71
71
|
},
|
|
72
|
+
...(outcome.failed
|
|
73
|
+
? [
|
|
74
|
+
{
|
|
75
|
+
namespace: simLambdaMetricNamespace,
|
|
76
|
+
metricName: "Errors",
|
|
77
|
+
dimensions,
|
|
78
|
+
value: 1,
|
|
79
|
+
unit: "Count",
|
|
80
|
+
},
|
|
81
|
+
]
|
|
82
|
+
: []),
|
|
72
83
|
];
|
|
73
|
-
if (outcome.failed) {
|
|
74
|
-
data.push({
|
|
75
|
-
namespace: simLambdaMetricNamespace,
|
|
76
|
-
metricName: "Errors",
|
|
77
|
-
dimensions,
|
|
78
|
-
value: 1,
|
|
79
|
-
unit: "Count",
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
84
|
return data;
|
|
83
85
|
}
|
|
@@ -146,7 +146,9 @@ export class SimLambdaCommands {
|
|
|
146
146
|
runAsOwner: properties.runAsOwner,
|
|
147
147
|
// Shared across function creations so a conflicting environment variable
|
|
148
148
|
// is only reported once for this simulated Lambda.
|
|
149
|
-
environmentConflicts: new SimLambdaEnvironmentConflicts(
|
|
149
|
+
environmentConflicts: new SimLambdaEnvironmentConflicts({
|
|
150
|
+
currentEnvironments: () => properties.functions.values().map((existing) => existing.environment),
|
|
151
|
+
}),
|
|
150
152
|
eventInvokeConfigs: this.eventInvokeConfigStore,
|
|
151
153
|
destinations,
|
|
152
154
|
codeStore,
|
|
@@ -84,12 +84,5 @@ export class SimLogsMetricFilter {
|
|
|
84
84
|
* A batch's events grouped into the aggregation periods they were taken in.
|
|
85
85
|
*/
|
|
86
86
|
function byPeriod(events) {
|
|
87
|
-
|
|
88
|
-
for (const event of events) {
|
|
89
|
-
const period = Math.floor(event.ingestionTime / periodMilliseconds) * periodMilliseconds;
|
|
90
|
-
const inPeriod = grouped.get(period) ?? [];
|
|
91
|
-
inPeriod.push(event);
|
|
92
|
-
grouped.set(period, inPeriod);
|
|
93
|
-
}
|
|
94
|
-
return grouped;
|
|
87
|
+
return Map.groupBy(events, (event) => Math.floor(event.ingestionTime / periodMilliseconds) * periodMilliseconds);
|
|
95
88
|
}
|
|
@@ -19,11 +19,12 @@ export class SimRoute53DnsZoneFinder {
|
|
|
19
19
|
let bestZoneNameLength = -1;
|
|
20
20
|
for (const hostedZone of this.hostedZones.values()) {
|
|
21
21
|
const zoneName = simRoute53ZoneName(hostedZone);
|
|
22
|
-
if (zoneContainsName(zoneName, name)
|
|
23
|
-
zoneName.length
|
|
24
|
-
|
|
25
|
-
bestZoneNameLength = zoneName.length;
|
|
22
|
+
if (!zoneContainsName(zoneName, name) ||
|
|
23
|
+
zoneName.length <= bestZoneNameLength) {
|
|
24
|
+
continue;
|
|
26
25
|
}
|
|
26
|
+
bestZone = hostedZone;
|
|
27
|
+
bestZoneNameLength = zoneName.length;
|
|
27
28
|
}
|
|
28
29
|
return bestZone;
|
|
29
30
|
}
|
|
@@ -20,10 +20,11 @@ export class SimRoute53HostedZoneRecordFinder {
|
|
|
20
20
|
continue;
|
|
21
21
|
}
|
|
22
22
|
const record = hostedZone.records.get(name, type);
|
|
23
|
-
if (record
|
|
24
|
-
|
|
25
|
-
bestZoneNameLength = zoneName.length;
|
|
23
|
+
if (record === undefined || zoneName.length <= bestZoneNameLength) {
|
|
24
|
+
continue;
|
|
26
25
|
}
|
|
26
|
+
bestRecord = record;
|
|
27
|
+
bestZoneNameLength = zoneName.length;
|
|
27
28
|
}
|
|
28
29
|
return bestRecord;
|
|
29
30
|
}
|
|
@@ -43,10 +44,11 @@ export class SimRoute53HostedZoneRecordFinder {
|
|
|
43
44
|
if (!this.zoneContainsName(zoneName, name)) {
|
|
44
45
|
continue;
|
|
45
46
|
}
|
|
46
|
-
if (zoneName.length
|
|
47
|
-
|
|
48
|
-
bestZoneNameLength = zoneName.length;
|
|
47
|
+
if (zoneName.length <= bestZoneNameLength) {
|
|
48
|
+
continue;
|
|
49
49
|
}
|
|
50
|
+
bestHostedZone = hostedZone;
|
|
51
|
+
bestZoneNameLength = zoneName.length;
|
|
50
52
|
}
|
|
51
53
|
return bestHostedZone;
|
|
52
54
|
}
|
|
@@ -55,13 +55,10 @@ export class SimS3NotificationFilter {
|
|
|
55
55
|
* filters nothing.
|
|
56
56
|
*/
|
|
57
57
|
toOutput() {
|
|
58
|
-
const rules = [
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (this.suffix !== "") {
|
|
63
|
-
rules.push({ Name: "suffix", Value: this.suffix });
|
|
64
|
-
}
|
|
58
|
+
const rules = [
|
|
59
|
+
...(this.prefix === "" ? [] : [{ Name: "prefix", Value: this.prefix }]),
|
|
60
|
+
...(this.suffix === "" ? [] : [{ Name: "suffix", Value: this.suffix }]),
|
|
61
|
+
];
|
|
65
62
|
if (rules.length === 0) {
|
|
66
63
|
return undefined;
|
|
67
64
|
}
|
|
@@ -23,9 +23,17 @@ export declare class SimS3Bucket extends SimS3BucketConfiguration {
|
|
|
23
23
|
private readonly accountRegionScope;
|
|
24
24
|
private readonly systemMetadata;
|
|
25
25
|
private readonly objects;
|
|
26
|
+
private readonly keyWrites;
|
|
26
27
|
constructor(properties: SimS3BucketProperties);
|
|
27
28
|
/** Put an Object into storage, answering the version it was given, if any. */
|
|
28
29
|
putObject(object: SimS3Object): Promise<SimS3ObjectVersion | undefined>;
|
|
30
|
+
/**
|
|
31
|
+
* Run a write to a key once every earlier write to it has finished.
|
|
32
|
+
*
|
|
33
|
+
* A write that checks the key before replacing it does both inside this,
|
|
34
|
+
* so no other write to the key lands between the check and the put.
|
|
35
|
+
*/
|
|
36
|
+
writeKey<T>(key: string, write: () => Promise<T>): Promise<T>;
|
|
29
37
|
/** Get a simulated S3 Object from storage. */
|
|
30
38
|
getObject(key: string): Promise<SimS3Object | undefined>;
|
|
31
39
|
/** List simulated S3 Objects from storage. */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SimRealClock } from "../../../util/clock/sim-clock.js";
|
|
2
2
|
import { MemoryS3BucketStorage } from "../storage/s3-memory-storage.js";
|
|
3
3
|
import { SimS3BucketObjects } from "./sim-s3-bucket-objects.js";
|
|
4
|
+
import { SimS3KeyWrites } from "./sim-s3-key-writes.js";
|
|
4
5
|
import { SimS3LifecycleConfiguration } from "./lifecycle/sim-s3-lifecycle-configuration.js";
|
|
5
6
|
import { simS3BucketWebsiteUrl } from "./website/sim-s3-bucket-website-url.js";
|
|
6
7
|
import { validateS3BucketName } from "./validate/validate-s3-bucket-name.js";
|
|
@@ -16,6 +17,7 @@ export class SimS3Bucket extends SimS3BucketConfiguration {
|
|
|
16
17
|
accountRegionScope;
|
|
17
18
|
systemMetadata = new SimS3BucketSystemMetadata();
|
|
18
19
|
objects;
|
|
20
|
+
keyWrites = new SimS3KeyWrites();
|
|
19
21
|
constructor(properties) {
|
|
20
22
|
super(properties);
|
|
21
23
|
const { bucketName, accountRegionScope = simAwsAccountRegionScopeFactory.make(), storage = new MemoryS3BucketStorage(), lifecycle = SimS3LifecycleConfiguration.empty(), clock = new SimRealClock(), creationDate = clock.now(), } = properties;
|
|
@@ -29,6 +31,15 @@ export class SimS3Bucket extends SimS3BucketConfiguration {
|
|
|
29
31
|
async putObject(object) {
|
|
30
32
|
return await this.objects.put(object);
|
|
31
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Run a write to a key once every earlier write to it has finished.
|
|
36
|
+
*
|
|
37
|
+
* A write that checks the key before replacing it does both inside this,
|
|
38
|
+
* so no other write to the key lands between the check and the put.
|
|
39
|
+
*/
|
|
40
|
+
async writeKey(key, write) {
|
|
41
|
+
return await this.keyWrites.exclusive(key, write);
|
|
42
|
+
}
|
|
32
43
|
/** Get a simulated S3 Object from storage. */
|
|
33
44
|
async getObject(key) {
|
|
34
45
|
return await this.objects.get(key);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The writes in progress against each key of a Bucket, run one at a time.
|
|
3
|
+
*
|
|
4
|
+
* A conditional write reads the key and then writes it, with awaits between
|
|
5
|
+
* the two. Real S3 lets the first of several conditional writes to one key
|
|
6
|
+
* finish and refuses the rest. Running each key's writes in turn gives the
|
|
7
|
+
* same answer, because a write's condition is read after the write before it
|
|
8
|
+
* has landed. Writes to different keys still run side by side.
|
|
9
|
+
*/
|
|
10
|
+
export declare class SimS3KeyWrites {
|
|
11
|
+
private readonly pending;
|
|
12
|
+
/**
|
|
13
|
+
* Run a write once every earlier write to the same key has settled.
|
|
14
|
+
*/
|
|
15
|
+
exclusive<T>(key: string, write: () => Promise<T>): Promise<T>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The writes in progress against each key of a Bucket, run one at a time.
|
|
3
|
+
*
|
|
4
|
+
* A conditional write reads the key and then writes it, with awaits between
|
|
5
|
+
* the two. Real S3 lets the first of several conditional writes to one key
|
|
6
|
+
* finish and refuses the rest. Running each key's writes in turn gives the
|
|
7
|
+
* same answer, because a write's condition is read after the write before it
|
|
8
|
+
* has landed. Writes to different keys still run side by side.
|
|
9
|
+
*/
|
|
10
|
+
export class SimS3KeyWrites {
|
|
11
|
+
pending = new Map();
|
|
12
|
+
/**
|
|
13
|
+
* Run a write once every earlier write to the same key has settled.
|
|
14
|
+
*/
|
|
15
|
+
async exclusive(key, write) {
|
|
16
|
+
const running = afterEarlier(this.pending.get(key), write);
|
|
17
|
+
const settled = settle(running);
|
|
18
|
+
this.pending.set(key, settled);
|
|
19
|
+
try {
|
|
20
|
+
return await running;
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
if (this.pending.get(key) === settled) {
|
|
24
|
+
this.pending.delete(key);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async function afterEarlier(earlier, write) {
|
|
30
|
+
await earlier;
|
|
31
|
+
return await write();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Wait for a write without taking on its failure, which its own caller sees.
|
|
35
|
+
*/
|
|
36
|
+
async function settle(running) {
|
|
37
|
+
try {
|
|
38
|
+
await running;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// The next write to the key runs whether this one failed or not.
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -34,9 +34,7 @@ export class SimS3BucketVersioning {
|
|
|
34
34
|
* The state a PutBucketVersioning request asks for.
|
|
35
35
|
*/
|
|
36
36
|
static fromConfiguration(configuration) {
|
|
37
|
-
return configuration.Status === "Enabled"
|
|
38
|
-
? new SimS3BucketVersioning("Enabled")
|
|
39
|
-
: new SimS3BucketVersioning("Suspended");
|
|
37
|
+
return new SimS3BucketVersioning(configuration.Status === "Enabled" ? "Enabled" : "Suspended");
|
|
40
38
|
}
|
|
41
39
|
/**
|
|
42
40
|
* What GetBucketVersioning answers with.
|
package/dist/service/s3/command/complete-multipart-upload/complete-multipart-upload.command.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SimResponseMetadata } from "../../../aws/metadata/response-metadata.type.js";
|
|
2
|
+
import type { SimS3WritePrecondition } from "../object/sim-s3-write-precondition.js";
|
|
2
3
|
/**
|
|
3
4
|
* Minimal structural sim S3 CompleteMultipartUpload command.
|
|
4
5
|
*/
|
|
@@ -18,7 +19,7 @@ export interface SimCompletedUploadPart {
|
|
|
18
19
|
* The parts are named by the request rather than assumed by S3, so a client can
|
|
19
20
|
* finish an upload it sent a part of twice.
|
|
20
21
|
*/
|
|
21
|
-
export interface SimCompleteMultipartUploadCommandInput {
|
|
22
|
+
export interface SimCompleteMultipartUploadCommandInput extends SimS3WritePrecondition {
|
|
22
23
|
readonly Bucket?: string | undefined;
|
|
23
24
|
readonly Key?: string | undefined;
|
|
24
25
|
readonly UploadId?: string | undefined;
|
package/dist/service/s3/command/complete-multipart-upload/complete-multipart-upload.handler.js
CHANGED
|
@@ -3,6 +3,7 @@ import { simS3BucketUrl } from "../../bucket/sim-s3-endpoint-url.js";
|
|
|
3
3
|
import { simS3QuotedETag } from "../../object/s3-object-etag.js";
|
|
4
4
|
import { simS3CompletedUploadObject, } from "../../upload/sim-s3-completed-upload.js";
|
|
5
5
|
import { SimS3MultipartAccess, } from "../multipart/sim-s3-multipart-access.js";
|
|
6
|
+
import { simS3RequireWritePrecondition } from "../object/sim-s3-write-precondition.js";
|
|
6
7
|
/**
|
|
7
8
|
* Simulated S3 CompleteMultipartUploadCommand handler.
|
|
8
9
|
*
|
|
@@ -34,7 +35,10 @@ export class CompleteMultipartUploadCommandHandler {
|
|
|
34
35
|
parts: namedParts(command.input.MultipartUpload?.Parts ?? []),
|
|
35
36
|
completedAt: this.access.now(),
|
|
36
37
|
});
|
|
37
|
-
const version = await bucket.
|
|
38
|
+
const version = await bucket.writeKey(Key, async () => {
|
|
39
|
+
await simS3RequireWritePrecondition(bucket, Key, command.input);
|
|
40
|
+
return await bucket.putObject(object);
|
|
41
|
+
});
|
|
38
42
|
bucket.getMultipartUploads().discard(UploadId);
|
|
39
43
|
this.notifications.objectCreated({
|
|
40
44
|
bucket,
|