ConoHa のオブジェクトストレージを php-opencloud で操作する (2017年版)


概要

ConoHaオブジェクトストレージを PHP から扱う方法を調べていたのですが、古い情報しか見付からず、動かすだけでも苦労したので、最低限こうすれば動いたよというのをここに書いておこうと思います。

こちらの記事によると、ConoHa のオブジェクトストレージは OpenStack で構成されていて、それを PHP から扱うには php-opencloud を使うのがおすすめだそうです。
ただ、この記事が書かれてから 2 年くらい経っていて、php-opencloud の使い方もだいぶ変わっていました。

新しくなった php-opencloud の使い方

OpenStack のインスタンス作成

元記事ではまず、OpenCloud\OpenStack のインスタンスを作成していますが、ネームスペースが変更になったようで、代わりに OpenStack\OpenStack を使います。
インスタンスの作り方も変わっていて、全てのパラメータをひとつの配列で渡す形になっていました。

vendor/php-opencloud/openstack/src/OpenStack.php
    /**
     * @param array    $options User-defined options
     *
     * $options['username']         = (string)            Your OpenStack username        [REQUIRED]
     *         ['password']         = (string)            Your OpenStack password        [REQUIRED]
     *         ['tenantId']         = (string)            Your tenant ID                 [REQUIRED if tenantName omitted]
     *         ['tenantName']       = (string)            Your tenant name               [REQUIRED if tenantId omitted]
     *         ['authUrl']          = (string)            The Keystone URL               [REQUIRED]
     *         ['debugLog']         = (bool)              Whether to enable HTTP logging [OPTIONAL]
     *         ['logger']           = (LoggerInterface)   Must set if debugLog is true   [OPTIONAL]
     *         ['messageFormatter'] = (MessageFormatter)  Must set if debugLog is true   [OPTIONAL]
     *         ['requestOptions']   = (array)             Guzzle Http request options    [OPTIONAL]
     *
     * @param Builder $builder
     */
    public function __construct(array $options = [], Builder $builder = null)

ConoHa の場合だと、次のようにしてインスタンスを作成します。

$options = [
    'authUrl'    => 'https://identity.tyo1.conoha.io/v2.0',  // Identity Service の URL
    'username'   => 'gncu****',  // API ユーザーのユーザー名
    'password'   => '****',      // API ユーザーのパスワード
    'tenantName' => 'gnct****',  // テナント情報のテナント名
    'region'     => 'tyo1',      // エンドポイントのリージョン
];

$client = new OpenStack\OpenStack($options);

この後、認証を行うための Identity Service や、オブジェクトストレージを扱うための Object Store Service を扱うためのインスタンスを作成するのですが、その際にもここで渡したオプションが利用されます。
コンストラクタのコメントには説明のない region を設定していますが、これは後に各サービスで使うためのものです。

認証

元記事では OpenCloud\OpenStack クラスの authenticate() メソッドを呼んでいますが、新しい OpenStack\OpenStack にそんなものはありません。
OpenStack での認証は Identity Service が担当しているのですが、サービスごとにクラスを分けたようです。

ConoHa が用意しているのは Identity Service v2 なので、ここでは OpenStack\Identity\v2\Service を使います。
OpenStack\Identity\v2\Service のインスタンスは、OpenStack\OpenStackidentityV2() というメソッドを使って取得します。

vendor/php-opencloud/openstack/src/OpenStack.php
    /**
     * Creates a new Identity v2 service.
     *
     * @param array $options Options that will be used in configuring the service.
     *
     * @return \OpenStack\Identity\v2\Service
     */
    public function identityV2(array $options = []): \OpenStack\Identity\v2\Service

引数に先ほどと同じくオプションを取りますが、先ほど設定したオプションが引き継がれるので、ここでは何も設定しません。

$identityService = $client->identityV2();

この OpenStack\Identity\v2\Serviceauthenticate() などの認証のためのメソッドを持っているようです。
元記事では自分で authenticate() を呼んでいましたが、他のサービスを使用する際に自動的に呼ばれるようだったので、ここでは省略します。

サービスを使う

元記事では CloudStack\OpenStackgetCatalog() メソッドを使ってサービスの一覧を取得していますが、これに対応するメソッドは見付けられませんでした。
サービスの一覧は POST /v2.0/tokens という API で取得するようなので、あるとしたら OpenStack\Identity\v2\Service クラスにありそうなのですが、authenticate() の中で直接この API を呼んでいるだけで、結果を返すようなメソッドは見付けられませんでした

vendor/php-opencloud/openstack/src/Identity/v2/Service.php
    public function authenticate(array $options = []): array
    {
        $definition = $this->api->postToken();

        $response = $this->execute($definition, array_intersect_key($options, $definition['params']));

        $token = $this->model(Token::class, $response);

        $serviceUrl = $this->model(Catalog::class, $response)->getServiceUrl(
            $options['catalogName'],
            $options['catalogType'],
            $options['region'],
            $options['urlType']
        );

        return [$token, $serviceUrl];
    }

Object Store Service を使う

元記事では Compute Service を使っていましたが、今回はオブジェクトストレージを使うのが目的なので、Object Store Service を使います。

先ほどと同じように、Object Store Service を扱うための OpenStack\ObjectStore\v1\Service クラスのインスタンスを取得するのですが、単に objectStoreV1() を呼んだだけではエラーになってしまいます。

NG
$objectStoreService = $client->objectStoreV1();

こうすると、vendor/php-opencloud/openstack/src/Identity/v3/Models/Token.php:L104

InvalidArgumentException
Either a user or token must be provided.

というメッセージの InvalidArgumentException が投げられてしまいます。どうやら v2 ではなく v3 の Identity Service が使われてしまっているようです。
そこで、どの Identity Service を使うかをオプションで指定します。

NG
$objectStoreService = $client->objectStoreV1([
    'identityService' => $identityService,
]);

ここで指定したオプションは、初めに OpenStack\OpenStack のインスタンスを作ったときのオプションとマージされて使用されます。

Identity Service を指定すると、認証が必要な際に、自動的に指定した Identity Service を使って認証を行ってくれるようです。
前述した authenticate() メソッドもここで呼ばれていました。

しかし、今度は vendor/php-opencloud/openstack/src/Identity/v2/Models/Catalog.php:L51

RuntimeException
Endpoint URL could not be found in the catalog for this service.
Name: swift
Type: object-store
Region: tyo1
URL type: publicURL

という内容の RuntimeException が投げられました。
これは objectStoreV1() メソッドでデフォルト値として指定されているカタログ名が、ConoHa が提供している Object Storage Service ではなく swift になっているために発生しています。

vendor/php-opencloud/openstack/src/OpenStack.php
    /**
     * Creates a new Object Store v1 service.
     *
     * @param array $options Options that will be used in configuring the service.
     *
     * @return \OpenStack\ObjectStore\v1\Service
     */
    public function objectStoreV1(array $options = []): \OpenStack\ObjectStore\v1\Service
    {
        $defaults = ['catalogName' => 'swift', 'catalogType' => 'object-store'];
        return $this->builder->createService('ObjectStore\\v1', array_merge($defaults, $options));
    }

これもオプションで指定できるので、指定します。

OK
$objectStoreService = $client->objectStoreV1([
    'identityService' => $identityService,
    'catalogName' => 'Object Storage Service',
]);

これで Object Store Service が使えるようになりました。

Object Store Service については公式ドキュメントに説明が載っているので、あとは悩まなくても使うことができそうです。

終わりに

今までの内容をまとめるとこんな感じになります。
Identity Service は OpenStack\OpenStack のインスタンスを作成する際にも指定できるので、もう少しスマートに書けるかもしれませんが、ひとまずこれで ConoHa のオブジェクトストレージにアクセスすることができました。

<?php

$options = [
    'authUrl'    => 'https://identity.tyo1.conoha.io/v2.0',  // Identity Service の URL
    'username'   => 'gncu****',  // API ユーザーのユーザー名
    'password'   => '****',      // API ユーザーのパスワード
    'tenantName' => 'gnct****',  // テナント情報のテナント名
    'region'     => 'tyo1',      // エンドポイントのリージョン
];

$client = new OpenStack\OpenStack($options);

$identityService = $client->identityV2();

$objectStoreService = $client->objectStoreV1([
    'identityService' => $identityService,
    'catalogName' => 'Object Storage Service',
]);


// コンテナ一覧の取得
$containers = $objectStoreService->listContainers();

foreach ($containers as $container) {
    var_dump($container);
}