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
Keyword(s):
References:[SideMenu] [Rake の実例]