GoogleスプレッドシートAPIをサービスアカウント認証方式でNode.jsから利用する


Google系のAPIはOAuth認証、サービスアカウント、APIキーの利用の3パターンあると認識してますが、簡単な読み書きくらいのやりとりだとサービスアカウント利用は手ごろです。

> 参考
> [Google DriveのAPIをサービスアカウント認証方式でNode.jsから利用するメモ](https://qiita.com/n0bisuke/items/59607865040a63d1a640)

## 前提

以下ができてる前提になります。

- Google SheetsのAPIを許可してサービスアカウント作成
- 作成したサービスアカウントを対象のシートに権限付与
- サービスアカウントの鍵ファイルをJSON形式で取得

鍵ファイルをDLすると以下のような雰囲気の中身となります。

```json
{
	"type": "service_account",
	"project_id": "xxxxxxxx",
	"private_key_id": "xxxxx",
	"private_key": "-----BEGIN PRIVATE KEY----○-----END PRIVATE KEY-----\n",
	"client_email": "hogehoge@hogehoge.iam.gserviceaccount.com",
	"client_id": "1111111111111111111111111111",
	"auth_uri": "https://accounts.google.com/o/oauth2/auth",
	"token_uri": "https://oauth2.googleapis.com/token",
	"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
	"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/xxxxxxxxxxxxx"
}
```

↑はあくまでもサンプルです。

## とりあえず情報を取得するコピペサンプル

この場合必要ライブラリはgoogleapisのみです。

```bash
$ npm i googleapis
```

以下のコードで、サービスアカウントを追加してアクセスを許可したシートの`ID`と`範囲`を変更しつつ、鍵ファイルを`keyfile.json`として同一フォルダに置いておけば動くと思います。

```ss.js
'use strict';

const {google} = require('googleapis');

const KEYFILE_NAME = `keyfile.json`;
const KEYFILE_PATH = `./${KEYFILE_NAME}`;

const main = async () => {

    console.log(`--Google SpreadSheet認証`);
    let sheets = {};
    try {
        const auth = new google.auth.GoogleAuth({
            keyFile: KEYFILE_PATH,
            scopes: ['https://www.googleapis.com/auth/spreadsheets'],
        });
        sheets = google.sheets({version: 'v4', auth});
        console.log(`認証done--`);
    } catch (error) {
        console.log(error);
    }
    
    console.log(`--sheetアクセス`);
    try {
        const res = await sheets.spreadsheets.values.get({
            spreadsheetId: `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`, //シートのIDを指定
            range: `Form1!A:H`, //取得する範囲を指定
        });
        console.log(res.data);
    } catch (error) {
        console.log(error);
    }

}

main();
```

これで実行すると以下のように情報が取れると思います。

```bash
$ node ss.js
--Google SpreadSheet認証
認証done--
--sheetアクセス
--sheetアクセス
{
  range: 'Form1!A1:H104',
  majorDimension: 'ROWS',
  values: [
    [
      'hoge',
      'お名前',
      '所属',
      'GitHubのマイページURL',
      'GitHubに登録したメールアドレス',
      'ステータス',
      '追加team',
      '担任チェック'
    ],

・・・省略
```

## CIで動かしたいver

CIで動かす時にkeyfileを直接GitHubなどに載せるのはしたくないので環境変数に設定したいです。

ただ以下のようにGoogleAuthの関数がKeyFileの指定をしてくるので（これ別のオプションありますかね...?）

```js
const auth = new google.auth.GoogleAuth({
            keyFile: KEYFILE_PATH,
            scopes: ['https://www.googleapis.com/auth/spreadsheets'],
        });
```

環境変数から情報取得してファイルに書き出し -> 認証 -> 1番最後に消すという処理も加えてみました。

手元でも試したいのでdotenvも追加してます。

```bash
npm i dotenv
```

ローカルだと.envファイルに詰め込みつつ

```.env
GOOGLE_PRIVATE_KEY='{ "type": "service_account", ・・・省略
SHEET_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

実行する際にfs.writeFileSyncでファイルを作っています。

```js
'use strict';

const {google} = require('googleapis');

const fs = require('fs');

require('dotenv').config();

const KEYFILE_NAME = `keyfile.json`;
const KEYFILE_PATH = `./${KEYFILE_NAME}`;

const main = async () => {
    try {
        console.log(`--keyfile作成`);
        fs.writeFileSync(KEYFILE_NAME, process.env.GOOGLE_PRIVATE_KEY);
        console.log(`作成done--`);
    } catch (error) {
        console.log(error);
    }

    console.log(`--Google SpreadSheet認証`);
    let sheets = {};
    try {
        const auth = new google.auth.GoogleAuth({
            keyFile: KEYFILE_PATH,
            scopes: ['https://www.googleapis.com/auth/spreadsheets'],
        });
        sheets = google.sheets({version: 'v4', auth});
        console.log(`認証done--`);
    } catch (error) {
        console.log(error);
    }
    
    console.log(`--sheetアクセス`);
    try {
        const res = await sheets.spreadsheets.values.get({
            spreadsheetId: process.env.SHEET_ID,
            range: `Form1!A:H`,
        });
        console.log(res.data);

    } catch (error) {
        console.log(error);
    }

    console.log('---Keyファイル削除');
    try {
        fs.unlinkSync(KEYFILE_PATH);
        console.log('削除しました。--');
        return;
    } catch (error) {
        throw error;
    }
}

main();
```

これでCIでも動くはず...だったんですが、CircleCIだと何故かうまくいかずGitHub Actionsではうまくいきました。ファイルを作成して書き込みあたりの何かの不具合があるのかも...?