UnfuddleAPIのラッパーとSVNバックアップ機能をRubyで書いてみた
UnfuddleのSVNバックアップを自動化したかったので、UnfuddleAPIのラッパーとSVNバックアップをRubyで書きました。
晒したコードは自分の記憶のためとメモを兼ねてます。
かなり中途実装なので、突っ込みどころがたくさんあると思います。
最低限自分がしたい事は実装したので、APIは一部分しかラップしていません。(^^;
準備
依存しているJSONとかTmailなどのパッケージをgemsを使ってインストールする。
基本的な使い方(Revが上がっていたらSVNのバックアップを行う)
UnfuddleAPIWrapperを読み込み、パラメータを用意して、インスタンスを作ります。
最低限必要なパラメータは「unfuddle_settings」だけです。
「backup_settings」パラメータがない場合は、svnbackupメソッドが使用できません。*2
require 'unfuddleapiwrpp' # -------------------------------------------------------------- # 【パラメータ】 # 1.Unfuddleに関する設定 # 2.バックアップ出力に関する設定 ※任意((SVNリポジトリBACKUP機能を使う時は必要)) # -------------------------------------------------------------- # Unfuddleに関する情報 settings = { :unfuddle_settings => { :subdomain => 'hoge', #アカウント:サブドメイン :username => 'hoge', #アカウント:ユーザー名 :password => 'passwordxx', #アカウント:パスワード :url => 'http://hoge.unfuddle.com/svn/hoge', #アカウント:SVN }, :backup_settings => { :rootpath => 'C:', #バックアップディレクトリを作成するディレクトリ :backupdir => 'svnbackup' #出力するバックアップディレクトリ } } # インスタンスを生成 exe = UnfuddleApiWrpp.new(settings) # Unfuddleへ最終リビジョンを要求し response = exe.query(:changesets, :lastest) # 結果をXMLで取得し lastestRev = exe.getXMLElement(response, "changeset/revision") # SVNリポジトリのバックアップを行う result = exe.svnbackup(lastestRev.to_i)
そうすると、指定したディレクトリにSVNリポジトリのバックアップが作成されます。
Revが1しかあがっていなくても全てのREVを取得します・・・
差分機能は未実装です。
APIラッパーのみの使い方
require 'unfuddleapiwrpp' # -------------------------------------------------------------- # 【パラメータ】 # 1.Unfuddleに関する設定 # -------------------------------------------------------------- # Unfuddleに関する情報 settings = { :unfuddle_settings => { :subdomain => 'hoge', #アカウント:サブドメイン :username => 'hoge', #アカウント:ユーザー名 :password => 'passwordxx', #アカウント:パスワード :url => 'http://hoge.unfuddle.com/svn/hoge', #アカウント:SVN } } # インスタンスを生成 exe = UnfuddleApiWrpp.new(settings) # Unfuddleへアカウント情報を要求し((3つ目のパラメータには明示的にXMLを渡してもOK)) response = exe.query(:projects, :projects, 'xml') # XML形式からプロジェクトIDを取得する projectID = exe.getXMLElement(response,"projects/project/id")
APIラッパーでJSONを使う
実はこちらの方が便利な気がする。
デフォルトはこちらにすべき??
require 'unfuddleapiwrpp' # -------------------------------------------------------------- # 【パラメータ】 # 1.Unfuddleに関する設定 # -------------------------------------------------------------- # Unfuddleに関する情報 settings = { :unfuddle_settings => { :subdomain => 'hoge', #アカウント:サブドメイン :username => 'hoge', #アカウント:ユーザー名 :password => 'passwordxx', #アカウント:パスワード :url => 'http://hoge.unfuddle.com/svn/hoge', #アカウント:SVN } } # インスタンスを生成 exe = UnfuddleApiWrpp.new(settings) # JSONでリビジョン取得する場合 response = exe.query(:changesets, :lastest, 'json') lastestRev = JSON.parse(response.body)["revision"] # JSONでアカウントのメッセージ取得する場合 response2 = exe.query(:accounts, :account, 'json') message = JSON.parse(response.body)["message"]
実装したAPI一覧
あとで書く予定。
ソースコード
UnfuddleApiWrpp.rb
# = UnfuddleAPI Wrapper & SVNBackUP # # Author:: Guyon # Version:: 0.1 # # Unfuddleで提供されているAPIから任意の形式で値を取得し、 # SVNリポジトリのバックアップを行います。 # # このクラスを使用するには「json」と「tmail」のインスト # ールが必要です。 # # 主な機能: # # 1.任意の形式でUnfuddleからデータを取得します。*1 *2 # 2.前回行ったリポジトリバックアップよりRevが新しければ # SVNリポジトリのバックアップを行う # 3.ログ、お知らせメール # # ※ *1 Version 0.1での取得形式は「XML」と「JSON」のみ対応 # *2 Version 0.1ではAccount、project、changesetのみ対応 require 'rubygems' require 'logger' require 'kconv' require 'net/https' require 'net/smtp' require 'rexml/document' require 'tmail' require 'json' include REXML # =================================================== # Unfuddleのリポジトリをバックアップ # =================================================== class UnfuddleApiWrpp # バックアップディレクトリを日付で作成 @@makedir = Time.now.strftime("%Y%m%d%H%M%S") # UnfuddleAPIのアドレス @@unfuddle_urls = { :accounts => { :account => {:uri => 'account' , :method => 'PUT'}, :activity => {:uri => 'account/activity' , :method => 'GET'}, :search => {:uri => 'account/search' , :method => 'GET'}, :resetAccessKeys => {:uri => 'account/reset_access_keys' , :method => 'PUT'} }, :projects => { :projects => {:uri => 'projects' , :method => 'GET'}, :archives => {:uri => 'archives' , :method => 'GET'}, :components => {:uri => 'components' , :method => 'GET'}, :severities => {:uri => 'severities' , :method => 'GET'}, :versions => {:uri => 'versions' , :method => 'GET'}, :project => {:uri => 'projects/${projectId}' , :method => 'GET'}, :activity => {:uri => 'projects/${projectId}/activity' , :method => 'GET'}, :search => {:uri => 'projects/${projectId}/search' , :method => 'GET'}, :dump => {:uri => 'projects/${projectId}/dump' , :method => 'PUT'}, :archive => {:uri => 'archives/${projectId}/archives' , :method => 'GET'}, :archive_download => {:uri => 'archives/${projectId}/archives/download' , :method => 'GET'}, :component => {:uri => 'components/${projectId}' , :method => 'GET'}, :severitie => {:uri => 'severities/${projectId}' , :method => 'GET'}, :version => {:uri => 'versions/${projectId}' , :method => 'GET'} }, :changesets => { :changeset => {:uri => 'projects/${projectId}/changesets' , :method => 'POST'}, :lastest => {:uri => 'projects/${projectId}/changesets/latest' , :method => 'GET'}, :processMessageActions => {:uri => 'projects/${projectId}/changesets/process_message_actions' , :method => 'GET'} } } # コンストラクタ Hashで初期設定値を受け取る def initialize(settings = nil) unless settings return end @mail_settings = settings.has_key?(:mail_settings) ? settings[:mail_settings] : nil @unfuddle_settings = settings.has_key?(:unfuddle_settings) ? settings[:unfuddle_settings] : nil @backup_settings = settings.has_key?(:backup_settings) ? settings[:backup_settings] : nil @options = settings.has_key?(:options) ? settings[:backup_settings] : nil # バックアップ if @backup_settings #対象ディレクトリ @backup_fullpath = @backup_settings[:rootpath] + '\\' + @backup_settings[:backupdir] + '\\' + @@makedir # リビジョン管理ファイルパス @lastestSyncRevLogFile = @backup_settings[:rootpath] + '\\' + @backup_settings[:backupdir] + '\\' + 'lastest_sync_rev_log.txt' #リビジョンログを残すファイル名 end # ロガー @use_log = @options[:use_log] if @options && @options.has_key?(:use_log) if @use_log @log = Logger.new(@options[:logPath]) #ログレベル case @options[:logLevel] when 1 @log.level = Logger::DEBUG when 2 @log.level = Logger::INFO when 3 @log.level = Logger::WARN when 4 @log.level = Logger::ERROR when 5 @log.level = Logger::FATAL else @log.level = Logger::FATAL end end # メール @use_mail = @options[:use_mail] if @options && @options.has_key?(:use_mail) end # レスポンスフォーマットを指定して問い合わせ実行 def query(menuname, action, format = 'xml') # TODO 本当は再帰的に問い合わせをしてパラメータをよしなに解決したい # 現在はprojectIdのみが必要なので、固定として処理する # プロジェクトIDが必要なリクエストなら、projectIdを取得する params = Hash.new if @@unfuddle_urls[menuname][action][:uri] =~ /\$\{projectId\}/ # パラメータリストにプロジェクトIDをセットする params[:projectId] = query_projects end # resoponsを返す return getRespons(@@unfuddle_urls[menuname][action], format, params) end # プロジェクトIDを取得する def query_projects response = getRespons(@@unfuddle_urls[:projects][:projects] ,'xml' , {}) # XMLオブジェクトを作成 return getXMLElement(response,"projects/project/id").to_i end # responsで取得したXMLから要素を取得する def getXMLElement(xmlRespons,filter) doc = REXML::Document.new(xmlRespons.body) REXML::XPath.first(doc,filter).text end #-------------------------------------------- # GET通信を行い結果を取得する #-------------------------------------------- def getRespons(target, format, params) http = Net::HTTP.new("#{@unfuddle_settings[:subdomain]}.unfuddle.com", @unfuddle_settings[:ssl] ? 443 : 80) # SSLを使用するならHTTPクライアントに設定しておく if @unfuddle_settings[:url] =~ /^https/ http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE end begin # uriのテンプレートを取得し uri = target[:uri] # パラメータが存在すればテンプレートuriを置換する params.each_pair{|key, value| uri = uri.sub(/\$\{#{key}\}/, value.to_s) } # リクエスト情報をHTTPクライアントに設定する request = Net::HTTP::Get.new('/api/v1/' + uri + '.' + format) request.basic_auth @unfuddle_settings[:username], @unfuddle_settings[:password] # HTTP通信を行い結果を取得し response = http.request(request) # レスポンスコードによりエラーを判断する unless response.code == "200" # レスポンス結果が失敗だったらエラーコードを出力する puts "HTTP Status Code: #{response.code}." @log.error("HTTP Status Code: #{response.code}.") if @use_log sendmail_by_tmail("HTTP Status Code: #{response.code}.") if @use_mail end rescue => e # 障害ログ @log.fatal(e) if @use_log sendmail_by_tmail(e) if @use_mail return nil end return response end # ------------------------------------------------------------ # メール送信処理 # # action_mailer > TMail > 標準ライブラリの順で楽に実装できる? # ここはあえて、[action_mailer]ではなく[TMail]を選択 # @see http://code.nanigac.com/source/view/339 # ------------------------------------------------------------ def sendmail_by_tmail(body) # Mailオブジェクトを生成する mail = TMail::Mail.new # 送信情報を設定する mail.to = @mail_settings[:to] mail.from = @mail_settings[:from] mail.reply_to = @mail_settings[:from] # メールを日本語に対応させる work = Kconv.tojis(@mail_settings[:subject]).split(//,1).pack('m').chomp mail.subject = "=?ISO-2022-JP?B?"+work.gsub('\n', '')+"?=" mail.body = Kconv.tojis(body) # その他 mail.date = Time.now mail.mime_version = '1.0' mail.set_content_type 'text', 'plain', {'charset'=>'iso-2022-jp'} # マルチパートに対応する為に上書き? mail.write_back # 送信する Net::SMTP.start(@mail_settings[:smtp]){ |smtp| smtp.sendmail(mail.encoded, mail.from, mail.to) } end #SVNのバックアップを取る def svnbackup(lastestRev) # バックアップのパラメータ設定がされていなければ処理を行わない return unless @backup_fullpath # 前回同期した時のRevisonの値を読み込む lastestSyncRev = nil begin File::open(@lastestSyncRevLogFile, "r"){|f| lastestSyncRev = f.read.chomp.to_i} rescue => e # ファイルがなかった時は警告ログ出力しておき @log.fatal(e) if @use_log # メールも出しておく sendmail_by_tmail(e) if @use_mail end # 前回同期時のリビジョンが不明か新しくなっていたらバックアップを処理を行う if lastestSyncRev.blank? || lastestRev > lastestSyncRev # リポジトリを作成する exit unless runSvnCmd("svnadmin create " + @backup_fullpath) # フックする為にpre-revprop-changeの設定値を行う if ENV["OS"] =~ /Windows/ # Windowsならフックの為のpre-revprop-change.batを作成する。 File::open(@backup_fullpath + "\\hooks\\pre-revprop-change.bat", "w"){|f| f.puts "exit 0" } else # Windows以外はUnix系と想定しているのでShellを編集する File::open(@backup_fullpath + "\\hooks\\pre-revprop-change.tmpl", "w"){|f| f.puts "#!bin/sh" f.puts "exit 0" } end # リポジトリを初期化する exit unless runSvnCmd("svnsync init file:///" + @backup_settings[:backupdir] + "/" + @@makedir + " " + @unfuddle_settings[:url]) # リポジトリを同期化する exit unless runSvnCmd("svnsync sync file:///" + @backup_settings[:backupdir] + "/" + @@makedir) end # 最後にログとして最後に同期を行ったリビジョンを書き込む File::open(@lastestSyncRevLogFile, "w"){|f| f.puts lastestRev } end # SVNコマンド処理を行う # 正常時:true 異常持:false def runSvnCmd(cmdStr) isSvnCmd = system(cmdStr) # 実行エラーの場合はログ出力 unless isSvnCmd @log.fatal("SVNディレクトリのバックアップ処理に失敗しました。(" + cmdStr + ")") sendmail_by_tmail(e) if @use_mail return false end return true end end