rcmdnk's blog

THE CHECKERS SUPER BEST COLLECTION

ブログのリンク切れをチェックするために LinkChecker を使っていて、GitHub Actionsで動かしていますが 最近のバージョンで上手く動かなくなっていたのでなんとか動くように変更した話。

Linkchecker

サイト内のリンクをチェックしてリンク切れを見つけてくれるツール。

開発も未だにアクティブにやってくれているので 使い続けています。

一時期メンテナンスがストップして有志がフォークして開発、みたいな感じだったみたいですが、 現在はlinkcheckerというアカウントのもと運用されています。

contribution guidelines and FAQ · Issue #1 · linkchecker/linkchecker

起こった問題

環境はGitHub Actionsは

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
jobs:
  main:
    runs-on: ubuntu-latest

    steps:
    ...

    - name: Setup python
      uses: actions/[email protected]
      with:
        python-version: 3.9.X
        architecture: x64
    - name: linkchecker
        id: linkchecker
        continue-on-error: true
        run: |
          pip install linkchecker
          linkchecker -o html https://rcmdnk.com > linkchecker.html

みたいな感じでやっているもの。 これで入るlinkcheckerは 現在10.1.0です。

ただし、下記のエラーは10.0.0でも10.0.1でも出ます。

9.X系はPython2用なので、Python3にして10.1.0を使おうと思って変えたところエラーが出るようになりました。

Error: Unknown logger type 'html' in 'html' for option '-o, --output'

Linkcheckerでは結果をいろいろな形式で出力することが出来て、-o htmlでHTMLを指定していますが、 このフォーマットは使えない、とのこと。

ただ、フォーマットを指定しないと、デフォルトではtextなんですが、

  File "/opt/hostedtoolcache/Python/3.9.13/x64/lib/python3.9/site-packages/linkcheck/configuration/__init__.py", line 225, in logger_new
    args = self[loggername]
KeyError: 'text'

なエラーが。

また、コマンドを実行してすぐに、

ERROR: refuse to load module from world writable file '/opt/hostedtoolcache/Python/3.9.13/x64/lib/python3.9/site-packages/linkcheck/logger/graph.py'
ERROR: refuse to load module from world writable file '/opt/hostedtoolcache/Python/3.9.13/x64/lib/python3.9/site-packages/linkcheck/logger/gxml.py'
ERROR: refuse to load module from world writable file '/opt/hostedtoolcache/Python/3.9.13/x64/lib/python3.9/site-packages/linkcheck/logger/html.py'
ERROR: refuse to load module from world writable file '/opt/hostedtoolcache/Python/3.9.13/x64/lib/python3.9/site-packages/linkcheck/logger/sql.py'
...

といったエラーが連発されていました。 これはlinkchecker --helpしてみても出ます。

問題調査

このERRORが出る部分を探してみると、 ここで、 check_writable_by_othersからさらにis_writable_by_othersでチェックが行われてこれが0以外を返しているのが問題っぽい。

check_writable_by_othersにあるos.nameのチェックはGitHub Actions上でもposixということは確認しました。

is_writable_by_othersで、

1
2
3
4
def is_writable_by_others(filename):
    """Check if file or directory is world writable."""
    mode = os.stat(filename)[stat.ST_MODE]
    return mode & stat.S_IWOTH

といったことをしてますが、os.statでファイルの状態情報が取れて、stat.ST_MODE(=0)はファイルのパーミッション情報をもつもの。

stat — Interpreting stat() results — Python 3.10.5 documentation

stat.S_IWOTH(=2)のビットに所有者以外に書き込み権限があるかどうか、が入っているとのこと。

なのでこれが0を返せば所有者以外は書き込み権限がなく、所有者以外に書き込み権限があれば2が返ります。

ということで見てみると2が返ってきてました。

普通にlsで見てみると、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-rw-rw-rw-+ 1 runner docker  4842 Jun XX XX:XX csvlog.py
-rw-rw-rw-+ 1 runner docker 14915 Jun XX XX:XX __init__.py
-rw-rw-rw-+ 1 runner docker  3322 Jun XX XX:XX xmllog.py
-rw-rw-rw-+ 1 runner docker 12480 Jun XX XX:XX text.py
-rw-rw-rw-+ 1 runner docker  4312 Jun XX XX:XX sql.py
-rw-rw-rw-+ 1 runner docker  4340 Jun XX XX:XX sitemapxml.py
-rw-rw-rw-+ 1 runner docker  1247 Jun XX XX:XX none.py
-rw-rw-rw-+ 1 runner docker 14377 Jun XX XX:XX html.py
-rw-rw-rw-+ 1 runner docker  3253 Jun XX XX:XX gxml.py
-rw-rw-rw-+ 1 runner docker  3328 Jun XX XX:XX graph.py
-rw-rw-rw-+ 1 runner docker  2826 Jun XX XX:XX gml.py
-rw-rw-rw-+ 1 runner docker  3755 Jun XX XX:XX failures.py
-rw-rw-rw-+ 1 runner docker  3143 Jun XX XX:XX dot.py
-rw-rw-rw-+ 1 runner docker  3814 Jun XX XX:XX customxml.py
drwxrwxrwx+ 2 runner docker  4096 Jun XX XX:XX __pycache__

な感じで666でグループ以外の人にも書き込み権限が与えられている状態でした。

これを変えてやれば動くだろうということで、

1
2
3
4
5
6
7
- name: linkchecker
    id: linkchecker
    continue-on-error: true
    run: |
      pip install linkchecker
      chmod 644 $(find $(python -c "import site;print(site.getsitepackages()[0])")/linkcheck/ -name "*.py")
      linkchecker -o html https://rcmdnk.com > linkchecker.html

とすると動くようになりました。 logger以外のディレクトリに関してもチェックしてエラーが出ている部分があったので、linkcheck内を全部変更します。

Linkcheckerのモジュール名はlinkchecklinkcheckerではないので注意。

なぞ

ここ とかを見るとこの辺のチェックはずいぶん前からあったようです。

まず最初になぜこのチェックが必要なのか、というのがよくわかってません。 ファイルが下手な変更されていると困るというのはわかりますが、 他のモジュールとかも全部しないと意味ないような?

一方で、pipで入れたもののpermissionがなぜ666になっているのか? actions/setup-python がよしなに用意してくれているもので、実行ユーザーのrunnerが直接 pip installで入れられるsite-packagesディレクトリを用意してくれていますが、 その中にファイルを作ると全て他人にも書き込み権限が与えられてしまう仕様なのか pipの仕様なのか。。。

ちょっと見てみると、 (わかりやすく$ 付けてますがそれぞれのコマンドをGitHub Actions上でrunで実行した結果)

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
$ umask
0022

$ getfacl  /usr
getfacl: Removing leading '/' from absolute path names
# file: usr
# owner: root
# group: root
user::rwx
group::r-x
other::r-x

$ getfacl  /opt
getfacl: Removing leading '/' from absolute path names
# file: opt
# owner: root
# group: root
user::rwx
group::rwx
other::rwx
default:user::rwx
default:user:runner:rwx
default:group::rwx
default:mask::rwx
default:other::rwx
getfacl: Removing leading '/' from absolute path names

$ getfacl  /opt/hostedtoolcache/Python/3.9.13/x64/lib/python3.9/site-packages/
# file: opt/hostedtoolcache/Python/3.9.13/x64/lib/python3.9/site-packages/
# owner: runner
# group: runneradmin
user::rwx
group::rwx
other::rwx
default:user::rwx
default:user:runner:rwx
default:group::rwx
default:mask::rwx
default:other::rwx

という感じで、umaskは一般的な0022ですが、/optのdefault ACLがotherにもrwxになってます。

Pythonのパッケージが入るsite-packagesowner/groupは違いますが全てrwx。 ということで、この権限状態が問題なようです。

/optも通常のLinuxだと

1
2
3
user::rwx
group::r-x
other::r-x

だとは思いますが、GitHub Actionsで用意されているUbuntuでは最初から(setup-pythonやcheckoutなどを行う前に見ても)この状態でした。

通常はコンテナ的な使い方するだけならこの状態の方が便利といえば便利なわけですが、 Linkcheckerでなくとも、ファイルの権限を気にするツールは他にもあるので、 ちょっとこの仕様は覚えておいた方が良いな、と。

Sponsored Links
Sponsored Links

« 気象庁アメダスの情報をRaspberry Piで取得して表示する