The commit range that is associated with a CI build is used for a couple of things (mostly related to payload-size tracking): - Determine whether a size change was caused by application code or dependencies (or both). - Add the messages of the commits associated with the build (and thus the payload-size change). NOTE: The commit range is only used on push builds. Previously, the commit range was computed based on the `CIRCLE_COMPARE_URL` environment variable. With [CircleCI Pipelines][1] enabled, `CIRCLE_COMPARE_URL` is no longer available and the commit range cannot be reliably detected. This commit switches `CI_COMMIT_RANGE` to only include the last commit. This can be less accurate in some rare cases, but is true in the majority of cases (on push builds). Additionally, it stores the CircleCI build URL in the database along with the payload data, so the relevant info can be retrieved when needed. [1]: https://circleci.com/docs/2.0/build-processing PR Close #32537
167 lines
5.6 KiB
JavaScript
167 lines
5.6 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* **Usage:**
|
|
* ```
|
|
* node get-commit-range <build-number> [<compare-url> [<circle-token>]]
|
|
* ```
|
|
*
|
|
* Returns the commit range, either extracting it from `compare-url` (if defined), which is of the
|
|
* format of the `CIRCLE_COMPARE_URL` environment variable, or by retrieving the equivalent of
|
|
* `CIRCLE_COMPARE_URL` for jobs that are part of a rerun workflow and extracting it from there.
|
|
*
|
|
* > !!! WARNING !!!
|
|
* > !!
|
|
* > !! When [CircleCI Pipelines](https://circleci.com/docs/2.0/build-processing) is enabled, the
|
|
* > !! `CIRCLE_COMPARE_URL` environment variable is not available at all and this script does not
|
|
* > !! work.
|
|
* > !!!!!!!!!!!!!!!
|
|
*
|
|
* **Context:**
|
|
* CircleCI sets the `CIRCLE_COMPARE_URL` environment variable (from which we can extract the commit
|
|
* range) on push builds (a.k.a. non-PR, non-scheduled builds). Yet, when a workflow is rerun
|
|
* (either from the beginning or from failed jobs) - e.g. when a job flakes - CircleCI does not set
|
|
* the `CIRCLE_COMPARE_URL`.
|
|
*
|
|
* **Implementation details:**
|
|
* This script relies on the fact that all rerun workflows share the same CircleCI workspace and the
|
|
* (undocumented) fact that the workspace ID happens to be the same as the workflow ID that first
|
|
* created it.
|
|
*
|
|
* For example, for a job on push build workflows, the CircleCI API will return data that look like:
|
|
* ```js
|
|
* {
|
|
* compare: 'THE_COMPARE_URL_WE_ARE_LOOKING_FOR',
|
|
* //...
|
|
* previous: {
|
|
* // ...
|
|
* build_num: 12345,
|
|
* },
|
|
* //...
|
|
* workflows: {
|
|
* //...
|
|
* workflow_id: 'SOME_ID_A',
|
|
* workspace_id: 'SOME_ID_A', // Same as `workflow_id`.
|
|
* }
|
|
* }
|
|
* ```
|
|
*
|
|
* If the workflow is rerun, the data for jobs on the new workflow will look like:
|
|
* ```js
|
|
* {
|
|
* compare: null, // ¯\_(ツ)_/¯
|
|
* //...
|
|
* previous: {
|
|
* // ...
|
|
* build_num: 23456,
|
|
* },
|
|
* //...
|
|
* workflows: {
|
|
* //...
|
|
* workflow_id: 'SOME_ID_B',
|
|
* workspace_id: 'SOME_ID_A', // Different from current `workflow_id`.
|
|
* // Same as original `workflow_id`. \o/
|
|
* }
|
|
* }
|
|
* ```
|
|
*
|
|
* This script uses the `previous.build_num` (which points to the previous build number on the same
|
|
* branch) to traverse the jobs backwards, until it finds a job from the original workflow. Such a
|
|
* job (if found) should also contain the compare URL.
|
|
*
|
|
* **NOTE 1:**
|
|
* This is only useful on workflows which are created by rerunning a workflow for which
|
|
* `CIRCLE_COMPARE_URL` was defined.
|
|
*
|
|
* **NOTE 2:**
|
|
* The `circleToken` will be used for CircleCI API requests if provided, but it is not needed for
|
|
* accessing the read-only endpoints that we need (as long as the current project is FOSS and the
|
|
* corresponding setting is turned on in "Advanced Settings" in the project dashboard).
|
|
*
|
|
* ---
|
|
* Inspired by https://circleci.com/orbs/registry/orb/iynere/compare-url
|
|
* (source code: https://github.com/iynere/compare-url-orb).
|
|
*
|
|
* We are not using the `compare-url` orb for the following reasons:
|
|
* 1. (By looking at the code) it would only work if the rerun workflow is the latest workflow on
|
|
* the branch (which is not guaranteed to be true).
|
|
* 2. It is less efficient (e.g. makes unnecessary CircleCI API requests for builds on different
|
|
* branches, installs extra dependencies, persists files to the workspace (as a means of passing
|
|
* the result to the calling job), etc.).
|
|
* 3. It is slightly more complicated to setup and consume than our own script.
|
|
* 4. Its implementation is more complicated than needed for our usecase (e.g. handles different git
|
|
* providers, handles newly created branches, etc.).
|
|
*/
|
|
|
|
// Imports
|
|
const {get: httpsGet} = require('https');
|
|
|
|
// Constants
|
|
const API_URL_BASE = 'https://circleci.com/api/v1.1/project/github/angular/angular';
|
|
const COMPARE_URL_RE = /^.*\/([0-9a-f]+\.\.\.[0-9a-f]+)$/i;
|
|
|
|
// Run
|
|
_main(process.argv.slice(2));
|
|
|
|
// Helpers
|
|
async function _main([buildNumber, compareUrl = '', circleToken = '']) {
|
|
try {
|
|
if (!buildNumber || isNaN(buildNumber)) {
|
|
throw new Error(
|
|
'Missing or invalid arguments.\n' +
|
|
'Expected: buildNumber (number), compareUrl? (string), circleToken? (string)');
|
|
}
|
|
|
|
if (!compareUrl) {
|
|
compareUrl = await getCompareUrl(buildNumber, circleToken);
|
|
}
|
|
|
|
const commitRangeMatch = COMPARE_URL_RE.exec(compareUrl)
|
|
const commitRange = commitRangeMatch ? commitRangeMatch[1] : '';
|
|
|
|
console.log(commitRange);
|
|
} catch (err) {
|
|
console.error(err);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
function getBuildInfo(buildNumber, circleToken) {
|
|
console.error(`BUILD ${buildNumber}`);
|
|
const url = `${API_URL_BASE}/${buildNumber}?circle-token=${circleToken}`;
|
|
return getJson(url);
|
|
}
|
|
|
|
async function getCompareUrl(buildNumber, circleToken) {
|
|
let info = await getBuildInfo(buildNumber, circleToken);
|
|
const targetWorkflowId = info.workflows.workspace_id;
|
|
|
|
while (info.workflows.workflow_id !== targetWorkflowId) {
|
|
info = await getBuildInfo(info.previous.build_num, circleToken);
|
|
}
|
|
|
|
return info.compare || '';
|
|
}
|
|
|
|
function getJson(url) {
|
|
return new Promise((resolve, reject) => {
|
|
const opts = {headers: {Accept: 'application/json'}};
|
|
const onResponse = res => {
|
|
const statusCode = res.statusCode || -1;
|
|
const isSuccess = (200 <= statusCode) && (statusCode < 400);
|
|
let responseText = '';
|
|
|
|
res.
|
|
on('error', reject).
|
|
on('data', d => responseText += d).
|
|
on('end', () => isSuccess ?
|
|
resolve(JSON.parse(responseText)) :
|
|
reject(`Error getting '${url}' (status ${statusCode}):\n${responseText}`));
|
|
};
|
|
|
|
httpsGet(url, opts, onResponse).
|
|
on('error', reject).
|
|
end();
|
|
});
|
|
}
|