ハトネコエ Web がくしゅうちょう

プログラミングやサーバー・Web制作、チームマネジメントなど得た技術のまとめ

haml2slim を改善して haml から slim への移行をしやすくしました

動機

haml から slim へ移行しようとして haml2slim を使おうとしたものの、
上手く変換されず自力で直さないといけないところが多い状態でした。

最初は「リファクタリングする箇所も多いから、一行ずつ注視して自力で変更していけばいいか」と思っていたんですが、
同じところを何度も直していると、ミスも起こるし時間もかかるしで、
haml2slim 自体を直すのが効率良いなと気付きまして、以下のリポジトリを作りました。

気が向いたら本家にプルリクエストを送る可能性もありますが、
2014年のプルリクエストが2022年の今なお放置されている状態で、
希望が薄いので作成する可能性は低いです……。

フォーク元のベースとして、そのプルリクエストである https://github.com/a2ikm/haml2slim/tree/ruby19-hash を使わせていただきました。
ありがとうございます。

本家の haml2slim から変更したこと

https://github.com/nekonenene/haml2slim/compare/9d79c40...4ce0c64

1. .gitignore を調整

Gemfile.lock.bundle が gitignore の対象になっていたので削除しました。
逆に vendor/bundle が gitignore の対象になっていなかったので追加しています。

関連する差分: https://github.com/nekonenene/haml2slim/compare/6490994...bef06a9

2. minitest を Gemfile に追加

minitest をテストで使っているのに Gemfile に指定がされていなかったので追加しています。
また、 minitest の現在のバージョンでは deprecated になっている記法を修正しました。

関連する差分: https://github.com/nekonenene/haml2slim/commit/dfc0d919433ee93af21853e8623409a14e11d755

3. Ruby 1.9 以降での Hash Syntax に対応

私がベースに使ったリポジトリの作者さんのプルリクエスト ( https://github.com/slim-template/haml2slim/pull/23 ) で
Ruby 1.9 以降での Hash Syntax に対応しているのですが、使ってみて、
これにはまだ不足があることに気付きました。

%button{data: { confirm: "OK?" }}

のような data 属性の変換に対応していないことです。

また、Hash Syntax に関わらず、
キーや値が single quotations ( ' )で囲まれている場合や、
値が Symbol である場合や、値にスペースが含まれる場合に、
正常に変換されていなかったので修正しました。

関連する差分:

4. 文字列内の変数展開の修正

%span
  #{1 + 1}

span
  #{1 + 1}

と変換されるようになっていましたが、これでは2行目でエラーを吐いてしまうので、

span
  | #{1 + 1}

と変換されるように修正しました。

また、

%span 次は#{1 + 2}回目です

がパースの関係で

span 次は# 1 + 2 回目です

のように変換されてしまう問題も修正しました。

関連する差分: https://github.com/nekonenene/haml2slim/compare/77a5072...17acd8d

5. ( , [ , { は HTML エスケープするように

%span (#{aaa})

haml では問題なく、文字列としてカッコがレンダリングされるのですが、

span (#{aaa})

と slim でなっている場合、カッコは Attributes Wrapper と思われてしまい
パースしようとするのですが、上手くいかずにパースエラーが起こってしまいます。

思わぬエラーの要因になるので、
HTMLタグのすぐ後ろに ( , [ , { がある場合には HTML エスケープをおこなうようにしました。
不格好ではありますが、エラーが起こるよりは断然マシです。

関連する差分: https://github.com/nekonenene/haml2slim/commit/4ce0c640b2d0e580e9c3a5274d5be535ce8117d0

6. Vue の変数展開に対応

Rails と Vue.js を組み合わせて使っている場合のみに関係する話ですが、

%span {{ 1 + 1 }}

のように Vue の変数展開を書いているものを、

span {{ 1 + 1 }}

のように slim 向けに直しても上手くいってくれません。
(参考: https://qiita.com/Take-st/items/4bb3ebd25f361276c4c4#comment-ae32b23dae3266a24e54

span
  | {{ 1 + 1 }}

と変換されるようにしてみました。

関連する差分: https://github.com/nekonenene/haml2slim/commit/f23bdc1ee282919a57ad5f65cb4f448ea53c35c4

7. HTMLタグの後ろにスペースを付ける

haml では

%span= Time.now

のように、HTMLタグのすぐ後ろに = を付けるのが一般的ですが、
slim では

span = Time.now

のようにHTMLタグの後ろにスペースを付けるのが一般的です。
( http://slim-lang.com )
そのように変換されるように調整しました。

関連する差分: https://github.com/nekonenene/haml2slim/compare/17acd8d...18b334e

解決していない問題

これで多くの問題が片付きましたが、これでもまだ全てではありません。
私の気付いているものですと、

= render partial: "dummy",
  collection: @some_variable,
  as: :some_variable

のように複数行に渡るものが

= render partial: "dummy",
  | collection: @some_variable,
  | as: :some_variable

と変換されてしまい、パースエラーになってしまう問題を残しています。
あまりあるケースではないのと、対応が面倒そうに感じるので放置しています。

おわりに

これで haml から slim への移行作業が大幅に効率化されました。

やはり、自力で繰り返しおこなう作業は、コンピューターに任せるのがなによりですね。

github.com

Amazon OpenSearch Service を使ってみてハマったところまとめ

Amazon OpenSearch Service を導入しようとしてハマったところをまとめます。
記事を残してくれた数多くの人に感謝です。

ハマったところいっぱいあったんですが、
逐一メモっていなかったので思い出した順に書いていきます。

↓ 前回の Amazon OpenSearch Service 関連の記事

nekonenene.hatenablog.com

CloudWatch Logs を OpenSearch に送る Lambda 処理でエラーが出る件

f:id:nekonenene:20211113064330p:plain

CloudWatch Logs データの Amazon OpenSearch Service へのストリーミング - Amazon CloudWatch Logs』に従って進めて、
CloudWatch から OpenSearch Service にログを流すサブスクリプションフィルターを作ったあと、
最初はまずログが出せなくて困りました。

Lambda の Role に AWSLambdaBasicExecutionRole Policy をアタッチして解決しました。
いつも Lambda を新規作成する場合は勝手に作られるからその作業抜けてた。

で、ログ出すようになったら困ったのが以下のようなエラーが出ること。
どうも権限周りの話らしい。

{
    "errorType": "Error",
    "errorMessage": "{\"statusCode\":403,\"responseBody\":{\"error\":{\"root_cause\":[{\"type\":\"security_exception\",\"reason\":\"no permissions for [indices:data/write/bulk] and User [name=arn:aws:iam::123456781234:role/ControlOpenSearchTestFromLambda, backend_roles=[arn:aws:iam::123456781234:role/ControlOpenSearchTestFromLambda], requestedTenant=null]\"}],\"type\":\"security_exception\",\"reason\":\"no permissions for [indices:data/write/bulk] and User [name=arn:aws:iam::123456781234:role/ControlOpenSearchTestFromLambda, backend_roles=[arn:aws:iam::123456781234:role/ControlOpenSearchTestFromLambda], requestedTenant=null]\"},\"status\":403}}",
    "stack": [
        "Error: {\"statusCode\":403,\"responseBody\":{\"error\":{\"root_cause\":[{\"type\":\"security_exception\",\"reason\":\"no permissions for [indices:data/write/bulk] and User [name=arn:aws:iam::123456781234:role/ControlOpenSearchTestFromLambda, backend_roles=[arn:aws:iam::123456781234:role/ControlOpenSearchTestFromLambda], requestedTenant=null]\"}],\"type\":\"security_exception\",\"reason\":\"no permissions for [indices:data/write/bulk] and User [name=arn:aws:iam::123456781234:role/ControlOpenSearchTestFromLambda, backend_roles=[arn:aws:iam::123456781234:role/ControlOpenSearchTestFromLambda], requestedTenant=null]\"},\"status\":403}}",
        "    at _homogeneousError (/var/runtime/CallbackContext.js:12:12)",
        "    at postError (/var/runtime/CallbackContext.js:29:54)",
        "    at done (/var/runtime/CallbackContext.js:58:7)",
        "    at fail (/var/runtime/CallbackContext.js:70:7)",
        "    at Object.fail (/var/runtime/CallbackContext.js:106:16)",
        "    at /var/task/index.js:43:25",
        "    at IncomingMessage.<anonymous> (/var/task/index.js:177:13)",
        "    at IncomingMessage.emit (events.js:326:22)",
        "    at endReadableNT (_stream_readable.js:1241:12)",
        "    at processTicksAndRejections (internal/process/task_queues.js:84:21)"
    ]
}

https://aws.amazon.com/jp/premiumsupport/knowledge-center/opensearch-troubleshoot-cloudwatch-logs を読んだら解決方法が書いてあって、
OpenSearch の管理画面から all_access Role に User と Backend role の両方で
AWS Role を当ててあげると解決するらしいので修正。

f:id:nekonenene:20211113065312p:plain

ちなみに、Backend role だけ割り当てると次に紹介するエラーが起こるので注意。

とりあえず上手くいって、じゃあ次に他のロググループからも同様に OpenSearch にデータを送ろうと思いました。
同名の Lambda は作れないので、最初と違って
『Create Amazon OpenSearch Service subscription filter』ではなく
『Lambda サブスクリプションフィルターを作成』を選ぶ点に注意。

f:id:nekonenene:20211113065658p:plain

じゃあそれで作成して上手くいくかと思いきや、以下のエラーが!
原因が……わからない……!!!

{
    "errorType": "Error",
    "errorMessage": "{\"statusCode\":200,\"responseBody\":{\"took\":6,\"errors\":true}}",
    "stack": [
        "Error: {\"statusCode\":200,\"responseBody\":{\"took\":6,\"errors\":true}}",
        "    at _homogeneousError (/var/runtime/CallbackContext.js:12:12)",
        "    at postError (/var/runtime/CallbackContext.js:29:54)",
        "    at done (/var/runtime/CallbackContext.js:58:7)",
        "    at fail (/var/runtime/CallbackContext.js:70:7)",
        "    at Object.fail (/var/runtime/CallbackContext.js:106:16)",
        "    at /var/task/index.js:43:25",
        "    at IncomingMessage.<anonymous> (/var/task/index.js:177:13)",
        "    at IncomingMessage.emit (events.js:412:35)",
        "    at endReadableNT (internal/streams/readable.js:1317:12)",
        "    at processTicksAndRejections (internal/process/task_queues.js:82:21)"
    ]
}

ステータスコードが 200 なのにエラー……なかなか奥が深い状態です。

しばらく謎だったんですが、
こちら ↓ の記事が解答に導いてくれました。

qiita.com

複数の type を送れないようです。

ここを記事にあるように書き換えても、ロググループの名称は @log_group から取得できるのでご安心ください。

f:id:nekonenene:20211113070141p:plain

AWS コンソールから見られない箇所がある

AWSコンソールで OpenSearch Service の「クラスターの状態」が表示されない問題がありました。
とはいえ、これは明らかに権限エラーだと表示されていたのですぐ解決できました。

OpenSearch の Role の all_access に、IAMユーザー名を User としてマッピングすれば解決です。

こういうの。
arn:aws:iam::123456781234:user/nekonenene

f:id:nekonenene:20211113070654p:plain

ただ、IAMユーザーを1つずつ割り当てるのは面倒なので、
IAM Role と IAM Group を活用していい感じに出来るのでは、などと思ったりします。

Amazon OpenSearch Service のヘルスエラー

これはあるあるのようですが、
Elasticsearchのshardとreplica - なんかかきたい』の記事にあるように、
デフォルトだと number_of_shards の数が多いために
『割り当てられていないシャード』が多く発生し、
AWSコンソールで Amazon OpenSearch Service のヘルスエラーが表示されます。

対応は簡単で、Dev Tools から例えば

POST _template/cwl-2021.11.12
{
  "index_patterns": ["cwl-*"],
  "order": 1,
  "settings": {
    "index": {
      "number_of_shards" : 2
    }
  }
}

のようにおこない、 number_of_shards を減らします。

もちろん、本番運用でなくテスト運用の場合です。
本番運用する場合のオススメの設定については AWS が書いているので、以下の記事を参考にしましょう。

Amazon Elasticsearch Service ドメインを設定するベストプラクティス | Amazon Web Services ブログ

インデックスの登録場所がわかりにくい

Stack Management > Index patterns の順に進みます。
未来の自分も忘れそう。

インデックスの自動削除(Rotation)の仕方がわからない

Index Management > State management policies のページでポリシーを作成するのですが、
JSONで書かなければならず、マジでよくわかんなかったです。

この2つの記事のおかげで、無事に設定ができました。

Kibana を使ったことある方なら知っていることも多いんでしょうが、
自分は経験ないので大変でした。なんとか、なんとなくでやってます……ムズい……。
ようやくいろいろが終わって、データ分析に使えそうです。

AWS OpenSearch Dashboards に Google 認証でログインする方法

Amazon OpenSearch Service から作成した
AWS OpenSearch Dashboards に、Google 認証(Google Workspace を用いた SAML認証)でログインできるようにする方法を解説します。

1. Amazon OpenSearch Service のドメイン作成

まずはテスト用に適当に作成します。
カスタムエンドポイントを有効化する場合は、 Route 53 で CNAME 設定を入れることを忘れないようにします。(忘れてちょっとハマった)

カスタムエンドポイントを使用する場合の参考記事: https://docs.aws.amazon.com/ja_jp/opensearch-service/latest/developerguide/customendpoint.html

f:id:nekonenene:20211112215726p:plain

f:id:nekonenene:20211112215759p:plain

f:id:nekonenene:20211112220148p:plain

f:id:nekonenene:20211112220220p:plain

デフォルト設定から以上のような箇所を変更して作成しました。

作成後、画面上部に以下のようなメッセージが表示されます。
SAML設定をあとからおこなうのですが、このドメインの設定には時間がかかるので、その間に Google 側の設定を始めます。

f:id:nekonenene:20211112220351p:plain
SAML設定はあとからおこなう

2. Google 側の設定 その1

まず前提として、 Google Admin に入れる権限が必要です。
Google Workspace の管理者権限がない場合には付与してもらってください。

まず、ウェブアプリとモバイルアプリ のページから
「アプリを追加」>「カスタムSAMLアプリの追加」をおこないます。

f:id:nekonenene:20211112221937p:plain
カスタムSAMLアプリの追加

f:id:nekonenene:20211112221905p:plain

アプリの詳細をわかりやすいように設定した後、
次の画面で IdP メタデータをダウンロードできます。これはあとで使います。

なお、SAML アプリケーションに対するシングル サインオン(SSO)の設定 からもダウンロードできます。

続いて、サービスプロバイダの詳細を設定する画面に進みます。
ACS の URL」および「エンティティ ID」の入力を求められますが、
これは AWS 側の OpenSearch Service のドメイン作成が終わっている必要があるので、それを待ちます。

3. AWS OpenSearch Service の設定更新

AWS 側の OpenSearch Service のドメイン作成が終わっていたら、
ドメインの「セキュリティ設定の編集」に進みます。

f:id:nekonenene:20211112222608p:plain

再び「SAML 認証を有効化」のチェックをONにして、設定していきます。

ここの「サービスプロバイダーエンティティ ID」と「SP によって開始された SSO URL」は、このあとの 4. の工程で使います。

f:id:nekonenene:20211112223402p:plain
IdP メタデータをインポート

さて、「XMLファイルからインポート」のボタンから
2. の工程のときにダウンロードした IdP メタデータをアップロードします。

その後、その下の「SAML マスターユーザーネーム」の箇所に、
管理者として使いたい Google Workspace のメールアドレスを指定してあげてください。

アクセスポリシー」については、最初の作成時に誰もアクセスできないような設定で作っていたので、
誰でもアクセスできるように今度は変更します。

f:id:nekonenene:20211112224331p:plain
アクセスポリシーの変更

そうしたら「変更の保存」を押して完了です。

4. Google 側の設定 その2

ACS の URL」には、「SP によって開始された SSO URL」を設定します。
これが認証後のリダイレクト先です。 (例: https://search-opensearch-test-hogehoge.ap-northeast-1.es.amazonaws.com/dashboards/opendistro/_security/saml/acs

「エンティティID」には「サービスプロバイダーエンティティ ID」を設定します。
(例: https://search-opensearch-test-hogehoge.ap-northeast-1.es.amazonaws.com/dashboards/opendistro/_security/saml/acs

結果として、以下のようになります。

f:id:nekonenene:20211112233132p:plain
サービスプロバイダの詳細の設定

属性のマッピングは特に設定せず完了させます。

完了すると、以下のような画面になります。

f:id:nekonenene:20211112223835p:plain

この状態のままだと、まだユーザーアクセスが「オフ」になっていて誰もログインできないので、
ユーザーアクセス」の箇所をクリックして、
このアカウントのすべてのユーザー、もしくはユーザーグループ単位でアクセスを許可します。(「オン」にします)

5. ダッシュボードへのアクセス

ダッシュボード(OpenSearch Dashboards の URL)にアクセスしてみましょう。 (例: https://search-opensearch-test-hogehoge.ap-northeast-1.es.amazonaws.com/_dashboards

どの Google アカウントでログインするか選択する画面になるので、
3. の「SAML マスターユーザーネーム」で設定したメールアドレスを選択して、次の画面に進みます。

認証が許可されていない場合は以下のような 403 ページが表示されます。

もし、2. の「ユーザーアクセス」の設定を変更しているはずなのに表示される場合、
もう少し待ってから再度アクセスしてみてください。(GoogleSAML 設定が反映されるまでは数分かかるため。最長24時間かかると書かれていますが、さすがにそんなにはかからないはず)
それでもダメなら、4. の「ユーザーアクセス」の設定や「エンティティID」の設定が失敗していないか再度確認してみてください。

f:id:nekonenene:20211112224637p:plain

無事成功すると、Dashboard のトップ画面に進みます! おめでとうございます!

f:id:nekonenene:20211112233422p:plain
OpenSearch Dashboards へのログインが成功しました

6. ユーザーの招待

4. でユーザーアクセスを許可した人たちが Google ログインできるようになりましたが、
OpenSearch Dashboards のアカウントはまだ作られていないので、
3. で設定した「SAML マスターユーザーネーム」のメールアドレス以外でログインすると
Missing Role のエラーが表示されます。

f:id:nekonenene:20211112234437p:plain
OpenSearch Missing Role

SAML マスターユーザーネーム」のメールアドレスでログインした
管理者が、他のユーザーの登録をおこなっておく必要があります。

Amazon OpenSearch Service のきめ細かなアクセスコントロール - Amazon OpenSearch Service
のページを参照してください。

メニューから Security > Roles に進んで、その画面から例えば「all_access」のロールに対してユーザーをアサインします。

f:id:nekonenene:20211112235622p:plain
Security > Roles

ロールを選んで「Manage mapping」ボタンを押します。

f:id:nekonenene:20211112235758p:plain
Manage mapping

「Map user」の画面に進んだら、
権限を与えたいユーザーのメールアドレスを入力し、「Map」ボタンを押して保存します。

f:id:nekonenene:20211113000023p:plain
Map user の画面でメールアドレスを登録

なお、 Missing Role の画面からは Cookie などをいじらない限りログアウトできないという罠があるので、
もし他の Google アカウントでのログインを自身が試す場合は、
ログアウトせずに別の Web ブラウザから、他のアカウントを使ったログインを試すと良いです。

7. おわりに

公式ドキュメントには Okta を用いた SAML認証しか解説しておらず、
情報がなくてとってもとっても苦労したので、この記事を作成しました。

手順としてはそこまで複雑でないことが伝わったかと思います。
お役に立てましたら幸いです。