<p align="center">
<img src="https://raw.githubusercontent.com/dsecurity49/safe-migrate/HEAD/docs/logo.svg" width="120" alt="safe-migrate logo">
</p>
<h1 align="center">safe-migrate</h1>
<p align="center">
<a href="https://github.com/dsecurity49/safe-migrate/actions/workflows/ci.yml"><img src="https://github.com/dsecurity49/safe-migrate/actions/workflows/ci.yml/badge.svg" alt="CI status"></a>
<a href="https://crates.io/crates/safe-migrate"><img src="https://img.shields.io/crates/v/safe-migrate.svg" alt="crates.io version"></a>
<a href="https://docs.rs/safe-migrate"><img src="https://docs.rs/safe-migrate/badge.svg" alt="docs.rs"></a>
</p>
safe-migrate는 위험한 PostgreSQL 마이그레이션이 프로덕션에 도달하기 전에 이를 찾아냅니다.
SQL을 파싱하고, 스키마 변경을 시뮬레이션하며, 그 결과를 동기화된 데이터베이스 스냅샷과 비교하여 확인합니다. 이 도구는 절대 마이그레이션 SQL을 실행하지 않습니다.
PostgreSQL 14–18을 지원합니다.
설치 (Install)
Rust 1.94 이상 버전이 필요합니다.
Rust가 설치된 경우:
cargo install safe-migrate --locked
사전 빌드 바이너리는 GitHub Releases에서 이용할 수 있습니다. 설치 프로그램은 릴리스 체크섬을 확인합니다:
VERSION='v0.11.0'
curl -fsSL "https://raw.githubusercontent.com/dsecurity49/safe-migrate/${VERSION}/install.sh" |
bash -s -- --version "${VERSION}"
먼저 install.sh를 다운로드한 후, sh install.sh --help를 실행하여 목적지(destination) 및 대상(target) 옵션을 검토하세요.
빠른 시작 (Quick start)
export DATABASE_URL='postgres://readonly_user@localhost:5432/app'
safe-migrate sync
safe-migrate lint-chain --dir migrations
sync는 .safe-migrate.cache 파일을 작성합니다. 이후 검사는 이 스냅샷을 오프라인으로 사용합니다. safe-migrate cache inspect를 실행하여 그 출처(provenance)와 마스킹된 내용을 확인할 수 있습니다.
확인하는 내용 (What it checks)
내장 규칙은 다음 사항들을 다룹니다:
- blocking locks, 테이블 재작성(table rewrites), 제약 조건(constraints), 인덱스(indexes), 파티션(partitions), 그리고
재현 뷰 새로고침(materialized-view refreshes);
- 파괴적 변경(destructive changes), 캐스케이드(cascades), 스키마 드리프트(schema drift), 의존성 깨짐(dependency breakage), 그리고
마이그레이션 순서 충돌(migration ordering conflicts);
- 권한 부여(grants), 정책(policies), 비활성화된 트리거(disabled triggers), 역할(roles), 그리고 특권 민감 변경(privilege-sensitive changes);
- 누락된 타임아웃(missing timeouts), 트랜잭션 비호환 작업(transaction-incompatible operations), 동적 SQL(dynamic SQL),
휘발성 기본값(volatile defaults), 그리고 재실행 안전성(rerun safety).
safe-migrate rules를 사용하여 카탈로그에 대한 규칙을 실행하거나, 특정 규칙을 직접 검사할 수 있습니다:
safe-migrate rules --rule require-concurrent-index
GitHub Actions
safe-migrate-baseline GitHub 환경을 생성하고 보호한 다음, 다음 명령어를 실행합니다:
safe-migrate init github-actions --path migrations --configure-secrets
이 명령어는 신뢰할 수 있는 기준선 새로고침(trusted baseline refresh)과 오프라인 PR 검사(offline PR check)를 생성합니다. GitHub Action 가이드를 따라 러너(runner)를 연결하고 첫 번째 기준선을 만드세요.
결과 (Results)
| 등급 (Tier) | 의미 (Meaning) | 기본 명령어 결과 (Default command result) |
|---|
Tier1 | 차단 안전성 문제 (Blocking safety problem) | 종료 코드 2 |
| ... | | |
운영 실패(Operational failures)—예를 들어, 유효하지 않은 SQL, 구성 또는 캐시 데이터—는 종료 코드 1을 반환합니다. 모든 발견 사항에는 안정적인 규칙 ID, 이유, 그리고 해결 방법이 포함됩니다: | | |
[HALT] Require concurrent index (require-concurrent-index)
reason : 이 인덱스를 생성하는 것이 대규모 테이블에 쓰기 작업을 차단할 수 있습니다.
recipe : 트랜잭션 외부에서 CREATE INDEX CONCURRENTLY를 사용하세요.
자동화를 위해 --json을, 검토 아티팩트를 위해 --markdown을 사용합니다. 스키마, 신뢰도(confidence), 판결(verdicts), 그리고 호환성 보장(compatibility guarantees)에 대한 내용은 CLI 및 보고서 계약을 참조하세요.
명령어 (Commands)
| 명령어 (Command) | 목적 (Purpose) |
|---|
lint --file migration.sql | 하나의 마이그레이션을 검사합니다. |
| ... | |
모든 옵션에 대해서는 safe-migrate <명령어> --help를 실행하세요. | |
데이터베이스 기준선 (Database baseline)
sync는 읽기 전용의 반복 가능 읽기 트랜잭션(read-only, repeatable-read transaction)으로 PostgreSQL 카탈로그를 읽습니다. 원격 직접 연결은 거부되며, localhost, Unix 소켓 또는 신뢰할 수 있는 터널을 사용해야 합니다:
ssh -N -L 5433:db.internal:5432 bastion
export DATABASE_URL='postgres://readonly_user@localhost:5433/app'
safe-migrate sync
스냅샷은 연결된 역할과 세션 기본값을 반영합니다. 액션 가이드에 설명된 정확도와 자격 증명 트레이드오프를 기반으로 제한된 카탈로그 리더와 실제 마이그레이션 역할 중 하나를 선택하십시오.
캐시에는 스키마, 역할, 권한, 종속성 및 통계를 포함한 인프라 메타데이터가 담겨 있습니다. 자격 증명, 비밀번호 해시 또는 구독 연결 문자열은 포함하고 있지 않지만 여전히 민감하게 취급해야 합니다.
--no-cache는 파서 조사와 제한적인 SQL 전용 검사를 위한 명시적인 저하 모드입니다. 기존 객체는 알 수 없으므로 신뢰도는 Tainted이며 많은 발견 사항이 보수적으로 처리됩니다.
설정 (Configuration)
대부분의 프로젝트는 내장 기본값으로 시작할 수 있습니다. 재정의 값은 safe-migrate.toml에 배치하십시오:
schemas = ["public", "auth"]
tier1_threshold_rows = 100000
...
알 수 없는 설정과 규칙 ID는 거부됩니다. safe-migrate rules --json은 각 규칙이 지원하는 구성을 나열합니다.
동기화된 기준선(baseline) 없이, 내장 버전 폴백(fallback)은 의도적으로 보수적입니다. assume_pg_version은 대상이 PostgreSQL 14–18임을 아는 경우에만 설정하십시오. 예를 들어, assume_pg_version = 170000과 같이 사용합니다.
target_database를 마이그레이션이 적용되는 데이터베이스로 설정하십시오. 예를 들어, target_database = "app"입니다. 이 값에 의존하는 검사는 해당 값이 없으면 알 수 없는 것으로 보고됩니다.
주요 규칙 ID를 사용하여 검토된 발견 사항을 무시합니다:
CREATE INDEX users_email_idx ON users (email);
인라인 무시(Inline suppressions)는 기본적으로 비활성화되어 있습니다. 이를 활성화하려면 safe-migrate.toml에 다음을 추가하십시오:
allow_inline_suppressions = true
이 설정 없이는 인라인 지시문(inline directive)을 사용해도 규칙을 억제하는 대신 Tier 1의 inline-suppression-disabled 발견 사항을 생성합니다.
이는 의도된 동작입니다. TOML 파일에 억제 설정을 유지하면 풀 리퀘스트(pull request)의 diff와 검토 대상이 되기 때문입니다. 표준 억제 경로는 safe-migrate.toml에서 규칙별로 [rules.require-concurrent-index] disabled = true를 사용하는 것입니다.
억제 설정은 범위가 좁도록 유지하고, 마이그레이션 검토 시 그 이유를 설명하십시오.
마이그레이션 타임아웃(Migration timeouts)
마이그레이션 러너(migration runner)가 이미 타임아웃을 설정하지 않은 경우, 잠금에 민감한 변경 사항(lock-sensitive changes) 이전에 다음을 추가하십시오:
SET lock_timeout = '5s';
SET statement_timeout = '15min';
긍정적인 lock_timeout 값은 긍정적인 statement_timeout 값보다 짧게 유지해야 합니다.
Rust 라이브러리
Rust 통합(integrations)에서는 safe_migrate::api를 사용합니다. 사용 가능한 경우 동기화된 기준선(synchronized baseline)을 로드하고, 그렇지 않으면 명시적인 보수적 분석(explicit conservative analysis)을 선택하십시오.
use safe_migrate::api::{self, Baseline, Config};
use std::path::Path;
...
load_optional은 캐시가 누락된 경우만 사용 불가능으로 처리하며, 손상되거나(corrupt), 호환되지 않거나(incompatible), 잘못 암호화된(incorrectly encrypted) 캐시는 여전히 오류로 남습니다. 이 API는 타입이 지정된 불변 발견 사항(typed immutable findings), 판결(verdicts), 증거(evidence), 기준선 검사(baseline inspection), 규칙 메타데이터(rule metadata), 그리고 동기화 기능을 노출합니다. 가변 파서(mutable parser), 캐시, 상태 기계 내부 요소는 공개되지 않습니다. 전체 API 문서는 docs.rs에서 확인할 수 있습니다.
임베디드 애플리케이션은 검증된 DatabaseUrl과 선택적 CacheKey를 사용하여 sync_with_secrets를 호출할 수 있습니다. 이를 통해 프로세스 전체의 환경 변수를 변경하는 것을 방지하며, CLI는 계속해서 환경 변수에서 비밀(secrets)을 읽습니다.
기여하기(Contributing)
개발 명령어, 테스트 스위트, 풀 리퀘스트 기대 사항은 CONTRIBUTING.md를 참조하십시오.
라이선스(License)
MIT 또는 Apache-2.0 하에 이중 라이선스가 적용됩니다.