Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lint/Prettier + Fixes from #59 (batch cancel + list 100) + Cancel all but latest #35 #62

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .eslintignore
@@ -0,0 +1,2 @@
dist/
node_modules/
59 changes: 59 additions & 0 deletions .eslintrc.json
@@ -0,0 +1,59 @@
{
"plugins": ["jest", "@typescript-eslint"],
mikehardy marked this conversation as resolved.
Show resolved Hide resolved
"extends": ["plugin:github/es6"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 9,
"sourceType": "module",
"project": "./tsconfig.json"
},
"rules": {
"eslint-comments/no-use": "off",
"import/no-namespace": "off",
"no-unused-vars": "off",
"no-console": "off",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
"@typescript-eslint/no-require-imports": "error",
"@typescript-eslint/array-type": "error",
"@typescript-eslint/await-thenable": "error",
"@typescript-eslint/ban-ts-ignore": "error",
"camelcase": "off",
"@typescript-eslint/class-name-casing": "error",
"@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
"@typescript-eslint/func-call-spacing": ["error", "never"],
"@typescript-eslint/generic-type-naming": ["error", "^[A-Z][A-Za-z]*$"],
"@typescript-eslint/no-array-constructor": "error",
"@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-extraneous-class": "error",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-for-in-array": "error",
"@typescript-eslint/no-inferrable-types": "error",
"@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-non-null-assertion": "warn",
"@typescript-eslint/no-object-literal-type-assertion": "error",
"@typescript-eslint/no-unnecessary-qualifier": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-useless-constructor": "error",
"@typescript-eslint/no-var-requires": "error",
"@typescript-eslint/prefer-for-of": "warn",
"@typescript-eslint/prefer-function-type": "warn",
"@typescript-eslint/prefer-includes": "error",
"@typescript-eslint/prefer-interface": "error",
"@typescript-eslint/prefer-string-starts-ends-with": "error",
"@typescript-eslint/promise-function-async": "error",
"@typescript-eslint/require-array-sort-compare": "error",
"@typescript-eslint/restrict-plus-operands": "error",
"semi": "off",
"@typescript-eslint/semi": ["error", "always"],
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/unbound-method": "error"
},
"env": {
"node": true,
"es6": true,
"jest/globals": true
}
}
22 changes: 22 additions & 0 deletions .github/workflows/cancel-all-but-latest.yml
@@ -0,0 +1,22 @@
name: Cancel All But Latest

on:
push:
workflow_dispatch:

jobs:
task:
# Use macOS because it has a 5 concurrent job limit: easier to test by manual enqueue
# https://docs.github.com/en/actions/reference/usage-limits-billing-and-administration#usage-limits
runs-on: macos-latest
name: Task
steps:
- name: Checkout
uses: actions/checkout@v2
- uses: actions/setup-node@v2
- name: Test Step
uses: ./ # Uses an action in the root directory
with:
all_but_latest: true
ignore_sha: true
- run: echo 'Sleeping...'; sleep 240; echo 'Done.';
8 changes: 4 additions & 4 deletions .github/workflows/cancel-self.yml
@@ -1,6 +1,8 @@
name: Cancel Self

on: [push]
on:
push:
workflow_dispatch:

jobs:
task:
Expand All @@ -9,9 +11,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
- uses: actions/setup-node@v2
- name: Test Step
uses: ./ # Uses an action in the root directory
with:
access_token: ${{ github.token }}
- uses: actions/setup-node@v1
- run: echo 'Sleeping...'; sleep 120; echo 'Done.';
19 changes: 19 additions & 0 deletions .github/workflows/lint.yml
@@ -0,0 +1,19 @@
name: Build and Analyze

on:
pull_request:
push:
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v2
with:
node-version: v14
- uses: actions/checkout@v2
- run: yarn
- run: yarn build
- run: yarn format-check
- run: yarn lint
4 changes: 4 additions & 0 deletions .husky/pre-commit
@@ -0,0 +1,4 @@
#!/bin/sh
yarn build && git add dist/index.js
yarn format-check
yarn lint
2 changes: 2 additions & 0 deletions .prettierignore
@@ -0,0 +1,2 @@
dist/
node_modules/
11 changes: 11 additions & 0 deletions .prettierrc.json
@@ -0,0 +1,11 @@
{
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"trailingComma": "none",
"bracketSpacing": true,
"arrowParens": "avoid",
"parser": "typescript"
}
15 changes: 15 additions & 0 deletions README.md
Expand Up @@ -100,6 +100,21 @@ jobs:
workflow_id: 479426
```

### Advanced: Cancel All But Latest

In some cases, you may wish to cancel all workflow runs except the newest one. This can help if you have very deep workflow queues and you find that the newer runs are not even executing thus they cannot clear out of date runs. In this mode, the out-of-date workflow runs will cancel all but the latest run and themselves, freeing up the workflow queue.

```yml
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.8.0
with:
all_but_latest: true
```

## Contributing

- Clone this repo
Expand Down
7 changes: 5 additions & 2 deletions action.yml
Expand Up @@ -9,9 +9,12 @@ inputs:
description: 'Optional - A single Workflow ID or a comma separated list of IDs'
required: false
ignore_sha:
description: 'Optional - Allow canceling other workflows with the same SHA. Useful for the `pull_request.closed` event.'
description: 'Optional - Allow canceling other workflows with the same SHA. Useful for `pull_request.closed` or `workflow_dispatch` event.'
required: false
default: 'false'
all_but_latest:
description: "Optional - Cancel all but the most recent action, can help with very deep queues"
required: false
default: false
access_token:
description: 'Your GitHub Access Token, defaults to: {{ github.token }}'
default: '${{ github.token }}'
Expand Down
76 changes: 56 additions & 20 deletions dist/index.js
Expand Up @@ -5862,8 +5862,9 @@ async function main() {
}
console.log({ eventName, sha, headSha, branch, owner, repo, GITHUB_RUN_ID });
const token = core.getInput('access_token', { required: true });
const workflow_id = core.getInput('workflow_id', { required: false });
const workflow_id_input = core.getInput('workflow_id', { required: false });
const ignore_sha = core.getInput('ignore_sha', { required: false }) === 'true';
const all_but_latest = core.getInput('all_but_latest', { required: false }) === 'true';
console.log(`Found token: ${token ? 'yes' : 'no'}`);
const workflow_ids = [];
const octokit = github.getOctokit(token);
Expand All @@ -5872,38 +5873,55 @@ async function main() {
repo,
run_id: Number(GITHUB_RUN_ID)
});
if (workflow_id) {
workflow_id.replace(/\s/g, '')
.split(',')
.forEach(n => workflow_ids.push(n));
if (workflow_id_input) {
const workflow_ids_input = workflow_id_input.replace(/\s/g, '').split(',');
for (const id of workflow_ids_input) {
workflow_ids.push(parseInt(id, 10));
}
}
else {
workflow_ids.push(String(current_run.workflow_id));
workflow_ids.push(current_run.workflow_id);
}
console.log(`Found workflow_id: ${JSON.stringify(workflow_ids)}`);
await Promise.all(workflow_ids.map(async (workflow_id) => {
try {
const { data } = await octokit.actions.listWorkflowRuns({
per_page: 100,
owner,
repo,
workflow_id,
branch,
branch
});
const branchWorkflows = data.workflow_runs.filter(run => run.head_branch === branch);
console.log(`Found ${branchWorkflows.length} runs for workflow ${workflow_id} on branch ${branch}`);
console.log(branchWorkflows.map(run => `- ${run.html_url}`).join('\n'));
const runningWorkflows = branchWorkflows.filter(run => (ignore_sha || run.head_sha !== headSha) &&
run.status !== 'completed' &&
new Date(run.created_at) < new Date(current_run.created_at));
console.log(`with ${runningWorkflows.length} runs to cancel.`);
for (const { id, head_sha, status, html_url } of runningWorkflows) {
console.log('Canceling run: ', { id, head_sha, status, html_url });
const res = await octokit.actions.cancelWorkflowRun({
owner,
repo,
run_id: id
});
console.log(`Cancel run ${id} responded with status ${res.status}`);
console.log(branchWorkflows.map(run => `- ${run.html_url} @ ${run.created_at}`).join('\n'));
let runningWorkflows = branchWorkflows.filter(run => run.status !== 'completed');
console.log(`${runningWorkflows.length} of the workflows are not completed`);
runningWorkflows = runningWorkflows.filter(run => {
console.log(`SHA info: ignore? ${ignore_sha} / ${run.head_sha} !== ${headSha} ?`);
return ignore_sha || run.head_sha !== headSha;
});
console.log(`${runningWorkflows.length} of those workflows pass the SHA filter`);
let cancel_before;
if (all_but_latest) {
cancel_before = new Date(data.workflow_runs.reduce((a, b) => new Date(a.created_at) > new Date(b.created_at) ? a : b).created_at);
}
else {
cancel_before = new Date(current_run.created_at);
}
console.log(`Canceling matching runs enqueued before ${cancel_before}`);
runningWorkflows = runningWorkflows.filter(run => {
if (all_but_latest && run.id === current_run.id) {
return false;
}
return new Date(run.created_at) < cancel_before;
});
console.log(`${runningWorkflows.length} of the workflows are in a time range to cancel`);
console.log(`${runningWorkflows.length} matching runs to cancel.`);
await cancelWorkflowRuns(runningWorkflows, owner, repo, token);
if (all_but_latest && new Date(current_run.created_at) < cancel_before) {
console.log('in all_but_latest mode and canceling ourselves now');
await cancelWorkflowRuns([current_run], owner, repo, token);
}
}
catch (e) {
Expand All @@ -5913,6 +5931,24 @@ async function main() {
console.log('');
}));
}
async function cancelWorkflowRuns(runningWorkflows, owner, repo, token) {
const octokit = github.getOctokit(token);
const promises = [];
for (const { id, head_sha, status, html_url } of runningWorkflows) {
console.log('Canceling run: ', { id, head_sha, status, html_url });
const current_promise = octokit.actions
.cancelWorkflowRun({
owner,
repo,
run_id: id
})
.then(res => {
console.log(`Cancel run ${id} responded with status ${res.status}`);
});
promises.push(current_promise);
}
await Promise.all(promises);
}
main()
.then(() => core.info('Cancel Complete.'))
.catch(e => core.setFailed(e.message));
Expand Down
14 changes: 12 additions & 2 deletions package.json
Expand Up @@ -4,14 +4,24 @@
"main": "dist/index.js",
"license": "MIT",
"scripts": {
"build": "ncc build src/index.ts --license LICENSES.txt"
"build": "ncc build src/index.ts --license LICENSES.txt",
"format": "prettier --write '**/*.ts'",
"format-check": "prettier --check '**/*.ts'",
"lint": "eslint src/**/*.ts",
"prepare": "husky install"
},
"dependencies": {
"@actions/core": "1.2.6",
"@actions/github": "4.0.0"
},
"devDependencies": {
"@typescript-eslint/parser": "^2.8.0",
"@vercel/ncc": "0.27.0",
"typescript": "4.1.5"
"eslint": "^5.16.0",
"eslint-plugin-github": "^2.0.0",
"eslint-plugin-jest": "^22.21.0",
"prettier": "2.2.1",
"typescript": "4.1.5",
"husky": "^6.0.0"
}
}