# OTO CLI YAML로 정의된 CI/CD 파이프라인을 실행하는 Dart 기반 자동화 도구. Jenkins, FTP, Git, Slack/Mattermost, iOS/Flutter/Android 빌드 등 다양한 작업을 커맨드로 추상화하고, 파이프라인 엔진이 순차 실행한다. --- ## 현재 상태 OTO CLI는 현재 Jenkins 실행, 로컬 YAML 파일 실행, 스케줄러 실행을 지원한다. 이 실행 표면은 이후 구조화 출력, YAML validation, 독립 Control Plane agent 확장을 추가할 때도 깨지면 안 되는 호환 기준선이다. 상세 진행 상황과 다음 작업 후보는 [`agent-roadmap/current.md`](agent-roadmap/current.md)와 [`agent-roadmap/ROADMAP.md`](agent-roadmap/ROADMAP.md)에서 확인한다. --- ## 설치 ```bash dart pub get dart compile exe bin/main.dart -o oto ``` --- ## 실행 모드 | 플래그 | 모드 | 설명 | |--------|------|------| | `-j` | `jenkins` | Jenkins 환경 변수에서 워크스페이스·YAML 읽기 | | `-t` | `test` | 하드코딩된 테스트 데이터로 로컬 실행 | | `-f ` | `file` | 지정한 YAML 파일 경로에서 파이프라인 실행 | | (daemon) | `scheduler` | 스케줄러 데몬 모드 | ```bash oto -j # Jenkins CI 모드 oto -t # 로컬 테스트 모드 oto -f ./build.yaml # 파일 기반 실행 ``` --- ## CLI 호환 기준선 | 표면 | 호환 기준 | |------|-----------| | `oto -j` | Jenkins 환경 변수와 Jenkins용 YAML 탐색을 사용하는 기존 CI 실행 경로로 유지한다. Jenkins 지원을 제거하지 않는다. | | `oto -f ` | 지정한 YAML 파일을 Jenkins 없이 실행하는 로컬/외부 자동화 진입점으로 유지한다. | | `oto scheduler ...` | YAML을 등록하고 백그라운드 스케줄러가 실행하는 daemon 경로를 유지한다. `-r`, `-l`, `-e`, `-u` 명령 형태를 호환 표면으로 본다. | | YAML 파이프라인 | `property`, `commands`, `pipeline.workflow` 구조를 핵심 입력 계약으로 유지한다. | | 커맨드 확장 | `DataParam` 모델, `CommandType`, `Command.register(...)`, `Command.specs`/`Command.catalogRows`, 샘플 YAML을 커맨드 확장 경계로 유지한다. | | 단일 바이너리 | `dart compile exe bin/main.dart -o oto`로 만드는 단일 실행 파일을 설치와 배포의 기본 산출물로 유지한다. | Jenkins 의존은 `jenkins` 실행 모드의 호환 경로로 남긴다. 새 자동화 표면은 `file`과 `scheduler` 모드가 Jenkins 환경 변수 없이 동작할 수 있다는 전제를 깨지 않아야 한다. ### Jenkins 호환 경계 - `oto -j`만 Jenkins 환경 변수와 Jenkins BuildData 입력을 읽는 compatibility adapter다. - `oto -f `는 YAML 파일만 입력으로 삼으며 Jenkins env 없이 local workspace 기본값으로 실행된다. - `oto scheduler ...`는 등록된 YAML과 scheduler 설정으로 실행되며 Jenkins env를 요구하지 않는다. - `JenkinsParameterModify`는 Jenkins job XML을 다루는 command일 뿐 `-j` 실행 모드를 요구한다는 뜻이 아니다. --- ## 실행 흐름 ``` bin/main.dart └── Application (singleton) └── DataComposer ← 실행 모드별 YAML/env 파싱 └── Pipeline └── Command.byType(CommandType) → execute(DataCommand) ``` --- ## YAML 파이프라인 구조 ```yaml property: workspace: /path/to/project version: 1.0.0 env: prod commands: - command: BuildiOS id: buildApp param: xcworkspaceFilePath: /ios/Runner.xcworkspace scheme: Runner pipeline: id: main workflow: - exe: buildApp ``` ### 태그 시스템 | 태그 | 방향 | 설명 | |------|------|------| | `` | 읽기 | property에서 값 치환 | | `<@property.key>` | 쓰기 | 커맨드 결과를 property에 저장 | - 태그가 문자열 전체이면 원본 타입(List, Map 등) 유지 - 태그가 문자열 일부이면 `toString()` 변환 - 중첩 접근 지원: `` --- ## 파이프라인 흐름 제어 ### exe — 단순 실행 ```yaml - exe: buildApp ``` ### exe-handle — 성공/실패 분기 ```yaml - exe-handle: id: uploadArtifact on-success: - exe: notifySuccess on-fail: - exe: notifyError ``` ### if — 조건 분기 ```yaml - if: condition-string: " == prod" on-true: - exe: deployProd on-false: - exe: deployDev ``` **지원 연산자:** `==`, `!=`, `<`, `<=`, `>`, `>=` **지원 타입:** `string`, `int`, `float`, `bool`, `date`, `version`, `object` ### foreach — 반복 ```yaml - foreach: iterator: # List 또는 Map setValue: <@property.target> on-do: - exe: buildTarget ``` ### while — 조건 루프 ```yaml - while: condition-int: < 3 on-do: - exe: checkStatus ``` ### switch — 멀티 케이스 ```yaml - switch: condition-string: cases: - value: prod tasks: - exe: deployProd - value: dev tasks: - exe: deployDev ``` ### wait-until — 대기 ```yaml - async: buildStep - wait-until-string: != complete ``` ### async — 비동기 실행 ```yaml - async: notifyStart # 완료를 기다리지 않고 다음 단계 진행 ``` --- ## 커맨드 카탈로그 등록된 커맨드는 `Command.specs`와 `Command.catalogRows`가 단일 진실 소스다. 샘플은 `assets/yaml/sample/**`에서 확인한다. ### CLI 조회 경로 CLI 명령을 통해 등록된 커맨드 정보와 카탈로그를 직접 조회할 수 있다. ```bash # 사람용 테이블 형식 출력 oto catalog oto catalog --category build oto catalog --command Print # 자동화 및 AI 연동용 구조화된 JSON 출력 (stdout 실행 로그가 억제됨) oto catalog --json oto catalog --command BuildiOS --json ``` --- ## 데이터 모델 구조 모든 커맨드 파라미터는 `DataParam`을 상속한다. ```dart DataParam ├── workspace?: String // 기본 작업 디렉토리 ├── passExitCodes?: List // 성공으로 간주할 exit code ├── setResult?: String // 결과 저장 태그 ├── setExitCode?: String // exit code 저장 태그 └── showPrint?: bool // 예시 DataBuildiOS extends DataParam { xcodeProjectFilePath?: String xcworkspaceFilePath?: String scheme: String configuration?: String ... } ``` **워크스페이스 해석 우선순위:** 1. `param.workspace` 2. `property.workspace` 3. `DataCommon.workspace` (Jenkins env) --- ## 새 커맨드 추가 ``` 1. lib/oto/data/*.dart DataParam 상속 모델 정의 + @JsonSerializable 2. lib/oto/commands/command.dart CommandType enum에 값 추가 3. lib/oto/commands/{category}/ Command 상속 클래스 구현 4. Command.register(..., spec: CommandSpec(type: ..., category: ..., dataModel: ..., samplePath: ...))에 구현체 등록 5. lib/oto/commands/command_registry.dart registerAllCommands()에 등록 함수 연결 6. dart run build_runner build *.g.dart 생성 등록된 커맨드 목록: Command.catalogRows (category별 정렬) ``` --- ## 스케줄러 ```bash oto scheduler -r build.yaml # 스케줄러 등록 oto scheduler -l # 목록 조회 oto scheduler -e true # 활성화/비활성화 oto scheduler -u # 해제 ``` **YAML 스케줄러 설정:** ```yaml scheduler: alias: nightly-build cron: "0 2 * * *" # cron 표현식 또는 # interval: 3600000 # 밀리초 단위 interval enableLog: true ``` **플랫폼별 실행 메커니즘:** - **Linux:** cron - **macOS:** LaunchAgent - **Windows:** Task Scheduler --- ## 로드맵 상세 로드맵은 [`agent-roadmap/ROADMAP.md`](agent-roadmap/ROADMAP.md)에 둔다. README에는 장기 방향과 작업 진입 문서 경로만 유지한다. OTO의 장기 방향은 Jenkins 안에서 실행되는 CLI에 머물지 않고, OTO Control Plane에 등록되는 가벼운 build/deploy agent까지 확장하는 것이다. - 현재 활성 Milestone 후보는 [`agent-roadmap/current.md`](agent-roadmap/current.md)에서 확인한다. - CLI 자동화 표면 정리: 기존 CLI 실행 모드와 YAML 파이프라인, 커맨드 확장, 단일 바이너리 배포 구조를 장기 호환 표면으로 정리한다. - 독립 Control Plane 기반 `oto-agent`: Jenkins node 연결식 UX를 OTO Server bootstrap 기반 `oto-agent` 설치와 등록 계약으로 재해석한다. - 메시지 기반 빌드 에이전트: OTO Server가 OTO 파이프라인을 원격 제어할 수 있게 한다. ### Agent bootstrap 환경 기준 - `OTO_RUNNER_RELEASE_BASE_URL`은 runner binary asset을 내려받는 release source of truth다. - Core가 HTTP로 실행될 때는 release base URL을 자동 추론하지 않으며, 환경 변수로 주입한 값도 `https://` scheme이어야 한다. - bootstrap command 예시는 public validation 기준만 문서화하고, enrollment token, private release host, credential 원문은 tracked 문서에 남기지 않는다. - bootstrap script의 `--edge-url` 인자는 기존 호출 호환 alias로만 보며, 새 문서와 smoke 기준은 `--server-url`을 사용한다. --- ## 프로젝트 구조 ``` lib/ ├── cli/ # CLI 인자 파싱, 스케줄러 관리 └── oto/ ├── application.dart # 싱글턴 오케스트레이터 ├── commands/ # 커맨드 구현체 (카테고리별) ├── core/ # 태그 시스템, 데이터 합성, 정의 데이터 ├── data/ # JSON 직렬화 데이터 모델 (*.g.dart 포함) ├── pipeline/ # 파이프라인 흐름 제어 엔진 └── utils/ # 공통 유틸리티 ``` --- ## 기술 스택 - **언어:** Dart (SDK >=2.18.6 <4.0.0) - **의존성:** dart_framework (커스텀), http, json_annotation, cron, xml - **코드 생성:** json_serializable + build_runner ```bash dart run build_runner build # 데이터 모델 생성 dart test # 테스트 실행 ``` --- ## 에러 처리 - `Application.build()`는 `catch (e, stacktrace)`로 Exception과 Error 모두 처리 - 실패 시 `BuildResult.failure(..., exitCode: 10)`을 반환하고 CLI가 부모 프로세스에 exit code 전달 - `passExitCodes`로 성공으로 간주할 exit code 지정 가능