# 通知設計書 98-UnparseableSchedule

## 概要

本ドキュメントは、CronJobのスケジュール表現が解析不能な際に発行されるKubernetesイベント通知「UnparseableSchedule」の設計を記述する。

### 本通知の処理概要

CronJobのSpec.Scheduleフィールドに設定されたCron表現が、Cronスケジュールパーサーによって解析できない場合にWarningイベントとして発行される通知である。

**業務上の目的・背景**：CronJobのスケジュール表現は標準的なCron形式（5フィールドまたは事前定義キーワード）で記述される必要がある。構文エラーがある場合、CronJobは一切のJobを作成できなくなるため、この通知により設定ミスを即座に検知し修正を促す。

**通知の送信タイミング**：`syncCronJob`メソッド内で`parsers.ParseCronScheduleWithPanicRecovery`がエラーを返した場合に発行される（行524）。また、`updateCronJob`メソッド内でスケジュール変更時にも同様の解析が行われ、解析不能な場合はイベントが発行される（行404）。ただしupdateCronJobではReasonが「UnParseableCronJobSchedule」であり、本通知とは異なる点に注意。

**通知の受信者**：Kubernetesイベントとして該当CronJobオブジェクトに記録される。

**通知内容の概要**：EventTypeはWarning、Reasonは「UnparseableSchedule」、メッセージには解析不能なスケジュール表現とパースエラーの詳細が含まれる。フォーマットは「unparseable schedule: "{スケジュール表現}" : {エラー内容}」である。

**期待されるアクション**：受信者はCronJobのSpec.Scheduleを正しいCron形式に修正する。一般的な修正としてはフィールド数の確認、値範囲の確認、特殊文字のエスケープなどが必要となる。

## 通知種別

Kubernetesイベント（Event API リソース）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（syncCronJob処理内で即座に発行） |
| 優先度 | 高（CronJobが一切動作しない） |
| リトライ | CronJobのSpec.Scheduleが更新されるまで再調停されない（nilが返されsyncが終了） |

### 送信先決定ロジック

CronJobコントローラの`recorder.Eventf`メソッドにより、対象のCronJobオブジェクトに対してイベントが記録される。

## 通知テンプレート

### メール通知の場合

本通知はKubernetesイベントであり、メール送信は行わない。

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | N/A（Kubernetesイベント） |
| 送信元名称 | cronjob-controller |
| 件名 | N/A |
| 形式 | Kubernetesイベントリソース |

### 本文テンプレート

```
unparseable schedule: %q : %s
```

第1変数`%q`にはスケジュール表現（`cronJob.Spec.Schedule`、ダブルクォート付き）、第2変数`%s`にはパースエラーの内容が挿入される。

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| N/A | N/A | N/A | 添付ファイルなし |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| cronJob.Spec.Schedule | CronJobのスケジュール表現 | CronJob.Spec.Schedule | Yes |
| err | パースエラー内容 | parsers.ParseCronScheduleWithPanicRecovery戻り値 | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| コントローラ同期 | CronJob同期処理 | parsers.ParseCronScheduleWithPanicRecoveryがエラーを返した場合 | syncCronJob内のスケジュール解析 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| スケジュール解析成功 | パースが成功した場合は通常のスケジュール処理に進む |
| CronJobがSuspend中 | Suspend=trueの場合はスケジュール解析に到達しない |
| タイムゾーン不正 | TimeZoneが不正な場合は先にUnknownTimeZoneが発行され、スケジュール解析に到達しない |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[syncCronJob] --> B{CronJob削除中?}
    B -->|Yes| C[ステータス更新のみ]
    B -->|No| D{TimeZone有効?}
    D -->|無効| E[UnknownTimeZoneイベント]
    D -->|有効/未設定| F{Suspend中?}
    F -->|Yes| G[スキップ]
    F -->|No| H[formatSchedule]
    H --> I[ParseCronScheduleWithPanicRecovery]
    I --> J{パース結果}
    J -->|成功| K[スケジュール処理続行]
    J -->|失敗| L[UnparseableScheduleイベント発行]
    L --> M[nil返却（リキューなし）]
```

## データベース参照・更新仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| CronJob | スケジュール表現取得 | Informerキャッシュ経由 |

### テーブル別参照項目詳細

#### CronJob

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| Spec.Schedule | スケジュール解析対象 | syncCronJob内 |
| Spec.TimeZone | formatScheduleでTZプレフィックス付与 | syncCronJob内 |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| Event | INSERT | UnparseableScheduleイベント作成 |

#### 送信ログテーブル

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| INSERT | Event.Type | Warning | イベント種別 |
| INSERT | Event.Reason | UnparseableSchedule | イベント理由 |
| INSERT | Event.Message | unparseable schedule: "{schedule}" : {err} | スケジュール解析エラー |
| INSERT | Event.Source.Component | cronjob-controller | 発行元コンポーネント |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 構文エラー | Cron表現の構文が不正 | Spec.Scheduleを正しいCron形式に修正 |
| フィールド数エラー | Cronフィールドの数が不正 | 5フィールド形式（分 時 日 月 曜日）に修正 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 自動リトライなし。Spec.Schedule更新により再キューされる |
| リトライ間隔 | N/A |
| リトライ対象エラー | N/A |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | Kubernetesイベントのデフォルト集約ルールに従う |
| 1日あたり上限 | 特に制限なし |

### 配信時間帯

制限なし。

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

スケジュール表現がメッセージに含まれるが、機密情報は通常含まれない。

## 備考

- `formatSchedule`関数（行766-784）は、TimeZoneが設定されている場合に「TZ={timezone} {schedule}」形式に変換する。また、スケジュール内に「TZ」が含まれる場合はUnsupportedScheduleイベントを発行する。
- sync関数の戻り値としてnilが返されるため（行525）、ワークキューへのリキューは行われない。Spec.Scheduleの更新イベントにより再キューされる。
- updateCronJob内（行399-405）でも同様のパースが行われるが、Reasonが「UnParseableCronJobSchedule」と異なる点に注意。

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | types.go | `staging/src/k8s.io/api/batch/v1/types.go` | CronJob.Spec.Schedule（string型）とSpec.TimeZone（*string型）を理解する |

**読解のコツ**: Scheduleは標準Cron形式（"*/5 * * * *"等）または事前定義キーワード（"@hourly"等）を受け付ける。

#### Step 2: スケジュール解析を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | cronjob_controllerv2.go | `pkg/controller/cronjob/cronjob_controllerv2.go` | formatSchedule関数（行766-784）でTZプレフィックスが付与される流れと、syncCronJob内のパース処理（行519-526）を理解する |
| 2-2 | parsers.go | `pkg/util/parsers/parsers.go` | ParseCronScheduleWithPanicRecovery関数の実装を理解する |

**主要処理フロー**:
1. **行519**: formatSchedule(cronJob, jm.recorder)でスケジュール文字列を整形
2. **行519**: parsers.ParseCronScheduleWithPanicRecoveryでパース実行
3. **行520-526**: エラー時 - ログ出力 + UnparseableScheduleイベント発行 + nil返却

#### Step 3: formatSchedule関数を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | cronjob_controllerv2.go | `pkg/controller/cronjob/cronjob_controllerv2.go` | formatSchedule関数（行766-784）の3つの分岐を理解する |

**主要処理フロー**:
- **行767-773**: Schedule内に「TZ」を含む場合 -> UnsupportedScheduleイベント発行 + そのまま返却
- **行775-781**: Spec.TimeZone設定済み -> "TZ={timezone} {schedule}"形式で返却
- **行783**: その他 -> Spec.Scheduleをそのまま返却

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

```
ControllerV2.syncCronJob (行426)
    |
    +-- [TimeZoneチェック] (行505-512)
    +-- [Suspendチェック] (行514-517)
    |
    +-- formatSchedule (行519, 766)
    |    |
    |    +-- [TZ含む] -> UnsupportedScheduleイベント (行769)
    |    +-- [TimeZone設定] -> "TZ=xxx schedule"
    |    +-- [その他] -> schedule
    |
    +-- parsers.ParseCronScheduleWithPanicRecovery (行519)
         |
         +-- [成功] -> 通常処理続行
         +-- [失敗] -> recorder.Eventf "UnparseableSchedule" (行524) ★本通知
                        +-- nil返却 (行525)
```

### データフロー図

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

CronJob.Spec.Schedule    --> formatSchedule              --> UnparseableSchedule Event
CronJob.Spec.TimeZone         |                              (Warning)
                              +-- ParseCronSchedule
                              +-- recorder.Eventf
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| cronjob_controllerv2.go | `pkg/controller/cronjob/cronjob_controllerv2.go` | ソース | UnparseableScheduleイベント発行（行524）、formatSchedule（行766-784） |
| parsers.go | `pkg/util/parsers/parsers.go` | ソース | ParseCronScheduleWithPanicRecovery関数 |
