rcmdnk's blog

20240218_pythondockergithub_200_200

Poetryで管理しているPythonのパッケージをGitHub Actionsでテストする方法について。

Poetryで管理しているPythonパッケージのテスト

Poetryで管理しているPythonパッケージで、 testsディレクトリにテストがありpytestでテストを行うようなものを考えます。

ルートディレクトリで、

1
2
poetry install
poetry run pytest

とすればテストを行うことが出来る状態。

Poetryで環境を作りテストを行う

以下のような.github/workflows/test.ymlを作成します。

.github/workflows/test.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
---
name: test

on: push

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-python@v4
        with:
        python-version: '3.11'
    - name: Make poetry environment
      run: |
        pip install poetry
        poetry install
    - name: Run pytest
      run: poetry run pytest

これでpushされたときにpoetryで環境を作ってpytestを実行することが出来ます。

ちなみにこの辺のPytestはほとんどの場所で共通して使ってるので 下にあるようなactionを作って共通化して使ってます。

Docker環境で共通のテストを行う

Dockerfileの準備

でやったPoetryで管理しているプロジェクトの環境構築のDockerfileを使います。

pyrproject.tomlは上のものに加え、テスト用のpytestdevのグループに追加しておきます。

pyproject.toml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[tool.poetry]
name = "myproject"
version = "0.0.1"
description = ""
authors = ["rcmdnk <[email protected]>"]

[tool.poetry.dependencies]
python = "^3.11"

[tool.poetry.group.dev.dependencies]
pytest = "^8.0.0"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

[tool.poetry.scripts]
hello = "myproject:main"

これに対応するDockerfileは以下のようになります。

Dockefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
FROM python:3.12-slim-bookworm as builder

ENV PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1 \
VIRTUAL_ENV=/app/.venv

WORKDIR /app

RUN pip install poetry-plugin-export

COPY pyproject.toml poetry.lock src ./

RUN poetry export --with dev --without-hashes --format=requirements.txt > requirements.txt
RUN echo "./" >> requirements.txt

RUN python -m venv "$VIRTUAL_ENV"
RUN . "$VIRTUAL_ENV/bin/activate" && pip install -r requirements.txt

#########

FROM python:3.12-slim-bookworm as runtime

ENV VIRTUAL_ENV=/app/.venv \
PATH="/app/.venv/bin:$PATH" \
USER_NAME=appuser

COPY --from=builder $VIRTUAL_ENV $VIRTUAL_ENV

WORKDIR /app

RUN useradd -r -u 1000 $USER_NAME
RUN chown -R $USER_NAME:$USER_NAME /app

USER $USER_NAME

変えたのは

Dockefile
1
RUN poetry export --with dev --without-hashes --format=requirements.txt > requirements.txt

の部分だけで、--with devが追加されています。 これでdevのグループに含まれるpytestもインストールされます。

テスト部分の共通化

共通のテストにするため、テスト実行部分を別途actionsとして作成します。

.github/actions/test/action.yml
1
2
3
4
5
6
7
8
9
name: test
description: Run tests

runs:
  using: 'composite'
  steps:
    - name: pytest
      shell: bash
      run: pytest

こんな感じでpytestを実行するだけのcomposite actionを作っておきます。

必要であればここで色々と実行するコマンドを追加しておきます。

これでレポジトリをcheckoutした後なら

1
    - uses: ./.github/actions/test

と指定するとpytestが実行されます。

Poetry用Actionsファイル

上のactionを使って、Poetry用のactionを作ります。

.github/workflows/poetry-test.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
---
name: poetry test

on: push

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-python@v4
        with:
        python-version: '3.11'
    - name: Make poetry environment
      run: |
        pip install poetry
        poetry install
        echo "$(poetry show -v 2>/dev/null|grep 'Using virtualenv:'|awk '{print $3}')/bin" >> $GITHUB_PATH
    - uses: ./.github/actions/test

最初の場合は最後にpoetry runpytestを実行することで仮想環境下での実行を行っていましたが、 この場合ではtest actionの中ではpoetryを使ってません。

test actionの引数としてpoetry runを使うかどうか、を指定しても良いかもしれませんが、 ごちゃごちゃするので、ここでは全体の環境のPATHに poetryの仮想環境のbinディレクトリを追加しています。

あとは最後の 最後のpytest部分をactionに置き換えるだけです。

Docker用Actionsファイル

.github/workflows/docker-test.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
---
name: docker test

on: push

jobs:
  docker-build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
    - uses: actions/checkout@v4
    - uses: docker/login-action@v3
      with:
        registry: ghcr.io
        username: $
        password: $
    - uses: docker/setup-buildx-action@v3
    - uses: docker/build-push-action@v5
      with:
        context: .
        file: ./Dockerfile
        push: true
        tags: ghcr.io/$/test-image:$
  docker-test:
    needs: docker-build
    runs-on: ubuntu-latest
    container:
      image: ghcr.io/$/test-image:$
      credentials:
        username: $
        password: $
      options: --user root
    steps:
    - uses: actions/checkout@v4
    - uses: ./.github/actions/test

こんな感じ。

まず、docker-buildジョブでイメージをビルドしています。

GitHubでは GitHub Packages でContainer registoryが提供されていて、 ghcr.ioでアクセス出来るレジストリで Dockerイメージを保存したり公開したりすることが出来ます。

コンテナレジストリの利用 - GitHub Docs

それを使うため、 login-actionでログインし、build-push-actionでイメージをビルドしてpushしています。 setup-buildx-actionはDocker Buildxを使うためのもので、 今回の例では無理に使う必要はないかもしれませんが入れておきました。

これでレポジトリの環境をDockerイメージとしてビルドし、 Container registoryに登録されます。

次にdocker-testジョブで docker-buildジョブでビルドしたイメージを使ってテストを行います。

containerで作ったイメージを指定して 後はレポジトリをcheckoutしてテストを実行するだけです。

ここで、イメージ名など共通なのでenvとかで変数化したいところですが、 steps内では使えるもののこの上のcontainerなどの階層部分では Unrecognized named-value: 'env'.なエラーになってしまいます。

レポジトリの設定の Actions secrets and variablesで設定した変数であれば vars.<変数名>で使え、これはcontainerの中でも使えるので それを設定すればよりきれいに書くことは出来ます。

イメージが複数になるような場合にはそのようなことをした方が良いかもしれません。

テストのステップに関してはまず actionsやtestsディレクトリを取得するために レポジトリをcheckoutしています。

もしこのDockerfileがGitHub Actions専用であればDockerfileの中で

Dockefile
1
2
3
RUN mkdir -p .github
COPY .github/actions ./.github/
COPY tests ./

のようにしておけば docker-test側のcheckoutは不要になります。

今回はなんらか別に使うものでそのテストも兼ねるということで上のような形でイメージに余計なものを入れないようにしています。

containerの部分で--user rootを指定していますが、 これはGitHub Actionsでcheckoutを行う際、 root権限でないとcheckoutできないためです。

今回のDockefileではUSERを指定しているため、 そのままだと

1
Error: EACCES: permission denied, open '/__w/_temp/_runner_file_commands/save_state_XXXXXXXX-XXXX-XXXX-XXXX-XXXX[XX](https://github.com/SensoftInc/xxxxxxxxxxxx/actions/runs/xxxxxxxxxx/jobs/xxxxxxxxxx#step:x:xx)xxxxxx'

というエラーが出てしまいます。

Error: EACCES: permission denied in container on self hosted Linux runner · Issue #1014 · actions/checkout

これに関しては上のIssueで 上のように--user rootを指定するか、 中で/__wを作って実行するユーザー(もしくは全員)に権限をもたせる方法が 紹介されていますが、上のIssueにあるような

Dockefile
1
RUN mkdir -m 1777 /__w

をしても同じエラーになりました。

Dockefile
1
2
RUN mkdir /__w
RUN chown -R $USER_NAME:$USER_NAME /__w

みたいなのもうまくいきません。

何かおまじないがあるのか…

というわけで、ちょっと環境変えてしまうことにもなってしまいますが 一旦--user rootで対応しています。

実行するコマンドによってはユーザーが誰か、で結果が変わるようなこともあると思うので、 そういった場合は最初からDockerfileの定義でUSERを指定せずに rootで実行するようにして正しく動作するように設定したが方が良いです。

push時にはpoetryで、手動テストでDocker環境も使えるようにする

.github/workflows/test.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
name: test

on:
  push:
  workflow_dispatch:
    inputs:
      exe_env:
        description: "Execution environment."
        type: choice
        required: false
        default: "docker"
        options:
          - "docker"
          - "poetry"

jobs:
  docker-build:
    if: ${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.exe_env == 'docker') }}

    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
    - uses: actions/checkout@v4
    - uses: docker/login-action@v3
      with:
        registry: ghcr.io
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}
    - uses: docker/setup-buildx-action@v3
    - uses: docker/build-push-action@v5
      with:
        context: .
        file: ./Dockerfile
        push: true
        tags: ghcr.io/${{ github.repository }}/test-image:${{ github.sha }}
  docker-test:
    if: ${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.exe_env == 'docker') }}
    needs: docker-build
    runs-on: ubuntu-latest
    container:
      image: ghcr.io/${{ github.repository }}/test-image:${{ github.sha }}
      credentials:
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}
      options: --user root
    steps:
    - uses: actions/checkout@v4
    - uses: ./.github/actions/test
  poetry-test:
    if: ${{ (github.event_name == 'push') || (github.event_name == 'workflow_dispatch' && github.event.inputs.exe_env == 'poetry') }}
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-python@v4
      with:
        python-version: '3.12'
    - name: setup poetry
      run: |
        pip install poetry
        poetry install
        echo "$(dirname $(poetry run which python))" >> $GITHUB_PATH
    - uses: ./.github/actions/test

こんな感じのGitHub Actionsを作成しておけば、

  • push時にはpoetryでテスト
  • 手動で実行するときにはdockerpoetryを選択して実行

を行うことが出来ます。

まあこのように手動でテスト出来るようにしておく意味があるかどうかはわかりませんが、 テストがてら作るときに便利かな、と思ってこうしてみただけです。

以下のレポジトリはこのテストを実際にやってみたもの。

Sponsored Links
Sponsored Links

« Poetryを使ったDocker環境構築について ARM用のtkinterdnd2-universal+Nuitkaでdrag and dropでファイルを渡すアプリをPythonで作る »

}