kikukawa's diary

都内で活動するシステムエンジニアが書いてます。 興味を持った技術やハマったポイント、自分用メモをつけてます。 最近はweb中心

composer scriptでphpのci周りを登録する

毎回調べているのでメモ

phpでciを回すのによく使うものに

があります。
php7ならphanもですね。

これを、composerのrun-script 経由で叩けるようにしておくと、
毎回オプションなどを指定しなくて済むので楽です。
書き方を毎回調べているのでメモ的に残します。

composer.json(抜粋)

    "scripts": {
        "l": "php -l ./src",
        "test": "./vendor/bin/phpunit -c ./phpunit.xml",
        "cs": "./vendor/bin/phpcs --standard=./codesniffer/ruleset.xml ./src ./tests",
        "cbf": "./vendor/bin/phpcbf --standard=./codesniffer/ruleset.xml ./src ./tests",
        "md": "./vendor/bin/phpmd ./src,./tests text ./messdetector/ruleset.xml",
        "ci": [
            "@l",
            "@cs",
            "@md",
            "@test"
        ]
    }

解説

各コマンドのキーになっているところが、 composerのサブコマンドになります。
サブコマンドは、@ つきでエイリアスとして使用できるので、2重定義しなくて済みます。
複数のコマンドを実行したい時は、配列で指定します。

実行方法

composer run-script ci

定義した ci を実行します。
または、単に

composer ci

としても動きます。 サブコマンドにオプションを渡したいときは、 -- で区切ります。

composer run-script test -- --group=Foo

上記は、 test に--group=Foo を渡して実行してくれます。

./vendor/bin/phpunit -c ./phpunit.xml --group=Foo

と同義です。

Facebook APIのバージョンアップ対応 phpのgraph-sdkのバージョンアップ

Facebookが提供しているPHPSDKのバージョンアップについてです。
3系から5系にバージョンアップしたので少しだけサンプル残したいと思います。

3系

$params = [
  'message' => '投稿メッセージ',
  'link' => 'http:example.com',
  'access_token' => '{access_token}'
];
$facebook = new Facebook(['appId' => '{app-id}','secret' => '{app-secret}'];
$ret = $facebook->api('/v2.4/me/feed/', 'POST', $params);

5系

$params = [
  'message' => '投稿メッセージ',
  'link' => 'http:example.com',
];
$facebook = new FacebookSDK([
    'app_id' => '{app-id}',
    'app_secret' => '{app-secret}',
    'default_graph_version' => 'v2.9',
]);
//postメソッドの場合、第5引数にバージョンを指定することもできます。
//getメソッドは第4引数にバージョンを指定できます。
$facebook->post('/me/feed', $params, '{access_token}');

5系の戻り値について

5系の場合、post、getメソッドの戻り値は、FacebookResponseのオブジェクトです。
ここからエンドポイントにあった方法で値を取得することができます。
どのエンドポイントでどのオブジェクトを使用できるかは、
分かってないんですが、合わないメソッドを呼ぶとエラーが出るので
開発時には気づくと思います。 下記の他にもいくつかるのですが、よく使いそうなものを紹介します。

getGraphNode

getGraphObjectに替わる新しいメソッドです。 多分、単一のオブジェクトを取得する用なんだと思います。

$res = $facebook->post('/me/feed', $params, '{access_token}');
$node = $res->getGraphNode();
$id = $node->getField('id'); //これでポストしたメッセージのidがとれます。

ちなみにgetGraphObject は、sdk のv6で削除予定です。

* @deprecated 5.0.0 getGraphObject() has been renamed to getGraphNode()
* @todo v6: Remove this method

getGraphEdge

多分、リスト形式のオブジェクトを取り扱う用です。 配列として扱えます。

$res = $facebook->get('/me/permissions', '{access_token}');
$edge = $res->getGraphEdge();
if(count($edge) > 0){
  foreach($edge as $item){
    //somthing
  }
}

getGraphUser

ユーザー情報に特価したオブジェクト用です。

$res = $facebook->get('/me?fields=id,name', '{access-token}');
$user = $res->getGraphUser();
$name = $user['name'];

Facebook APIのバージョンアップ対応 影響箇所の調査

前回の記事の続きです

実際に自分がやらなければならない対応を知るためには
アップグレードツール、APIの変更履歴を確認します。

アップグレードツール

https://developers.facebook.com/docs/graph-api/advanced/api-upgrade-tool/

facebookが提供しているツールです
自分がアプリの管理者(もしくは開発者)で、
このツールを開くと、
影響を受けるGraph API(エンドポイント)を叩いている場合、それを警告してくれます。

ただし、このツールで分かるのは、
過去7日間に影響のあるエンドポイントを叩かれた場合だけです。
ツールのページで Items to fix to upgrade XXXXX from v2.4 to v2.9: と書かれている表の
Call Volumeは 過去7日間に叩かれた回数だそうです。

頻繁に叩かれないようなものは、ここには出てこないので
分かりません。
その場合は、Graph APIの変更履歴と
照らし合わせながら、自分で調査する必要があります。

変更履歴

https://developers.facebook.com/docs/apps/changelog?locale=ja_JP

Graph APIマーケティングAPIの変更履歴が分かります。
詳細な変更は、このページから各詳細を参照することで分かります。

このページで気をつけないといけないのが、
90日後に変更(削除) となっている箇所です。
この箇所に書かれていることは、どのバージョンを使っていたとしても
そのバージョンがリリースされてから
90日後に影響が出ることになります。

例えば、現在、v2.4を使っていたとしても、
v2.9の90日後に削除のところに書かれている

エッジやダイアログでリンクを投稿にアタッチできる次のフィールドは廃止されます。

は影響を受けます。
このことは、 例えば ‘feed'のエンドポイントのページも書いてあったりします。

As of April 18, 2017, the following parameters are no longer supported by Graph API versions 2.9 and higher. For versions 2.8 and lower, the parameters will continue working until July 17, 2017.

Facebook APIのバージョンアップ対応 バージョンとは

Graph APIのバージョンアップをすることがあったので
対応内容とか周辺知識をまとめておきます。
マーケティングAPIは対象外です。

バージョンについて

まず、バージョンアップといっても何をバージョンアップすればいいか
分からなかったので調べました。

見るべきところは、アプリ自体のバージョンと
Graph APIを実際にコールしているところのバージョンです。
私の場合は、Graph APIを直接叩くことはしていなく
phpやjsなどのSDKを用いていたので、そこを見ました。

アプリのバージョン

facebook for developers で自分が管理しているアプリのバージョンです。
アプリのダッシュボードの画面で、今どのバージョンを使っているか確認できます。

ここのバージョンがいったい何に影響しているのかは
よく分かりませんでした。
多分、Graph APIでバージョンを指定しないで叩いたときのデフォルトの
バージョンなんだと思います。

Graph APIを叩くときに指定するバージョン

facebookが出しているsdkで指定するバージョンです。
SDKの初期化時に指定する方法と、
APIを叩くときに指定する方法があります。

php

手元にあった sdk5.4.4では、
default_graph_version を指定しなくても大丈夫ですが、
Facebook\Facebook の中を除くと

// @todo v6: Throw an InvalidArgumentException if "default_graph_version" is not set

というコメントがあったので、将来的に必須になるようです。

初期化時に指定するデフォルトバージョン

$fb = new Facebook\Facebook([
  'app_id' => '{app-id}',
  'app_secret' => '{app-secret}',
  'default_graph_version' => 'v2.2',
  ]);

叩くときに指定する方法

初期化時にバージョンを指定しなかった場合には
ここでバージョンを指定する必要があります。

$fb->get('/v2.9/me/feed', '{access-token}');

or

$fb->get('/me/feed', '{access-token}', null, 'v2.9');

js

いいね!ボタン構成ツールで取得できるコードを見ると
バージョンを指定している箇所があります。
私の場合は、いいねボタンくらいしか使ってなかったので
ここだけしかありませんでした。

生成されたもの

<script>(function(d, s, id) {
  var js, fjs = d.getElementsByTagName(s)[0];
  if (d.getElementById(id)) return;
  js = d.createElement(s); js.id = id;
  js.src = "//connect.facebook.net/ja_JP/sdk.js#xfbml=1&version=v2.9&appId={app-id}";
  fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>

自前で初期化している場合

<script>
window.fbAsyncInit = function() {
    FB.init({appId  : '{app-id}',
             status : true,
             cookie : true,
             xfbml  : true,
             version: 'v2.9'
    });
};
(function(d, s, id) {
        var js, fjs = d.getElementsByTagName(s)[0];
        if (d.getElementById(id)) return;
        js = d.createElement(s); js.id = id;
        js.src = "//connect.facebook.net/ja_JP/sdk.js";
        fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
</script>

Gitのリベース途中でエラーメッセージ - VCS_INFO_get_data_git:223

現象

Gitのリベース途中でコンフリクトの修正中に起こった問題です。
git checkout --oursとか打つと、下記のメッセージが出てきて困りました。

VCS_INFO_get_data_git:223: no such file or directory: .git/rebase-apply/msg-clean

解決方法

oh-my-zshのissueで話題に上がってました。

zshのバージョンを上げると解決するって書いてあるんですが、
私の場合は、意図したバージョンのzshが使えてなかったために起きてました。

環境によっては、zshのバージョンの確認方法に気をつけなればなりません。
バージョンの確認方法は2つあって、

  • zsh –version
  • echo $ZSH_VERSION

です。

❯ zsh --version
zsh 5.3.1 (x86_64-apple-darwin15.6.0)
❯ echo $ZSH_VERSION
5.0.8

私の場合、上記のような結果となってした。

何をしたか

/etc/shells に /usr/local/bin/zshを追記して、 改めて chsh -s /usr/local/bin/zsh

無事に解決しました。

AnsibleのPlaybookを作る前に考えておくこと

この記事はAnsible Advent Calendar 2016 - Qiitaの24日目の記事になります。
Ansibleで個人開発環境を構築して、一通り使ってみて感じたことを残しておきます。
基本的に自分のメモ程度なので、何かあったらコメントお願い致します。

ベスト・プラクティス

取り敢えず読んでおきます。
更新されているので、たまに読み返すと新しい情報が追加されていることがあります。

Best Practices — Ansible Documentation

ディレクトリ構成

ベスト・プラクティスで下記2つが紹介されています。

qiitaでも別解として話題に上がっています。

どれがいいかはプロジェクト次第です。
作り始める前にどれがいいか検討すべきですが、決められなければ、
とりあえず、ベスト・プラクティスのDirectory Layoutで始めてみてあとで構成変えるでもいいと思います。

ちゃんと設計する

最初からロールを分割しすぎない

「早すぎる最適化」はよくないです。 ロール名を変数名の接頭辞にするのが慣例なので、
タスクを別のロールに移動するとか考えるとリファクタリングコストがそこそこかかります。

同じミドルウェアのタスクでも分割したほうがいいタスクがある

例えば、MySQLのロールの中で

  • MySQLを起動するLinuxユーザー作成
  • インストール
  • プロジェクト用のデータベースの作成

をすべて一つのロールでやらないほうがいいかもしれないです。
Playbookにした時に、ユーザーの作成とインストールまで行って、
何か別のタスクを実行した後に、データベースの作成という流れにしたいときに困ります。

共通で使う変数

各ロールやPlaybookで共通で使用したい変数名の命名規則は最初に決めておいたほうがいいです
後から変更するとそこそこリファクタリングコストがかかります。

部分実行しやすいようにする

Playbookの開発中に毎回毎回すべてのタスクを実行していては効率が悪いです。
--start-at-task--tags を使えるようにしておくと再実行しやすいので時短につながります。
なので、タスクやロールにtagはつけておいたほうがいいです。
上記オプションをつけて実行するとき、pre_taskは実行されないっぽいので注意が必要です。

冪等性をどこまで担保するか

なんでもかんでも冪等性を担保しようとすると辛い場面が出てきます。

ドライラン対策

確認のために -C をつけて実行することがあると思います。
しかし、一部のモジュール(commandやshell)はこのドライランに対応していないです。
冪等性を担保するなら自分でどうにかする必要があります。

不要になったタスクをどこまで面倒みるか

例えば、Vagrantで各自の開発環境を用意するとして、そこに流すPlaybookの場合を想定します。
前のバージョンでは作っていたけど、いらなくなったディレクトリが出てきたとき、次のバージョンで削除するタスクを書いておくのか?
という問題が出てきます。
前のバージョンを実行していたメンバーが次のバージョンを実行したときに
そのいらなくなったディレクトリを削除するためだけに タスクを残しておくと本来必要のないコードが残っている状態になります。
コードの見通しが悪くなるので、タスク自体消したほうがいいですが、いつ消すかはちゃんと考えておかないとメンバーに迷惑がかかることもあります。

モジュール一覧を一通り眺めてみる

意外と細かいことをやってくれるモジュールもあるのでcommandモジュール使う前に、
Ansibleのドキュメントのモジュール一覧を見てみるとよいです。
apt_key とか apt_repository とかcommandでやりがちなモジュールもあったりします。

Vagrant環境での注意

ディレクトリのオーナー、パーミッション

Ansibleの問題ではなくVagrant(VirtualBox)の使い方の問題です。
ホストOSのパーミッションはゲストOSでは変えられないことを念頭に置きましょう。

ディレクトリのオーナーやパーミッションを変えるようなタスクを作るとき
ホストOSとの共有ディレクトリは、Vagrant側から流したPlaybookでは変更できません。
Ansibleの実行結果はchengedとなりますが、実際には変わりません。

個人開発環境を作るときなんかは、ソースは共有ディレクトリに置くことが多いと思うので
その辺意識しておかないと、なんで変わってないんだとなってはまってしまうので注意です。

例えば、ソースを置くディレクトリは、 wwwをオーナーにしたい場合を考えます。 Vagrantfileでディレクトリ共有の設定でwwwを指定すれば、ゲストOS側でそのオーナーになります。 しかし、そこに指定するユーザーは、Playbook流す前は存在しない場合、
1つのPlaybookだけで解決しようとするとなかなか難しい状況に遭遇します。
ユーザーやグループの追加はプレイブック分けたほうがいいかもしれません。 vagrant up だけで環境整うと嬉しいけど、そこまでいくにはいろいろ考えないといけないことが多いです。

saharaを使う

Playbookの開発を行うときにVagrantを使うなら sahara というプラグインを使うとよいです。
Playbookを流しては、失敗して、修正し、もう一度流す。みたいなことを繰り返します。
ある程度動作が固まってきたときに、ゲストOSをまっさらな状態にしてから流し直すことをやると思います。 そんなときは sahara が便利です。
他にも同じようなことができるプラグインはありますが、
ゲストOSの状態をバージョン管理したいとかでなければsahara で十分です。

Ansibleの流し方を考える

Vagrantでは、ゲストOSに対してAnsibleを実行する方法が幾つかあります。
Vagrantのプロビジョナを使う場合は、Shell, Ansible, Ansible Local のどれかを使うことになるでしょう。
どれがいいかはプロジェクト次第だと思うので、一概に言えません。

私は、 Shell を使用して、Ansibleのインストールのみを行い、
Playbookの実行はVagrantに入って、手動でやる方法を取りました。
Playbookの開発中は何度も実行するし、どう変わったかの確認もするため、結局sshするからです。
そもそも開発メンバーの中に、Windowsの人がいたので、Ansibleは選択できませんでした。

テストコード

Ansibleに限った話ではないですが、考えておいたほうがいいです。
Ansibleのタスクに対してテストコードを書く場合、今ならServerSpecを選択することが多いと思います。
そこまで大きなプロジェクトでないなら、書かないほうがコストやすいことのほうが多いかもしれないと感じました。
書くなら書き始める時期もちゃんと検討しましょう。

実行ユーザー

  • remote_user
  • become_user

はちゃんと運用時のことを想定して考えておきましょう。
Vagrant環境だとvagrantだけでいろいろいけちゃいますが、Vagrant以外の環境のこともちゃんと考えないと後が大変です。

他のサーバーにsshするとき(git cloneなど)にどの秘密鍵を使うのか、
その秘密鍵はどうやって管理するのか
はたまた、エージェント転送で解決していくのかも先にある程度考えておくといいと思います。

becomeの単位

Playbook単位でbecome: Trueとするかタスク単位で切り替えて行くのかは
上記実行ユーザーと一緒に考えておきましょう。

変数の分割

apache_document_root:
  name: www
  owner: www
  group: www
  mode: 0755

みたいなハッシュが defaults にあったときに apache_document_root.name だけ上書きしたい時に困ります。 Ansibleはデフォルトでハッシュを置換するので、別のところで

apache_document_root:
  name: foo

とかできないです。 ansible.cfg で置換ではなくマージに変えることもできますが、
全体的な影響(自分が中身をよく知らないロールとか)を考えたときに、他は大丈夫なのかという問題が出てきます。
あと、Ansibleをよく知らないメンバーが、マージがデフォルトだと思ってしまうこともあるかもしれません。

ansible-galaxyで管理するロールの配置場所

これは正解が分かりませんでした。
前提として下記の条件があったとします。

  • プロジェクトごとにPlaybookを管理するリポジトリを持つ
  • プロジェクト特有のロールは、このリポジトリに入れる
  • Ansibleを実行するサーバーは、共有で複数のプロジェクトがそこから実行する

デフォルトだと、システムグローバルに置かれますが、それはしたくないです。
それぞれのプロジェクトで、同じロールの違うバージョンを使う可能性があるからです。
require.yml で、個別の名前をつければ対応できると思いますが、しっくりこないです。

結局、下記のような運用にしました。

  • ansible.cfg の中でインストール場所に ./roles を指定する
  • require.ymlの中で、インストールするの名前を role-xxx というような命名規則にする
  • .gitignoreroles/role-* 追加して、galaxyで管理しているロールは除外する

Ansibleのソースを読む

Ansibleのエラーメッセージはあまり親切ではないので、よく分からないことが多いです。
どうにもならないエラーに出会ったら、Ansibleのソースを読んでみることをおすすめします。
homebrewで入れたなら下記辺りに入っているはずです。

/usr/local/Cellar/ansible/2.1.0.0/libexec/lib/python2.7/site-packages/ansible/modules

ここに coreextras というディレクトリがあり、それぞれの中にカテゴライズされたモジュールがあります。
モジュールが coreextras のどっちに属するかは公式ドキュメントに書いてあります。

例えば、gitモジュールだったら、ここの下の方に This is a Core Module と記載されています。
もしくは、モジュール一覧で (E) がついていれば、 extras 扱い
gitモジュールは Source Control Modules にカテゴライズされているので

/usr/local/Cellar/ansible/2.1.0.0/libexec/lib/python2.7/site-packages/ansible/modules/core/source_control/git.py

がソースになります。

Tips

jinja2のフィルターを活用する

例えば、標準出力の整形には from_jsonfrom_ini が結構便利です。
自分で冪等性を担保したいときなど、一旦設定を情報などを確認すると思うのですが、
標準出力に出た内容をAnsible(jinja2)で使えるハッシュに直すときなどによく使いました。

group_varsはイベントリファイルの書き方で意図しない動きになる

  • group_vars
    • web.yml
    • db.yml
    • batch.yml

というような構成になっているときにそれぞれで、 common_main_user という変数を定義しているとします。
かつ、hosts(イベントリファイル)の中で、下記のようにすべて同じものを指している場合

[web]
localhost

[db]
localhost

[batch]
localhost

ansible-playook -i hosts db.yml と実行しても、 多分、期待通りに動かないでしょう。
common_main_user の中身が db.yml に指定したものとは限らないからです。
group_vars の中を探しに行く際に、 マシン名をベースに、最初に見つかったグループを取得するようです。
この例だと、多分、 batch.yml に指定したものが入ってくると思います。
Vagrantで開発しているときなどは、全部を localhost などにしがちですが、これをやるとハマることがあります。
意図したとおりに動かしたい場合、
開発中なら一旦他のグループのlocalhostコメントアウトしておくとか、xip.ioを使うとかしたほうがいいと思います。

blockinfile便利

tipsでもなんでもないですが、よく存在を忘れるのでメモ。
blockinfile モジュールを使うとファイルへの追記などが楽になります。
使うときは marker を指定しておいたほうが無難です。

LinuxユーザーのIDの固定はそれなりに考える必要がある

データベースサーバーでマスタースレーブ構成を取りたいときは、サーバーを複数台用意すると思います。
その時、それぞれのサーバーでユーザーのIDを固定化しておきたいという要望があったとします。
userモジュールでID指定をすれば固定できますが、よく考えてから固定化するしたほうがいいです。

sudo apt-get install mysql-server などとパッケージ管理からインストールすると
mysqlのユーザー、グループも一緒に作られます。そして、mysqlのサービスも起動された状態になります。
ユーザーのIDを変更するには、そのユーザーでサービスが起動していてはいけないので、一旦サービスを止める必要があります。
なので、例えばこんな感じのタスクになる。

- apt: name=mysql-server state=present
- service: name=mysql-server state=stopped
- user: name=mysql uid=9999

本番環境でこんなのは流せないです。
先にユーザーを作ってしまうという手もありますが、
例えば、redis-serverは、redisというユーザーがすでに存在するとaptによるインストールが失敗します。
なので、ユーザーのIDの固定化はタスクとかロールとかがごちゃごちゃしがちになるので、本当に必要かどうか考えたほうがいいです。

Ansibleはshellでコマンドを流しているわけではない

Ansibleを触り始めた頃に思い込みをしていたので、メモとして残しておきます。
Ansibleはsshで他のサーバーに接続してタスクを実行していきますが、各タスクのshellのコマンドを実行するわけではないです。
例えば、gitのクローンは git clone git@github.com/foo/bar.git をshell上で実行しているのではなく、上記を行うpythonスクリプトを送り込んで、そのスクリプトを実行しています。
エラーが起こったときなど、実際どのようなコマンドが発行されたかをつい知りたくなりますが、
コマンド履歴などに残っているようなshellのコマンドを探そうとしても見つかりません。

Ansible Galaxyでロールの管理 - コマンド編

個人(主にGithub)でAnsible Galaxyでロールを管理する上で、
よく使うサブコマンドは、initinstallの2つです。
import,search,removeなどは、Ansible Galaxyにある
ロールを使う時によく使うサブコマンドです。

ただ、実際のプロジェクトでは、
Ansible Galaxyにあるロールをあまり使うことはないのかなと思ってます。
既存のロールは、不要なタスクがあったり、タスクが足りなかったりで
自分のプロジェクトにジャストフィットするものは
あまりないという印象です。

自分でロールを開発する時に参考として見るという使い方が
一番しっくり来ています。

init

ロール開発をする雛形を生成してくれるコマンドです。
ansible-galaxy init ansible-role-foo という形で使用します。
ansible-role-foo という部分は自分で指定します。

大体このコマンドで作成したディレクトリをそのまま
リポジトリとして扱っていくことが多いと思います。

ロール名

Ansible Galaxyのサイトでは、 リポジトリ名 = ロール名 になっていないものが多いですが、
個人で管理する場合は、リポジトリ名 = ロール名にしてしまうのが簡単だと思います。
私は、 ansbile-role- か 単に role- を接頭辞につけることが多いです。

ディレクトリ構造

実行すると下記のような構造になります。

ansible-role-foo
├── README.md
├── defaults
│   └── main.yml
├── files
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── tasks
│   └── main.yml
├── templates
├── tests
│   ├── inventory
│   └── test.yml
└── vars
    └── main.yml

何をどこに書くのかは、公式ドキュメントにあるベストプラクティス
従うのが今のところ良いのではないかと思います。
日本語で解説されているサイトも多いのでググるといろいろ出てきます。

install

playbookで使用したいロールをローカルに落としてくるのに利用します。
私は、基本的に require.yml に記載したものをインストールするときに使用しています。
ansible-galaxy install -r require.yml のような形で実行します。

実際のプロジェクトでは、ansible-galaxy install -fr require.yml
f オプションを追加して使うことが多いです。fはフォースオプションで、
ロールを落としてくる先に、すでに同名のロールが存在した場合、上書きするオプションです。

Playbookを一旦開発し終わった後でも、ロールに対して修正することがよくあります
そんなときに、再度インストールし直すには、fオプションつけないと更新できません。

ちなみにこの時、 fr の順番に気をつけないとエラーがでることがあるようです。

kkkw.hatenablog.jp kkkw.hatenablog.jp http://kkkw.hatenablog.jp/entry/ansible/ansible-galaxy-repositorieskkkw.hatenablog.jp