くりにっき

フルスタックキュアエンジニアです

sentry-ravenを使ってるアプリは基本的にsentry-rubyに移行した方がよさそう

tl;dr;

経緯

正月休み中にRubyで趣味アプリを作っててSentryを導入しようとしたところ、いつもと違うセットアップ手順だったので気づきました。

詳しいこと

v3までがsentry-ravenでv4以降がsentry-rubyらしいです。

f:id:sue445:20210127203822p:plain

https://rubygems.org/gems/sentry-raven/versions

f:id:sue445:20210127203753p:plain

https://rubygems.org/gems/sentry-ruby/versions

先月上旬にはv4が出ていたらしい。

dependabotとかで自動bundle updateしてるだけだと絶対に気づかないやつ...

古いsentry-ravenについて

https://github.com/getsentry/sentry-ruby/tree/master/sentry-raven によると

Future Support: sentry-raven has entered maintenance mode, which means it won't receive any new feature supports or aggressive bug fixes.

とのことなので移行した方がよさそうです

新しいsentry-rubyについて

github.com

移行方法は https://docs.sentry.io/platforms/ruby/migration を参照。

注意点

社だとSaaS版ではなくオンプレミス版のSentryを自前でホスティングして運用してるのですが、社で使っているSentryのバージョンが古くてsentry-rubyが動かなくてハマりました。

詳しくは下記のissueを参照

github.com

https://github.com/getsentry/sentry-ruby/issues/1185#issuecomment-755441492 によると

in the new SDK we've switched the reporting endpoint from /store to /envelope, which requires 20.6.0 and above version if you use the on-premise installation.

とのこと。

余談:投げたパッチ一覧

同じgemでメジャーバージョンが上がったら気づくけどgemの名前が変わるとさすがに移行に気づかないので、gem install 後にdeprecation messageを出すPRを投げました。

github.com

【追記】マージされて3.1.2に入りました。

https://github.com/getsentry/sentry-ruby/blob/master/sentry-raven/CHANGELOG.md#312

あとついでに rake build がコケる問題もついでに直してPR投げました。

github.com

上記パッチは2つともマージはされているんだけど、deprecatedなgemなのでいつリリースされるかは不明です

あとsentry-rubyの方も使ってて気になった箇所があったのでPR投げました。

github.com

こっちは4.1.5のマイルストーンに入ってるので順調に行けば次バージョンに入るかなと思ってます

マージされて4.1.5に入りました。

https://github.com/getsentry/sentry-ruby/blob/master/sentry-ruby/CHANGELOG.md#415

Cloud FunctionsでRubyを使う時はdevelopmentやtestのgemはインストールされない

前置き

Cloud Functionsにデプロイする時には gcloud functions deploy を使います。

そこで bundle install も自動で実行されるのだけど、その時に --without development test 的なことをやってくれるのか調べてみました。(調べた範囲では公式ドキュメントには記載はなかった)

検証コード

個人アプリから適当に抜粋

Gemfile

# frozen_string_literal: true

source "https://rubygems.org"

ruby "~> 2.7.0"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "functions_framework"
gem "google-cloud-firestore"
gem "google-cloud-secret_manager"
gem "moji"
gem "parallel"
gem "sentry-ruby"
gem "slack-notifier"
gem "tweet_sanitizer"
gem "twitter"
gem "twitter_retry", ">= 0.2.1"

group :development do
  gem "dotenv"
end

group :test do
  gem "rspec"
end

app.rb

HTTPのリクエストがきた時に bundle list の結果を標準出力に出すだけのシンプルなコード。

GCP公式の Functions Framework を使っている以外は見慣れたRubyのコードです。

require "functions_framework"

begin
  require "dotenv/load"
rescue LoadError
end

FunctionsFramework.http("test") do |request|
  puts `bundle list`
  "test\n"
end

ローカルでの実行結果

ログに rspecdotenv がいるのを確認

$ bundle exec functions-framework-ruby --verbose --target test
I, [2021-01-23T23:32:08.699683 #4427]  INFO -- : FunctionsFramework v0.7.0
I, [2021-01-23T23:32:08.699761 #4427]  INFO -- : FunctionsFramework: Loading functions from "./app.rb"...
I, [2021-01-23T23:32:10.147408 #4427]  INFO -- : FunctionsFramework: Looking for function name "test"...
I, [2021-01-23T23:32:10.147486 #4427]  INFO -- : FunctionsFramework: Starting server...
I, [2021-01-23T23:32:10.201662 #4427]  INFO -- : FunctionsFramework: Serving function "sentry_test" on port 8080...
I, [2021-01-23T23:32:16.165209 #4427]  INFO -- : FunctionsFramework: Handling HTTP GET request
Gems included by the bundle:
  * addressable (2.7.0)
  * buftok (0.2.0)
  * cloud_events (0.1.2)
  * concurrent-ruby (1.1.8)
  * diff-lcs (1.4.4)
  * domain_name (0.5.20190701)
  * dotenv (2.7.6)
  * equalizer (0.0.11)
  * faraday (1.3.0)
  * faraday-net_http (1.0.1)
  * ffi (1.14.2)
  * ffi-compiler (1.0.1)
  * functions_framework (0.7.0)
  * gapic-common (0.3.4)
  * google-cloud-core (1.5.0)
  * google-cloud-env (1.4.0)
  * google-cloud-errors (1.0.1)
  * google-cloud-firestore (2.4.1)
  * google-cloud-firestore-v1 (0.2.3)
  * google-cloud-secret_manager (1.0.1)
  * google-cloud-secret_manager-v1 (0.5.1)
  * google-cloud-secret_manager-v1beta1 (0.6.6)
  * google-protobuf (3.14.0)
  * googleapis-common-protos (1.3.10)
  * googleapis-common-protos-types (1.0.5)
  * googleauth (0.14.0)
  * grpc (1.35.0)
  * grpc-google-iam-v1 (0.6.10)
  * http (4.4.1)
  * http-cookie (1.0.3)
  * http-form_data (2.3.0)
  * http-parser (1.2.3)
  * http_parser.rb (0.6.0)
  * jwt (2.2.2)
  * memoist (0.16.2)
  * memoizable (0.4.2)
  * moji (1.6)
  * multi_json (1.15.0)
  * multipart-post (2.1.1)
  * naught (1.1.0)
  * nio4r (2.5.4)
  * os (1.1.1)
  * parallel (1.20.1)
  * public_suffix (4.0.6)
  * puma (4.3.7)
  * rack (2.2.3)
  * rake (13.0.3)
  * rbtree (0.4.4)
  * rspec (3.10.0)
  * rspec-core (3.10.1)
  * rspec-expectations (3.10.1)
  * rspec-mocks (3.10.1)
  * rspec-support (3.10.1)
  * ruby2_keywords (0.0.4)
  * sentry-ruby (4.1.4)
  * signet (0.14.0)
  * simple_oauth (0.3.1)
  * slack-notifier (2.3.2)
  * thread_safe (0.3.6)
  * tweet_sanitizer (0.2.0)
  * twitter (7.0.0)
  * twitter_retry (0.2.1)
  * unf (0.1.4)
  * unf_ext (0.0.7.7)
Use `bundle info` to print more detailed information about a gem

Cloud Functions上での動作確認結果

コンソールから関数を実行

f:id:sue445:20210124000003p:plain

出力されたログにはdotenvやrspecがいないので bundle install --without development test が実行されてることが分かります。

f:id:sue445:20210123235704p:plain

f:id:sue445:20210123235626p:plain

あくまでドキュメント化されていない*1仕様なので今後この挙動は変わる可能性はあります。

*1:僕が見つけられなかったところに載ってる可能性はあります

至極の難問YAMLクイズ

前置き

  • 社内勉強会のLTで発表したら好評だったので投下
  • 自称YAMLエンジニアのsue445が今まで踏んだ罠をクイズにしました
  • Ruby 3.0.0の Psych で動作確認していますが他言語での挙動は調べていません
    • Psychがlibyamlベースなので他の言語のパーサでもだいたい同じ挙動をすると思うけど

練習問題

Q: 出力されるものは?

yaml = <<YAML
a: 1
YAML

YAML.load(yaml)
#=> ?
  1. {"a"=>"1"}
  2. {"a"=>1}
  3. シンタックスエラー
  4. その他

回答 2

YAMLの数字っぽい文字列はその言語の数字の型(Rubyだと Integer )として解釈されます。 文字列として解釈させたい場合は "1" (ダブルクオーテーション)や '1' (シングルクォーテーション)のように囲んでください

問題1

Q: 出力されるものは?

yaml = <<YAML
splash: ふたりはプリキュア Splash Star
yes: Yes!プリキュア5
yes_gogo: Yes!プリキュア5GoGo
fresh: フレッシュプリキュア!
YAML

data = YAML.load(yaml)
"#{data['splash']},#{data['yes']},#{data['yes_gogo']},#{data['fresh']}"
#=> ?
  1. "ふたりはプリキュア Splash Star,Yes!プリキュア5,Yes!プリキュア5GoGo,フレッシュプリキュア!"
  2. "ふたりはプリキュア Splash Star,,Yes!プリキュア5GoGo,フレッシュプリキュア!"
  3. ",Yes!プリキュア5,Yes!プリキュア5GoGo,フレッシュプリキュア!"
  4. シンタックスエラー

回答 2

true, false, yes, no, on, off はダブルクオーテーションなどで囲まない限りYAMLでは全て 真偽値 として扱われます

data = YAML.load(yaml)
=> {"splash"=>"ふたりはプリキュア Splash Star", true=>"Yes!プリキュア5GoGo!", "yes_gogo"=>"Yes!プリキュア5GoGo", "fresh"=>"フレッシュプリキュア!"}
data = YAML.load("{1: yes, 2: Yes, 3: on, 4: On, 5: true, 6: True}")
#=> {1=>true, 2=>true, 3=>true, 4=>true, 5=>true, 6=>true}

data = YAML.load("{1: no, 2: No, 3: off, 4: Off, 5: false, 6: False}")
#=> {1=>false, 2=>false, 3=>false, 4=>false, 5=>false, 6=>false}

問題2

Q: 出力されるものは?

yaml = <<YAML
default: &default
  slack:
    webhook_url: "https://example.com/"
    channel: "random"

production:
  <<: *default
  slack:
    channel: "production_notify"
YAML

data = YAML.load(yaml)
data["production"]
#=> ?
  1. {"slack"=>{"webhook_url"=>"https://example.com/", "channel"=>"random"}}
  2. {"slack"=>{"webhook_url"=>"https://example.com/", "channel"=>"production_notify"}}
  3. {"slack"=>{"channel"=>"production_notify"}}
  4. シンタックスエラー

回答 3

& (アンカー)と *エイリアス)で定義済みの値をいい感じに共通化できるのはYAMLのよくあるリファクタリング手法ですが、<<: (マージ)はdeep merge(要素内に別の要素があった時に再帰的にマージされる)ではなく第1要素だけを上書きするマージ(代入に近い)なので、今回の場合 default の内容が打ち消されます。

イメージ的にはこんな感じ

data["slack"] = {"webhook_url"=>"https://example.com/", "channel"=>"random"}
data["slack"] = {"channel"=>"production_notify"}

余談ですが https://github.com/railsware/global はdeep mergeしてくれるのがかなり便利で、前職のRailsアプリにはだいたい入っていました

問題3

Q: 出力されるものは?

yaml = <<YAML
go:
  - 1.9
  - 1.10
  - 1.11
  - 1.12
YAML

data = YAML.load(yaml)
data["go"]
#=> ?
  1. [1.9, 1.10, 1.11, 1.12]
  2. [1.9, 1.1, 1.11, 1.12]
  3. [1.90, 1.10, 1.11, 1.12]
  4. シンタックスエラー

回答 2

クオーテーションで囲んでいないので小数として解釈されるため 1.101.1 として解釈されます

厳密に 1.10 として評価するには "1.10" のように囲む必要があります。

余談ですがGo 1.10が出た時に .travis.yml1.10 を追加したらGo 1.1でCIが実行されてビルドが失敗したことがあります。

https://github.com/sue445/zatsu_monitor/commit/fc6b8ab806a3c48617cb084437d2d1c018777ee9#comments

問題4

Q: この中でシンタックスエラーになるのはどれか?

a

excludes:
  - *_test.rb

b

excludes:
  - test/**
  1. aのみシンタックスエラー
  2. bのみシンタックスエラー
  3. aとb両方シンタックスエラー
  4. シンタックスエラーは無い

回答 1

a

yaml = <<YAML
excludes:
  - *_test.rb
YAML

YAML.load(yaml)
Traceback (most recent call last):
        8: from /Users/sue445/.rbenv/versions/3.0.0/bin/irb:23:in `<main>'
        7: from /Users/sue445/.rbenv/versions/3.0.0/bin/irb:23:in `load'
        6: from /Users/sue445/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/irb-1.3.0/exe/irb:11:in `<top (required)>'
        5: from (irb):62:in `<main>'
        4: from /Users/sue445/.rbenv/versions/3.0.0/lib/ruby/3.0.0/psych.rb:280:in `load'
        3: from /Users/sue445/.rbenv/versions/3.0.0/lib/ruby/3.0.0/psych.rb:390:in `parse'
        2: from /Users/sue445/.rbenv/versions/3.0.0/lib/ruby/3.0.0/psych.rb:456:in `parse_stream'
        1: from /Users/sue445/.rbenv/versions/3.0.0/lib/ruby/3.0.0/psych.rb:456:in `parse'
Psych::SyntaxError ((<unknown>): did not find expected alphabetic or numeric character while scanning an alias at line 2 column 3)

b

yaml = <<YAML
excludes:
  - test/**
YAML

YAML.load(yaml)
#=> {"excludes"=>["test/**"]}

*_test.rb* が前述のエイリアスとして評価されるのですが、対応する _test.rb という名前のアンカーが存在しないためシンタックスエラーになります。

シンタックスエラーにしないためには "*_test.rb" のように囲む必要があります。

参考文献

おまけ:LT直後のみんなの反応

  • 「囲んでも囲まなくてもいいみたいなsyntaxやだ…」
  • 「問題として聞かれるとわかるけど、実際に yaml 書いてたらミスりそう」
  • YAMLの特別な挙動にうごかされないようにするのは、ダブルクォートつけるのがいいのかな。」
    • -> sue「『それはそう』なんですが、実際にそれやろうとするとかなり冗長になってだいぶ見づらくなるんですよね」
  • 6E-1 が(文字列として判定されてほしいのに) 指数表記で判定されてハマった」
  • YAMLほんとうにわかってなかった」

GitHub Actionsのpushイベントとpull_requestイベントではGITHUB_SHAが異なる

tl;dr;

タイトルが全て

検証内容

サンプルコード

GitHub Actionsで使える(事前定義済みの)環境変数 *1を列挙するだけのシンプルなワークフローです

on:
  - push
  - pull_request

jobs:
  show_env:
    runs-on: ubuntu-latest

    steps:
      - run: env | grep GITHUB_ | sort

https://github.com/sue445/github_actions_sandbox_20210106/blob/master/.github/workflows/sandbox.yml

masterブランチに普通にpushした時

コミットグラフはこんな感じ

f:id:sue445:20210106234932p:plain

https://github.com/sue445/github_actions_sandbox_20210106/runs/1656991711 には

GITHUB_SHA=f308d17b80228b432d841e96624b583f632d2411

と出ているので、 master ブランチ *2 のHEADと一致していることが分かると思います。(分かる)

PullRequestに対してpushした場合

検証用のPR https://github.com/sue445/github_actions_sandbox_20210106/pull/1

onpushpull_request を指定してるのでそれぞれイベントが発火します。

f:id:sue445:20210106235514p:plain

この時点でコミットグラフはこんな感じ

f:id:sue445:20210106235440p:plain

pushイベントの結果

https://github.com/sue445/github_actions_sandbox_20210106/pull/1/checks?check_run_id=1657017337 には

GITHUB_SHA=8d1396316903bd747589ff2a81930e659cca1a4d

と出ているので、 tmp/pr_test ブランチのHEADと一致しています。(分かる)

pull_requestイベントの結果

https://github.com/sue445/github_actions_sandbox_20210106/pull/1/checks?check_run_id=1657017535

GITHUB_SHA=9b0ee21ee4668fab1d29961f60108b9ef3c1946b

え!!!???

解説

https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows#push によるとpushイベントでの GITHUB_SHA

Commit pushed, unless deleting a branch (when it's the default branch)

とのこと。

https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows#pull_request によるとpull_requestイベントでの GITHUB_SHA

Last merge commit on the GITHUB_REF branch

とのこと。

これしか書いていなくていまいち意味が分からなかったのですが id:r7kamura さんが RuboCop Problem Matchers

pull_request イベントを起点に動かす場合、actions/checkout はそのPull Requestが生成しようとしているmerge commitをチェックアウトする。

と書いてるように、この GITHUB_SHA はそのPull Requestで生成しようとしてるmerge commitのSHAだと思われます。

ちなみにこの挙動はTravis CIと同じような仕様です。

2021/01/08 追記

  • Travis CI以外にもCircleCIやJenkinsのGithub Pull Request Builderが同じ挙動
  • BitriseはGitHubのPullRequestのマージコミットではなく、内部でマージした時のマージコミットをcheckoutしてる
  • WerckerはブランチのHEADを使ってる

トノコト

GITHUB_SHAが異なることで何が困るか

ほとんどのケースではそれほど困ることはないと思います。*3

しかしジョブの中で GITHUB_SHA を使おうとした場合に注意が必要です。

具体的には tfnotify でTerraformの実行結果をPull Requestのコメントに出そうとした時に、pull_requestイベントを使うとPullRequestと直接紐付かない前述のマージコミットに対してコメントがつくため、初見だと謎のコミットに対してコメントが付く状態になります。(PullRequest上に登場するコミットに対してコメントがつけばPullRequest上で表示されます)

f:id:sue445:20210107001641p:plain

f:id:sue445:20210107001655p:plain

余談:tfnotifyでpull_requestイベントの時にもPullRequestにコメントをつけたい

半年くらいずっと例の謎コミットにコメントがついて困ってたのですがつい先日下記で解決しました。

- name: terraform plan
  run: |
    # NOTE: tfnotify uses GITHUB_SHA, but GITHUB_SHA can't be override in env
    if [ -n "$PR_HEAD_SHA" ]; then
      export GITHUB_SHA=$PR_HEAD_SHA
    fi
    terraform plan -input=false | tfnotify plan
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}

実際にPull Requestに対してtfnotifyのコメントがついた図

f:id:sue445:20210107004749p:plain

ちなみにコード中のコメントでも書いていますが

env:
  GITHUB_SHA: ${{ github.event.pull_request.head.sha }}

のように書いても GITHUB_SHA を上書きできないので、別名として取得してジョブの中で代入して上書きするしかないです。

FAQ

Q. だったらpull_requestは不要では?

業務リポジトリのように開発者全員が同一リポジトリ内でPullRequestをやり取りする場合にはpushイベントだけで問題ないです。

しかしpushイベントだとforkされたリポジトリからPullRequestがきた時にイベントが発火しなくてCIのジョブが実行されないのでOSSリポジトリで困ります。(経験済)

かといって今回のサンプルみたいに

on:
  - push
  - pull_request

で書くとPullRequestがきた時に2つジョブが実行されて無駄です。

色々検証した結果個人リポジトリだと下記に落ち着きました。

on:
  push:
    branches:
      - master
  pull_request:
    types:
      - opened
      - synchronize
      - reopened

40個以上のリポジトリで上記設定を半年以上使ってますが今の所不満はないです。

今の心境

*1:公式の説明は https://docs.github.com/en/free-pro-team@latest/actions/reference/environment-variables

*2:最近GitHubではmainブランチがデフォルトになってますが毎回両方併記のは大変なので本エントリでは masterブランチで統一します

*3:トピックブランチを作った時点でのmasterブランチとPullRequestを出した時のmasterブランチが大きく剥離してるとローカルではテストが通るのにCIだとテストがコケるという事象は発生するかもしれません

rubicure 2.0.0をリリースした

https://github.com/sue445/rubicure/blob/master/CHANGELOG.md#v200

rubocopを最新にしようと思ったら最新のrubocopだとRuby 2.4以降必須になってたのでこの機会にRuby 2.2系と2.3系のサポートを切りました。

rubocopのバージョンを上げたのでソース上の差分が結構大きいですが機能面での変更点は無いです

今年作ったもの(2020年版)

今年新しく作ったものを作った順に紹介

go-mod-tidy-pr

sue445.hatenablog.com

今年の頭に作ったやつでたまにPRきてる。

最近GitHub本体のdependabotの方で go mod tidy 正式対応されたので go-mod-tidy-prはお役御免かなと思ってdeprecateにしました

github.blog

prismdb-ruby, faker-pretty_series

下記エントリ参照

sue445.hatenablog.com

gcp-kmsenv

下記エントリ参照

sue445.hatenablog.com

gcp-secretmanagerenv

下記エントリ参照

sue445.hatenablog.com

プリマップ

個人的には今年のイチオシ

sue445.hatenablog.com

sue445.hatenablog.com

今まで使ったことのないアーキテクチャで作りつつも、割と爆速で動くというのがウリ。

しかもGCPの費用が1ヶ月で1ドル切ってるのでお財布的にも嬉しい。(リリース直後にプチバズってアクセス増えたけどそれでも1ドルちょっと超えたくらい)

f:id:sue445:20201222205635p:plain

Cloud Functions実行時に毎回Secret Managerから機微情報を読み込んでるのでそこをGoのビルド時にいい感じに埋め込めばほぼ0円にできそうだが*1、今のままでも十分安いのでそこまでのモチベーションはないw

*1:FirestoreのQuota超えることもあるので完全に0円は無理

今年買ってよかったもの(2020年版)

Amazon楽天などの購入履歴からピックアップ

エクスジェル アウルカンフィ3Dプレミアム

exgel.jp

家だと座椅子なのですが座椅子で仕事しててクッションに不満があった時に同僚に勧められて購入。

長時間座ってても尻が痛くならないのが良い

Oculus Rift S

www.oculus.com

ハッピーおしゃれタイム をやりたくて購入。*1

けどGoogle Earth VRで昔住んでた場所にVR帰省したり、バーチャルデスクトップでライブのBDや配信イベントの鑑賞してる方が多いかもしれない。

仮想大画面でライブ見るのいいですね。(目がむっちゃ疲れるけど)

プリパラ LIVE COLLECTION

avex.jp

avex.jp

avex.jp

「プリパラミュージックコレクションがあるならライブ版もあるのでは?」ということでググって見つけたので購入。

プリパラやプリ☆チャンのCGライブが好きなのでCGライブだけをずっと見れるのは神。

これをBGM(Back Ground Movie)でコードを書くとテンションMAX!

難点は推し*2のパートで手が完全に止まること。

KPro01

www.owltech.co.jp

Makuake で支援してたのが先月届いたので使ってます。

操作に慣れるまでに時間がかかったけど慣れたらかなり快適。

フル充電で9時間持つので仕事で使うには十分

*1:Quest 2じゃなくてRift Sなのは購入時点でQuest 2が発表されていなかったため

*2:アロマゲドンとガァルマゲドン