# 機能設計書 100-ファイルコピー（cp）

## 概要

本ドキュメントは、kubectl cpコマンドによるコンテナとの間のファイルコピー機能の設計を記述する。

### 本機能の処理概要

kubectl cpコマンドは、ローカルファイルシステムとPod内コンテナ間でファイルやディレクトリをコピーするCLI機能である。内部的にtarコマンドをコンテナ上で実行し、パイプ経由でデータ転送する。

**業務上の目的・背景**：コンテナ内のログファイル、設定ファイル、データファイル等の取得や配置を行うために利用される。デバッグ、障害調査、設定変更、データ移行等の運用作業で頻繁に使用される。

**機能の利用シーン**：コンテナ内ログの取得、設定ファイルの配置、データバックアップ、デバッグ用ツールの配置、クラッシュダンプの回収。

**主要な処理内容**：
1. ソースとデスティネーションのファイル仕様をパース
2. コピー方向の決定（ローカル→Pod / Pod→ローカル）
3. tarアーカイブを使用したパイプベースのファイル転送
4. ファイル展開と書き込み

**関連システム・外部連携**：Pod内コンテナのtarコマンドを前提とする。内部的にkubectl exec機能を使用してリモートコマンドを実行。

**権限による制御**：pods/execのcreate権限が必要。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | kubectl cp | 主機能 | ローカル⇔コンテナ間のファイルコピー |

## 機能種別

ファイル操作 / リモートファイル転送

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| SRC | string | Yes | コピー元（ローカルパス or [namespace/]pod:path） | fileSpec形式 |
| DEST | string | Yes | コピー先（ローカルパス or [namespace/]pod:path） | fileSpec形式 |
| -c / --container | string | No | コンテナ名 | 有効なコンテナ名 |
| --no-preserve | bool | No | 所有権・パーミッション非保持 | - |
| --retries | int | No | リトライ回数（0=無効、負数=無限） | 整数 |

### ファイル仕様（fileSpec）形式

| 形式 | 説明 | 例 |
|-----|------|-----|
| `file/path` | ローカルファイル | `/tmp/foo` |
| `pod:file/path` | Pod内ファイル（デフォルトnamespace） | `mypod:/tmp/bar` |
| `namespace/pod:file/path` | Pod内ファイル（namespace指定） | `kube-system/mypod:/tmp/bar` |

### 入力データソース

CLI引数、kubeconfig

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| コピー結果 | - | 正常終了時は出力なし |
| 警告メッセージ | string | シンボリックリンクスキップ時の警告 |
| リトライ情報 | string | リトライ時の進捗メッセージ |

### 出力先

標準出力（stdout）、標準エラー出力（stderr）

## 処理フロー

### 処理シーケンス

```
1. Complete: 引数と環境の初期化
   └─ namespace、Clientset、ClientConfig設定

2. Validate: 入力検証
   └─ 引数が2つであることを確認

3. Run: コピー実行
   ├─ extractFileSpec: ソースとデスティネーションのパース
   ├─ コピー方向の判定
   │   ├─ Pod→ローカル（copyFromPod）
   │   │   ├─ TarPipe: リモートでtar cf実行→パイプ読み取り
   │   │   └─ untarAll: ローカルに展開
   │   └─ ローカル→Pod（copyToPod）
   │       ├─ checkDestinationIsDir: 宛先がディレクトリか確認
   │       ├─ makeTar: ローカルファイルをtar化
   │       └─ exec: リモートでtar xf実行
   └─ 両方リモートの場合はエラー
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B[Complete: 初期化]
    B --> C[Validate: 引数2つ確認]
    C --> D[extractFileSpec: パース]
    D --> E{ソースにPodName?}
    E -->|Yes| F{デストにPodName?}
    E -->|No| G{デストにPodName?}
    F -->|Yes| H[エラー: 両方リモート不可]
    F -->|No| I[copyFromPod]
    G -->|Yes| J[copyToPod]
    G -->|No| K[エラー: どちらかリモート必須]
    I --> L[TarPipe: tar cf → パイプ読み取り]
    L --> M[untarAll: ローカルに展開]
    J --> N[checkDestinationIsDir]
    N --> O[makeTar: tar化]
    O --> P[exec: tar xf リモート実行]
    M --> Q[終了]
    P --> Q
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-100-01 | tarバイナリ前提 | コンテナ内にtarコマンドが存在する必要がある | 常時 |
| BR-100-02 | 片方のみリモート | ソースとデスティネーションの一方のみがリモート（Pod）可能 | 常時 |
| BR-100-03 | シンボリックリンクスキップ | Pod→ローカルコピー時、シンボリックリンクは警告してスキップ | Pod→ローカル時 |
| BR-100-04 | パス検証 | 展開先がベースディレクトリの外に出るファイルはスキップ（ディレクトリトラバーサル防止） | Pod→ローカル時 |
| BR-100-05 | tarプレフィックス検証 | tarエントリのプレフィックスが期待値と一致しない場合はコンテンツ破損エラー | Pod→ローカル時 |
| BR-100-06 | 宛先ディレクトリ判定 | ローカル→Pod時、宛先がディレクトリの場合はその中にコピー | ローカル→Pod時 |
| BR-100-07 | リトライ機能 | --retriesで指定した回数、コピーをリトライ可能。tail -c+N で中断位置から再開 | retries > 0時 |

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| exec | pods/exec | CREATE | tar コマンドをリモート実行 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 引数エラー | 引数が2つでない | ソースとデスティネーションの両方を指定 |
| - | fileSpec形式エラー | ファイル仕様が不正 | 正しい形式を使用 |
| - | 両方リモートエラー | ソースとデストの両方がPod | 片方をローカルパスに変更 |
| - | 両方ローカルエラー | ソースとデストの両方がローカル | 片方をPodパスに変更 |
| - | ソースファイル不在 | ローカルファイルが存在しない | パスを確認 |
| - | tarコンテンツ破損 | tarエントリのプレフィックスが不正 | ソースファイルを確認 |
| - | ファイルパス空エラー | ファイルパスが空 | 有効なパスを指定 |
| - | タイムアウトエラー | 宛先ディレクトリ確認のタイムアウト（30秒） | ネットワーク接続を確認 |

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

ファイルコピーはストリーミング操作。部分的なコピーが発生する可能性がある。--retriesで中断からの再開が可能。

## パフォーマンス要件

ファイルサイズに比例した転送時間。tarによるストリーミング転送のため、大きなファイルでもメモリ効率が良い。チェックDestinationIsDirは30秒タイムアウト。

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

- pods/execのcreate権限が必要
- ディレクトリトラバーサル防止（isRelative関数、stripPathShortcuts関数）
- シンボリックリンクはセキュリティ上の理由からスキップ
- tarコンテンツの検証（プレフィックスチェック）

## 備考

- コンテナ内にtarバイナリが必要。tarがない場合はkubectl cpは使用不可
- 高度なユースケース（シンボリックリンク、ワイルドカード、パーミッション保持）にはkubectl execの直接使用を推奨
- --no-preserveでパーミッション/所有権の非保持が可能
- --retriesで転送失敗時のリトライが可能（tail -c+Nで再開位置を指定）

---

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | cp.go | `staging/src/k8s.io/kubectl/pkg/cmd/cp/cp.go` | **70-83行目**: CopyOptions構造体で全オプションを確認 |
| 1-2 | filespec.go | `staging/src/k8s.io/kubectl/pkg/cmd/cp/filespec.go` | **25-29行目**: fileSpec構造体（PodName, PodNamespace, File） |
| 1-3 | filespec.go | `staging/src/k8s.io/kubectl/pkg/cmd/cp/filespec.go` | **31-33行目**: pathSpecインターフェース |
| 1-4 | filespec.go | `staging/src/k8s.io/kubectl/pkg/cmd/cp/filespec.go` | **38-40行目**: localPath構造体（クライアントOS依存パス） |
| 1-5 | filespec.go | `staging/src/k8s.io/kubectl/pkg/cmd/cp/filespec.go` | **85-87行目**: remotePath構造体（常にUNIXパス） |

**読解のコツ**: localPathはpath/filepath（OS依存）、remotePathはpath（常にUNIX `/`）を使用。この区別がコピー処理の正確性を担保している。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | cp.go | `staging/src/k8s.io/kubectl/pkg/cmd/cp/cp.go` | **93-164行目**: NewCmdCp関数。シェル補完ロジックが特徴的 |
| 2-2 | cp.go | `staging/src/k8s.io/kubectl/pkg/cmd/cp/cp.go` | **170-200行目**: extractFileSpec関数でfileSpec形式のパース |

#### Step 3: コピー方向の決定と実行を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | cp.go | `staging/src/k8s.io/kubectl/pkg/cmd/cp/cp.go` | **237-261行目**: Run関数でコピー方向を判定 |
| 3-2 | cp.go | `staging/src/k8s.io/kubectl/pkg/cmd/cp/cp.go` | **300-350行目**: copyToPod関数（ローカル→Pod） |
| 3-3 | cp.go | `staging/src/k8s.io/kubectl/pkg/cmd/cp/cp.go` | **352-360行目**: copyFromPod関数（Pod→ローカル） |

#### Step 4: tarベースの転送処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | cp.go | `staging/src/k8s.io/kubectl/pkg/cmd/cp/cp.go` | **423-431行目**: makeTar関数。ローカルファイルをtar化 |
| 4-2 | cp.go | `staging/src/k8s.io/kubectl/pkg/cmd/cp/cp.go` | **433-501行目**: recursiveTar関数。ディレクトリ、シンボリックリンク、通常ファイルの処理分岐 |
| 4-3 | cp.go | `staging/src/k8s.io/kubectl/pkg/cmd/cp/cp.go` | **362-421行目**: TarPipe構造体。リモートtarの出力をパイプで読み取り。リトライ対応 |
| 4-4 | cp.go | `staging/src/k8s.io/kubectl/pkg/cmd/cp/cp.go` | **503-572行目**: untarAll関数。tarエントリの展開。セキュリティチェック含む |
| 4-5 | cp.go | `staging/src/k8s.io/kubectl/pkg/cmd/cp/cp.go` | **267-298行目**: checkDestinationIsDir関数。リモートで`test -d`コマンド実行（30秒タイムアウト） |

#### Step 5: セキュリティ関連処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | cp.go | `staging/src/k8s.io/kubectl/pkg/cmd/cp/cp.go` | **521-523行目**: tarプレフィックス検証（コンテンツ破損検出） |
| 5-2 | cp.go | `staging/src/k8s.io/kubectl/pkg/cmd/cp/cp.go` | **532-535行目**: isRelative関数によるディレクトリトラバーサル防止 |
| 5-3 | cp.go | `staging/src/k8s.io/kubectl/pkg/cmd/cp/cp.go` | **547-557行目**: シンボリックリンクのスキップ処理 |
| 5-4 | filespec.go | `staging/src/k8s.io/kubectl/pkg/cmd/cp/filespec.go` | **75-81行目**: isRelative関数の実装 |
| 5-5 | filespec.go | `staging/src/k8s.io/kubectl/pkg/cmd/cp/filespec.go` | **142-161行目**: stripPathShortcuts関数（../の除去） |

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

```
NewCmdCp
    │
    ├─ Complete
    │      ├─ f.ToRawKubeConfigLoader().Namespace()
    │      ├─ f.KubernetesClientSet()
    │      └─ f.ToRESTConfig()
    │
    ├─ Validate
    │      └─ len(args) == 2 チェック
    │
    └─ Run
           ├─ extractFileSpec(src) / extractFileSpec(dest)
           │
           ├─ [src=Pod] copyFromPod
           │      ├─ newTarPipe(src)
           │      │      └─ initReadFrom(0)
           │      │             └─ exec("tar cf - {file}")  ───▶ Container
           │      └─ untarAll(reader)
           │             ├─ tarReader.Next() ループ
           │             ├─ プレフィックス検証
           │             ├─ isRelative チェック
           │             ├─ symlink スキップ
           │             └─ io.Copy → ローカルファイル書き込み
           │
           └─ [dest=Pod] copyToPod
                  ├─ checkDestinationIsDir(dest)
                  │      └─ exec("test -d {path}")  ───▶ Container
                  ├─ makeTar(src, dest)
                  │      └─ recursiveTar()
                  │             ├─ ディレクトリ: 再帰処理
                  │             ├─ シンボリックリンク: ヘッダ書き込み
                  │             └─ 通常ファイル: ヘッダ+データ書き込み
                  └─ exec("tar -xmf -")  ───▶ Container
```

### データフロー図

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

CLI引数                ───▶ Complete（初期化）
 (SRC DEST)                  │
                             ▼
kubeconfig             ───▶ Run
                        ├─ extractFileSpec()
                        │
                        ├─ [Pod→ローカル]
                        │   ├─ TarPipe
                        │   │   └─ exec("tar cf -")  ◀─── Container (tar出力)
                        │   └─ untarAll
                        │       └─ tarReader          ───▶ ローカルファイル書き込み
                        │
                        └─ [ローカル→Pod]
                            ├─ checkDestinationIsDir
                            │   └─ exec("test -d")    ◀──▶ Container
                            ├─ makeTar
                            │   └─ recursiveTar       ◀─── ローカルファイル読み込み
                            └─ exec("tar xf -")       ───▶ Container (tar入力)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| cp.go | `staging/src/k8s.io/kubectl/pkg/cmd/cp/cp.go` | ソース | cpコマンドのメイン実装（592行） |
| filespec.go | `staging/src/k8s.io/kubectl/pkg/cmd/cp/filespec.go` | ソース | ファイルパス抽象化（localPath/remotePath）（162行） |
| exec.go | `staging/src/k8s.io/kubectl/pkg/cmd/exec/` | ソース | リモートコマンド実行機能 |
