From 0faaaa96bb1a9f237c4456deee0347aadf2db013 Mon Sep 17 00:00:00 2001 From: klu909 <55161078+klu909@users.noreply.github.com> Date: Mon, 5 Oct 2020 17:30:09 -0700 Subject: [PATCH] fix(2068): Large step logs causes browser to hang (#2200) * fix(2068): Large step logs causes browser to hang * fix uuid * fix uuid * fix test * add comment Co-authored-by: Kevin Lu --- package.json | 4 +-- plugins/builds/steps/logs.js | 50 +++++++++++++++++++++++++++++------- test/plugins/builds.test.js | 25 ++++++++++++++++++ 3 files changed, 68 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 2494f9432..4566177dd 100644 --- a/package.json +++ b/package.json @@ -74,14 +74,14 @@ "@hapi/good-console": "^9.0.0", "@hapi/good-squeeze": "^6.0.0", "@hapi/hapi": "^20.0.0", - "@hapi/hoek": "^9.0.4", + "@hapi/hoek": "^9.1.0", "@hapi/inert": "^6.0.1", "@hapi/vision": "^6.0.0", "@promster/hapi": "^4.2.0", "async": "^3.2.0", "config": "^1.31.0", "date-fns": "^1.30.1", - "dayjs": "^1.8.33", + "dayjs": "^1.9.1", "deepmerge": "^4.2.2", "hapi-auth-bearer-token": "^6.1.6", "hapi-auth-jwt2": "^10.1.0", diff --git a/plugins/builds/steps/logs.js b/plugins/builds/steps/logs.js index a87dc9f1a..ad2948e78 100644 --- a/plugins/builds/steps/logs.js +++ b/plugins/builds/steps/logs.js @@ -6,6 +6,8 @@ const request = require('request'); const ndjson = require('ndjson'); const MAX_LINES_SMALL = 100; const MAX_LINES_BIG = 1000; +const jwt = require('jsonwebtoken'); +const uuid = require('uuid'); const logger = require('screwdriver-logger'); @@ -144,7 +146,7 @@ module.exports = config => ({ notes: 'Returns the logs for a step', tags: ['api', 'builds', 'steps', 'log'], auth: { - strategies: ['token'], + strategies: ['token', 'session'], scope: ['user', 'pipeline', 'build'] }, plugins: { @@ -156,7 +158,6 @@ module.exports = config => ({ const { stepFactory } = req.server.app; const buildId = req.params.id; const stepName = req.params.name; - const { headers } = req; return stepFactory .get({ buildId, name: stepName }) @@ -174,10 +175,28 @@ module.exports = config => ({ const isDone = stepModel.code !== undefined; const baseUrl = `${config.ecosystem.store}/v1/builds/${buildId}/${stepName}/log`; - const authToken = headers.authorization; - const { sort } = req.query; - const pagesToLoad = req.query.pages; - const linesFrom = req.query.from; + const authToken = jwt.sign( + { + buildId, + stepName, + scope: ['user'] + }, + config.authConfig.jwtPrivateKey, + { + algorithm: 'RS256', + expiresIn: '5s', + jwtid: uuid.v4() + } + ); + const { sort, type } = req.query; + let pagesToLoad = req.query.pages; + let linesFrom = req.query.from; + + if (type === 'download' && isDone) { + // 100 lines per page + pagesToLoad = Math.ceil(stepModel.lines / 100); + linesFrom = 0; + } // eslint-disable-next-line max-len return getMaxLines({ baseUrl, authToken }) @@ -191,9 +210,22 @@ module.exports = config => ({ maxLines }) ) - .then(([lines, morePages]) => - h.response(lines).header('X-More-Data', (morePages || !isDone).toString()) - ); + .then(([lines, morePages]) => { + if (type !== 'download') { + return h.response(lines).header('X-More-Data', (morePages || !isDone).toString()); + } + + let res = ''; + + for (let i = 0; i < lines.length; i += 1) { + res = `${res}${lines[i].m}\n`; + } + + return h + .response(res) + .type('text/plain') + .header('content-disposition', `attachment; filename="${stepName}-log.txt"`); + }); }) .catch(err => { throw err; diff --git a/test/plugins/builds.test.js b/test/plugins/builds.test.js index c8a287914..2c05c5a53 100644 --- a/test/plugins/builds.test.js +++ b/test/plugins/builds.test.js @@ -4525,6 +4525,31 @@ describe('build plugin test', () => { }); }); + it('returns download link for download option', () => { + nock('https://store.screwdriver.cd') + .get(`/v1/builds/${id}/${step}/log.0`) + .twice() + .replyWithFile(200, `${__dirname}/data/step.log.ndjson`); + + const expectedLog = 'Building stuff\nStill building...\nDone Building stuff\n'; + + return server + .inject({ + url: `/builds/${id}/steps/${step}/logs?type=download`, + auth: { + credentials: { + scope: ['user'] + }, + strategy: ['session'] + } + }) + .then(reply => { + assert.equal(reply.statusCode, 200); + assert.deepEqual(reply.result, expectedLog); + assert.propertyVal(reply.headers, 'content-disposition', `attachment; filename="${step}-log.txt"`); + }); + }); + it('returns logs for a step that is split across pages', () => { nock('https://store.screwdriver.cd') .get(`/v1/builds/${id}/${step}/log.0`)