「プロビジョニング」は大げさ過ぎるから「セットアップ」にする。ただし、拡張子は prov.sh のまま。
セットアップスクリプトのすることを、
- 準備行動
- 主行動
に分ける。
セットアップ・スクリプトの原則は
- 設定情報が不備なときは何もしない。
- 準備行動は、確認して準備する。このとき、既に準備が出来ていることには何もしない。
- 主行動はひとつだけ、最後に行う。
- 主行動は、既にある状態・状況を変更する。
- 現在の状況と同じ設定情報が与えられたときは、状態・状況を変更する主行動が実行されても、タイムスタンプを除いてシステムは変更されない。
つまり、準備行動に関しては if_needed で、主行動に関しては force で実行する。
CRUD操作の観点から言うと:
- 準備行動ではCR操作だけが許される。
- 主行動ではUD操作をする。
安全性の観点から次の原則を守る。
これを巻き戻し原則〈ロールバック原則〉と呼ぶことにする。巻き戻し原則の実現のために、サブ原則として:
- PREV原則: 変更(UpdateとDelete)するファイルは、拡張子 .PREV として残す。もし、.PREVファイルがあれば何もしない。
- 生成ログ原則: 新しく生成したファイルは、その旨ログに書く。
[追記]
セットアップスクリプトがやることは、ファイル/プロセスへの変更だ。ファイルシステムのファイルと実行中のプロセスの集まりを“領域”と呼ぶことにして、スクリプトの捜査対象を主対象領域と補助対象領域に分ける。
- 補助対象領域: バックアップファイルとログ、具体的には *.PREV, *.DIFF, /var/log/provision-scripts.log
- 主対象領域: 実行可能ファイルや設定ファイルやデーモン
それぞれの対象領域に対する作用を主作用、補助作用と呼ぶ。また、ファイルのタイムスタンプ、オーナー、パーミッションなどを属性、データを内容と呼ぶ。プロセスの実行ステータスも(不自然だが)内容という。
この用語法で、セットアップスクリプトへの要求は:
- 主作用の内容ベキ等性: 主作用に関しては、スクリプトの1回実行後と2回実行後で、リソース内容が変わらない。
- 主作用の一回内容巻き戻し可能性: 主作用に関しては、実行直後であれば巻き戻し可能。巻き戻しにより、対象領域の内容は復元される。
- 上記の(弱い)ベキ等性、(弱い)巻き戻し可能性を破る実行は事前に検知されて、何も行わずに終了する。
[/追記]
関数ライブラリとセットアップスクリプトの事例
#!/bin/sh # -*- coding: utf-8-unix -*- echo This is functions. # # predicates # function file_exists { file=$1 [ -f "$file" ] } function dir_exists { dir=$1 [ -d "$dir" ] } function file_not_exist { file=$1 [ ! -f "$file" ] } function dir_not_exist { dir=$1 [ ! -d "$dir" ] } function need_install_missing_file { file=$1 [ ! -f "$file" -o -n "$PROV_FORCE_INSTALL" ] } function need_config_missing_file { file=$1 [ ! -f "$1" -o -n "$PROV_FORCE_CONFIG" ] } # # messaging and logging # function start_message { todo=$1 echo "" echo "***" echo "*** Start: $todo." echo "***" } function done_message { done=$1 echo "*** Done: $done" } function creation_log { created=$1 echo `date +%Y-%m-%dT%H:%M:%S` " Created: $created" >> /var/log/provision-scripts.log } function modification_log { modified=$1 echo `date +%Y-%m-%dT%H:%M:%S` " Modified: $modified" >> /var/log/provision-scripts.log } # 無意味だった function show_this_script_name { echo "* This is $0." } function show_env { var_name=$1 # eval "echo \* $var_name=\${${var_name}}" # old style echo "* $var_name=${!var_name}" # new style } # # prev and diff # function _make_prev { path=$1 if [ -f $path.PREV ]; then echo "$path.PREV exists, exit." exit 1 fi if [ -f $path ]; then cp $path $path.PREV fi } function make_diff { path=$1 diff -u $path.PREV $path > $path.DIFF } # # install # #* 特定のファイルが存在しない場合に、指定のパッケージをインストールする function install_unless { file=$1 pkg=$2 if [ ! -f $file -o -n "$PROV_FORCE_INSTALL" ]; then yum -y install $pkg fi } #* 特定のコマンドがwhichで見つからない場合に、そのコマンドをインストールする # コマンド名とパッケージ名が同じ時にしか使えないので注意。 function install_which { cmd=$1 which $1 if [ $? -ne 0 ]; then yum -y install $cmd fi } # # conditional execution # function cp_unless { src=$1 dst=$2 if [ ! -f $dst -o -n "$PROV_FORCE_CP" ]; then echo "* copy $src --> $dst" cp $src $dst fi } function source_if { src=$1 if [ -f $src ]; then source $src fi } function _source_must { src=$1 if [ -f $src ]; then source $src else echo "Error: Not exist: $src" exit 1 fi } # # service control # function stop_service { service=$1 service $service status if [ $? -eq 1 ]; then return fi service $service status | grep running > /dev/null if [ $? -eq 0 ]; then service $service stop fi } function set_service { service=$1 service $service status if [ $? -eq 1 ]; then return fi service $service status | grep running > /dev/null if [ $? -eq 0 ]; then service $service reload else service $service start fi chkconfig $service on echo "chkconfig:" chkconfig --list $service } function unset_service { service=$1 service $service status if [ $? -ne 0 ]; then return fi service $service status | grep running > /dev/null if [ $? -eq 0 ]; then service $service stop fi chkconfig $service off echo "chkconfig:" chkconfig --list $service }
メールフォーワードの設定:
#!/bin/sh # -*- coding: utf-8-unix -*- start_message "setup mail forwarding" _source_must ./mail_forward.conf.sh # gurard and exit if dir_not_exist /home/$USER ; then /usr/sbin/useradd $USER echo "$PASSWD $PASSWD" | passwd $USER creation_log "user: $USER" done_message "user created: $USER" fi # main activity _make_prev /home/$USER/.forward # guard and exit echo "$FORWARD" > /home/$USER/.forward modification_log "file: /home/$USER/.forward" done_message "mail forwarding: $USER --> $FORWARD"