Repository navigation
ci(AIC-3417): test the published packages in a real AWS Lambda [4/4] #79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
3e10204
feat(AIC-3416): diagnose ESM externalization during initialization
tonytrinh3 3df525d
ci(AIC-3417): canary published artifacts in an AWS Lambda
tonytrinh3 77a106f
docs(AIC-3417): rewrite the canary docs in plain layered English
tonytrinh3 63fe94e
fix(AIC-3417): check exact export types and fail on log group cleanup…
tonytrinh3 d073901
Merge branch 'tonytrinh3/AIC-3415-dual-esm-cjs-output' into tonytrinh…
tonytrinh3 b67dc9c
Merge branch 'main' into tonytrinh3/AIC-3416-esm-externalization-diag…
tonytrinh3 6f0b562
Merge branch 'tonytrinh3/AIC-3416-esm-externalization-diagnostics' in…
tonytrinh3 898a02e
fix(AIC-3417): pin and canary the published core package
tonytrinh3 7845de6
Merge remote-tracking branch 'origin/main' into tonytrinh3/AIC-3417-l…
tonytrinh3 bd31003
fix(AIC-3417): harden the canary AWS role guidance and step order
tonytrinh3 41e0fdf
ci(AIC-3417): skip the canary cleanup when the credentials step does …
tonytrinh3 11acd8d
docs(AIC-3417): name the boundary repository variable and gate the jo…
tonytrinh3 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,5 +27,6 @@ output/ | |
| spec.md | ||
| tmp/ | ||
| integration-tests/**/.staging/ | ||
| integration-tests/**/.canary/ | ||
|
|
||
| .claude/ | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| # Published-artifact Lambda release canary | ||
|
|
||
| Owner: the LaunchDarkly AI SDK team. The team maintains `launchdarkly/js-ai-sdk`. | ||
|
|
||
| ## Plain version | ||
|
|
||
| We publish packages to npm. A customer then installs them and runs them on AWS Lambda. | ||
|
|
||
| This test does the same thing after each release. It installs the published packages. | ||
| It runs them in a real AWS Lambda function. It then deletes that function. | ||
|
|
||
| If the test fails, the packages are broken for customers. We learn this in minutes, not weeks. | ||
|
|
||
| The test only loads the packages. It uses no SDK key. It evaluates no flag. It calls no model. | ||
| So the test costs almost nothing. | ||
|
|
||
| ## What it means for the code (junior eng) | ||
|
|
||
| A canary is a small test that runs against production artifacts after a release. | ||
| This canary is the second half of the work in | ||
| [AIC-3370](https://launchdarkly.atlassian.net/browse/AIC-3370). | ||
|
|
||
| The suite in `integration-tests/commonjs-webpack` packs the workspace with `npm pack`. | ||
| That suite tests the tarball that we plan to publish. | ||
| This canary tests the artifact that npm actually serves. | ||
|
|
||
| The canary installs exact published versions. It never uses a workspace link. | ||
| It never uses a dist-tag such as `latest`. | ||
|
|
||
| The fixture bundles the app with Serverless Framework 3 and Webpack 5. | ||
| It also uses `webpack-node-externals` 3 with no allowlist. | ||
| So Node resolves the LaunchDarkly packages at runtime. | ||
| That path is the exact path that failed in AIC-3370. | ||
|
|
||
| The workflow then deploys one function. It invokes the function one time. | ||
| It checks the JSON answer. It then deletes the function and the log group. | ||
|
|
||
| ### The driver | ||
|
|
||
| `canary.mjs` holds all of the logic. It takes one exact version for each package. | ||
| You pass the versions as flags. You can also set | ||
| `LD_AI_CANARY_VERSIONS='ai-server=0.4.0,ai-node=0.3.0,openai-messages=0.3.0,ai-otel=0.2.0'`. | ||
| The driver rejects a version that is not an exact semver. | ||
|
|
||
| The list includes `@launchdarkly/ai-server`, the core package. | ||
| `@launchdarkly/ai-node` depends on the core package with `*`. | ||
| So npm picks any visible core version when the canary does not pin it. | ||
| The canary installs the core version directly, and a core-only release also runs the canary. | ||
|
|
||
| | Command | What it does | | ||
| | --- | --- | | ||
| | `wait` | Calls `npm view` until npm serves all four versions. A new publish is not readable at once. | | ||
| | `build` | Copies `fixture/` into `.canary/app`. Installs the exact versions with `--save-exact`. Checks the installed tree. Runs `serverless package` when you pass `--package`. | | ||
| | `assert` | Reads the invoke payload. Checks `ok`, a `v22.x` runtime, the capability names, and the loaded versions. | | ||
|
|
||
| The function name is `ld-ai-canary-<stage>`. The workflow gives each run a unique stage. | ||
|
|
||
| ### Local dry run | ||
|
|
||
| This run needs no AWS credentials. | ||
|
|
||
| ```bash | ||
| cd integration-tests/lambda-commonjs | ||
| node canary.mjs build --ai-server=0.3.0 --ai-node=0.2.0 --openai-messages=0.2.0 --ai-otel=0.1.1 --package --stage=local | ||
| unzip -l .canary/app/.serverless/ld-ai-canary.zip | grep '@launchdarkly' | ||
| ``` | ||
|
|
||
| The config schema of Serverless 3 does not know the `nodejs22.x` runtime. | ||
| So the package step prints an `Invalid configuration encountered` warning for `provider.runtime`. | ||
| The fixture sets `configValidationMode: warn`, and the message stays a warning. | ||
| CloudFormation accepts the runtime. The function runs on Node 22. | ||
| The canary reads `process.version` and fails on any other runtime. | ||
|
|
||
| `yarn test:integration` runs `canary.test.ts`. | ||
| That file tests the assert logic and the exact-version guard. | ||
| It uses no network and no AWS account. | ||
|
|
||
| ### AWS prerequisite | ||
|
|
||
| The workflow job uses GitHub OIDC only. | ||
| OIDC lets the job get short-lived AWS credentials from a role. | ||
| This repository holds no long-lived AWS keys. Please do not add any. | ||
|
|
||
| The job stays skipped until these repository variables exist. | ||
|
|
||
| | Variable | Purpose | | ||
| | --- | --- | | ||
| | `AWS_LAMBDA_CANARY_ROLE_ARN` | The role that the job assumes. The job is skipped while this variable is empty. | | ||
| | `AWS_LAMBDA_CANARY_PERMISSIONS_BOUNDARY` | The boundary policy ARN. It must match the ARN in the role's `iam:PermissionsBoundary` condition. The job is skipped while this variable is empty. | | ||
| | `AWS_LAMBDA_CANARY_REGION` | Optional. The default is `us-east-1`. | | ||
|
|
||
| The trust policy of the role must allow `token.actions.githubusercontent.com`. | ||
| Limit `sub` to `repo:launchdarkly/js-ai-sdk:ref:refs/heads/main`. | ||
| The release push runs from `main`, and you start a canary dispatch from `main` too. | ||
| A wider `sub` lets a workflow on any branch assume the role. | ||
| Set `aud` to `https://github.com/launchdarkly`. | ||
|
|
||
| The role needs these permissions. Scope each one to the `ld-ai-canary-*` resources. | ||
|
|
||
| - `cloudformation:CreateStack`, `UpdateStack`, `DeleteStack`, `DescribeStacks`, `DescribeStackEvents`, | ||
| `DescribeStackResource(s)`, `ValidateTemplate`, `GetTemplate`, `ListStackResources` | ||
| - `s3:CreateBucket`, `DeleteBucket`, `ListBucket`, `GetObject`, `PutObject`, `DeleteObject`, | ||
| `GetBucketLocation`, `GetBucketPolicy`, `PutBucketPolicy`, `PutEncryptionConfiguration` on the | ||
| Serverless deployment bucket | ||
| - `lambda:CreateFunction`, `DeleteFunction`, `GetFunction`, `GetFunctionConfiguration`, | ||
| `UpdateFunctionCode`, `UpdateFunctionConfiguration`, `InvokeFunction`, `ListVersionsByFunction`, | ||
| `TagResource` | ||
| - `logs:CreateLogGroup`, `DeleteLogGroup`, `DescribeLogGroups`, `PutRetentionPolicy` | ||
| - `iam:CreateRole`, `DeleteRole`, `GetRole`, `PassRole`, `PutRolePolicy`, `DeleteRolePolicy` | ||
| for the execution role of the function | ||
|
|
||
| The generated stack puts inline `Policies` on the execution role only. | ||
| So the role needs no `iam:AttachRolePolicy` and no `iam:DetachRolePolicy`. | ||
|
|
||
| The `iam:CreateRole` and `iam:PutRolePolicy` actions need an `iam:PermissionsBoundary` condition. | ||
| That condition stops the role from granting itself more rights than the boundary allows. | ||
| The workflow passes `vars.AWS_LAMBDA_CANARY_PERMISSIONS_BOUNDARY` to the fixture. | ||
| The fixture reads it as `LD_AI_CANARY_PERMISSIONS_BOUNDARY`. | ||
| Set that repository variable to the same boundary policy ARN that the condition names. | ||
|
|
||
| ### When the canary fails | ||
|
|
||
| A failure means that a published package does not load from a CommonJS Lambda bundle. | ||
| That is the AIC-3370 bug again. | ||
|
|
||
| Run the local dry run above with the published versions. | ||
| Then invoke the generated bundle directly. | ||
|
|
||
| The `serverless-webpack` plugin deletes `.webpack/` after it writes the zip. | ||
| So you unzip the package first, and then you load `handler.js` from there. | ||
|
|
||
| ```bash | ||
| cd integration-tests/lambda-commonjs/.canary/app | ||
| unzip -o .serverless/ld-ai-canary.zip -d .unzipped | ||
| node -e "require('./.unzipped/handler.js').handler().then(console.log, console.error)" | ||
| ``` | ||
|
|
||
| The cleanup step always runs. The step prints an error when it cannot delete the stack. | ||
| Then you delete the `ld-ai-canary-<stage>` CloudFormation stack by hand. | ||
| You also delete the `/aws/lambda/ld-ai-canary-<stage>` log group by hand. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.