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

screwdriver-cd / screwdriver / #3412

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

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

13.11
/plugins/pipelines/create.js
1
'use strict';
2

3
const urlLib = require('url');
1✔
4
const boom = require('@hapi/boom');
1✔
5
const schema = require('screwdriver-data-schema');
1✔
6
const logger = require('screwdriver-logger');
1✔
7
const { formatCheckoutUrl, sanitizeRootDir } = require('./helper');
1✔
8
const { getUserPermissions } = require('../helper');
1✔
9
const ANNOTATION_USE_DEPLOY_KEY = 'screwdriver.cd/useDeployKey';
1✔
10

11
module.exports = () => ({
393✔
12
    method: 'POST',
13
    path: '/pipelines',
14
    options: {
15
        description: 'Create a new pipeline',
16
        notes: 'Create a specific pipeline',
17
        tags: ['api', 'pipelines'],
18
        auth: {
19
            strategies: ['token'],
20
            scope: ['user', '!guest']
21
        },
22

23
        handler: async (request, h) => {
UNCOV
24
            const checkoutUrl = formatCheckoutUrl(request.payload.checkoutUrl);
×
UNCOV
25
            const rootDir = sanitizeRootDir(request.payload.rootDir);
×
UNCOV
26
            const { autoKeysGeneration } = request.payload;
×
UNCOV
27
            const { pipelineFactory, userFactory, collectionFactory, secretFactory } = request.server.app;
×
UNCOV
28
            const { username, scmContext } = request.auth.credentials;
×
UNCOV
29
            const deployKeySecret = 'SD_SCM_DEPLOY_KEY';
×
30

31
            // fetch the user
UNCOV
32
            const user = await userFactory.get({ username, scmContext });
×
UNCOV
33
            const token = await user.unsealToken();
×
34

35
            let scmUri;
36

UNCOV
37
            try {
×
UNCOV
38
                scmUri = await pipelineFactory.scm.parseUrl({
×
39
                    scmContext,
40
                    rootDir,
41
                    checkoutUrl,
42
                    token
43
                });
44
            } catch (error) {
UNCOV
45
                logger.error(error.message);
×
UNCOV
46
                throw boom.boomify(error, { statusCode: error.statusCode });
×
47
            }
48

49
            // get the user permissions for the repo
UNCOV
50
            await getUserPermissions({ user, scmUri });
×
51

52
            // see if there is already a pipeline
UNCOV
53
            let pipeline = await pipelineFactory.get({ scmUri });
×
54

55
            // if there is already a pipeline for the checkoutUrl, reject
UNCOV
56
            if (pipeline) {
×
UNCOV
57
                throw boom.conflict(`Pipeline already exists with the ID: ${pipeline.id}`, {
×
58
                    existingId: pipeline.id
59
                });
60
            }
61
            // set up pipeline admins, and create a new pipeline
UNCOV
62
            const pipelineConfig = {
×
63
                admins: {
64
                    [username]: true
65
                },
66
                adminUserIds: [user.id],
67
                scmContext,
68
                scmUri
69
            };
70

UNCOV
71
            logger.info(`[Audit] user ${user.username}:${scmContext} creates the pipeline for ${scmUri}.`);
×
UNCOV
72
            pipeline = await pipelineFactory.create(pipelineConfig);
×
73

UNCOV
74
            const collections = await collectionFactory.list({
×
75
                params: {
76
                    userId: user.id,
77
                    type: 'default'
78
                }
79
            });
80
            let defaultCollection;
81

UNCOV
82
            if (collections && collections.length > 0) {
×
UNCOV
83
                [defaultCollection] = collections;
×
84
            }
85

UNCOV
86
            if (!defaultCollection) {
×
87
                defaultCollection = await collectionFactory.create({
×
88
                    userId: user.id,
89
                    name: 'My Pipelines',
90
                    description: `The default collection for ${user.username}`,
91
                    type: 'default'
92
                });
93
            }
94

95
            // Check if the pipeline exists in the default collection
96
            // to prevent the situation where a pipeline is deleted and then created right away with the same id
UNCOV
97
            if (!defaultCollection.pipelineIds.includes(pipeline.id)) {
×
98
                Object.assign(defaultCollection, {
×
99
                    pipelineIds: [...defaultCollection.pipelineIds, pipeline.id]
100
                });
101

102
                await defaultCollection.update();
×
103
            }
104

UNCOV
105
            const results = await pipeline.sync();
×
106

107
            // check if pipeline has deploy key annotation then create secrets
UNCOV
108
            const deployKeyAnnotation = pipeline.annotations && pipeline.annotations[ANNOTATION_USE_DEPLOY_KEY];
×
109

UNCOV
110
            if (autoKeysGeneration || deployKeyAnnotation) {
×
UNCOV
111
                const privateDeployKey = await pipelineFactory.scm.addDeployKey({
×
112
                    scmContext,
113
                    checkoutUrl,
114
                    token
115
                });
UNCOV
116
                const privateDeployKeyB64 = Buffer.from(privateDeployKey).toString('base64');
×
117

UNCOV
118
                await secretFactory.create({
×
119
                    pipelineId: pipeline.id,
120
                    name: deployKeySecret,
121
                    value: privateDeployKeyB64,
122
                    allowInPR: true
123
                });
124
            }
125

UNCOV
126
            await pipeline.addWebhooks(`${request.server.info.uri}/v4/webhooks`);
×
127

UNCOV
128
            const location = urlLib.format({
×
129
                host: request.headers.host,
130
                port: request.headers.port,
131
                protocol: request.server.info.protocol,
132
                pathname: `${request.path}/${pipeline.id}`
133
            });
UNCOV
134
            const data = await results.toJson();
×
135

UNCOV
136
            return h.response(data).header('Location', location).code(201);
×
137
        },
138
        validate: {
139
            payload: schema.models.pipeline.create
140
        }
141
    }
142
});
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