AWS CodeBuildを使ってSPAのフロント側を強制リロードする

# やりたいこと
- フロントエンド側をデプロイした後に、画面を開き続けてるユーザを強制的にリロードさせたい
- バージョン管理はできるだけ自動化したい

# 作ったもの
1. AWS CodeBuildのbuldspec.yml
    - 自動でバージョンを採番する
    - バージョンをビルドしたモジュールに埋め込む
    - 最新のバージョン情報をAWS S3にファイルとして保存する
1. バージョンを比較するAWS Lambda
1. 強制リロードするフロントエンド

[![Image from Gyazo](https://i.gyazo.com/302304d16e665505f60ea5a381b865db.png)](https://gyazo.com/302304d16e665505f60ea5a381b865db)

# AWS CodeBuildのbuldspec.yml
`${CODEBUILD_RESOLVED_SOURCE_VERSION}`でCodeBuildが把握しているソースのバージョンを取得できます。（[詳細](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/build-env-ref-env-vars.html)）
それを使って、`./src/version.js`を置換しています。
```yml
  build:
    commands:
      - sed -i -e "s/CODEBUILD_RESOLVED_SOURCE_VERSION/${CODEBUILD_RESOLVED_SOURCE_VERSION}/g" ./src/version.js
      - cat ./src/version.js
```

AWS S3に`${CODEBUILD_RESOLVED_SOURCE_VERSION}`の出力結果を配置しています。
```yml
  post_build:
    commands:
      - echo ${CODEBUILD_RESOLVED_SOURCE_VERSION} > version
      - aws s3 cp ./version s3://${S3_BUCKET_NAME}/ --acl bucket-owner-full-control
```

# バージョンを比較するAWS Lambda
S3とクエリで指定されたバージョンとの比較をしています。その結果を返します。
このLambdaはクライアント端末から実行されるので、外部アクセスを許可するようにします。
```python
    bucket = s3.Bucket(backet_name)
    obj = bucket.Object(OBJECT_KEY_NAME)
    response = obj.get()    
    body = response['Body'].read()

    if event['queryStringParameters']['version'] == body:
```

## 全文
```python
import json
import datetime
import boto3

# バケット名,オブジェクト名
STG_BUCKET_NAME = 'frontend-stg-source'
PRO_BUCKET_NAME = 'frontend-pro-source'
OBJECT_KEY_NAME = 'version'

s3 = boto3.resource('s3')

def lambda_handler(event, context):
    print(event['queryStringParameters'])
    bucket_name = ''
    
    # バリデーションチェック
    if not 'queryStringParameters' in event.keys():
        return {
            'statusCode': 400,
            'body': 'not parameter key'
        }

    # 環境を指定されているか確認
    if not 'env' in event['queryStringParameters'].keys():
        return {
            'statusCode': 400,
            'body': 'not env parameter key'
        }
    
    # パラメータチェック
    if not 'version' in event['queryStringParameters'].keys():
        return {
            'statusCode': 400,
            'body': 'not version parameter key'
        }
    
    # 環境別にバケット名が違うので、バケット名を取得する
    if event['queryStringParameters']['env'] == 'staging':
        backet_name = STG_BUCKET_NAME
    elif event['queryStringParameters']['env'] == 'production':
        backet_name = PRO_BUCKET_NAME
    else:
        # 環境指定が間違っているのでエラー
        return {
            'statusCode': 400,
            'body': 'not expect env parameter key'
        }

    # S3からデータを取得します
    bucket = s3.Bucket(backet_name)
    obj = bucket.Object(OBJECT_KEY_NAME)
    
    response = obj.get()    
    body = response['Body'].read()
    body = body.decode('utf-8')
    body = body.strip()
    
    print({'client': event['queryStringParameters']['version'], 's3': body})
    if event['queryStringParameters']['version'] == body:
        return {
            'statusCode': 200,
            'body': json.dumps( "OK" )
        }
    else:
        return {
            'statusCode': 202,
            'body': json.dumps( "NG" )
        }
```

# 1. 最新バージョン出なかったらリロードするフロントエンド
以下は「URLが変わったら」という意味です。つまりページ遷移したらバージョンチェック処理が実行されます
```typescript
watch(() => route, function (to, from) {
```

リクエストの結果がNGの場合はリロードする処理です。
```typescript
    if (res.data == 'NG') location.href = location.href
```

## 全文
```HTML
<script lang="ts" setup>
import {watch} from 'vue'
import {useRoute} from "vue-router";
import * as Version from "@/version"
import axios from 'axios'
const route = useRoute()
watch(() => route, function (to, from) {
  const request = axios.create({
    timeout: 9000
  })

  request({
    url: 'https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.lambda-url.ap-northeast-1.on.aws',
    method: "get",
    params: {
      version: Version.Version,
      env: import.meta.env.VITE_ENV
    }
  })
  .then((res) => {
    console.log("バージョン確認：" + res.data)
    if (res.data == 'NG') location.href = location.href
  })
  .catch((error) => {
    console.log(error)
  })
}, {deep: true})
</script>
```

# ちなみに。version.jsの中身
以下一文だけです。CODEBUILD_RESOLVED_SOURCE_VERSIONはbuildspec.ymlでsedを使って書き換えられます。importしたjavascriptで定義済みの定数が使えます。
```javascript
export const Version = "CODEBUILD_RESOLVED_SOURCE_VERSION"
```

---
これで、CodeBuildを使ってデブロイされたら、自動でソースバージョンが保管されて、ページ遷移するたびに呼ばれるチェック処理によって、NGの場合は自動更新されるようになりました。