# 機能設計書 101-リソース差分（diff）

## 概要

本ドキュメントは、`kubectl diff` コマンドによるリソース差分表示機能の設計を記述する。現在のクラスター上のリソースと、マニフェストを適用した場合の状態との差分をYAML形式で表示する。

### 本機能の処理概要

**業務上の目的・背景**：Kubernetesクラスターの運用において、マニフェストを適用する前に変更内容を事前確認することは、意図しない変更やダウンタイムを防ぐために不可欠である。`kubectl diff` は `kubectl apply` を実行する前のドライラン的な差分確認手段を提供し、GitOpsやCI/CDパイプラインにおける安全なデプロイフローを実現する。

**機能の利用シーン**：マニフェストファイルを変更した後、実際に `kubectl apply` を実行する前に差分を確認する場面で利用される。特にコードレビューやデプロイ承認プロセスにおいて、変更の影響範囲を視覚的に把握するために使用される。

**主要な処理内容**：
1. マニフェストファイルの読み込みとパース
2. 現在のクラスター上のリソース（LIVE）の取得
3. Dry-Run Applyによるマージ後の状態（MERGED）の計算
4. LIVE と MERGED の差分をディレクトリベースで比較
5. Secretリソースの機密値マスキング
6. `--prune` オプションによる削除対象リソースの差分表示
7. 外部diffプログラムの実行と結果出力

**関連システム・外部連携**：API Serverとの通信（リソース取得、Dry-Run Apply）、外部diffコマンド（KUBECTL_EXTERNAL_DIFF環境変数で指定可能）

**権限による制御**：リソースの読み取り権限、およびDry-Run Apply実行権限が必要。Server-Side Applyを使用する場合はパッチ権限も必要。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 29 | kubectl diff | 主機能 | 現在のリソースとマニフェストの差分を表示する主処理 |
| 29 | kubectl diff | API連携 | 現在のリソース状態取得のためAPI Serverにリクエストを送信する |

## 機能種別

データ比較・差分表示

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| -f / --filename | string[] | Yes | 差分対象のマニフェストファイルパス | ファイルまたはKustomize指定が必須 |
| --server-side | bool | No | Server-Side Applyを使用するか | デフォルト: false |
| --field-manager | string | No | フィールドマネージャー名 | デフォルト: "kubectl-client-side-apply" |
| --force-conflicts | bool | No | コンフリクト時に強制適用するか | --server-side 指定時のみ有効 |
| --show-managed-fields | bool | No | managedFieldsを差分に含めるか | デフォルト: false |
| --concurrency | int | No | 並列処理数 | デフォルト: 1 |
| -l / --selector | string | No | ラベルセレクター | 有効なラベルセレクター形式 |
| --prune | bool | No | プルーニング対象も差分に含めるか | デフォルト: false |
| --prune-allowlist | string[] | No | プルーニング対象の許可リスト | group/version/kind 形式 |

### 入力データソース

- ローカルファイルシステム上のマニフェストファイル（YAML/JSON）
- 標準入力（stdin）
- Kustomizeディレクトリ
- API Serverからの現在のリソース状態

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| diff出力 | text | unified diff形式の差分テキスト |

### 出力先

標準出力（stdout）。外部diffプログラムの出力がそのまま表示される。

## 処理フロー

### 処理シーケンス

```
1. コマンドライン引数のパースとバリデーション
   └─ FilenameOptions.RequireFilenameOrKustomize()で入力ファイル必須チェック
2. DiffOptionsの初期化（Complete）
   ├─ Server-Side Apply / Field Manager設定
   ├─ OpenAPI V3クライアント初期化
   ├─ DynamicClient初期化
   └─ Prune設定（--prune指定時）
3. 一時ディレクトリの作成（LIVE / MERGED）
   └─ NewDiffer("LIVE", "MERGED")
4. リソースビルダーによるマニフェスト解析
   └─ Unstructured().FilenameParam().Flatten().Do()
5. 各リソースに対するVisit処理（最大4回リトライ）
   ├─ info.Get()で現在のリソース取得
   ├─ InfoObject.Merged()でマージ後状態計算
   │   ├─ SSA: Dry-Run Apply Patch
   │   └─ CSA: Patcher.Patch（既存）/ Create（新規）
   ├─ managedFieldsの除去（オプション）
   ├─ Secret値のマスキング
   └─ YAML形式でファイル出力
6. プルーニング対象の出力（--prune指定時）
   └─ LIVEディレクトリにのみ出力
7. 外部diffプログラムの実行
   └─ DiffProgram.Run(from, to)
8. 一時ディレクトリの削除
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B[引数パース・バリデーション]
    B --> C[DiffOptions初期化]
    C --> D[一時ディレクトリ作成 LIVE/MERGED]
    D --> E[マニフェスト解析]
    E --> F{各リソースに対して}
    F --> G[現在のリソース取得 info.Get]
    G --> H{リソースが存在するか}
    H -->|Yes| I[Dry-Run Apply でマージ後状態を計算]
    H -->|No| J[Dry-Run Create で新規作成状態を計算]
    I --> K{Secretか}
    J --> K
    K -->|Yes| L[機密値マスキング]
    K -->|No| M[YAML出力]
    L --> M
    M --> N{次のリソース}
    N -->|あり| F
    N -->|なし| O{--prune?}
    O -->|Yes| P[プルーニング対象出力]
    O -->|No| Q[外部diffプログラム実行]
    P --> Q
    Q --> R[一時ディレクトリ削除]
    R --> S[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-101 | 終了コード規約 | 差分なし=0、差分あり=1、エラー=2以上 | 常に適用 |
| BR-102 | Secretマスキング | v1/Secret の data フィールドを *** でマスク | Secret リソースの差分時 |
| BR-103 | リトライ制御 | コンフリクト時は最大4回リトライ | resourceVersionの競合発生時 |
| BR-104 | managedFields除外 | デフォルトでmanagedFieldsを差分から除外 | --show-managed-fields=false（デフォルト）時 |
| BR-105 | force-conflicts制約 | --force-conflictsは--server-side指定時のみ有効 | force-conflicts フラグ指定時 |

### 計算ロジック

Secretマスキングロジック: 同一キーで値が異なる場合は `*** (before)` / `*** (after)` に、同一値の場合は `***` に、片方のみ存在する場合は `***` にマスクする。

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| リソース取得 | etcd（API Server経由） | SELECT（GET） | 現在のリソース状態を取得 |
| Dry-Run Apply | etcd（API Server経由） | SELECT（Dry-Run） | マージ後状態の計算（実際のデータ変更なし） |

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

本機能はDry-Runモードでのみ動作するため、データの永続的な変更は行わない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| Exit 2+ | コマンドエラー | ファイル未指定、接続失敗等 | エラーメッセージを表示 |
| Exit 1 | 差分あり | 正常系: 差分が検出された | 差分を表示して終了 |
| Conflict | APIコンフリクト | resourceVersionの競合 | 最大4回リトライ |
| NotFound | リソース未検出 | クラスター上にリソースが存在しない | 新規作成としてDry-Run Create |

### リトライ仕様

リソースバージョンのコンフリクトが発生した場合、最大4回リトライする（maxRetries=4）。最後のリトライ時はforceフラグを有効にしてresourceVersionチェックをスキップする。

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

Dry-Runモードで動作するため、トランザクション管理は不要。API ServerへのリクエストはすべてdryRun=trueで実行される。

## パフォーマンス要件

`--concurrency` オプションにより並列処理数を制御可能（デフォルト: 1）。大量リソースの差分比較時は並列数を増やすことでパフォーマンスを向上できるが、メモリ・CPU使用量が増加する。

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

- Secret リソースの data フィールドは自動的にマスキングされ、差分出力に機密情報が漏洩しない
- KUBECTL_EXTERNAL_DIFF 環境変数で指定される外部コマンドのパラメータは、英数字・ハイフン・等号のみ許可する正規表現でフィルタリングされる
- Dry-Run モードで動作するため、クラスター上のリソースに実際の変更は加えない

## 備考

- 出力は常にYAML形式
- `KUBECTL_EXTERNAL_DIFF` 環境変数で外部diffプログラムを指定可能（例: `colordiff -N -u`）
- デフォルトのdiffコマンドは `-u`（unified diff）と `-N`（存在しないファイルを空として扱う）オプションで実行される

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | diff.go | `staging/src/k8s.io/kubectl/pkg/cmd/diff/diff.go` | DiffOptions構造体（103-123行目）：コマンド全体のオプションを保持 |
| 1-2 | diff.go | `staging/src/k8s.io/kubectl/pkg/cmd/diff/diff.go` | InfoObject構造体（324-335行目）：各リソースのLIVE/MERGED取得を担うObjectインターフェース実装 |
| 1-3 | diff.go | `staging/src/k8s.io/kubectl/pkg/cmd/diff/diff.go` | Masker構造体（444-447行目）：Secret値のマスキングを行う |

**読解のコツ**: DiffOptionsがコマンド全体を制御し、InfoObjectが個々のリソースの差分計算を担当する。ObjectインターフェースのLive()とMerged()メソッドが差分の両端を提供する。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | diff.go | `staging/src/k8s.io/kubectl/pkg/cmd/diff/diff.go` | NewCmdDiff関数（134-179行目）：Cobraコマンド定義とフラグ登録 |

**主要処理フロー**:
1. **142-157行目**: Run関数でComplete -> Validate -> Run を順次実行
2. **151-156行目**: diffError()でexit code 1（差分あり）を通常エラーと区別

#### Step 3: 初期化処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | diff.go | `staging/src/k8s.io/kubectl/pkg/cmd/diff/diff.go` | Complete関数（624-679行目）：各種クライアント初期化とPrune設定 |

**主要処理フロー**:
- **636-641行目**: Server-Side Applyフラグと関連設定の取得
- **643-651行目**: OpenAPI V3クライアントの初期化（CSAモード時）
- **663-675行目**: --prune指定時のtracker/pruner初期化

#### Step 4: メイン処理ループを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | diff.go | `staging/src/k8s.io/kubectl/pkg/cmd/diff/diff.go` | Run関数（684-780行目）：差分計算のメインループ |

**主要処理フロー**:
- **685-689行目**: Differの作成（LIVE/MERGEDディレクトリ）
- **693-703行目**: ResourceBuilderによるマニフェスト読み込み
- **705-753行目**: Visit関数内でリトライ付きの差分計算
- **711-748行目**: 最大4回のリトライループ（コンフリクト対応）
- **755-773行目**: プルーニング対象の処理
- **779行目**: 外部diffプログラムの実行

#### Step 5: マージ処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | diff.go | `staging/src/k8s.io/kubectl/pkg/cmd/diff/diff.go` | InfoObject.Merged関数（346-408行目）：MERGED状態の計算 |

**主要処理フロー**:
- **350-366行目**: SSAモードでのDry-Run Apply Patch
- **369-377行目**: CSAモードで新規リソースのDry-Run Create
- **396-407行目**: CSAモードでの既存リソースへのPatcher.Patch

#### Step 6: プルーニング処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 6-1 | prune.go | `staging/src/k8s.io/kubectl/pkg/cmd/diff/prune.go` | tracker/pruner構造体とpruneAll関数（34-123行目） |

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

```
NewCmdDiff
    │
    ├─ DiffOptions.Complete()
    │      ├─ Factory.DynamicClient()
    │      ├─ Factory.OpenAPIV3Client()
    │      └─ newPruner() [--prune時]
    │
    ├─ DiffOptions.Validate()
    │
    └─ DiffOptions.Run()
           ├─ NewDiffer("LIVE", "MERGED")
           │      ├─ NewDiffVersion("LIVE")
           │      └─ NewDiffVersion("MERGED")
           │
           ├─ Builder.Unstructured().FilenameParam().Do()
           │
           ├─ r.Visit()
           │      ├─ info.Get() [現在のリソース取得]
           │      ├─ InfoObject.Merged()
           │      │      ├─ helper.Patch() [SSA / Dry-Run]
           │      │      ├─ helper.Create() [新規 / Dry-Run]
           │      │      └─ Patcher.Patch() [CSA / Dry-Run]
           │      ├─ Masker.run() [Secret時]
           │      └─ Differ.Diff() [YAML出力]
           │
           ├─ pruner.pruneAll() [--prune時]
           │
           └─ Differ.Run() [外部diffプログラム実行]
                  └─ DiffProgram.Run()
```

### データフロー図

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

マニフェストファイル ──▶ ResourceBuilder ──▶ InfoObject
                                               │
API Server ◀── info.Get() ◀──────────────────┘
                                               │
API Server ◀── Dry-Run Apply ◀─── Merged() ──┘
       │
       ▼
   LIVE YAML ──────▶ 一時ディレクトリ(LIVE)  ──┐
   MERGED YAML ────▶ 一時ディレクトリ(MERGED) ──┼──▶ diff ──▶ stdout

```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| diff.go | `staging/src/k8s.io/kubectl/pkg/cmd/diff/diff.go` | ソース | メインロジック：コマンド定義、差分計算、Secretマスキング |
| prune.go | `staging/src/k8s.io/kubectl/pkg/cmd/diff/prune.go` | ソース | プルーニング対象の検出と追跡 |
| diff_test.go | `staging/src/k8s.io/kubectl/pkg/cmd/diff/diff_test.go` | テスト | 差分機能のユニットテスト |
