
Позвольте мне для начала немного пофилософствовать на тему технологий. Технологии позволяют нам концентрироваться на результате, на конечной цели, дают ощущение контроля. Вот вы в белоснежном кителе на мостике своего технологичного лайнера выходите в очередной плавание. Ваш лайнер снаряжен всем необходимым, чтобы противостоять любой проблеме. Вам не страшны волны, айсберги и даже пьяные боцманы.
Вообще лирическое вступление было навеяно вполне конкретной историей про сломанный гитхаб. Сделанная на заре одного из проектов синхронизация домашнего репо в гитхаб решила проблему переезда. Потом про костыль забыли. Древнее зло уснуло и терпеливо ждало своего часа. В один прекрасный день
И все же зачем..?
Действительно, можно нафантазировать большое количество количество возможных проблем, например:
- Недоступность удаленного репозитория и отсутсвие актуальной локальной копии
- Злоумышленники с помощью украденного пароля испортили/удалили репозитории
- Ошибки в манипуляциях с репозиториями
Итак, что и как бэкапить...
Вот вкратце наш хитрый план действий:
- Получаем список репозиториев для организации
- Клонируем репозитории поз полученного списка
- Архивируем
- Кладем в AWS S3
Немного больше конкретики в случае использования github.com
Разумно завести для процедуры бэкапа отдельного readonly-пользователя. Так же необходимо сгенерировать для него token(Settings -> Personal Access Tokens -> Generate new token).
Для начала с помощью pygithub3 получаем репозитории, которые мы впоследствии собираемся бэкапить:
from pygithub3 import Github
def get_repos(args):
config = {'token': args.token}
gh = Github(**config)
return gh.repos.list_by_org(args.organization).all()
Для клонирования будем использовать консольный git:
def clone_repo(repo_list,args):
if os.path.isdir(args.directory):
shutil.rmtree(args.directory)
os.mkdir(args.directory)
if args.mirror is True:
args.git += " --mirror"
for repo in repo_list:
repo_url = "https://%(token)s:x-oauth-basic@github.com/%(organization)s/%(repo)s.git" % {'token': args.token,
'organization': args.organization,
'repo': repo.name}
os.system('git clone %(arguments)s %(repo_url)s %(directory)s/%(repo)s' % {'arguments': args.git,
'repo_url': repo_url,
'directory': args.directory, 'repo': repo.name})
Обратите внимание на опцию "--mirror" — с помощью нее создается зеркальная копия удаленного репозитория.
Кстати, в случае использования bitbucket.org...
Получаем список репозиториев:
def _get_repositories(owner, username, password):
auth_value = ('%s:%s' % (username, password)).encode('base64').strip()
headers = {'Authorization': 'Basic %s' % auth_value}
url = 'https://bitbucket.org/api/2.0/repositories/%s?role=member' % owner
values = []
while url is not None:
request = urllib2.Request(url, None, headers)
data = json.loads(urllib2.urlopen(request).read())
values = values + data['values']
url = data.get('next')
return values
И клонируем:
def _git_clone(username, password, directory, sub_dir_name, owner, slug, verbose=False):
os.chdir(directory)
cmd = 'git clone --mirror https://%s:%s@bitbucket.org/%s/%s.git %s' % (username, password,
owner, slug, sub_dir_name)
proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
ret_value = proc.wait()
msg = proc.stdout.read()
sys.stdout.write('%s%s%s%s' % (sub_dir_name, os.linesep,
'=' * len(sub_dir_name), os.linesep))
sys.stdout.write("%s%s" % (msg, os.linesep))
return ret_value
Кстати, slug — это url-friendly название вашего репо в bitbucket.
Готовый скрипт для github можно найти тут.
Комментарии (24)
altexxx
20.01.2016 19:56+2cmd = 'git clone --mirror %s:%s@bitbucket.org/%s/%s.git %s' % (username, password, owner, slug, sub_dir_name)
Я бы не рекомендовал использовать пароль от аккаунта для этого.
И в github и bitbucket можно добавить deployment key, который работает для read-only операций с репозиторием. С его помощью можно склонировать репозитории, но нельзя пушить и делать деструктивные действия.
С другой стороны, если сам список репозиториев в bb нельзя получить без пароля (не знаю, есть ли там oauth и т.п.), тогда это не так важно.
Ещё я бы рекомендовал хранить локальный кеш репозиториев, тоесть сохранять все репозитории с предыдущего бекапа. Это позволяло бы не делать каждый раз clone --mirror, а делать git remote update. Таким образом выкачивались бы каждый раз только новые коммиты и изменения в репозиториях, а не целиком все репозитории со всеми ветками с нуля. Для крупных репозиториев это работает существевнно быстрее.
В остальном нормальное решение, сам так делаю.
maxp
Странная проблема, конечно.
Если предположить, что на компьтере уже есть ключи для гитхаба.битбакета, и имена репозиториев известны.
(Было бы странно, если бы разработчики не знали, какие у них репы. Да ведь?).
То весь бэкап делается примерно так:
cd ~/repos/; for i in `ls`; do (cd $i; git pull) done
maxp
Как можно заметить этой команде абслютно пофиг на каком гит сервере лежит все богатство, надо только чтобы в репах ориджин был на месте.
Если кто не в курсе, то юзера, хосты, ключи и алиасы замечательно настраиваются в ~/.ssh/config
yaak
Можете пояснить свое первую мысль?
yaak
1) Что надо сделать, чтобы уведомить вашу систему бэкапа о добавлении новых репозиториев?
2) Что делает ваша команда git pull? что-то мне подсказывает, что мерджит текущую ветку с ориджином. Как быть с остальными?
maxp
Добавить репо?
cd ~/repos; git clone ....where...new...repo...is…
Или у вас все репы лежат в одном экаунте в Гитхабе? Ну это вам пока повезло.
Практика показывает, что нужная репа внезапно может оказаться на чьем-то личном ГитЛабе или еще непойми где.
git pull делает что обычно.
Там есть еще перед ней команда cd и еще перед ними for… `ls`.
Но если это вызывает вопросы, то пролистайте какой-нибудь тюториал по башу —
сэкономите себе кучу времени.
С точки зрения программиста баш выглядит ужасно, но я этой одной строкой решаю практически аналогичную задачу у себя. А у вас там пара страниц кода написана, который еще как-то там настраивать надо.
yaak
В вашем решении есть две проблемы: