This is an automated email from the ASF dual-hosted git repository. potiuk pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/airflow-cancel-workflow-runs.git
commit f06d03cd576a179ea5169d048dbd8c8d73757b52 Author: Jarek Potiuk <[email protected]> AuthorDate: Sun Nov 1 17:41:54 2020 +0100 Adds self-preservation mechanism. (#12) 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. --- README.md | 8 ++++++++ action.yml | 5 +++++ dist/index.js | 27 ++++++++++++++++++--------- src/main.ts | 34 +++++++++++++++++++++++++++------- 4 files changed, 58 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index b212dd9..5f98862 100644 --- a/README.md +++ b/README.md @@ -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. | @@ -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: @@ -584,7 +590,9 @@ jobs: cancelMode: allDuplicatedNamedJobs token: ${{ secrets.GITHUB_TOKEN }} jobNameRegexps: '["Branch: .* Repo: .* Event: .* "]' + selfPreservation: false notifyPRCancel: true + ``` diff --git a/action.yml b/action.yml index 541094a..f678dc6 100644 --- a/action.yml +++ b/action.yml @@ -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 diff --git a/dist/index.js b/dist/index.js index 2a806db..09fdb5d 100644 --- a/dist/index.js +++ b/dist/index.js @@ -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; @@ -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); } @@ -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); }); } @@ -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}, ` + @@ -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); }); } /** @@ -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 @@ -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) @@ -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)); }); } diff --git a/src/main.ts b/src/main.ts index abf39f1..3c4eab1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -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) @@ -802,6 +804,8 @@ async function filterAndMapWorkflowRunsToGroups( cancelFutureDuplicates: boolean, jobNameRegexps: string[], skipEventTypes: string[], + selfRunId: number, + selfPreservation: boolean, workflowRuns: Map< number, rest.ActionsListWorkflowRunsResponseWorkflowRunsItem @@ -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, @@ -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) } @@ -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( @@ -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( @@ -1027,6 +1039,8 @@ async function findAndCancelRuns( cancelFutureDuplicates, jobNameRegexps, skipEventTypes, + selfRunId, + selfPreservation, workflowRuns ) return await cancelTheRunsPerGroup( @@ -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, @@ -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' @@ -1192,7 +1208,8 @@ async function performCancelJob( notifyPRMessageStart, jobNameRegexps, skipEventTypes, - reason + reason, + selfPreservation ) } @@ -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 @@ -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 @@ -1449,7 +1468,8 @@ async function run(): Promise<void> { notifyPRMessageStart, jobNameRegexps, skipEventTypes, - cancelFutureDuplicates + cancelFutureDuplicates, + selfPreservation ) verboseOutput('cancelledRuns', JSON.stringify(cancelledRuns))
