# から始まるコミットメッセージは無視される

起こったこと

僕はコミットメッセージに #ISSUE_NUMBER#PR_NUMBER を積極的に入れる派閥の人間なので

git revert -m 1 xxxxxxxxx を行った時にコミットメッセージの編集で

"#PR_NUMBER をマージしたコミットをリバートした #PR_NUMBER_2 をマージしたコミットを再度リバート(三度目の正直)"

という頭の悪いメッセージを入れようとしていたんですが、保存してもなぜか反映されないので困りました。

Revert "Merge pull request #PR_NUMBER_2 from revert-ikima-su"
    
This reverts commit xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, reversing
changes made to yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy.

#PR_NUMBER をマージしたコミットをリバートした #PR_NUMBER_2 をマージしたコミットを再度リバート(三度目の正直)

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Fri Jun 11 10:51:42 2021 +0900
#
.
.
.

:wq
$ git show
commit zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz (HEAD -> sandomeno-shojiki)
Author: shutooike <shutofootball@icloud.com>
Date:   Fri Jun 11 10:51:42 2021 +0900

    Revert "Merge pull request #PR_NUMBER_2 from revert-ikima-su"
    
    This reverts commit xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, reversing
    changes made to yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy.

diff --git ・・・・

文章がややこしすぎて vim が理解できない・・・?

解決

Revert "Merge pull request #PR_NUMBER_2 from revert-ikima-su"
    
This reverts commit xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, reversing
changes made to yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy.

#PR_NUMBER をマージしたコミットをリバートした #PR_NUMBER_2 をマージしたコミットを再度リバート(三度目の正直)

# Please enter the commit message for your changes. Lines starting 
# with '#' will be ignored, and an empty message aborts the commit. 
#
# Date:      Fri Jun 11 10:51:42 2021 +0900
#
.
.
.

:wq

先ほどの vim の画面をもう一度よく読むと、

Lines starting with '#' will be ignored

書いてました。なるほど、vim は悪くなかったんですね。勉強になります📝

Revert "Merge pull request #PR_NUMBER_2 from revert-ikima-su"
    
This reverts commit xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, reversing
changes made to yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy.

PR #PR_NUMBER をマージしたコミットをリバートした PR #PR_NUMBER_2 をマージしたコミットを再度リバート(三度目の正直)

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Fri Jun 11 10:51:42 2021 +0900
#
.
.
.

:wq

頭に「PR」をつけることで解決しました。

$ git show
commit zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz (HEAD -> sandomeno-shojiki)
Author: shutooike <shutofootball@icloud.com>
Date:   Fri Jun 11 10:51:42 2021 +0900

    Revert "Merge pull request #PR_NUMBER_2 from revert-ikima-su"
    
    This reverts commit xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, reversing
    changes made to yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy.
    
    PR #PR_NUMBER をマージしたコミットをリバートした PR #PR_NUMBER_2 をマージしたコミットを再度リバート(三度目の正直)
   
diff --git ・・・・

いつもは git commit -m "コミットメッセージ #ISSUE_NUMBER" というスタイルだったのではじめてハマりました。

半年ぐらい後の自分がまたハマってそうなのでメモっておきます。

おわりに

弊社では全ての分野のエンジニアを絶賛大募集中です!ご興味あればぜひ下記リンクからお願いします!

ingage.co.jp

ではまた!

Linux デスクトップの環境変数、どこに設定してますか?

f:id:masm11:20210530233947j:plain

こんにちは、時々 Linux デスクトップの話を混ぜていきたい masm11 です。 今回は Linux デスクトップの環境変数をどこに設定するか、 というテーマで書きたいと思います。

環境変数の設定方法はいくつか用意されています。その紹介と、結局どこがいいの? という話です。

シェルの rc

環境変数と言えば真っ先に思いつくのがこれだと思います。 bash なら ~/.bashrc や ~/.bash_profile、私は zsh を使っているので ~/.zshenv になります。

export FOO=bar

という形式で設定しますね。

しかし、ここに書いても、効果があるのはシェルおよびそこから起動したプロセスのみですね。 デスクトップには効果がありません。 例えば gdm 等からログインすると、デスクトップから Emacs を起動しても、 ここに設定した環境変数は反映されていません。

~/.xprofile

「それならここじゃん!」と思う方、結構いらっしゃるのでは? ~/.xprofile です。

gdm からログインすると、デスクトップ起動前にこのファイルを読み込んで 実行してくれますね。

中身はシェルスクリプトですので、

export FOO=bar

と書いておけばいいです。また、シェルが実行するわけですから、

. $HOME/.zshenv

と書けば、設定箇所は一箇所で済みます。

しかしこれにも効果がない場合があります。

~/.xprofile はファイル名のとおり、X の場合にしか通用しません。 最近流行りの Wayland では読み込んでくれません。 私は Wayland なのです。

~/.pam_environment

さて、この辺からなんだか怪しくなってきます。 ~/.pam_environment というファイルに設定することもできます。

PAM をご存知でしょうか? Pluggable Authentication Module の略で、 認証まわりを引き受けているプログラムや設定ファイル群です。

設定ファイルは /usr/lib/pam.d/ や /etc/pam.d/ にあります。 例えば、私の /etc/pam.d/system-login には以下の行があります。

session    required   pam_env.so           user_readenv=1

この設定により、ログインセッションの初期化時に pam_env.so が実行されます。 pam_env.so に与えた user_readenv=1 によって、~/.pam_environment が 読み込まれます。

この ~/.pam_environment は以下のような形式です。

PATH    DEFAULT=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin

DEFAULT= だけでなく OVERRIDE= も書けるようなのですが、 詳細はまだ私も理解していないので省略します。

さすが PAM から読み込まれるだけあって、結構優秀です。いろんなパターンで使えます。

で、これの残念な点は、マニュアルに以下のように書いてあることです。

Due to problematic security this functionality is deprecated since
the 1.5.0 version and will be removed completely at some point in
the future.

なんと非推奨! 将来的には削除されるそうです。

homectl

次行きましょう。systemd-homed は以前の記事で一言だけ紹介した記憶があります。 そのコマンドで homectl というものがあります。

homectl update --setenv FOO=bar

と実行すると、~/.identity ファイルの中に次のように書き込まれます。

"environment" : [
        "FOO=bar"
],

JSON です。この状態でログインすると、環境変数が設定されています。

なお、この環境変数も、PAM 中で設定されます。/etc/pam.d/system-login の中に

-session   optional   pam_systemd.so

という行があり、この pam_systemd.so が設定してくれます。

これも良さそうなのですが、こちらも残念な点が。pam_systemd.so のコード中に以下の部分があります。

https://github.com/systemd/systemd/blob/4ab334472cd1e4a0dd17a4e35e888d380f56ff04/src/login/pam_systemd.c#L545-L549

既に環境変数がセットされていたら、セットしない、という処理です。

PAM のモジュールと言えど、PATH や LANG が設定されていないわけがありません。 つまり、PATH や LANG は homectl で設定しても反映されないのです。

使えね-ー

あ、ちなみに homectl にはいくつか癖がありまして…

  • homectl --setenv は最後に実行したものしか残らない

    つまり、FOO=barBAR=baz を設定しようとして

    homectl --setenv FOO=bar; homectl --setenv BAR=baz

    としても、後者しか残らない、ということです。--setenv は複数書くことができますので、

    homectl --setenv FOO=bar --setenv BAR=baz

    と実行してください。

  • ~/.identity を直接編集しても効果はない

    homectl コマンドなんて使わなくても ~/.identity を直接書けばいいじゃん、と思ったあなた。 それは意味がありません。直接書き換えても反映されません。

    諦めて homectl コマンドを使いましょう。

結局、

どれがいいんでしょう……?

私がぐぐりまくって辿り着いた結論を以下に書きます。

基本的には ~/.zshenv に書きます。しかしそれだけではデスクトップに効果がありませんので、

/usr/share/wayland-sessions/wayfire.desktop:

[Desktop Entry]
Name=Wayfire
Exec=/usr/local/bin/start-wayland-session wayfire
TryExec=wayfire
Icon=
Type=Application
DesktopNames=Wayfire

Exec= の行を書き換えて、 wayfire 起動時に wayfire そのものでなく /usr/local/bin/start-wayland-session を起動するようにします。

/usr/local/bin/start-wayland-session:

#!/bin/bash

if [ -x "$HOME"/.wayland-session ]; then
  exec "$HOME"/.wayland-session "$@"
else
  exec "$@"
fi

こうして、~/.wayland-session があったらそれを実行します。なければ引数の wayfire を そのまま実行します。

~/.wayland-session:

#!/bin/zsh
exec "$@"

このスクリプトは zsh で実行されます。zsh は ~/.zshenv を読み込みますので、そこで 環境変数が設定されます。その後、引数の wayfire を実行します。

まとめ

システム側のファイルを書き換えて解決してしまいました。 以前はユーザ側ファイルだけでなんとかなりました。

最近の Linux デスクトップのヘタレっぷりはとても残念です。 環境変数を一箇所で設定して、いろんなパターンで反映されて欲しい、 たったそれだけのことなのに、何故こんなに悩まないといけないのでしょう? 既に設定されてたら上書きしないとか、意味わかんないです。 設定しろって設定したんだから、ちゃんと設定してくれよ!

ちなみに gdm は PATH を無駄に上書きするので、使うのをやめました。 gdm は複雑すぎるためか、よく不具合起こしますね。

こんなことを愚痴りあえる人が欲しいです。以下のページからお願いします!

https://ingage.co.jp/recruit/

サーバのSSL証明書情報を調べるには?

f:id:ingage:20210519225330p:plain

id:kizashi1122 です。

弊社のサービスを利用しているお客様から「設定したSMTPサーバにうまく接続できない」という問合せを受けることがたまにあります。

接続できない原因としてありえるのが、SSL証明書の問題です。有効期限が切れていたり、接続に使ったドメインと証明書の Common Name が一致してない場合などです。

HTTPS であればブラウザからアクセスし、ブラウザの機能をつかって証明書の内容を確認することができます。

SMTPやPOP3であればどうすればよいでしょうか?

それ、 openssl コマンドを使えばできます。

SMTPS の場合

$ openssl s_client -connect smtp.example.com:465 -showcerts

POP3S の場合

$ openssl s_client -connect pop.example.com:995 -showcerts

SMTP(STRATTLS) の場合

たまに SMTPS には対応しておらず STARTTLS には対応している場合もあります。 そんな場合はこうします。

$ openssl s_client -starttls smtp -crlf -connect smtp.example.com:587

接続後はそのままSMTPのやりとりができます。

便利ですね!

Athena のパーティション設定2種

こんにちは、masm11 です。 今回は AWS Athena のパーティションの設定方法について説明したいと思います。

まず Athena とは

Cloudwatch Logs やその他の場所でログが発生すると、 手軽に検索したくなりますよね。

そういう場合、S3 に置いておくと Athena で SQL を使って検索できます。

発生したログはただのテキストだと思いますので、なんとかして以下のように JSON にしておきます。Cloudwatch Logs に溜まっているなら、Firehose から Lambda を使うのが良いでしょう。

{"timestamp":"2021-04-24T22:50:45","message":"test log 1"}
{"timestamp":"2021-04-24T22:51:00","message":"test log 2"}

こういうファイルをたくさん S3 に溜めておきます。

Athena では以下のように設定します。

CREATE EXTERNAL TABLE logs (
  timestamp string,
  message   string
)
ROW FORMAT SERDE 'org.apache.hive.hcatalog.data.JsonSerDe'
LOCATION 's3://bucket/logs';

こうすると、SQL を使って検索できます。

SELECT * FROM logs WHERE message LIKE 'test log%';

ただし、ここで重大な問題があります。この SQL でスキャンしたデータ量で 料金が発生するのです。なんとかしてスキャンする量を絞る必要があります。

そこでパーティションの出番です。

設定方法その1

S3 バケットの中を以下のように構造化しておきます。 Hive 形式と呼ばれるものです。

s3://bucket/logs/year=YYYY/month=MM/day=DD/file.ext

year= 等はそのままプレフィックスに含めます。YYYY 等は 数字に置き換えます。つまり、プレフィックスには year=2021 等という 文字列が含まれるわけですね。

そして、CREATE EXTERNAL TABLE は以下のように実行します。

CREATE EXTERNAL TABLE logs (
  timestamp string,
  message   string
)
PARTITIONED BY (year STRING, month STRING, day STRING)
ROW FORMAT SERDE 'org.apache.hive.hcatalog.data.JsonSerDe'
LOCATION 's3://bucket/logs';

その後、

MSCK REPAIR TABLE logs;

を実行します。これで S3 のファイル名がスキャンされて、パーティションの 一覧が作成されます。この時には料金はかかりません。

これで準備ができました。あとは SELECT するだけです。

SELECT * FROM logs WHERE year = '2021' AND month = '04' AND day = '24' AND message LIKE 'test log%';

こうすると、2021-04-24 のログだけがスキャンされます。

ただし、プレフィックスが増えた時には MSCK REPAIR TABLE をしてやる 必要があります。

MSCK REPAIR TABLE だと、ファイル数が増えてくると時間がかかりますので、 代わりに ALTER TABLE ADD PARTITION を使っても良いでしょう。 その場合は、プレフィックスが不要になった場合は ALTER TABLE DROP PARTITION をする必要があります。しなくてもエラーは発生しませんが、 パーティションが増えると検索が遅くなります。

設定方法その2

設定方法その1は、パーティションのメンテが手間なんですよね。 そこで2つめの方法です。

s3://bucket/logs/YYYY/MM/DD/file.ext

year= 等がなくなり、シンプルになりましたね。

CREATE EXTERNAL TABLE は以下のようになります。

CREATE EXTERNAL TABLE logs (
  timestamp string,
  message   string
)
PARTITIONED BY (
  year INT,
  month INT,
  day INT
)
ROW FORMAT SERDE 'org.apache.hive.hcatalog.data.JsonSerDe'
LOCATION 's3://bucket/logs'
TBLPROPERTIES (
  'projection.enabled' = 'true',

  'projection.year.type' = 'integer',
  'projection.year.range' = '2021,2100',
  'projection.year.digits' = '4',

  'projection.month.type' = 'integer',
  'projection.month.range' = '1,12',
  'projection.month.digits' = '2',

  'projection.day.type' = 'integer',
  'projection.day.range' = '1,31'
  'projection.day.digits' = '2',

  'storage.location.template' = 's3://bucket/logs/${year}/${month}/${day}'
);

随分長くなりました。

'projection.day.type' = 'integer',
'projection.day.range' = '1,31'
'projection.day.digits' = '2',

day は整数で、1〜31 で、2桁であることを宣言しています。桁数が足りなければ 0 でパディングされます。

準備はこれで終わりです。検索はその1とだいたい同じですが、年月日は整数になります。

SELECT * FROM logs WHERE year = 2021 AND month = 4 AND day = 24 AND message LIKE 'test log%';

そしてこちらの方法はパーティションのメンテは不要です。

ただし、パーティションのキーはあらかじめ範囲を決めておく必要があります。つまり、

'projection.year.range' = '2021,∞',

なんて指定はできません。

まとめ

Athena のパーティションの設定方法を2つ紹介しました。 特に理由がなければ後者で良いと思います。

インゲージではエンジニアを募集しています。詳細は以下のページへ!

https://ingage.co.jp/recruit/

RSpec の spec type を独自フォルダ配下の spec にも自動で付与したい

f:id:shutooike:20210420184634p:plain

おはようございます!

最近 DMM Books の70%OFF祭りで読みたかった技術書を50冊 *1 ほど買い込んだ @shutooike です!

前回 と同様に、今回も整備中に見つけた RSpec 小ネタを共有します。

前提条件

rspec-rails: 3.9.0

spec type の自動付与とは?

# spec/rails_helper.rb

RSpec.configure do |config|
  config.infer_spec_type_from_file_location!
end

rails_helper.rb 内で上のようにすると

spec/models 配下の spec には type: :model というメタデータがつきます。これを今回は spec type の自動付与と呼びます。

この自動付与が行われるのは以下の場合のみで spec/ に掘った独自ディレクトリには当たり前ですが付与されません。

ディレクトリ タイプ
spec/controllers :controller
spec/helpers :helper
spec/jobs :job
spec/mailer :mailer
spec/models :model
spec/request, spec/integration, spec/api :request
spec/routing :routing
spec/view :views
spec/feature :feature
spec/system :system

やりたいこと

うちでは spec/lib/ という独自ディレクトリに lib/ の spec を書いていて、配下の spec に type: lib を付与する必要が出てきました。

解決策

# spec/rails_helper.rb

RSpec::Rails::DIRECTORY_MAPPINGS[:lib] = %w(spec lib) # <- この行を追加

RSpec.configure do |config|
.
.

解説

先ほど紹介した config.infer_spec_type_from_file_location!実装 を見ると

def infer_spec_type_from_file_location!
  DIRECTORY_MAPPINGS.each do |type, dir_parts|
    escaped_path = Regexp.compile(dir_parts.join('[\\\/]') + '[\\\/]')
    define_derived_metadata(:file_path => escaped_path) do |metadata|
      metadata[:type] ||= type
    end
  end
end

ほうほう、DIRECTORY_MAPPINGS というハッシュを回して type を付与しているっぽいですね。

DIRECTORY_MAPPINGS定義 を見にいきましょう。

DIRECTORY_MAPPINGS = {
  :controller => %w[spec controllers],
  :helper     => %w[spec helpers],
  :job        => %w[spec jobs],
  :mailer     => %w[spec mailers],
  :model      => %w[spec models],
  :request    => %w[spec (requests|integration|api)],
  :routing    => %w[spec routing],
  :view       => %w[spec views],
  :feature    => %w[spec features],
  :system     => %w[spec system]
}

ビンゴ!しかも freeze されていないので後から追加可能です。

また DIRECTORY_MAPPINGS は 2021/04/25 現在の main ブランチでも定義されてたので当分はバージョンアップに怯える必要もなさそうです。

おわりに

Rubymine のコードジャンプがあればソースリーディングが簡単にできていいですね〜

弊社では全ての分野のエンジニアを絶賛大募集中です!ご興味あればぜひ下記リンクからお願いします!

ingage.co.jp

ではまた!

*1:15万(総額) - 10万(クーポン) = 5万(支払い金額) という崩壊っぷりでした。

RSpec で特定の spec type の時だけ before フックを動かしたい

f:id:shutooike:20210420184634p:plain

おはようございます!

最近は隙間時間にインゲージのテスト環境を整備をしている @shutooike です!

今回は整備中に見つけた RSpec 小ネタを共有します。

前提条件

rspec-rails: 3.9.0

やりたいこと

RSpec で特定の spec type の時だけ before フックを動かしたい

解決策

# spec/rails_helper.rb

RSpec.configure do |config|
  # type が :model の時
  config.before(:each, type: :model) do
    # do something
  end
  
  # type が :model, :request ではない時
  config.before(:each, type: -> (t) { %i(model request).exclude?(t) }) do
    # do something
  end
end 

もちろん before:each 以外でも動きます。

おわりに

lambda も渡せるの便利ですねー!

弊社ではテスト環境に一家言お持ちの方もお持ちでない方も募集しております!!

ご興味あればぜひ下記リンクを覗いてみてください 👀

ingage.co.jp

ではまた!

Content-type: message/rfc822 の謎

f:id:ingage:20210419194939p:plain

id:kizashi1122 です。 メールの話です。

メールってとてもややこしいんですよね。 マルチパートと言って、パート部分を複数持つこともできますし、マルチパートを入れ子にすることもできます。

  • メール本体
    • 本文(テキスト)
    • 本文(HTML)
    • 添付ファイル1

みたいなのが典型的なマルチパートメールです。

添付ファイルであれば、Content-Type が image/png だったり application/pdf だったりするわけです。
たまに Content-Type が message/rfc822 の場合もあります。

よくあるのがメールの不達で返ってきたメール(バウンスメール)です。 @ より前のユーザが存在しない場合は、Unknown User で返ってくる例のあのメールです。

メールサーバーによりますがバウンスメールにはそもそも届けたかったメールが添付されていることがあります。 つまりメールにメールが添付されてるということになります。

そのときに使う Content-Typeが message/rfc822 なのです。 こんなイメージです。

Date: Mon, 19 Apr 2021 08:38:03 +0900 (JST)
From: MAILER-DAEMON@example.com (Mail Delivery System)
Subject: Undelivered Mail Returned to Sender
To: system@example.com
MIME-Version: 1.0
Content-Type: multipart/report; report-type=delivery-status;
        boundary="aaaaaa"
Message-Id: <20210418233803@example.com>

This is a MIME-encapsulated message.

--aaaaaa
Content-Description: Notification
Content-Type: text/plain; charset=us-ascii

Unknown User ですよ的なメッセージ



--aaaaaa
Content-Description: Undelivered Message
Content-Type: message/rfc822

Content-Transfer-Encoding: 7bit
Content-Type: text/plain; charset=iso-2022-jp
Mime-Version: 1.0
To: example@example.jp
From: example@example.co.jp
Subject: foo


元メールの本分


--aaaaaa--

みたいな感じです。

タイトルで「謎」と書きましたが、何が謎なのか。

最近こういうバウンスメールをみるのです。

Content-Type: message/rfc822; charset=utf-8
Content-Transfer-Encoding: base64


WyRyO3Ikajh8JC84Zk5pPz0kNz5lJDIkXiQ5ISMbKEINCg0KGyRCQWFCLiRHJE8kNCQ2JCQkXiQ5
JCwhIhsoQg0KGyRCQGhITCEiJSshPCVJMnEwd01NJCskaSROJDRNeE1RSF1HJyROPz1AQSRLSDwk
:
:

ふーん、 元のメッセージが base64 エンコードされてるのね、というメールなのですが、うちのサービスではこのバウンスメールの扱いに失敗します。

なんでだろう?

と思って、RFCのドキュメントを読むことにしました。

RFC 2046: Multipurpose Internet Mail Extensions (MIME) Part Two: Media Types

の Page 29 に

No encoding other than "7bit", "8bit", or "binary" is permitted for the body of a "message/rfc822" entity

とあります。つまり RFC 違反のメールであり、うちが使っているライブラリでは RFC に則って作られていたため、 Content-Transfer-Encoding: base64 が指定してあってもそれを無視して本文をそのまま扱っていたということになります。

フォーマットの違うCSVをインポートしようとしたらエラーで弾けばいいでしょう。 ただメールの世界ではルール違反(RFC違反)はもはや当たり前です。どこまでをサポートすればよいのかは悩み外どころです。

うちでは今回は Content-Transfer-Encoding: base64 が指定してあれば、明示的に base64 でデコードしてあげることにしました。

以上です。

インゲージではエンジニアを募集しています!

ではまた。