Docker + シェルスクリプトによるCLI作成時のちょっとした工夫

最近,皆が使うグラフ描画ツールを統一したほうがいいんじゃないかという話になり,Python + matplotlib を使おうとしています.そこで,実行環境も揃えるために Docker + シェルスクリプトでコマンドを作ってみました.

matplot というコマンドです.これで,環境によってグラフの凡例に日本語が出ないとかいうことがなくなりそうです.
nomlab/tools - GitHub

短かいので,同じものを末尾にも丸ごと載せておきます.

このスクリプトのウリは,Dockerfile も内蔵しているところです.これまで Docker + シェルスクリプトで CLI を作ることはあったのですが,対応する Dockerfile と共に管理するのが面倒でした.
シェルスクリプト中にヒアドキュメントとして入れておいて,なおかつ Dockerfile 部分の書換後や初回実行時には,自動で docker build が走ります.

中身は単に matplotlib 他をあらかじめインストールした Python なので,
matplot ./your-plot-script.py という感じで使えます (pwd をマウントしてます).引数なしだと bash が起動します.

もう1つのウリは,実行する UID をホストとゲストで (極力) そろえているところです.作ったファイルが root 所有にならないようにしています.

よかったら,使ってみて PR を下さい.

#!/bin/bash

set -e

HOST_USER_ID=$(id -u)
HOST_USER_NAME=$(id -un)

IMG_NAME="matplot"
IMG_TAG="latest"
IMG_NAME_TAG="${IMG_NAME}:${IMG_TAG}"

function dockerfile() {
  cat <<EOF
FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install -y sudo vim python3 python3-pip
RUN apt-get install -y fonts-ipaexfont
RUN pip3 install pandas matplotlib scipy

RUN useradd -m -s /bin/bash -u $HOST_USER_ID $HOST_USER_NAME
RUN usermod -aG sudo $HOST_USER_NAME
RUN sed -i 's/^%sudo.*$/%sudo ALL=(ALL:ALL) NOPASSWD: ALL/' /etc/sudoers

RUN mkdir /home/$HOST_USER_NAME/work
RUN touch /home/$HOST_USER_NAME/.sudo_as_admin_successful
RUN chown -R ${HOST_USER_NAME}:${HOST_USER_NAME} /home/$HOST_USER_NAME

RUN sed -i "s/^#font\.family.*/font.family: IPAexGothic/g" /usr/local/lib/python*/dist-packages/matplotlib/mpl-data/matplotlibrc

USER $HOST_USER_NAME
WORKDIR /home/$HOST_USER_NAME/work

CMD ["bash"]
EOF
}

function dockerfile_signature() {
  dockerfile | md5sum | sed 's/ .*//'
}

function img_signature() {
  local name_and_tag="$1"
  docker inspect "$name_and_tag" \
         --format='{{.Config.Labels.dockerfile_signature}}' 2>/dev/null
}

function img_is_uptodate() {
  local name_and_tag="$1"
  test "$(dockerfile_signature)" = "$(img_signature "$name_and_tag")"
}

# Build Docker image locally.
# Usage: build_img "matplot:latest"
#
function build_img() {
  local name_and_tag="$1"
  dockerfile | \
    docker build \
           --label dockerfile_signature=$(dockerfile_signature) \
           -t "$name_and_tag" -f - .
}

################################################################
## main

if ! img_is_uptodate "$IMG_NAME_TAG"; then
  build_img "${IMG_NAME_TAG}"
fi

if [ $# -eq 0 ]; then
  command="bash"
else
  command="python3"
fi

docker run -t -i --rm  -v .:/home/$HOST_USER_NAME/work \
       --name "$IMG_NAME" "$IMG_NAME_TAG" "$command" "$@"


Comment

No comment