Skip to content

Add auto-creation of PR for upgrade of MongoDB / Node.js CI env #7186

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

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
dc9f90d
Merge commit 'ccb045b68c5b4d983a90fa125513fc476e4e2387'
mtrezza Nov 19, 2020
4d72525
Merge remote-tracking branch 'upstream/master'
mtrezza Dec 4, 2020
65a6bdb
Merge remote-tracking branch 'upstream/master'
mtrezza Dec 5, 2020
50274b8
Merge remote-tracking branch 'upstream/master'
mtrezza Dec 8, 2020
3b337cd
Merge remote-tracking branch 'upstream/master'
mtrezza Dec 15, 2020
073f0fc
Merge remote-tracking branch 'upstream/master'
mtrezza Dec 18, 2020
2ee5907
Merge remote-tracking branch 'upstream/master'
mtrezza Dec 19, 2020
241a1d8
Merge remote-tracking branch 'upstream/master'
mtrezza Dec 26, 2020
4f097ce
Merge remote-tracking branch 'upstream/master'
mtrezza Jan 14, 2021
8f3ea1c
Merge remote-tracking branch 'upstream/master'
mtrezza Jan 23, 2021
4743cbc
Merge remote-tracking branch 'upstream/master'
mtrezza Jan 28, 2021
59de429
Merge remote-tracking branch 'upstream/master'
mtrezza Feb 1, 2021
ed2944f
Merge remote-tracking branch 'upstream/master'
mtrezza Feb 2, 2021
3ff82c8
Merge commit '7f47b0427ea56214d9b0199f0fcfa4af38794e02'
mtrezza Feb 9, 2021
19d7556
Merge remote-tracking branch 'upstream/master'
mtrezza Feb 12, 2021
26aa32d
moved lint check into its own CI task
mtrezza Feb 12, 2021
2608a84
added PR creation
mtrezza Feb 12, 2021
bd5bf49
added PR base branch
mtrezza Feb 12, 2021
03e24c0
refactored version component range operator
mtrezza Feb 12, 2021
db226c3
added YAML update
mtrezza Feb 12, 2021
df42c67
added conditoinal PR creation
mtrezza Feb 12, 2021
0603673
fix PR creation
mtrezza Feb 12, 2021
4ece4b5
fix PR creation
mtrezza Feb 12, 2021
662c597
fix PR creation
mtrezza Feb 12, 2021
3fdf715
fix PR creation
mtrezza Feb 12, 2021
0d83e0b
fix PR creation
mtrezza Feb 12, 2021
b19db81
fix PR creation
mtrezza Feb 12, 2021
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
43 changes: 36 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,41 @@ jobs:
run: npm ci
- name: CI Self-Check
run: npm run ci:check
- name: Create PR to upgrade CI environments if needed
if: ${{ always() }}
uses: peter-evans/create-pull-request@v3
with:
title: Upgrade CI environments
body: This is an automated PR to upgrade package versions in CI environments.
commit-message: Upgrading versions of MongoDB / Node.js
branch: upgrade-ci-environments
base: master
check-lint:
name: Lint
timeout-minutes: 30
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.NODE_VERSION }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Cache Node.js modules
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-
- name: Install dependencies
run: npm ci
- run: npm run lint
check-mongo:
strategy:
matrix:
include:
- name: Mongo 4.4, ReplicaSet, WiredTiger
MONGODB_VERSION: 4.4.4
MONGODB_VERSION: 4.4.3
MONGODB_TOPOLOGY: replicaset
MONGODB_STORAGE_ENGINE: wiredTiger
NODE_VERSION: 14.15.5
Expand All @@ -58,27 +87,28 @@ jobs:
NODE_VERSION: 14.15.5
- name: Redis Cache
PARSE_SERVER_TEST_CACHE: redis
MONGODB_VERSION: 4.4.4
MONGODB_VERSION: 4.4.3
MONGODB_TOPOLOGY: standalone
MONGODB_STORAGE_ENGINE: wiredTiger
NODE_VERSION: 14.15.5
- name: Node 10
MONGODB_VERSION: 4.4.4
MONGODB_VERSION: 4.4.3
MONGODB_TOPOLOGY: standalone
MONGODB_STORAGE_ENGINE: wiredTiger
NODE_VERSION: 10.23.3
- name: Node 12
MONGODB_VERSION: 4.4.4
MONGODB_VERSION: 4.4.3
MONGODB_TOPOLOGY: standalone
MONGODB_STORAGE_ENGINE: wiredTiger
NODE_VERSION: 12.20.2
- name: Node 15
MONGODB_VERSION: 4.4.4
MONGODB_VERSION: 4.4.3
MONGODB_TOPOLOGY: standalone
MONGODB_STORAGE_ENGINE: wiredTiger
NODE_VERSION: 15.8.0
name: ${{ matrix.name }}
timeout-minutes: 30
needs: check-ci
runs-on: ubuntu-18.04
services:
redis:
Expand Down Expand Up @@ -106,8 +136,6 @@ jobs:
${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-
- name: Install dependencies
run: npm ci
- if: ${{ matrix.name == 'Mongo 3.6.21' }}
run: npm run lint
- run: npm run pretest
- run: npm run coverage
env:
Expand All @@ -129,6 +157,7 @@ jobs:
POSTGRES_IMAGE: postgis/postgis:13-3.1
name: ${{ matrix.name }}
timeout-minutes: 30
needs: check-ci
runs-on: ubuntu-18.04
services:
redis:
Expand Down
14 changes: 11 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
"node-fetch": "2.6.1",
"nyc": "15.1.0",
"prettier": "2.0.5",
"yaml": "1.10.0"
"yaml": "2.0.0-3"
},
"scripts": {
"ci:check": "node ./resources/ci/ciCheck.js",
Expand Down
102 changes: 89 additions & 13 deletions resources/ci/CiVersionCheck.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const core = require('@actions/core');
const semver = require('semver');
const yaml = require('yaml');
const lodash = require('lodash');
const fs = require('fs').promises;

/**
Expand Down Expand Up @@ -43,6 +44,8 @@ class CiVersionCheck {
* test against 2.0.0.
* If the latest version component is `major` then the check would
* fail and recommend an upgrade to version 2.0.0.
* @param {Boolean} [config.updateYaml=false] Is true if the YAML file
* should be updated and package versions should be bumped.
*/
constructor(config) {
const {
Expand All @@ -54,6 +57,7 @@ class CiVersionCheck {
releasedVersions,
ignoreReleasedVersions = [],
latestComponent = CiVersionCheck.versionComponents.patch,
updateYaml = false,
} = config;

// Ensure required params are set
Expand All @@ -64,6 +68,9 @@ class CiVersionCheck {
ciEnvironmentsKeyPath,
ciVersionKey,
releasedVersions,
ignoreReleasedVersions,
latestComponent,
updateYaml,
].includes(undefined)) {
throw 'invalid configuration';
}
Expand All @@ -80,6 +87,7 @@ class CiVersionCheck {
this.releasedVersions = releasedVersions;
this.ignoreReleasedVersions = ignoreReleasedVersions;
this.latestComponent = latestComponent;
this.updateYaml = updateYaml;
}

/**
Expand All @@ -101,9 +109,10 @@ class CiVersionCheck {
// Get CI workflow
const ciYaml = await fs.readFile(this.yamlFilePath, 'utf-8');
const ci = yaml.parse(ciYaml);
this.ci = ci;

// Extract package versions
let versions = this.ciEnvironmentsKeyPath.split('.').reduce((o,k) => o !== undefined ? o[k] : undefined, ci);
let versions = this._getKeyAtPath(ci, this.ciEnvironmentsKeyPath);
versions = Object.entries(versions)
.map(entry => entry[1])
.filter(entry => entry[this.ciVersionKey]);
Expand All @@ -114,6 +123,16 @@ class CiVersionCheck {
}
}

/**
* Returns the value at a given key path.
* @param {Object} obj The object to traverse.
* @param {String} keyPath The path to the key to return.
* @returns {Any} The value at the given key path.
*/
_getKeyAtPath(obj, keyPath) {
return keyPath.split('.').reduce((o,k) => o !== undefined ? o[k] : undefined, obj);
}

/**
* Returns the package versions which are missing in the CI environment.
* @param {Array<String>} releasedVersions The released versions; need to
Expand Down Expand Up @@ -144,11 +163,7 @@ class CiVersionCheck {
// ];

// Determine operator for range comparison
const operator = versionComponent == CiVersionCheck.versionComponents.major
? '>='
: versionComponent == CiVersionCheck.versionComponents.minor
? '^'
: '~'
const operator = this._getOperatorForVersionComponent(versionComponent);

// Get all untested versions
const untestedVersions = releasedVersions.reduce((m, v) => {
Expand Down Expand Up @@ -187,11 +202,7 @@ class CiVersionCheck {
*/
getNewerVersion(versions, version, versionComponent) {
// Determine operator for range comparison
const operator = versionComponent == CiVersionCheck.versionComponents.major
? '>='
: versionComponent == CiVersionCheck.versionComponents.minor
? '^'
: '~'
const operator = this._getOperatorForVersionComponent(versionComponent);
const latest = semver.maxSatisfying(versions, `${operator}${version}`);
return semver.gt(latest, version) ? latest : undefined;
}
Expand All @@ -209,6 +220,57 @@ class CiVersionCheck {
}
}

/**
* Returns the semver range operator that relates to the a version component.
* For example, the operator for the `patch` version component defines a
* range up to the highest patch version.
* @param {String} component The version component (`patch`, `minor`,
* `major`).
* @returns {String} The semver operator.
*/
_getOperatorForVersionComponent(component) {
// Determine operator for range comparison
return component == CiVersionCheck.versionComponents.major
? '>='
: component == CiVersionCheck.versionComponents.minor
? '^'
: '~'
}

/**
* Updates a key in the CI YAML file with a given value.
* @param {Array<Object>} update The key to update.
* @param {String} update.old The old value to update.
* @param {String} update.new The new value to set.
*/
async _updateYaml(update) {
if (this.ci === undefined) {
throw 'YAML file has not been read.';
}

// Get environments
const newCi = lodash.cloneDeep(this.ci);
const envs = this._getKeyAtPath(newCi, this.ciEnvironmentsKeyPath);

// Update keys
for (const key of Object.keys(update)) {
for (const oldValue of Object.keys(update[key])) {
const newValue = update[key][oldValue];

// Update value
for (const env of envs) {
if (env[key] == oldValue) {
env[key] = newValue;
}
}
}
}

// Write to file
const newYaml = yaml.stringify(newCi);
await fs.writeFile(this.yamlFilePath, newYaml);
}

/**
* Runs the check.
*/
Expand All @@ -233,6 +295,9 @@ class CiVersionCheck {
// Is true if any of the checks failed
let failed = false;

// The keys that should be updated
const keyUpdatesNeeded = {};

// Check whether each tested version is the latest patch
for (const test of tests) {
const version = test[this.ciVersionKey];
Expand All @@ -249,6 +314,7 @@ class CiVersionCheck {
if (newer) {
console.log(`❌ CI environment '${test.name}' uses an old ${this.packageName} ${this.latestComponent} version ${version} instead of ${newer}.`);
failed = true;
keyUpdatesNeeded[this.ciVersionKey] = Object.assign(keyUpdatesNeeded[this.ciVersionKey] || {}, { [version]: newer });
} else {
console.log(`✅ CI environment '${test.name}' uses the latest ${this.packageName} ${this.latestComponent} version ${version}.`);
}
Expand All @@ -268,9 +334,19 @@ class CiVersionCheck {
core.setFailed(
`CI environments are not up-to-date with the latest ${this.packageName} versions.` +
`\n\nCheck the error messages above and update the ${this.packageName} versions in the CI YAML ` +
`file.\n\nℹ️ Additionally, there may be versions of ${this.packageName} that have reached their official end-of-life ` +
`support date and should be removed from the CI, see ${this.packageSupportUrl}.`
`file. Additionally, check for versions of ${this.packageName} that have reached their official end-of-life ` +
`support date and may be removed from the CI, see ${this.packageSupportUrl}.`
);

// If packages in YAML file should be updated
if (this.updateYaml && Object.keys(keyUpdatesNeeded).length > 0) {
try {
this._updateYaml(keyUpdatesNeeded);
} catch (e) {
console.log(`Failed to update ${this.packageName} versions in YAML file with error: ${e}`);
}
console.log(`\n🚀 Updated YAML file to use newer versions of ${this.packageName}. Check the Pull Request list for a PR.`);
}
}

} catch (e) {
Expand Down
2 changes: 2 additions & 0 deletions resources/ci/ciCheck.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ async function checkMongoDbVersions() {
ciVersionKey: 'MONGODB_VERSION',
releasedVersions,
latestComponent: CiVersionCheck.versionComponents.path,
updateYaml: true,
ignoreReleasedVersions: [
'<3.6.0', // These versions have reached their MongoDB end-of-life support date
'~3.7.0', // This is a development release according to MongoDB support
Expand All @@ -58,6 +59,7 @@ async function checkNodeVersions() {
ciVersionKey: 'NODE_VERSION',
releasedVersions,
latestComponent: CiVersionCheck.versionComponents.minor,
updateYaml: true,
ignoreReleasedVersions: [
'<10.0.0', // These versions have reached their end-of-life support date
'>=11.0.0 <12.0.0', // These versions have reached their end-of-life support date
Expand Down