Harness Open Source
harness/harness — 1 つの Go バイナリに git ホスティング・CI・アーティファクトレジストリ・開発環境を同居させる
GitHub プロジェクト harness/harness(Go モジュール名は github.com/harness/gitness、ビルド成果物も gitness)は、開発プラットフォームの主要機能を 1 つのサーバープロセスにまとめたオープンソース実装である。扱う領域は 4 つ。
- コードホスティング — ベアリポジトリ、smart HTTP と SSH、プルリクエスト、ブランチ保護、コードオーナー、LFS。
- パイプライン — Drone 由来の実行エンジンで、コミットやプルリクエストからコンテナベースの CI を回す。
- アーティファクトレジストリ — OCI/Docker を含む 11 種のパッケージ形式と、上流ミラーリング。
- Gitspaces —
devcontainer.jsonを読んでコンテナ開発環境を起動し、VS Code や JetBrains Gateway から接続させる。
Drone との関係は README に明記されている。Harness Open Source は Drone の次世代版という位置づけで、Drone 時点のコードは drone ブランチにスナップショットとして残され、開発は main で続いている。
バックエンドは Go(go 1.26.6)。フロントエンドは web ディレクトリの React 17 + TypeScript 4.7 + webpack 5 で、ビルド結果は Go バイナリに埋め込まれる。
既定の永続化は SQLite、イベントバス・ロック・pub/sub は既定でインメモリ。Redis・PostgreSQL・S3・GCS はいずれも差し替え可能な選択肢として用意されている。
設定は環境変数 1 本槍で、プレフィックスは GITNESS_。gitness server <envfile> のように env ファイルを渡せる。
エントリポイントは cmd/gitness/main.go。kingpin ベースの CLI(cli/)が server / migrate / user / account / hooks / swagger を登録する。server は env を読み込んでから Google Wire が生成した巨大なインジェクタ(cmd/gitness/wire_gen.go)を呼び、ストア・git サービス・イベント・全コントローラ・レジストリ・パイプライン・Gitspaces を 1 つの System に組み立てる。
組み立て後は初回ブートストラップ(管理者ユーザーと内部呼び出し用のサービスプリンシパル作成)を行い、errgroup で HTTP サーバー、SSH サーバー、ジョブスケジューラ、CI ポーラー、メトリクス収集を並行起動する。停止は SIGINT/SIGTERM でグレースフルに行う。
HTTP の入口は 1 つだが、app/router の Router.ServeHTTP が登録順にサブルーターへ IsEligibleTraffic を尋ね、最初に引き受けたものへ渡す。内部はいずれも go-chi。
flowchart TD
REQ["HTTP リクエスト :3000"] --> R{"Router.ServeHTTP
登録順に IsEligibleTraffic"}
R -->|"git ホスト / git パス"| GIT["git ルーター
info/refs, git-upload-pack,
git-receive-pack, info/lfs"]
R -->|"レジストリ URL"| REG["registry ルーター
/v2/* OCI, /pkg/*"]
R -->|"/api/ 前置"| API["API ルーター
/api/v1/* + openapi.yaml"]
R -->|"それ以外"| WEB["web ルーター
埋め込み SPA + /swagger"]
SSH["SSH :3022"] --> CTRL["repo / lfs コントローラ"]
GIT --> CTRL
API --> CTRL
CTRL --> GITSVC["git サービス"]
| 順 | サブルーター | 受け持つもの |
|---|---|---|
| 0 | git | smart HTTP(info/refs、git-upload-pack、git-receive-pack)、LFS API、go get 用メタ。ホスト名でもパスでも判定できる。 |
| 1 | registry | OCI distribution API(/v2/*)とパッケージ別 API(/pkg/*)、レジストリ用 Swagger。 |
| 2 | api | /api/ 前置を剥がして /api/v1 の REST API へ。認証ミドルウェアはここで適用される。 |
| 3 | web | 埋め込み SPA の配信、/openapi.yaml、Swagger UI。未知パスは / にフォールバックする。 |
Git 操作は libgit2 や go-git ではなく、OS の git コマンドを exec する方式で実装されている。層は 3 段。
| 層 | パッケージ | 役割 |
|---|---|---|
| 実行 | git/command | Command{Globals, Name, Action, Flags, Args, PostSepArgs, Envs} という型で git 起動を組み立てる。ユーザー入力は -- 以降に置く PostSepArgs へ回し、フラグ注入を防ぐ。終了コードと stderr は型付きエラーへ変換する。 |
| アダプタ | git/api | git の概念ごとに 1 ファイル(commit.go、tree.go、diff.go、merge.go、blame.go、cat-file.go、pack-objects.go、gc.go ほか)。foreachref や parser で出力を構造体に落とす。 |
| サービス | git(interface.go / service.go) | ベアリポジトリの配置場所、共有一時ディレクトリ、削除待ちの墓場ディレクトリを管理し、フッククライアントを持つ。Wire で in-process に注入されるため、ネットワーク境界はない。 |
CreateRPCGitPushWriteParams のような RPC 由来の型名が残っているが、このツリーでは git サービスは同一プロセス内の関数呼び出しである。別マイクロサービスとして説明しないこと。
サーバーサイドフックは同じバイナリ
リポジトリ作成時に、hooks/pre-receive と hooks/post-receive が gitness 実行ファイル自身へのシンボリックリンクとして張られる(update は性能上コメントアウト)。push のたびに次の往復が起きる。
ペイロード(リポジトリ ID、プリンシパル ID、リクエスト ID、操作種別、コールバック先 URL)は gob エンコード+base64 で環境変数 GIT_HOOK_PAYLOAD 1 本に詰められる。cli/cli.go は起動時に argv を見てフック呼び出しだと判定すると hooks サブコマンドへ経路を切り替える。フック処理のタイムアウトは 3 分。
SSH 側
ssh/server.go は gliderlabs/ssh のサーバーで、許可コマンドを git-upload-pack / git-receive-pack / git-lfs-authenticate / git-lfs-transfer の 4 つに限定する。公開鍵認証は app/services/publickey が担い、その先は HTTP と同じリポジトリコントローラへ合流する。
CI は Drone のコードベースを依存ライブラリとして取り込む形で実装されている。ランナーは同一プロセス内で動き、ポーラーも HTTP ではなく in-process クライアント経由でジョブを受け取る。
| 依存モジュール | 用途 |
|---|---|
drone-runners/drone-runner-docker | Docker 実行エンジン。レガシー Drone YAML 用の engine と v1 spec 用の engine2 の両方を使う。 |
drone/runner-go | ランナーの骨格(クライアント、ポーラー、ランタイム、レポーター、アップローダ、シークレット)。 |
drone/spec | v1 パイプライン spec のパース・正規化・解決。 |
drone/drone-yaml, drone/drone-go | レガシー YAML のパースと lint、API 型。 |
drone/go-convert, go-generate, funcmap | 変換とテンプレート展開。 |
2 つの YAML 方言
方言の判定は単純で、パイプラインファイルが ^spec: にマッチすれば v1 spec、しなければレガシー Drone YAML として扱う(app/pipeline/triggerer/trigger.go の isV1Yaml)。レガシー側は jsonnet / starlark のコンバータと lint を通り、DAG を組んでからステージ化される。
flowchart TD EV["git / pullreq イベント
(内部イベントバス)"] --> TS["trigger サービス
app/services/trigger"] TS -->|"有効トリガを突合"| TR["triggerer
app/pipeline/triggerer"] TR --> FS["file サービス
指定 ref の YAML を取得"] FS --> DIA{"^spec: か"} DIA -->|"はい"| V1["drone/spec で parse
→ ステージ生成"] DIA -->|"いいえ"| LEG["converter → lint
→ DAG → ステージ生成"] V1 --> EXE["Execution + Stage を
1 トランザクションで保存"] LEG --> EXE EXE --> SCH["scheduler
ステージキュー"] SCH --> POLL["poller(in-process)"] POLL --> MGR["ExecutionManager
Accept / Details / Watch / Write"] MGR --> RUN["Docker エンジンで step 実行"] RUN --> LOG["livelog でライブログ配信"] RUN --> CHK["コミットチェックに結果書き戻し"]
| コンポーネント | 場所 | 役割 |
|---|---|---|
| trigger サービス | app/services/trigger | ブランチ・タグ・プルリクの各イベントを購読し、リポジトリの有効トリガと突き合わせる。 |
| triggerer | app/pipeline/triggerer | Hook を Execution と Stage 群に変換。skip.go がブランチ・イベント・アクション・ref・cron の除外条件を判定する。 |
| file | app/pipeline/file | 内部 git サービスから、指定 ref のパイプライン YAML を読む。 |
| converter | app/pipeline/converter | jsonnet / starlark テンプレートを Drone YAML へ展開する。 |
| manager | app/pipeline/manager | ランナー側から見た API。Details がリポジトリ・実行・ステージ・シークレット・netrc をまとめて渡す。 |
| scheduler | app/pipeline/scheduler | ステージのキューイングとキャンセル通知。分散ロックを併用する。 |
| runner | app/pipeline/runner | Docker エンジンとポーラーの組み立て。API バージョンをデーモンと自動ネゴシエートする。 |
| resolver | app/pipeline/resolver | v1 spec のプラグイン・テンプレート解決。実行 → リポジトリ → スペースの順にスコープを広げて探す。 |
| livelog | livelog | 実行中の step ログをブラウザへ流す。実装はインメモリのみ。 |
plugins/docker と ACR / ECR / GCR / Heroku 系プラグインだけが privileged で動く。Docker ソケットの位置は GITNESS_DOCKER_HOST で変更でき、Rancher Desktop や Colima も README に手順がある。
同じ git イベントバスに、目的の異なる 2 つの購読者がぶら下がっている。app/services/webhook は外向きで、ユーザーが登録した HTTP エンドポイントを叩く。app/services/trigger は内向きで、イベントをパイプライン実行に変える。レジストリ側にも独立した webhook 実装がある。
レジストリは registry ディレクトリ配下の別 Go モジュールで、ルート側の go.mod の replace でローカル参照されている。OCI distribution spec の適合テストがリポジトリに入っており、Makefile から ./gitness server を立てて走らせられる。
対応パッケージ形式
実装は registry/app/pkg の形式別ディレクトリに分かれる。ただし HELM は専用ディレクトリを持たず、Docker/OCI の経路で扱われる(docker のローカル・リモート実装がいずれも DOCKER と HELM を宣言する)。
レジストリの種類とディスパッチ
| 種類 | 意味 |
|---|---|
VIRTUAL | ローカル保管に加え、順序付けした上流レジストリへフォールバックする。取得したアーティファクトはキャッシュされる。 |
UPSTREAM | 外部レジストリを指す定義。Docker Hub、npmjs、PyPI、NuGet、Maven Central、crates.io、Go proxy、RPM、AWS ECR などのアダプタがある。 |
ハンドラの選択は (パッケージ形式, レジストリ種類) をキーにしたファクトリ(registry/app/pkg/base)で行われ、プロキシ有無とチェック有無のラッパーが被せられる。
| ストレージドライバ | 登録名 | 備考 |
|---|---|---|
| ファイルシステム | filesystem | 既定。Docker イメージでは /data/registry に置かれる。 |
| S3 互換 | s3 と R2 名 | 同一ファクトリを 2 つの名前で登録している。 |
| Google Cloud Storage | gcs | — |
ドライバのファクトリ構造は distribution/distribution を踏襲しており、OCI 側ハンドラは blob の resumable upload、manifest、tag 一覧、catalog、referrers(OCI 1.1)、トークン発行までカバーする。ガベージコレクタ(registry/gc)と隔離機構(quarantine)も別立てで用意されている。
quarantine パッケージはアーティファクトの隔離状態を問い合わせて配布をブロックする仕組みで、スキャナ本体はこのリポジトリには含まれない。
Gitspaces は app/gitspace(orchestrator / infrastructure / scm / secret / logutil)と app/services/gitspace*、そしてインフラ抽象の infraprovider で構成される。API は app/api/controller/gitspace と infraprovider。
インフラプロバイダ
抽象インターフェイスは Provision / Find / Stop / CleanupInstanceResources / Deprovision / パラメータ検証などを備え、列挙型には docker、harness_gcp、harness_cloud、hybrid_vm_gcp、hybrid_vm_aws が並ぶ。ただしこの OSS ツリーで実装されているのは Docker だけで、ファクトリにも docker しか登録されない。Kubernetes プロバイダは含まれない。
イベント駆動で中断・再開する起動シーケンス
flowchart TD API["API: start gitspace"] --> ACT["app/services/gitspace
action_start"] ACT --> SCM["SCM 詳細取得
clone 認証 + devcontainer.json 解析"] SCM --> PORT["必要ポート算出
forwardPorts + IDE ポート"] PORT --> PROV["infra provisioner に
Provision イベントを投げて返る"] PROV -.->|"完了イベント"| RES["ResumeStartGitspace"] RES --> CONT["コンテナ orchestrator
作成・起動・features 解決"] CONT --> LC["lifecycle コマンド実行
postCreate / postStart"] LC --> SSHD["コンテナ内 sshd と IDE 準備"] SSHD -.->|"操作完了イベント"| FIN["FinishResumeStartGitspace"]
起動は同期的に完走せず、プロビジョニング完了とコンテナ操作完了がそれぞれイベントとして戻り、ResumeStartGitspace と FinishResumeStartGitspace が続きを引き受ける。停止・削除・クリーンアップも同じ形。状態は設定レベル(running / stopped / starting / stopping / cleaning / error / uninitialized)とインスタンスレベル(resetting、pending_cleanup などを含む 12 種)で二重管理される。
接続できる IDE
各 IDE は app/gitspace/orchestrator/ide の実装をファクトリで選ぶ。VS Code Web はアセットを同梱し、デスクトップ IDE と JetBrains Gateway はコンテナ内で起動する sshd に接続する。
ssh パッケージは git 用の SSH サーバーで、Gitspaces の接続先はコンテナ内で動く sshd である。後者にはプロバイダのポートマッピング経由で到達する。
主体(プリンシパル)は user / serviceaccount / service の 3 種。Admin フラグを持つプリンシパルは検査をバイパスする。内部処理は起動時に作られるシステム・パイプライン・Gitspace のサービスプリンシパルとして自分自身の API を呼ぶ。
リソースはネストしたスペース(パスで表現)の下に並ぶ。認可はリソースをスペースパスに写し、祖先方向にメンバーシップを辿って判定する。公開アクセス設定が先に評価され、次に管理者バイパス、最後にメンバーシップという順序。
reader の権限集合を土台に、上位ロールが積み増す形で静的に定義されている。
権限は 56 個の文字列
権限は <リソース>_<動詞> 形式の文字列で、リポジトリ系だけでも repo_view / repo_create / repo_edit / repo_push / repo_review / repo_delete がある。レジストリ系は registry_* とアーティファクト操作系(artifacts_upload / artifacts_download / artifacts_delete / artifacts_quarantine)に分かれている。
検査の位置
認証(JWT の抽出と検証)はミドルウェアで常に試行されるが、強制はコントローラ側で行う。app/api/auth の CheckRepo、CheckSpace、CheckPipeline、CheckRegistry、CheckGitspace といったヘルパーを各コントローラが呼ぶ。トークンはヘッダの Bearer、HTTP Basic のパスワード、Cookie などから取り出す。git ルートではブラウザのセッショントークンをミドルウェアで弾く。
git-receive-pack は repo_push、git-upload-pack は repo_view を要求する。
永続化は app/store/database.go に並ぶ約 60 のストアインターフェイスと、その実装(app/store/database、sqlx + squirrel)で構成される。プレースホルダは PostgreSQL 形式($1)に固定されており、SQLite でもそのまま通す方針。
| 項目 | 内容 |
|---|---|
| 対応 DB | sqlite3(既定)と postgres。GITNESS_DATABASE_DRIVER / GITNESS_DATABASE_DATASOURCE で切り替える。 |
| マイグレーション | goose ではなく、埋め込み SQL ディレクトリを走らせる方式。PostgreSQL 用と SQLite 用の SQL を go:embed で持ち、番号順に適用する。SQLite では外部キーを一時的に無効化して走らせる。 |
| SQL の規模 | PostgreSQL 用は up/down 合わせて約 390 ファイル、番号は 0189 付近まで。SQLite 用も同数規模。 |
| Go 製の補助 | データ変換が必要な移行は Go コードとして併存する(アーティファクト移行や RPM 移行など)。 |
| CLI | gitness migrate current / gitness migrate to で確認と巻き戻しができる。 |
単体起動をやりやすくするため、分散化が必要な部品はすべて「インメモリ既定 + 外部バックエンド任意」の形で抽象化されている。Redis を入れれば同じインターフェイスのまま複数インスタンス構成に寄せられる。
| パッケージ | 用途 | バックエンド |
|---|---|---|
stream | ストリームの生産者・消費者プリミティブ | インメモリ / Redis Streams |
events | 型付きドメインイベント(at-least-once)。git・repo・pullreq・rule・pipeline・check・gitspace 系のイベントを定義 | インメモリ / Redis |
pubsub | 投げっぱなしの pub/sub。SSE 配信の土台でもある | インメモリ / Redis |
lock | 分散ミューテックス | インメモリ / Redis |
job | DB に永続化するバックグラウンドジョブ。ロックと pub/sub で協調する | DB のみ |
blob | 添付・アップロード用オブジェクトストア | ファイルシステム / GCS |
cache | 型付きキャッシュ(LRU / TTL / Redis / no-op)。git の last-commit キャッシュや権限キャッシュに使う | 複数 |
livelog | CI のライブログ | インメモリのみ |
app/sse | ブラウザへの Server-Sent Events | pubsub に追従 |
フロントエンドは web ディレクトリの単一 yarn パッケージ(パッケージ名 codeui)。React 17 + webpack 5 で、Vite は使っていない。
| ライブラリ | 宣言バージョン | 役割 |
|---|---|---|
react / react-dom | ^17.0.2 | UI ランタイム |
webpack | ^5.58.0 | バンドラ。Module Federation も担う |
react-router-dom | ^5.2.1 | ルーティング(v5 API の Switch 系) |
restful-react | 15.6.0 | データ取得と OpenAPI からのクライアント生成 |
@tanstack/react-query | 4.20.4 | レジストリ側モジュールのクエリキャッシュ |
@blueprintjs/core | 3.26.1 | 基礎ウィジェット |
@harnessio/uicore / design-system / icons | ^4.3.4 / ^2.1.1 / 2.1.22 | 自社コンポーネントとトークン |
monaco-editor + monaco-yaml | ^0.45.0 / ^4.0.4 | パイプライン YAML とファイル編集 |
@codemirror/* | ^6.x | Markdown 入力(コメント・PR 本文) |
diff2html | 3.4.22 | 差分表示 |
sass + typed-scss-modules | ^1.32.8 / ^7.1.4 | Sass CSS Modules と .d.ts 生成 |
jotai | ^2.6.3 | アトム状態(現在ユーザー、リポジトリメタ等) |
highcharts | 9.2.0 | グラフ |
i18n に i18next は使わず、YAML の文言ファイル(strings.en.yaml / strings.es.yaml)を独自フレームワークが読み、useStrings() と <String/> で参照する。型はビルド時に自動生成される。
API 型定義の正は Go コードである。TypeScript クライアントはそこから 3 段で生成される。
app/api/openapiの 26 ファイルがopenapi3.Reflectorにオペレーションを登録する。サーバーは/api/v1、認証スキームは bearer。- 隠しコマンド
gitness swaggerが仕様を YAML で吐く。README の手順どおり、出力をフロントエンドのswagger.yaml(約 1.75 万行)へ流し込む。実行中のサーバーは同じ仕様を/openapi.yamlで配信し、/swaggerに UI を置く。 yarn servicesがrestful-react importを走らせ、useGet*/useMutate*フックと型を含む約 1.13 万行の TSX を生成する。ベース URL はランタイム設定関数から解決する。
oapi-codegen で Go サーバー側のコードを生成する。TypeScript クライアントはリポジトリ内で生成せず、公開済み npm パッケージを取り込む。
1 つの webpack プロジェクトから 3 つのリモートを出力し、react / react-dom / react-router-dom / formik をバージョン固定の singleton として共有する。
| リモート | 中身 | 主な expose |
|---|---|---|
codeRemote | SCM 本体 | ./App、./Repositories、./PullRequests、./Compare、./Webhooks ほか |
harRemote | アーティファクトレジストリ UI | ./HAREnterpriseApp、./HARCreateRegistryButton |
cdeRemote | Gitspaces UI | ./Gitspaces、./GitspaceInfra、./UsageDashboard ほか |
レジストリ UI の合成方法は 2 通り
- OSS バイナリで動くモード: メインアプリのルーティングが
lazy(() => import(...))でレジストリアプリを読み込む。コード分割はされるがネットワーク越しではなく、同じdistに同梱される。アダプタがmatchPath、401 ハンドラ、トークン生成、ルート定義、ホスト側コンポーネントを注入する。 - プラットフォーム埋め込みモード: 同じコードを
./HAREnterpriseAppとして federated module で公開し、親シェルがコンテキストを与える。API クライアントはインターセプタでヘッダと base URL を差し替えるため、レジストリ UI 自体は接続先を知らないまま動く。
Go バイナリは web/dist.go の go:embed dist/* でフロントエンドのビルド成果物を取り込む。dist は git 管理外なので、クリーンなチェックアウトでは make web-build(または Docker の web ステージ)が先に必要。make build は web-build に依存していない点に注意する。
# 手元でひととおり動かす
make dep && make tools # wire, dbmate, goimports, protoc 系
cd web && yarn install && yarn build && cd ..
make build # wire 生成 → go build -o ./gitness
./gitness server .local.env # http://localhost:3000| Makefile ターゲット | 内容 |
|---|---|
dep / tools | wire・dbmate の導入、goimports・govulncheck・protoc プラグイン・gci の導入 |
generate | Google Wire で DI コードを再生成する |
web-build | yarn install && yarn build |
build / run | バイナリ生成と、./gitness server .local.env による起動 |
test | 一部レジストリ結合テストを除外して go test + カバレッジ HTML |
lint / lint-full | golangci-lint。設定はスキーマ v2 で、既定オフの上に約 50 個を明示有効化している |
ar-api-update | レジストリ OpenAPI から Go サーバーコードを生成 |
ar-conformance-test | サーバーを起動して OCI distribution spec 適合テストを走らせる |
ビルドツールは実行時依存と分離され、go.tool.mod 側のモジュールとして go tool -modfile=go.tool.mod で呼ばれる。
公式イメージは 4 ステージ構成。node で web をビルド → golang-alpine で make dep・make tools の後にビルド成果物を web/dist へコピーして静的リンク(CGO_ENABLED=1、arm64 はクロスコンパイラを取得)→ CA 証明書ステージ → alpine/git ベースの最終イメージにバイナリだけを置く。バージョン情報は ldflags で埋め込まれる。
docker run -d \
-p 3000:3000 -p 3022:3022 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /tmp/harness:/data \
--name harness --restart always \
harness/harness| イメージ既定値 | 値 |
|---|---|
GITNESS_GIT_ROOT | /data |
GITNESS_DATABASE_DRIVER / DATASOURCE | sqlite3 / /data/database.sqlite |
GITNESS_REGISTRY_FILESYSTEM_ROOT_DIRECTORY | /data/registry |
GITNESS_SSH_ENABLE / GITNESS_GITSPACE_ENABLE | いずれも true |
| 公開ポート | 3000(HTTP)と 3022(SSH) |
Docker ソケットをマウントするのは、パイプラインの step と Gitspaces のコンテナをホストの Docker で動かすため。/data にはリポジトリ・SQLite・レジストリのブロブが入るので、名前付きボリュームかバインドマウントを当てないとコンテナ停止でデータが消える。
Dockerfile.uiv2 は web ステージを「公開済み UI イメージからファイルをコピーする」だけに置き換える。Go 側は go:embed のままなので、次世代 UI を web に手を入れずに載せられる。開発中は GITNESS_DEVELOPMENT_UI_SOURCE_OVERRIDE で埋め込み FS をローカルディレクトリに差し替えられる。
Kubernetes 向けには charts/gitness に Helm チャート(deployment / service / ingress / pvc / serviceaccount)がある。
- GitHub: harness/harness — 本体リポジトリ。
mainが開発ブランチ - harness/harness の drone ブランチ — Drone のスナップショット
- Harness Open Source ドキュメント — 利用者向けガイド
- Docker Hub: harness/harness — 公開イメージ
- OCI Distribution Specification — レジストリの
/v2/*が準拠する仕様 - Development Containers — Gitspaces が読む
devcontainer.jsonの仕様 - google/wire — 起動時 DI に使われるコード生成ツール
- Drone — パイプライン実装の出自