Dockerですか？Node.JSですか？ビルドスクリプトはJSですか？

# はじめに
Docker Toolboxがあるため、我々はDockerイメージをビルドする環境に特に制約を受けません。
会社支給のPCがLinuxではなく、macOSだろうと、Windowsだろうと、どこでもビルドできるのは良いことです。
ただ、Docker ToolboxでDockerイメージをビルドするには、Docker Machineを起動し、加えて環境変数を設定してあげる必要があります。

**たった2行を打つだけですが**、面倒なのです。

というわけで、ビルド用のBATファイルをよく書くようにしていたのですが(主環境はWindows PC)、BATだとLinuxでビルドできません。
LinuxではDocker Machineを起動する必要はなく、**たった1行を打つだけですが**、面倒なのです。

そこで、Node.JSを使った開発ならスクリプトもNode.JSで書こうと思った次第です。
結構気に入ったので、記事にしてみました🍣。

# 概要
通常のNode.JSプロジェクトの中に、`scripts`フォルダを作り、ビルド用のスクリプトはそっちに詰めることにしました。
今回はビルドスクリプトなので、`scripts/build.js`に書いていきます。

やることは単純です。
Docker Machineを起動して、環境変数を設定して、Dockerでビルドコマンドを実行するだけです🍣。

# ソース
説明はコメントに書くようにしています。
素晴らしいコーディングスタイルです。
…、え、少ないって？面倒くさがりですいません🍣。

```js:scripts/build.js
const spawn = require('child_process').spawn;
const meta = require('../package');

// 作業ディレクトリをパッケージのあるディレクトリにする。
process.chdir(__dirname);
process.chdir('..');

Promise.resolve()
  // Docker Machineがインストールされているか確認する。
  .then(() => (new Promise((resolve, reject) => {
    console.log('Check Docker Machine.')
    const machine = spawn('docker-machine', ['version']);
    machine.on('error', reject);
    machine.on('exit', () => {
      console.log('Docker Machine found.');
      return resolve();
    });
  })))
  // Docker Machineがインストールされている場合、Docker Machineを起動する。
  .then(() => (new Promise((resolve, reject) => {
    console.log('\nStart Docker Machine.');
    const machine = spawn('docker-machine', ['start'], {
      stdio: [null, process.stdout, process.stderr],
    });
    machine.on('error', reject);
    machine.on('exit', () => {
      return resolve();
    });
  })))
  // Dockerコマンドへ渡すための、Docker Machineの設定を取得する。
  .then(() => (new Promise((resolve, reject) => {
    console.log('\nGet configuration of Docker Machine.');
    const machine = spawn('docker-machine', ['config'], {
      stdio: [null, null, process.stderr],
    });
    let out = '';
    machine.stdout.on('data', (data) => { out += data; });
    machine.on('error', reject);
    machine.on('exit', (code) => {
      if (code !== 0) {
        return reject(new Error('Doccker Machine failed.'));
      }
      const config = out.split('\n').filter((item) => (item !== ''));
      return resolve(config);
    });
  })))
  // Docker Machineが見つからない場合のエラーを無視する。
  .catch((err) => {
    if (err.code === 'ENOENT') {
      return [];
    }
    throw err;
  })
  // 取得した設定を用いて、Dockerイメージをビルドする。
  .then((config) => (new Promise((resolve, reject) => {
    console.log('\nBuild Docker Image.');
    const machine = spawn('docker', [...config, 'build', '-t', `${meta.name}:${meta.version}`, '.'], {
      stdio: [null, process.stdout, process.stderr],
    });
    machine.on('error', reject);
    machine.on('exit', (code) => {
      if (code !== 0) {
        return reject(new Error('Docker failed.'));
      }
      return resolve(config);
    });
  })))
  .then(() => {
    console.log('\nBuild succeeded.\n')
    process.exit(0);
  })
  .catch((err) => {
    console.error(`\nBuild failed. Error: ${err}\n`);
    process.exit(1);
  });
```

スクリプトを簡単にキックできるように、`package.json`にも書いておきます🍣。

```json:package.json
{
  "name": "docker-building",
  "version": "1.0.0",
  "scripts": {
    "build": "node ./scripts/build"
  }
}
```

# 使い方
簡単です。
面倒なことを減らすためにしたので当然です。
コンソールで`npm run build`もしくは`node scripts/build.js`と叩くだけです。

まず、Docker Machineがインストールされている環境であれば、Docker Machineの起動と設定値の取得を行います。
続いて、Dockerコマンドを使用してイメージをビルドする感じになります🍣。

# 工夫
最初は、Docker Machineから環境変数を取得して、それをDockerコマンド時に設定する方法しか考えていなかったのですが、`docker-machine config`で取得した設定値を`docker`コマンドにたれ流せばよいことを知ったので、そちらを使っています。
また、Dockerodeなどのモジュールを使うことも考えましたが、`.dockerignore`が参照されないなど少し面倒だったので、コマンドを直接実行するようにしました。
その結果、追加で必要なモジュールがなくなり、面倒なことも減ったので、とてもよかったと思います🍣。

# さいごに
この記事で示したコードはWTFPL v2としておりますので、ご自由に使っていただいて大丈夫です。
一応、GitHub (https://github.com/leak4mk0/DockerBuilding) にもアップロードしております。
ただし、一切の責任を取りかねますのでご了承ください。
既にご存知かもしれませんが、私は大の面倒くさがりなのです。