# 機能設計書 61-Terraformステート管理

## 概要

本ドキュメントは、GitLabにおけるTerraformステート管理機能の設計仕様を記載する。Terraform CLIからHTTPバックエンドとしてGitLabを使用し、インフラストラクチャの状態ファイル（tfstate）を安全に保存・管理する機能を提供する。

### 本機能の処理概要

Terraformステート管理機能は、Infrastructure as Code（IaC）ツールであるTerraformの状態ファイルをGitLabプロジェクト内で一元管理するための機能である。

**業務上の目的・背景**：Infrastructure as Codeの実践において、Terraformの状態ファイル（tfstate）は非常に重要なデータである。この状態ファイルには、インフラの現在の状態と管理対象リソースの情報が含まれており、チーム開発において共有・管理する必要がある。従来はS3やAzure Blob Storageなどの外部ストレージを別途用意する必要があったが、本機能によりGitLabをTerraformバックエンドとして直接使用でき、インフラ管理とソースコード管理を統合できる。

**機能の利用シーン**：
- チームでTerraformを使用したインフラ管理を行う場合のステート共有
- CI/CDパイプライン内でのTerraform実行時のステート読み書き
- インフラの変更履歴の追跡とバージョン管理
- 複数環境（dev、staging、production）のステート分離管理

**主要な処理内容**：
1. Terraformステートの取得（GET）- 指定された名前のステートファイル内容を返却
2. Terraformステートの作成・更新（POST）- 新規ステートの作成または既存ステートの更新
3. Terraformステートの削除（DELETE）- 指定された名前のステートを削除
4. ステートのロック（POST /lock）- 同時更新を防ぐためのロック機構
5. ステートのアンロック（DELETE /lock）- ロック状態の解除

**関連システム・外部連携**：Terraform CLIがHTTPバックエンドとしてGitLab APIを使用。CI/CDジョブトークンによる認証もサポート。

**権限による制御**：
- `read_terraform_state`権限：ステートの閲覧
- `admin_terraform_state`権限：ステートの作成・更新・削除・ロック操作

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 89 | Terraform状態一覧 | 主機能 | Terraformステートの一覧表示 |

## 機能種別

CRUD操作 / データ連携 / バリデーション

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | String/Integer | Yes | プロジェクトのIDまたはURLエンコードされたパス | 存在するプロジェクトであること |
| name | String | Yes | Terraformステートの名前 | 最大255文字 |
| ID | String | No | Terraformステートのロック識別子 | 最大255文字 |
| Operation | String | Lockリクエスト時Yes | Terraform操作種別 | - |
| Info | String | Lockリクエスト時Yes | Terraform情報 | - |
| Who | String | Lockリクエスト時Yes | ロック所有者 | - |
| Version | String | Lockリクエスト時Yes | Terraformバージョン | - |
| Created | String | Lockリクエスト時Yes | ロック作成日時 | - |
| Path | String | Lockリクエスト時Yes | Terraformパス | - |
| file | File | POST時Yes | Terraformステートファイル | JSONフォーマット、serialフィールド必須 |

### 入力データソース

- Terraform CLI からのHTTPリクエスト
- GitLab CI/CDジョブからのAPIリクエスト
- 外部システムからのREST API呼び出し

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| state_content | Binary/JSON | Terraformステートファイルの内容 |
| lock_info | JSON | ロック情報（ID, Who, Created, Operation, Info, Version, Path） |

### 出力先

- HTTPレスポンスとしてTerraform CLIへ返却
- オブジェクトストレージまたはローカルファイルシステムへのファイル保存

## 処理フロー

### 処理シーケンス

```
1. 認証・認可チェック
   └─ Basic認証（個人アクセストークン）またはジョブトークン認証
   └─ read_terraform_state / admin_terraform_state 権限確認

2. ステート取得（GET）
   └─ プロジェクトとステート名でステートを検索
   └─ 最新バージョンのファイルを取得
   └─ 暗号化状態に応じてファイル内容を返却

3. ステート作成・更新（POST）
   └─ Workhorse経由でファイルを受信
   └─ JSONパースしてserialを抽出
   └─ ロック状態を確認
   └─ 新規バージョンを作成してファイルを保存

4. ステートロック（POST /lock）
   └─ ステートのロック状態を確認
   └─ 未ロック時のみロック情報を設定
   └─ ロック情報を返却

5. ステートアンロック（DELETE /lock）
   └─ ロックIDの一致を確認
   └─ ロック情報をクリア

6. ステート削除（DELETE）
   └─ ロック状態を確認
   └─ deleted_atを設定して論理削除
   └─ 非同期ワーカーで物理削除を実行
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B{認証チェック}
    B -->|失敗| C[401 Unauthorized]
    B -->|成功| D{権限チェック}
    D -->|権限なし| E[403 Forbidden]
    D -->|権限あり| F{HTTPメソッド}

    F -->|GET| G[ステート取得]
    G --> H{ステート存在?}
    H -->|No| I[404 Not Found]
    H -->|Yes| J{最新ファイル存在?}
    J -->|No| K[204 No Content]
    J -->|Yes| L[ファイル内容返却]

    F -->|POST| M{authorize?}
    M -->|Yes| N[Workhorse認可]
    M -->|No| O[ステート更新処理]
    O --> P{ロック確認}
    P -->|ロック不一致| Q[409 Conflict]
    P -->|OK| R[バージョン作成]
    R --> S[200 OK]

    F -->|DELETE| T{ロック確認}
    T -->|ロック中| U[エラー]
    T -->|OK| V[論理削除]
    V --> W[非同期削除]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | ロック排他制御 | ステートがロック中の場合、ロックIDが一致しない更新は拒否 | ステート更新時 |
| BR-02 | シリアル番号必須 | ステートファイルにはserial（バージョン番号）が必須 | ステート作成・更新時 |
| BR-03 | ロック中削除禁止 | ロック状態のステートは削除不可 | ステート削除時 |
| BR-04 | UUID一意性 | ステートのUUIDは32文字の16進数でグローバルに一意 | ステート作成時 |
| BR-05 | ファイルサイズ制限 | 最大ステートサイズはシステム設定に従う | ステートアップロード時 |

### 計算ロジック

バージョン番号（serial）はTerraformステートファイルのJSONから抽出し、ステートバージョンの識別に使用する。

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ステート作成 | terraform_states | INSERT | 新規ステートレコードの作成 |
| ステート更新 | terraform_state_versions | INSERT | 新規バージョンレコードの作成 |
| ステート取得 | terraform_states, terraform_state_versions | SELECT | ステートと最新バージョンの取得 |
| ステートロック | terraform_states | UPDATE | lock_xid, locked_by_user_id, locked_atの更新 |
| ステートアンロック | terraform_states | UPDATE | ロック関連カラムのクリア |
| ステート削除 | terraform_states | UPDATE | deleted_atの設定（論理削除） |
| 物理削除 | terraform_states, terraform_state_versions | DELETE | レコードの完全削除 |

### テーブル別操作詳細

#### terraform_states

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | uuid | SecureRandom.hex(16) | 32文字のランダム16進数 |
| INSERT | project_id | 対象プロジェクトID | 外部キー |
| INSERT | name | リクエストパラメータ | 最大255文字 |
| UPDATE | lock_xid | リクエストのlock_id | ロック時に設定 |
| UPDATE | locked_by_user_id | current_user.id | ロック実行者 |
| UPDATE | locked_at | Time.current | ロック日時 |
| UPDATE | deleted_at | Time.current | 論理削除日時 |
| UPDATE | versioning_enabled | true | レガシーマイグレーション時 |

#### terraform_state_versions

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | terraform_state_id | 親ステートのID | 外部キー |
| INSERT | version | JSONのserial値 | バージョン番号 |
| INSERT | created_by_user_id | current_user.id | 作成者 |
| INSERT | ci_build_id | CI/CDビルドID | ジョブ連携時 |
| INSERT | file | アップロードファイル | CarrierWaveで管理 |
| INSERT | is_encrypted | 暗号化フラグ | システム設定に依存 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 401 | Unauthorized | 認証失敗 | 有効なアクセストークンを使用 |
| 403 | Forbidden | 権限不足 | 適切な権限を持つユーザーで実行 |
| 404 | Not Found | ステートが存在しない | ステート名を確認 |
| 409 | Conflict | ロック競合 | 他のプロセスのロック解除を待つ |
| 413 | Request Entity Too Large | ファイルサイズ超過 | ステートファイルを分割 |
| 422 | Unprocessable Entity | バリデーションエラー、JSONパースエラー、serial不在 | 入力データを修正 |

### リトライ仕様

ロック競合（409）の場合、Terraformクライアントは一定間隔でリトライを行う。サーバー側でのリトライ処理はなし。

## トランザクション仕様

- ステートの作成・更新はActiveRecordのオプティミスティックロック（`activerecord_lock_version`カラム）を使用
- `retry_lock`メソッドによりロック競合時の自動リトライを実装
- ステート削除は論理削除→非同期物理削除の2段階で実行

## パフォーマンス要件

- ステートファイル取得：暗号化ステートは直接読み込み、非暗号化ステートはWorkhorseによるSendfile/SendURLで効率的に配信
- 大規模ステートファイルのアップロードはWorkhorseによるバッファリング

## セキュリティ考慮事項

- ステートファイルは暗号化して保存可能（`terraform_state_encryption_enabled`設定）
- Basic認証または個人アクセストークン、CI/CDジョブトークンによる認証
- ロックIDの比較には`ActiveSupport::SecurityUtils.secure_compare`を使用（タイミング攻撃対策）
- API利用状況は`p_terraform_state_api_unique_users`としてトラッキング

## 備考

- レガシーステート（バージョニング導入前）からの自動マイグレーション機能あり
- GitLab 14.0以降はバージョニング必須
- feature_category: `infrastructure_as_code`

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

Terraformステート管理で使用される主要なデータモデルを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | state.rb | `app/models/terraform/state.rb` | Terraformステートの主要モデル。UUID生成、バージョニング、ロック機構を確認 |
| 1-2 | state_version.rb | `app/models/terraform/state_version.rb` | ステートバージョンモデル。ファイルアップロード、暗号化フラグを確認 |

**読解のコツ**:
- `locking_column = :activerecord_lock_version`でオプティミスティックロックを実装している
- `update_file!`メソッドがバージョン作成の中心ロジック
- **14-26行目**: `belongs_to`と`has_many`の関連定義でステートとバージョンの1:N関係を把握
- **45-69行目**: `update_file!`メソッドでバージョニング有無による処理分岐を確認

#### Step 2: エントリーポイントを理解する

API経由でのリクエスト処理の起点となるファイルを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | state.rb (API) | `lib/api/terraform/state.rb` | REST APIエンドポイント定義。認証、権限チェック、各HTTPメソッドの処理を確認 |

**主要処理フロー**:
1. **23-44行目**: `before`フックで認証・認可・トラッキング処理
2. **81-97行目**: GETリクエスト処理 - ステートファイルの取得と返却
3. **141-168行目**: POSTリクエスト処理 - ステートの作成・更新
4. **182-191行目**: DELETEリクエスト処理 - ステートの削除
5. **215-241行目**: POST /lock処理 - ステートのロック
6. **259-266行目**: DELETE /lock処理 - ステートのアンロック

#### Step 3: サービス層を理解する

ビジネスロジックを実装するサービスクラスを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | remote_state_handler.rb | `app/services/terraform/remote_state_handler.rb` | ステートの検索・作成、ロック/アンロック処理の実装 |
| 3-2 | trigger_destroy_service.rb | `app/services/terraform/states/trigger_destroy_service.rb` | ステート削除のトリガー処理 |
| 3-3 | states_finder.rb | `app/finders/terraform/states_finder.rb` | ステート検索のFinderクラス |

**主要処理フロー**:
- **remote_state_handler.rb 11-15行目**: `find_with_lock` - ロック付きステート検索
- **remote_state_handler.rb 17-29行目**: `handle_with_lock` - ロック確認付き更新処理
- **remote_state_handler.rb 31-43行目**: `lock!` - ステートロック処理
- **remote_state_handler.rb 46-59行目**: `unlock!` - ステートアンロック処理
- **trigger_destroy_service.rb 11-21行目**: `execute` - 削除トリガー処理

#### Step 4: コントローラー層を理解する

Web UIからのアクセス時のコントローラーを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | terraform_controller.rb | `app/controllers/projects/terraform_controller.rb` | Web UI用コントローラー。主にindexアクションのみ |

**主要処理フロー**:
- **4行目**: `before_action :authorize_can_read_terraform_state!` - 権限チェック
- **9行目**: `index`アクション - ステート一覧画面

#### Step 5: ポリシー・権限を理解する

アクセス制御のポリシー定義を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | state_policy.rb | `app/policies/terraform/state_policy.rb` | ステートに対するアクセスポリシー。プロジェクトポリシーに委譲 |

### プログラム呼び出し階層図

```
Terraform CLI / CI/CD Job
    │
    ├─ GET /projects/:id/terraform/state/:name
    │      └─ API::Terraform::State#get
    │             └─ RemoteStateHandler#find_with_lock
    │                    └─ Terraform::State.find_by
    │                           └─ StateVersion#file (最新バージョンのファイル取得)
    │
    ├─ POST /projects/:id/terraform/state/:name
    │      └─ API::Terraform::State#post
    │             └─ RemoteStateHandler#handle_with_lock
    │                    └─ Terraform::State#update_file!
    │                           └─ StateVersion.create (新バージョン作成)
    │
    ├─ DELETE /projects/:id/terraform/state/:name
    │      └─ API::Terraform::State#delete
    │             └─ TriggerDestroyService#execute
    │                    └─ DestroyWorker.perform_async (非同期削除)
    │
    ├─ POST /projects/:id/terraform/state/:name/lock
    │      └─ API::Terraform::State#lock
    │             └─ RemoteStateHandler#lock!
    │
    └─ DELETE /projects/:id/terraform/state/:name/lock
           └─ API::Terraform::State#unlock
                  └─ RemoteStateHandler#unlock!
```

### データフロー図

```
[入力]                    [処理]                           [出力]

Terraform CLI ───────▶ GitLab API ──────────────────▶ HTTP Response
(terraform init/        (認証・認可)                    (ステートファイル)
 apply/destroy)              │
                             ▼
                    RemoteStateHandler
                    (ロック制御・検索)
                             │
                             ▼
                    Terraform::State
                    (モデル操作)
                             │
                             ▼
              ┌──────────────┴──────────────┐
              ▼                              ▼
      terraform_states              terraform_state_versions
      (PostgreSQL)                  (PostgreSQL)
                                            │
                                            ▼
                                    Object Storage / Local
                                    (ステートファイル実体)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| state.rb | `app/models/terraform/state.rb` | モデル | ステートのActiveRecordモデル |
| state_version.rb | `app/models/terraform/state_version.rb` | モデル | ステートバージョンのモデル |
| state.rb (API) | `lib/api/terraform/state.rb` | API | REST APIエンドポイント |
| remote_state_handler.rb | `app/services/terraform/remote_state_handler.rb` | サービス | ステート操作のハンドラー |
| trigger_destroy_service.rb | `app/services/terraform/states/trigger_destroy_service.rb` | サービス | ステート削除サービス |
| states_finder.rb | `app/finders/terraform/states_finder.rb` | Finder | ステート検索 |
| state_policy.rb | `app/policies/terraform/state_policy.rb` | ポリシー | アクセス制御 |
| terraform_controller.rb | `app/controllers/projects/terraform_controller.rb` | コントローラー | Web UIコントローラー |
| state_uploader.rb | `app/uploaders/terraform/state_uploader.rb` | アップローダー | ファイルアップロード処理 |
| terraform_helper.rb | `app/helpers/projects/terraform_helper.rb` | ヘルパー | ビューヘルパー |
| state_parser.rb | `lib/gitlab/terraform/state_parser.rb` | ライブラリ | ステートJSONパーサー |
