【heroku】環境変数から接続情報を取得しよう【MySQL】

# 更新情報

- 2022.04.29 : JawsDBについて、Heroku Postgresについて追記しました。

# 序文

[heroku](https://heroku.com)にて[cleardb](https://www.cleardb.com/)や[cloudinary](https://cloudinary.com/)などの付随サービスを利用する際のちょっとしたテクニックです。

# 手順

## herokuのアドオンについて

herokuでは、サードパーティ製のサービスをアドオンという形で組み込んで利用することができます。
例えば、今回ご紹介するこの2つのサービスです。いずれも無料プランがあります。
※(2022.04.29)Heroku Postgresを追加しました。

### ClearDB MySQL

MySQLのホスティングサービスです。ユーザー名は固定、作成できるデータベースは1つだけ、容量も5MBまで、バージョンが5.5というなかなか厳しい制限がありますが無料で使用できるIgniteという物騒な名前のプランがあります。

※(2022.04.29追記)現在はClearDBよりも[JawsDB](https://elements.heroku.com/addons/jawsdb)を使うのが個人的におすすめです。
その理由は拙作　[HerokuでMySQLを使うならJawsDBにしよう](https://qiita.com/haruyan_hopemucci/items/14e0dbeb0aedd85a74ee)　にて。

### Cloudinary

クラウドストレージサービスです。画像や映像のアップロードが可能。無料枠では容量100MB、月の転送量に制限があります。
Cloudinaryは単なるストレージとしての機能だけでなく、アップロードした画像をその場で加工できるそうです。
今回は画像加工機能は使っていません。

### Heroku Postgres

こちらはHeroku側が公式に用意しているPostgreSQLデータベースです。
上記ClearDB, JawsDBと同じようにアドオンとして追加します。
こちらにも無料プランがあり、上限は100,000レコード分となっています。
SSLによる接続に対応していますが、ハンドシェイクに使う証明書が**自己署名証明書**です。なんでや。

## herokuでのアドオン登録方法

DashboardのResoucesタブにAdd-onsという項目があります。
ここにある検索窓からお好みのサービスを検索し、サービスアイコンをクリックすると
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653626/dca4ecfc-3129-6f21-1cd7-0642213db92a.png)
登録をするかどうかのダイアログが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653626/7812285a-99f3-9940-823b-3d81aed2753e.png)
「注文フォームを送信する」をクリックすると登録完了です。簡単ですね。

なお、注文とか書いてありますが、無料プランを使う分には費用は掛かりません。

## アドオンの環境変数

アドオンを登録すると、Settingsタブの Config Vars 項目に当該サービスのURLがセットされます。
ページを表示した段階では非表示になっています。 Reveal Config Vars ボタンを押すと追加でセットした環境変数の一覧が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653626/f16e9160-e213-db1d-29c9-71b3941f87b6.png)
`登録したサービス名_URL` という分かりやすい名称でサービスURLが登録されているのが分かります。

## URLの構造

登録されているURLの構造は、このようになっています。

```text:cleardb

mysql://[ユーザー名]:[パスワード]@[MySQLのホスト名]/[サービスのインスタンス名]

```
```text:cloudinary

cloudinary://[APIキー]:[APIシークレット]@[Cloud Name]

```

ここからそれぞれの項目を抜出し、設定ファイル等に記載すれば各サーバにアクセスすることができます。
herokuからだけでなく、ローカルのPCからも接続できます。なのでローカル上で各アドオンサーバに接続できるかのテストも可能です。

## 環境変数を読み取る

ここで、サーバへの接続情報をソースに直書きしてしまうのは大いに危険です。ソースコードはいつ何時漏れるか分かりません。
うっかりpublicなgithubリポジトリにpushしちゃことだってあるわけです。

なので、ソースコードや設定ファイルなどのリソースには生の接続情報は記載せず、都度環境変数から接続情報を読み取るようにします。
今回はDjangoでの例ですが、どのフレームワークでも同様の操作で読取可能です。

### ClearDBの設定

```python:settings.py
# herokuのアドオンでcleadbを設定した場合、環境変数にURLが入っている。
# このURLからusername, password, host, database_name を正規表現で取り出す。
# そうするとソースに埋め込まなくてよい
cleardb_url = os.environ['CLEARDB_DATABASE_URL']
params = re.findall(r'mysql://(.*):(.*)@(.*)/(.*)\?.*',cleardb_url)[0]
# params = re.findall(r'mysql://(.*):(.*)@(.*\.cleadb\.com)/(.*)\?reconnect=true',cleardb_url)[0]
# print(params)
DATABASES = {
  'default': {
    'ENGINE': 'django.db.backends.mysql',
    'NAME': params[3],
    'USER': params[0],
    'PASSWORD': params[1], 
    'HOST': params[2],
    'PORT': '3306',
    'OPTIONS': {
        'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
    }
  }
}

```

やっていることは単純で、正規表現で各部のテキストを抜き出しているだけです。
区切りになる文字はユーザー名やパスワードに含まれることがないので楽に抽出できます。
開発環境でも同じ形式で`CLEARDB_DATABASE_URL=mysql://username:password@localhost/dbname`と入れておくと面倒がないです。

### JawsDBの設定

```python:setting.py
db_url = os.environ['JAWSDB_URL'] 
params = re.findall(r'mysql://(.*):(.*)@(.*):(.*)/(.*)',db_url)[0]
DATABASES = {
  'default': {
    'ENGINE': 'django.db.backends.mysql',
    'NAME': params[4],
    'USER': params[0],
    'PASSWORD': params[1], 
    'HOST': params[2],
    'PORT': params[3],
    'OPTIONS': {
        'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
    }
  }
}
```

JawsDBの場合はホスト名の部分にポート番号が入っています。そのためポート番号を抜き出すための正規表現を追加しています。またClearDBにあったクエリパラメータがJawsDBには存在しないので、その部分の正規表現は削除しています。

### Heroku Postgres　の設定

```python:setting.py
db_url = os.environ['DATABASE_URL'] 
params = re.findall(r'postgres://(.*):(.*)@(.*):(.*)/(.*)',db_url)[0]
DATABASES = {
  'default': {
    'ENGINE': 'django.db.backends.mysql',
    'NAME': params[4],
    'USER': params[0],
    'PASSWORD': params[1], 
    'HOST': params[2],
    'PORT': params[3],
    'OPTIONS': {
        'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
    }
  }
}
```

スキーマが"postgres"になっている以外はJawsDBと同じです。

### Cloudinaryの設定

```python:settings.py
# PRODUCTIONがYES（Heroku環境に手動で設定する）の場合のみに設定する
import re
if 'PRODUCTION' in os.environ and  os.environ['PRODUCTION'] == 'YES':
  DEFAULT_FILE_STORAGE = 'cloudinary_storage.storage.MediaCloudinaryStorage'
  # herokuのアドオンでcloudinaryを設定した場合、環境変数にURLが入っている。
  # このURLからcloud name, api-key, api-secretを正規表現で取り出す。
  # そうするとソースに埋め込まなくてよい
  cloudinary_url = os.environ['CLOUDINARY_URL']
  params = re.findall(r'cloudinary://(.*):(.*)@(.*)',cloudinary_url)[0]
  CLOUDINARY_STORAGE = {
      'CLOUD_NAME': params[2],
      'API_KEY': params[0],
      'API_SECRET': params[1],
  }
```
ClearDBの場合と同様に正規表現を用いて各種情報を抜き出しています。
また、ClearDBは環境変数`PRODUCTION`が`YES`に設定されているかどうかで本番環境、ローカル開発環境の区別を行っています。
ローカル環境ではCloudinaryを使用せず普通にローカルディスクに画像を保存させるので、設定する必要はありません。

以上の設定でコード中に接続情報を記載することなく、アドオン登録時に設定された環境変数のみで接続情報を取得する仕組みを作成できました。

## ローカル環境でのテスト

先ほどのheroku settings の Config Vars から接続情報の環境変数と `PRODUCTION=YES` をローカルのシェルへコピーし、実行するだけでアドオンのデータベース、ストレージに接続ができるかのテストが簡単に行えます。

# おわりに

ソースコードに直接データベースなどの接続情報を埋めてしまうとうっかり漏れた際に危険です。herokuでは登録したアドオンの接続情報がそのまま環境変数に入るので、うまく利用しましょう。