screwdriver-api 7.0.202 → 7.0.203

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "screwdriver-api",
3
- "version": "7.0.202",
3
+ "version": "7.0.203",
4
4
  "description": "API server for the Screwdriver.cd service",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -83,10 +83,12 @@
83
83
  "@hapi/inert": "^7.0.0",
84
84
  "@hapi/vision": "^7.0.0",
85
85
  "@promster/hapi": "^14.0.0",
86
+ "archiver": "^7.0.1",
86
87
  "async": "^3.2.4",
87
88
  "badge-maker": "^3.3.1",
88
89
  "config": "^3.3.8",
89
90
  "dayjs": "^1.11.7",
91
+ "got": "^11.8.3",
90
92
  "hapi-auth-bearer-token": "^8.0.0",
91
93
  "hapi-auth-jwt2": "^10.4.0",
92
94
  "hapi-rate-limit": "^7.1.0",
@@ -40,7 +40,7 @@ module.exports = () => ({
40
40
 
41
41
  const build = await buildFactory.get(request.params.buildId);
42
42
  const job = await jobFactory.get(build.jobId);
43
- const pipeline = pipelineFactory.get(job.pipelineId);
43
+ const pipeline = await pipelineFactory.get(job.pipelineId);
44
44
 
45
45
  profile = request.server.plugins.auth.generateProfile({
46
46
  username: request.params.buildId,
@@ -0,0 +1,117 @@
1
+ 'use strict';
2
+
3
+ const archiver = require('archiver');
4
+ const boom = require('@hapi/boom');
5
+ const request = require('got');
6
+ const joi = require('joi');
7
+ const jwt = require('jsonwebtoken');
8
+ const logger = require('screwdriver-logger');
9
+ const { PassThrough } = require('stream');
10
+ const schema = require('screwdriver-data-schema');
11
+ const { v4: uuidv4 } = require('uuid');
12
+ const idSchema = schema.models.build.base.extract('id');
13
+
14
+
15
+ module.exports = config => ({
16
+ method: 'GET',
17
+ path: '/builds/{id}/artifacts',
18
+ options: {
19
+ description: 'Get a zipped file including all build artifacts',
20
+ notes: 'Redirects to store with proper token',
21
+ tags: ['api', 'builds', 'artifacts'],
22
+ auth: {
23
+ strategies: ['session', 'token'],
24
+ scope: ['user', 'build', 'pipeline']
25
+ },
26
+
27
+ handler: async (req, h) => {
28
+ const { name: artifact, id: buildId } = req.params;
29
+ const { credentials } = req.auth;
30
+ const { canAccessPipeline } = req.server.plugins.pipelines;
31
+ const { buildFactory, eventFactory } = req.server.app;
32
+
33
+ return buildFactory.get(buildId)
34
+ .then(build => {
35
+ if (!build) {
36
+ throw boom.notFound('Build does not exist');
37
+ }
38
+
39
+ return eventFactory.get(build.eventId);
40
+ })
41
+ .then(event => {
42
+ if (!event) {
43
+ throw boom.notFound('Event does not exist');
44
+ }
45
+
46
+ return canAccessPipeline(credentials, event.pipelineId, 'pull', req.server.app);
47
+ })
48
+ .then(async () => {
49
+ const token = jwt.sign({
50
+ buildId, artifact, scope: ['user']
51
+ }, config.authConfig.jwtPrivateKey, {
52
+ algorithm: 'RS256',
53
+ expiresIn: '10m',
54
+ jwtid: uuidv4()
55
+ });
56
+ const baseUrl = `${config.ecosystem.store}/v1/builds/${buildId}/ARTIFACTS`;
57
+
58
+ // Fetch the manifest
59
+ const manifest = await request({
60
+ url: `${baseUrl}/manifest.txt?token=${token}`,
61
+ method: 'GET'
62
+ }).text();
63
+ const manifestArray = manifest.trim().split('\n');
64
+
65
+ // Create a stream and set up archiver
66
+ const archive = archiver('zip', { zlib: { level: 9 } });
67
+ // PassThrough stream to make archiver readable by Hapi
68
+ const passThrough = new PassThrough();
69
+
70
+ archive.on('error', (err) => {
71
+ logger.error('Archiver error:', err);
72
+ passThrough.emit('error', err); // Propagate the error to the PassThrough stream
73
+ });
74
+ passThrough.on('error', (err) => {
75
+ logger.error('PassThrough stream error:', err);
76
+ });
77
+
78
+ // Pipe the archive to PassThrough so it can be sent as a response
79
+ archive.pipe(passThrough);
80
+
81
+ // Fetch the artifact files and append to the archive
82
+ try {
83
+ for (const file of manifestArray) {
84
+ if (file) {
85
+ const fileStream = request.stream(`${baseUrl}/${file}?token=${token}&type=download`);
86
+
87
+ fileStream.on('error', (err) => {
88
+ logger.error(`Error downloading file: ${file}`, err);
89
+ archive.emit('error', err);
90
+ });
91
+
92
+ archive.append(fileStream, { name: file });
93
+ }
94
+ }
95
+ // Finalize the archive after all files are appended
96
+ archive.finalize();
97
+ } catch (err) {
98
+ logger.error('Error while streaming artifact files:', err);
99
+ archive.emit('error', err);
100
+ }
101
+
102
+ // Respond with the PassThrough stream (which is now readable by Hapi)
103
+ return h.response(passThrough)
104
+ .type('application/zip')
105
+ .header('Content-Disposition', 'attachment; filename="SD_ARTIFACTS.zip"');
106
+ })
107
+ .catch(err => {
108
+ throw err;
109
+ });
110
+ },
111
+ validate: {
112
+ params: joi.object({
113
+ id: idSchema
114
+ })
115
+ }
116
+ }
117
+ });
@@ -10,6 +10,7 @@ const createRoute = require('./create');
10
10
  const stepGetRoute = require('./steps/get');
11
11
  const listStepsRoute = require('./steps/list');
12
12
  const artifactGetRoute = require('./artifacts/get');
13
+ const artifactGetAllRoute = require('./artifacts/getAll');
13
14
  const artifactUnzipRoute = require('./artifacts/unzip');
14
15
  const stepUpdateRoute = require('./steps/update');
15
16
  const stepLogsRoute = require('./steps/logs');
@@ -508,6 +509,7 @@ const buildsPlugin = {
508
509
  tokenRoute(),
509
510
  metricsRoute(),
510
511
  artifactGetRoute(options),
512
+ artifactGetAllRoute(options),
511
513
  artifactUnzipRoute()
512
514
  ]);
513
515
  }