トークン管理ツール「DevTools Manager」の開発とデプロイ手順（ポートフォリオ）

## なぜこのアプリを作成したか
以前アルバイトでGitLabのアクセストークン管理についてのissueを割り当てられました。内容としては、誰に属していてどのプロジェクトで使われているかを簡単に管理できるようにするタスクで、その際に作成したアプリケーションを、ポートフォリオ用にトークン管理ツール「DevTools Manager」として改良し、GCP（Google Cloud Platform）にデプロイしてみました。

### フロントエンドとバックエンドの統合
このトークン管理ツールにおいて、フロントエンドとバックエンドの統合をまず行いました。フロントエンドにはReactを使用し、バックエンドはGo言語で実装しました。このように、フロントエンドとバックエンドを明確に分けることで、各部分の開発とメンテナンスが容易になり、将来的な拡張や改善にも柔軟に対応できます。また、今後Reactにおいてはjestを使用したテストも記述したいと考えています。

### CORS対応
異なるオリジンからのリクエストを許可するためにCORSミドルウェアを実装しました。

以下のコードでCORS対応を行っています。

```go
// CORSミドルウェアの設定
func cors(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // オリジンを許可
        w.Header().Set("Access-Control-Allow-Origin", "*")
        // 許可するHTTPメソッド
        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
        // 許可するヘッダー
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")

        // OPTIONSメソッドのリクエストに対しては200 OKを返す
        if r.Method == "OPTIONS" {
            w.WriteHeader(http.StatusOK)
            return
        }

        // 次のハンドラを呼び出す
        next(w, r)
    }
}

// エンドポイントの設定にCORSミドルウェアを適用
http.HandleFunc("/api/tokens", cors(handleTokens))
http.HandleFunc("/api/upload", cors(handleUpload))
```

このミドルウェアは、すべてのHTTPリクエストに対してCORSヘッダーを追加し、OPTIONSリクエストに対して適切なレスポンスを返すことで、CORSエラーを防ぎました。

## 使用した技術について
このプロジェクトで使用した技術は、React、Go、Docker、そしてGoogle Cloud Platform（GCP）です。Reactではコンポーネントベースで開発が出来ることにより、再利用可能なUIパーツを簡単に作成でき、開発効率が高いです。バックエンドにはGo言語を使用しました。Go言語はシンプルで効率的なプログラミングを可能にし、高いパフォーマンスを実現できるため、選択しました。

また、Dockerを使用することで、開発環境と本番環境の一貫性を保つことができました。Dockerイメージを作成し、これをGCPのGoogle Container Registryに保存することで、クラウド環境におけるデプロイメントがスムーズに行えます。GCPのCloud Runを利用してコンテナ化されたアプリケーションを迅速にデプロイできる点が大きな利点です。

## GCPで行った操作について
- プロジェクトの設定を行い、必要なAPIを有効化
- Cloud Buildを使用してDockerイメージをビルドし、Google Container Registryにプッシュ
- Cloud Runを使用してコンテナ化されたアプリケーションをデプロイ

### Cloud Buildの設定
Cloud Buildを使用して、アプリケーションのビルドとデプロイを自動化しました。以下は、cloudbuild.yamlファイルの内容です。

```yaml
steps:
  # Dockerイメージをビルドするステップ
  - name: "gcr.io/cloud-builders/docker"
    args:
      [
        "build",  # Dockerイメージをビルド
        "-t", "gcr.io/${_PROJECT_ID}/${_IMAGE_NAME}:latest",  # タグを付ける
        "-f", "Dockerfile",  # Dockerfileを指定
        ".",
      ]

  # ビルドしたDockerイメージをGoogle Container Registryにプッシュするステップ
  - name: "gcr.io/cloud-builders/docker"
    args: ["push", "gcr.io/${_PROJECT_ID}/${_IMAGE_NAME}:latest"]

  # Cloud Runにデプロイするステップ
  - name: "gcr.io/cloud-builders/gcloud"
    args:
      [
        "run", "deploy", "${_IMAGE_NAME}",  # Cloud Runにデプロイ
        "--image", "gcr.io/${_PROJECT_ID}/${_IMAGE_NAME}:latest",  # 使用するイメージを指定
        "--platform", "managed",
        "--region", "${_REGION}",
        "--allow-unauthenticated",  # 認証なしでアクセスを許可
      ]

images:
  - "gcr.io/${_PROJECT_ID}/${_IMAGE_NAME}:latest"
```
この設定ファイルにより、Cloud BuildがDockerイメージのビルド、プッシュ、およびCloud Runへのデプロイを自動で行います。

### Dockerファイル
Dockerファイルでは、フロントエンドとバックエンドを別々のステージでビルドし、最終的に統合しています。

```dockerfile
# フロントエンドのビルドステージ
FROM node:latest AS build-frontend
WORKDIR /app
ENV CI=true
COPY frontend/package.json frontend/package-lock.json ./  # 依存関係ファイルをコピー
RUN npm install  # 依存関係をインストール
COPY frontend/ ./  # フロントエンドのソースコードをコピー
RUN npm run build  # フロントエンドをビルド

# バックエンドのビルドステージ
FROM golang:1.21.6-alpine3.19 AS build-backend
WORKDIR /app
COPY backend/go.mod backend/go.sum ./  # Goモジュールファイルをコピー
RUN go mod download  # 依存関係をダウンロード
COPY backend/ ./  # バックエンドのソースコードをコピー
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main ./main.go  # バイナリをビルド

# 最終ステージ
FROM alpine:latest
WORKDIR /root/
COPY --from=build-frontend /app/build ./frontend  # フロントエンドのビルド成果物をコピー
COPY --from=build-backend /app/main .  # バックエンドのバイナリをコピー
EXPOSE 8080  # ポート8080を公開
CMD ["./main"]  # アプリケーションを起動
```

### IAMポリシーの設定
Cloud BuildがCloud Runにデプロイできるように、必要なIAMポリシーを設定します。以下のコマンドは、Cloud Buildサービスアカウントに必要な権限を付与します。

```bash
# Cloud BuildがCloud Runにデプロイするための権限
gcloud projects add-iam-policy-binding [PROJECT_ID] --member=serviceAccount:[CLOUD_BUILD_SERVICE_ACCOUNT] --role=roles/run.admin

# Cloud Buildがサービスアカウントを操作するための権限
gcloud projects add-iam-policy-binding [PROJECT_ID] --member=serviceAccount:[CLOUD_BUILD_SERVICE_ACCOUNT] --role=roles/iam.serviceAccountUser

# Cloud BuildがGoogle Container Registryのイメージを参照するための権限
gcloud projects add-iam-policy-binding [PROJECT_ID] --member=serviceAccount:[CLOUD_BUILD_SERVICE_ACCOUNT] --role=roles/storage.objectViewer
```
ここで、`[PROJECT_ID]`はプロジェクトID、`[CLOUD_BUILD_SERVICE_ACCOUNT]`はCloud Buildサービスアカウントです。

- GCPプロジェクトを設定するコマンド:
```bash
gcloud config set project [PROJECT_ID]
```
- Cloud Buildを使用してビルドおよびデプロイするコマンド:
```bash
gcloud builds submit --config=cloudbuild.yaml --substitutions _PROJECT_ID="[PROJECT_ID]",_IMAGE_NAME="[IMAGE_NAME]",_REGION="[REGION]"
```

## 困った点とその解決方法
### CORSの問題
フロントエンドとバックエンドが異なるドメインに配置されたため、CORSエラーが発生しました。これを解決するために、`cors`ミドルウェアを導入し、適切なヘッダーを設定しました。

### CSVファイルの処理
CSVファイルの読み書き処理でエラーが発生することがありました。ファイルの存在チェックやエラーハンドリングを強化し、安定した処理を実現しました。

### デプロイの自動化
手動でのデプロイに手間がかかり、ミスが発生しやすかったです。Cloud BuildとCloud Runを使用して、デプロイの自動化を行いました。

## このアプリの使い方
![スクリーンショット 2024-07-03 120425.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3819690/a9c39098-48ec-0be7-2b68-4fbd338de4d6.png)![スクリーンショット 2024-07-03 120444.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3819690/cf1afbaf-e8ed-a320-bdf9-1af84cff2188.png)


### [トークン管理ツール](https://devtools-manager-wp754fjpdq-dt.a.run.app/)


1. **CSVファイルのアップロード:**
   アップロードするCSVファイルを選択し、「アップロード」ボタンをクリックします。

2. **トークンの追加:**
   フォームに必要な情報を入力し、「トークンを追加」ボタンをクリックします。

3. **トークンの更新:**
   プロジェクト名を入力し、「トークンを取得」ボタンをクリックします。フォームに表示されたトークンの情報を編集し、「トークンを更新」ボタンをクリックします。

4. **トークンの削除:**
   削除したいトークンの行番号を入力し、「トークンを削除」ボタンをクリックします。

5. **CSVファイルとして保存:**
   「CSVとして保存」ボタンをクリックし、ファイル名を入力して保存します。