Logical Rabbit.

さくらのVPS

ActiveAdminのCSS/JavaScriptがアプリケーション全体に波及して邪魔な時の対処パターン

ActiveAdminを導入するときの定型パターンなのに毎回忘れるのでメモ。ていうか、これいい加減ActiveAdmin側で何かしてくれないかな(←Pull request送れや)

  1. app/assets/stylesheets/admin.css を app/assets/stylesheets/admin/admin.css 等のようにして下層ディレクトリへ隔離 (app/assets/javascripts も同様)
  2. app/assets/stylesheets/application.css 内の require_tree .require_directory . に変更
    • これにより app/assets/stylesheets のサブディレクトリを読み込まなくなるので app/assets/stylesheets/admin が隔離されるが、副作用として app/assets/stylesheets/hoehoe-func のような機能ごとのサブディレクトリ運用もできなくなるので、必要ならば適宜 require_directory hoehoe-func を追記する必要がある
  3. config/initializer/active_admin.rb の == Register Stylesheets & Javascripts の項に以下を追記

    config.clear_stylesheets!
    config.register_stylesheet 'admin/active_admin.css'
    config.clear_javascripts!
    config.register_javascript 'admin/active_admin.js'
    
    1. config.clear_stylesheets! ... 念のため、他の箇所で読み込まれたスタイルシート情報を破棄
    2. config.register_stylesheet 'admin/active_admin.css' ... app/assets/stylesheets/admin/active_admin.css を読み込むよう設定
    3. (javascripts 側も同様の処理)

dockerでphpを起動していたら `strtotime(): It is not safe to rely on the system's timezone settings.~` と怒られたときの対処

なんか毎回「あーはいはいOSのTZ環境変数設定してなかったねー」で済ましては改善せずに英文読み直すことになるのでメモ(最初から英文読め)。

以下を記述した php.ini をdockerイメージ内に配置。Dockerイメージ内既存のphp.iniを上書きしていないかどうかは事前に確認すること。

php.ini:

[Date]
date.timezone = Asia/Tokyo

Dockerfile:

COPY ./php.ini /usr/local/etc/php/

どっとはらい。

簡易なPageView測定器の実装

長期にログを取る用途ではなく、開発過程でどのページがどのタイミングでアクセスされたかをチェックするための簡易サーバとクライアントサイドからの呼び出しコードです。

簡易ですがサーバ側でログを取るので、Webブラウザの環境に依存しにくい動作を期待できます。次のようなケースのとき向け。

  • JavaScriptコンソールやWebブラウザの通信ログでは余計な情報が多すぎる
  • モバイル端末での挙動確認なのでWebブラウザの開発者ツール類が使用できない

起動させたサーバに対して ?view=<測定対象URL> のようにしてGETアクセスすると、 <測定対象URL> のみを console.log() で出力します。後はそのまま眺めるなり、もう一工夫してファイルに記録するなり、用途に応じてカスタマイズ。

サーバ側

node.js 実装 + Docker 起動。停止時は適宜 docker stop などで。

index.js:

const express = require('express')

const server = express()
server.get('/', (req, res) => {
  const url = new URL(req.url, `${req.protocol}://${req.hostname}`)
  console.log('pageview: ' + url.searchParams.get('view'))
  res.setHeader('Access-Control-Allow-Origin', '*')
  res.statusCode = 200
  res.end()
})

server.listen(8080)

起動:

$ docker run -it -v${PWD}:/mnt/app -p 8080:8080  node node /mnt/app/index.js

クライアント側(Webブラウザ側)

jQueryやaxios等が使えるのであれば、そちらの方が色々便利かと思います。今回はターゲットWebブラウザがChromeだけなので、XMLHttpRequest決め打ちでよいだろうと判断。 なお、例ではRuby on Railsへ組み込んでいるので turbolinks:load イベントからの起動になっています。

<script>
  document.addEventListener('turbolinks:load', event => {
    const url = event.data.url;
    const req = new XMLHttpRequest()
    req.open('GET', 'http://' + window.location.hostname + ':8080/?view=' + url);
    req.send();
  });
</script>

DockerのMySQLでRails環境を作っていたらハマったメモ。

Docker の mysql でMySQLサーバを起動して、Rails環境のほうが既存コードで開発環境でも一般ユーザを作ってDBアクセスする仕組みにしていたので、 MYSQL_DATABASE, MYSQL_USER, MYSQL_PASSWORD を使ってDBとユーザを自動で作らせようとしたのだけど…ハマった。

Rails側が app、DB側が db と思いなせぇ。

取り敢えずMigrationエラー出るのは承知でRails側を起動させ、アクセスしてみたところ、以下のエラーが発生。

app_1  | Mysql2::Error::ConnectionError (Access denied for user 'user'@'test_app_1.test_default' (using password: YES)):

もちろんパスワードとかは合ってる。アクセス拒否されたユーザ名のホスト名部分が test_app_1.test_default となっているのだけど、当初ここを「アクセス先」と誤認してしまい、 DBコンテナのホスト名がどこかで混線してる??? と考えてあちこち弄ったりデバッグコード仕込んでみたりして調べること、約1日。

ちがうよねー

これはアクセス元(app)のホスト名が、MySQL側に登録されていないので拒否られているのよねー。

で。気付いたので MYSQL_USER を user@test_app_1.test_default にしてみたり(これだと docker-compose run するたびにホスト名が変わるのでよろしくない)、 user@% にしてみたり(というか元々こうなる気がするんだが…)したのだけど、結局うまく行かず、面倒くさいので Rails 側のDBユーザ名を root に変えてしまいましたとさ。

どっとはらい。

…まあ、後々必要になりそうな気もするので、今度どのように書くのが良いのか、ちゃんと調べようかと(今はただRailsアプリの動作環境を作ってコーディングをせねばならぬ)

そんな平成最後の年度末。

Docker関連のメモ(O'reillyの「Docker」に見つからないものを中心に)

Dockerfile の ARG に対する値入力

--build-arg <変数名>=<値>

複数の場合は1つずつ。

--build-arg <変数名1>=<値> --build-arg <変数名2>=<値>

Docker外とDoker内で作業ユーザを一致させたい

この方法が本当に最適解なのかは分からないが。Doker内に既存の一般ユーザが居ない前提。

docker buildへの指定:

--build-arg USER_ID=`id -u` --build-arg USER_GROUP_ID=`id -g` --build-arg USER_NAME=`id -un`

Dockerfile内:

ARG USER_ID
ARG USER_GROUP_ID
ARG USER_NAME

RUN groupadd -g $USER_GROUP_ID $USER_NAME \
  && useradd -u $USER_ID -g $USER_NAME -m $USER_NAME

さらに作業ディレクトリVOLUMEを作業ユーザで使えるようにする

Dockerfile内:

RUN mkdir -p /mnt/work_dir_root/workarea
  && chown -R $USER_NAME:$USER_NAME /mnt/work_dir_root/workarea

VOLUME /mnt/work_dir_root

USER $USER_NAME

VOLUMEに書いたディレクトリ直接だと(マウントポイントなので)chownが使えないので、さらに一段掘って作業エリアとする。

オンラインの参考資料 (Docker ドキュメント日本語化プロジェクト)


Vagrant+Ansible+VirsualBox on CentOS。

CentOS 7.5上でVagrantとAnsibleを使ってVirsualBoxを操作しようとしたら環境設定に色々ハマった記録。バグトラップな上にグーグル先生が的確な情報出してくれない!!

問題山積み

  1. VirsualBoxのインストールで失敗する (modprobe vboxdrv が云々)
    • secure bootの影響で、署名がないモジュールをロードできないのが原因。署名すればよい。
  2. だがしかし、署名に使った公開鍵がenrollできない
    • mokutilのバグ。1つ前のバージョンに戻す。
  3. Mac OS X用のVagrantfileとCentOS(Linux)用のVagrantfileではansible providerの名前が異なる
  4. なんかVagrantfileからは実行できないのに、ansible-playbookでは実行できるんですが… (これは秘密鍵のパスとか、そういう問題かも)
  5. NFSマウントが失敗する
    • firewalldがお仕事してた。vboxnet0をtrusted zoneへ叩き込む(いいのかそれで?)
  6. "CentOS VirtualBox Vagrant"とかでググると「Vagrantで(WIndowsの)VirtualBoxにCentOSを入れようず」いう話ばかりが出てくるんですよね…。しかし他に適当なキーワードもなく。

各々の対策記録

VirsualBoxのインストールで失敗する

CentOSのおそらく7.4以降~バグ修正版が出るまでは、先に「署名に使った公開鍵がenrollできない」を読んでください。二度手間になります。

RedHatの 26.8. セキュアブート用のカーネルモジュールの署名 が日本語ですし、「何をやっているのか」まで分かってよいです。

単にやるべきことを知りたいだけならば Signing VirtualBox & VMware modules 等があります。 なお、CN=以降はcommon nameを入れるところなので各自考えて置き換えることになります。

例に出ているのは「vboxdrv」だけなのですが、実際は同じディレクトリ内の全ファイルに署名しないとダメでした(全ファイル必要なのかは組み合わせテストが面倒なので確認していません)。

modinfo -n vboxdrv でこのモジュールファイルのフルパスが出てくるので、そこでパス名から必要なファイルを見つけてください。ちょっとイイ感じにやりたければ ls $(dirname $(modinfo -n vboxdrv) )/* |xargs -l1とかすればイケるんじゃないかとは思う。

署名に使った公開鍵がenrollできない

問題は、上記でモジュールに署名したあと、その公開鍵を mokutil へ登録した後で、再起動するとUEFI コンソールで mokutil にセットしたパスワードを聞かれ、パスすれば以後は公開鍵がOSへ永続登録される、という部分です。何度再起動してもそんな画面出てこないのです…っ

原因は、最新版の mokutil にバグがあり、肝心のUEFIコンソールが起動しないとのこと。 以下にその説明があります。

0014050: Failed to Enter Shim UEFI key management screen while rebooting on CentOS7 if upgrade to 7-4.1708

元々問題に気付いたのはこのあたりの記事。 "The uefi secure boot bug" の文言を読んだ時の脱力感がハンパない。

Red Hat Bugzilla – Bug 1477735 Shim was unable to measure state into the TPM

対策ですが、バグのない1つ前の mokutil はCentOSの場合7.3にあります。 http://vault.centos.org/7.3.1611/os/x86_64/Packages/ から mokutilshim のRPMをダウンロードして、最新版を yum remove で削除後にインストールします。

成功すれば、ブート時にきっちり止まりますので確実に分かります。

この作業前に mokutil へ鍵を登録したりパスワードをセットしていた場合、その作業からやり直しになるので注意してください。

Mac OS X用とCentOS(Linux)用とのVagrantfile差異

一覧に書いた通りです。Mac OS X 用は ansible 。CentOS用は ansible_local 。このあからさまな違いから見て、CentOS用は何か使い方を変えると ansible になるのかもしれませんが。

参考記事は以下。

Ansible Local Provisioner

NFSマウントが失敗する

原因がわからないときは、 vagrant ssh でゲストOSに入ったのち、おそらく画面に表示されているであろうコマンド( mount -o vers=3,udp <ホスト側IP>:<ホスト側Path> /vagrant 等)を実行してみるのがよいです。この際、mount コマンドに --verbose を追加することで、ある程度情報が増えます。

私のケースの場合 mount.nfs: portmap query failed: RPC: Unable to receive - No route to host が出ていたので、これは経路が切れてるな…と考えた次第。その後sshでゲスト→ホスト接続を試してみて、接続できたのでfirewallかな、と。

  1. firewall-cmd --add-interface vboxnet0 --zone=trusted
  2. firewall-cmd --list-all --zone=trusted (確認)
trusted (active)
  target: ACCEPT
  icmp-block-inversion: no
  interfaces: vboxnet0
  sources: 
  services: 
  ports: 
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 

のような結果になればOK。

gulp-gh-pagesを使おうとしてハマったメモ(おそらく今だけの一過性)。

発端

gulp-gh-pages を使おうとしたら、以下のような謎のエラー。

node_modules/gulp-gh-pages/node_modules/gift/lib/commit.js:145
      ref1 = /^.+? (.*) (\d+) .*$/.exec(line), m = ref1[0], actor = ref1[1], epoch = ref1[2];
                                                       ^

TypeError: Cannot read property '0' of null

エラー箇所のコードを読むと、gitのcommitログ解析でおかしくなっているように見えるのだけど、なんでcommitログの正規表現処理なんてやっているのかが不明だにゃー、と。

原因

ググると以下のページが見つかったので、取り敢えず gulp-gh-pages-will ではなくて手動で何とかする方法をとってみた。

gulpの「gulp-gh-pages」がエラー履くようになったときの対応

記事内の解説、およびgiftのissueとかを読んでみると、giftの過去バージョンに起因する問題で、gift自体では問題解決済みの版が出ている。しかし、gulp-gh-pagesのpackage.jsonが古いままなので、gulp-gh-pagesとしては問題解決できていない、ということらしい。

どうも gulp-gh-pages 本家もGitHub上では次バージョンのタグが出ていて、最新のpackage.jsonではgiftも最新版使うようになっているので、近々根本的な解決があることを期待したいところ。

なお、gulp-gh-pagesをGitHub版で使えばいいよね、と思ってやってみたところ、今度は gulp-gh-pages が依存する gulp に未リリース・GitHub版を要求され、仕方ないので gulp もGitHub版にしてみたところ、新たな謎エラーが出てしまったというオチがつきましたとさ。

解決手順

  1. "gift": "^0.10.2" を package.json に追記
  2. npm install 実行後、rm -rf node_modules/gulp-gh-pages/node_modules/gift として gulp-gh-pagesのインストール結果内giftを削除(こうしないと v0.10.2 のほうを使ってくれない)

Amazon Product Advertising APIについての備忘録。

JavaScriptベースで一通りつついてみたので、実装方法の備忘録。秘密鍵扱うので、クライアントサイドJavaScriptはNGですな。基本的にはプログラミングガイド読めば分かるものです。コーディングはお仕事しますよ? 的な。

必要なもの

アクセスキーID:
Amazonアソシエイト・セントラル →ツール →Product Advertising API で取得可能
秘密キー:
Amazonアソシエイト・セントラル →ツール →Product Advertising API で取得可能(認証情報生成時のみ表示されるようなので注意)
アソシエイトID:
Amazonアソシエイト・セントラルの右肩に出てるアレです

実装上のポイント

一先ず、ASINで指定した特定商品の情報取得を試しました。

  • エンドポイントは固定。シグネチャ生成時にhostとpathに分割することになります。
  • クエリは文字コード順でソートすることになるので、& で連結せずに扱ったほうが良さ気でした。ソートはArray.sort()のアルゴリズムで問題ない筈。
  • 各所の値はencodeURIComponent()でエンコード。
  • TimestampはISO形式の日付+時間ですが、ミリ秒以下があるとエラーになりますので(new Date()).toISOString()は使用できません。
  • シグネチャはbase64 エンコードの HMAC_SHA256です。Node.jsの場合 crypto.createHmac()が使用可能です。

うっかりハマる点として、各所に出てくるサンプルでのエンドポイントがアメリカ版ですが、日本のAmazonでアカウント登録している場合は日本のエンドポイントを使う必要があります。

処理の流れ

  1. タイムスタンプを生成します。UTC~メソッド群を使用して、ミリ秒を含まないISO形式の日付を生成。getUTCMonth()が0~11を返すことと、1桁の値は右側に0が必要になることに注意
  2. クエリを項目ごとに分割・格納したArrayを組み立てます →query

    AWSAccessKeyId
    アクセスキーID
    AssociateTag
    アソシエイトID
    Operation
    ItemLookup (今回は商品情報取得なので)
    ItemId
    ASIN
    Timestamp
    タイムスタンプ
  3. エンドポイントURLをバラします。Node.jsの場合url.parse()でパースして、hostとpathを使用します。
  4. クエリをソートし、&で連結しておきます。 →sorted_query
  5. 'GET', エンドポイントのhost, エンドポイントのpath, sorted_queryを\nで連結し、crypto.createHmac()で処理した後digest('base64')でBASE64形式で取り出し、更にencodeURIComponent()でエンコードします。 →signature
  6. エンドポイント + ? + sorted_query(queryでもいい筈) + & + signature にアクセスします。後はレスポンスを適宜調理。

リクエスト制限は「1秒に1回」とされているのですが、試行しているときにコード修正で明らかに数秒以上経過してても制限にかかってエラーになったので、もう少し余裕持ったほうが良さそう。

ところでPAAPIってやっぱり「パァイ」って読めばいいんですかね。

Vue.js+&lt;input type=&quot;date&quot;&gt;+初期値new Date()の場合

data() で初期値を定めるとき、new Date()の値をyyyy-mm-ddにしてやれば万事解決。←結論

細かく解説すると。

  • Vue.jsではコンポーネントの data() で初期値セットを返す
  • <input type="date" > はvalueとしてISO形式(yyyy-mm-dd)を与えないと初期状態として日付を表示してくれない(値があっても "yyyy/mm/dd" というユーザに大変お優しい表示となってしまう)
  • <input type="date" > に new Date() の値をそのままセットしても上記理由によりまともに表示してくれない

解決パターン。

Google Shortener(Google短縮URL) API。

Google Shortener APIで短縮URLを生成する処理をちょっと試してみたのでメモ的に残しておく。

  • APIキーは各自Googleアカウントで発行すること
  • fieldsクエリは全部乗せになってます
  • 正常に実行できると、アラートダイアログで結果が表示されます
  • エラー処理? なにそれおいしい?
$(function() {
  var apikey = <各自APIキーを入力>;
  var url = 'https://twitter.com/mami_tuchino'; /* これが短縮したいURL */
  $.post({
    url: 'https://www.googleapis.com/urlshortener/v1/url?fields=analytics%2Ccreated%2Cid%2Ckind%2ClongUrl%2Cstatus&key=' + apikey,
    data: JSON.stringify({ longUrl: url }),
    dataType: 'JSON',
    contentType: 'application/json',
    success: function(json) { alert(json.id); },
    error: function(error) { console.logt(error); }
  });
});