Ansible2.1がARMに対応したので"少しだけ"試してみた #2

先日、Ansible2.1でARMを試した記事を書きましたが、その後もう少し深くいじってみました。

kaz29.hatenablog.com

前回はリソースグループを作っただけでしたが、今回は、牛尾さん(id:simplearchitect)の以下の記事でTerraformを使ってやっていることをなぞってみました。

qiita.com

設定内容

設定した内容はこんな感じ。yamlでかけるのは読みやすくていいですね。

---

azure_resource_group:
  - name: Testing
    region: japanwest
    state: present
    tags:
      testing: testing
      delete: never

azure_virtual_network:
  - name: test
    state: present
    resource_group: Testing
    address_prefixes_cidr:
        - "10.1.0.0/16"
    dns_servers: []
    tags:
        testing: testing
        delete: on-exit

azure_subnet:
  - name: acctsub
    state: present
    virtual_network_name: test
    resource_group: Testing
    address_prefix_cidr: "10.1.2.0/24"

azure_publicipaddress:
  - name: ansibletestip
    state: present
    resource_group: Testing
    allocation_method: Static
    domain_name: ansibletestlinux
    tags:
        testing: testing
        environment: Production

azure_networkinterface:
  - name: testnic1
    state: present
    resource_group: Testing
    virtual_network_name: test
    subnet_name: acctsub
    security_group_name: 
    public_ip_address_name: ansibletestip
    private_ip_allocation_method: Static
    private_ip_address: 10.1.2.10
    tags:
        testing: testing

azure_storage_account:
  - name: accsa1971eey
    state: present
    resource_group: Testing
    type: Standard_LRS
    tags:
        testing: testing

azure_storage_container:
  - name: vhds
    state: present
    resource_group: Testing
    storage_account_name: accsa1971eey
    tags:
      testing: testing

azure_virtualmachine:
  - name: AnsibleVM02
    state: present
    resource_group: Testing
    vm_size: Standard_A0
    storage_account: accsa1971eey
    storage_container_name: vhds
    network_interfaces: testnic1
    admin_username: kaz
    remove_on_absent:
      - virtual_storage
    image:
      offer: UbuntuServer
      publisher: Canonical
      sku: '14.04.2-LTS'
      version: latest
    tags:
      testing: testing

Ansibleを使用して実行してる内容は以下。

  1. ストレージクループの作成
  2. 仮想ネットワークの作成
  3. サブネットの作成
  4. パブリックIPアドレスの作成
  5. ネットワークI/Fを作成
  6. ストレージアカウントの作成
  7. ストレージコンテナの作成
  8. VMの作成

今回作ったものはGithubにあげましたので参考になれば。

github.com

ポータルを使わないで、いつものAnsibleで構築して何回でもやり直せるのはかなり便利ですね。

まとめ

まだ理解しきてれていないので、VMを削除しようとしたらエラーが出てうまく削除できませんでした。VM削除時に自動で消されるものがあるようなので、その辺りが原因(`remove_on_absent`)かなと思うので、もう少し調べようと思います。

とはいえ、ポータルをポチポチしないで良いのはとても便利なので今後 Terraformとどっちを使うかも含めて色々検討しようと思います。

Ansible2.1がARMに対応したので"少しだけ"試してみた

Ansible2.1が発表された記事がFacebookに流れてきたので、何気に眺めていたらARM(Azure Resource Manager)のサポートが追加されたらしいので少し試してみた。

Broader support for Microsoft Azure, expanding Ansible’s support for hybrid cloud deployments, including the ability to take advantage of Azure’s Resource Manager functionality.

www.redhat.com

お試しの前に...

実務でAnsibleを使っている場合(特に1.x系)、影響が出てしまうと困るので以下などを参考にpyenv/pyenv-virtualenvを入れて環境を切り替えられるようにしてから試すほうがいいでしょう。

qiita.com

Azure Python SDKのインストール

pyenv環境にazure SDKを入れて試したところ以下のようなエラーが出て、SDKを認識できませんでした。

fatal: [ansible.decr.jp]: FAILED! => {"changed": false, "failed": true, "msg": "The Azure Python SDK is not installed (try 'pip install azure') - No module named enum"}

試しに global の方に入れてみたところ認識されました。*1

$ sudo pip install azure==2.0.0rc2

ちなみに、最新のSDKは2.0.0rc4だったのでこちらで試したところ以下のようなエラーで動作しませんでした。

fatal: [ansible.decr.jp]: FAILED! => {"changed": false, "failed": true, "msg": "Expecting azure.mgmt.compute.__version__ to be >= 2016-03-30. Found version Do you have Azure >= 2.0.0rc2 installed?"}

この辺りはまだ出たばかりなので、今後の改善を期待したいですね。

Ansibleのインストール

Ansible本体をインストールします

$ pip install ansible

サービスプリンシパル認証のための設定

以下の2のあたりを参考にclient_id/secret/tenant_idなどを取得する

qiita.com

Ansibleの設定を書く

  • hosts/hosts

Asure関連の処理はlocalで動作するのでhostsはこんな感じで適当に...

[ansibletest]
ansibletest.example.com
  • roles/azure/tasks/main.yml

<サブスクリプションID>などの設定は各自の環境に合わせて修正してください。*2

- name: Create a resource group
  azure_rm_resourcegroup:
    name: Testing
    location: japanwest
    state: present
    subscription_id: <サブスクリプションID>
    client_id: <クライアントID>
    secret: <secret>
    tenant: <テナントID>
    tags:
      testing: testing
      delete: never
  • azuretest.yml
---
- name: ansible test
  hosts: ansibletest
  connection: local

  roles:
    - azure

playbookを実行する

$ ansible-playbook -i hosts/hosts default.yml

PLAY [ansible test] ************************************************************

TASK [setup] *******************************************************************
ok: [ansibletest.example.com]

TASK [azure : include] *********************************************************
included: /Users/kaz/dev/ansible21/roles/azure/tasks/resource-group.yml for ansibletest.example.com

TASK [azure : Create a resource group] *****************************************
ok: [ansibletest.example.com]

PLAY RECAP *********************************************************************
ansibletest.example.com : ok=3 changed=0 unreachable=0 failed=0


問題なく実行されると、portal上でもリソースグループが追加されました。


f:id:kaz_29:20160531065723j:plain

まとめ

今回はリソースグループを作ってみただけですが、業務に適用できるか引き続き色々試してみようと思います。
TerraformもARM対応したりと色々面白くなってきましたね(^^。

*1:python力足らないんで原因わからないですが、引き続き調査。

*2:ドキュメントには、subscription_idなどの情報を環境変数や、~/.azure/credentialsに設定できると書いてあるのですがうまくいかなかったのでこの辺りも引き続き調査。

swaggerでAPIドキュメントを書いたらめっちゃはかどった話

Swaggerは、REST APIの仕様とそれに関連するツール群の総称です。REST APIの仕様を定義したJSONファイル(Swagger Spec)を軸に以下のようなツールから構成されています。

  • Swagger UI - Swagger Spec から動的にAPIドキュメントを生成するツール
  • Swagger Editor - Swagger Specのエディタ
  • Swagger Codegen - Swagger Specからクライアントのコードを生成するツール

最近では、Open API InitiativeがAPIの記述のためにSwaggerを採用して話題になりました。

www.publickey1.jp

APIドキュメントのメンテは結構面倒

一般的にAPIの仕様書は、古くはExcel/Wordなどを使ったり、最近ではWikiMarkdown形式で記述したりなどプロジェクトによって色々な方法で記述するのが一般的かなと思います。

このには幾つか問題があって、APIの実装ができてもドキュメントがない状態があったり、せっかくドキュメントを作ってもAPIの修正・追加に追いていない状態になったりすることがよくあります。

そこで今回は、実際に開発が進んでいたCakePHP3で作成したiOSアプリのバックエンドのAPIドキュメントをSwaggerで記述したので、そこで得た知見をもとに実際の記述方法などを解説します。

SwaggerでAPIの仕様を書く

CakePHPのブログチュートリアル(http://book.cakephp.org/3.0/en/tutorials-and-examples/bookmarks/intro.html)で使用するスキーマを若干変更した、以下の様なデータをAPIで扱う場合のドキュメントを書いてみます。

  • User
    • id
    • username
    • password
    • created
    • modified
  • Article
    • id
    • user_id
    • title
    • body
    • created
    • modified
アプリケーションの定義

まずは、今回使用するアプリケーション自体の定義を記述します。以下の様に、@SWG\Info内にアプリケーションの名前、説明、APIバージョンなどの情報を記載します。

後ほど一覧取得APIで共通で使用するPageInfoデータに関してもここに記載します。詳細は後述します。

このファイル自体は、コメントだけなのでどこに置いておいても構わないのですが、今回は app/config/swagger.phpとしておきます。

app/config/swagger.php
<?php
/**
 * @SWG\Swagger(
 *     basePath="/api",
 *     host="api.example.com",
 *     schemes={"https"},
 *     produces={"application/json"},
 *     consumes={"application/json"},
 *     @SWG\Info(
 *         version="1.0.0",
 *         title="Swaggerサンプル",
 *         description="SwaggerサンプルAPI仕様",
 *         @SWG\Contact(name="foo@example.com"),
 *         @SWG\License(name="ライセンス表記")
 *     ),
 *
 *     @SWG\Definition(
 *         definition="PageInfo",
 *         required={"page_count", "current_page", "has_next_page", "has_prev_page", "count", "limit"},
 *         @SWG\Property(
 *             property="page_count",
 *             type="integer",
 *             format="int32",
 *             description="総ページ数"
 *         ),
 *         @SWG\Property(
 *             property="current_page",
 *             type="integer",
 *             format="int32",
 *             description="現在のページ番号"
 *         ),
 *         @SWG\Property(
 *             property="has_next_page",
 *             type="boolean",
 *             description="次ページ有/無"
 *         ),
 *         @SWG\Property(
 *             property="has_prev_page",
 *             type="boolean",
 *             description="前ページ有/無"
 *         ),
 *         @SWG\Property(
 *             property="count",
 *             type="integer",
 *             format="int32",
 *             description="ページ内アイテム数"
 *         ),
 *         @SWG\Property(
 *             property="limit",
 *             type="integer",
 *             format="int32",
 *             description="ページ内最大アイテム数"
 *         )
 *     )
 * )
 */
Modelの定義

CakePHP3では、エンティティの内容を明示的に宣言しないので今回は、各Entityクラスのヘッダーに定義を記述しました。実際の記述は以下のような感じになります。

Model/Entity/User.php
<?php
namespace App\Model\Entity;

use Cake\ORM\Entity;

/**
 * User Entity.
 *
 * @SWG\Definition(
 *      definition="User",
 *      required={
 *          "id", "username", "password", "created", "modified"
 *      },
 *      @SWG\Property(
 *          property="id",
 *          type="integer",
 *          description="ユーザid"
 *      ),
 *      @SWG\Property(
 *          property="username",
 *          type="string",
 *          description="ユーザ名",
 *          maxLength=255,
 *      ),
 *      @SWG\Property(
 *          property="password",
 *          type="string",
 *          description="パスワード",
 *          maxLength=255,
 *      ),
 *      @SWG\Property(
 *          property="created",
 *          type="datetime",
 *          description="作成日時"
 *      ),
 *      @SWG\Property(
 *          property="modified",
 *          type="datetime",
 *          description="更新日時"
 *      )
 * )
 */
class User extends Entity
{
...
}
Model/Entity/Article.php
<?php
namespace App\Model\Entity;

use Cake\ORM\Entity;

/**
 * Article Entity.
 *
 * @SWG\Definition(
 *      definition="Article",
 *      required={
 *          "id", "user_id", "title", "body", "created", "modified"
 *      },
 *      @SWG\Property(
 *          property="id",
 *          type="integer",
 *          description="アーティクルid"
 *      ),
 *      @SWG\Property(
 *          property="user_id",
 *          type="integer",
 *          description="ユーザid"
 *      ),
 *      @SWG\Property(
 *          property="title",
 *          type="string",
 *          description="タイトル",
 *			maxLength=255
 *      ),
 *      @SWG\Property(
 *          property="body",
 *          type="string",
 *          description="記事本文"
 *      ),
 *      @SWG\Property(
 *          property="created",
 *          type="datetime",
 *          description="作成日時"
 *      ),
 *      @SWG\Property(
 *          property="modified",
 *          type="datetime",
 *          description="更新日時"
 *      )
 * )
 */
class Article extends Entity
{
...
}
APIの定義

APIで使うモデルの定義が完了したので、次に実際のAPIの定義を記述します。APIの定義は各コントローラのアクションのコメントに記述します。すべてのAPIの記述方法を書くのとなかなかの量になるので、特徴的な記述を使うことが多い一覧系のAPI仕様の書き方について説明します。

今回は、CakePHP3+Crud Pluginを使用しましたので、一覧取得時の実際のレスポンスは以下の様な形式になります。

CakePHP3+Crud Pluginに関しては id:skywaker さんの以下の記事が詳しいです。
slywalker.hateblo.jp

{
    "success": true,		// boolean: 処理結果
    "data": [			// [Entity]: エンティティの配列
        {
            "id": "1",
            "username": 'test',
            "pasword": "xxxx",
            "created": "2016-01-01 01:01:01"
        }
    ],					// PageInfo: ページング情報
    "pagination": {
        "page_count": 1,
        "current_page": 1,
        "has_next_page": false,
        "has_prev_page": false,
        "count": 1,
        "limit": 20
    }
}

*** Controller/UsersController.php

/api/users.json はユーザ一覧を取得するAPIで20件ごとにページングされ、検索文字列を指定することで、ユーザ名で検索できる仕様とします。

レスポンスコードごとに@SWG\Responseブロックを記述してレスポンス内容の解説と、どんなデータが帰るかを記述します。

@SWG\Propertytypearrayとして記述し、参照先のモデルを@SWG\Itemsで参照することができます。また、プロパティ自体が、定義済みのモデルを使用する場合は、ref="#/definitions/PageInfo"の様に記述することで参照できます。

<?php
namespace App\Controller\Api;

use Cake\Event\Event;
use Cake\ORM\TableRegistry;

class UsersController extends ApiController
{
	...
    /**
     * Index method
     *
     * @return void
     * @SWG\Get(
     *      path="/users",
     *      description="ユーザ一覧",
     *      produces={"application/json"},
     *      @SWG\Parameter(
     *          description="検索文字列",
     *          in="path",
     *          name="q",
     *          required=false,
     *          type="string"
     *      ),
     *      @SWG\Response(
     *          response=200,
     *          description="取得成功",
     *          @SWG\Schema(
     *              @SWG\Property(
     *                  property="status",
     *                  type="boolean",
     *                  description="処理結果",
     *              ),
     *              @SWG\Property(
     *                  property="data",
     *                  type="array",
     *                  @SWG\Items(ref="#/definitions/User"),
     *                  description="ユーザ情報",
     *              ),
     *              @SWG\Property(
     *                  property="pagination",
     *                  ref="#/definitions/PageInfo",
     *                  description="ページ情報",
     *              ),
     *          )
     *      ),
     *      @SWG\Response(
     *          response="401",
     *          description="Unauthorized"
     *      ),
     * )
     */
    public function index()
    {
    	...
    }

*** Controller/ArticlesController.php

api/articles.jsonは、記事の一覧を返すAPIを想定しています。api/users.jsonと違い、記事のデータだけでなく記事を投稿したユーザを含む形で以下の様なレスポンスを返すことにします。

{
    "success": true,		// boolean: 処理結果
    "data": [			// [Article]: 記事の配列
        {
            "id": "1",
            "user_id": 1,
            "title": 'test title',
            "body": "test body",
            "created": "2016-01-01 01:01:01"
            "user": {
	            "id": "1",
	            "username": 'test',
	            "pasword": "xxxx",
	            "created": "2016-01-01 01:01:01"
	        }
        }
    ],					// PageInfo: ページング情報
    "pagination": {
        "page_count": 1,
        "current_page": 1,
        "has_next_page": false,
        "has_prev_page": false,
        "count": 1,
        "limit": 20
    }
}

記事一覧のレスポンスのdata内で定義しているItemは先ほど定義したArticleではなく、ユーザ情報を含む形で定義した、IndexArticleを指定しています。

IndexArticleの定義は allOfを使うことで定義済みのArticleモデルの情報を再利用して、userプロパティを追加しています。

この様に、@ref,@SWG\ItemsallOfなどを使用して定義済みのモデルを参照することで、個別のAPIごとにレスポンス内容が微妙み異なる場合などに記述量をかなり減らすことができます。

<?php
namespace App\Controller\Api;

use Cake\Event\Event;
use Cake\ORM\TableRegistry;

class ArticleController extends ApiController
{
	...
    /**
     * Index method
     *
     * @return void
     * @SWG\Get(
     *      path="/articles",
     *      description="記事一覧",
     *      produces={"application/json"},
     *      @SWG\Parameter(
     *          description="検索文字列",
     *          in="path",
     *          name="q",
     *          required=false,
     *          type="string"
     *      ),
     *      @SWG\Response(
     *          response=200,
     *          description="取得成功",
     *          @SWG\Schema(
     *              @SWG\Property(
     *                  property="status",
     *                  type="boolean",
     *                  description="処理結果",
     *              ),
     *              @SWG\Property(
     *                  property="data",
     *                  type="array",
     *                  @SWG\Items(ref="#/definitions/IndexArticle"),
     *                  description="記事",
     *              ),
     *              @SWG\Property(
     *                  property="pagination",
     *                  ref="#/definitions/PageInfo",
     *                  description="ページ情報",
     *              ),
     *          )
     *      ),
     *      @SWG\Response(
     *          response="401",
     *          description="Unauthorized"
     *      ),
     * )
     *
     * @SWG\Definition(
     *      definition="IndexArticle",
     *      allOf={
     *          @SWG\Schema(ref="#/definitions/Article"),
     *      },
     *      @SWG\Property(
     *          property="user",
     *          description="ユーザ情報",
     *          ref="#/definitions/User"
     *      )
     * )
     */
    public function index()
    {
    	...
    }

swagger-phpのインストール

APIの定義の記述が終わったら、記述したアノテーションを解析するツール swagger-php をインストールしてSpecを生成します。composerでインストールできるので、いつものようにこんな感じですね。

$ composer require --dev zircote/swagger-php

Specファイルの生成

アプリケーションのルートで以下の様な感じでswagger-phpを実行します。-o で出力先のディレクトリを指定できるので適宜環境に合わせて指定してください。今回は、Specファイルは後述のswagger-uiで参照しますので、ブラウザからアクセスできる必要があります。

$ ./app/vendor/bin/swagger ./app/src/ -o ./app/webroot/api-documents/

swagger-uiのインストール

Specファイルを解析して、APIドキュメントとして表示する swagger-ui は以下で公開されています。

https://github.com/swagger-api/swagger-ui

このリポジトリの `dist` ディレクトリの内容をブラウザからアクセスできる場所に適宜コピーしてください。

swagger-uiの設定を修正

設置したswagger-ui内のindex.htmlの以下の箇所を、先ほど作成したSpecファイルを参照するように修正します。

var url = window.location.search.match(/url=([^&]+)/);
if (url && url.length > 1) {
  url = decodeURIComponent(url[1]);
} else {
  url = "http://petstore.swagger.io/v2/swagger.json"; // ここを先ほど作成したSpecファイルのURLに修正
}

問題なく生成されれば以下のように、APIドキュメントが参照できます。



f:id:kaz_29:20160218112002p:plain

f:id:kaz_29:20160218112015p:plain

今回作成したファイルは gistで公開しましたので swagger-uiで実際にAPIドキュメントをみられます

まとめ

APIドキュメント作成は、なかなかに気の重い作業という人も多いと思いますがswaggerを使うとかなり手間を減らせる上に、実際のコード内に仕様を記述するため、APIが更新されたのにドキュメントが更新されないといった問題も防ぎやすくなると思います。また、CIに組み込んでリポジトリの更新時に自動生成・公開するようにすることも簡単にできますので、APIの実装が完了したらAPIドキュメントも自動で公開するみたいなこともできますね。

チームで効率良く開発を進めるには、必要なドキュメントをいかに効率良く作成するかがとても重要だと思うので是非swaggerを試してみてください!

ErrorTypeをNSErrorにキャストするとuserInfoが消えてしまう問題の対策

Swiftを使い始めてそろそろ4w位経ちますが、まだまだ細かく引っかかることが多くて若干発狂気味なわたなべです。

今開発中のアプリで使う為に、Swift2+Alamofire+PromiseKit3でアプリの基盤的なものを作っているのですが、ErrorTypeをNSErrorにキャストするとuserInfoが消えてしまう問題が発生したので今回とった対策のメモ

以下のようにPromiseKitから受け取る error は ErrorType(実態はNSError)なので、userInfoを参照できるようにNSError型の変数に代入するとuserInfoが消えてしまいます。

do {
    try RestAPI.post(data)
        .then { (response: Foo) -> Void in
            debugPrint(response)
        }.error { (error) -> Void in
            let err: NSError = error as NSError // これでerr.userInfo が消える...
        }
} catch let error as NSError {
    // Error
}

色々調べた結果以下のようにすることでNSErrorにキャスト出来るようです。

    ((error as Any) as! NSError).localizedDescription

毎回キャストするのは面倒なのでこんな extension を作って対応しました。(作ったってほどのものじゃないけど...)

extension ErrorType {
    func nserror() -> NSError {
        return ((self as Any) as! NSError)
    }
}

このextensionを使えばこんな感じにかけてちょっとスッキリしました。

do {
    try RestAPI.post(data)
        .then { (response: Foo) -> Void in
            debugPrint(response)
        }.error { (error) -> Void in
            debugPrint(error.nserror().userInfo)
        }
} catch let error as NSError {
    // Error
}

Swift2はまだまだはまることも多いですが、Objective-Cと比べて格段にコード量は減るし便利なことも沢山あるのでいい感じです。早く使いこなせるようにならないとねー。

参考

Swift 2標準ガイドブック

Swift 2標準ガイドブック

stackoverflow.com

Xcode7にしてちょっと困ったこと

アプデ作業がようやく落ち着いたので、Xcode7を入れて実験中のアプリを動かそうと思ったら、ハマったのでメモ

PromiseKitがコンパイルエラー

Swift2になった影響で、PromiseKitのコンパイルがエラーになってしまいす。現在対応中のようでCocoapodsには対応バージョンが無い様なので、対応済みブランチを使うようにPodfileを修正しました。

pod 'PromiseKit', :git => 'https://github.com/mxcl/PromiseKit.git', :branch => 'swift-2.0-minimal-changes'


執筆時点の最新バージョンは2.2.1です。近いうちに対応されたら普通に最新版にあげればいけると思います。

iOS9でHTTP通信をドメイン指定で許可する方法

実験アプリでは、外部のテストサイトにアクセスするんですが、iOS9で追加されたApp Transport Security(ATS)の影響で以下のようなエラーが出てアクセスできませんでした。

App Transport Security has blocked a cleartext HTTP (http://)  resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.

対応方法としてはATS自体を無効にする方法もあるのですが、以下のようにInfo.plistに設定することでドメイン指定で許可することができます。

f:id:kaz_29:20150926180152p:plain

example.comの部分をアクセス対象のFQDNに変えればOKです。

僕が作っているアプリでも細かな問題が起きて色々対応した(してる)んですが、メジャーバージョンが上がる時はなかなか大変ですね(^^;。

2015/9/28追記

スクショが切れちゃってたんでplistの該当部分を貼っときます。

<key>NSAppTransportSecurity</key>
<dict>
	<key>NSExceptionDomains</key>
	<dict>
		<key>labo.decr.jp</key>
		<dict>
			<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
			<true/>
		</dict>
	</dict>
</dict>

CakePHPでもChatOps

ここ2週間ほど、追い込みで自宅に引きこもっていたわたなべです。

ChatOps

去年の夏ころから弊社でもSlackを使っていて、いま開発中の案件でも開発中の様々なイベントがSlackに流れてくるようになっています。いま連携してるのはこんな感じ。

  • Github
  • Pivotal Tracker
  • Jenkins
  • Trello
  • Crashlytics

あとは、増井さんが作った「チャットで勤怠管理する「みやもとさん」」とかも使ってます。

blog.masuidrive.jp

アプリからもSlackへ

で、いま開発中のアプリはとあるスケジュールに従って自動的にインスタンスが立ち上がったり、落ちたり(破棄まで)する機能があります。この状態もSlackに流したいなーとおもって調べらた、PHPでSlackのIncoming WebHooksを使って連携させるライブラリが山ほどありました。

Community-built Integrations - Slack

どれか選んで使ってもよかったのですが、CakePHPのHttpSocketクラスを使えばサクッと実装できそうだったので、Pluginを作りました。

CakeSlack


READMEにも書きましたが、bootstrap.phpなりに設定を書いた上で、以下のような感じ。

<?php
App::uses('Slack', 'CakeSlack.Lib');

....

Slack::send('CakePHPからこんにちわ');

これで、以下のように簡単に連携できます。

f:id:kaz_29:20150319114452p:plain

Packagistにも登録したのでインストールは以下一発で。

$ composer require "kaz29/cake_slack":"dev-master"


簡単に連携はできますが、確かSlackにも流量制限(1秒1リクエスト?未確認です)があったはずなので大量に流れる可能性がある場合はキューに溜めるなりの工夫が必要かと思います。


ということでproduction環境に仕込んだこのプラグインが動くまで(あと30分)の待ち時間に久々の更新でした(^^;。

13:05追記

ということで無事起動して一安心(^^。

f:id:kaz_29:20150320081922p:plain

「CakePHPで学ぶ継続的インテグレーション」- CakePHPを使って継続的インテグレーションを実践するながれを解説した書籍が出版されます

一部の方には事前にお話していましたが、わたしも共著で執筆に参加した「CakePHPで学ぶ継続的インテグレーション」という、CakePHPを使って継続的インテグレーションを実践するながれを解説した書籍が9/19にインプレスから出版されます!

f:id:kaz_29:20140903081908j:plain

CakePHPで学ぶ継続的インテグレーション

CakePHPで学ぶ継続的インテグレーション

既にインプレスさんのサイトやAmazonにも掲載されています。まだ、書影が反映されていませんが、予約受付中です!是非ポチッとお願いします(^^。

今年頭頃のミーティングから約9ヶ月、途中本業が忙しくなかなか執筆がすすまなかったりもしましたが、なんとか書き上げることができました。今回、初めて僕自身の企画+執筆のとりまとめをすることになり、段取りが悪く、他の執筆陣や編集担当にはいろいろと心配や迷惑をかけましたm(__)m。

今回も、原稿のmarkdownはgithubで管理してたのですが、追い込みだった7月、8月はなかなかすごい状況でした。

f:id:kaz_29:20140902164813p:plain

執筆陣

  • 渡辺 一宏
  • 吉羽 龍太郎
  • 岸田 健一郎
  • 穴澤 康裕

私はともかくw、今回の執筆陣かなり豪華です。強力な執筆陣のおかげで、継続的インテグレーションやテストに関して、私自身、執筆を通してたくさん勉強させてもらいました!

目次

まだ多分どこにもでていませんが、目次はこんな感じ。

  • Chapter 1 概論
  • Chapter 2 導入
    • 2-1 バージョン管理
    • 2-2 テストの自動化
    • 2-3 インスペクションの自動化
    • 2-4 ドキュメント生成の自動化
    • 2-5 デプロイの自動化
    • 2-6 フィードバック
  • Chapter 3 使用ツール
  • Chapter 4 環境構築
    • 4-1 環境の説明
    • 4-2 環境設定
    • 4-3 サンプルアプリケーションの環境構築
  • Chapter 5 開発工程(1)
    • 5-1 開発の進め方
    • 5-2 ユーザーストーリーの定義
    • 5-3 機能実装
  • Chapter 6 開発工程(2)
  • Chapter 7 デプロイと運用
    • 7-1 デプロイ
    • 7-2 継続的な機能追加
    • 7-3 継続的なテスト実行
    • 7-4 ソースコード品質の維持

継続的インテグレーション自体の解説から始まり、使用する各種のツールの解説、実際の環境構築方法、ユニットテストの効率的な書き方、Behatを使った受入テスト、そして、実際に継続的インテグレーションサーバに全てを組込む方法の解説、はたまたデプロイの自動化方法の解説とかなり盛りだくさんな内容になっています。

私は、Chapter 1,2と3,7の一部を担当させて頂きました。

まとめ

今回も恒例(?)のように、脱稿直前にCakePHPとJenkinsのバージョンアップのおかげでスクリーンショット取り直すことになったり、composerのバージョンアップで挙動がおかしくなったりなどなどなどなど...。色々ひやひやすることもたくさんありましたが、最終的にかなり読み応えがある書籍に仕上がっているのではないかと思います。

私自身、2年くらい前に初めて継続的インテグレーションを始めたばかりときに持っていた疑問や、たくさんのはまりどころが、この書籍を読むことでスムースに理解、解決できるように...。との思いで執筆しました。これから継続的インテグレーションを始めようとしている人、検討している人、そして今の開発現場を少しでも改善したいと考えている人の手助けになったら嬉しいです。価格はちょっと高めですが、その価値はあると思います。是非、一度手に取って(そして是非購入して(^^;)みてください!

あと、もしチャンスがあればこの書籍を題材にハンズオンとか出来たら面白いかなと思ってます!興味のある方はお声掛けくださいー。

最後に、共著者の @ryuzeeさん、@sizuhikoさん、@hampomさん、編集の丸山さん、本当にお疲れさまでした!

CakePHPで学ぶ継続的インテグレーション

CakePHPで学ぶ継続的インテグレーション

あわせてどうぞ。

CakePHP2 実践入門 (WEB+DB PRESS plus)

CakePHP2 実践入門 (WEB+DB PRESS plus)

クラウドセキュリティ クラウド活用のためのリスクマネージメント入門

クラウドセキュリティ クラウド活用のためのリスクマネージメント入門