Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 26 additions & 4 deletions .github/workflows/publish-compass-web.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
workflow_dispatch:
inputs:
publish_environment:
descript: 'Atlas Cloud environment to publish compass-web to'
description: 'Atlas Cloud environment to publish compass-web to'
type: choice
default: 'qa'
options:
Expand All @@ -15,9 +15,13 @@ on:
- prod
required: true
dangerously_override_commit_hash:
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!'
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!'
default: ''
required: false
dangerously_skip_e2e_tests:
description: 'Skips the e2e tests and starts the publishing process immediately. ONLY USE IF YOU KNOW WHAT YOU ARE DOING!'
type: boolean
default: false
dry_run:
description: 'Run the publish process but do not upload the file. Useful for testing that the script is functioning correctly'
type: boolean
Expand All @@ -35,6 +39,15 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
# Evergreen CLI needs the full history
ref: ${{ github.head_ref }}
fetch-depth: '0'

- name: Setup git user
run: |
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"

- name: Setup Node.js Environment
uses: actions/setup-node@v4
Expand All @@ -51,6 +64,17 @@ jobs:
run: |
npm ci

- name: Test compass-web with Atlas Cloud
if: ${{ !inputs.dangerously_skip_e2e_tests }}
env:
EVG_USER: ${{ secrets.EVERGREEN_SERVICE_USER_USERNAME }}
EVG_API_KEY: ${{ secrets.EVERGREEN_SERVICE_USER_API_KEY }}
COMPASS_E2E_ATLAS_CLOUD_ENVIRONMENT: ${{ inputs.publish_environment }}
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 }})'
COMPASS_WEB_RELEASE_COMMIT: ${{ inputs.dangerously_override_commit_hash }}
run: |
npm run --workspace @mongodb-js/compass-web test-e2e-atlas

- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@56d6a583f00f6bad6d19d91d53a7bc3b8143d0e9 # v5.1.1
with:
Expand All @@ -67,6 +91,4 @@ jobs:
DOWNLOAD_CENTER_NEW_AWS_SECRET_ACCESS_KEY: '${{ env.AWS_SECRET_ACCESS_KEY }}'
DOWNLOAD_CENTER_NEW_AWS_SESSION_TOKEN: '${{ env.AWS_SESSION_TOKEN }}'
run: |
# TODO(COMPASS-10227): trigger compass web with atlas tests for the
# provided publish environment
npm run --workspace @mongodb-js/compass-web upload-entrypoint
1 change: 1 addition & 0 deletions packages/compass-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"test-cov": "nyc --compact=false --produce-source-map=false -x \"**/*.spec.*\" --reporter=lcov --reporter=text --reporter=html npm run test",
"test-watch": "npm run test -- --watch",
"test-ci": "npm run test-cov",
"test-e2e-atlas": "node --experimental-strip-types scripts/spawn-e2e-with-atlas-cloud.mts",
"reformat": "npm run eslint . -- --fix && npm run prettier -- --write .",
"upload-dist": "node --experimental-strip-types scripts/release/upload-dist.mts",
"upload-entrypoint": "node --experimental-strip-types scripts/release/upload-entrypoint.mts",
Expand Down
213 changes: 213 additions & 0 deletions packages/compass-web/scripts/spawn-e2e-with-atlas-cloud.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import fs from 'fs';
import { spawnSync } from 'child_process';
import type { SpawnSyncOptions } from 'child_process';
import path from 'path';
import os from 'os';
import { Readable, promises } from 'stream';
import type { ReadableStream as ReadableStreamWeb } from 'stream/web';

const {
EVG_USER,
EVG_API_KEY,
EVG_API_SERVER = 'https://evergreen.mongodb.com/api',
EVG_UI_SERVER = 'https://evergreen.mongodb.com',
} = process.env;

const RELEASE_COMMIT =
process.env.COMPASS_WEB_RELEASE_COMMIT ||
spawnSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf8' }).stdout.trim();

if (!EVG_USER || !EVG_API_KEY) {
throw new Error('Evergreen credentials missing');
}

function mapArchNameToEvgAssetName(arch = os.arch()) {
switch (arch) {
case 'x64':
return 'amd64';
default:
return arch;
}
}

const OS = os.type().toLowerCase();
const ARCH = mapArchNameToEvgAssetName(os.arch());
const CLI_DIR = path.join(os.tmpdir(), crypto.randomUUID());
const EVERGREEN_CONFIG = path.join(CLI_DIR, '.evergreen.yml');

await fs.promises.mkdir(CLI_DIR, { recursive: true, mode: 0o700 });

function cleanup() {
console.debug('cleaning up...');
fs.rmSync(CLI_DIR, { recursive: true, force: true });
}

process.on('beforeExit', cleanup);
process.on('uncaughtExceptionMonitor', cleanup);

await fs.promises.writeFile(
EVERGREEN_CONFIG,
`
user: ${EVG_USER}
api_key: ${EVG_API_KEY}
api_server_host: ${EVG_API_SERVER}
ui_server_host: ${EVG_UI_SERVER}
do_not_run_kanopy_oidc: true
`.trimStart()
);

const EVERGREEN_CLI_BIN = path.join(CLI_DIR, 'evergreen');

function spawnEvergreenSync(
args: string[],
extraOptions?: Omit<SpawnSyncOptions, 'encoding'>
) {
const res = spawnSync(
EVERGREEN_CLI_BIN,
[
'--level',
'error', // Only allow important logs to reduce the output noise
...args,
],
{
encoding: 'utf-8',
...extraOptions,
env: {
...process.env,
// in theory there is a `--config` flag, but it doesn't seem to work
// specifically with the `user` commands, so we override the HOME to
// make sure that our config is picked up from the "default" dir
// correctly for all cases
HOME: CLI_DIR,
...extraOptions?.env,
},
}
);
if (res.error) {
throw res.error;
}
if (res.status !== 0) {
throw Object.assign(
new Error('Evergreen CLI exited with a non-zero status code'),
{
args,
code: res.status,
signal: res.signal,
stdout: res.stdout,
stderr: res.stderr,
}
);
}
return res;
}

console.debug(
'downloading evergreen cli for %s %s to %s...',
OS,
ARCH,
CLI_DIR
);

await fetch(`https://evergreen.mongodb.com/clients/${OS}_${ARCH}/evergreen`, {
headers: {
'Api-User': EVG_USER,
'Api-Key': EVG_API_KEY,
},
}).then((res) => {
if (!res.ok || !res.body) {
throw new Error(
`Failed to download evergreen cli: ${res.status} (${res.statusText})`
);
}
return promises.pipeline(
Readable.fromWeb(
// node `streamWeb.ReadableStream` and dom `ReadableStream` are currently
// not fully compatible, but actually work together okay for our case.
// When this issue goes away, typescript will highlight this as a
// unnecessary assertion
res.body as ReadableStreamWeb<Uint8Array<ArrayBuffer>>
),
fs.createWriteStream(EVERGREEN_CLI_BIN)
);
});

await fs.promises.chmod(EVERGREEN_CLI_BIN, 0o700);

console.debug(spawnEvergreenSync(['--version']).stdout.trimEnd());

console.debug(
'current user: %s',
spawnEvergreenSync(['client', 'user']).stdout.trimEnd()
);

const ATLAS_CLOUD_ENV =
process.env.COMPASS_E2E_ATLAS_CLOUD_ENVIRONMENT ?? 'dev';

const patchInfoStr = spawnEvergreenSync([
'patch',
'--project',
'10gen-compass-main',
'--variants',
'test-web-sandbox-atlas-cloud',
'--tasks',
'test-web-sandbox-atlas-cloud',
'--description',
process.env.COMPASS_WEB_E2E_TEST_EVERGREEN_PATCH_DESCRIPTION ??
`Test compass-web with Atlas Cloud against ${ATLAS_CLOUD_ENV} environment`,
'--param',
`compass_web_publish_environment=${ATLAS_CLOUD_ENV}`,
'--ref',
RELEASE_COMMIT,
'--json',
'--finalize',
]).stdout;

const patchInfo = JSON.parse(patchInfoStr);

console.debug(
'created evergreen patch https://evergreen.mongodb.com/version/%s',
patchInfo.patch_id
);

const startTime = Date.now();
// 2 hours: this is more than our global evergreen config timeout, evergreen
// will fail first and this will abort the loop, but just in case something goes
// really wrong we will also have the timeout here making sure this is not
// running forever
const timeoutMs = 1000 * 60 * 60 * 60 * 2;

const intervalId = setInterval(() => {
const currentPatchInfoStr = spawnEvergreenSync([
'list-patches',
'--id',
patchInfo.patch_id,
'--json',
]).stdout;
const currentPatchInfo = JSON.parse(currentPatchInfoStr);

if (currentPatchInfo.status === 'success') {
console.debug('finished running the patch successfully');
clearInterval(intervalId);
return;
}

if (currentPatchInfo.status === 'failed') {
throw Object.assign(
new Error(
`Patch https://evergreen.mongodb.com/version/${currentPatchInfo.patch_id} failed`
),
{ patch: currentPatchInfo }
);
}

if (Date.now() - startTime >= timeoutMs) {
throw Object.assign(
new Error(
`Patch https://evergreen.mongodb.com/version/${currentPatchInfo.patch_id} failed due to the timeout`
),
{ patch: currentPatchInfo }
);
}

console.debug('current patch status: %s', currentPatchInfo.status);
}, 60_000); // no need to check too often
Loading