//MaintainingCVSPasswords
//1. Maintaining CVS Passwords
//1. CVS パスワードの保守

//!1.1 Introduction
!1.1 はじめに
//This is a very simple real world example of using rake to automate tasks. The task is a simplified example of a small task I do at work.
これは、Rake を使用して、作業の自動化をはかる、非常にシンプルな実例です。日常的に行われる小さな作業例です。

//!!Task Description
!!概要
//Each user of a CVS system needs an entry in a file named "passwd" in the CVSROOT directory of the repository. Each line of this passwd file is formatted:
CVS を利用するユーザは、CVSROOT ディレクトリの passwd ファイルに登録されている必要があります。以下の形式で、行単位で記述されます。

 username:password:effectiveuser

//The password should be unix-style encrypted. The effective user is what the CVS server runs as when updating the archives. This will be "cvs" in our case.
パスワードは、UNIX スタイルで暗号化されている必要があります。effectiveuser の部分は、データを更新する際に CVS サーバを操作する UNIX ユーザ名です。ここでのユーザ名は cvs になります。

//The input will be a list of user names that are active in the CVS repository. We will get the passwords from the system (everybody uses their unix passwords for CVS access). The output will be a CVS passwd file uploaded to the correct place in the repository.
入力に使用するのは、CVS リポジトリで有効なユーザ名のリストです。パスワードはシステムで発行します(全ユーザが、UNIX パスワードを利用します)。出力されるのは、リポジトリの決められた場所に置かれる passwd ファイルです。

//Oh, I forgot to mention. We actually have three repositories. The same passwd file will be delivered to all repositories (just to keep things simple).
おっと、忘れてました。ここでは、3つのリポジトリを管理することを想定しています。同じ passwd ファイルを、すべてリポジトリに配置します(単純な例にしておきます)。

//!!Building the Rakefile
!!Rakefile の作成
//We will now build the Rakefile to accomplish the above task. We will do it in small steps, describing each option as we us it.
それでは、上記を実現するために Rakefile を作成しましょう。小さなステップを踏んで、説明していきましょう。

//!1.2 Step 1: Creating the passwd File
!1.2 Step 1: passwd ファイルの作成
//We will attack the creation of the passwd file first. Later we will worry about deployment to the repositories. So, in our Rakefile, we create a file task entry named "passwd". This says the goal of this task is to create a file name "passwd". The contents of "passwd" depend on the contents of a file containing a list of our users. Let's call this file "userlist".
まずは、passwd ファイルの作成からやっつけましょう。リポジトリへの配置については、後で触れます。まずは、Rakefile に passwd という名前のファイルタスクを定義します。これは、このタスクの目標が passwd というファイルを作成することを意味します。passwd ファイルの内容は、入力されるユーザ名のリストに依存します。この、ユーザ名のリストは userlist と呼ぶことにしましょう。

//Here is the skeleton of the entry.
大枠は以下のようになります。

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

//Now we just write the Ruby code that will generate our passwd file. For now, assume I have a read_users function that will read the userlist file, and a function named read_passwords that returns a hash of passwords indexed by user name.
続いて、passwd ファイルを作成する Ruby コードを記述します。とりあえず、userlist を読み込むための read_users メソッドと、ユーザ名とパスワードの Hash を返す read_passwords メソッドが存在すると仮定してください。

//Our Rakefile now looks like
この時点での 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

//As you can see, the body of our rake task (the stuff between do and end) is just normal Ruby code.
ご覧の通り、タスクの内容(do / end の間)は、ふつうの Ruby コードです。

//To complete this first step we need to provide the functions read_users and read_password. Read_users is straight forward. read_passwords may vary depending on how your system stores passwords. In this example, I just read the /etc/passwd file. Most likely this won't work on your system for several reasons. Your passwords may be in a shadow password file (a good move on the part of your system administrator), or you may be on a large network where passwords are kept on an NIST server somewhere. In the later case you can run ypcat to read the passwords.
この、最初のステップを完了させるためには、read_users と read_password メソッドを実装する必要があります。read_users メソッドは簡単でしょう。read_password メソッドは、システムがパスワードを管理する方法に依存します。この例では /etc/passwd を読み込んでいます。しかし、いくつかの理由で、あなたのシステムで機能しない可能性があります。あなたが良い管理者であれば shadow password を利用しているでしょうし、あるいは 認証機構として NIST サーバを採用する、大規模ネットワークに所属しているかもしれません。NIST サーバを採用している場合は、ypcat コマンドを実行して、パスワードを取得することができます。

//Ok, here is the complete Rakefile up to this point.
それでは以下が、これまでのポイントをクリアした 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

//To run our Rakefile, just type rake and the task name you wish to execute:
この Rakefile を実行するために、rake コマンドにタスク名を指定して、実行しましょう。

 rake passwd

//You should have a "passwd" file in your local directory.
ローカルディレクトリに passwd が作成されたはずです。

 $ ls
 Rakefile  Rakefile~  passwd  userlist

//Yes! Success!
成功です!!

//!1.3 Step 2: Cleaning up Your Act
!1.3 Step 2: クリーンアップ
//As you see from our directory listing, we have some garbage files lying around. Files that end in "~" are backup files created by our editor. It would be nice to have a simple way to clean them up. We can create a :clean target like this …
「Step 1」の最後の ls コマンドの結果に、ゴミファイルが存在することが確認できます。ファイル名が ~ で終わるものは、エディタによって作成されたバックアップファイルです。このゴミファイルを簡単に削除できる方法があると便利でしょう。では、作成しましょう。以下のように clean タスクを定義します。

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

//A FileList is a list of files in Rake. FileList's can be easily created using the FileList[list_of_patterns] expression. This is equivalent to:
FileList は Rake で使用可能なファイルリストです。FileList[list_of_patterns] で簡単に利用できます。FileList[list_of_patterns] は以下と同等です。

 filelist = FileList.new
 filelist.include(list_of_patterns)

//One small wrinkle: notice the clear_exclude call just before the :clean task? FileList's will automatically ignore files that end in "~", ".bak", or are named "core", or have a "CVS" in the directory path. Most of the time this is good, most tasks aren't interested in these temporary files. But in this case it is the temporary files we wish to deal with. So we need to clear the list of excluded files, hence the clear_exclude call.
'''ちょっとした注意 : '''clean タスクを実行する前に、clear_exclude が呼ばれていることに注目してください。FileList では、ファイル名の終わりが ~ や .bak のファイル、ファイル名が core のファイル、ディレクトリパスに CVS を含むものは、自動的に無視されます。ほとんどのタスクにおいては、これら一時ファイルは作業の対象にならないので、自動的に無視されることで手間が省けます。しかし、今回のケースではその一時ファイルが作業の対象でになります。そのため、自動的に無視される一時ファイルをクリアする必要があり、そのために clear_exclude を呼び出す必要あるのです。

//!!The Built-in Clean Task
!!組み込み clean タスク
//Rake has a built-in clean task. All you need to do is require the clean file. If you want to add additional patterns to the clean list, just include them on the CLEAN file list.
Rake には組み込みの clean タスクがあります。rake/clean を require するだけで利用可能です。clean リストに追加したいパターンがある場合は、CLEAN.include を利用してください。

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

//Notice the double star "**" in the pattern above. That will recursively search all subdirectories for files ending in ".temp".
上記の ** に注目してください。これは、すべてのサブディレクトリを再帰的に、ファイル名が .temp で終わるファイルを探すことを意味します。

//!!The Built-in Clobber Task
!!組み込み clobber タスク
//While the clean task will get rid of temporary files, sometimes you want a more powerfully cleaning action, one that will take your project back to its pristine state before any files are generated. I use the clobber task for this.
clean タスクで、一時ファイルを除去していると時々、より強力なタスクが欲しくなることがあります。プロジェクトを、すべてのファイル生成作業より前の状態、つまり一番最初の状態に戻すタスクです。

//clobber is also defined in the "rake/clean" library that comes with rake. And like clean, you can add files and patterns to the CLOBBER file list.
clobber タスクは rake/clean ライブラリの中に含まれています。clean タスク同様に、CLOBBER にパターンを追加することが出来ます。

//Here is our Rakefile up to this point. I've taken the liberty of moving the read_users and read_passwords functions to a utility library just to get them out of the way. The "passwd" file gets added to the CLOBBER list since can be generated from our user list.
ここまでポイントを押さえた 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: Deploying the Files
!1.4 Step 3: ファイルの配置
//Now that we can generate a "passwd" file at will, we need to make sure that the file gets copied to the right location. We have three CVS repositories, but let's concentrate on one first.
passwd ファイルの生成が出来るようになりましたので、次はそのファイルを、適切な場所にコピーする必要があります。3つのリポジトリが対象になりますが、とりあえずは、最初の1つに専念しましょう。

//First we define some constants to make the rest of the task easier. This is the repository for "groupa", so take note of the "groupa" in the target directory path.
まず、残った仕事をより簡単に実現するために、いくつかの定数を定義します。以下は groupa リポジトリのためのものなので、ディレクトリパスの groupa に注目してください。

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

//Now we create a file task for TARGETFILE, and make it depend upon "passwd" (i.e. whenever "passwd" changes, we need to run the TARGETFILE task). First we make sure the target directory exists (this is probably not needed, but let's be safe). Then we copy the "passwd" file to the target file.
ファイルタスク TARGETFILE を作成し、そのファイルタスクと passwd ファイルに依存関係を持たせます(ようは passwd ファイルが変更されると、TARGETFILE タスクの実施する必要があります)。まず、ターゲットディレクトリの存在を確認します(おそらく必要はないと思いますが念のためです)。そして、passwd ファイルをターゲットディレクトリにコピーします。

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

//We can test this by running rake with the TARGETFILE task. Unfortunately, the command line doesn't know about our TARGETFILE constant, so we have to spell the file name out.
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

//That's cool! It works. But it handles only one of the repositories, there are two more to go.
素晴らしい!良い感じです。しかしまだ、1つのリポジトリにしか対応していないので、残りの2つにも対応する必要があります。

//A simple approach would be to duplicate the file command two more times for something like this …
手っ取り早く解決するには、以下のように、同じことをもう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

//Yuck. That's a lot of duplication with a strong possibility for error. Let's avoid the duplication by creating the file tasks in a loop …
うーん。このやり方だと、繰り返しの際に、エラーが入り込む可能性が高いですよね。ループを使ってファイルタスクを作成し、繰り返しを避けましょう。

 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

//We put the groups in a list. Then we loop over the group names and generate the targetdir and targetfile variables for each group. The file task is identical to the previous version, except that it uses the variables calculated in the loop rather than the constant calculated for a single group.
グループ名のリストを用意しました。そして、ループの中でそれぞれのグループ名を利用して、一時変数 targetdir と targetfile を生成します。定数が一つのグループ名を扱っていたこと以外は、前のバージョンのファイルタスクと変わりありません。

//As a final touch, we introduce a task named :deploy. Each time through the loop, :deploy is made to be dependent on each of the target files. Rake tasks are additive. Each time they are mentioned in a file, the dependents and actions are added to the existing task definition.
最後の仕上げ deploy タスクを紹介します。ループするたびに deploy タスクが、それぞれのターゲットファイルと依存関係を定義します。Rake のタスクは追加方式です。タスクが定義されるたびに、既存の定義に、依存関係とアクションを追加していきます。

//Now, instead of asking for each deployed target file individually, I can request all of them at once using the :deploy task. I like that.
これで、複数のターゲットファイルの配置を個々に指定せず、deploy タスクですべてを一度に指定することが出来るようになりました。私はこのやり方を気に入っています。

//Trying out our deploy task gives the following:
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

//We are about done. Let's just make a few final adjustments.
これで、だいたいの目的は達成できました。最後に、少しだけ調整をしましょう。

//!1.5 Step 4: Some Final Touches
!1.5 Step 4: 最後の仕上げ
//If rake is invoked without any tasks, then it looks for a default task to run. We need to provide that default task. The :deploy task seems to be a good choice.
タスクを指定せず rake コマンドを実行した場合、デフォルトタスクが実施されます。そのため、デフォルトタスクを定義しておく必要があります。今回の場合、デフォルトタスクには deploy タスクがふさわしいと思います。

 task :default => [:deploy]

//Also, rake is willing to provide a description of each task, but only if you describe the task to rake. Use the desc command to provide the description. Here is an example on the :deploy task.
また Rake は、それぞれのタスクに対して説明を表示することが出来ます。タスクに説明が定義されている場合だけですが。説明を定義するには desc メソッドを使用します。deploy タスクの例です。

// desc "Deploy the generated passwd file to each of the repositories" 
// task :deploy
 desc "それぞれのリポジトリに、生成された passwd ファイルを配置します。" 
 task :deploy

//After adding descriptions, we can run rake with the -T flag.
説明を定義した後、-T オプションで rake コマンドを実行します。

// $ rake -T
// (in /home/jim/pgm/misc/cvsusers)
// rake clean    # Remove any temporary products.
// rake clobber  # Remove any generated file.
// rake default  # Default task deploys the password files
// rake deploy   # Deploy the generated passwd file to each of the repositories
// rake passwd   # Generate the passwd file from the user list
 $ 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 ファイルを生成します

//And now, the final form of our Rakefile:
それでは、Rakefile の最終形です。

// # -*- ruby -*-
// 
// require "rake/clean" 
// require 'utility'
// 
// CLOBBER.include("passwd")
// 
// desc "Default task deploys the password files" 
// task :default => [:deploy]
// 
// desc "Generate the passwd file from the user list" 
// 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 "Deploy the generated passwd file to each of the repositories" 
// 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
 # -*- 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