# 機能設計書：49-パイプラインスケジュール

## 1. 機能概要

### 1.1 機能の目的
パイプラインスケジュール機能は、指定した時間に自動的にCI/CDパイプラインを実行するスケジュールを管理する機能である。cron式による柔軟なスケジューリング、変数の設定、所有権管理などを提供し、定期的なビルド・テスト・デプロイを自動化する。

### 1.2 関連画面
| 画面ID | 画面名 | パス |
|--------|--------|------|
| SCR-SCHEDULES | スケジュール一覧 | `/:namespace/:project/-/pipeline_schedules` |
| SCR-SCHEDULE-NEW | スケジュール作成 | `/:namespace/:project/-/pipeline_schedules/new` |
| SCR-SCHEDULE-EDIT | スケジュール編集 | `/:namespace/:project/-/pipeline_schedules/:id/edit` |

### 1.3 関連API
| APIエンドポイント | メソッド | 概要 |
|-------------------|----------|------|
| `/projects/:id/pipeline_schedules` | GET | スケジュール一覧取得 |
| `/projects/:id/pipeline_schedules` | POST | スケジュール作成 |
| `/projects/:id/pipeline_schedules/:schedule_id` | GET | スケジュール詳細取得 |
| `/projects/:id/pipeline_schedules/:schedule_id` | PUT | スケジュール更新 |
| `/projects/:id/pipeline_schedules/:schedule_id` | DELETE | スケジュール削除 |
| `/projects/:id/pipeline_schedules/:schedule_id/play` | POST | スケジュール即時実行 |
| `/projects/:id/pipeline_schedules/:schedule_id/take_ownership` | POST | 所有権取得 |

## 2. データモデル

### 2.1 主要エンティティ

#### Ci::PipelineSchedule
```
ci_pipeline_schedules テーブル
├── id: bigint (PK)
├── project_id: bigint (FK)
├── owner_id: bigint (FK, User)
├── description: varchar(255)
├── ref: varchar
├── cron: varchar(255)
├── cron_timezone: varchar
├── next_run_at: timestamp
├── active: boolean
├── created_at: timestamp
└── updated_at: timestamp

制限:
- limit_name = 'ci_pipeline_schedules'
- limit_scope = :project
- VALID_REF_FORMAT_REGEX（refs/heads/... または refs/tags/...）
```

#### Ci::PipelineScheduleVariable
```
ci_pipeline_schedule_variables テーブル
├── id: bigint (PK)
├── pipeline_schedule_id: bigint (FK)
├── key: varchar
├── value: text (encrypted)
├── variable_type: varchar
├── created_at: timestamp
└── updated_at: timestamp
```

#### Ci::PipelineScheduleInput
```
ci_pipeline_schedule_inputs テーブル
├── id: bigint (PK)
├── pipeline_schedule_id: bigint (FK)
├── name: varchar
├── value: text
├── created_at: timestamp
└── updated_at: timestamp

制限:
- 最大数: Ci::Pipeline::INPUTS_LIMIT (20)
```

### 2.2 ER図
```
┌─────────────────────┐      ┌──────────────────┐
│      Project        │      │       User       │
├─────────────────────┤      ├──────────────────┤
│ id                  │      │ id               │
│ ...                 │      │ ...              │
└─────────────────────┘      └──────────────────┘
         │                            │
         │1                           │1
         ▼ *                          ▼ *
┌─────────────────────────────────────────────────┐
│            Ci::PipelineSchedule                 │
├─────────────────────────────────────────────────┤
│ id                                              │
│ project_id, owner_id                            │
│ description, ref, cron, cron_timezone           │
│ next_run_at, active                             │
└─────────────────────────────────────────────────┘
         │                    │
         │1                   │1
         ▼ *                  ▼ *
┌──────────────────────┐  ┌───────────────────────┐
│Ci::PipelineSchedule  │  │ Ci::PipelineSchedule  │
│     Variable         │  │       Input           │
├──────────────────────┤  ├───────────────────────┤
│ id                   │  │ id                    │
│ pipeline_schedule_id │  │ pipeline_schedule_id  │
│ key, value           │  │ name, value           │
│ variable_type        │  │                       │
└──────────────────────┘  └───────────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────┐
│              Ci::Pipeline                       │
├─────────────────────────────────────────────────┤
│ id                                              │
│ pipeline_schedule_id (nullable)                 │
│ source = 'schedule'                             │
└─────────────────────────────────────────────────┘
```

## 3. 処理フロー

### 3.1 スケジュールパイプライン実行フロー（自動）
```
[Cron] (Settings.cron_jobs['pipeline_schedule_worker']['cron'])
     │
     ▼
[PipelineScheduleWorker]
     │
     ├── ExclusiveLease取得（LOCK_TTL = 5分）
     │
     ├── runnable_schedulesを取得
     │   └── active && next_run_at <= Time.now
     │
     ├── バッチ処理（BATCH_SIZE = 500）
     │   └── DELAY = 7秒ずつずらして実行
     │
     └── RunPipelineScheduleWorker.bulk_perform_in_with_contexts
         └── 各スケジュールをキューに投入

[RunPipelineScheduleWorker]
     │
     ├── 1. スケジュール検証
     │   ├── スケジュール存在チェック
     │   ├── プロジェクト存在チェック
     │   ├── アーカイブ済みチェック
     │   ├── 削除予定チェック
     │   ├── オーナー有効性チェック
     │   └── next_run_at未来チェック
     │
     ├── 2. 次回実行時刻更新
     │   └── schedule.schedule_next_run!
     │
     └── 3. パイプライン作成
         └── Ci::CreatePipelineService.execute(:schedule)
             ├── ref: schedule.ref
             ├── inputs: schedule.inputs_hash
             └── schedule: schedule
```

### 3.2 スケジュール手動実行フロー
```
[ユーザー]
     │
     │ play
     ▼
[Ci::PipelineSchedules::PlayService]
     │
     ├── 1. 権限チェック (play_pipeline_schedule)
     │
     ├── 2. プロジェクト検証
     │   └── アーカイブ済みでないこと
     │
     └── 3. Worker実行
         └── RunPipelineScheduleWorker.perform_async(schedule.id, user.id)
```

### 3.3 次回実行時刻計算フロー
```
[Ci::PipelineSchedules::CalculateNextRunService]
     │
     ├── schedule_cron: ユーザー定義のcron
     │
     ├── worker_cron: PipelineScheduleWorkerのcron
     │
     ├── plan_cron: プラン制限による最小間隔
     │   └── 1日 / daily_limit 分間隔
     │
     └── 計算ロジック:
         1. schedule_next_run = schedule_cron.next_time_from(now)
         2. worker_cronにマッチ && plan_min_run以降なら採用
         3. そうでなければplan_cronまたはworker_cronで調整
```

## 4. ビジネスルール

### 4.1 スケジュールの制約
| ルールID | ルール | 実装箇所 |
|----------|--------|----------|
| PS-R01 | cron式は有効な形式であること | validates :cron, cron: true |
| PS-R02 | cron_timezoneは有効なタイムゾーン | validates :cron_timezone, cron_timezone: true |
| PS-R03 | refは必須 | validates :ref, presence: true |
| PS-R04 | descriptionは必須 | validates :description, presence: true |
| PS-R05 | 変数名の重複禁止 | validates :variables, nested_attributes_duplicates: true |
| PS-R06 | 入力名の重複禁止 | validates :inputs, nested_attributes_duplicates |
| PS-R07 | 入力数は最大20件 | Ci::Pipeline::INPUTS_LIMIT |
| PS-R08 | プロジェクトごとのスケジュール数制限 | Limitable, limit_scope = :project |

### 4.2 権限
| 操作 | 必要権限 | 説明 |
|------|----------|------|
| 一覧表示 | read_pipeline_schedule | プロジェクトメンバー |
| 作成 | create_pipeline_schedule | Developer以上 |
| 更新 | update_pipeline_schedule | オーナーまたはMaintainer以上 |
| 削除 | admin_pipeline_schedule | オーナーまたはMaintainer以上 |
| 即時実行 | play_pipeline_schedule | Developer以上 |
| 所有権取得 | admin_pipeline_schedule | Maintainer以上 |

### 4.3 オーナー有効性チェック
```ruby
def schedule_owner_not_available?(schedule)
  !schedule.owner&.can?(:create_pipeline, schedule.project)
end

# オーナーが無効な場合:
# 1. プロジェクトオーナーに通知
# 2. スケジュールを無効化（deactivate!）
```

### 4.4 レートリミット
```ruby
check_rate_limit!(:play_pipeline_schedule, scope: [current_user, schedule])
# 手動実行の頻度制限
```

### 4.5 ソート順
```ruby
SORT_ORDERS = {
  id_asc/id_desc,
  description_asc/description_desc,
  ref_asc/ref_desc,
  next_run_at_asc/next_run_at_desc,
  created_at_asc/created_at_desc,
  updated_at_asc/updated_at_desc
}
```

## 5. Worker設定

### 5.1 PipelineScheduleWorker
```ruby
class PipelineScheduleWorker
  include CronjobQueue
  include ::Gitlab::ExclusiveLeaseHelpers

  LOCK_RETRY = 3        # ロック取得リトライ回数
  LOCK_TTL = 5.minutes  # ロックTTL
  DELAY = 7.seconds     # バッチ間遅延
  BATCH_SIZE = 500      # バッチサイズ

  feature_category :continuous_integration
  worker_resource_boundary :cpu
end
```

### 5.2 RunPipelineScheduleWorker
```ruby
class RunPipelineScheduleWorker
  sidekiq_options retry: 3
  queue_namespace :pipeline_creation
  feature_category :pipeline_composition
  deduplicate :until_executed, including_scheduled: true
  idempotent!
end
```

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

### 6.1 読み解く順序
1. **モデル層**: Ci::PipelineSchedule（CronSchedulable concern含む）
2. **Worker層**: PipelineScheduleWorker -> RunPipelineScheduleWorker
3. **サービス層**: CreateService, UpdateService, PlayService, CalculateNextRunService
4. **コントローラー層**: PipelineSchedulesController

### 6.2 プログラム呼び出し階層図
```
PipelineScheduleWorker
├── Ci::PipelineSchedule.runnable_schedules
└── RunPipelineScheduleWorker.bulk_perform_in_with_contexts

RunPipelineScheduleWorker
├── schedule_valid?
│   ├── プロジェクト検証
│   ├── アーカイブ検証
│   └── オーナー検証
├── schedule.schedule_next_run!
│   └── Ci::PipelineSchedules::CalculateNextRunService
└── Ci::CreatePipelineService.execute(:schedule)

Ci::PipelineSchedules::CreateService
├── authorize!
├── schedule.expand_short_ref
├── schedule.save
└── schedule.schedule_next_run!

Ci::PipelineSchedules::PlayService
├── check_access!
└── RunPipelineScheduleWorker.perform_async
```

### 6.3 データフロー図
```
[cron設定]
     │
     │ "0 * * * *" (例: 毎時0分)
     ▼
[Ci::PipelineSchedule]
     │
     ├── cron: "0 * * * *"
     ├── cron_timezone: "Asia/Tokyo"
     ├── ref: "refs/heads/main"
     └── variables: [{key: "ENV", value: "production"}]
     │
     ▼
[CalculateNextRunService]
     │
     ├── schedule_cron（ユーザー設定）
     ├── worker_cron（システム設定）
     └── plan_cron（プラン制限）
     │
     ▼
[next_run_at]
     │
     ▼
[PipelineScheduleWorker]
     │
     └── where(active: true, next_run_at <= now)
     │
     ▼
[RunPipelineScheduleWorker]
     │
     ▼
[Ci::CreatePipelineService]
     │
     └── source: :schedule
     │
     ▼
[Ci::Pipeline]
```

### 6.4 関連ファイル一覧
| ファイルパス | 責務 |
|--------------|------|
| `app/models/ci/pipeline_schedule.rb` | スケジュールモデル |
| `app/models/ci/pipeline_schedule_variable.rb` | スケジュール変数モデル |
| `app/models/ci/pipeline_schedule_input.rb` | スケジュール入力モデル |
| `app/models/concerns/cron_schedulable.rb` | cron共通機能 |
| `app/controllers/projects/pipeline_schedules_controller.rb` | コントローラー |
| `app/services/ci/pipeline_schedules/create_service.rb` | 作成サービス |
| `app/services/ci/pipeline_schedules/update_service.rb` | 更新サービス |
| `app/services/ci/pipeline_schedules/play_service.rb` | 手動実行サービス |
| `app/services/ci/pipeline_schedules/calculate_next_run_service.rb` | 次回実行計算 |
| `app/workers/pipeline_schedule_worker.rb` | スケジュールトリガーWorker |
| `app/workers/run_pipeline_schedule_worker.rb` | パイプライン作成Worker |
| `app/finders/ci/pipeline_schedules_finder.rb` | スケジュール検索 |

## 7. 拡張ポイント

### 7.1 EE拡張
- **プラン別制限**: ci_daily_pipeline_schedule_triggers
- **高度なスケジューリング**: より細かい間隔制限

### 7.2 カスタムcron追加
1. `Gitlab::Ci::CronParser`でcron式をパース
2. `next_time_from`で次回実行時刻計算
3. `match?`で時刻がcron式にマッチするか確認

### 7.3 新規変数タイプ追加
1. `Ci::PipelineScheduleVariable`に変数タイプ追加
2. パイプライン作成時に変数を適切に処理
