セットアップスクリプトの原則と事例

「プロビジョニング」は大げさ過ぎるから「セットアップ」にする。ただし、拡張子は prov.sh のまま。

セットアップスクリプトのすることを、

  1. 準備行動
  2. 主行動

に分ける。

セットアップ・スクリプトの原則は

  1. 設定情報が不備なときは何もしない。
  2. 準備行動は、確認して準備する。このとき、既に準備が出来ていることには何もしない。
  3. 主行動はひとつだけ、最後に行う。
  4. 主行動は、既にある状態・状況を変更する。
  5. 現在の状況と同じ設定情報が与えられたときは、状態・状況を変更する主行動が実行されても、タイムスタンプを除いてシステムは変更されない。

つまり、準備行動に関しては if_needed で、主行動に関しては force で実行する。

CRUD操作の観点から言うと:

  1. 準備行動ではCR操作だけが許される。
  2. 主行動ではUD操作をする。

安全性の観点から次の原則を守る。

これを巻き戻し原則〈ロールバック原則〉と呼ぶことにする。巻き戻し原則の実現のために、サブ原則として:

  1. PREV原則: 変更(UpdateとDelete)するファイルは、拡張子 .PREV として残す。もし、.PREVファイルがあれば何もしない。
  2. 生成ログ原則: 新しく生成したファイルは、その旨ログに書く。

[追記]

セットアップスクリプトがやることは、ファイル/プロセスへの変更だ。ファイルシステムのファイルと実行中のプロセスの集まりを“領域”と呼ぶことにして、スクリプトの捜査対象を主対象領域と補助対象領域に分ける。

  • 補助対象領域: バックアップファイルとログ、具体的には *.PREV, *.DIFF, /var/log/provision-scripts.log
  • 主対象領域: 実行可能ファイルや設定ファイルやデーモン

それぞれの対象領域に対する作用を主作用、補助作用と呼ぶ。また、ファイルのタイムスタンプ、オーナー、パーミッションなどを属性、データを内容と呼ぶ。プロセスの実行ステータスも(不自然だが)内容という。

この用語法で、セットアップスクリプトへの要求は:

  1. 主作用の内容ベキ等性: 主作用に関しては、スクリプトの1回実行後と2回実行後で、リソース内容が変わらない。
  2. 主作用の一回内容巻き戻し可能性: 主作用に関しては、実行直後であれば巻き戻し可能。巻き戻しにより、対象領域の内容は復元される。
  3. 上記の(弱い)ベキ等性、(弱い)巻き戻し可能性を破る実行は事前に検知されて、何も行わずに終了する。

[/追記]


関数ライブラリとセットアップスクリプトの事例

#!/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"