Skip to content
This repository was archived by the owner on Mar 13, 2024. It is now read-only.

Commit 4ccb601

Browse files
gilesknapcoretl
authored andcommitted
Rely on the container less
- Moved wheel and sdist creation to the dist job - Rely on the test matrix to run tests - Simplified container build to make minimal for build and runtime and use wheel from 'dist': only publish to GHCR for tagged builds - Create separate requirements-*.txt for each of the test matrix - Fix actions-gh-pages version and don't run it for dependabot - Move Dockerfile to .devcontainer and use as context to improve build times - Other minor improvements and simplifications
1 parent dd95a77 commit 4ccb601

File tree

11 files changed

+212
-176
lines changed

11 files changed

+212
-176
lines changed

.devcontainer/Dockerfile

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# This file is for use as a devcontainer and a runtime container
2+
#
3+
# The devcontainer should use the build target and run as root with podman
4+
# or docker with user namespaces.
5+
#
6+
FROM python:3.11 as build
7+
8+
ARG PIP_OPTIONS
9+
10+
# Add any system dependencies for the developer/build environment here e.g.
11+
# RUN apt-get update && apt-get upgrade -y && \
12+
# apt-get install -y --no-install-recommends \
13+
# desired-packages \
14+
# && rm -rf /var/lib/apt/lists/*
15+
16+
# set up a virtual environment and put it in PATH
17+
RUN python -m venv /venv
18+
ENV PATH=/venv/bin:$PATH
19+
20+
# Copy any required context for the pip install over
21+
COPY . /context
22+
WORKDIR /context
23+
24+
# install python package into /venv
25+
RUN pip install ${PIP_OPTIONS}
26+
27+
FROM python:3.11-slim as runtime
28+
29+
# Add apt-get system dependecies for runtime here if needed
30+
31+
# copy the virtual environment from the build stage and put it in PATH
32+
COPY --from=build /venv/ /venv/
33+
ENV PATH=/venv/bin:$PATH
34+
35+
# change this entrypoint if it is not the same as the repo
36+
ENTRYPOINT ["python3-pip-skeleton"]
37+
CMD ["--version"]
Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@
44
"build": {
55
"dockerfile": "Dockerfile",
66
"target": "build",
7-
"context": ".",
8-
"args": {}
7+
// Only upgrade pip, we will install the project below
8+
"args": {
9+
"PIP_OPTIONS": "--upgrade pip"
10+
}
911
},
1012
"remoteEnv": {
1113
"DISPLAY": "${localEnv:DISPLAY}"
1214
},
1315
// Set *default* container specific settings.json values on container create.
1416
"settings": {
15-
"python.defaultInterpreterPath": "/venv/bin/python",
16-
"python.linting.enabled": true
17+
"python.defaultInterpreterPath": "/venv/bin/python"
1718
},
1819
// Add the IDs of extensions you want installed when the container is created.
1920
"extensions": [
@@ -24,6 +25,7 @@
2425
"initializeCommand": "bash -c 'for i in $HOME/.inputrc; do [ -f $i ] || touch $i; done'",
2526
"runArgs": [
2627
"--net=host",
28+
"--security-opt=label=type:container_runtime_t",
2729
"-v=${localEnv:HOME}/.ssh:/root/.ssh",
2830
"-v=${localEnv:HOME}/.inputrc:/root/.inputrc"
2931
],
@@ -35,5 +37,5 @@
3537
"workspaceMount": "source=${localWorkspaceFolder},target=${localWorkspaceFolder},type=bind",
3638
"workspaceFolder": "${localWorkspaceFolder}",
3739
// After the container is created, install the python project in editable form
38-
"postCreateCommand": "pip install $([ -f requirements_dev.txt ] && echo -r requirements_dev.txt ) -e .[dev]"
40+
"postCreateCommand": "pip install -e .[dev]"
3941
}

.dockerignore

Lines changed: 0 additions & 6 deletions
This file was deleted.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: Install requirements
2+
description: Run pip install with requirements and upload resulting requirements
3+
inputs:
4+
requirements_file:
5+
description: Name of requirements file to use and upload
6+
required: true
7+
install_options:
8+
description: Parameters to pass to pip install
9+
required: true
10+
python_version:
11+
description: Python version to install
12+
default: "3.x"
13+
14+
runs:
15+
using: composite
16+
17+
steps:
18+
- name: Setup python
19+
uses: actions/setup-python@v4
20+
with:
21+
python-version: ${{ inputs.python_version }}
22+
23+
- name: Pip install
24+
run: |
25+
touch ${{ inputs.requirements_file }}
26+
# -c uses requirements.txt as constraints, see 'Validate requirements file'
27+
pip install -c ${{ inputs.requirements_file }} ${{ inputs.install_options }}
28+
shell: bash
29+
30+
- name: Create lockfile
31+
run: |
32+
mkdir -p lockfiles
33+
pip freeze --exclude-editable > lockfiles/${{ inputs.requirements_file }}
34+
# delete the self referencing line and make sure it isn't blank
35+
sed -i '/file:/d' lockfiles/${{ inputs.requirements_file }}
36+
shell: bash
37+
38+
- name: Upload lockfiles
39+
uses: actions/upload-artifact@v3
40+
with:
41+
name: lockfiles
42+
path: lockfiles
43+
44+
# This eliminates the class of problems where the requirements being given no
45+
# longer match what the packages themselves dictate. E.g. In the rare instance
46+
# where I install some-package which used to depend on vulnerable-dependency
47+
# but now uses good-dependency (despite being nominally the same version)
48+
# pip will install both if given a requirements file with -r
49+
- name: If requirements file exists, check it matches pip installed packages
50+
run: |
51+
if [ -s ${{ inputs.requirements_file }} ]; then
52+
if ! diff -u ${{ inputs.requirements_file }} lockfiles/${{ inputs.requirements_file }}; then
53+
echo "Error: ${{ inputs.requirements_file }} need the above changes to be exhaustive"
54+
exit 1
55+
fi
56+
fi
57+
shell: bash
58+

.github/workflows/code.yml

Lines changed: 89 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ on:
44
push:
55
pull_request:
66
schedule:
7-
# Run every Monday at 8am to check latest versions of dependencies
7+
# Run weekly to check latest versions of dependencies
88
- cron: "0 8 * * WED"
9+
env:
10+
# The target python version, which must match the Dockerfile version
11+
CONTAINER_PYTHON: "3.11"
912

1013
jobs:
1114
lint:
@@ -17,24 +20,28 @@ jobs:
1720
- name: Checkout
1821
uses: actions/checkout@v3
1922

20-
- name: Setup python
21-
uses: actions/setup-python@v4
23+
- name: Install python packages
24+
uses: ./.github/actions/install_requirements
2225
with:
23-
python-version: "3.10"
26+
requirements_file: requirements-dev-3.x.txt
27+
install_options: -e .[dev]
2428

2529
- name: Lint
26-
run: |
27-
touch requirements_dev.txt requirements.txt
28-
pip install -r requirements.txt -r requirements_dev.txt -e .[dev]
29-
tox -e pre-commit,mypy
30+
run: tox -e pre-commit,mypy
3031

3132
test:
3233
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository
3334
strategy:
3435
fail-fast: false
3536
matrix:
3637
os: ["ubuntu-latest"] # can add windows-latest, macos-latest
37-
python: ["3.8", "3.9", "3.10"]
38+
python: ["3.9", "3.10", "3.11"]
39+
install: ["-e .[dev]"]
40+
# Make one version be non-editable to test both paths of version code
41+
include:
42+
- os: "ubuntu-latest"
43+
python: "3.8"
44+
install: ".[dev]"
3845

3946
runs-on: ${{ matrix.os }}
4047
env:
@@ -45,37 +52,84 @@ jobs:
4552
- name: Checkout
4653
uses: actions/checkout@v3
4754
with:
55+
# Need this to get version number from last tag
4856
fetch-depth: 0
4957

50-
- name: Setup python ${{ matrix.python }}
51-
uses: actions/setup-python@v4
58+
- name: Install python packages
59+
uses: ./.github/actions/install_requirements
5260
with:
53-
python-version: ${{ matrix.python }}
61+
python_version: ${{ matrix.python }}
62+
requirements_file: requirements-test-${{ matrix.os }}-${{ matrix.python }}.txt
63+
install_options: ${{ matrix.install }}
5464

55-
- name: Install with latest dependencies
56-
run: pip install .[dev]
65+
- name: List dependency tree
66+
run: pipdeptree
5767

5868
- name: Run tests
59-
run: pytest tests
69+
run: pytest
6070

6171
- name: Upload coverage to Codecov
6272
uses: codecov/codecov-action@v3
6373
with:
6474
name: ${{ matrix.python }}/${{ matrix.os }}
6575
files: cov.xml
6676

67-
container:
77+
dist:
6878
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository
79+
runs-on: "ubuntu-latest"
80+
81+
steps:
82+
- name: Checkout
83+
uses: actions/checkout@v3
84+
with:
85+
# Need this to get version number from last tag
86+
fetch-depth: 0
87+
88+
- name: Build sdist and wheel
89+
run: |
90+
export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) && \
91+
pipx run build
92+
93+
- name: Upload sdist and wheel as artifacts
94+
uses: actions/upload-artifact@v3
95+
with:
96+
name: dist
97+
path: dist
98+
99+
- name: Check for packaging errors
100+
run: pipx run twine check dist/*
101+
102+
- name: Install python packages
103+
uses: ./.github/actions/install_requirements
104+
with:
105+
python_version: ${{env.CONTAINER_PYTHON}}
106+
requirements_file: requirements.txt
107+
install_options: dist/*.whl
108+
109+
- name: Test module --version works using the installed wheel
110+
# If more than one module in src/ replace with module name to test
111+
run: python -m $(ls src | head -1) --version
112+
113+
container:
114+
needs: [lint, dist, test]
69115
runs-on: ubuntu-latest
116+
70117
permissions:
71118
contents: read
72119
packages: write
73120

74121
steps:
75122
- name: Checkout
76123
uses: actions/checkout@v3
124+
125+
# image names must be all lower case
126+
- name: Generate image repo name
127+
run: echo IMAGE_REPOSITORY=ghcr.io/$(tr '[:upper:]' '[:lower:]' <<< "${{ github.repository }}") >> $GITHUB_ENV
128+
129+
- name: Download wheel and lockfiles
130+
uses: actions/download-artifact@v3
77131
with:
78-
fetch-depth: 0
132+
path: .devcontainer
79133

80134
- name: Log in to GitHub Docker Registry
81135
if: github.event_name != 'pull_request'
@@ -89,74 +143,45 @@ jobs:
89143
id: meta
90144
uses: docker/metadata-action@v4
91145
with:
92-
images: ghcr.io/${{ github.repository }}
146+
images: ${{ env.IMAGE_REPOSITORY }}
93147
tags: |
94-
type=ref,event=branch
95148
type=ref,event=tag
149+
type=raw,value=latest
96150
97151
- name: Set up Docker Buildx
98152
id: buildx
99153
uses: docker/setup-buildx-action@v2
100154

101-
- name: Build developer image for testing
102-
uses: docker/build-push-action@v3
103-
with:
104-
tags: build:latest
105-
context: .
106-
target: build
107-
load: true
108-
109-
- name: Run tests in the container locked with requirements_dev.txt
110-
run: |
111-
docker run --name test build bash /project/.github/workflows/container_tests.sh
112-
docker cp test:/project/dist .
113-
docker cp test:/project/lockfiles .
114-
docker cp test:/project/cov.xml .
115-
116-
- name: Upload coverage to Codecov
117-
uses: codecov/codecov-action@v3
118-
with:
119-
name: 3.10-locked/ubuntu-latest
120-
files: cov.xml
121-
122155
- name: Build runtime image
123156
uses: docker/build-push-action@v3
124157
with:
125-
push: ${{ github.event_name != 'pull_request' }}
158+
build-args: |
159+
PIP_OPTIONS=-r lockfiles/requirements.txt dist/*.whl
160+
push: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags') }}
161+
load: ${{ ! (github.event_name == 'push' && startsWith(github.ref, 'refs/tags')) }}
126162
tags: ${{ steps.meta.outputs.tags }}
127-
context: .
128-
labels: ${{ steps.meta.outputs.labels }}
163+
context: .devcontainer
164+
# If you have a long docker build, uncomment the following to turn on caching
165+
# For short build times this makes it a little slower
166+
#cache-from: type=gha
167+
#cache-to: type=gha,mode=max
129168

130169
- name: Test cli works in runtime image
131-
# check that the first tag can run with --version parameter
132-
run: docker run $(echo ${{ steps.meta.outputs.tags }} | head -1) --version
133-
134-
- name: Test cli works in sdist installed in local python
135-
# ${GITHUB_REPOSITORY##*/} is the repo name without org
136-
# Replace this with the cli command if different to the repo name
137-
run: pip install dist/*.gz && ${GITHUB_REPOSITORY##*/} --version
138-
139-
- name: Upload build files
140-
uses: actions/upload-artifact@v3
141-
with:
142-
name: dist
143-
path: dist
144-
145-
- name: Upload lock files
146-
uses: actions/upload-artifact@v3
147-
with:
148-
name: lockfiles
149-
path: lockfiles
170+
run: docker run ${{ env.IMAGE_REPOSITORY }} --version
150171

151172
release:
152173
# upload to PyPI and make a release on every tag
153-
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
154-
needs: container
174+
needs: [lint, dist, test]
175+
if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags') }}
155176
runs-on: ubuntu-latest
156177

157178
steps:
158179
- uses: actions/download-artifact@v3
159180

181+
- name: Fixup blank lockfiles
182+
# Github release artifacts can't be blank
183+
run: for f in lockfiles/*; do [ -s $f ] || echo '# No requirements' >> $f; done
184+
160185
- name: Github Release
161186
# We pin to the SHA, not the tag, for security reasons.
162187
# https://docs.github.com/en/actions/learn-github-actions/security-hardening-for-github-actions#using-third-party-actions

.github/workflows/container_tests.sh

Lines changed: 0 additions & 14 deletions
This file was deleted.

0 commit comments

Comments
 (0)