You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by po...@apache.org on 2020/12/27 13:52:54 UTC
[airflow-cancel-workflow-runs] 38/44: Add feature of cancelling
future duplicates (enabled by default) (#9)
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 c8448eb1e435664b3731ea1ead2efa0d1bb83b5b
Author: Jarek Potiuk <ja...@potiuk.com>
AuthorDate: Fri Oct 30 19:32:07 2020 +0100
Add feature of cancelling future duplicates (enabled by default) (#9)
---
README.md | 27 ++++++++++++---------
action.yml | 5 ++++
dist/index.js | 71 +++++++++++++++++++++++++++++++++----------------------
src/main.ts | 76 +++++++++++++++++++++++++++++++++++++++--------------------
4 files changed, 115 insertions(+), 64 deletions(-)
diff --git a/README.md b/README.md
index 90c9a22..0a13160 100644
--- a/README.md
+++ b/README.md
@@ -106,17 +106,18 @@ and `schedule` events are no longer needed.
## Inputs
-| Input | Required | Default | Comment |
-|-------------------------|----------|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| `token` | yes | | The github token passed from `${{ secrets.GITHUB_TOKEN }}` |
-| `cancelMode` | no | `duplicates` | The mode to run cancel on. The available options are `duplicates`, `self`, `failedJobs`, `namedJobs` |
-| `sourceRunId` | no | | Useful only in `workflow_run` triggered events. It should be set to the id of the workflow triggering the run `${{ github.event.workflow_run.id }}` in case cancel operation should cancel the source workflow. |
-| `notifyPRCancel` | no | | Boolean. If set to true, it notifies the cancelled PRs with a comment containing reason why they are being cancelled. |
-| `notifyPRCancelMessage` | no | | Optional cancel message to use instead of the default one when notifyPRCancel is true. It is only used in 'self' cancelling mode. |
-| `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` cancel 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. |
-| `workflowFileName` | no | | Name of the workflow file. It can be used if you want to cancel a different workflow than yours. |
+| Input | Required | Default | Comment |
+|--------------------------|----------|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `token` | yes | | The github token passed from `${{ secrets.GITHUB_TOKEN }}` |
+| `cancelMode` | no | `duplicates` | The mode to run cancel on. The available options are `duplicates`, `self`, `failedJobs`, `namedJobs` |
+| `cancelFutureDuplicates` | no | true | 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. |
+| `sourceRunId` | no | | Useful only in `workflow_run` triggered events. It should be set to the id of the workflow triggering the run `${{ github.event.workflow_run.id }}` in case cancel operation should cancel the source workflow. |
+| `notifyPRCancel` | no | | Boolean. If set to true, it notifies the cancelled PRs with a comment containing reason why they are being cancelled. |
+| `notifyPRCancelMessage` | no | | Optional cancel message to use instead of the default one when notifyPRCancel is true. It is only used in 'self' cancelling mode. |
+| `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` cancel 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. |
+| `workflowFileName` | no | | Name of the workflow file. It can be used if you want to cancel a different workflow than yours. |
The job cancel modes work as follows:
@@ -205,6 +206,7 @@ jobs:
name: "Cancel duplicate workflow runs"
with:
cancelMode: duplicates
+ cancelFutureDuplicates: true
token: ${{ secrets.GITHUB_TOKEN }}
sourceRunId: ${{ github.event.workflow_run.id }}
notifyPRCancel: true
@@ -266,6 +268,7 @@ jobs:
name: "Cancel duplicate CI runs"
with:
cancelMode: duplicates
+ cancelFutureDuplicates: true
token: ${{ secrets.GITHUB_TOKEN }}
notifyPRCancel: true
notifyPRMessageStart: |
@@ -512,6 +515,7 @@ on:
uses: potiuk/cancel-workflow-runs@v2
with:
cancelMode: duplicates
+ cancelFutureDuplicates: true
token: ${{ secrets.GITHUB_TOKEN }}
workflowFileName: other_workflow.yml
```
@@ -553,6 +557,7 @@ jobs:
name: "Cancel duplicate workflow runs"
with:
cancelMode: duplicates
+ cancelFutureDuplicates: true
notifyPRCancel: true
```
diff --git a/action.yml b/action.yml
index 103d4e4..aaec312 100644
--- a/action.yml
+++ b/action.yml
@@ -36,6 +36,11 @@ inputs:
* `failedJobs` - cancels all runs that failed in jobs matching one of the regexps
* `namedJobs` - cancels runs where names of some jobs match some of regexps
required: false
+ cancelFutureDuplicates:
+ description: |
+ 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
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 a347a2c..69bd164 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -1622,7 +1622,7 @@ function getWorkflowRuns(octokit, statusValues, cancelMode, createListRunQuery)
return workflowRuns;
});
}
-function shouldBeCancelled(octokit, owner, repo, runItem, headRepo, cancelMode, sourceRunId, jobNamesRegexps, skipEventTypes) {
+function shouldBeCancelled(octokit, owner, repo, runItem, headRepo, cancelMode, cancelFutureDuplicates, sourceRunId, jobNamesRegexps, skipEventTypes) {
return __awaiter(this, void 0, void 0, function* () {
if ('completed' === runItem.status.toString()) {
core.info(`\nThe run ${runItem.id} is completed. Not cancelling it.\n`);
@@ -1651,17 +1651,17 @@ function shouldBeCancelled(octokit, owner, repo, runItem, headRepo, cancelMode,
else if (cancelMode === CancelMode.NAMED_JOBS) {
// Cancel all jobs that have failed jobs (no matter when started)
if (yield jobsMatchingNames(octokit, owner, repo, runItem.id, jobNamesRegexps, false)) {
- core.info(`\nSome jobs have matching names in ${runItem.id} . Cancelling it.\n`);
+ core.info(`\nSome jobs have matching names in ${runItem.id} . Returning it.\n`);
return true;
}
else {
- core.info(`\nNone of the jobs match name in ${runItem.id}. Not cancelling it.\n`);
+ core.info(`\nNone of the jobs match name in ${runItem.id}. Returning it.\n`);
return false;
}
}
else if (cancelMode === CancelMode.SELF) {
if (runItem.id === sourceRunId) {
- core.info(`\nCancelling the "source" run: ${runItem.id}.\n`);
+ core.info(`\nReturning the "source" run: ${runItem.id}.\n`);
return true;
}
else {
@@ -1675,16 +1675,20 @@ function shouldBeCancelled(octokit, owner, repo, runItem, headRepo, cancelMode,
`repo: ${runHeadRepo} (expected ${headRepo}). Not cancelling it\n`);
return false;
}
- if (runItem.id === sourceRunId) {
- core.info(`\nThis is my own run ${runItem.id}. I have self-preservation mechanism. Not cancelling myself!\n`);
- return false;
- }
- else if (runItem.id > sourceRunId) {
- core.info(`\nThe run ${runItem.id} is started later than mt own run ${sourceRunId}. Not cancelling it\n`);
- return false;
+ if (cancelFutureDuplicates) {
+ core.info(`\nCancel Future Duplicates: Returning run id that might be duplicate or my own run: ${runItem.id}.\n`);
+ return true;
}
else {
- core.info(`\nCancelling duplicate of my own run: ${runItem.id}.\n`);
+ if (runItem.id === sourceRunId) {
+ core.info(`\nThis is my own run ${runItem.id}. Not returning myself!\n`);
+ return false;
+ }
+ else if (runItem.id > sourceRunId) {
+ core.info(`\nThe run ${runItem.id} is started later than my own run ${sourceRunId}. Not returning it\n`);
+ return false;
+ }
+ core.info(`\nFound duplicate of my own run: ${runItem.id}.\n`);
return true;
}
}
@@ -1710,7 +1714,7 @@ function cancelRun(octokit, owner, repo, runId) {
}
});
}
-function findAndCancelRuns(octokit, selfRunId, sourceWorkflowId, sourceRunId, owner, repo, headRepo, headBranch, sourceEventName, cancelMode, notifyPRCancel, notifyPRMessageStart, jobNameRegexps, skipEventTypes, reason) {
+function findAndCancelRuns(octokit, selfRunId, sourceWorkflowId, sourceRunId, owner, repo, headRepo, headBranch, sourceEventName, cancelMode, cancelFutureDuplicates, notifyPRCancel, notifyPRMessageStart, jobNameRegexps, skipEventTypes, reason) {
return __awaiter(this, void 0, void 0, function* () {
const statusValues = ['queued', 'in_progress'];
const workflowRuns = yield getWorkflowRuns(octokit, statusValues, cancelMode, function (status) {
@@ -1735,29 +1739,39 @@ function findAndCancelRuns(octokit, selfRunId, sourceWorkflowId, sourceRunId, ow
throw Error(`\nWrong cancel mode ${cancelMode}! This should never happen.\n`);
}
});
- const idsToCancel = [];
+ const workflowsToCancel = [];
const pullRequestToNotify = [];
for (const [key, runItem] of workflowRuns) {
- core.info(`\nChecking run number: ${key}, RunId: ${runItem.id}, Url: ${runItem.url}. Status ${runItem.status}\n`);
- if (yield shouldBeCancelled(octokit, owner, repo, runItem, headRepo, cancelMode, sourceRunId, jobNameRegexps, skipEventTypes)) {
+ core.info(`\nChecking run number: ${key}, RunId: ${runItem.id}, Url: ${runItem.url}. Status ${runItem.status},` +
+ ` Created at ${runItem.created_at}\n`);
+ if (yield shouldBeCancelled(octokit, owner, repo, runItem, headRepo, cancelMode, cancelFutureDuplicates, sourceRunId, jobNameRegexps, skipEventTypes)) {
if (notifyPRCancel && runItem.event === 'pull_request') {
const pullRequest = yield findPullRequest(octokit, owner, repo, runItem.head_repository.owner.login, runItem.head_branch, runItem.head_sha);
if (pullRequest) {
pullRequestToNotify.push(pullRequest.number);
}
}
- idsToCancel.push(runItem.id);
+ workflowsToCancel.push([runItem.id, runItem.created_at]);
}
}
- // Sort from smallest number - this way we always kill current one at the end (if we kill it at all)
- const sortedIdsToCancel = idsToCancel.sort((id1, id2) => id1 - id2);
- if (sortedIdsToCancel.length > 0) {
+ // Sort from most recent date - this way we always kill current one at the end (if we kill it at all)
+ const sortedRunTuplesToCancel = workflowsToCancel.sort((runTuple1, runTuple2) => runTuple2[1].localeCompare(runTuple1[1]));
+ if (sortedRunTuplesToCancel.length > 0) {
+ if (cancelMode === CancelMode.DUPLICATES && cancelFutureDuplicates) {
+ core.info(`\nSkipping the first run (${sortedRunTuplesToCancel[0]}) of all the matching ` +
+ `duplicates - this one we are going to leave in peace!\n`);
+ sortedRunTuplesToCancel.shift();
+ }
+ if (sortedRunTuplesToCancel.length === 0) {
+ core.info(`\nNo duplicates to cancel!\n`);
+ return sortedRunTuplesToCancel.map(runTuple => runTuple[0]);
+ }
core.info('\n###### Cancelling runs starting from the oldest ##########\n' +
- `\n Runs to cancel: ${sortedIdsToCancel.length}\n` +
+ `\n Runs to cancel: ${sortedRunTuplesToCancel.length}\n` +
`\n PRs to notify: ${pullRequestToNotify.length}\n`);
- for (const runId of sortedIdsToCancel) {
- core.info(`\nCancelling run: ${runId}.\n`);
- yield cancelRun(octokit, owner, repo, runId);
+ for (const runTuple of sortedRunTuplesToCancel) {
+ core.info(`\nCancelling run: ${runTuple}.\n`);
+ yield cancelRun(octokit, owner, repo, runTuple[0]);
}
for (const pullRequestNumber of pullRequestToNotify) {
const selfWorkflowRunUrl = `https://github.com/${owner}/${repo}/actions/runs/${selfRunId}`;
@@ -1768,7 +1782,7 @@ function findAndCancelRuns(octokit, selfRunId, sourceWorkflowId, sourceRunId, ow
else {
core.info('\n###### There are no runs to cancel! ##########\n');
}
- return sortedIdsToCancel;
+ return sortedRunTuplesToCancel.map(runTuple => runTuple[0]);
});
}
function getRequiredEnv(key) {
@@ -1837,7 +1851,7 @@ function getOrigin(octokit, runId, owner, repo) {
];
});
}
-function performCancelJob(octokit, selfRunId, sourceWorkflowId, sourceRunId, owner, repo, headRepo, headBranch, sourceEventName, cancelMode, notifyPRCancel, notifyPRCancelMessage, notifyPRMessageStart, jobNameRegexps, skipEventTypes) {
+function performCancelJob(octokit, selfRunId, sourceWorkflowId, sourceRunId, owner, repo, headRepo, headBranch, sourceEventName, cancelMode, notifyPRCancel, notifyPRCancelMessage, notifyPRMessageStart, jobNameRegexps, skipEventTypes, cancelFutureDuplicates) {
return __awaiter(this, void 0, void 0, function* () {
core.info('\n###################################################################################\n');
core.info(`All parameters: owner: ${owner}, repo: ${repo}, run id: ${sourceRunId}, ` +
@@ -1867,7 +1881,7 @@ function performCancelJob(octokit, selfRunId, sourceWorkflowId, sourceRunId, own
throw Error(`Wrong cancel mode ${cancelMode}! This should never happen.`);
}
core.info('\n###################################################################################\n');
- return yield findAndCancelRuns(octokit, selfRunId, sourceWorkflowId, sourceRunId, owner, repo, headRepo, headBranch, sourceEventName, cancelMode, notifyPRCancel, notifyPRMessageStart, jobNameRegexps, skipEventTypes, reason);
+ return yield findAndCancelRuns(octokit, selfRunId, sourceWorkflowId, sourceRunId, owner, repo, headRepo, headBranch, sourceEventName, cancelMode, cancelFutureDuplicates, notifyPRCancel, notifyPRMessageStart, jobNameRegexps, skipEventTypes, reason);
});
}
function verboseOutput(name, value) {
@@ -1887,6 +1901,7 @@ function run() {
const notifyPRMessageStart = core.getInput('notifyPRMessageStart');
const sourceRunId = parseInt(core.getInput('sourceRunId')) || selfRunId;
const jobNameRegexpsString = core.getInput('jobNameRegexps');
+ const cancelFutureDuplicates = (core.getInput('cancelFutureDuplicates') || 'true').toLowerCase() === 'true';
const jobNameRegexps = jobNameRegexpsString
? JSON.parse(jobNameRegexpsString)
: [];
@@ -1944,7 +1959,7 @@ function run() {
body: `${notifyPRMessageStart} [The workflow run](${selfWorkflowRunUrl})`
});
}
- const cancelledRuns = yield performCancelJob(octokit, selfRunId, sourceWorkflowId, sourceRunId, owner, repo, headRepo, headBranch, sourceEventName, cancelMode, notifyPRCancel, notifyPRCancelMessage, notifyPRMessageStart, jobNameRegexps, skipEventTypes);
+ const cancelledRuns = yield performCancelJob(octokit, selfRunId, sourceWorkflowId, sourceRunId, owner, repo, headRepo, headBranch, sourceEventName, cancelMode, notifyPRCancel, notifyPRCancelMessage, notifyPRMessageStart, jobNameRegexps, skipEventTypes, cancelFutureDuplicates);
verboseOutput('cancelledRuns', JSON.stringify(cancelledRuns));
});
}
diff --git a/src/main.ts b/src/main.ts
index 9f60f3d..71da8d9 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -203,6 +203,7 @@ async function shouldBeCancelled(
runItem: rest.ActionsListWorkflowRunsResponseWorkflowRunsItem,
headRepo: string,
cancelMode: CancelMode,
+ cancelFutureDuplicates: boolean,
sourceRunId: number,
jobNamesRegexps: string[],
skipEventTypes: string[]
@@ -259,18 +260,18 @@ async function shouldBeCancelled(
)
) {
core.info(
- `\nSome jobs have matching names in ${runItem.id} . Cancelling it.\n`
+ `\nSome jobs have matching names in ${runItem.id} . Returning it.\n`
)
return true
} else {
core.info(
- `\nNone of the jobs match name in ${runItem.id}. Not cancelling it.\n`
+ `\nNone of the jobs match name in ${runItem.id}. Returning it.\n`
)
return false
}
} else if (cancelMode === CancelMode.SELF) {
if (runItem.id === sourceRunId) {
- core.info(`\nCancelling the "source" run: ${runItem.id}.\n`)
+ core.info(`\nReturning the "source" run: ${runItem.id}.\n`)
return true
} else {
return false
@@ -284,18 +285,22 @@ async function shouldBeCancelled(
)
return false
}
- if (runItem.id === sourceRunId) {
+ if (cancelFutureDuplicates) {
core.info(
- `\nThis is my own run ${runItem.id}. I have self-preservation mechanism. Not cancelling myself!\n`
+ `\nCancel Future Duplicates: Returning run id that might be duplicate or my own run: ${runItem.id}.\n`
)
- return false
- } else if (runItem.id > sourceRunId) {
- core.info(
- `\nThe run ${runItem.id} is started later than mt own run ${sourceRunId}. Not cancelling it\n`
- )
- return false
+ return true
} else {
- core.info(`\nCancelling duplicate of my own run: ${runItem.id}.\n`)
+ if (runItem.id === sourceRunId) {
+ core.info(`\nThis is my own run ${runItem.id}. Not returning myself!\n`)
+ return false
+ } else if (runItem.id > sourceRunId) {
+ core.info(
+ `\nThe run ${runItem.id} is started later than my own run ${sourceRunId}. Not returning it\n`
+ )
+ return false
+ }
+ core.info(`\nFound duplicate of my own run: ${runItem.id}.\n`)
return true
}
} else {
@@ -338,6 +343,7 @@ async function findAndCancelRuns(
headBranch: string,
sourceEventName: string,
cancelMode: CancelMode,
+ cancelFutureDuplicates: boolean,
notifyPRCancel: boolean,
notifyPRMessageStart: string,
jobNameRegexps: string[],
@@ -400,11 +406,12 @@ async function findAndCancelRuns(
}
}
)
- const idsToCancel: number[] = []
+ const workflowsToCancel: [number, string][] = []
const pullRequestToNotify: number[] = []
for (const [key, runItem] of workflowRuns) {
core.info(
- `\nChecking run number: ${key}, RunId: ${runItem.id}, Url: ${runItem.url}. Status ${runItem.status}\n`
+ `\nChecking run number: ${key}, RunId: ${runItem.id}, Url: ${runItem.url}. Status ${runItem.status},` +
+ ` Created at ${runItem.created_at}\n`
)
if (
await shouldBeCancelled(
@@ -414,6 +421,7 @@ async function findAndCancelRuns(
runItem,
headRepo,
cancelMode,
+ cancelFutureDuplicates,
sourceRunId,
jobNameRegexps,
skipEventTypes
@@ -432,20 +440,33 @@ async function findAndCancelRuns(
pullRequestToNotify.push(pullRequest.number)
}
}
- idsToCancel.push(runItem.id)
+ workflowsToCancel.push([runItem.id, runItem.created_at])
}
}
- // Sort from smallest number - this way we always kill current one at the end (if we kill it at all)
- const sortedIdsToCancel = idsToCancel.sort((id1, id2) => id1 - id2)
- if (sortedIdsToCancel.length > 0) {
+ // Sort from most recent date - this way we always kill current one at the end (if we kill it at all)
+ const sortedRunTuplesToCancel = workflowsToCancel.sort(
+ (runTuple1, runTuple2) => runTuple2[1].localeCompare(runTuple1[1])
+ )
+ if (sortedRunTuplesToCancel.length > 0) {
+ if (cancelMode === CancelMode.DUPLICATES && cancelFutureDuplicates) {
+ core.info(
+ `\nSkipping the first run (${sortedRunTuplesToCancel[0]}) of all the matching ` +
+ `duplicates - this one we are going to leave in peace!\n`
+ )
+ sortedRunTuplesToCancel.shift()
+ }
+ if (sortedRunTuplesToCancel.length === 0) {
+ core.info(`\nNo duplicates to cancel!\n`)
+ return sortedRunTuplesToCancel.map(runTuple => runTuple[0])
+ }
core.info(
'\n###### Cancelling runs starting from the oldest ##########\n' +
- `\n Runs to cancel: ${sortedIdsToCancel.length}\n` +
+ `\n Runs to cancel: ${sortedRunTuplesToCancel.length}\n` +
`\n PRs to notify: ${pullRequestToNotify.length}\n`
)
- for (const runId of sortedIdsToCancel) {
- core.info(`\nCancelling run: ${runId}.\n`)
- await cancelRun(octokit, owner, repo, runId)
+ for (const runTuple of sortedRunTuplesToCancel) {
+ core.info(`\nCancelling run: ${runTuple}.\n`)
+ await cancelRun(octokit, owner, repo, runTuple[0])
}
for (const pullRequestNumber of pullRequestToNotify) {
const selfWorkflowRunUrl = `https://github.com/${owner}/${repo}/actions/runs/${selfRunId}`
@@ -465,7 +486,7 @@ async function findAndCancelRuns(
'\n###### There are no runs to cancel! ##########\n'
)
}
- return sortedIdsToCancel
+ return sortedRunTuplesToCancel.map(runTuple => runTuple[0])
}
function getRequiredEnv(key: string): string {
@@ -581,7 +602,8 @@ async function performCancelJob(
notifyPRCancelMessage: string,
notifyPRMessageStart: string,
jobNameRegexps: string[],
- skipEventTypes: string[]
+ skipEventTypes: string[],
+ cancelFutureDuplicates: boolean
): Promise<number[]> {
core.info(
'\n###################################################################################\n'
@@ -635,6 +657,7 @@ async function performCancelJob(
headBranch,
sourceEventName,
cancelMode,
+ cancelFutureDuplicates,
notifyPRCancel,
notifyPRMessageStart,
jobNameRegexps,
@@ -662,6 +685,8 @@ async function run(): Promise<void> {
const notifyPRMessageStart = core.getInput('notifyPRMessageStart')
const sourceRunId = parseInt(core.getInput('sourceRunId')) || selfRunId
const jobNameRegexpsString = core.getInput('jobNameRegexps')
+ const cancelFutureDuplicates =
+ (core.getInput('cancelFutureDuplicates') || 'true').toLowerCase() === 'true'
const jobNameRegexps = jobNameRegexpsString
? JSON.parse(jobNameRegexpsString)
: []
@@ -761,7 +786,8 @@ async function run(): Promise<void> {
notifyPRCancelMessage,
notifyPRMessageStart,
jobNameRegexps,
- skipEventTypes
+ skipEventTypes,
+ cancelFutureDuplicates
)
verboseOutput('cancelledRuns', JSON.stringify(cancelledRuns))