• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

screwdriver-cd / screwdriver / #3099

14 Apr 2025 07:45PM UTC coverage: 95.102% (-0.07%) from 95.168%
#3099

push

screwdriver

web-flow
fix: Add feature flag for control log payload (#3321)

Co-authored-by: Ming-Hay <157658916+minghay@users.noreply.github.com>

1978 of 2144 branches covered (92.26%)

Branch coverage included in aggregate %.

2 of 2 new or added lines in 1 file covered. (100.0%)

1 existing line in 1 file now uncovered.

4973 of 5165 relevant lines covered (96.28%)

103.3 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.16
/lib/server.js
1
'use strict';
2

3
const Hapi = require('@hapi/hapi');
6✔
4
const logger = require('screwdriver-logger');
6✔
5
const registerPlugins = require('./registerPlugins');
6✔
6

7
process.on('unhandledRejection', (reason, p) => {
6✔
8
    console.error('Unhandled Rejection at: Promise', p, 'reason:', reason); /* eslint-disable-line no-console */
12✔
9
});
10

11
/**
12
 * @method handlePreResponseLogs
13
 * @param  {Hapi.Request}    request Hapi Request object
14
 * @param  {Hapi.h}     h   Hapi Response Toolkit
15
 */
16
function handlePreResponseLogs(request, h) {
17
    const { response } = request;
6✔
18
    const { release, log } = request.server.app;
6✔
19

20
    if (release && release.cookieName && request.state && !request.state[release.cookieName]) {
6✔
21
        h.state(release.cookieName, release.cookieValue);
5✔
22
    }
23

24
    // Pretty print errors
25
    if (response.isBoom) {
6✔
26
        const err = response;
4✔
27
        const errName = err.output.payload.error;
4✔
28
        const errMessage = err.message;
4✔
29
        const { statusCode } = err.output.payload;
4✔
30
        const stack = err.stack || errMessage;
4!
31

32
        // If we're throwing errors, let's have them say a little more than just 500
33
        if (statusCode === 500) {
4✔
34
            request.log(['server', 'error'], stack);
2✔
35
        }
36

37
        const res = {
4✔
38
            statusCode,
39
            error: errName,
40
            message: errMessage
41
        };
42

43
        if (err.data) {
4✔
44
            res.data = err.data;
1✔
45
        }
46

47
        return h.response(res).code(statusCode);
4✔
48
    }
49

50
    // Log request payload when it takes longer than 5 seconds to respond
51
    // This is to prevent logging payloads for every request
52
    if (
2!
53
        log &&
2!
54
        log.payload &&
55
        log.payload.enabled &&
56
        request.info &&
57
        request.info.received &&
58
        Date.now() - request.info.received > 5000 &&
59
        request.payload
60
    ) {
UNCOV
61
        request.log(['payload'], {
×
62
            method: request.method,
63
            path: request.path,
64
            payload: request.payload,
65
            statusCode: request.response && request.response.statusCode,
×
66
            responseTime: Date.now() - request.info.received
67
        });
68
    }
69

70
    return h.continue;
2✔
71
}
72

73
/**
74
 * Configures & starts up a HapiJS server
75
 * @method
76
 * @param  {Object}      config
77
 * @param  {Object}      config.httpd
78
 * @param  {Integer}     config.httpd.port          Port number to listen to
79
 * @param  {String}      config.httpd.host          Host to listen on
80
 * @param  {String}      config.httpd.uri           Public routable address
81
 * @param  {Object}      config.httpd.tls           TLS Configuration
82
 * @param  {Object}      config.webhooks            Webhooks settings
83
 * @param  {String}      config.webhooks.restrictPR Restrict PR setting
84
 * @param  {Boolean}     config.webhooks.chainPR    Chain PR flag
85
 * @param  {Object}      config.ecosystem           List of hosts in the ecosystem
86
 * @param  {Object}      config.ecosystem.ui        URL for the User Interface
87
 * @param  {Factory}     config.pipelineFactory     Pipeline Factory instance
88
 * @param  {Factory}     config.jobFactory          Job Factory instance
89
 * @param  {Factory}     config.userFactory         User Factory instance
90
 * @param  {Factory}     config.bannerFactory       Banner Factory instance
91
 * @param  {Factory}     config.buildFactory        Build Factory instance
92
 * @param  {Factory}     config.buildClusterFactory Build Cluster Factory instance
93
 * @param  {Factory}     config.stepFactory         Step Factory instance
94
 * @param  {Factory}     config.secretFactory       Secret Factory instance
95
 * @param  {Factory}     config.tokenFactory        Token Factory instance
96
 * @param  {Factory}     config.eventFactory        Event Factory instance
97
 * @param  {Factory}     config.collectionFactory   Collection Factory instance
98
 * @param  {Factory}     config.stageFactory        Stage Factory instance
99
 * @param  {Factory}     config.stageBuildFactory   Stage Build Factory instance
100
 * @param  {Factory}     config.triggerFactory      Trigger Factory instance
101
 * @param  {Object}      config.builds              Config to include for builds plugin
102
 * @param  {Object}      config.builds.ecosystem    List of hosts in the ecosystem
103
 * @param  {Object}      config.builds.authConfig   Configuration for auth
104
 * @param  {Object}      config.builds.externalJoin Flag to allow external join
105
 * @param  {Object}      config.unzipArtifactsEnabled  Flag to allow unzip artifacts
106
 * @param  {Object}      config.artifactsMaxDownloadSize Maximum download size for artifacts
107
 * @param  {Function}    callback                   Callback to invoke when server has started.
108
 * @return {http.Server}                            A listener: NodeJS http.Server object
109
 */
110

111
module.exports = async config => {
6✔
112
    try {
6✔
113
        // Hapi Cross-origin resource sharing configuration
114
        // See http://hapijs.com/api for available options
115

116
        let corsOrigins = [config.ecosystem.ui];
6✔
117

118
        if (Array.isArray(config.ecosystem.allowCors)) {
6!
119
            corsOrigins = corsOrigins.concat(config.ecosystem.allowCors);
6✔
120
        }
121

122
        const cors = {
6✔
123
            origin: corsOrigins,
124
            additionalExposedHeaders: ['x-more-data'],
125
            credentials: true
126
        };
127
        // Create a server with a host and port
128
        const server = new Hapi.Server({
6✔
129
            port: config.httpd.port,
130
            host: config.httpd.host,
131
            uri: config.httpd.uri,
132
            routes: {
133
                cors,
134
                log: { collect: true }
135
            },
136
            state: {
137
                strictHeader: false
138
            },
139
            router: {
140
                stripTrailingSlash: true
141
            }
142
        });
143

144
        // Set the factories within server.app
145
        // Instantiating the server with the factories will apply a shallow copy
146
        server.app = {
6✔
147
            commandFactory: config.commandFactory,
148
            commandTagFactory: config.commandTagFactory,
149
            templateFactory: config.templateFactory,
150
            templateTagFactory: config.templateTagFactory,
151
            pipelineTemplateFactory: config.pipelineTemplateFactory,
152
            pipelineTemplateVersionFactory: config.pipelineTemplateVersionFactory,
153
            jobTemplateTagFactory: config.jobTemplateTagFactory,
154
            pipelineTemplateTagFactory: config.pipelineTemplateTagFactory,
155
            stageFactory: config.stageFactory,
156
            stageBuildFactory: config.stageBuildFactory,
157
            triggerFactory: config.triggerFactory,
158
            pipelineFactory: config.pipelineFactory,
159
            jobFactory: config.jobFactory,
160
            userFactory: config.userFactory,
161
            buildFactory: config.buildFactory,
162
            stepFactory: config.stepFactory,
163
            bannerFactory: config.bannerFactory,
164
            secretFactory: config.secretFactory,
165
            tokenFactory: config.tokenFactory,
166
            eventFactory: config.eventFactory,
167
            collectionFactory: config.collectionFactory,
168
            buildClusterFactory: config.buildClusterFactory,
169
            ecosystem: config.ecosystem,
170
            release: config.release,
171
            queueWebhook: config.queueWebhook,
172
            unzipArtifacts: config.unzipArtifactsEnabled,
173
            log: config.log
174
        };
175

176
        const bellConfigs = await config.auth.scm.getBellConfiguration();
6✔
177

178
        config.auth.bell = bellConfigs;
6✔
179

180
        if (config.release && config.release.cookieName) {
6!
181
            server.state(config.release.cookieName, {
6✔
182
                path: '/',
183
                ttl: config.release.cookieTimeout * 60 * 1000, // (2 mins)
184
                isSecure: config.auth.https,
185
                isHttpOnly: !config.auth.https
186
            });
187
        }
188

189
        server.ext('onPreResponse', handlePreResponseLogs);
6✔
190

191
        // Audit log
192
        if (config.log && config.log.audit.enabled) {
6!
193
            server.ext('onCredentials', (request, h) => {
×
194
                const { username, scope, pipelineId } = request.auth.credentials;
×
195

196
                if (scope) {
×
197
                    const validScope = config.log.audit.scope.filter(s => scope.includes(s));
×
198

199
                    if (Array.isArray(validScope) && validScope.length > 0) {
×
200
                        let context;
201

202
                        if (validScope.includes('admin')) {
×
203
                            context = `Admin ${username}`;
×
204
                        } else if (validScope.includes('user')) {
×
205
                            context = `User ${username}`;
×
206
                        } else if (validScope.includes('build') || validScope.includes('temporal')) {
×
207
                            context = `Build ${username}`;
×
208
                        } else if (validScope.includes('pipeline')) {
×
209
                            context = `Pipeline ${pipelineId}`;
×
210
                        } else {
211
                            context = `Guest ${username}`;
×
212
                        }
213

214
                        logger.info(`[Login] ${context} ${request.method} ${request.path}`);
×
215
                    }
216
                }
217

218
                return h.continue;
×
219
            });
220
        }
221

222
        // Register events for notifications plugin
223
        server.event(['build_status', 'job_status']);
6✔
224

225
        // Register plugins
226
        await registerPlugins(server, config);
6✔
227

228
        // Initialize common data in buildFactory and jobFactory
229
        server.app.buildFactory.apiUri = server.info.uri;
5✔
230
        server.app.buildFactory.tokenGen = (buildId, metadata, scmContext, expiresIn, scope = ['temporal']) =>
5✔
231
            server.plugins.auth.generateToken(
2✔
232
                server.plugins.auth.generateProfile({ username: buildId, scmContext, scope, metadata }),
233
                expiresIn
234
            );
235
        server.app.buildFactory.executor.tokenGen = server.app.buildFactory.tokenGen;
5✔
236
        server.app.buildFactory.maxDownloadSize = parseInt(config.artifactsMaxDownloadSize, 10) * 1024 * 1024 * 1024;
5✔
237

238
        server.app.jobFactory.apiUri = server.info.uri;
5✔
239
        server.app.jobFactory.tokenGen = (username, metadata, scmContext, scope = ['user']) =>
5✔
240
            server.plugins.auth.generateToken(
2✔
241
                server.plugins.auth.generateProfile({ username, scmContext, scope, metadata })
242
            );
243
        server.app.jobFactory.executor.userTokenGen = server.app.jobFactory.tokenGen;
5✔
244

245
        if (server.plugins.shutdown) {
5!
246
            server.plugins.shutdown.handler({
×
247
                taskname: 'executor-queue-cleanup',
248
                task: async () => {
249
                    await server.app.jobFactory.cleanUp();
×
250
                    logger.info('completed clean up tasks');
×
251
                }
252
            });
253
        }
254

255
        // Start the server
256
        await server.start();
5✔
257

258
        return server;
5✔
259
    } catch (err) {
260
        logger.error('Failed to start server', err);
1✔
261
        throw err;
1✔
262
    }
263
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc