Skip to content

Commit

Permalink
Adds self-preservation mechanism. (#12)
Browse files Browse the repository at this point in the history
This mechanism is useful in some edge-cases, especially when you
re-run a build and you base the cancelling on named jobs, the
jobs might get the name in the previous run and when re-running,
the action might cancel its own run.
  • Loading branch information
potiuk committed Nov 1, 2020
1 parent 19f8bd2 commit f06d03c
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 16 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ If you want a comprehensive solution, you should use the action as follows:
| `notifyPRMessageStart` | no | | Only for workflow_run events triggered by the PRs. If not empty, it notifies those PRs with the message specified at the start of the workflow - adding the link to the triggered workflow_run. |
| `jobNameRegexps` | no | | An array of job name regexps. Only runs containing any job name matching any of of the regexp in this array are considered for cancelling in `failedJobs` and `namedJobs` and `allDuplicateNamedJobs` modes. |
| `skipEventTypes` | no | | Array of event names that should be skipped when cancelling (JSON-encoded string). This might be used in order to skip direct pushes or scheduled events. |
| `selfPreservation` | no | true | Do not cancel self. |
| `workflowFileName` | no | | Name of the workflow file. It can be used if you want to cancel a different workflow than yours. |


Expand Down Expand Up @@ -566,6 +567,11 @@ match.
Note that the match must be identical. If there are two jobs that have a different Branch
they will both match the same pattern, but they are not considered duplicates.

Also, this is one of the jobs It has also self-preservation turned off.
This means that in case the job determines that it is itself a duplicate it will cancel itself. That's
why checking for duplicates of self-workflow should be the last step in the cancelling process.


```yaml
on:
push:
Expand All @@ -584,7 +590,9 @@ jobs:
cancelMode: allDuplicatedNamedJobs
token: ${{ secrets.GITHUB_TOKEN }}
jobNameRegexps: '["Branch: .* Repo: .* Event: .* "]'
selfPreservation: false
notifyPRCancel: true

```


Expand Down
5 changes: 5 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ inputs:
In case of duplicate canceling, cancel also future duplicates leaving only the "freshest" running
job and not all the future jobs. By default it is set to true.
required: false
selfPreservation:
description: |
Do not cancel your own run. There are cases where selfPreservation should be disabled but it is
enabled by default. You can disable it by setting 'false' as value.
required: false
jobNameRegexps:
description: |
Array of job name regexps (JSON-encoded string). Used by `failedJobs` and `namedJobs` cancel modes
Expand Down
27 changes: 18 additions & 9 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2013,17 +2013,23 @@ function findPullRequestForRunItem(repositoryInfo, runItem) {
* @param cancelFutureDuplicates - whether to cancel future duplicates
* @param jobNameRegexps - regexps for job names
* @param skipEventTypes - array of event names to skip
* @param selfPreservation - whether the run will cancel itself if requested
* @param selfRunId - my own run id
* @param workflowRuns - map of workflow runs found
* @parm maps with string key and array of run items as value. The key might be
* * source group id (allDuplicates mode)
* * matching job name (allDuplicatedMatchingJobNames mode)
*/
function filterAndMapWorkflowRunsToGroups(repositoryInfo, triggeringRunInfo, cancelMode, cancelFutureDuplicates, jobNameRegexps, skipEventTypes, workflowRuns) {
function filterAndMapWorkflowRunsToGroups(repositoryInfo, triggeringRunInfo, cancelMode, cancelFutureDuplicates, jobNameRegexps, skipEventTypes, selfRunId, selfPreservation, workflowRuns) {
return __awaiter(this, void 0, void 0, function* () {
const mapOfWorkflowRunCandidates = new Map();
for (const [key, runItem] of workflowRuns) {
core.info(`\nChecking run number: ${key} RunId: ${runItem.id} Url: ${runItem.url} Status ${runItem.status}` +
` Created at ${runItem.created_at}\n`);
if (runItem.id === selfRunId && selfPreservation) {
core.info(`\nI have self-preservation built in. I refuse to cancel myself :)\n`);
continue;
}
yield checkCandidateForCancelling(repositoryInfo, runItem, triggeringRunInfo, cancelMode, cancelFutureDuplicates, jobNameRegexps, skipEventTypes, mapOfWorkflowRunCandidates);
}
return mapOfWorkflowRunCandidates;
Expand Down Expand Up @@ -2083,7 +2089,7 @@ function cancelAllRunsInTheGroup(repositoryInfo, sortedRunItems, notifyPRCancel,
yield notifyPR(repositoryInfo, selfRunId, pullRequestNumber, reason);
}
}
core.info(`\nCancelling run: ${runItem}.\n`);
core.info(`\nCancelling run: ${runItem.id}.\n`);
yield cancelRun(repositoryInfo, runItem.id);
cancelledRuns.push(runItem.id);
}
Expand Down Expand Up @@ -2143,13 +2149,14 @@ function cancelTheRunsPerGroup(repositoryInfo, mapOfWorkflowRunCandidatesCandida
* @param jobNameRegexps - array of regular expressions to match hob names in case of named modes
* @param skipEventTypes - array of event names that should be skipped
* @param reason - reason for cancelling
* @param selfPreservation - whether the run will cancel itself if requested
* @return array of canceled workflow run ids
*/
function findAndCancelRuns(repositoryInfo, selfRunId, triggeringRunInfo, cancelMode, cancelFutureDuplicates, notifyPRCancel, notifyPRMessageStart, jobNameRegexps, skipEventTypes, reason) {
function findAndCancelRuns(repositoryInfo, selfRunId, triggeringRunInfo, cancelMode, cancelFutureDuplicates, notifyPRCancel, notifyPRMessageStart, jobNameRegexps, skipEventTypes, reason, selfPreservation) {
return __awaiter(this, void 0, void 0, function* () {
const statusValues = ['queued', 'in_progress'];
const workflowRuns = yield getWorkflowRunsMatchingCriteria(repositoryInfo, statusValues, cancelMode, triggeringRunInfo);
const mapOfWorkflowRunCandidatesCandidatesToCancel = yield filterAndMapWorkflowRunsToGroups(repositoryInfo, triggeringRunInfo, cancelMode, cancelFutureDuplicates, jobNameRegexps, skipEventTypes, workflowRuns);
const mapOfWorkflowRunCandidatesCandidatesToCancel = yield filterAndMapWorkflowRunsToGroups(repositoryInfo, triggeringRunInfo, cancelMode, cancelFutureDuplicates, jobNameRegexps, skipEventTypes, selfRunId, selfPreservation, workflowRuns);
return yield cancelTheRunsPerGroup(repositoryInfo, mapOfWorkflowRunCandidatesCandidatesToCancel, cancelMode, cancelFutureDuplicates, notifyPRCancel, selfRunId, reason);
});
}
Expand Down Expand Up @@ -2207,15 +2214,16 @@ function getTriggeringRunInfo(repositoryInfo, runId) {
* @param selfRunId - number of own run id
* @param triggeringRunInfo - information about the workflow that triggered the run
* @param cancelMode - cancel mode used
* @param cancelFutureDuplicates - whether to cancel future duplicates for duplicate cancelling
* @param notifyPRCancel - whether to notify in PRs about cancelling
* @param notifyPRCancelMessage - message to send when cancelling the PR (overrides default message
* generated automatically)
* @param notifyPRMessageStart - whether to notify PRs when the action starts
* @param jobNameRegexps - array of regular expressions to match hob names in case of named modes
* @param skipEventTypes - array of event names that should be skipped
* @param cancelFutureDuplicates - whether to cancel future duplicates for duplicate cancelling
* @param selfPreservation - whether the run will cancel itself if requested
*/
function performCancelJob(repositoryInfo, selfRunId, triggeringRunInfo, cancelMode, notifyPRCancel, notifyPRCancelMessage, notifyPRMessageStart, jobNameRegexps, skipEventTypes, cancelFutureDuplicates) {
function performCancelJob(repositoryInfo, selfRunId, triggeringRunInfo, cancelMode, notifyPRCancel, notifyPRCancelMessage, notifyPRMessageStart, jobNameRegexps, skipEventTypes, cancelFutureDuplicates, selfPreservation) {
return __awaiter(this, void 0, void 0, function* () {
core.info('\n###################################################################################\n');
core.info(`All parameters: owner: ${repositoryInfo.owner}, repo: ${repositoryInfo.repo}, ` +
Expand Down Expand Up @@ -2259,7 +2267,7 @@ function performCancelJob(repositoryInfo, selfRunId, triggeringRunInfo, cancelMo
throw Error(`Wrong cancel mode ${cancelMode}! Please correct it.`);
}
core.info('\n###################################################################################\n');
return yield findAndCancelRuns(repositoryInfo, selfRunId, triggeringRunInfo, cancelMode, cancelFutureDuplicates, notifyPRCancel, notifyPRMessageStart, jobNameRegexps, skipEventTypes, reason);
return yield findAndCancelRuns(repositoryInfo, selfRunId, triggeringRunInfo, cancelMode, cancelFutureDuplicates, notifyPRCancel, notifyPRMessageStart, jobNameRegexps, skipEventTypes, reason, selfPreservation);
});
}
/**
Expand Down Expand Up @@ -2304,7 +2312,7 @@ function verboseOutput(name, value) {
}
/**
* Performs sanity check of the parameters passed. Some of the parameter combinations do not work so they
* are verified and in case od unexpected combination found, approrpriate error is raised.
* are verified and in case od unexpected combination found, appropriate error is raised.
*
* @param eventName - name of the event to act on
* @param runId - run id of the triggering event
Expand Down Expand Up @@ -2401,6 +2409,7 @@ function run() {
const notifyPRMessageStart = core.getInput('notifyPRMessageStart');
const sourceRunId = parseInt(core.getInput('sourceRunId')) || selfRunId;
const jobNameRegexpsString = core.getInput('jobNameRegexps');
const selfPreservation = (core.getInput('selfPreservation') || 'true').toLowerCase() === 'true';
const cancelFutureDuplicates = (core.getInput('cancelFutureDuplicates') || 'true').toLowerCase() === 'true';
const jobNameRegexps = jobNameRegexpsString
? JSON.parse(jobNameRegexpsString)
Expand All @@ -2427,7 +2436,7 @@ function run() {
const triggeringRunInfo = yield getTriggeringRunInfo(repositoryInfo, sourceRunId);
produceBasicOutputs(triggeringRunInfo);
yield notifyActionStart(repositoryInfo, triggeringRunInfo, selfRunId, notifyPRMessageStart);
const cancelledRuns = yield performCancelJob(repositoryInfo, selfRunId, triggeringRunInfo, cancelMode, notifyPRCancel, notifyPRCancelMessage, notifyPRMessageStart, jobNameRegexps, skipEventTypes, cancelFutureDuplicates);
const cancelledRuns = yield performCancelJob(repositoryInfo, selfRunId, triggeringRunInfo, cancelMode, notifyPRCancel, notifyPRCancelMessage, notifyPRMessageStart, jobNameRegexps, skipEventTypes, cancelFutureDuplicates, selfPreservation);
verboseOutput('cancelledRuns', JSON.stringify(cancelledRuns));
});
}
Expand Down
34 changes: 27 additions & 7 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,8 @@ async function findPullRequestForRunItem(
* @param cancelFutureDuplicates - whether to cancel future duplicates
* @param jobNameRegexps - regexps for job names
* @param skipEventTypes - array of event names to skip
* @param selfPreservation - whether the run will cancel itself if requested
* @param selfRunId - my own run id
* @param workflowRuns - map of workflow runs found
* @parm maps with string key and array of run items as value. The key might be
* * source group id (allDuplicates mode)
Expand All @@ -802,6 +804,8 @@ async function filterAndMapWorkflowRunsToGroups(
cancelFutureDuplicates: boolean,
jobNameRegexps: string[],
skipEventTypes: string[],
selfRunId: number,
selfPreservation: boolean,
workflowRuns: Map<
number,
rest.ActionsListWorkflowRunsResponseWorkflowRunsItem
Expand All @@ -815,6 +819,12 @@ async function filterAndMapWorkflowRunsToGroups(
`\nChecking run number: ${key} RunId: ${runItem.id} Url: ${runItem.url} Status ${runItem.status}` +
` Created at ${runItem.created_at}\n`
)
if (runItem.id === selfRunId && selfPreservation) {
core.info(
`\nI have self-preservation built in. I refuse to cancel myself :)\n`
)
continue
}
await checkCandidateForCancelling(
repositoryInfo,
runItem,
Expand Down Expand Up @@ -908,7 +918,7 @@ async function cancelAllRunsInTheGroup(
await notifyPR(repositoryInfo, selfRunId, pullRequestNumber, reason)
}
}
core.info(`\nCancelling run: ${runItem}.\n`)
core.info(`\nCancelling run: ${runItem.id}.\n`)
await cancelRun(repositoryInfo, runItem.id)
cancelledRuns.push(runItem.id)
}
Expand Down Expand Up @@ -999,6 +1009,7 @@ async function cancelTheRunsPerGroup(
* @param jobNameRegexps - array of regular expressions to match hob names in case of named modes
* @param skipEventTypes - array of event names that should be skipped
* @param reason - reason for cancelling
* @param selfPreservation - whether the run will cancel itself if requested
* @return array of canceled workflow run ids
*/
async function findAndCancelRuns(
Expand All @@ -1011,7 +1022,8 @@ async function findAndCancelRuns(
notifyPRMessageStart: string,
jobNameRegexps: string[],
skipEventTypes: string[],
reason: string
reason: string,
selfPreservation: boolean
): Promise<number[]> {
const statusValues = ['queued', 'in_progress']
const workflowRuns = await getWorkflowRunsMatchingCriteria(
Expand All @@ -1027,6 +1039,8 @@ async function findAndCancelRuns(
cancelFutureDuplicates,
jobNameRegexps,
skipEventTypes,
selfRunId,
selfPreservation,
workflowRuns
)
return await cancelTheRunsPerGroup(
Expand Down Expand Up @@ -1105,13 +1119,14 @@ async function getTriggeringRunInfo(
* @param selfRunId - number of own run id
* @param triggeringRunInfo - information about the workflow that triggered the run
* @param cancelMode - cancel mode used
* @param cancelFutureDuplicates - whether to cancel future duplicates for duplicate cancelling
* @param notifyPRCancel - whether to notify in PRs about cancelling
* @param notifyPRCancelMessage - message to send when cancelling the PR (overrides default message
* generated automatically)
* @param notifyPRMessageStart - whether to notify PRs when the action starts
* @param jobNameRegexps - array of regular expressions to match hob names in case of named modes
* @param skipEventTypes - array of event names that should be skipped
* @param cancelFutureDuplicates - whether to cancel future duplicates for duplicate cancelling
* @param selfPreservation - whether the run will cancel itself if requested
*/
async function performCancelJob(
repositoryInfo: RepositoryInfo,
Expand All @@ -1123,7 +1138,8 @@ async function performCancelJob(
notifyPRMessageStart: string,
jobNameRegexps: string[],
skipEventTypes: string[],
cancelFutureDuplicates: boolean
cancelFutureDuplicates: boolean,
selfPreservation: boolean
): Promise<number[]> {
core.info(
'\n###################################################################################\n'
Expand Down Expand Up @@ -1192,7 +1208,8 @@ async function performCancelJob(
notifyPRMessageStart,
jobNameRegexps,
skipEventTypes,
reason
reason,
selfPreservation
)
}

Expand Down Expand Up @@ -1242,7 +1259,7 @@ function verboseOutput(name: string, value: string): void {

/**
* Performs sanity check of the parameters passed. Some of the parameter combinations do not work so they
* are verified and in case od unexpected combination found, approrpriate error is raised.
* are verified and in case od unexpected combination found, appropriate error is raised.
*
* @param eventName - name of the event to act on
* @param runId - run id of the triggering event
Expand Down Expand Up @@ -1380,6 +1397,8 @@ async function run(): Promise<void> {
const notifyPRMessageStart = core.getInput('notifyPRMessageStart')
const sourceRunId = parseInt(core.getInput('sourceRunId')) || selfRunId
const jobNameRegexpsString = core.getInput('jobNameRegexps')
const selfPreservation =
(core.getInput('selfPreservation') || 'true').toLowerCase() === 'true'
const cancelFutureDuplicates =
(core.getInput('cancelFutureDuplicates') || 'true').toLowerCase() === 'true'
const jobNameRegexps = jobNameRegexpsString
Expand Down Expand Up @@ -1449,7 +1468,8 @@ async function run(): Promise<void> {
notifyPRMessageStart,
jobNameRegexps,
skipEventTypes,
cancelFutureDuplicates
cancelFutureDuplicates,
selfPreservation
)

verboseOutput('cancelledRuns', JSON.stringify(cancelledRuns))
Expand Down

0 comments on commit f06d03c

Please sign in to comment.