3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

設定ファイルを読み込むライブラリnobushi-config

Last updated at Posted at 2017-01-21

npmにパッケージをたくさん公開することを今年の目標にしました。公開するからには使ってもらえるようにしたいので、Qiitaに記事を書くところまでを目標にしたいです。

まずは第1弾として設定ファイルを読み込むためのライブラリを公開したので、どんなライブラリなのかを解説します。

目的

Node.jsで作成しているWebアプリケーションで、設定ファイルをJSやJSONのような頭使う形式1ではなく、YAMLで書きたい。ファイル内にはデータベースのパスワードやJWTのsecretなど、他人に見られては困るものもある。暗号化すればいいけど、どの設定値が暗号化されていて、どの設定値はされていないのか、明確なルールを作りたい。暗号化には基本的に公開鍵暗号を使いたいが、環境によっては別の方法を使いたいこともあるので、変更できるようにしたい。あと環境変数も使いたい。

こんな要望を一つのパッケージで実現したかったので、作ってみました。

URL

npm: https://www.npmjs.com/package/nobushi-config
GitHub: https://github.com/kou64yama/nobushi-config

使い方

npmパッケージなので、下記のコマンドでインストールできます。

npm i -S nobushi-config

設定ファイルはYAMLかJSONで書きます。JSONはおまけで対応しているだけで、YAML使った方がいいと思います。

# config/default.yml

server:
    host: localhost
    port: 3000

datasource:
    url: postgresql://localhost:5432/sample
    username: sample
    password: secret

基本的な使い方は、nobushi-configをインポートするだけです。

// main.js
import config from 'nobushi-config';

console.log(config.server.host);    // => localhost

設定ファイルのルール

読み込み順序

設定ファイルは環境変数NODE_CONFIG_DIRで設定されたディレクトリか、設定されていなければプロセスの相対パスで./configにあるものを読み込みます。

default.yml${NODE_ENV}.yml(環境変数NODE_ENVが設定されていれば)の順でファイルを読み込みます(JSONの場合は拡張子を.jsonに読み替えてください)。同じキーがあれば、後から読み込んだもので上書きします。

オブジェクトの扱い

YAMLやJSONのオブジェクトは何重にもネストして記述することができますが、nobushi-config内部ではすべてフラットな構造に変換されます。

例えば、

server:
    host: localhost
    port: 3000

という設定は、nobushi-config内部では

server.host: localhost
server.port: 3000

と解釈されます。

インポートしたオブジェクトからアクセスする際には、このフラットな構造を元のネストされたオブジェクトに復元します。

この仕様は

foo:
    bar:
        baz:
            qux: foobarbazqux

みたいな設定を

foo.bar.baz.qux: foobarbazqux

と書けるようにしたかったからです。

配列の扱い

上記仕様により、配列の扱いには注意しなければなりません。

例えば

api:
    - https://api1.example.com
    - https://api2.example.com
    - https://api3.example.com

のような設定は

api.0: https://api1.example.com
api.1: https://api2.example.com
api.2: https://api3.example.com

と解釈されます。インポートした際もオブジェクトになっています。

console.log(config.api);
// {
//   '0': 'https://api1.example.com',
//   '1': 'https://api2.example.com',
//   '2': 'https://api3.example.com'
// }

${NODE_ENV}.ymlで上書きする際にも配列全体を上書きするようなことはできないので、

# config/default.yml
api:
    - https://api1.example.com
    - https://api2.example.com
    - https://api3.example.com
# config/production.yml
api:
    - https://api1.example.com/v2
    - https://api2.example.com/v2

というように書いても、3つ目の要素がなくなることはありません。

api:
    - https://api1.example.com/v2
    - https://api2.example.com/v2
    - https://api3.example.com

この配列に関する仕様は望ましいものではありませんので、今後のバージョンアップで変更する予定です。

暗号化

設定ファイルを公開鍵暗号方式で保護することができます。設定ファイルには公開鍵で暗号化した暗号文を記述し、実行環境では秘密鍵を用いて復号して読み取ります。公開鍵はその名の通り公開してしまってもいいため、暗号化された設定ファイルとともにGitHubなどに挙げてしまっても安心です。

秘密にしたい設定値を暗号化して設定ファイルに書くことになるわけですが、このとき、設定したいキーに接尾語.secureをつけます。例えばdatasource.passwordの暗号文はdatasource.password.secureに設定します。

datasource:
    url: postgresql://localhost:5432/sample_app
    username: sample_app
    password:
        secure: <Base64エンコードされた暗号文>

上述のオブジェクトの仕様により、次のように書くこともできます。

datasource:
    url: postgresql://localhost:5432/sample_app
    username: sample_app
    password.secure: <Base64エンコードされた暗号文>

この仕様はTravis CIのEncryption keysを参考にしています。

秘密鍵はBase64エンコードして環境変数NODE_CONFIG_PRIVATE_KEYに設定します。あとは通常の設定ファイルと同様にnobushi-configをインポートするだけです。

import config from 'nobushi-config';

console.log(config.datasource.password);    // => 復号された設定値

config/development.ymlに暗号化されていない開発用データベースのパスワード、config/production.ymlに暗号化された本番用のパスワード、というように使えます。

環境変数

設定値が文字列で${}で囲まれた文字列がある場合、環境変数を展開します。

server:
    port: ${PORT}
datasource:
    url: ${HEROKU_POSTGRESQL_CRIMSON_URL}
message: Hello, ${USER}

middlewareとfilter

暗号化と環境変数の動作はmiddlewareとfilterという仕組みにより実現しています。筆者の勤務先では、データベースのパスワードやJWTのsecretなどを安全にテスト環境や本番環境に配信する仕組みがあり、公開鍵暗号方式ではなくこちらを使いたいこともあります(安全に配信できる仕組み自体はどのみち公開鍵暗号ですが)。そのような場合には独自にmiddlewareやfilterを定義することもできます。この件は長くなりそうなので、別の機会に書くことにします。

今後の展開

まずは配列に関する奇妙な仕様を変更したいです。設定値に配列があった場合、配列はフラットな構造にせず、配列の中にオブジェクトがある場合はさらにそのオブジェクトをフラットな構造にする、というようにすればいい感じになるのではと思っています。

また、コードはTypeScriptで書いているので、型ファイルもnpmにアップロードするようにしたいです。

  1. JSONだと「"」で囲まないととか余分な「,」をつけてはいけないとか、JSだと「module.exports」とか「export default」とか、とにかく儀式的なルールが多い。JSで設定を書けるようになっていると設定ファイルにロジックを書く人が出てくるのもよくない。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?