著者:しょっさん
梅雨も明けて、さぁ夏本番、と思ったら雨がしとしと続く2017年夏です。梅雨が戻ってきたかのようですね。夏場は暑いから外出は控えてきたものですが、今年は、雨で外に出られない涼しい夏を過ごしています。こんな時は、部屋に引きこもってプログラミングですね。体調崩さないよう、今回も頑張ってプログラムを学んでいきましょう。
記事本文掲載のシェルスクリプトマガジンvol.50は以下リンク先でご購入できます。
1 |
$ git clone -b ruby020 https://github.com/40ruby/reins.git |
1 |
$ git clone -b ruby020 https://github.com/40ruby/reins_agent.git |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
$ rubocop lib/reins/host_registry.rb Inspecting 1 file W Offenses: lib/reins/host_registry.rb:2:1: C: Add an empty line after magic comments. require 'csv' ^ lib/reins/host_registry.rb:16:17: C: Operator = should be surrounded by a single space. @hosts = File.exists?(@filename) ? CSV.read(@filename) : [] ^ (中略) lib/reins/host_registry.rb:125:9: C: Redundant return detected. return addr ^^^^^^ lib/reins/host_registry.rb:127:14: C: Do not use :: for method calls. Reins::logger.warn("#{addr} が見つかりません.") ^^ lib/reins/host_registry.rb:128:9: C: Redundant return detected. return nil ^^^^^^ 1 file inspected, 33 offenses detected |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# 該当のディレクトリ以下のファイルは対象外 AllCops: Exclude: - bin/* - db/schema.rb - db/migrate/* - vendor/**/* # "Missing top-level class documentation comment."を無効 Style/Documentation: Enabled: false # "Use only ascii symbols in comment."を無効 Style/AsciiComments: Enabled: false # "Prefer single-quoted strings when you don't need string interpolation or special symbols."を無効 Style/StringLiterals: Enabled: false # "Line is too long"を無効 Metrics/LineLength: Enabled: false |
1 2 3 |
module Reins VERSION = "0.2.0".freeze end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# coding: utf-8 # filename: dispatcher.rb module Reins class Dispatch # 対象となるクライアントのIPアドレスを保持 # == パラメータ # ip:: 対象となるクライアントのIPアドレス # == 返り値 # 特になし def initialize(ip, key) @ip_address = ip @keycode = key end # コマンドを受け取って、対象の機能へ振り分ける # == パラメータ # comm:: クライアントからの要求コマンド # value:: コマンドの実行に必要な引数 # == 返り値 # exception:: 失敗した場合 # exception以外:: コマンドの実行結果 def command(comm, value) Reins.logger.debug("#{comm}(#{value}) : 指定のコマンドへディスパッチします") case comm when /^add/ Reins.regist_host.create(@ip_address, @key) when /^list/ Reins.regist_host.read_hosts when /^delete/ Reins.regist_host.delete(@ip_address) end end end end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
end # メモリ上のアドレスリストを、ファイルへ保管する # == パラメータ # filename:: 保管先ファイル名。指定がない場合は、初期化時に採用したファイル名 def store(filename = @filename) Reins.logger.debug("#{filename} : ホスト一覧を保存します") CSV.open(filename, "w") do |csv| @hosts.each do |addr| Reins.logger.debug("保管中... ] #{addr}") csv << addr end end end # 登録可能かどうかを検査する # == パラメータ # ipaddr:: 検査するIPアドレス # == 返り値 # IPアドレス:: 登録可能な場合 IPアドレスを返す # false:: 登録不可 def varid_ip?(ipaddr) addr = IPAddr.new(ipaddr).native.to_s read_hosts.include?(addr) ? false : addr rescue => e Reins.logger.error("#{e}: #{ipaddr} は登録可能なIPアドレスではありません.") false end # アドレスを新規に登録する。既に登録済みのものであれば登録しない。 # == パラメータ # ipaddr:: 登録するIPアドレス # key:: 登録する IPアドレスをキーにした、接続用Keyをもつハッシュデータ # == 返り値 # true:: 登録できた # false:: 既に同じアドレスまたはIPアドレスではないため、登録せず def create(ipaddr, key) if (addr = varid_ip?(ipaddr)) @hosts << [addr, key, Time.now.getlocal] Reins.logger.info("#{addr} を追加しました") store true else false end end # 登録済みホスト一覧を、IPアドレスをKey、接続KeyをValueとするハッシュで返す # == 返り値 # hash: hash[ipアドレス] = key を一つの要素とする def read_hostkeys @hosts.each.map { |addr, key| [addr, key] }.to_h end # 登録済みホスト一覧を配列で返す # == 返り値 # array: 登録済みのIPアドレス一覧 def read_hosts read_hostkeys.keys end # 登録済みのアドレスを削除する # == パラメータ # addr: 削除対象のアドレス # == 返り値 # string:: 削除された要素 # nil:: 削除すべき要素が見つからなかったとき def delete(addr) if (i = read_hosts.index(addr)) @hosts.delete_at(i) Reins.logger.info("#{addr} を削除しました.") store addr else Reins.logger.warn("#{addr} が見つかりません.") nil end end # まだアドレスが登録されていないかどうか # == 返り値 # true:: アドレス未登録 # false:: 1つ以上のアドレスが登録済み def empty? @hosts.empty? end end end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# coding: utf-8 # filename: lib/reins.rb require "reins/version" require "reins/dispatcher" require "reins/auth_service" require "reins/host_registry" require "reins/task_control" require "reins/config" require "socket" require "ipaddr" module Reins class << self # 指定されたポートでサーバを起動する # == パラメータ # port:: 割り当てるポート番号 # == 返り値 # Exception:: サーバが起動できなかった場合、例外を発生させ起動させない # TCPSocket:: 正常に起動した場合、Socket オブジェクトを返す def run_server(port) Reins.logger.info("Reins #{VERSION} を #{port} で起動します") TCPServer.new(port) rescue => e Reins.logger.error("Reins が起動できませんでした: #{e}") puts(e.to_s) exit end # 起動されているサーバを停止する # == パラメータ # server:: 起動中のサーバの Socket # == 返り値 # 特になし def exit_server(server) Reins.logger.info("Reins #{VERSION} を終了します") Reins.regist_host.store server.close exit end end class Clients attr_reader :command def initialize(client) @keycode, @command, @options = client.gets.chomp.split @addr = IPAddr.new(client.peeraddr[3]).native.to_s Reins.logger.debug("addr = #{@addr}, keycode = #{@keycode}, command = #{@command}, options = #{@options}") end # 認証処理を行う # == パラメータ # 特になし # == 返り値 # key:: 認証が成功した場合は接続用の認証キーを返す # false:: 認証が失敗した時は "false" 文字列を返す def run_auth Reins.logger.debug("#{@addr} : 認証を行います") if (key = Reins.auth_service.authenticate_key(@keycode, @addr)) if key == true Reins.regist_host.read_hostkeys[@addr] else Reins.regist_host.create(@addr, key) ? key : "false" end else "false" end end # サーバで実行されるコマンドを受け渡す # == パラメータ # 特になし # == 返り値 # false:: 実行できなければ "false" 文字列を返す # false以外:: 実行された結果を、改行を含む文字列で返す def run_command if Reins.auth_service.varid?(@keycode) == @addr Reins::Dispatch.new(@addr, @keycode).command(@command, @options) else "false" end end end def start server = run_server(Reins.port) loop do begin Thread.start(server.accept) do |c| client = Reins::Clients.new(c) c.puts client.command == 'auth' ? client.run_auth : client.run_command c.close end rescue Interrupt exit_server(server) end end end module_function :start end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# coding: utf-8 # filename: config.rb require 'logger' module ReinsAgent class << self attr_accessor :logger, :client_key, :client_port, :server_host, :server_port def configure yield self end end end ReinsAgent.configure do |config| config.logger = Logger.new(ENV['REINS_AGENT_LOGGER'] || "/tmp/reins_agent.log") config.client_key = ENV['REINS_AGENT_KEY'] || "40ruby" config.client_port = ENV['REINS_AGENT_PORT'] || 24_368 config.server_host = ENV['REINS_SERVER_HOST'] || "127.0.0.1" config.server_port = ENV['REINS_PORT'] || 16_383 end ReinsAgent.logger.level = ENV['REINS_AGENT_LOGLEVEL'] ? eval("Logger::#{ENV['REINS_AGENT_LOGLEVEL']}") : Logger::WARN |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# coding: utf-8 # filename: reins_agent.rb require "reins_agent/config" require "reins_agent/version" require "ipaddr" require "socket" module ReinsAgent class << self def run_agent(port) ReinsAgent.logger.info("Reins Agent #{VERSION} を #{port} で起動します") throw unless (@cert_key = ReinsAgent.exec("#{ReinsAgent.client_key} auth").chomp) ReinsAgent.logger.debug("認証キー : #{@cert_key}") @agent = TCPServer.new(ReinsAgent.client_port) rescue => e ReinsAgent.logger.fatal("Reins Agent が起動できませんでした: #{e}") exit end def define_value(r) @addr = IPAddr.new(r.peeraddr[3]).native.to_s @keycode, @command, @options = r.gets.chomp.split ReinsAgent.logger.debug("addr = #{@addr}, keycode = #{@keycode}, command = #{@command}, options = #{@options}") end def exit_agent(agent) ReinsAgent.logger.info("Reins Agent #{VERSION} を終了します") ReinsAgent.exec("#{@cert_key} delete") agent.close exit end # サーバへコマンドを送信し、その返り値を取得する # == パラメータ # command:: サーバへ送信するコマンド+オプションを指定 # == 返り値 # response:: サーバからの返り値(改行付き/複数行) def exec(command) s = TCPSocket.open(ReinsAgent.server_host, ReinsAgent.server_port) s.puts command response = s.read s.close if s response end end def start agent = run_agent(ReinsAgent.client_port) puts ReinsAgent.exec("#{@cert_key} list") loop do begin Thread.start(agent.accept) do |r| define_value(r) if @cert_key == @keycode r.puts("OK") end r.close end rescue Interrupt exit_agent(agent) end end end module_function :start end |
1 2 |
$ ruby -Ilib bin/reins_agent 127.0.0.1 |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
I, [2017-MM-DDThh:mm:ss.498901 #1701] INFO -- : Reins 0.2.0 を 16383 で起動します D, [2017-MM-DDThh:mm:ss.648611 #1701] DEBUG -- : addr = 127.0.0.1, keycode = 40ruby, command = auth, options = D, [2017-MM-DDThh:mm:ss.648704 #1701] DEBUG -- : 127.0.0.1 : 認証を行います I, [2017-MM-DDThh:mm:ss.649729 #1701] INFO -- : 127.0.0.1 : 認証が成功しました I, [2017-MM-DDThh:mm:ss.651070 #1701] INFO -- : 127.0.0.1 を追加しました D, [2017-MM-DDThh:mm:ss.651132 #1701] DEBUG -- : ./40ruby.csv : ホスト一覧を保存します D, [2017-MM-DDThh:mm:ss.651967 #1701] DEBUG -- : 保管中... > ["127.0.0.1", "ed8a4bfe4fed89005d25ae33179ea441c4933a8f841ab8c6154490f5ca34a18978fe8d91d490096a0589edbd0bcd394358a7efb65aa87e48e683a1e7847e6d3f", 2017-MM-DD hh:mm:ss +0900] D, [2017-MM-DDThh:mm:ss.654361 #1701] DEBUG -- : addr = 127.0.0.1, keycode = ed8a4bfe4fed89005d25ae33179ea441c4933a8f841ab8c6154490f5ca34a18978fe8d91d490096a0589edbd0bcd394358a7efb65aa87e48e683a1e7847e6d3f, command = list, options = D, [2017-MM-DDThh:mm:ss.654452 #1701] DEBUG -- : list() : 指定のコマンドへディスパッチします D, [2017-MM-DDThh:mm:35.315032 #1701] DEBUG -- : addr = 127.0.0.1, keycode = ed8a4bfe4fed89005d25ae33179ea441c4933a8f841ab8c6154490f5ca34a18978fe8d91d490096a0589edbd0bcd394358a7efb65aa87e48e683a1e7847e6d3f, command = delete, options = D, [2017-MM-DDThh:mm:ss.315186 #1701] DEBUG -- : delete() : 指定のコマンドへディスパッチします I, [2017-MM-DDThh:mm:ss.315280 #1701] INFO -- : 127.0.0.1 を削除しました. D, [2017-MM-DDThh:mm:ss.315324 #1701] DEBUG -- : ./40ruby.csv : ホスト一覧を保存します |