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

screwdriver-cd / screwdriver / #3414

12 May 2026 12:11AM UTC coverage: 75.519% (-19.9%) from 95.405%
#3414

Pull #3493

screwdriver

web-flow
Merge branch 'master' into fix_event_meta_update
Pull Request #3493: fix: Exclusive control over meta updates

1703 of 2430 branches covered (70.08%)

Branch coverage included in aggregate %.

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

1057 existing lines in 57 files now uncovered.

4374 of 5617 relevant lines covered (77.87%)

106.76 hits per line

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

59.38
/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✔
UNCOV
8
    console.error('Unhandled Rejection at: Promise', p, 'reason:', reason); /* eslint-disable-line no-console */
×
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
    ) {
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  {Object}      config.ecosystem           List of hosts in the ecosystem
84
 * @param  {Object}      config.ecosystem.ui        URL for the User Interface
85
 * @param  {Factory}     config.pipelineFactory     Pipeline Factory instance
86
 * @param  {Factory}     config.jobFactory          Job Factory instance
87
 * @param  {Factory}     config.userFactory         User Factory instance
88
 * @param  {Factory}     config.bannerFactory       Banner Factory instance
89
 * @param  {Factory}     config.buildFactory        Build Factory instance
90
 * @param  {Factory}     config.buildClusterFactory Build Cluster Factory instance
91
 * @param  {Factory}     config.stepFactory         Step Factory instance
92
 * @param  {Factory}     config.secretFactory       Secret Factory instance
93
 * @param  {Factory}     config.tokenFactory        Token Factory instance
94
 * @param  {Factory}     config.eventFactory        Event Factory instance
95
 * @param  {Factory}     config.collectionFactory   Collection Factory instance
96
 * @param  {Factory}     config.stageFactory        Stage Factory instance
97
 * @param  {Factory}     config.stageBuildFactory   Stage Build Factory instance
98
 * @param  {Factory}     config.triggerFactory      Trigger Factory instance
99
 * @param  {Object}      config.builds              Config to include for builds plugin
100
 * @param  {Object}      config.builds.ecosystem    List of hosts in the ecosystem
101
 * @param  {Object}      config.builds.authConfig   Configuration for auth
102
 * @param  {Object}      config.builds.externalJoin Flag to allow external join
103
 * @param  {Object}      config.unzipArtifactsEnabled  Flag to allow unzip artifacts
104
 * @param  {Object}      config.artifactsMaxDownloadSize Maximum download size for artifacts
105
 * @param  {Function}    callback                   Callback to invoke when server has started.
106
 * @return {http.Server}                            A listener: NodeJS http.Server object
107
 */
108

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

114
        let corsOrigins = [config.ecosystem.ui];
6✔
115

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

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

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

174
        const bellConfigs = await config.auth.scm.getBellConfiguration();
6✔
175

176
        config.auth.bell = bellConfigs;
6✔
177

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

187
        server.ext('onPreResponse', handlePreResponseLogs);
6✔
188

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

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

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

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

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

216
                return h.continue;
×
217
            });
218
        }
219

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

223
        // Register plugins
224
        await registerPlugins(server, config);
6✔
225

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

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

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

253
        // Start the server
254
        await server.start();
5✔
255

256
        return server;
5✔
257
    } catch (err) {
258
        logger.error('Failed to start server', err);
1✔
259
        throw err;
1✔
260
    }
261
};
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