新年あけましておめでとうございます。ことよろ。
最近 Forkwell のポートフォリオ機能を弄っている@sinsoku です。
今日は弊社で起きた「リポジトリが突然消えた事件」について書きたいと思います。
事の始まり
昨年末の26日の朝、 fork したけど使っていないリポジトリがあったので、何気なく削除しました。 その30分後、同僚のapp2641に声をかけられました。
app2641:「なぜか(メイン)リポジトリが404なんですが、sinsoku さん何か知ってます?」
sinsoku:「え、いや、分からないです。私の方でも調べてみます。(もしかして...)」
自分でもリポジトリのページを表示してみました。
404 ですね。マジか...。Audit log を確認してみるか。
見覚えあるアイコンの横に repo.destroy て書いてあるじゃないか...o..rz
GitHub のサポートへ連絡
下記の内容で問合せました。
Hi, GitHub Team. I accidentally deleted a "grooves/xxx" repository. Can you resurrect it?
その後、サポートの人から返ってきた返信を要約すると下記の通り。
- GitHub ではリポジトリを復元できるようにアーカイブに保存している
- grooves/xxx のリポジトリは検索したけど、アーカイブに存在しない
- sinsoku/xxx のリポジトリはあるけど、これ?
GitHub では削除した後に問い合わせれば復元できるっぽいですね。 通常なら
I can't see a "grooves/xxx" repo. https://github.com/grooves/xxx And Audit log shows "repo.destroy".
と送ったところ、「技術的な問題があって復元できなかった。エンジニアチームに尋ねた」と返ってきた。
(´-`).。oO(もしかして GitHub のバグを踏んでしまいリポジトリが死んだのでは...)
そして新年
年末年始で返信もなく GitHub も休んでそうだったので、連休明けの 1/11(水) に進捗を確認してみた。
Could you tell us about the current status? The repository is not restored yet. How long will it take?
翌日1/12(木)の夜には返信があって、無事にリポジトリが復活した。
Sorry for the delay on this one but the repository should now be restored.
リポジトリが無い期間の開発
初日
すぐ復活するだろうと思っていたのと小さい変更だったので、2つだけ Slack上で diff をレビュー してデプロイしました。
「手順書を用意 => バックアップ作成 => パッチ適用 => ホットデプロイ」を手動でやるとか、旧世代のデプロイに懐かしさを感じた1日。
2日目以降
リポジトリの復元に時間かかりそうな返信内容だったのと、さすがに 2017年にもなって手動デプロイとか無理 なので、一時リポジトリを作成して作業することになりました。
- 一時リポジトリの作成
- CI の環境構築
- Capistrano の設定変更
こういうとき Git だとリポジトリの複製が楽で良いですね。
リポジトリが復活した後
Capistrano の設定を戻し、一時リポジトリの Pull Request, Review Comment はスクリプトを書いてバックアップを保存しました。
#!/usr/bin/env ruby # frozen_string_literal: true require 'fileutils' require 'octokit' OWNER = 'grooves' REPO = 'tmp_repo' FULL_NAME = "#{OWNER}/#{REPO}" DOC_PATH = File.expand_path('../../doc', __FILE__) PULLS_PATH = "#{DOC_PATH}/#{REPO}/pulls" COMMENTS_PATH = "#{DOC_PATH}/#{REPO}/review_comments" Octokit.auto_paginate = true class RepoBackup attr_reader :token def initialize @token = ENV['TOKEN'] end def save_pulls mkdir_p(PULLS_PATH) pulls = client.pulls(FULL_NAME, state: :all) pulls.each do |pull| write_json("#{PULLS_PATH}/#{pull[:number]}", pull) puts "saved pulls##{pull[:number]}" end end def save_comments mkdir_p(COMMENTS_PATH) comments = client.reviews_comments(FULL_NAME) comments.each do |comment| write_json("#{COMMENTS_PATH}/#{comment[:id]}", comment) puts "saved review_comment##{comment[:id]}" end end private def client @client ||= Octokit::Client.new(access_token: token) end def mkdir_p(path) FileUtils.mkdir_p(path) unless FileTest.exist?(path) end def write_json(path, obj) File.write(path, JSON.pretty_generate(obj.to_h)) end end backup = RepoBackup.new if backup.token puts 'Start backup script' backup.save_pulls backup.save_comments puts "Backup finished and saved to #{DOC_PATH}." else puts 'USAGE: TOKEN=xxx ./script/backup_forkwell_tmp' end
対策方法
通常なら GitHub に問い合わせれば復元できるっぽいので、厳重な対策はしなくて良いかもしれません。 ただ、公表されているサポートって訳でも無いですし、数時間でもリポジトリが消えたら困るってケースもあると思うので、いくつか方法を挙げておきます。
1. 削除時の確認
今回の件で初めて知ったのですが、リポジトリを削除するときの確認画面でリポジトリ名だけでなく ユーザー名 も入力することが可能です。
削除時は sinsoku/xxx
のようにユーザー名(もしくは Organization 名)を含めて入力し、念のためスクリーンショットを保存しておくのが良さそうです。
2. 開発者に Admin 権限をつけない
Terraform の GitHub provider を使えば、Organization のリポジトリをコードで管理することが可能です。
- Admin を1人だけ作成する
- リポジトリを削除するときは Pull Request でレビューしてからマージする
- マージ後 Admin の token を使って
terraform apply
でリポジトリを削除する
上記のような運用を行うことで、より安全なリポジトリ管理ができるかと思います。
3. 外部サービス
BackHub という GitHub のリポジトリを定期的にバックアップしてくれるサービスを見つけました。 使ったことがないので詳細は不明ですが Issue, Pull Request などを定期的に保存するなら便利そうです。
まとめ
GitHub のバグなのか、私の操作ミスなのか最終的な原因は分かりませんでしたが、リポジトリが無事に復旧して良かったです。
この記事が皆様の 参考にならない事 を祈っていますが、もしリポジトリが消えてしまった時は本記事を参考にして頑張ってください。