ConohaちゃんのオブジェクトストレージのデータをPythonで出し挿れしたメモ

![main_conoha_type3.png](https://qiita-image-store.s3.amazonaws.com/0/246065/12ee9b03-e548-f320-b2eb-81bff1cdc887.png)

このはちゃん可愛い！！

# ディスク拡張とオブジェクトストレージ

[Conoha](https://www.conoha.jp/referral/?token=JTNdgxS78hCrYVET7jiJ9y_THVTbcmQ_5dh.6MgJWxD5HHvtYps-BD7)のVPSプランは色々あるんですが、どのプランでもディスク容量は**50G**です。遊び用として使うには十分な容量なんですが、画像とか動画を配信したいな～って思うと全く足りませんよね。[Conoha](https://www.conoha.jp/referral/?token=JTNdgxS78hCrYVET7jiJ9y_THVTbcmQ_5dh.6MgJWxD5HHvtYps-BD7)は**スケーラブル**なVPSなので、あとから簡単に拡張出来ます。早速SSDを拡張しましょう

|  プラン |  月額  | 1時間あたり|
| ---- | ---- | ---- |
|  200G  |  2,500円 | 3.5円 | 
|  500G  |  4,500円 | 6.3円 | 

面倒くさいことが嫌いな方、上記の値段と容量に満足できる方、お金で解決することを厭わない方は素直にディスク拡張しておきましょう。単純に**楽**です。

さて、オブジェクトストレージのプランを見てみましょう...

|  プラン |  月額  | 1時間あたり|
| ---- | ---- | ---- |
|  100GB毎  |  450円 | 0.7円　| 

## 安い！

この値段と容量にあっさり食いついた私でしたが、めっちゃ大変だったので備忘録を残しておきます...ｗ

# オブジェクトストレージってなんなの？

## 早見表

| 名前 | アクセス方法 | お値段| 難易度 | FTP転送 |
| :------| :------ | :------: | :------: | :------: |
| ディスク拡張 | ローカルアクセス| 高い | 簡単 | ○ |
| オブジェクトストレージ | REST_API([OpenStack](https://developer.openstack.org/api-ref/object-store/)準拠) | 安い | 難しい | △([要専用ソフト](https://cyberduck.io/)) |

**AWS**触ったことがある人なら、**バケット**と言えば分かると思います。
簡単に言ってしまうと凄く高機能な**クラウドストレージ**ですね。何をするにも**API**を使ってアクセスするので、言語、環境、場所を問わずにデータアクセスが出来ます。

正直SSDの拡張とは何から何まで**全く違う**ので、無理っぽいな...って思ったら辞めておいたほうが良いです。以後はそれでも[Conoha](https://www.conoha.jp/referral/?token=JTNdgxS78hCrYVET7jiJ9y_THVTbcmQ_5dh.6MgJWxD5HHvtYps-BD7)ちゃんのオブジェクトストレージとダンスしたい**紳士**のための内容になっています。

# 前提条件

- [Conoha](https://www.conoha.jp/referral/?token=JTNdgxS78hCrYVET7jiJ9y_THVTbcmQ_5dh.6MgJWxD5HHvtYps-BD7)オブジェクトストレージを契約していること

# 今回使う言語とかツールとか環境とか

- CentOS7
- python3.7
- python-swiftclient

# やること

1. マニュアルを見ながらやろう
1. python-swiftclientのインストール
    - `pip install python-swiftclient`
    - 環境変数の設定
1. pythonでオブジェクトストレージを操作する
    - アクセストークンの取得
    - metadataの追加、削除
        - `X-Container-Meta-Access-Control-Allow-Origin`
    - コンテナの取得
    - オブジェクトの取得

# マニュアルを見ながらやろう


conohaはドキュメントが少ないことで有名です


ドキュメントが用意されているので、それらを参照して作業を進めていきましょう。`swift`コマンドのインストール、オブジェクトストレージへのアクセスまではドキュメントを見れば出来るはずです。

**下手にググると**古い情報が出てきて余計混乱する可能性があります(私のこと)。なるべく公式が用意しているものを参考にしましょう。OpenStackに準拠しているので、そちらのドキュメントも役に立ちます。

## apiユーザ名、パスワード、エンドポイントなど

[ログイン](https://www.conoha.jp/referral/?token=JTNdgxS78hCrYVET7jiJ9y_THVTbcmQ_5dh.6MgJWxD5HHvtYps-BD7)後、**API**タブをクリック！

## Conohaちゃん公式ドキュメント
- [ConoHaのオブジェクトストレージについて(導入編)](https://www.conoha.jp/guide/objectstorage.php)
- [オブジェクトストレージを使う(Cyberduck編)](https://www.conoha.jp/guide/objectstoragecyberduck.php)
- [オブジェクトストレージを使う(RESTful API編)](https://www.conoha.jp/guide/objectstoragerestapi.php)
- [オブジェクトストレージを使う(swiftコマンド編)](https://www.conoha.jp/guide/objectstorageswift.php)
- [オブジェクトストレージを使う(SDK+応用編)](https://www.conoha.jp/guide/objectstoragesdk.php)
- [ConoHa API Documantation](https://www.conoha.jp/docs/)
- [旧ドキュメント](https://www.conoha.jp/guide/gen1/)
    - 上のドキュメントには乗っていない情報がかなりあります。古いものなので今は使えない機能もあるのかもしれませんが、参考程度に。

## OpenStack公式ドキュメント

- [Object Storage API](https://developer.openstack.org/api-ref/object-store/)
- [CORS](https://docs.openstack.org/swift/latest/cors.html)
    - `X-Container-Meta-Access-Control-Allow-Origin` ←超大事

## MDN

ajaxとかCORSとかあやふやな方は読んでおくと良いです。preflightって美味いの？

- [オリジン間リソース共有 (CORS)](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS)

# python-swiftclientのインストール

```bash
pip install python-swiftclient
```
環境によっては`pip`だとpython2系の方にインストールされるかもしれません。その場合は`pip3`とかを試すと良いです。

# 環境変数の設定

オブジェクトストレージはオンライン上にあるので、当然認証情報が必要です。必要な情報は[conoha](https://www.conoha.jp/referral/?token=JTNdgxS78hCrYVET7jiJ9y_THVTbcmQ_5dh.6MgJWxD5HHvtYps-BD7)にログイン後、APIの設定画面を開くと書いてあります。

以下の設定は`~/.bashrc`に追記して下さい。

```bash

# export OS_AUTH_URL='[Identity Service]'
export OS_AUTH_URL='https://identity.tyo2.conoha.io/v2.0'
# export OS_TENANT_NAME='[テナント名]'
export OS_TENANT_NAME='gnct12345678'
# export OS_USERNAME='[ユーザ名]'
export OS_USERNAME='gncu12345678'
# export OS_PASSWORD='[APIユーザー作成時に設定したパスワード]'
export OS_PASSWORD='conohachanpassword'
```
追記できたら保存して再ログインもしくは以下のコマンドで再読込します。

```bash
source ~/.bashrc
```

## 確認

```bash
[kiwi@edge ~]$ swift stat
                        Account: nc_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
                     Containers: 2
                        Objects: 6016
                          Bytes: 17443618515
Containers in policy "policy-0": 2
   Objects in policy "policy-0": 6016
     Bytes in policy "policy-0": 17443618515
               Meta Quota-Bytes: 107374182400
                    X-Timestamp: 1530874927.30163
                   Content-Type: text/plain; charset=utf-8
                  Accept-Ranges: bytes
                     X-Trans-Id: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

インストール、環境変数の設定が出来ていれば`swift stat`コマンドが実行出来るようになるはず...

# pythonでオブジェクトストレージを操作する

以下のクラスは私が個人で使用するために書いたやつです。
外部ライブラリを利用しているので、使用する場合は以下のコマンドで`requests`をインストールして下さい。

```bash
pip install requests
```

```python:storage.py
from requests import post
from requests import get
from json import loads
from os.path import join
from os import chmod
from subprocess import Popen, PIPE


class RestApi(object):
    def __init__(self):
        self.auth: dict = {
            'tenant_name': 'gnct12345678',
            'tenant_id': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
            'auth_url': 'https://identity.tyo2.conoha.io/v2.0/',
            'endpoint_url': 'https://object-storage.tyo2.conoha.io/v1/nc_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/',
            'password_credentials': {
                'username': 'gncu12345678',
                'password': 'conohachanpassword',
            },
            'tempurl_key': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
        }

    def fetch_token(self) -> str:
        """
        apiアクセスにはtoken取得が必要なので、事前にこの関数を実行して返り値を貰う必要があります。
        """
        url: str = join(self.auth['auth_url'], 'tokens')
        data: dict = {
            "auth": {
                'passwordCredentials': self.auth['password_credentials'],
                'tenantId': self.auth['tenant_id'],
            }
        }
        r = post(url=url, json=data)
        if r.status_code == 200:
            return loads(r.text, encoding='utf-8')['access']['token']['id']
        else:
            raise AuthorizedError()

    def fetch(self, container: str = '', obj: str = ''):
        """
        container, objが共に空なら、オブジェクトストレージ全体の情報を返します。
        objのみが空なら対象オブジェクト内に存在するファイルをdictで返します。
        container, objに文字列が渡されていれば、対象objectファイルのバイナリを返却します。
        """
        url: str = join(self.auth['endpoint_url'], container, obj)
        payload: dict = {
            'X-Auth-Token': self.fetch_token(),
            'Accept': 'application/json',
        }

        r = get(url=url, headers=payload)
        if r.status_code == 200:
            if obj:
                return r.content
            else:
                return loads(r.text, encoding='utf-8')
        else:
            raise AuthorizedError()

    def fetch_container(self) -> iter:
        r = self.fetch()
        return (i['name'] for i in r)

    def create_tempurl(self, container: str, obj: str) -> str:
        method: str = 'GET'
        expires: str = "300"
        path: str = join(self.auth['endpoint_url'], container, obj)
        key: str = self.auth['tempurl_key']
        command: list = f'swift tempurl {method} {expires} {path} {key}'.split(
            ' ')
        std = Popen(command, shell=False, encoding='utf-8',
                    stdout=PIPE, stderr=PIPE)
        outs, _ = std.communicate()
        return outs

    def register_for_metadata(self, payload: dict, container: str = "", obj: str = ""):
        url: str = join(self.auth['endpoint_url'], container, obj)
        r = post(url=url, headers=payload)
        return r.status_code, r.headers, r.text

    def remove_for_metadata(self, metaname: str, container: str = "", obj: str = ""):
        metaname = metaname.title()
        if metaname[:2] == 'X-':
            metaname = metaname[2:]
        url: str = join(self.auth['endpoint_url'], container, obj)
        meta = 'X-Remove-' + metaname
        payload: dict = {
            'X-Auth-Token': self.fetch_token(),
            meta: '',
        }
        r = post(url=url, headers=payload)
        return r.status_code, r.headers, r.text

    def write_token(self,
                    path: str = "./token.txt",
                    mode: str = 'wt',
                    encoding: str = 'utf-8'):
        token: str = self.fetch_token()
        try:
            with open(file=path, mode=mode, encoding=encoding) as fp:
                fp.write(token)
        except (IOError, OSError, TypeError):
            raise IOError
        else:
            chmod(path, 0o704)


class AuthorizedError(Exception):
    pass
```

以後、上のクラスを利用してオブジェクトストレージを操作します。認証情報などは各々```RestApi.auth```を書き換えて下さい。

## storage.pyの使い方

### アクセストークンを取得する

```python:test.py
from storage import RestApi


api = RestApi()
print(api.fetch_token())
```

### アクセストークンを`./token.txt`に保存する

```python:test.py
from storage import RestApi


api = RestApi()
api.write_token(path='./token.txt')
```

### `image`コンテナに`metadata`を追加する

```python:test.py
from storage import RestApi


api = RestApi()
origin: str = "https://www.kiwi-bird.xyz"
payload: dict = {
    'X-Auth-Token': api.fetch_token(),
    'Accept': 'application/json',
    'X-Container-Meta-Access-Control-Allow-Origin': origin, # CORS対策
}
api.register_for_metadata(payload=payload, container="image")
```

### `image`コンテナのmetaname`X-Container-Read`を削除する

```python:test.py
from storage import RestApi


api = RestApi()
metaname: str = 'X-Container-Read'
api.remove_for_metadata(metaname=metaname, container="image")
```

### `image`コンテナの`test.jpg`オブジェクトを取得する

```python:test.py
from storage import RestApi


api = RestApi()
print(api.fetch(container='image', obj='test.jpg'))
```

# 終わり

以上！すぐに忘れるので備忘録として残しました。[Conoha](https://www.conoha.jp/referral/?token=JTNdgxS78hCrYVET7jiJ9y_THVTbcmQ_5dh.6MgJWxD5HHvtYps-BD7)ちゃんLOVE