Skip to content
This repository was archived by the owner on Apr 2, 2020. It is now read-only.
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
*.log
config/local.json
12 changes: 12 additions & 0 deletions error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class Boom extends Error {
constructor (message, statusCode = 500) {
super(message);
this.isBoom = true;
this.statusCode = statusCode;
}
}

Boom.unauthorized = (message) => new Boom(message, 401);
Boom.accepted = (message) => new Boom(message, 202);

module.exports = Boom;
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"dependencies": {
"@parity/parity.js": "^0.2.120",
"body-parser": "^1.15.2",
"celebrate": "7.0.4",
"config": "1.29.4",
"express": "^4.13.3",
"js-sha3": "^0.5.5",
Expand All @@ -29,7 +30,6 @@
"eslint-plugin-promise": "3.6.0",
"eslint-plugin-standard": "3.0.1",
"mocha": "5.0.0",
"mock-http-server": "0.2.0",
"sinon": "4.2.2"
"mock-http-server": "0.2.0"
}
}
92 changes: 60 additions & 32 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,30 @@ const config = require('config');
const request = require('request');
const express = require('express');
const bodyParser = require('body-parser');
const { celebrate, isCelebrate } = require('celebrate');
const keccak256 = require('js-sha3').keccak_256;
const Parity = require('@parity/parity.js');

const validate = require('./validation');
const boom = require('./error');

const transport = new Parity.Api.Transport.Http(`http://localhost:${config.get('rpc.port')}`);
const api = new Parity.Api(transport);

const app = express();
app.use(bodyParser.urlencoded({extended: true}));
// validate secret for every request
app.use((req, res, next) => {
if (keccak256(req.body.secret || '') !== secretHash) {
next(boom.unauthorized('Invalid secret'));
} else {
next();
}
});
module.exports = app;

const reduceObject = (obj, prop) => ({ ...obj, [prop]: true });
const enabledTracks = config.get('enabledTracks').reduce(reduceObject, {});
const supportedPlatforms = config.get('supportedPlatforms').reduce(reduceObject, {});

const account = {
address: config.get('account.address'),
Expand Down Expand Up @@ -44,22 +55,23 @@ const tracks = {
testing: 4
};

app.post('/push-release/:tag/:commit', handleAsync(async function (req, res) {
if (keccak256(req.body.secret || '') !== secretHash) {
throw new Error('Invalid secret');
const validateRelease = celebrate({
params: {
tag: validate.tag,
commit: validate.commit
},
body: {
secret: validate.secret
}
});
app.post('/push-release/:tag/:commit', validateRelease, handleAsync(async function (req, res) {
const { commit, tag } = req.params;

console.log(`curl --data "secret=${req.body.secret}" http://localhost:${httpPort}/push-release/${tag}/${commit}`);

const isCritical = false; // TODO: should take from Git release notes for stable/beta.
const goodTag = isGoodTag(tag);

if (!goodTag) {
throw new Error(`Invalid tag: ${tag}`);
}

console.log(`Pushing commit: ${commit} (tag: ${tag}/${goodTag})`);
console.log(`Pushing commit: ${commit} (tag: ${tag})`);

const miscBody = await fetchFile(commit, '/util/src/misc.rs');
const branch = match(
Expand All @@ -71,7 +83,7 @@ app.post('/push-release/:tag/:commit', handleAsync(async function (req, res) {
console.log(`Track: ${branch} => ${track} (${tracks[track]}) [enabled: ${enabledTracks[track]}]`);

if (!enabledTracks[track]) {
throw new Error(`Track not enabled: ${track}`);
throw boom.accepted(`Track not enabled: ${track}`);
}

let ethereumMod = await fetchFile(commit, '/ethcore/src/ethereum/mod.rs');
Expand Down Expand Up @@ -109,26 +121,28 @@ app.post('/push-release/:tag/:commit', handleAsync(async function (req, res) {
res.send(`RELEASE: ${commit}/${track}/${branch}/${forkSupported}`);
}));

app.post('/push-build/:tag/:platform', handleAsync(async function (req, res) {
if (keccak256(req.body.secret || '') !== secretHash) {
throw new Error('Invalid secret');
const validateBuild = celebrate({
params: {
tag: validate.tag,
platform: validate.platform
},
body: {
secret: validate.secret,
sha3: validate.sha3,
filename: validate.filename,
commit: validate.commit
}

});
app.post('/push-build/:tag/:platform', validateBuild, handleAsync(async function (req, res) {
const { tag, platform } = req.params;
const { commit, filename, sha3 } = req.body;
console.log(`curl --data "secret=${req.body.secret}&commit=${commit}&filename=${filename}&sha3=${sha3}" http://localhost:${httpPort}/push-build/${tag}/${platform}`);

const url = `${baseUrl}/${tag}/${platform}/${filename}`;
const goodTag = isGoodTag(tag);
const goodPlatform = !!supportedPlatforms[platform];

const out = `BUILD: ${platform}/${commit} -> ${sha3}/${tag}/${filename}/${goodTag}/${goodPlatform} [${url}]`;
const out = `BUILD: ${platform}/${commit} -> ${sha3}/${tag}/${filename} [${url}]`;
console.log(out);

if (sha3 === '' || !goodTag || !goodPlatform) {
throw new Error(`Invalid sha3 (${sha3}), tag (${tag}) or platform (${platform}).`);
}

const body = await fetchFile(commit, '/util/src/misc.rs');
const branch = match(
body,
Expand All @@ -140,12 +154,9 @@ app.post('/push-build/:tag/:platform', handleAsync(async function (req, res) {
console.log(`Track: ${branch} => ${track} (${tracks[track]}) [enabled: ${!!enabledTracks[track]}]`);

if (!enabledTracks[track]) {
throw new Error(`Track not enabled: ${track}`);
throw boom.accepted(`Track not enabled: ${track}`);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not an error, right? Why using boom as such? You could just use res to send an early response.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is an error, but I want to return a specific status code in such case. Also I want to separate validation&logic from dealing with req/res directly.

}

// make sure the node is running
await getNetwork();

const registryAddress = await api.parity.registryAddress();
const reg = api.newContract(RegistrarABI, registryAddress);
const githubHintAddress = await reg.instance.getAddress.call({}, [githubHint, 'A']);
Expand All @@ -163,6 +174,27 @@ app.post('/push-build/:tag/:platform', handleAsync(async function (req, res) {
res.send(out);
}));

// make sure that the errors are added at the end
app.use((err, req, res, next) => {
if (isCelebrate(err)) {
const fields = err.details.map(x => x.path && x.path.join ? x.path.join('.') : x.path);
if (fields.indexOf('platform') !== -1 || fields.indexOf('tag') !== -1) {
res.status(202).send(err.message);
} else {
res.status(400).send(err.message);
}
return;
}

if (err.isBoom) {
res.status(err.statusCode).send(err.message);
return;
}

console.error(err);
return res.status(500).send(err.message);
});

function match (string, pattern, comment) {
const match = string.match(pattern);
if (!match) {
Expand All @@ -173,15 +205,15 @@ function match (string, pattern, comment) {
}

function handleAsync (asyncFn) {
return (req, res) => asyncFn(req, res)
return (req, res, next) => asyncFn(req, res)
.then(() => {
if (!res.headersSent) {
throw new Error('No response from handler');
}
})
.catch(err => {
console.error(err);
res.status(400).end(`Error while processing the request:\n${err.message}\n`);
next(err);
});
}

Expand Down Expand Up @@ -209,10 +241,6 @@ async function getNetwork () {
return network;
}

function isGoodTag (tag) {
return tag === 'nightly' || /^v[0-9]+\.[0-9]+\.[0-9]+$/.test(tag);
}

function sendTransaction (abi, address, method, args) {
let o = api.newContract(abi, address);
let tx = {
Expand Down
101 changes: 77 additions & 24 deletions test/server.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,42 @@ const server = new ServerMock({ host: 'localhost', port: 8545 });
describe('push-release', () => {
it('should reject invalid secret', async () => {
let res = await request(app => app
.post('/push-release/v1.9.5/123')
.post('/push-release/v1.9.5/8b749367fd5fea897cee98bd892fff1ce90f8260')
.type('form')
.send({ secret: 'xx' })
.send({ secret: 'xxx' })
);

expect(res).to.have.status(400);
expect(res.text).to.equal('Error while processing the request:\nInvalid secret\n');
expect(res).to.have.status(401);
expect(res.text).to.equal('Invalid secret');
});

it('should reject invalid tags', async () => {
it('should gently reject invalid tags', async () => {
const test = async tag => {
let res = await request(app => app
.post(`/push-release/${tag}/123`)
.post(`/push-release/${tag}/8b749367fd5fea897cee98bd892fff1ce90f8260`)
.type('form')
.send({ secret })
);

expect(res).to.have.status(400);
expect(res.text).to.equal(`Error while processing the request:\nInvalid tag: ${tag}\n`);
expect(res).to.have.status(202);
expect(res.text).to.have.string(`child "tag" fails`);
};

await test('xxx');
await test('v1.9.5-ci0');
});

it('should reject invalid commit', async () => {
let res = await request(app => app
.post('/push-release/v1.7.13/8b749367')
.type('form')
.send({ secret })
);

expect(res).to.have.status(400);
expect(res.text).to.have.string(`child "commit" fails`);
});

beforeEach(done => server.start(done));
afterEach(done => server.stop(done));

Expand Down Expand Up @@ -84,30 +95,71 @@ describe('push-build', () => {
let res = await request(app => app
.post('/push-build/v1.9.5/x86_64-unknown-linux-gnu')
.type('form')
.send({ secret: 'xx' })
.send({
secret: 'xxx',
sha3: 'a00ead491c0e47efe4abefeb27ddc6ed8d1ea4daa43683d8e349e1e7459b74ba',
commit: '8b749367fd5fea897cee98bd892fff1ce90f8260',
filename: 'parity'
})
);

expect(res).to.have.status(400);
expect(res.text).to.equal('Error while processing the request:\nInvalid secret\n');
expect(res).to.have.status(401);
expect(res.text).to.equal('Invalid secret');
});

it('should reject invalid tags', async () => {
it('should gently reject invalid tags', async () => {
const test = async tag => {
let res = await request(app => app
.post(`/push-build/${tag}/x86_64-unknown-linux-gnu`)
.type('form')
.send({ secret, sha3: 'none' })
.send({
secret,
sha3: 'a00ead491c0e47efe4abefeb27ddc6ed8d1ea4daa43683d8e349e1e7459b74ba',
commit: '8b749367fd5fea897cee98bd892fff1ce90f8260',
filename: 'parity'
})
);

expect(res).to.have.status(400);
expect(res.text).to.equal(
`Error while processing the request:\nInvalid sha3 (none), tag (${tag}) or platform (x86_64-unknown-linux-gnu).\n`);
expect(res).to.have.status(202);
expect(res.text).to.have.string(`child "tag" fails`);
};

await test('xxx');
await test('v1.9.5-ci0');
});

it('should gently reject invalid platform', async () => {
let res = await request(app => app
.post(`/push-build/nightly/x86_64-debian-linux-gnu`)
.type('form')
.send({
secret,
sha3: 'a00ead491c0e47efe4abefeb27ddc6ed8d1ea4daa43683d8e349e1e7459b74ba',
commit: '8b749367fd5fea897cee98bd892fff1ce90f8260',
filename: 'parity'
})
);

expect(res).to.have.status(202);
expect(res.text).to.have.string(`child "platform" fails`);
});

it('should reject missing fields', async () => {
let res = await request(app => app
.post(`/push-build/nightly/x86_64-unknown-linux-gnu`)
.type('form')
.send({
secret,
sha3: 'a00ead491c0e47efe4abefeb27ddc6ed8d1ea4daa43683d8e349e1e7459b74ba',
// commit: '8b749367fd5fea897cee98bd892fff1ce90f8260',
filename: 'parity'
})
);

expect(res).to.have.status(400);
expect(res.text).to.have.string(`child "commit" fails`);
});

beforeEach(done => server.start(done));
afterEach(done => server.stop(done));

Expand Down Expand Up @@ -135,24 +187,25 @@ describe('push-build', () => {
.type('form')
.send({
secret,
sha3: 'beefcafe',
commit: '8b749367fd5fea897cee98bd892fff1ce90f8260'
sha3: 'a00ead491c0e47efe4abefeb27ddc6ed8d1ea4daa43683d8e349e1e7459b74ba',
commit: '8b749367fd5fea897cee98bd892fff1ce90f8260',
filename: 'parity'
})
);

expect(res).to.have.status(200);
// Githubhint registration
expect(requests[3].method).to.equal('eth_sendTransaction');
expect(requests[3].params).to.deep.equal([{
data: '0x02f2008dbeefcafe000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004f687474703a2f2f64316834786c3463723168306d6f2e636c6f756466726f6e742e6e65742f76312e372e31332f7838365f36342d756e6b6e6f776e2d6c696e75782d676e752f756e646566696e65640000000000000000000000000000000000',
expect(requests[2].method).to.equal('eth_sendTransaction');
expect(requests[2].params).to.deep.equal([{
data: '0x02f2008da00ead491c0e47efe4abefeb27ddc6ed8d1ea4daa43683d8e349e1e7459b74ba0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004c687474703a2f2f64316834786c3463723168306d6f2e636c6f756466726f6e742e6e65742f76312e372e31332f7838365f36342d756e6b6e6f776e2d6c696e75782d676e752f7061726974790000000000000000000000000000000000000000',
from: '0x0066ac7a4608f350bf9a0323d60dde211dfb27c0',
gasPrice,
to: '0x'
}]);
// Build registration
expect(requests[5].method).to.equal('eth_sendTransaction');
expect(requests[5].params).to.deep.equal([{
data: '0x793b0efb0000000000000000000000008b749367fd5fea897cee98bd892fff1ce90f82607838365f36342d756e6b6e6f776e2d6c696e75782d676e750000000000000000beefcafe00000000000000000000000000000000000000000000000000000000',
expect(requests[4].method).to.equal('eth_sendTransaction');
expect(requests[4].params).to.deep.equal([{
data: '0x793b0efb0000000000000000000000008b749367fd5fea897cee98bd892fff1ce90f82607838365f36342d756e6b6e6f776e2d6c696e75782d676e750000000000000000a00ead491c0e47efe4abefeb27ddc6ed8d1ea4daa43683d8e349e1e7459b74ba',
from: '0x0066ac7a4608f350bf9a0323d60dde211dfb27c0',
gasPrice,
to: '0x'
Expand Down
8 changes: 8 additions & 0 deletions test/validation.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const { expect } = require('chai');
const { tag } = require('../validation');

it('should allow nightly', () => {
const result = tag.validate('nightly');

expect(result.value).to.equal('nightly');
});
Loading