@kensio/yulin 1.20.14 → 1.20.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/{dist/config/oxlint/cffjs2.oxlintrc.json → cffjs2.oxlintrc.json} +1 -1
- package/dist/config/oxlint/cffjs2.oxlint.config.js +4 -3
- package/dist/sdk/module/sim-sdk-module-client-interceptor.d.ts +5 -0
- package/dist/sdk/module/sim-sdk-module-client-interceptor.js +14 -0
- package/dist/sdk/module/sim-sdk-static-client-factory.d.ts +26 -0
- package/dist/sdk/module/sim-sdk-static-client-factory.js +59 -0
- package/dist/sdk/send-patch.d.ts +8 -0
- package/dist/sdk/send-patch.js +11 -0
- package/dist/service/aws/caller/sim-aws-caller-resolver.d.ts +28 -6
- package/dist/service/aws/caller/sim-aws-caller-resolver.js +18 -11
- package/dist/service/aws/caller/sim-aws-caller.d.ts +9 -0
- package/dist/service/aws/caller/sim-aws-resolved-caller.d.ts +19 -0
- package/dist/service/aws/caller/sim-aws-resolved-caller.js +26 -0
- package/dist/service/aws/factory/sim-aws-account-region-service-builder.d.ts +6 -0
- package/dist/service/aws/factory/sim-aws-account-region-service-builder.js +3 -0
- package/dist/service/aws/factory/sim-aws-account-service-cache.d.ts +6 -0
- package/dist/service/aws/factory/sim-aws-account-service-cache.js +5 -0
- package/dist/service/aws/factory/sim-aws-service-factory.d.ts +5 -0
- package/dist/service/aws/factory/sim-aws-service-factory.js +6 -0
- package/dist/service/aws/sim-aws-properties.d.ts +15 -0
- package/dist/service/aws/sim-aws.js +2 -1
- package/dist/service/cloudformation/resource/resolve/property/sim-cfn-resource-property-resolver.d.ts +11 -4
- package/dist/service/cloudformation/resource/resolve/property/sim-cfn-resource-property-resolver.js +21 -8
- package/dist/service/cloudformation/resource/sim-cfn-resource.type.d.ts +9 -0
- package/dist/service/cloudformation/template/dynamic/make-sim-cfn-dynamic-references.d.ts +2 -0
- package/dist/service/cloudformation/template/dynamic/make-sim-cfn-dynamic-references.js +2 -2
- package/dist/service/cloudformation/template/dynamic/sim-cfn-dynamic-reference-resolvers.d.ts +10 -0
- package/dist/service/cloudformation/template/dynamic/sim-cfn-dynamic-reference-resolvers.js +4 -3
- package/dist/service/cloudformation/template/dynamic/sim-cfn-dynamic-reference-scan.d.ts +11 -0
- package/dist/service/cloudformation/template/dynamic/sim-cfn-dynamic-reference-scan.js +21 -0
- package/dist/service/cloudformation/template/dynamic/sim-cfn-dynamic-references.d.ts +24 -2
- package/dist/service/cloudformation/template/dynamic/sim-cfn-dynamic-references.js +50 -11
- package/dist/service/cloudformation/template/dynamic/sim-cfn-prefetched-dynamic-references.d.ts +42 -0
- package/dist/service/cloudformation/template/dynamic/sim-cfn-prefetched-dynamic-references.js +80 -0
- package/dist/service/eventbridge/delivery/sim-event-bridge-delivery-failures.d.ts +16 -0
- package/dist/service/eventbridge/delivery/sim-event-bridge-delivery-failures.js +19 -0
- package/dist/service/iam/authorize/context/sim-iam-auth-z-caller-context-builder.d.ts +30 -7
- package/dist/service/iam/authorize/context/sim-iam-auth-z-caller-context-builder.js +20 -11
- package/dist/service/iam/authorize/context/sim-iam-auth-z-context-builder.d.ts +2 -1
- package/dist/service/iam/authorize/context/sim-iam-auth-z-context-builder.js +5 -1
- package/dist/service/iam/authorize/match/sim-iam-policy-principal-matcher.d.ts +10 -0
- package/dist/service/iam/authorize/match/sim-iam-policy-principal-matcher.js +15 -3
- package/dist/service/iam/authorize/sim-iam-account-auth-z.d.ts +11 -4
- package/dist/service/iam/authorize/sim-iam-account-auth-z.js +5 -3
- package/dist/service/iam/authorize/sim-iam-authorizer.d.ts +2 -1
- package/dist/service/iam/authorize/sim-iam-authorizer.js +1 -0
- package/dist/service/iam/command/policy/create-policy/create-policy-input-resolver.js +4 -1
- package/dist/service/iam/command/policy/put-role-policy/put-role-policy.handler.js +5 -1
- package/dist/service/iam/command/policy/put-user-policy/put-user-policy.handler.js +5 -1
- package/dist/service/iam/policy/parse/sim-iam-document-parser.d.ts +4 -1
- package/dist/service/iam/policy/parse/sim-iam-document-parser.js +13 -15
- package/dist/service/iam/policy/sim-iam-statement-label.d.ts +28 -0
- package/dist/service/iam/policy/sim-iam-statement-label.js +17 -0
- package/dist/service/iam/policy/sim-iam-statement-strings.d.ts +11 -0
- package/dist/service/iam/policy/sim-iam-statement-strings.js +27 -0
- package/dist/service/iam/role/sim-iam-role-with-policy.factory.d.ts +9 -0
- package/dist/service/iam/role/sim-iam-role-with-policy.factory.js +4 -2
- package/dist/service/iam/sim-iam-account-parts.d.ts +8 -0
- package/dist/service/iam/sim-iam-account-parts.js +1 -0
- package/dist/service/iam/validate/sim-iam-policy-document-validator.d.ts +9 -2
- package/dist/service/iam/validate/sim-iam-policy-document-validator.js +37 -19
- package/dist/service/lambda/function/code/sim-lambda-vm-zip-function.factory.d.ts +7 -0
- package/dist/service/lambda/function/code/sim-lambda-vm-zip-function.factory.js +1 -0
- package/dist/service/lambda/function/code/vm/sdk/sim-lambda-provided-sdk-module.d.ts +19 -0
- package/dist/service/lambda/function/code/vm/sdk/sim-lambda-provided-sdk-module.js +78 -0
- package/dist/service/lambda/function/code/vm/sdk/sim-lambda-sdk-packages-not-installed.error.d.ts +15 -0
- package/dist/service/lambda/function/code/vm/sdk/sim-lambda-sdk-packages-not-installed.error.js +27 -0
- package/dist/service/lambda/function/code/vm/sdk/sim-lambda-vm-sdk-module-provider.d.ts +9 -0
- package/dist/service/lambda/function/code/vm/sdk/sim-sdk-lambda-vm-module-provider.d.ts +10 -0
- package/dist/service/lambda/function/code/vm/sdk/sim-sdk-lambda-vm-module-provider.js +28 -34
- package/dist/service/lambda/function/code/vm/sim-lambda-host-modules.d.ts +15 -0
- package/dist/service/lambda/function/code/vm/sim-lambda-host-modules.js +31 -0
- package/dist/service/lambda/function/code/vm/sim-lambda-vm-module-resolver.d.ts +7 -0
- package/dist/service/lambda/function/code/vm/sim-lambda-vm-module-resolver.js +15 -0
- package/dist/service/lambda/function/code/vm/sim-lambda-vm-modules.js +5 -4
- package/dist/service/s3/command/put-bucket-policy/put-bucket-policy.handler.js +4 -1
- package/dist/service/scheduler/delivery/sim-scheduler-delivery-failures.d.ts +16 -0
- package/dist/service/scheduler/delivery/sim-scheduler-delivery-failures.js +19 -0
- package/dist/service/scheduler/index.d.ts +1 -1
- package/dist/service/scheduler/index.js +1 -1
- package/dist/service/secretsmanager/cfn/dynamic/sim-cfn-secrets-manager-dynamic-reference-resolver.d.ts +13 -15
- package/dist/service/secretsmanager/cfn/dynamic/sim-cfn-secrets-manager-dynamic-reference-resolver.js +22 -43
- package/dist/service/secretsmanager/cfn/dynamic/sim-cfn-secrets-manager-reference-stand-in.d.ts +9 -0
- package/dist/service/secretsmanager/cfn/dynamic/sim-cfn-secrets-manager-reference-stand-in.js +14 -0
- package/dist/service/secretsmanager/cfn/dynamic/sim-cfn-secrets-manager-reference-value.d.ts +12 -0
- package/dist/service/secretsmanager/cfn/dynamic/sim-cfn-secrets-manager-reference-value.js +29 -0
- package/dist/service/ssm/cfn/dynamic/sim-cfn-ssm-dynamic-reference-resolver.d.ts +22 -6
- package/dist/service/ssm/cfn/dynamic/sim-cfn-ssm-dynamic-reference-resolver.js +39 -24
- package/dist/service/ssm/cfn/dynamic/sim-cfn-ssm-secure-dynamic-reference-resolver.d.ts +10 -5
- package/dist/service/ssm/cfn/dynamic/sim-cfn-ssm-secure-dynamic-reference-resolver.js +10 -8
- package/dist/service/ssm/sim-ssm.d.ts +10 -2
- package/dist/service/ssm/sim-ssm.js +12 -4
- package/dist/service/sts/auth-z/assume-role-auth-z-coordinator.d.ts +9 -2
- package/dist/service/sts/auth-z/assume-role-auth-z-coordinator.js +2 -2
- package/dist/service/sts/auth-z/assume-role-source-account-auth-z.d.ts +10 -4
- package/dist/service/sts/auth-z/assume-role-source-account-auth-z.js +8 -7
- package/dist/service/sts/auth-z/assume-role-target-auth-z.d.ts +2 -2
- package/dist/service/sts/auth-z/assume-role-trust-policy-authorizer.d.ts +6 -2
- package/dist/service/sts/auth-z/assume-role-trust-policy-authorizer.js +14 -11
- package/dist/service/sts/command/assume-role/assume-role.handler.d.ts +5 -1
- package/dist/service/sts/command/assume-role/assume-role.handler.js +5 -2
- package/dist/service/sts/command/get-caller-identity/get-caller-identity.handler.d.ts +5 -1
- package/dist/service/sts/command/get-caller-identity/get-caller-identity.handler.js +4 -1
- package/dist/service/sts/service-role/sim-service-role.js +5 -1
- package/dist/service/sts/sim-sts.d.ts +9 -1
- package/dist/service/sts/sim-sts.js +4 -0
- package/docs/README.md +54 -0
- package/docs/ai-skill/README.md +69 -0
- package/docs/cli/README.md +284 -0
- package/docs/factories/README.md +140 -0
- package/docs/lint/README.md +176 -0
- package/docs/non-aws-dependencies/README.md +261 -0
- package/docs/sdk/README.md +272 -0
- package/docs/serve/README.md +1126 -0
- package/docs/services/acm/README.md +823 -0
- package/docs/services/apigateway/README.md +1919 -0
- package/docs/services/apigatewayv2/README.md +2678 -0
- package/docs/services/athena/README.md +1044 -0
- package/docs/services/bedrock/README.md +350 -0
- package/docs/services/cloudformation/README.md +3440 -0
- package/docs/services/cloudfront/README.md +2874 -0
- package/docs/services/cloudwatch/README.md +420 -0
- package/docs/services/cognito/README.md +4923 -0
- package/docs/services/dynamodb/README.md +3784 -0
- package/docs/services/ecr/README.md +302 -0
- package/docs/services/ecs/README.md +2010 -0
- package/docs/services/elbv2/README.md +1897 -0
- package/docs/services/eventbridge/README.md +1110 -0
- package/docs/services/firehose/README.md +805 -0
- package/docs/services/glue/README.md +501 -0
- package/docs/services/iam/README.md +1283 -0
- package/docs/services/kinesis/README.md +454 -0
- package/docs/services/kms/README.md +659 -0
- package/docs/services/lambda/README.md +3645 -0
- package/docs/services/logs/README.md +868 -0
- package/docs/services/organizations/README.md +574 -0
- package/docs/services/personalize/README.md +835 -0
- package/docs/services/rekognition/README.md +1070 -0
- package/docs/services/route53/README.md +1783 -0
- package/docs/services/s3/README.md +3024 -0
- package/docs/services/scheduler/README.md +806 -0
- package/docs/services/secretsmanager/README.md +667 -0
- package/docs/services/ses/README.md +1011 -0
- package/docs/services/sns/README.md +1904 -0
- package/docs/services/sqs/README.md +1185 -0
- package/docs/services/ssm/README.md +1072 -0
- package/docs/services/stepfunctions/README.md +1332 -0
- package/docs/services/sts/README.md +245 -0
- package/docs/services/wafv2/README.md +1378 -0
- package/docs/terraform/README.md +248 -0
- package/docs/time/README.md +284 -0
- package/llms.txt +59 -0
- package/package.json +9 -5
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Linting CFF JS2
|
|
2
|
+
|
|
3
|
+
CloudFront Functions run JS2, ECMAScript 5.1 with a named subset of ES 6 to 12 on top, rather than
|
|
4
|
+
a current JavaScript engine. A class or a `for...of` is a syntax error, and CloudFront refuses the
|
|
5
|
+
code when you upload it. A call to `fetch` parses, and then fails at the edge, where that global is
|
|
6
|
+
absent and there is no network to reach. Both are a long way from where the code was written, and
|
|
7
|
+
Yulin publishes lint configs that refuse the same things in the editor.
|
|
8
|
+
|
|
9
|
+
The configs apply to `**/*.cff.js` files. That is the naming
|
|
10
|
+
[sim CloudFront](https://yulinsim.dev/services/cloudfront/ "Simulated CloudFront usage docs") already uses for
|
|
11
|
+
CloudFront Function source.
|
|
12
|
+
|
|
13
|
+
Every restriction comes from the runtime's
|
|
14
|
+
[own feature list](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-javascript-runtime-20.html),
|
|
15
|
+
an allow-list, and anything outside it is unsupported. None of it is house style. A rule
|
|
16
|
+
banning syntax JS2 accepts would send you away from code that works.
|
|
17
|
+
|
|
18
|
+
## Setting it up with ESLint
|
|
19
|
+
|
|
20
|
+
`@kensio/yulin/eslint` exports a flat config to spread into your own. It restricts itself to
|
|
21
|
+
`**/*.cff.js`. The rest of your config still applies everywhere else.
|
|
22
|
+
|
|
23
|
+
```typescript cff-js2-eslint-config
|
|
24
|
+
/**
|
|
25
|
+
* An ESLint config that lints CloudFront Function files as JS2.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import eslint from "@eslint/js";
|
|
29
|
+
import { defineConfig } from "eslint/config";
|
|
30
|
+
|
|
31
|
+
import { cloudFrontFunctionsJs2 } from "@kensio/yulin/eslint";
|
|
32
|
+
|
|
33
|
+
export default defineConfig(
|
|
34
|
+
eslint.configs.recommended,
|
|
35
|
+
|
|
36
|
+
// Applies only to **/*.cff.js, so it goes after the configs it relaxes.
|
|
37
|
+
...cloudFrontFunctionsJs2,
|
|
38
|
+
);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
It goes after any config whose rules it needs to turn off. That is a short list. `const`, `let`,
|
|
42
|
+
template literals, arrow functions, rest parameters and `async`/`await` all work in JS2, and the
|
|
43
|
+
rules asking for them stay on. Only `object-shorthand` is switched off, because shorthand property
|
|
44
|
+
names are ES 6 literal syntax the runtime lacks.
|
|
45
|
+
|
|
46
|
+
`eslint` and `typescript-eslint` are optional peer dependencies, needed only if you use this export.
|
|
47
|
+
|
|
48
|
+
## Setting it up with Oxlint
|
|
49
|
+
|
|
50
|
+
`@kensio/yulin/oxlint` ships the same rules as an Oxlint config fragment to extend. The rules
|
|
51
|
+
themselves are one plugin loaded by both linters. The two configs report the same things in the same
|
|
52
|
+
places.
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"extends": ["./node_modules/@kensio/yulin/cffjs2.oxlintrc.json"],
|
|
57
|
+
"rules": {
|
|
58
|
+
"no-console": "error"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Oxlint's `extends` takes a file path rather than a package name. The path into `node_modules` is
|
|
64
|
+
written out in full. The file sits in the package root. A build that moves what it emits under
|
|
65
|
+
`dist/` leaves the path alone. The fragment brings its own `overrides` entry scoped to `**/*.cff.js`
|
|
66
|
+
and the JS plugin the rules live in, and leaves every other file to your own rules.
|
|
67
|
+
|
|
68
|
+
The plugin is loaded through Oxlint's JS plugin support, which needs Oxlint 1.77 or later.
|
|
69
|
+
|
|
70
|
+
## What is reported
|
|
71
|
+
|
|
72
|
+
Each restriction is its own rule, under the `cff-js2` name in both linters.
|
|
73
|
+
|
|
74
|
+
| Rule | What it refuses |
|
|
75
|
+
| ------------------------------- | -------------------------------------------------------- |
|
|
76
|
+
| `cff-js2/no-import` | `import` declarations, apart from `cloudfront` |
|
|
77
|
+
| `cff-js2/only-handler-export` | Any export other than `export function handler(...)` |
|
|
78
|
+
| `cff-js2/only-built-in-require` | Requiring anything but `querystring`, `crypto`, `buffer` |
|
|
79
|
+
| `cff-js2/no-class` | Class declarations and expressions |
|
|
80
|
+
| `cff-js2/no-generator` | Generators and `yield` |
|
|
81
|
+
| `cff-js2/no-destructuring` | Object and array destructuring |
|
|
82
|
+
| `cff-js2/no-spread` | Spread syntax, but not rest parameters |
|
|
83
|
+
| `cff-js2/no-for-of` | `for...of`, in favour of an index-based loop |
|
|
84
|
+
| `cff-js2/no-unavailable-global` | Globals the runtime does not have, such as `fetch` |
|
|
85
|
+
|
|
86
|
+
`cff-js2/no-unavailable-global` covers three groups. `fetch`, `XMLHttpRequest` and `WebSocket` need
|
|
87
|
+
a network the runtime lacks. `process` needs Node.js. `setTimeout`, `setInterval`, `setImmediate`
|
|
88
|
+
and `clearTimeout` need an event loop a Function never gets. The rule resolves names through scope
|
|
89
|
+
rather than matching them as text, and a local variable named `fetch` or a property named
|
|
90
|
+
`event.fetch` is left alone. Each report says why the global is missing, because `fetch` being
|
|
91
|
+
absent for want of a network and `setTimeout` for want of an event loop call for different rewrites.
|
|
92
|
+
|
|
93
|
+
Alongside these, both configs turn on `no-eval`, `no-new-func` and `no-implied-eval`, which the
|
|
94
|
+
runtime refuses outright. They also set `no-unused-vars` to leave `handler` alone, since it is the
|
|
95
|
+
entry point CloudFront calls.
|
|
96
|
+
|
|
97
|
+
## What is not reported
|
|
98
|
+
|
|
99
|
+
These all work in JS2 and no rule here objects to them:
|
|
100
|
+
|
|
101
|
+
- Template literals, including interpolation and nesting
|
|
102
|
+
- Arrow functions and rest parameters
|
|
103
|
+
- `const` and `let`
|
|
104
|
+
- `async` and `await`
|
|
105
|
+
- `Promise`, including `all`, `allSettled`, `any` and `race`
|
|
106
|
+
- `Buffer`, and `require` of `querystring`, `crypto` or `buffer`
|
|
107
|
+
- `import cf from "cloudfront"`, which a Function needs for `cf.kvs()` and
|
|
108
|
+
`cf.updateRequestOrigin()`
|
|
109
|
+
- `String.prototype.replaceAll`, `atob`, `btoa` and numeric separators
|
|
110
|
+
|
|
111
|
+
## Turning one restriction off
|
|
112
|
+
|
|
113
|
+
Rules are individual. A restriction you disagree with can be switched off on its own. In ESLint:
|
|
114
|
+
|
|
115
|
+
```typescript cff-js2-eslint-relax
|
|
116
|
+
/**
|
|
117
|
+
* Allowing class syntax in CloudFront Function files.
|
|
118
|
+
*/
|
|
119
|
+
|
|
120
|
+
import { defineConfig } from "eslint/config";
|
|
121
|
+
|
|
122
|
+
import { cloudFrontFunctionsJs2 } from "@kensio/yulin/eslint";
|
|
123
|
+
|
|
124
|
+
export default defineConfig(...cloudFrontFunctionsJs2, {
|
|
125
|
+
files: ["**/*.cff.js"],
|
|
126
|
+
rules: {
|
|
127
|
+
"cff-js2/no-class": "off",
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
In Oxlint, the same thing goes in an `overrides` entry after the `extends`:
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"extends": ["./node_modules/@kensio/yulin/cffjs2.oxlintrc.json"],
|
|
137
|
+
"overrides": [
|
|
138
|
+
{
|
|
139
|
+
"files": ["**/*.cff.js"],
|
|
140
|
+
"rules": {
|
|
141
|
+
"cff-js2/no-class": "off"
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Available functionality
|
|
149
|
+
|
|
150
|
+
- A flat ESLint config at `@kensio/yulin/eslint`, exported as `cloudFrontFunctionsJs2`
|
|
151
|
+
- An Oxlint config fragment at `cffjs2.oxlintrc.json` in the package root, with the object it is
|
|
152
|
+
generated from exported as `cloudFrontFunctionsJs2Oxlint` from `@kensio/yulin/oxlint`
|
|
153
|
+
- Nine `cff-js2` rules, one per restriction, shared by both linters
|
|
154
|
+
- Scoping to `**/*.cff.js`, leaving a repository's own rules untouched elsewhere
|
|
155
|
+
|
|
156
|
+
## Limitations
|
|
157
|
+
|
|
158
|
+
Where the configs knowingly stop short:
|
|
159
|
+
|
|
160
|
+
- **`async` arguments and closures are not checked.** JS2 supports `async` and `await`, but not
|
|
161
|
+
`async` arguments or closures, and `await` only inside an `async` function. Where the runtime's
|
|
162
|
+
wording stops short of saying exactly which forms those are, no rule guesses at it. A Function can
|
|
163
|
+
pass the lint and still be refused for one.
|
|
164
|
+
- **The list of unavailable globals is the useful part of one, not all of it.** It names what test
|
|
165
|
+
code and Node habits reach for. A global outside that list goes unreported, even if JS2 lacks it.
|
|
166
|
+
- **The rules are syntactic.** Nothing here knows CloudFront's size limit on Function code or its CPU
|
|
167
|
+
budget. A Function that passes the lint can still be refused at publication for being too large or
|
|
168
|
+
too slow. No rule checks which methods of a supported built-in you call either, and the runtime
|
|
169
|
+
supports only some of them.
|
|
170
|
+
- **Only the `cff-js2` rules are shared between the linters.** ESLint and Oxlint each bring their own
|
|
171
|
+
built-in rules, and their own defaults for which are on. A `.cff.js` file linted by both may pick
|
|
172
|
+
up findings from one that the other lacks.
|
|
173
|
+
- **Oxlint's JS plugin support is in alpha.** It is what loads these rules into Oxlint, and its API
|
|
174
|
+
is still moving upstream.
|
|
175
|
+
- **The Oxlint fragment is extended by path.** Oxlint has no package-name resolution in `extends`.
|
|
176
|
+
The written-out path into `node_modules` depends on your installer's layout.
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# Non-AWS dependencies
|
|
2
|
+
|
|
3
|
+
Most applications talk to something other than AWS, such as a Redis or a Postgres. This page covers
|
|
4
|
+
what happens to those when the application runs under Yulin.
|
|
5
|
+
|
|
6
|
+
## Two kinds of dependency
|
|
7
|
+
|
|
8
|
+
A simulated Lambda function and a simulated ECS container both run your own code in the same Node.js
|
|
9
|
+
process as the test. Everything that code talks to falls into one of two categories.
|
|
10
|
+
|
|
11
|
+
Simulated AWS services are handled by Yulin. A DynamoDB call reaches an in-memory table and an S3
|
|
12
|
+
call reaches an in-memory bucket, with no network involved. [AWS SDK interception](https://yulinsim.dev/sdk/)
|
|
13
|
+
is how an ordinary SDK client in the code under test gets there.
|
|
14
|
+
|
|
15
|
+
Everything else is yours to provide, and connects the way it normally would. Yulin leaves it alone
|
|
16
|
+
entirely, with no simulation and no interception. Code that opens a Redis connection opens a real
|
|
17
|
+
one, to whatever address it was given.
|
|
18
|
+
|
|
19
|
+
## Pointing the code at your own dependency
|
|
20
|
+
|
|
21
|
+
A deployed Lambda function or ECS container reads its connection details from environment variables.
|
|
22
|
+
The simulated ones do the same. A function's `Environment.Variables` and a container definition's
|
|
23
|
+
`environment` are visible through `process.env` while the code runs, so pointing the application
|
|
24
|
+
somewhere else means setting the value it already reads. No Yulin feature is involved, and the code
|
|
25
|
+
under test stays as it is.
|
|
26
|
+
|
|
27
|
+
```typescript non-aws-dependency-lambda
|
|
28
|
+
/**
|
|
29
|
+
* Pointing a simulated Lambda function at a dependency Yulin does not
|
|
30
|
+
* simulate, alongside one it does.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { CreateFunctionCommand, InvokeCommand } from "@aws-sdk/client-lambda";
|
|
34
|
+
|
|
35
|
+
import { SimAws } from "@kensio/yulin";
|
|
36
|
+
import { makeLambdaZipFileInput } from "@kensio/yulin/lambda";
|
|
37
|
+
|
|
38
|
+
const simAws = new SimAws();
|
|
39
|
+
const lambda = simAws.lambda();
|
|
40
|
+
|
|
41
|
+
await lambda.createFunction(
|
|
42
|
+
new CreateFunctionCommand({
|
|
43
|
+
FunctionName: "rates",
|
|
44
|
+
Role: "arn:aws:iam::111111111111:role/RatesRole",
|
|
45
|
+
Environment: {
|
|
46
|
+
Variables: {
|
|
47
|
+
// Simulated by Yulin, reached with no network involved.
|
|
48
|
+
TABLE_NAME: "rates",
|
|
49
|
+
// Yours. A deployment points this at ElastiCache. A test points it
|
|
50
|
+
// at whatever it wants the code to talk to instead.
|
|
51
|
+
CACHE_URL: "redis://127.0.0.1:6379",
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
Code: {
|
|
55
|
+
ZipFile: makeLambdaZipFileInput(() => ({
|
|
56
|
+
// Building a Redis client from the second value involves nothing of
|
|
57
|
+
// Yulin's. It is an ordinary environment variable read.
|
|
58
|
+
tableName: process.env["TABLE_NAME"],
|
|
59
|
+
cacheUrl: process.env["CACHE_URL"],
|
|
60
|
+
})),
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const output = await lambda.invoke(
|
|
66
|
+
new InvokeCommand({ FunctionName: "rates" }),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (output.Payload === undefined) throw new Error("No invoke Payload");
|
|
70
|
+
// {"tableName":"rates","cacheUrl":"redis://127.0.0.1:6379"}
|
|
71
|
+
console.log(Buffer.from(output.Payload).toString());
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
What that address points at is up to the test. A Redis running on localhost, one the test suite
|
|
75
|
+
starts in a container, or a stand-in the test defines itself all work, because the application is
|
|
76
|
+
doing what it always does with the value it is given.
|
|
77
|
+
|
|
78
|
+
## A container reading from both
|
|
79
|
+
|
|
80
|
+
The worked example below runs one simulated ECS container that writes to a simulated DynamoDB table
|
|
81
|
+
and reads from a cache of its own. The two categories sit next to each other in the same handler and
|
|
82
|
+
are configured the same way, through the container definition's `environment`.
|
|
83
|
+
|
|
84
|
+
The cache here is a stand-in defined by the example, and it works with no server running. A real
|
|
85
|
+
client built from `CACHE_URL` would go in the same place.
|
|
86
|
+
|
|
87
|
+
```typescript non-aws-dependency-ecs
|
|
88
|
+
/**
|
|
89
|
+
* A simulated ECS container writing to a simulated DynamoDB table and reading
|
|
90
|
+
* from a cache of its own.
|
|
91
|
+
*/
|
|
92
|
+
|
|
93
|
+
import {
|
|
94
|
+
CreateTableCommand,
|
|
95
|
+
DynamoDBClient,
|
|
96
|
+
GetItemCommand,
|
|
97
|
+
PutItemCommand,
|
|
98
|
+
} from "@aws-sdk/client-dynamodb";
|
|
99
|
+
import {
|
|
100
|
+
CreateClusterCommand,
|
|
101
|
+
RegisterTaskDefinitionCommand,
|
|
102
|
+
RunTaskCommand,
|
|
103
|
+
} from "@aws-sdk/client-ecs";
|
|
104
|
+
import { CreateRoleCommand, PutRolePolicyCommand } from "@aws-sdk/client-iam";
|
|
105
|
+
|
|
106
|
+
import { SimSdk } from "@kensio/yulin/sdk";
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Where the worker reads exchange rates from.
|
|
110
|
+
*
|
|
111
|
+
* A deployment builds a Redis client from the URL. This example stands one in,
|
|
112
|
+
* so nothing has to be running for it to work.
|
|
113
|
+
*/
|
|
114
|
+
class RateCache {
|
|
115
|
+
constructor(private readonly url: string) {}
|
|
116
|
+
|
|
117
|
+
rate(currency: string): Promise<string> {
|
|
118
|
+
console.log(`reading ${currency} from ${this.url}`);
|
|
119
|
+
// reading GBP from redis://127.0.0.1:6379
|
|
120
|
+
return Promise.resolve("1.27");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
using simSdk = new SimSdk();
|
|
125
|
+
const { simAws } = simSdk;
|
|
126
|
+
const ecs = simAws.ecs();
|
|
127
|
+
|
|
128
|
+
simSdk.intercept(DynamoDBClient);
|
|
129
|
+
|
|
130
|
+
await simAws.dynamoDb().createTable(
|
|
131
|
+
new CreateTableCommand({
|
|
132
|
+
TableName: "rates",
|
|
133
|
+
KeySchema: [{ AttributeName: "currency", KeyType: "HASH" }],
|
|
134
|
+
AttributeDefinitions: [{ AttributeName: "currency", AttributeType: "S" }],
|
|
135
|
+
BillingMode: "PAY_PER_REQUEST",
|
|
136
|
+
}),
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const taskRole = await simAws.iam().createRole(
|
|
140
|
+
new CreateRoleCommand({
|
|
141
|
+
RoleName: "RatesTaskRole",
|
|
142
|
+
AssumeRolePolicyDocument: JSON.stringify({
|
|
143
|
+
Version: "2012-10-17",
|
|
144
|
+
Statement: {
|
|
145
|
+
Effect: "Allow",
|
|
146
|
+
Principal: { Service: "ecs-tasks.amazonaws.com" },
|
|
147
|
+
Action: "sts:AssumeRole",
|
|
148
|
+
},
|
|
149
|
+
}),
|
|
150
|
+
}),
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
await simAws.iam().putRolePolicy(
|
|
154
|
+
new PutRolePolicyCommand({
|
|
155
|
+
RoleName: "RatesTaskRole",
|
|
156
|
+
PolicyName: "WriteRates",
|
|
157
|
+
PolicyDocument: JSON.stringify({
|
|
158
|
+
Version: "2012-10-17",
|
|
159
|
+
Statement: {
|
|
160
|
+
Effect: "Allow",
|
|
161
|
+
Action: "dynamodb:PutItem",
|
|
162
|
+
Resource:
|
|
163
|
+
`arn:aws:dynamodb:${simAws.defaultRegionName}:` +
|
|
164
|
+
`${simAws.defaultAccountId}:table/rates`,
|
|
165
|
+
},
|
|
166
|
+
}),
|
|
167
|
+
}),
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
await ecs.createCluster(new CreateClusterCommand({}));
|
|
171
|
+
|
|
172
|
+
ecs.bindContainer({
|
|
173
|
+
family: "rates-worker",
|
|
174
|
+
containerName: "app",
|
|
175
|
+
run: async () => {
|
|
176
|
+
// Both reads happen inside the handler, so they see the container's own
|
|
177
|
+
// variables rather than the test process's.
|
|
178
|
+
const cache = new RateCache(process.env["CACHE_URL"] ?? "");
|
|
179
|
+
const rate = await cache.rate("GBP");
|
|
180
|
+
|
|
181
|
+
await new DynamoDBClient({}).send(
|
|
182
|
+
new PutItemCommand({
|
|
183
|
+
TableName: process.env["TABLE_NAME"],
|
|
184
|
+
Item: { currency: { S: "GBP" }, rate: { S: rate } },
|
|
185
|
+
}),
|
|
186
|
+
);
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
await ecs.registerTaskDefinition(
|
|
191
|
+
new RegisterTaskDefinitionCommand({
|
|
192
|
+
family: "rates-worker",
|
|
193
|
+
taskRoleArn: taskRole.Role.Arn,
|
|
194
|
+
containerDefinitions: [
|
|
195
|
+
{
|
|
196
|
+
name: "app",
|
|
197
|
+
image: "rates-worker:1",
|
|
198
|
+
environment: [
|
|
199
|
+
// Simulated by Yulin, reached with no network involved.
|
|
200
|
+
{ name: "TABLE_NAME", value: "rates" },
|
|
201
|
+
// Yours, connected to the way it normally would be.
|
|
202
|
+
{ name: "CACHE_URL", value: "redis://127.0.0.1:6379" },
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
}),
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
await ecs.runTask(new RunTaskCommand({ taskDefinition: "rates-worker" }));
|
|
210
|
+
await simAws.backgroundTasksComplete();
|
|
211
|
+
|
|
212
|
+
const stored = await simAws.dynamoDb().getItem(
|
|
213
|
+
new GetItemCommand({
|
|
214
|
+
TableName: "rates",
|
|
215
|
+
Key: { currency: { S: "GBP" } },
|
|
216
|
+
}),
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
console.log(stored.Item?.["rate"]?.S); // "1.27"
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
The DynamoDB write is authorized as the task role, in the same way it would be in a deployment. The
|
|
223
|
+
cache read passes through no authorization at all, because IAM has no part in it.
|
|
224
|
+
|
|
225
|
+
## Sidecar containers are not started
|
|
226
|
+
|
|
227
|
+
A task definition sometimes declares the dependency itself as a second container, such as a Redis
|
|
228
|
+
running next to the application in the same task. Yulin never looks inside a container image, and
|
|
229
|
+
the only thing it can run is JavaScript or TypeScript in its own process. An image holding a Redis
|
|
230
|
+
server is beyond it.
|
|
231
|
+
|
|
232
|
+
The container is stored and reported back as declared, and it is recorded as not simulated when the
|
|
233
|
+
task runs, with a reason saying so. An application expecting it has to be given something else to
|
|
234
|
+
talk to. Set the variable holding the address to something that answers, whether that is a Redis you
|
|
235
|
+
run yourself or a stand-in, in the same way as for any other dependency of your own.
|
|
236
|
+
|
|
237
|
+
## Reads happen inside the handler
|
|
238
|
+
|
|
239
|
+
Handler code gets the function's or the container's variables while it runs, and reads the host
|
|
240
|
+
process environment otherwise. A read at module scope, as in `const url = process.env.CACHE_URL` at
|
|
241
|
+
the top of a file, happens when the test imports that file rather than when the code runs. It sees
|
|
242
|
+
the host value.
|
|
243
|
+
|
|
244
|
+
That matters here because a connection is often built at module scope. Read inside the handler, or
|
|
245
|
+
build the client there, to get the configured value. Sim Lambda warns on the console when the
|
|
246
|
+
difference changes what the code sees, which is covered under
|
|
247
|
+
[environment variables](https://yulinsim.dev/services/lambda/#environment-variables) on the simulated Lambda
|
|
248
|
+
page. Zip code running in the vm runtime is unaffected, because it is imported during an invocation.
|
|
249
|
+
|
|
250
|
+
## Limitations
|
|
251
|
+
|
|
252
|
+
Current documented limitations:
|
|
253
|
+
|
|
254
|
+
- Everything outside the simulated AWS services is yours to run and to tear down. Yulin starts none
|
|
255
|
+
of it.
|
|
256
|
+
- A dependency declared as a sidecar container in an ECS task definition is not started, because
|
|
257
|
+
Yulin never runs a container image.
|
|
258
|
+
- A connection built at module scope reads the host environment rather than the function's or the
|
|
259
|
+
container's, since the value is read before anything runs.
|
|
260
|
+
- There is no interception point for a non-AWS dependency. The way to send the code somewhere else
|
|
261
|
+
is the environment variable it already reads, or an injection point in the code itself.
|