OSによって作られるメタデータファイル(.DS_Store
とかThumbs.db
とか)をgitignoreするとき、プロジェクトじゃなくてグローバルの設定にしたい。それで長年 ~/.config/git/ignore
にファイルを置いていた。内容はgithub/gitignoreから取ってくる。giboを使っているなら、gibo dump macOS > ~/.config/git/ignore
するだけだ。
最近Development Containersを使ってみていて、おおよそ気に入っているのだけど、このグローバルなgitignoreの扱いに悩んだ。手元のファイルシステムからマウントされるので、.DS_Store
ファイルがDevelopment Containerの中から見えてしまう。しかしグローバルなgitignoreは(あえてマウントしなければ)設定されていないから、gitの差分に出てきてしまう。
もちろんプロジェクトの .gitignore
ファイルに書いたらいいのだけど、どうも気乗りしない。ということで、Development Containersのfeatureとして作ってみる。
Featureとは
Development Containersについて何年前かに使ったときは、このfeatureという概念がなかったように思う。コンテナに何か追加したければDockerfileを書くような感じだった。ところが最近では、Development Containerにfeatureを適用することで、必要な機能を追加する。例えばコンテナにNode.jsを入れたければ、.devcontainer/devcontainer.json
のfeatures
にghcr.io/devcontainers/features/node
を書き加える。
"features": { "ghcr.io/devcontainers/features/node:1": {} },
このように、featureはOCI Imageとしてパッケージングされ、配布される。Node.jsのfeatureはfeatures/src/node at main · devcontainers/features · GitHubでその実態を見られる。
Featureを作る
自分でfeatureを作るのは、テンプレートリポジトリから始めるのがいちばん良さそうだ。GitHub Actionsもよく整備されている。サンプルとなるfeatureとして、color
やhello
が入っている。これを真似していく。
まずテンプレートリポジトリから自分のリポジトリを作る。ひとつのリポジトリで複数のfeatureを提供するのが普通なようだ。このリポジトリ自体がDevelopment Containerで開発するようになっているので、VS Codeからコンテナで開く。
src/
以下にディレクトリを作って、devcontainer-feature.json
とinstall.sh
を置く。サンプルではREADME.md
もあるが、これは後から自動生成されるので、自分で作る必要がない。
src └── gitignoreglobal ├── README.md ├── devcontainer-feature.json └── install.sh
devcontainer-feature.json
の仕様に合わせて書けばよい。options
を定義しておくと、featureへの入力として文字列か真偽値を得られる。
install.sh
の方が本体で、ここにシェルスクリプトを書く。これはroot
として実行される。Development Containerとして実行する際は、例えばvscode
ユーザーなどで実行されるので、その差に注意が要る。実際、gitignoreglobal
featureではsystemのgit configを書き換えることにした。あまり上品ではないが、後から作られるユーザーのことを知る由もないので、諦めた。
#!/bin/sh set -e GITIGNORE_PATH="$(git config --system --get core.excludesfile || true)" if [ -z "${GITIGNORE_PATH}" ]; then GITIGNORE_PATH=/etc/gitignore git config --system --add core.excludesfile $GITIGNORE_PATH fi echo "Using global gitignore file: ${GITIGNORE_PATH}" mkdir -p "$(dirname "${GITIGNORE_PATH}")" curl -sS "https://raw.githubusercontent.com/github/gitignore/main/${GITIGNORE}.gitignore" >> "${GITIGNORE_PATH}"
options
で設定した入力値は環境変数として渡されるので、$GITIGNORE
としてこれを使っている。
curl
やgit
はdevcontainer-feature.json
でdependsOn
を設定していることで使えている。
"dependsOn": { "ghcr.io/devcontainers/features/common-utils": {} }
テスト
テンプレートリポジトリから始めると、test/
にテストが入っている。scenarios.json
にテストシナリオを書いて、キー名と一致するkeyname.sh
に実際のテストを書く。test.sh
はデフォルトのテストということに決まっている。
test/gitignoreglobal ├── macos.sh ├── scenarios.json └── test.sh
今回はscenarios.json
で、macOS用のテストを定義する。
{ "macos": { "image": "mcr.microsoft.com/devcontainers/base:ubuntu", "features": { "gitignoreglobal": { "gitignore": "Global/macOS" } } } }
macos.sh
は次のように書いた。source dev-container-features-test-lib
すると、check
とreportResults
が使えるようになって、非常に便利。
#!/bin/bash set -e source dev-container-features-test-lib # check <LABEL> <cmd> [args...] mkdir -p /tmp/test_1 cd /tmp/test_1 git init check "no difference at start" [ -z "$(git status --porcelain)" ] touch .DS_Store check "no difference after adding .DS_Store" [ -z "$(git status --porcelain)" ] touch NOT_IGNORED check "difference after adding NOT_IGNORED" [ -n "$(git status --porcelain)" ] reportResults
実行するにはdevcontainer
CLIを使う。devcontainer features test --features gitignoreglobal
のようにすると、特定のfeatureのテストを実行できる。Docker in Dockerで、新しいコンテナの中で実行されるので、やろうと思えば複数のベースイメージに対してテストを実行させられる。
またGitHub Actionsでも実行されるようになっている。
デプロイ
デプロイもdevcontainer
CLIでできるが、事前に設定されたGitHub Actionsでやるのが簡単だった。GitHub Packagesにデプロイできる。README.md
もdevcontainer-feature.json
の内容から自動的に生成され、Pull Requestが作られるので、便利だ。
"features": { "ghcr.io/cockscomb/devcontainer-features/gitignoreglobal:1": { "gitignore": "Global/macOS" } },
書いていて気がついたけど、macOSとWindowsが混在している環境だったら複数指定したいと思う。options
は環境変数にマッピングされる都合からか、文字列と真偽値しか受け付けないので、スペース区切りで"Global/macOS Global/Windows"
のようにできるとよさそう。
完
ひとまずDevelopment Containersのfeatureを作ってみた。テンプレートリポジトリやCLIが整備されているおかげで、普通にやるとテストも書けるし、CI/CDも用意できる。OCI Imageとして配布されるのもハイテクな感じがする。全体的によくできたエコシステムと思う。