Skip to content

Commit f2734d7

Browse files
authored
chore(build): add local release command MONGOSH-530 (#667)
1 parent 460d665 commit f2734d7

27 files changed

+1307
-22
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"compile-exec": "npm run evergreen-release compile",
4949
"compile-all": "npm run compile-compass && npm run compile-exec",
5050
"evergreen-release": "cd packages/build && npm run evergreen-release --",
51+
"release": "cd packages/build && npm run release --",
5152
"report-missing-help": "lerna run --stream --scope @mongosh/shell-api report-missing-help",
5253
"report-supported-api": "lerna run --stream --scope @mongosh/shell-api report-supported-api",
5354
"report-coverage": "nyc report --reporter=text --reporter=html && nyc check-coverage --lines=95",

packages/build/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
"test-ci": "mocha -r \"../../scripts/import-expansions.js\" --timeout 30000 -r ts-node/register \"./src/**/*.spec.ts\"",
2020
"lint": "eslint \"**/*.{js,ts,tsx}\"",
2121
"check": "npm run lint",
22-
"evergreen-release": "ts-node -r ../../scripts/import-expansions.js src/index.ts"
22+
"evergreen-release": "ts-node -r ../../scripts/import-expansions.js src/index.ts",
23+
"release": "ts-node src/index.ts trigger-release"
2324
},
2425
"license": "Apache-2.0",
2526
"publishConfig": {

packages/build/src/evergreen.spec.ts renamed to packages/build/src/evergreen/artifacts.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { promises as fs } from 'fs';
33
import path from 'path';
44
import rimraf from 'rimraf';
55
import { promisify } from 'util';
6-
import { downloadArtifactFromEvergreen } from './evergreen';
6+
import { downloadArtifactFromEvergreen } from './artifacts';
77

8-
describe('evergreen', () => {
8+
describe('evergreen artifacts', () => {
99
describe('downloadArtifactFromEvergreen', () => {
1010
let tmpDir: string;
1111

File renamed without changes.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
export * from './artifacts';
3+
export * from './rest-api';
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { expect } from 'chai';
2+
import { promises as fs } from 'fs';
3+
import os from 'os';
4+
import path from 'path';
5+
import sinon from 'sinon';
6+
import YAML from 'yaml';
7+
import { EvergreenApi, EvergreenTask } from './rest-api';
8+
9+
describe('evergreen rest-api', () => {
10+
describe('from user configuration', () => {
11+
const configData = {
12+
api_server_host: 'host',
13+
user: 'user',
14+
api_key: 'key'
15+
};
16+
const writeEvergreenConfiguration = async(content: string): Promise<string> => {
17+
const configFile = path.join(os.tmpdir(), `evergreen-${new Date().getTime()}-${Math.random()}.yaml`);
18+
await fs.writeFile(configFile, content, { encoding: 'utf-8' });
19+
return configFile;
20+
};
21+
22+
it('parses a configuration file correctly', async() => {
23+
const configFile = await writeEvergreenConfiguration(YAML.stringify(configData));
24+
const api = await EvergreenApi.fromUserConfiguration(configFile);
25+
expect(api.apiBasepath).to.equal('host');
26+
expect(api.apiUser).to.equal('user');
27+
expect(api.apiKey).to.equal('key');
28+
});
29+
30+
it('throws an error when the configuration file does not exist', async() => {
31+
try {
32+
await EvergreenApi.fromUserConfiguration('kasldjflasjk dfalsd jfsdfk');
33+
} catch (e) {
34+
expect(e.message).to.contain('Could not find local evergreen configuration');
35+
return;
36+
}
37+
expect.fail('Expected error');
38+
});
39+
40+
['api_server_host', 'user', 'api_key'].forEach(key => {
41+
it(`throws an error if ${key} is missing`, async() => {
42+
const data: Record<string, string> = {
43+
...configData
44+
};
45+
delete data[key];
46+
const configFile = await writeEvergreenConfiguration(YAML.stringify(data));
47+
try {
48+
await EvergreenApi.fromUserConfiguration(configFile);
49+
} catch (e) {
50+
expect(e.message).to.contain(key);
51+
}
52+
});
53+
});
54+
});
55+
56+
describe('getTasks', () => {
57+
let fetch: sinon.SinonStub;
58+
let api: EvergreenApi;
59+
60+
beforeEach(() => {
61+
fetch = sinon.stub();
62+
api = new EvergreenApi(
63+
'//basePath/api', 'user', 'key', fetch as any
64+
);
65+
});
66+
67+
it('executes a proper GET', async() => {
68+
const task: EvergreenTask = {
69+
task_id: 'task_id',
70+
version_id: 'version',
71+
status: 'success',
72+
display_name: 'Task',
73+
build_variant: 'variant'
74+
};
75+
fetch.resolves({
76+
status: 200,
77+
json: sinon.stub().resolves([task])
78+
});
79+
80+
const tasks = await api.getTasks('mongosh', 'sha');
81+
expect(tasks).to.deep.equal([task]);
82+
expect(fetch).to.have.been.calledWith(
83+
'//basePath/api/rest/v2/projects/mongosh/revisions/sha/tasks',
84+
{
85+
headers: {
86+
'Api-User': 'user',
87+
'Api-Key': 'key'
88+
}
89+
}
90+
);
91+
});
92+
93+
it('fails if there is a non-200 response code', async() => {
94+
fetch.resolves({
95+
status: 404,
96+
text: sinon.stub().resolves('ERR: Not found')
97+
});
98+
99+
try {
100+
await api.getTasks('mongosh', 'sha');
101+
} catch (e) {
102+
expect(e.message).to.equal('Unexpected response status: 404 - ERR: Not found');
103+
return;
104+
}
105+
expect.fail('Expected error');
106+
});
107+
});
108+
});
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/* eslint-disable camelcase */
2+
import { promises as fs, constants } from 'fs';
3+
import { default as fetchFn } from 'node-fetch';
4+
import os from 'os';
5+
import path from 'path';
6+
import YAML from 'yaml';
7+
8+
export type EvergreenTaskStatus = 'undispatched' | 'scheduled' | 'started' | 'success' | 'failed' | 'aborted';
9+
10+
// For full specification of all fields see: https://github.com/evergreen-ci/evergreen/wiki/REST-V2-Usage#objects
11+
export interface EvergreenTask {
12+
task_id: string;
13+
version_id: string;
14+
display_name: string;
15+
build_variant: string;
16+
status: EvergreenTaskStatus;
17+
}
18+
19+
export class EvergreenApi {
20+
constructor(
21+
public readonly apiBasepath: string,
22+
public readonly apiUser: string,
23+
public readonly apiKey: string,
24+
private readonly fetch: typeof fetchFn = fetchFn
25+
) {}
26+
27+
public static async fromUserConfiguration(
28+
pathToConfiguration = path.join(os.homedir(), '.evergreen.yml')
29+
): Promise<EvergreenApi> {
30+
try {
31+
await fs.access(pathToConfiguration, constants.R_OK);
32+
} catch {
33+
throw new Error(`Could not find local evergreen configuration: ${pathToConfiguration}. Ensure it exists and can be read.`);
34+
}
35+
36+
const configuration = YAML.parse(await fs.readFile(pathToConfiguration, { encoding: 'utf-8' }));
37+
['api_server_host', 'user', 'api_key'].forEach(key => {
38+
if (typeof configuration[key] !== 'string') {
39+
throw new Error(`Evergreen configuration ${pathToConfiguration} misses required key ${key}`);
40+
}
41+
});
42+
return new EvergreenApi(
43+
configuration.api_server_host,
44+
configuration.user,
45+
configuration.api_key,
46+
);
47+
}
48+
49+
public async getTasks(
50+
project: string,
51+
commitSha: string
52+
): Promise<EvergreenTask[]> {
53+
return await this.apiGET<EvergreenTask[]>(
54+
`/projects/${project}/revisions/${commitSha}/tasks`
55+
);
56+
}
57+
58+
private async apiGET<T>(path: string): Promise<T> {
59+
const response = await this.fetch(
60+
`${this.apiBasepath}/rest/v2${path}`,
61+
{ headers: this.getApiHeaders() }
62+
);
63+
64+
if (response.status >= 300) {
65+
throw new Error(`Unexpected response status: ${response.status} - ${await response.text()}`);
66+
}
67+
return await response.json();
68+
}
69+
70+
private getApiHeaders(): Record<string, string> {
71+
return {
72+
'Api-User': this.apiUser,
73+
'Api-Key': this.apiKey,
74+
};
75+
}
76+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
export * from './spawn-sync';
3+
export * from './user-input';
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)