Skip to content

Commit

Permalink
Run tests as non-root user in Alpine Linux
Browse files Browse the repository at this point in the history
- Add a note to the fixture in test_util.py that its ability to
  create files where rmtree will fail is contingent on not running
  as root (since root doesn't need to own a dir to delete from it).

- Create a non-root user in the container. Give it the same UID as
  owns the repository files that are shared with the container.
  Also create a group with the GID of the repository files that
  are shared with the container and add the user to the group,
  though that is less important. Actually creating the user ensures
  it has a home directory and may help some commands work. Passing
  `options: --user 1001` under `container:` will not work because,
  even if we didn't make the user, the `apk add` commands still
  need to run as root.

- Run all commands as the new non-root user, except for the system
  administration commands that install needed apk packages and set
  up the new non-root user account. To continue clearly expressing
  each step separately and have them automatically run in the
  container, this uses the hacky approach of having the default
  shell be a "sudo" command that runs the script step with "sh"
  (and passes the desired shell arguments).

- Preserve environment variables that may have been set by or for
  the GHA runner, in commands that run as the non-root user. That
  is, pass those through, while still removing/resetting others.
  If this is not done, then the variables such as `CI`, which the
  init-tests-after-clone.sh uses to proceed without interactive
  confirmation, will not be set, and that step will fail. However,
  I think it is also a good idea to do this, which is why I've
  included all the relevant variables and not just `CI`.

- Now that a non-root user is using "pip", stop using a venv, at
  least for now. The other test jobs don't use one, since the
  runners are isolated, and a container on a runner is even more
  isolated. But it may be best to bring the venv back, maybe even
  on the other test jobs, or alternatively to use "python -m pip"
  instead of "pip", to ensure expected version of pip is used.

- Don't add safe.directory inside the container, in the hope that
  this may not be necessary because the cloned repository files
  should have the same UID (and even GID) as the user using them.
  But I expect this may need to be put back; it seems to be needed
  separately from that, as actions/checkout automatically attempts
  it for the git command it finds and attempts to use.

This is not the only approach that could work. Another approach is
to make use of the container explicit in each step, rather than
using the `container` key. I think that would make the relationship
between the commands here and in other test workflows less apparent
and make the workflow a bit less clear, but it could also simplify
things. A third approach is to create an image with the needed apk
packages and user account, which switches to that user, by writing
a Dockerfile and building in image, producing it in a previous
job and sharing the image with the job that runs the tests so that
`container` can still be used. That might be ideal if it could be
done with upload-artifact and download-artifact, but I think
`container` only supports getting images from registries.
  • Loading branch information
EliahKagan committed Feb 14, 2024
1 parent 1587b2e commit d275adc
Show file tree
Hide file tree
Showing 2 changed files with 11 additions and 16 deletions.
23 changes: 8 additions & 15 deletions .github/workflows/alpine-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,22 @@ jobs:

defaults:
run:
shell: sh -exo pipefail {0}
shell: sudo -u runner sh -exo pipefail {0}

steps:
- name: Install Alpine Linux packages
- name: Prepare Alpine Linux
run: |
apk add git git-daemon python3 py3-pip
apk add sudo git git-daemon python3 py3-pip
echo 'Defaults env_keep += "CI GITHUB_* RUNNER_*"' >/etc/sudoers.d/ci_env
addgroup -g 127 docker
adduser -D -u 1001 runner
adduser runner docker
shell: sh -exo pipefail {0} # Run this as root, not the "runner" user.

- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Special configuration for Alpine Linux git
run: |
git config --global --add safe.directory "$(pwd)"
- name: Prepare this repo for tests
run: |
./init-tests-after-clone.sh
Expand All @@ -38,24 +39,17 @@ jobs:
# and cause subsequent tests to fail
cat test/fixtures/.gitconfig >> ~/.gitconfig
- name: Create Python virtual environment
run: |
python -m venv .venv
- name: Update PyPA packages
run: |
# Get the latest pip, wheel, and prior to Python 3.12, setuptools.
. .venv/bin/activate
python -m pip install -U pip $(pip freeze --all | grep -ow ^setuptools) wheel
- name: Install project and test dependencies
run: |
. .venv/bin/activate
pip install ".[test]"
- name: Show version and platform information
run: |
. .venv/bin/activate
uname -a
command -v git python
git version
Expand All @@ -64,5 +58,4 @@ jobs:
- name: Test with pytest
run: |
. .venv/bin/activate
pytest --color=yes -p no:sugar --instafail -vv
4 changes: 3 additions & 1 deletion test/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ def permission_error_tmpdir(tmp_path):
# Set up PermissionError on Windows, where we can't delete read-only files.
(td / "x").chmod(stat.S_IRUSR)

# Set up PermissionError on Unix, where we can't delete files in read-only directories.
# Set up PermissionError on Unix, where non-root users can't delete files in
# read-only directories. (Tests that rely on this and assert that rmtree raises
# PermissionError will fail if they are run as root.)
td.chmod(stat.S_IRUSR | stat.S_IXUSR)

yield td
Expand Down

0 comments on commit d275adc

Please sign in to comment.