thorでサブコマンドを指定しない方法

thorCLI を作成するのによく使われる gem (らしい)ですが、サブコマンドを指定せずに実行する方法が Github Wiki公式サイト に見当たらず、小一時間悩んだのでメモっておく。

例えば こんなことがやりたい。

$ bin/cli.rb -l ja world
こんにちは world

$ bin/cli.rb -l en world
Hello world

解決方法

default_command と 可変長引数を使えばよい。

#!/usr/bin/env ruby
require 'thor'

module MyApp
  class CLI < Thor
    default_command :hello

    desc 'hello', 'description here'
    method_option :lang, type: :string,  aliases: ['-l']
    def hello *args
      case options[:lang]
      when "ja"
        hello = 'こんにちは'
      else
        hello = 'Hello'
      end

      puts "#{hello} #{args.join(', ')}"
    end
  end

end

MyApp::CLI.start

Rails で発生した Error を NewRelicに通知する

Rails アプリでは newrelic_rpm を Gemfile に書いておけば、よしなにエラーを NewRelic のダッシュボードに通知してくれます。

が、

rescue_from Exception, with: :render_500

def render_500(e = nil)
  logger.error e
  render file: 'public/500', status: :internal_server_error, layout: false   
end

のようなコードで Exception を握りつぶしてるとダッシュボードで通知されません。

Sending New Relic handled errors によれば、

Use the Ruby Agent API NewRelic::Agent.notice_error within your error handler, which tells the agent to send the error data to New Relic. This API call takes the exception and an optional options hash. Use this format:

とのことなので、

def render_500(e = nil)
  logger.error e
  NewRelic::Agent.notice_error(e)

  render file: 'public/500', status: :internal_server_error, layout: false
end

と書けば ok です。

XMLにおいて アンパサンド(&)はエスケープが必要

基本的すぎる話かもしれないが... sitemap_generator で XML を生成していて気付いた。

サイトマップ ファイルは UTF-8 エンコードで作成する必要があります (ファイルを保存すると、通常は UTF-8 エンコードで保存されます)。 他の XML ファイルと同じように、URL などのデータ値では、次の文字にエンティティのエスケープ コードを使用する必要があります。

文字 エスケープコード
アンパサンド & &amp;
一重引用符 ' &apos;
二重引用符 " &quot;
不等記号 (より大) > &gt;
不等記号 (より小) < &lt;

http://www.sitemaps.org/ja/protocol.html#escaping

W3C Recommendation には

The ampersand character (&) and the left angle bracket (<) must not appear in their literal form, except when used as markup delimiters, or within a comment, a processing instruction, or a CDATA section. If they are needed elsewhere, they must be escaped using either numeric character references or the strings " & " and " < " respectively.

と書かれている。

知らなかったー

ActiveRecordのestablish_connectionを読む

ActiveRecord における DB との接続確立方法をきちんと理解できてなかったので、pry-byebug を使いながらコードを読み解いてみる。

Railsのversion は 4.2.0 という前提で。

establish_connection

blog.livedoor.jp

sonots さん解説の通り、Rails を起動すると establish_connection メソッドが呼び出される。

# lib/active_record/connection_handling.rb

    def establish_connection(spec = nil)
      spec     ||= DEFAULT_ENV.call.to_sym
      resolver =   ConnectionAdapters::ConnectionSpecification::Resolver.new configurations
      spec     =   resolver.spec(spec)

      unless respond_to?(spec.adapter_method)
        raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
      end

      remove_connection
      connection_handler.establish_connection self, spec
    end

binding.pry でデバッグ

適当な箇所に binding.pry を挿入して rails server するとコードを追いかけやすくてよい。

self は何?

[12] pry(ActiveRecord::Base)> self
=> ActiveRecord::Base

なぜ self が ActiveRecord::Base になるかというと、ConnectionHandling モジュールを extendしてるから。 https://github.com/rails/rails/blob/v4.2.0/activerecord/lib/active_record/base.rb#L276

spec とは?

[11] pry(ActiveRecord::Base)> p spec
:development

resolver.spec(spec) の結果は?

[1] pry(ActiveRecord::Base):1> resolver.spec(spec)
=> #<ActiveRecord::ConnectionAdapters::ConnectionSpecification:0x007fa52311ae20
 @adapter_method="mysql2_connection",
 @config={:adapter=>"mysql2", :encoding=>"utf8mb4", :database=>"development", :pool=>5, :username=>"root", :password=>nil, :socket=>"/tmp/mysql.sock"}>

connection_handler とは?

def self.connection_handler
  ActiveRecord::RuntimeRegistry.connection_handler || default_connection_handler
end

def self.connection_handler=(handler)
  ActiveRecord::RuntimeRegistry.connection_handler = handler
end

self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new

https://github.com/rails/rails/blob/v4.2.0/activerecord/lib/active_record/core.rb#L100-L102

により、ActiveRecord::ConnectionAdapters::ConnectionHandler のインスタンスということになる。

そして...

# lib/active_record/connection_handling.rb

    def establish_connection(spec = nil)
      spec     ||= DEFAULT_ENV.call.to_sym
      resolver =   ConnectionAdapters::ConnectionSpecification::Resolver.new configurations
      spec     =   resolver.spec(spec)

      unless respond_to?(spec.adapter_method)
        raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
      end

      remove_connection
      connection_handler.establish_connection self, spec
    end

の最後の一行は

# lib/active_record/connection_adapters/abstract/connection_pool.rb
def establish_connection(owner, spec)
  @class_to_pool.clear
  raise RuntimeError, "Anonymous class is not allowed." unless owner.name
  owner_to_pool[owner.name] = ConnectionAdapters::ConnectionPool.new(spec)
end

lib/active_record/connection_adapters/abstract/connection_pool.rb に行き着く。

実は owner_to_pool に instance を代入しているだけで接続していない。

いつ接続するのか?

[Ruby] 例えば、ActiveRecord の connection_pool を止める - sonots:blog

同じく sonots さんの解説の通り、クエリが投げられる時に connection pool から取得 or 新規接続という感じになっている。

#connection

#checkout

#acquire_connection

#checkout_new_connection

#new_connection

という流れで、

def new_connection
  Base.send(spec.adapter_method, spec.config)
end

がコネクションを貼ってる箇所になり、spec.adapter_method を send で呼び出すことになる。

[10] pry(ActiveRecord::Base)> p spec.adapter_method
=> "mysql2_connection"

まとめ

establish_connectionでは接続確立せず、実際はクエリを投げるタイミングである。

また、spec.adapter_methodには 例えば "mysql2_connection" などが格納されており、これを動的に実行することでconfig/database.yml に書かれた DB に接続する。

ブロードキャストなICMP Echo Requestを無視する

OSはUbuntu 14.04。

結論から書くと、

echo "net.ipv4.icmp_echo_ignore_broadcasts=1" > /etc/sysctl.d/60-icmp-echo.conf

service procps start

でOK(のはず)。

設定前

$ ping 192.168.33.255
PING 192.168.33.255 (192.168.33.255): 56 data bytes
64 bytes from 192.168.33.1: icmp_seq=0 ttl=64 time=0.081 ms
64 bytes from 192.168.33.20: icmp_seq=0 ttl=64 time=0.395 ms
64 bytes from 192.168.33.1: icmp_seq=1 ttl=64 time=0.125 ms
64 bytes from 192.168.33.20: icmp_seq=1 ttl=64 time=0.665 ms

設定後

$ ping 192.168.33.255
PING 192.168.33.255 (192.168.33.255): 56 data bytes
64 bytes from 192.168.33.1: icmp_seq=0 ttl=64 time=0.081 ms
64 bytes from 192.168.33.1: icmp_seq=1 ttl=64 time=0.134 ms

以下、調べたことをメモしておく。

/etc/init/procps.conf

$ cat /etc/init/procps.conf
# procps - set sysctls from /etc/sysctl.conf
#
# This task sets kernel sysctl variables from /etc/sysctl.conf and
# /etc/sysctl.d

snip...

task
script
    cat /etc/sysctl.d/*.conf /etc/sysctl.conf | sysctl -e -p -
end script

このタスクにより、起動時に/etc/sysctl.d/ と /etc/sysctl.conf が適用される。

デーモン化するわけじゃないことに注意。単に task として sysctl コマンドにパイプしてるつくりなので。

/etc/sysctl.d/

README曰く..

This directory contains settings similar to those found in /etc/sysctl.conf.
In general, files in the 10-*.conf range come from the procps package and
serve as system defaults.  Other packages install their files in the
30-*.conf range, to override system defaults.  End-users can use 60-*.conf
and above, or use /etc/sysctl.conf directly, which overrides anything in
this directory.

なので、今回の設定は /etc/sysctl.d/60-icmp-echo.conf にしておいた。

White Paper : いまさら聞けない、SSL サーバ証明書とルート証明書の関係 - Symantec

分かりやすくまとまってるので、ふと忘れてしまった時に。 https://www.jp.websecurity.symantec.com/welcome/pdf/wp_sslandroot-certificate.pdf

ついでに CSR についてもメモっておく。

CSRとは、お客様が生成し、認証局に提出する署名リクエスト(Certificate Signing Request)です。ジオトラストのSSLサーバ証明書の申請には、SSLサーバ証明書を導入する環境で生成するCSRが必要になります。CSRには、お客様の公開鍵の情報の他、生成時に指定する情報(ディスティングイッシュネーム)が含まれます。 ジオトラストのSSLサーバ証明書は、認証の後、お客様の公開鍵に署名をした上でSSL証明書として発行しています。

https://www.geotrust.co.jp/support/ssl/csr/