If you create GitHub Actions via GitHub’s UI by going to the URL of the form `https://github.com/
There are four problems with the default template
- No dependency caching – so package dependencies will be resolved and reinstalled every time
- No cancelation of stale executions – If you pushed a commit and before the tests finish, you decide to push another commit then the stale commits are not canceled. Rather they continue executing!
- No path filtering – So a change to README will trigger the execution of, for example, linters and tests!
- No timeouts – Rogue tests can run forever leading to resource exhaustion
All these are fixable.
- Dependency caching is language-specific – see the directions in the actions/cache repository.
- Canceling stale executions is easy. Just add
concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true
- Path filtering requires knowing the right dependencies but it is not hard. For example, for a job linting Python files, it will be
**.py
- A reasonable job-level timeout makes sense. Look at the past execution and put a limit of 2X based on that. For example, if a job takes 5 minutes on average,
timeout-minutes: 10
limits the job to 10 minutes.
Let’s consider a simple template that GitHub generates for building Python code and improving it.
# Template generated by GitHub # This workflow will install Python dependencies, run tests and lint with a single version of Python # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python name: Python application on: push: branches: [ "master" ] pull_request: branches: [ "master" ] permissions: contents: read jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python 3.10 uses: actions/setup-python@v3 with: python-version: "3.10" - name: Install dependencies run: | python -m pip install --upgrade pip pip install flake8 pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest run: | pytest
My improvements are marked with # Improvement:
comments
name: Python application on: push: branches: [ "master", "main" ] # Improvement #1: Filter on files that should trigger this workflow paths: - 'requirements.txt' - '**.py' # Assume that this is the path of this file in the repo - '.github/workflows/python-app.yml' pull_request: branches: [ "master", "main" ] # Improvement #1: Filter on files that should trigger this workflow paths: - 'requirements.txt' - '**.py' # Assume that this is the path of this file in the repo - '.github/workflows/python-app.yml' permissions: contents: read # Improvement #2: Cancel existing executions when new commits are pushed onto the branch concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: # Improvement #3: Rename the job name, this makes it easier to run locally # with a tool like https://github.com/nektos/act buildPythonApp: runs-on: ubuntu-latest # Improvement #4: Add a timeout of 15 mins timeout-minutes: 15 steps: - uses: actions/checkout@v3 - name: Set up Python 3.10 uses: actions/setup-python@v3 with: python-version: "3.10" # Improvement #5: Cache Python dependencies using the hash of "requirements.txt" as the key # This step must be executed before "pip install" - uses: actions/cache@v3 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} restore-keys: | ${{ runner.os }}-pip- - name: Install dependencies run: | python -m pip install --upgrade pip pip install flake8 pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest run: | pytest
Update: after getting a lot of positive feedback. I have open-sourced a project gabo to automate this. Feel free to try it out.
There is also a cache argument for the setup-python action that can be used instead of the separate cache action.
Hello,
Just to let you know that the setup-python action can handle the cache for you, cf. https://github.com/actions/setup-python/blob/main/action.yml#L10
Thanks.
I didn’t know that.
I will update the example with it.
I still think it is useful for people to know “actions/cache” step.
Some awesome GHA snippets from me here: https://github.com/yengoteam/awesome-gha-snippets
I think may useful for someone here 🙂
Thanks, Denis. Feel free to send a PR to improve https://github.com/ashishb/gabo
Hi,
In the point about canceling stale jobs, I see a problem with you approach, using github.workflow (the Workflow name) as a part in your concurrency group can cancel jobs from the same branch from another repository if you are using reusable workflows with a generic name (e.g. “Build Nodejs Backend”)
I think a better way to do this is using github.repository, ${{ github.repository }}-${{ github.ref }} , (that is owner/repository-name) to avoid weird issues
Pretty interesting point, Alex.
I have never seen such cancelation happening.
However, I will definitely experiment and if your suggestion is correct, I will update the blog post.