Skip to content

Commit 4be9f7c

Browse files
authoredJun 22, 2022
feat(2669): Add endpoint to get pipeline stages (#2717)
1 parent c6a9e5b commit 4be9f7c

File tree

7 files changed

+185
-12
lines changed

7 files changed

+185
-12
lines changed
 

‎README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,14 @@ Three (3) options for executor:
5252
- Nomad (`nomad`)
5353

5454
Three (3) options for SCM:
55-
- Github (`github`)
56-
- Gitlab (`gitlab`)
55+
- GitHub (`github`)
56+
- GitLab (`gitlab`)
5757
- Bitbucket (`bitbucket`)
5858

5959
### Prerequisites
6060
To use Screwdriver, you will need the following prerequisites:
6161

62-
- Node v8.0.0 or higher
62+
- Node v12.0.0 or higher
6363
- [Kubernetes][kubectl] or [Docker][docker]
6464

6565
### From Source

‎package.json

+6-6
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,15 @@
7979
"@hapi/inert": "^6.0.4",
8080
"@hapi/vision": "^6.1.0",
8181
"@promster/hapi": "^6.1.0",
82-
"async": "^3.2.2",
82+
"async": "^3.2.4",
8383
"badge-maker": "^3.3.1",
8484
"config": "^1.31.0",
8585
"date-fns": "^1.30.1",
8686
"dayjs": "^1.10.7",
8787
"hapi-auth-bearer-token": "^8.0.0",
8888
"hapi-auth-jwt2": "^10.2.0",
8989
"hapi-rate-limit": "^5.0.1",
90-
"hapi-swagger": "^14.2.4",
90+
"hapi-swagger": "^14.5.4",
9191
"ioredis": "^4.28.0",
9292
"joi": "^17.4.2",
9393
"js-yaml": "^3.14.1",
@@ -114,14 +114,14 @@
114114
"screwdriver-executor-queue": "^3.1.2",
115115
"screwdriver-executor-router": "^2.3.0",
116116
"screwdriver-logger": "^1.1.0",
117-
"screwdriver-models": "^28.11.0",
117+
"screwdriver-models": "^28.16.1",
118118
"screwdriver-notifications-email": "^2.2.0",
119119
"screwdriver-notifications-slack": "^3.2.1",
120120
"screwdriver-request": "^1.0.3",
121121
"screwdriver-scm-base": "^7.3.0",
122122
"screwdriver-scm-bitbucket": "^4.5.1",
123-
"screwdriver-scm-github": "^11.6.3",
124-
"screwdriver-scm-gitlab": "^2.7.2",
123+
"screwdriver-scm-github": "^11.10.0",
124+
"screwdriver-scm-gitlab": "^2.10.0",
125125
"screwdriver-scm-router": "^6.3.0",
126126
"screwdriver-template-validator": "^5.2.0",
127127
"screwdriver-workflow-parser": "^3.2.1",
@@ -153,7 +153,7 @@
153153
"mocha-sonarqube-reporter": "^1.0.2",
154154
"mockery": "^2.0.0",
155155
"mz": "^2.6.0",
156-
"nock": "^13.2.1",
156+
"nock": "^13.2.7",
157157
"node-plantuml": "^0.5.0",
158158
"npm-auto-version": "^1.0.0",
159159
"nyc": "^15.0.0",

‎plugins/pipelines/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const listRoute = require('./list');
1212
const badgeRoute = require('./badge');
1313
const jobBadgeRoute = require('./jobBadge');
1414
const listJobsRoute = require('./listJobs');
15+
const listStagesRoute = require('./listStages');
1516
const listTriggersRoute = require('./listTriggers');
1617
const listSecretsRoute = require('./listSecrets');
1718
const listEventsRoute = require('./listEvents');
@@ -177,6 +178,7 @@ const pipelinesPlugin = {
177178
badgeRoute({ statusColor }),
178179
jobBadgeRoute({ statusColor }),
179180
listJobsRoute(),
181+
listStagesRoute(),
180182
listTriggersRoute(),
181183
listSecretsRoute(),
182184
listEventsRoute(),

‎plugins/pipelines/listStages.js

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
'use strict';
2+
3+
const boom = require('@hapi/boom');
4+
const joi = require('joi');
5+
const schema = require('screwdriver-data-schema');
6+
const pipelineIdSchema = schema.models.pipeline.base.extract('id');
7+
const stageListSchema = joi
8+
.array()
9+
.items(schema.models.stage.base)
10+
.label('List of stages');
11+
12+
module.exports = () => ({
13+
method: 'GET',
14+
path: '/pipelines/{id}/stages',
15+
options: {
16+
description: 'Get all stages for a given pipeline',
17+
notes: 'Returns all stages for a given pipeline',
18+
tags: ['api', 'pipelines', 'stages'],
19+
auth: {
20+
strategies: ['token'],
21+
scope: ['user', 'build', 'pipeline']
22+
},
23+
24+
handler: async (request, h) => {
25+
const { pipelineFactory, stageFactory } = request.server.app;
26+
const pipelineId = request.params.id;
27+
28+
return pipelineFactory
29+
.get(pipelineId)
30+
.then(pipeline => {
31+
if (!pipeline) {
32+
throw boom.notFound('Pipeline does not exist');
33+
}
34+
35+
const config = {
36+
params: { pipelineId }
37+
};
38+
39+
if (request.query.state) {
40+
config.params.state = request.query.state;
41+
}
42+
43+
return stageFactory.list(config);
44+
})
45+
.then(stages => h.response(stages.map(s => s.toJson())))
46+
.catch(err => {
47+
throw err;
48+
});
49+
},
50+
response: {
51+
schema: stageListSchema
52+
},
53+
validate: {
54+
params: joi.object({
55+
id: pipelineIdSchema
56+
})
57+
}
58+
}
59+
});

‎plugins/pipelines/listTriggers.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ module.exports = () => ({
2020
method: 'GET',
2121
path: '/pipelines/{id}/triggers',
2222
options: {
23-
description: 'Get all jobs for a given pipeline',
24-
notes: 'Returns all jobs for a given pipeline',
25-
tags: ['api', 'pipelines', 'jobs'],
23+
description: 'Get all triggers for a given pipeline',
24+
notes: 'Returns all triggers for a given pipeline',
25+
tags: ['api', 'pipelines', 'triggers'],
2626
auth: {
2727
strategies: ['token'],
2828
scope: ['user', 'build', 'pipeline']

‎test/plugins/data/stages.json

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[
2+
{
3+
"pipelineId": 12345,
4+
"name": "setup",
5+
"jobIds": [1, 2, 3, 4],
6+
"state": "ACTIVE"
7+
},
8+
{
9+
"pipelineId": 12345,
10+
"name": "deploy",
11+
"jobIds": [5, 6, 7],
12+
"state": "ACTIVE"
13+
},
14+
{
15+
"pipelineId": 12345,
16+
"name": "test",
17+
"jobIds": [],
18+
"state": "ARCHIVED"
19+
}
20+
]

‎test/plugins/pipelines.test.js

+92
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const testCollection = require('./data/collection.json');
1313
const gitlabTestPipelines = require('./data/pipelinesFromGitlab.json');
1414
const testJob = require('./data/job.json');
1515
const testJobs = require('./data/jobs.json');
16+
const testStages = require('./data/stages.json');
1617
const testTriggers = require('./data/triggers.json');
1718
const testBuild = require('./data/buildWithSteps.json');
1819
const testBuilds = require('./data/builds.json').slice(0, 2);
@@ -137,6 +138,22 @@ const getSecretsMocks = secrets => {
137138
return decorateJobMock(secrets);
138139
};
139140

141+
const decorateStageMock = stage => {
142+
const mock = hoek.clone(stage);
143+
144+
mock.toJson = sinon.stub().returns(stage);
145+
146+
return mock;
147+
};
148+
149+
const getStagesMocks = stages => {
150+
if (Array.isArray(stages)) {
151+
return stages.map(decorateStageMock);
152+
}
153+
154+
return decorateStageMock(stages);
155+
};
156+
140157
const getUserMock = user => {
141158
const mock = hoek.clone(user);
142159

@@ -170,6 +187,7 @@ describe('pipeline plugin test', () => {
170187
let tokenFactoryMock;
171188
let bannerFactoryMock;
172189
let jobFactoryMock;
190+
let stageFactoryMock;
173191
let triggerFactoryMock;
174192
let secretFactoryMock;
175193
let bannerMock;
@@ -223,6 +241,9 @@ describe('pipeline plugin test', () => {
223241
create: sinon.stub().resolves(null),
224242
list: sinon.stub().resolves(null)
225243
};
244+
stageFactoryMock = {
245+
list: sinon.stub()
246+
};
226247
tokenFactoryMock = {
227248
get: sinon.stub(),
228249
create: sinon.stub()
@@ -261,6 +282,7 @@ describe('pipeline plugin test', () => {
261282
server.app = {
262283
eventFactory: eventFactoryMock,
263284
jobFactory: jobFactoryMock,
285+
stageFactory: stageFactoryMock,
264286
triggerFactory: triggerFactoryMock,
265287
pipelineFactory: pipelineFactoryMock,
266288
userFactory: userFactoryMock,
@@ -915,6 +937,76 @@ describe('pipeline plugin test', () => {
915937
});
916938
});
917939

940+
describe('GET /pipelines/{id}/stages', () => {
941+
const id = 123;
942+
let options;
943+
let pipelineMock;
944+
let stagesMocks;
945+
946+
beforeEach(() => {
947+
options = {
948+
method: 'GET',
949+
url: `/pipelines/${id}/stages`
950+
};
951+
pipelineMock = getPipelineMocks(testPipeline);
952+
stagesMocks = getStagesMocks(testStages);
953+
stageFactoryMock.list.resolves(stagesMocks);
954+
pipelineFactoryMock.get.resolves(pipelineMock);
955+
});
956+
957+
it('returns 200 for getting stages', () =>
958+
server.inject(options).then(reply => {
959+
assert.equal(reply.statusCode, 200);
960+
assert.calledWith(stageFactoryMock.list, {
961+
params: {
962+
pipelineId: id
963+
}
964+
});
965+
assert.deepEqual(reply.result, testStages);
966+
}));
967+
968+
it('returns 200 for getting stages with state passed in as query param', () => {
969+
options.url = `/pipelines/${id}/stages?state=ARCHIVED`;
970+
971+
return server.inject(options).then(reply => {
972+
assert.equal(reply.statusCode, 200);
973+
assert.calledWith(stageFactoryMock.list, {
974+
params: {
975+
pipelineId: id,
976+
state: 'ARCHIVED'
977+
}
978+
});
979+
assert.deepEqual(reply.result, testStages);
980+
});
981+
});
982+
983+
it('returns 400 for passing in string as pipeline id', () => {
984+
const stringId = 'test';
985+
986+
options.url = `/pipelines/${stringId}/stages`;
987+
988+
return server.inject(options).then(reply => {
989+
assert.equal(reply.statusCode, 400);
990+
});
991+
});
992+
993+
it('returns 404 for updating a pipeline that does not exist', () => {
994+
pipelineFactoryMock.get.resolves(null);
995+
996+
return server.inject(options).then(reply => {
997+
assert.equal(reply.statusCode, 404);
998+
});
999+
});
1000+
1001+
it('returns 500 when the datastore returns an error', () => {
1002+
pipelineFactoryMock.get.rejects(new Error('icantdothatdave'));
1003+
1004+
return server.inject(options).then(reply => {
1005+
assert.equal(reply.statusCode, 500);
1006+
});
1007+
});
1008+
});
1009+
9181010
describe('GET /pipelines/{id}/triggers', () => {
9191011
const id = 123;
9201012
let options;

0 commit comments

Comments
 (0)
Please sign in to comment.