Skip to content

Commit a7cbc74

Browse files
authored
chore(web): add a script to spawn evergreen patch that runs compass-web with Atlas e2e tests COMPASS-10227 (#7861)
* chore(web): add a script to spawn evergreen patch that runs compass-web with Atlas e2e tests * chore(web): use completely random tmp folder; tighten file modes * chore(web): check > test-web-sandbox-atlas-cloud * chore(web): add test step to the publish task * fix(web): map arch name when downloading * fix(ci): do a full checkout * fix(ci): try with actionsbot user * fix(web): always use HOME override as a way to set up evergreen config; clearer arch mapping logic * chore(web): remove brackets from debug log to fix the link * fix(web): explicitly provide commit ref for spawned tests to make sure it's in sync with publish * chore(web): allow to skip e2e test during deploy * fix(ci): fix typo in input description key
1 parent 13ef027 commit a7cbc74

File tree

3 files changed

+240
-4
lines changed

3 files changed

+240
-4
lines changed

.github/workflows/publish-compass-web.yaml

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ on:
55
workflow_dispatch:
66
inputs:
77
publish_environment:
8-
descript: 'Atlas Cloud environment to publish compass-web to'
8+
description: 'Atlas Cloud environment to publish compass-web to'
99
type: choice
1010
default: 'qa'
1111
options:
@@ -15,9 +15,13 @@ on:
1515
- prod
1616
required: true
1717
dangerously_override_commit_hash:
18-
descript: 'An override for the commit hash to be used for the release. Default is the tip of the selected branch. ONLY USE IF YOU KNOW WHAT YOU ARE DOING!'
18+
description: 'An override for the commit hash to be used for the release. Default is the tip of the selected branch. ONLY USE IF YOU KNOW WHAT YOU ARE DOING!'
1919
default: ''
2020
required: false
21+
dangerously_skip_e2e_tests:
22+
description: 'Skips the e2e tests and starts the publishing process immediately. ONLY USE IF YOU KNOW WHAT YOU ARE DOING!'
23+
type: boolean
24+
default: false
2125
dry_run:
2226
description: 'Run the publish process but do not upload the file. Useful for testing that the script is functioning correctly'
2327
type: boolean
@@ -35,6 +39,15 @@ jobs:
3539
steps:
3640
- name: Checkout
3741
uses: actions/checkout@v4
42+
with:
43+
# Evergreen CLI needs the full history
44+
ref: ${{ github.head_ref }}
45+
fetch-depth: '0'
46+
47+
- name: Setup git user
48+
run: |
49+
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
50+
git config --local user.name "github-actions[bot]"
3851
3952
- name: Setup Node.js Environment
4053
uses: actions/setup-node@v4
@@ -51,6 +64,17 @@ jobs:
5164
run: |
5265
npm ci
5366
67+
- name: Test compass-web with Atlas Cloud
68+
if: ${{ !inputs.dangerously_skip_e2e_tests }}
69+
env:
70+
EVG_USER: ${{ secrets.EVERGREEN_SERVICE_USER_USERNAME }}
71+
EVG_API_KEY: ${{ secrets.EVERGREEN_SERVICE_USER_API_KEY }}
72+
COMPASS_E2E_ATLAS_CLOUD_ENVIRONMENT: ${{ inputs.publish_environment }}
73+
COMPASS_WEB_E2E_TEST_EVERGREEN_PATCH_DESCRIPTION: 'Test compass-web against Atlas ${{ inputs.publish_environment }} env before release (GHA: https://github.com/mongodb-js/compass/actions/runs/${{ github.run_id }})'
74+
COMPASS_WEB_RELEASE_COMMIT: ${{ inputs.dangerously_override_commit_hash }}
75+
run: |
76+
npm run --workspace @mongodb-js/compass-web test-e2e-atlas
77+
5478
- name: Configure AWS Credentials
5579
uses: aws-actions/configure-aws-credentials@56d6a583f00f6bad6d19d91d53a7bc3b8143d0e9 # v5.1.1
5680
with:
@@ -67,6 +91,4 @@ jobs:
6791
DOWNLOAD_CENTER_NEW_AWS_SECRET_ACCESS_KEY: '${{ env.AWS_SECRET_ACCESS_KEY }}'
6892
DOWNLOAD_CENTER_NEW_AWS_SESSION_TOKEN: '${{ env.AWS_SESSION_TOKEN }}'
6993
run: |
70-
# TODO(COMPASS-10227): trigger compass web with atlas tests for the
71-
# provided publish environment
7294
npm run --workspace @mongodb-js/compass-web upload-entrypoint

packages/compass-web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"test-cov": "nyc --compact=false --produce-source-map=false -x \"**/*.spec.*\" --reporter=lcov --reporter=text --reporter=html npm run test",
6363
"test-watch": "npm run test -- --watch",
6464
"test-ci": "npm run test-cov",
65+
"test-e2e-atlas": "node --experimental-strip-types scripts/spawn-e2e-with-atlas-cloud.mts",
6566
"reformat": "npm run eslint . -- --fix && npm run prettier -- --write .",
6667
"upload-dist": "node --experimental-strip-types scripts/release/upload-dist.mts",
6768
"upload-entrypoint": "node --experimental-strip-types scripts/release/upload-entrypoint.mts",
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
import fs from 'fs';
2+
import { spawnSync } from 'child_process';
3+
import type { SpawnSyncOptions } from 'child_process';
4+
import path from 'path';
5+
import os from 'os';
6+
import { Readable, promises } from 'stream';
7+
import type { ReadableStream as ReadableStreamWeb } from 'stream/web';
8+
9+
const {
10+
EVG_USER,
11+
EVG_API_KEY,
12+
EVG_API_SERVER = 'https://evergreen.mongodb.com/api',
13+
EVG_UI_SERVER = 'https://evergreen.mongodb.com',
14+
} = process.env;
15+
16+
const RELEASE_COMMIT =
17+
process.env.COMPASS_WEB_RELEASE_COMMIT ||
18+
spawnSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf8' }).stdout.trim();
19+
20+
if (!EVG_USER || !EVG_API_KEY) {
21+
throw new Error('Evergreen credentials missing');
22+
}
23+
24+
function mapArchNameToEvgAssetName(arch = os.arch()) {
25+
switch (arch) {
26+
case 'x64':
27+
return 'amd64';
28+
default:
29+
return arch;
30+
}
31+
}
32+
33+
const OS = os.type().toLowerCase();
34+
const ARCH = mapArchNameToEvgAssetName(os.arch());
35+
const CLI_DIR = path.join(os.tmpdir(), crypto.randomUUID());
36+
const EVERGREEN_CONFIG = path.join(CLI_DIR, '.evergreen.yml');
37+
38+
await fs.promises.mkdir(CLI_DIR, { recursive: true, mode: 0o700 });
39+
40+
function cleanup() {
41+
console.debug('cleaning up...');
42+
fs.rmSync(CLI_DIR, { recursive: true, force: true });
43+
}
44+
45+
process.on('beforeExit', cleanup);
46+
process.on('uncaughtExceptionMonitor', cleanup);
47+
48+
await fs.promises.writeFile(
49+
EVERGREEN_CONFIG,
50+
`
51+
user: ${EVG_USER}
52+
api_key: ${EVG_API_KEY}
53+
api_server_host: ${EVG_API_SERVER}
54+
ui_server_host: ${EVG_UI_SERVER}
55+
do_not_run_kanopy_oidc: true
56+
`.trimStart()
57+
);
58+
59+
const EVERGREEN_CLI_BIN = path.join(CLI_DIR, 'evergreen');
60+
61+
function spawnEvergreenSync(
62+
args: string[],
63+
extraOptions?: Omit<SpawnSyncOptions, 'encoding'>
64+
) {
65+
const res = spawnSync(
66+
EVERGREEN_CLI_BIN,
67+
[
68+
'--level',
69+
'error', // Only allow important logs to reduce the output noise
70+
...args,
71+
],
72+
{
73+
encoding: 'utf-8',
74+
...extraOptions,
75+
env: {
76+
...process.env,
77+
// in theory there is a `--config` flag, but it doesn't seem to work
78+
// specifically with the `user` commands, so we override the HOME to
79+
// make sure that our config is picked up from the "default" dir
80+
// correctly for all cases
81+
HOME: CLI_DIR,
82+
...extraOptions?.env,
83+
},
84+
}
85+
);
86+
if (res.error) {
87+
throw res.error;
88+
}
89+
if (res.status !== 0) {
90+
throw Object.assign(
91+
new Error('Evergreen CLI exited with a non-zero status code'),
92+
{
93+
args,
94+
code: res.status,
95+
signal: res.signal,
96+
stdout: res.stdout,
97+
stderr: res.stderr,
98+
}
99+
);
100+
}
101+
return res;
102+
}
103+
104+
console.debug(
105+
'downloading evergreen cli for %s %s to %s...',
106+
OS,
107+
ARCH,
108+
CLI_DIR
109+
);
110+
111+
await fetch(`https://evergreen.mongodb.com/clients/${OS}_${ARCH}/evergreen`, {
112+
headers: {
113+
'Api-User': EVG_USER,
114+
'Api-Key': EVG_API_KEY,
115+
},
116+
}).then((res) => {
117+
if (!res.ok || !res.body) {
118+
throw new Error(
119+
`Failed to download evergreen cli: ${res.status} (${res.statusText})`
120+
);
121+
}
122+
return promises.pipeline(
123+
Readable.fromWeb(
124+
// node `streamWeb.ReadableStream` and dom `ReadableStream` are currently
125+
// not fully compatible, but actually work together okay for our case.
126+
// When this issue goes away, typescript will highlight this as a
127+
// unnecessary assertion
128+
res.body as ReadableStreamWeb<Uint8Array<ArrayBuffer>>
129+
),
130+
fs.createWriteStream(EVERGREEN_CLI_BIN)
131+
);
132+
});
133+
134+
await fs.promises.chmod(EVERGREEN_CLI_BIN, 0o700);
135+
136+
console.debug(spawnEvergreenSync(['--version']).stdout.trimEnd());
137+
138+
console.debug(
139+
'current user: %s',
140+
spawnEvergreenSync(['client', 'user']).stdout.trimEnd()
141+
);
142+
143+
const ATLAS_CLOUD_ENV =
144+
process.env.COMPASS_E2E_ATLAS_CLOUD_ENVIRONMENT ?? 'dev';
145+
146+
const patchInfoStr = spawnEvergreenSync([
147+
'patch',
148+
'--project',
149+
'10gen-compass-main',
150+
'--variants',
151+
'test-web-sandbox-atlas-cloud',
152+
'--tasks',
153+
'test-web-sandbox-atlas-cloud',
154+
'--description',
155+
process.env.COMPASS_WEB_E2E_TEST_EVERGREEN_PATCH_DESCRIPTION ??
156+
`Test compass-web with Atlas Cloud against ${ATLAS_CLOUD_ENV} environment`,
157+
'--param',
158+
`compass_web_publish_environment=${ATLAS_CLOUD_ENV}`,
159+
'--ref',
160+
RELEASE_COMMIT,
161+
'--json',
162+
'--finalize',
163+
]).stdout;
164+
165+
const patchInfo = JSON.parse(patchInfoStr);
166+
167+
console.debug(
168+
'created evergreen patch https://evergreen.mongodb.com/version/%s',
169+
patchInfo.patch_id
170+
);
171+
172+
const startTime = Date.now();
173+
// 2 hours: this is more than our global evergreen config timeout, evergreen
174+
// will fail first and this will abort the loop, but just in case something goes
175+
// really wrong we will also have the timeout here making sure this is not
176+
// running forever
177+
const timeoutMs = 1000 * 60 * 60 * 60 * 2;
178+
179+
const intervalId = setInterval(() => {
180+
const currentPatchInfoStr = spawnEvergreenSync([
181+
'list-patches',
182+
'--id',
183+
patchInfo.patch_id,
184+
'--json',
185+
]).stdout;
186+
const currentPatchInfo = JSON.parse(currentPatchInfoStr);
187+
188+
if (currentPatchInfo.status === 'success') {
189+
console.debug('finished running the patch successfully');
190+
clearInterval(intervalId);
191+
return;
192+
}
193+
194+
if (currentPatchInfo.status === 'failed') {
195+
throw Object.assign(
196+
new Error(
197+
`Patch https://evergreen.mongodb.com/version/${currentPatchInfo.patch_id} failed`
198+
),
199+
{ patch: currentPatchInfo }
200+
);
201+
}
202+
203+
if (Date.now() - startTime >= timeoutMs) {
204+
throw Object.assign(
205+
new Error(
206+
`Patch https://evergreen.mongodb.com/version/${currentPatchInfo.patch_id} failed due to the timeout`
207+
),
208+
{ patch: currentPatchInfo }
209+
);
210+
}
211+
212+
console.debug('current patch status: %s', currentPatchInfo.status);
213+
}, 60_000); // no need to check too often

0 commit comments

Comments
 (0)