Edited at

apk --updateという引数はない


はじめに

軽量なDockerイメージを作るときに重宝されるAlpine Linuxのパッケージマネージャであるapk、そのapkでパッケージをインストールするときにつかう apk コマンド、その引数に --update というものがあると思っている人はいないでしょうか。何を隠そう私がその一人でした。

今日なんかいいオプションないかなと何気なく apk add --help を見てたところ、あると思いこんでいた --update がないことに気付きました。

ちなみに下記が執筆時点で最新のAlpine Linux 3.9での apk add --help の実行結果です。

apk add --helpの出力結果 (クリックで開きます)

# apk add --help

apk-tools 2.10.1, compiled for x86_64.

usage: apk add [-h|--help] [-p|--root DIR] [-X|--repository REPO] [-q|--quiet] [-v|--verbose] [-i|--interactive] [-V|--version] [-f|--force] [--force-binary-stdout]
[--force-broken-world] [--force-non-repository] [--force-old-apk] [--force-overwrite] [--force-refresh] [-U|--update-cache] [--progress] [--progress-fd FD]
[--no-progress] [--purge] [--allow-untrusted] [--wait TIME] [--keys-dir KEYSDIR] [--repositories-file REPOFILE] [--no-network] [--no-cache] [--cache-dir CACHEDIR]
[--cache-max-age AGE] [--arch ARCH] [--print-arch] [-s|--simulate] [--clean-protected] [--overlay-from-stdin] [--no-scripts] [--no-commit-hooks]
[--initramfs-diskless-boot] [--initdb] [-u|--upgrade] [-t|--virtual NAME] PACKAGE...

Description:
Add PACKAGEs to 'world' and install (or upgrade) them, while ensuring that all dependencies are met

Global options:
-h, --help Show generic help or applet specific help
-p, --root DIR Install packages to DIR
-X, --repository REPO Use packages from REPO
-q, --quiet Print less information
-v, --verbose Print more information (can be doubled)
-i, --interactive Ask confirmation for certain operations
-V, --version Print program version and exit
-f, --force Enable selected --force-* (deprecated)
--force-binary-stdout Continue even if binary data is to be output
--force-broken-world Continue even if 'world' cannot be satisfied
--force-non-repository Continue even if packages may be lost on reboot
--force-old-apk Continue even if packages use unsupported features
--force-overwrite Overwrite files in other packages
--force-refresh Do not use cached files (local or from proxy)
-U, --update-cache Alias for --cache-max-age 1
--progress Show a progress bar
--progress-fd FD Write progress to fd
--no-progress Disable progress bar even for TTYs
--purge Delete also modified configuration files (pkg removal) and uninstalled packages from cache (cache clean)
--allow-untrusted Install packages with untrusted signature or no signature
--wait TIME Wait for TIME seconds to get an exclusive repository lock before failing
--keys-dir KEYSDIR Override directory of trusted keys
--repositories-file REPOFILE Override repositories file
--no-network Do not use network (cache is still used)
--no-cache Do not use any local cache path
--cache-dir CACHEDIR Override cache directory
--cache-max-age AGE Maximum AGE (in minutes) for index in cache before refresh
--arch ARCH Use architecture with --root
--print-arch Print default arch and exit

Commit options:
-s, --simulate Show what would be done without actually doing it
--clean-protected Do not create .apk-new files in configuration dirs
--overlay-from-stdin Read list of overlay files from stdin
--no-scripts Do not execute any scripts
--no-commit-hooks Skip pre/post hook scripts (but not other scripts)
--initramfs-diskless-boot Enables options for diskless initramfs boot (e.g. skip hooks)

Add options:
--initdb Initialize database
-u, --upgrade Prefer to upgrade package
-t, --virtual NAME Instead of adding all the packages to 'world', create a new virtual package with the listed dependencies and add that to 'world'; the actions of the
command are easily reverted by deleting the virtual package

This apk has coffee making abilities.


長いので updateでgrepしますと、

# apk add --help | grep update

[--force-refresh] [-U|--update-cache]
-U, --update-cache Alias for

そう、 --update ではなく --update-cache が正しい引数でした。

とはいえ "apk add --update" でぐぐるといくつも記事が出てくるので「これはいったいぜんたいどういうことなのだろう?」と思って調べてみました。


間違った引数は無視されているだけなのでは?

と思って存在しない引数を使ってみたんですが、

 # apk add --doesnotexists

apk: unrecognized option: doesnotexists

とちゃんと警告されます。

つまり --update はヘルプに載ってないにもかかわらず正しい引数として認識されているというわけです。


ソースコードを読む

apkのコードはgitリポジトリで公開されています

このソースを読んでみたところ、getopt_longという関数で引数を処理していることがわかります。

この getopt_long (3) という関数は man をみると

Long option names may be abbreviated if the abbreviation is unique or

is an exact match for some defined option.

(意訳)
--XXXX形式の長いオプションは他のオプションと重複しない限りであれば短縮可能である。
(完全に一致したオプションがあれば他のオプションの一部があっても一致したほうが選択される)

つまり、 apk --update--update 引数は、 --update-cache の省略形としてgetopt_long が認識していたから使用できるわけです。

これがどういうことかというと、apkが新たに --update-hogehoge という引数を追加したら --update はエラーになりますし、そもそもこの挙動自体 apk の仕様というよりは getopt_long の仕様で短縮された引数でも使えているだけなので引数の解析を別のライブラリに変更しただけで使えなくなるかもしれません。まあさすがにないだろうけど。


昔は --update 引数があったんじゃないの?

と思って --update-cache より前に --update がないかをGitリポジトリ内で調べてみました。

すると --update-cache が追加されたのが2009年で、その時点で --update はありませんでした。

--update-cacheが追加されたコミット (はじめて git bisect つかった)

てことで、やっぱり apk--update なんて引数はありませんでした。

となるとなんで apk add --update という記述がインターネット上にいくつかあるのかが謎なんですが、最初に誰かが間違えたのが出回ってるんですかね・・・?

(追記) Docker hubの公式AlpineイメージのGitHubリポジトリのドキュメントapk add --update nginx という記述があることがわかりました。これが元凶なのかはわからないのですが、とにかくtypo修正のPull Requestを投げてみました。

https://github.com/gliderlabs/docker-alpine/pull/503


まとめ



  • apk --update という引数は apk --update-cache の間違いです。


  • getopt のGNU拡張 getopt_long はオプションが正確じゃなくても他に候補がないときには使えるため、--update でも --update-cache を指定したときと同じ動作になります。

  • インターネットの記事をコピペするだけじゃなくてちゃんと自分で調べましょう。

以上、自分への自戒でした。気をつけます。