Create  Edit  Diff  FrontPage  Index  Search  Changes  History  RSS  Source  Login

1. CVS パスワードの保守

1.1 はじめに

これは、Rake を使用して、作業の自動化をはかる、非常にシンプルな実例です。日常的に行われる小さな作業例です。

概要

CVS を利用するユーザは、CVSROOT ディレクトリの passwd ファイルに登録されている必要があります。以下の形式で、行単位で記述されます。

username:password:effectiveuser

パスワードは、UNIX スタイルで暗号化されている必要があります。effectiveuser の部分は、データを更新する際に CVS サーバを操作する UNIX ユーザ名です。ここでのユーザ名は cvs になります。

入力に使用するのは、CVS リポジトリで有効なユーザ名のリストです。パスワードはシステムで発行します(全ユーザが、UNIX パスワードを利用します)。出力されるのは、リポジトリの決められた場所に置かれる passwd ファイルです。

おっと、忘れてました。ここでは、3つのリポジトリを管理することを想定しています。同じ passwd ファイルを、すべてリポジトリに配置します(単純な例にしておきます)。

Rakefile の作成

それでは、上記を実現するために Rakefile を作成しましょう。小さなステップを踏んで、説明していきましょう。

1.2 Step 1: passwd ファイルの作成

まずは、passwd ファイルの作成からやっつけましょう。リポジトリへの配置については、後で触れます。まずは、Rakefile に passwd という名前のファイルタスクを定義します。これは、このタスクの目標が passwd というファイルを作成することを意味します。passwd ファイルの内容は、入力されるユーザ名のリストに依存します。この、ユーザ名のリストは userlist と呼ぶことにしましょう。

大枠は以下のようになります。

file "passwd" => ["userlist"] do
  # ここに、passwd ファイルを作成するコードを記述します。
end

続いて、passwd ファイルを作成する Ruby コードを記述します。とりあえず、userlist を読み込むための read_users メソッドと、ユーザ名とパスワードの Hash を返す read_passwords メソッドが存在すると仮定してください。

この時点での Rakefile です。

file "passwd" => ["userlist"] do
  passwords = read_passwords
  users = read_users("userlist")
  open("passwd", "w") do |outs|
    users.each do |user|
      outs.puts "#{user}:#{passwords[user]}:cvs" 
    end
  end
end

ご覧の通り、タスクの内容(do / end の間)は、ふつうの Ruby コードです。

この、最初のステップを完了させるためには、read_users と read_password メソッドを実装する必要があります。read_users メソッドは簡単でしょう。read_password メソッドは、システムがパスワードを管理する方法に依存します。この例では /etc/passwd を読み込んでいます。しかし、いくつかの理由で、あなたのシステムで機能しない可能性があります。あなたが良い管理者であれば shadow password を利用しているでしょうし、あるいは 認証機構として NIST サーバを採用する、大規模ネットワークに所属しているかもしれません。NIST サーバを採用している場合は、ypcat コマンドを実行して、パスワードを取得することができます。

それでは以下が、これまでのポイントをクリアした Rakefile です。

# -*- ruby -*-

def read_passwords
  result = {}
  open("/etc/passwd") do |ins|
    ins.each do |line|
      user, pw, *rest = line.split(":")
      result[user] = pw
    end
  end
  result
end

def read_users(filename)
  open(filename) do |ins| ins.collect { |line| line.chomp } end
end

file "passwd" => ["userlist"] do
  passwords = read_passwords
  users = read_users("userlist")
  open("passwd", "w") do |outs|
    users.each do |user|
      outs.puts "#{user}:#{passwords[user]}:cvs" 
    end
  end
end

この Rakefile を実行するために、rake コマンドにタスク名を指定して、実行しましょう。

rake passwd

ローカルディレクトリに passwd が作成されたはずです。

$ ls
Rakefile  Rakefile~  passwd  userlist

成功です!!

1.3 Step 2: クリーンアップ

「Step 1」の最後の ls コマンドの結果に、ゴミファイルが存在することが確認できます。ファイル名が ~ で終わるものは、エディタによって作成されたバックアップファイルです。このゴミファイルを簡単に削除できる方法があると便利でしょう。では、作成しましょう。以下のように clean タスクを定義します。

CLEAN_FILES = FileList['*~']
CLEAN_FILES.clear_exclude
task :clean do
  rm CLEAN_FILES
end

FileList は Rake で使用可能なファイルリストです。FileList[list_of_patterns] で簡単に利用できます。FileList[list_of_patterns] は以下と同等です。

filelist = FileList.new
filelist.include(list_of_patterns)

ちょっとした注意 : clean タスクを実行する前に、clear_exclude が呼ばれていることに注目してください。FileList では、ファイル名の終わりが ~ や .bak のファイル、ファイル名が core のファイル、ディレクトリパスに CVS を含むものは、自動的に無視されます。ほとんどのタスクにおいては、これら一時ファイルは作業の対象にならないので、自動的に無視されることで手間が省けます。しかし、今回のケースではその一時ファイルが作業の対象でになります。そのため、自動的に無視される一時ファイルをクリアする必要があり、そのために clear_exclude を呼び出す必要あるのです。

組み込み clean タスク

Rake には組み込みの clean タスクがあります。rake/clean を require するだけで利用可能です。clean リストに追加したいパターンがある場合は、CLEAN.include を利用してください。

require 'rake/clean'
CLEAN.include("**/*.temp")

上記の ** に注目してください。これは、すべてのサブディレクトリを再帰的に、ファイル名が .temp で終わるファイルを探すことを意味します。

組み込み clobber タスク

clean タスクで、一時ファイルを除去していると時々、より強力なタスクが欲しくなることがあります。プロジェクトを、すべてのファイル生成作業より前の状態、つまり一番最初の状態に戻すタスクです。

clobber タスクは rake/clean ライブラリの中に含まれています。clean タスク同様に、CLOBBER にパターンを追加することが出来ます。

ここまでポイントを押さえた Rakefile が、以下になります。勝手ですが、邪魔にならないように、read_users メソッドと read_passwords メソッドを、utility ライブラリに移動させました。passwd ファイルはユーザリストから生成されるものなので、CLOBBER に追加しています。

# -*- ruby -*-

require "rake/clean" 
require 'utility'

CLOBBER.include("passwd")

file "passwd" => ["userlist"] do
  passwords = read_passwords
  users = read_users("userlist")
  open("passwd", "w") do |outs|
    users.each do |user|
      outs.puts "#{user}:#{passwords[user]}:cvs" 
    end
  end
end

1.4 Step 3: ファイルの配置

passwd ファイルの生成が出来るようになりましたので、次はそのファイルを、適切な場所にコピーする必要があります。3つのリポジトリが対象になりますが、とりあえずは、最初の1つに専念しましょう。

まず、残った仕事をより簡単に実現するために、いくつかの定数を定義します。以下は groupa リポジトリのためのものなので、ディレクトリパスの groupa に注目してください。

TARGETDIR = '/share/cvs/groupa/CVSROOT'
TARGETFILE = File.join(TARGETDIR, "passwd")

ファイルタスク TARGETFILE を作成し、そのファイルタスクと passwd ファイルに依存関係を持たせます(ようは passwd ファイルが変更されると、TARGETFILE タスクの実施する必要があります)。まず、ターゲットディレクトリの存在を確認します(おそらく必要はないと思いますが念のためです)。そして、passwd ファイルをターゲットディレクトリにコピーします。

file TARGETFILE => ["passwd"] do
  mkdir_p TARGETDIR
  cp "passwd", TARGETFILE
end

rake コマンドを実行して TARGETFILE タスクを実施できます。ただ残念なことに、コマンドラインでは定数 TARGETFILE が使用できないので、ファイル名を指定する必要があります。

$ rake /share/cvs/groupa/CVSROOT/passwd
(in /home/jim/pgm/misc/cvsusers)
mkdir -p /share/cvs/groupa/CVSROOT
cp passwd /share/cvs/groupa/CVSROOT/passwd

素晴らしい!良い感じです。しかしまだ、1つのリポジトリにしか対応していないので、残りの2つにも対応する必要があります。

手っ取り早く解決するには、以下のように、同じことをもう2回繰り返すのですが・・・

TARGETDIRA = '/share/cvs/groupa/CVSROOT'
TARGETFILEA = File.join(TARGETDIRA, "passwd")

file TARGETFILEA => ["passwd"] do
  mkdir_p TARGETDIRA
  cp "passwd", TARGETFILEA
end

TARGETDIRB = '/share/cvs/groupb/CVSROOT'
TARGETFILEB = File.join(TARGETDIRB, "passwd")

file TARGETFILEB => ["passwd"] do
  mkdir_p TARGETDIRB
  cp "passwd", TARGETFILEB
end

TARGETDIRC = '/share/cvs/groupc/CVSROOT'
TARGETFILEC = File.join(TARGETDIRC, "passwd")

file TARGETFILEA => ["passwd"] do
  mkdir_p TARGETDIRC
  cp "passwd", TARGETFILEC
end

うーん。このやり方だと、繰り返しの際に、エラーが入り込む可能性が高いですよね。ループを使ってファイルタスクを作成し、繰り返しを避けましょう。

GROUPS = %w(groupa groupb groupc)
GROUPS.each do |group|
  targetdir = "/share/cvs/#{group}/CVSROOT" 
  targetfile = File.join(targetdir, "passwd")

  file targetfile => ["passwd"] do
    mkdir_p targetdir
    cp "passwd", targetfile
  end

  task :deploy => [targetfile]
end

グループ名のリストを用意しました。そして、ループの中でそれぞれのグループ名を利用して、一時変数 targetdir と targetfile を生成します。定数が一つのグループ名を扱っていたこと以外は、前のバージョンのファイルタスクと変わりありません。

最後の仕上げ deploy タスクを紹介します。ループするたびに deploy タスクが、それぞれのターゲットファイルと依存関係を定義します。Rake のタスクは追加方式です。タスクが定義されるたびに、既存の定義に、依存関係とアクションを追加していきます。

これで、複数のターゲットファイルの配置を個々に指定せず、deploy タスクですべてを一度に指定することが出来るようになりました。私はこのやり方を気に入っています。

deploy タスクを試すには、以下のようにします。

$ touch passwd
$ rake deploy
(in /home/jim/pgm/misc/cvsusers)
mkdir -p /share/cvs/groupa/CVSROOT
cp passwd /share/cvs/groupa/CVSROOT/passwd
mkdir -p /share/cvs/groupb/CVSROOT
cp passwd /share/cvs/groupb/CVSROOT/passwd
mkdir -p /share/cvs/groupc/CVSROOT
cp passwd /share/cvs/groupc/CVSROOT/passwd

これで、だいたいの目的は達成できました。最後に、少しだけ調整をしましょう。

1.5 Step 4: 最後の仕上げ

タスクを指定せず rake コマンドを実行した場合、デフォルトタスクが実施されます。そのため、デフォルトタスクを定義しておく必要があります。今回の場合、デフォルトタスクには deploy タスクがふさわしいと思います。

task :default => [:deploy]

また Rake は、それぞれのタスクに対して説明を表示することが出来ます。タスクに説明が定義されている場合だけですが。説明を定義するには desc メソッドを使用します。deploy タスクの例です。

desc "それぞれのリポジトリに、生成された passwd ファイルを配置します。" 
task :deploy

説明を定義した後、-T オプションで rake コマンドを実行します。

$ rake -T
(in /home/jim/pgm/misc/cvsusers)
rake clean    # Remove any temporary products.
rake clobber  # Remove any generated file.
rake default  # デフォルトタスクは、passwd ファイルの配置です。
rake deploy   # それぞれのリポジトリに、生成された passwd ファイルを配置します。
rake passwd   # ユーザリストから passwd ファイルを生成します

それでは、Rakefile の最終形です。

# -*- ruby -*-

require "rake/clean" 
require 'utility'

CLOBBER.include("passwd")

desc "デフォルトタスクは、passwd ファイルの配置です。" 
task :default => [:deploy]

desc "ユーザリストから passwd ファイルを生成します。" 
file "passwd" => ["userlist"] do
  passwords = read_passwords
  users = read_users("userlist")
  open("passwd", "w") do |outs|
    users.each do |user|
      outs.puts "#{user}:#{passwords[user]}:cvs" 
    end
  end
end

desc "それぞれのリポジトリに、生成された passwd ファイルを配置します。" 
task :deploy

GROUPS = %w(groupa groupb groupc)
GROUPS.each do |group|
  targetdir = "/share/cvs/#{group}/CVSROOT" 
  targetfile = File.join(targetdir, "passwd")

  file targetfile => ["passwd"] do
    mkdir_p targetdir
    cp "passwd", targetfile
  end

  task :deploy => [targetfile]
end
Last modified:2006/07/02 16:21:42
Keyword(s):
References:[SideMenu] [Rake の実例]