AWS Lambdaでkintoneアプリの簡易自動バックアップを作ってみた　その１（フォーム設計情報とレコードの取得）

[kintone](https://kintone.cybozu.co.jp/jp/)自体はアプリケーションを非常に簡単に作成できるのですが、そのバックアップ、となると有償の製品を購入するか、あるいは手動で実施する、というのが現状です。

そこで、有償のバックアップとまでは行かなくても、簡易的なバックアップの仕組みを作れないか、試してみました。

# ゴール
とりあえず以下の条件をゴールとします。

1. バックアップ対象は「フォームの設計」と「データ一式」
2. バックアップは自動で定期的に実施される
3. 必要であればリストアの仕組みも作る

# バックアップのための仕組み

・フォームの設計情報をバックアップ
　→[フォーム設計情報取得](https://developer.cybozu.io/hc/ja/articles/201941834-%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E8%A8%AD%E8%A8%88%E6%83%85%E5%A0%B1%E5%8F%96%E5%BE%97)
・データ一式のバックアップ
　→[第10回 kintone REST APIを利用したレコード取得](https://developer.cybozu.io/hc/ja/articles/203727120-%E7%AC%AC10%E5%9B%9E-kintone-REST-API%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%81%9F%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%E5%8F%96%E5%BE%97)

これらをAWS Lambda（Python 3）上から呼び出せるように実装します。リストアもjson形式で対応できそうなので、上記のページの通りの結果が得られればバックアップとしては問題なさそうです。

# 処理対象
こんな感じの備品在庫管理（既成品）を利用します。

![スクリーンショット 2018-01-28 16.28.42.png](https://qiita-image-store.s3.amazonaws.com/0/102769/f1f6942a-2336-7288-0e3a-daa6938034ca.png)

# 実装
## 前処理
色々と準備が必要なので、最初に実施します。

```python3
KINTONE_BASE_URL = os.environ['KINTONE_URL']
KINTONE_FORM_BASE_URL = os.environ['KINTONE_FORM_BASE_URL']
URL = KINTONE_BASE_URL.format(
    kintone_domain=os.environ['KINTONE_DOMAIN'],
    kintone_app=os.environ['KINTONE_APP']
)
FORM_URL = KINTONE_FORM_BASE_URL.format(
    kintone_domain=os.environ['KINTONE_DOMAIN'],
    kintone_app=os.environ['KINTONE_APP']
)
HEADERS_KEY = os.environ['KINTONE_HEADERS_KEY']
API_KEY = os.environ['KINTONE_API_KEY']

S3_BUCKET = os.environ['S3_BUCKET']
S3_OBJECT_PREFIX = os.environ['S3_OBJECT_PREFIX']

s3_client = boto3.client('s3')
```

## 全レコードの取得
こんな感じでQueryに何も指定しない全件取得を行います。

```python3
def get_all_records(headers):
    query = u''
    
    response_record = requests.get(URL + query , headers=headers)
    return json.loads(response_record.text)
```

## フォーム設計情報取得
フォームは前述のページを参考にこのような実装になります。

```python3
def get_form_info(headers):
    
    response_record = requests.get(FORM_URL, headers=headers)
    return json.loads(response_record.text)
```

## 取得したデータをS3にバックアップとして保持
botoライブラリを利用して、tmpフォルダに一時的に作成したファイルをS3にアップロードします。

```python3
def put_data_to_s3(contents):
    date = datetime.now()
    date_str = date.strftime("%Y%m%d%H%M%S")
    tmp_dir = "/tmp/"
    tmp_file = S3_OBJECT_PREFIX + "_" + date_str + ".json"

    with open(tmp_dir + tmp_file, 'w') as file:
        file.write(json.dumps(contents, ensure_ascii=False, indent=4, sort_keys=True, separators=(',', ': ')))

    s3_client.upload_file(tmp_dir + tmp_file, S3_BUCKET, tmp_file)

    return True
```

## 上記を呼び出し側メソッド
今まで作成したきた部品をつなぎ合わせます。それぞれレコード一覧とフォーム設計情報を一つのJSONファイルにまとめる様に実装しています。

```python3
def run(event, context):
    
    headers = {HEADERS_KEY: API_KEY}
    record_data = get_all_records(headers)
    records = record_data['records']

    form_data =get_form_info(headers)
    forms = form_data['properties']

    result = {
        "records": records,
        "properties": forms
    }

    return put_data_to_s3(result)
```

## （おまけ）環境変数はステージごとに切り替えるように実装
[クラメソさんの記事](https://dev.classmethod.jp/cloud/serverless-framework-conf-change/)を参考に、ステージごとに環境変数を切り替えられるようにしています。

```yaml:serverless.yml
service: aws-kintone-backup

provider:
  name: aws
  runtime: python3.6
  region: us-east-1
  stage: ${opt:stage, self:custom.defaultStage}
  environment:
    KINTONE_URL: https://{kintone_domain}/k/v1/records.json?app={kintone_app}
    KINTONE_FORM_BASE_URL: https://{kintone_domain}/k/v1/form.json?app={kintone_app}
    KINTONE_HEADERS_KEY: X-Cybozu-API-Token
custom:
  defaultStage: dev
  otherfile:
    environment:
      dev: ${file(./conf/dev.yml)}
      prd: ${file(./conf/prd.yml)}

functions:
  run:
    handler: handler.run
    environment:
      KINTONE_DOMAIN: ${self:custom.otherfile.environment.${self:provider.stage}.KINTONE_DOMAIN}
      KINTONE_API_KEY: ${self:custom.otherfile.environment.${self:provider.stage}.KINTONE_API_KEY}
      KINTONE_APP: ${self:custom.otherfile.environment.${self:provider.stage}.KINTONE_APP}
      S3_BUCKET: ${self:custom.otherfile.environment.${self:provider.stage}.S3_BUCKET}
      S3_OBJECT_PREFIX: ${self:custom.otherfile.environment.${self:provider.stage}.S3_OBJECT_PREFIX}
```

```yaml:dev.yml
KINTONE_DOMAIN: xxxxxx.cybozu.com
KINTONE_API_KEY: XXXXXXXXX
KINTONE_APP: XXX
S3_BUCKET: XXXXXX
S3_OBJECT_PREFIX: XXXXXX
```

slsコマンドでアップロードをする際に`--stage`オプションを指定する必要があります。（デフォルトは`dev`）

# 実行してみる
さて、それぞれ環境変数を指定して実行してみます。無事S3にファイルが出力されました。中身も無事出力されています。（コンテンツが長いからここには載せないけど）

![スクリーンショット 2018-02-05 22.08.06.png](https://qiita-image-store.s3.amazonaws.com/0/102769/44dee57e-b942-0140-82b5-0f8eca8116f3.png)

AWS Lambdaで開発しているので、あとはスケジューリングでもすれば自動起動は簡単ですね。

# 現状の課題
一応できましたが、いくつかすでにわかっている課題があります。

1. レコードの一括取得は一度に500件までしかできない。（kintoneの仕様）
2. レコードの一括登録は一度に100件までしかできない。（kintoneの仕様）
3. S3へアップロードしたファイルはずっと残ってしまう。（手動で削除しに行かないと後々ゴミになる）
4. リストアの仕組みがない

この辺を解消するような実装はまた後ほど。

# 成果物
ここまでの成果物は以下になります。

https://github.com/kojiisd/aws-kintone-backup/tree/v1.0

# まとめ
よくあるkintoneアプリケーションのバックアップの一部を自動化することができました。とはいえAPIとしてはプロセス管理やアプリケーションの一般的な設定まで取得するものがあるので、この辺を盛り込むと、どんどんとリッチなバックアップ処理になりそうです。その辺りはまた次にでも。