php-opencloudでConoHa APIを使う

この記事は[「ConoHa Advent Calendar2015」](http://qiita.com/advent-calendar/2015/conoha)の20日目の記事です。

19日目は[@wakadannacom](https://twitter.com/wakadannacom)さんの[「ConoHaにNutanix CEがインストールできるか検討だけしてみた」](http://qiita.com/wakadannacom/items/08fe4748ede67df2938e)
21日目は[@keita299](https://twitter.com/keika299)さんの[「ConoHa の メール API でアドレスの登録・一覧・削除をする。」](http://qiita.com/keika299/items/e7fe510b57b08170838b)
でした。

----------------------------

こんにちは、ひろのぶです( ´ ω ` )

先日Twitterでこんな発言をしてしまったので、ちょっとフォローアップなどを兼ねて、[php-opencloud](http://php-opencloud.com/)の導入編を書いてみたいと思います。基本的に本家のドキュメントに沿っていますので、自分で読んで実装できてしまう方は読む必要ないかもです。

<blockquote class="twitter-tweet" data-conversation="none" lang="ja"><p lang="ja" dir="ltr"><a href="https://twitter.com/aWebprogrammer">@aWebprogrammer</a> こんばんは。ConoHaでSDKはご用意していないのですが、中の人的にはphp-opencloudがおすすめです <a href="https://t.co/jyWT7fEK3d">https://t.co/jyWT7fEK3d</a></p>&mdash; Hironobu Saitoh (@hironobu_s) <a href="https://twitter.com/hironobu_s/status/678561726581706753">2015, 12月 20</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

# PHPでConoHa APIを使う

ConoHa APIはOpenStack + αな構成となっていて、要は単純なREST APIです。

[ConoHa APIドキュメント](https://www.conoha.jp/docs/index.html)

PHPはとにかくHTTPアクセスが簡単にできるので、SDKを用いなくてもAPI叩くのは簡単です。こういうの、ある意味すごい・・・。

```php
$body = file_get_contents('http://example.com/');
```

そして、ConoHa API(OpenStack APIも)は以下のようなワークフローになります。各サービス(Compute, Network, Storageなど)にアクセスする前にIdentityを使って認証を済ませ、トークンをあらかじめ取得しておく必要があります。そしてトークンは有効期限があり、その間はキャッシュして置いた方が効率よくAPIにアクセスできます。(詳しくは以下のスライド15ページを参照)

<img width="456" alt="api-workflow.png" src="https://qiita-image-store.s3.amazonaws.com/0/44152/4508ae70-0e99-d8cf-8a3d-c0b0d4e6cfd8.png">

[パブリッククラウドConoHaを使ってOpenStack APIを理解する](http://www.slideshare.net/hironobusaitoh/conohaopenstack-api)

SDKはこの辺のワークフローをうまく抽象化してくれます。トークンのキャッシュはある程度自分で書かないといけませんが、まぁそれでも簡単に書けます。

## php-opencloud 

php-opencloudは[Rackspace](http://www.rackspace.com/)が開発しているPHPのOpenStack SDKです。RackspaceはOpenStackベースのパブリッククラウドを展開している企業ですが、もともとOpenStackプロジェクトもRackspaceによって始められたものです。

<img width="321" alt="php-opencloud_by_rackspace.png" src="https://qiita-image-store.s3.amazonaws.com/0/44152/af0d46f3-d957-1e42-bdd5-a67564580dd8.png">

Rackspace APIはOpenStackを拡張したもので、php-opencloudもそれに対応したライブラリです。ですが、php-opencloudはその拡張部分を除いたピュアなOpenStackを扱うためのクラスも用意されています。オフィシャルのドキュメントは基本的にRackspace向けになっているので、読むときはその辺に注意する必要があります。

[php-opencloud](http://php-opencloud.com/)
[php-opencloud(GitHub)](https://github.com/rackspace/php-opencloud)

Rackspaceは個人でも使えるので、ConoHa以外のOpenStack環境を触ってみたい方は使ってみると良いと思います。(ちなみにアカウント作成すると英語で確認の電話がかかってきます。私は知らなかったのでアワアワしてしまいましたが、向こうのオペレーターもそういうの慣れてるようなので何とかなりました・・・)

# 使ってみる

では使ってみましょう。php-opencloudはPHP5.4以上と[composer](https://getcomposer.org/)が必要です。VPSなどでPHP環境を立てても良いのですが、今回はDockerでさくっとPHP環境を作りました。

```shell
mkdir php-opencloud && cd php-opencloud
docker pull php.5.6
docker run -v `pwd`:/php-opencloud -ti --rm --name php56 php:5.6 /bin/bash
```

コンテナに入ったらgitを追加でインストールしておきます

```shell
apt-get update
apt-get install -y git
```

そしてcomposerとphp-opencloudをインストールします

```shell
cd php-opencloud
curl -sS https://getcomposer.org/installer | php
./composer.phar require rackspace/php-opencloud
(結構時間かかる)
ls -la
total 1240
drwxrwxr-x  3 1000 1000    4096 Dec 23 05:49 .
drwxr-xr-x 53 root root    4096 Dec 23 05:47 ..
-rw-r--r--  1 root root      70 Dec 23 05:47 composer.json
-rw-r--r--  1 root root   11253 Dec 23 05:49 composer.lock
-rwxr-xr-x  1 root root 1238152 Dec 23 05:42 composer.phar
drwxr-xr-x  8 root root    4096 Dec 23 05:49 vendor
```

あとは、このディレクトリでスクリプトを書いていきます。

## 基本的なこと

本家ドキュメントにはRackspace setupとOpenStack setupと2つの方法が[載っています](http://docs.php-opencloud.com/en/latest/services/identity/index.html)。先ほど書いたとおり、php-opencloudは基本的にRackspace向けだからです。

ConoHaで使う場合は後者のOpenStack setupの内容で進めます。使うのはOpenStack\OpenStackで、あとは名前空間をuseしてOpenStackクラスをnewすればOKです。典型的な書き方は以下のようになります。

```php
<?php

use OpenCloud\OpenStack;
use Guzzle\Http\Exception\ClientErrorResponseException;

require_once __DIR__ . '/vendor/autoload.php';

$opts = [
    'username' => 'gncu********',
    'password' => 'password',
    'tenantName' => 'gnct*******',
];

try {
    $client = new OpenStack('https://identity.tyo1.conoha.io/v2.0', $opts);
	$client->authenticate();

	// ここに実装を書く

} catch(ClientErrorResponseException $ex) {
    // error
    echo $ex->getMessage();
}
```

$optsにはAPIな認証情報を書きます。これはConoHaのコントロールパネルの「API」メニューにあります。


## サービスを使う

先ほどのコードで$clientはあくまでREST APIを扱うためのクライアントなだけで、Compute, Network, Storageなどはまた別のオブジェクトになります。これらサービスの一覧(Catalog)を取得してみましょう。先ほどのコードの「ここに実装を書く」に下記を実装します。


```php
<?php

$catalog = $client->getCatalog();
foreach($catalog->getItems() as $item) {
    foreach($item->getEndpoints() as $endpoint) {
        echo sprintf("name:%s type:%s region:%s url:%s\n",
                     $item->getName(),
                     $item->getType(),
                     $endpoint->region,
                     $endpoint->publicURL);
    }
}
```

実行結果は以下のようになります(伏せ字の部分はTenantIDでユーザ毎に異なります)。このnameとtypeに注目して下さい。それぞれのサービスを使うにはこれらを指定してサービスオブジェクトを取得します。

```shell-session
name:Account Service type:account region:tyo1 url:https://account.tyo1.conoha.io/v1/****************************
name:Database Hosting Service type:databasehosting region:tyo1 url:https://database-hosting.tyo1.conoha.io/v1
name:Network Service type:network region:tyo1 url:https://networking.tyo1.conoha.io
name:Volume Service type:volumev2 region:tyo1 url:https://block-storage.tyo1.conoha.io/v2/****************************
name:Image Service type:image region:tyo1 url:https://image-service.tyo1.conoha.io
name:Compute Service type:compute region:tyo1 url:https://compute.tyo1.conoha.io/v2/****************************
name:Mail Hosting Service type:mailhosting region:tyo1 url:https://mail-hosting.tyo1.conoha.io/v1
name:DNS Service type:dns region:tyo1 url:https://dns-service.tyo1.conoha.io
name:Object Storage Service type:object-store region:tyo1 url:https://object-storage.tyo1.conoha.io/v1/nc_****************************
name:Identity Service type:identity region:sjc1 url:https://identity.sjc1.conoha.io/v2.0
name:Identity Service type:identity region:tyo1 url:https://identity.tyo1.conoha.io/v2.0
name:Identity Service type:identity region:sin1 url:https://identity.sin1.conoha.io/v2.0
```

## Compute Serviceを使う

サービスは色々あるのですが、わかりやすいところでComputeを使ってVPSを立ててみましょう(APIの説明するときの定番ですが)。[Compute Service](http://docs.php-opencloud.com/en/latest/services/compute/index.html)は、OpenStackの仮想マシンを扱うサービスで、ConoHaではVPSを扱うことになります。なお、ここで紹介する機能は一部で、詳細は[本家のドキュメント](http://docs.php-opencloud.com/en/latest/services/compute/index.html)をご覧下さい。

サービスオブジェクトは以下のように取得します。引数にnameとregionを渡します。サービスに対して何か実行する場合は全てこの$computeを介して行います。

```php
$compute = $client->computeService('Compute Service', 'tyo1');
```

### flavorを取得してみる

OpenStackでflavorというのは仮想マシンの定義を表します。CPUコア数、メモリ、ストレージなどですね。ConoHaではVPSのプランを指します。

以下のコードを先ほどの「ここに実装を書く」のところに挿入します。これだけです、SDKの便利なところですね。

```php
<?php
$compute = $client->computeService('Compute Service', 'tyo1');
foreach($compute->flavorList() as $img) {
    echo sprintf("id:%s name:%s\n", $img->id, $img->name);
}
```

実行結果は以下です。

```
id:294639c7-72ba-43a5-8ff2-513c8995b869 name:g-2gb
id:3aa001cd-95b6-46c9-a91e-e62d6f7f06a3 name:g-16gb
id:62e8fb4b-6a26-46cd-be13-e5bbf5614d15 name:g-4gb
id:7eea7469-0d85-4f82-8050-6ae742394681 name:g-1gb
id:965affd4-d9e8-4ffb-b9a9-624d63e2d83f name:g-8gb
id:a20905c6-3733-46c4-81cc-458c7dca1bae name:g-32gb
id:c2a97b05-1b4b-4038-bbcb-343201659279 name:g-64gb
```

### イメージ一覧を取得してみる

「イメージ」はOpenStackでは仮想マシンのディスクイメージのことで、ConoHaではOSのイメージやアプリケーションインストール済みイメージなど色々用意されています。基本的にflavorとメソッドが違うだけで同じような感じになります。

コード的には以下のようになります。flavorとメソッドが違うだけです。

```php
<?php
foreach($compute->imageList() as $img) {
    echo sprintf("id:%s name:%s\n", $img->id, $img->name);
}
```

実行結果は以下です

```shell
id:8f19ff24-3ff9-4beb-9603-d67dead1fc00 name:vmi-wp-kusanagi-centos-7
id:edf80c2c-fdf7-45ee-b625-9e18d40cedc6 name:vmi-zabbix-2.4-centos-7
id:e090aed7-5949-45ec-93f0-e735d8d0d29e name:s-abe-master
id:6b3a81fa-9ca1-4fb3-b000-e1495620a119 name:vmi-piwik-2.15-centos-6.7
id:7cab9247-1253-4bfd-8e4d-026fcd9168e3 name:vmi-drupal-8.0-centos-7
id:9f89b30b-09a4-4606-b27a-c33794559d51 name:ubuntu14-clean
id:238801ae-7256-453c-9133-5202aa39a621 name:vmi-opensuse-42.1-amd64
(以下略)
```

## 仮想マシン(VM)を作成する

では仮想マシンを作ってみます。ここはちょっと一手間必要になります。

[VM作成API](https://www.conoha.jp/docs/compute-create_vm.html)にはsecurity_groupsとadminPassという言うオプションがあるのですが、php-opencloudはこれらのパラメータに対応していません。なので、ここはphp-opencloudのOpenStack\Compute\Resource\Serverを継承したクラスを作成し、APIへのリクエストJSONに2つのパラメータを追加する必要があります。

継承したクラスは以下のようになります。プロパティを追加して、createJSON()をオーバーライドしてJSONオブジェクトにパラメータを追加します。adminPassは元のクラスにプロパティ定義はあるのですが、createJson()内では使用されていません。これはバグっぽいですね。

```php
class ConoHa extends Server {
    protected $security_groups = [];

    protected function createJson() {
        $server = parent::createJson();

        // adminPass
        if($this->getProperty('adminPass') != null) {
            $server->server->adminPass = $this->getProperty('adminPass');
        }

        // security_groups
        $security_groups = $this->getProperty('security_groups');
        if (count($security_groups) > 0) {
            $server->server->security_groups = $security_groups;
        }
        return $server;
    }
}
```

実際にVMを作成する部分のコードは以下です。ConoHaオブジェクトを作ってcreate()を呼びます。引数はVM作成のオプションで、一覧は[ドキュメント](http://docs.php-opencloud.com/en/latest/services/compute/servers.html)にあります。他によく使いそうなものでkeypairと言うオプションがあり、VMに設定するSSH公開鍵を指定するときに使います。

コード全文は長くなるので[Gist](https://gist.github.com/hironobu-s/eb9413ff01739acf4264)に置いてあります。

```php

$compute = $client->computeService('Compute Service', 'tyo1');
try {
    // ConoHaクラスを使わない場合は以下のようになる
    // $server = $compute->server()

    $server = new ConoHa($compute);

    $create_opts = [
        'name' => 'test-vps',
        'imageId' => 'edc9457e-e4a8-4974-8217-c254d215b460',
        'flavorId' => '7eea7469-0d85-4f82-8050-6ae742394681',
        'adminPass' => '***********',
        "security_groups" => [
            [
                "name" => "default",
            ],
            [
                "name" => "gncs-ipv4-all",
            ],
            [
                "name" => "gncs-ipv6-all",
            ]
        ],
    ];
    $resp = $server->create($create_opts);

} catch(BadResponseException $ex) {
    echo $ex->getResponse();
}

```

# トークンをキャッシュする

OpenStack APIは認証実行後にトークンが発行され、サービスにアクセスする際にそれが使われることは既に書きました。このトークンをファイルなどに書き出しておいて、2回目以降スクリプトを実行した際に使い回すことで認証をスキップすることができます。

OpenStackオブジェクト$clientにはexportCredentials()とimportCredentials()という2つのメソッドがあります。exportCredentials()はトークンを含む認証情報を書き出し、importCredentials()はその認証情報を読み込むときに使います。importCredentials()で読み込んだ認証情報が無効(トークンの有効期限切れなど)だった場合でも、SDKが自動的に再認証を行ってくれます。便利。

さて、実際にコードは以下のようになります。認証情報はPHPのstdClassなので、これをserialize()してファイルに書き出します。

```php
$opts = [
    'username' => 'gncu********',
    'password' => 'password',
    'tenantName' => 'gnct*******',
];

$client = new OpenStack('https://identity.tyo1.conoha.io/v2.0', $opts);

$cachefile = 'credentials.dat';
if (file_exists($cachefile)) {
    $str_credential = file_get_contents($cachefile);
    $credentials = unserialize($str_credential);
    if($credentials) {
        $client->importCredentials($credentials);
    }
}

$token = $client->getTokenObject();
if(!$token || $token->hasExpired()) {
    $client->authenticate();
    file_put_contents($cachefile, serialize($client->exportCredentials()));
}

```

# オブジェクトストレージを使う

そういえば昔このべんに書いたので、よかったらこちらもご覧下さい。認証部分は旧ConoHa向けの説明になっていますが、それ以外はそのまま使えます。

[ConoHaオブジェクトストレージを使ってみよう(SDK+応用編)](https://www.conoha.jp/conoben/archives/3487)

# おわりに

REST APIをそのまま叩いても良いのですが、SDKを使うとだいぶ楽できるのでどんどん使うと良いと思います(VM作成はちょっと大変でしたが、、、)。ただ、php-opencloudとConoHa API両方のドキュメントと睨めっこしながら作業するような感じになるので、まぁこれは頑張るしかないかなと。

PHPは利用者も多いですし、このドキュメントがお役に立ったら何よりです( ´ ω ` )