-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
143 lines (114 loc) · 3.55 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
const { send, json } = require('micro');
const fetch = require('node-fetch');
const flatten = array => [].concat.apply([], array);
const MAX_RETRIES = isNaN(process.env.MAX_RETRIES)
? 2
: process.env.MAX_RETRIES;
const WAIT_TIME = isNaN(process.env.WAIT_TIME) ? 10000 : process.env.WAIT_TIME;
const CIRCLE_CI_ACTIVE_STATUS = [
'running',
'queued',
'scheduled',
'not_running',
];
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const circleciApiEndpoint = (repo, url = '') => {
const { CIRCLE_TOKEN } = process.env;
if (!CIRCLE_TOKEN) {
console.error('Environment variable CIRCLE_TOKEN is missing.');
}
return `https://circleci.com/api/v1.1/project/github/${repo}${url}?circle-token=${CIRCLE_TOKEN}`;
};
const checkIfCircleHaveCommit = async (repo, commit) => {
const res = await fetch(circleciApiEndpoint(repo));
const builds = await res.json();
const isHavingCommit =
builds &&
builds.length > 0 &&
builds.some(({ vcs_revision }) => vcs_revision === commit);
return isHavingCommit ? builds : false;
};
const waitForCommitOnCircle = async (repo, commit, retryCount = 0) => {
console.log(
`Waiting for commit (${commit}) to show up on CircleCI.`,
retryCount > 0 ? `(Retry ${retryCount})` : ''
);
const builds = await checkIfCircleHaveCommit(repo, commit);
if (builds) {
return builds;
}
if (retryCount >= MAX_RETRIES) {
return false;
}
await sleep(WAIT_TIME);
return await waitForCommitOnCircle(repo, commit, retryCount + 1);
};
module.exports = async (req, res) => {
const payload = await json(req);
if (!payload || !payload.repository) {
return send(res, 400);
}
const headCommitSha = payload.head_commit.id;
const repo = payload.repository.full_name;
console.log(
`Push event received with the following head commit SHA: ${headCommitSha}`
);
// Send 200 early to prevent timeout error from GitHub webhooks.
if (req.headers['user-agent'].includes('GitHub-Hookshot')) {
send(res, 200);
}
const builds = await waitForCommitOnCircle(repo, headCommitSha);
if (!builds) {
console.log('The commit has not showed up on CircleCi.');
return '';
}
const activeBuilds = builds.filter(
({ status, branch }) => branch && CIRCLE_CI_ACTIVE_STATUS.includes(status)
);
if (activeBuilds.length === 0) {
console.log('Nothing to do here.');
return '';
}
const branches = activeBuilds.reduce(
(acc, build) => ({
...acc,
[build.branch]: acc[build.branch]
? [...acc[build.branch], build]
: [build],
}),
{}
);
const branchesAndWorkflows = Object.entries(branches).reduce(
(acc, [key, value]) => ({
...acc,
[key]: value.reduce((acc, branch) => {
const workflowId = branch.workflows.workflow_id;
return {
...acc,
[workflowId]: acc[workflowId]
? [...acc[workflowId], branch]
: [branch],
};
}, {}),
}),
{}
);
return flatten(
Object.values(branchesAndWorkflows).map(branch => {
if (Object.keys(branch).length > 1) {
const buildsToCancel = Object.values(branch).sort(
(a, b) => new Date(a[0].author_date) - new Date(b[0].author_date)
);
buildsToCancel.pop();
return flatten(buildsToCancel).map(({ build_num }) => {
console.log(`Build #${build_num} will be canceled.`);
fetch(circleciApiEndpoint(repo, `/${build_num}/cancel`), {
method: 'POST',
});
return build_num;
});
}
return [];
})
);
};