Процесс миграции нередко представляет собой трудную задачу, особенно, когда объем информации, который необходимо перенести, настолько велик, что выгоднее становится его автоматизировать. Именно необходимость миграции с Gitolite на GitLab и побудила меня написать статью о моем опыте в данном вопросе.
Для миграции репозиториев я буду использовать компьютер с установленной операционной системой CentOS 7. На ней необходимо установить следующий набор приложений: git, ssh-agent, curl, jq, xargs.
Для начала работы нам необходимо получить ключ доступа к API GitLab. В веб-интерфейсе заходим в раздел настройки пользователя. Далее выбираем пункт меню «Access Tokens». В открывшейся форме необходимо указать наименование получаемого ключа, а также можно указать срок, до которого ключ будет активен. Ниже ставим галочку напротив пункта api и нажимаем кнопку «Create personal access token». Этот ключ будет применяться при отправке запросов в API GitLab.
Подготовка рабочей среды
Для того чтобы мы могли загружать код в репозиторий GitLab по SSH, необходимо сгенерировать пару приватного и публичного ключей, выполнить регистрацию публичного ключа в GitLab. Для регистрации публичного ключа в панели администратора GitLab «Admin area» заходим в раздел «Deploy keys» и нажимаем кнопку «New deploy key». В открывшейся форме необходимо указать наименование загружаемого ключа, а также сам публичный ключ. После добавления ключа, нам необходимо определить идентификатор данного ключа в системе GitLab. Чтобы определить его мы можем выполнить следующий команду в bash:
curl -k -X GET --header "PRIVATE-TOKEN: <your private token>" <gitlab url>/api/v4/deploy_keys
Примечание: Чтобы не вводить пароль от приватного SSH ключа при выполнении действий git с сервером, необходимо добавить данный ключ в ssh-agent. Для этого выполним следующие команды:
eval `ssh-agent`
ssh-add ~/.ssh/id_rsa
Создание репозитория в GitLab с использованием API
Чтобы создать репозиторий в GitLab, необходимо отправить POST запрос в API GitLab, передав параметры, согласно документации. Подробнее о Projects API GitLab читайте в документации на сайте GitLab.
# $1 - gitlab remote url # $2 - api access token # $3 - remote repository name # $4 - remote repository namespace identifier initRemoteRepository() { local response=`curl -s -k -X POST --header "Private-Token: $2" --data "name=$3&namespace_id=$4&visibility=private&description=$3" $1/api/v4/projects` local id=`echo $response | jq -r ".id"` echo "$id" }
Функция initRemoteRepository возвращает идентификатор созданного репозитория. В дальнейшем идентификатор репозитория будет использоваться для установки прав доступа.
Примечание: ключ –k необходим, если серверный сертификат GitLab является самозаверенным.
Предоставление доступа на запись и чтение репозитория с использованием API
Для того, чтобы выполнить запись в созданный репозиторий, необходимо выдать соответствующее право. Подробнее о Deploy Keys API GitLab читайте в документации на сайте GitLab.
# $1 - remote url # $2 - api token # $3 - repository identifier # $4 - deploy key identifier enableRepositoryDeployKey() { local response=`curl -s -k -X POST --header "Private-Token: $2" $1/api/v4/projects/$3/deploy_keys/$4/enable` echo "$response" }
Функция enableRepositoryDeployKey позволяет добавить публичный ключ с соответствующим идентификатором в список ключей соответствующего репозитория.
# $1 - remote url # $2 - api token # $3 - repository identifier # $4 - deploy key identifier setRepositoryWriteAccess() { local response=`curl -s -k -X PUT --header "PRIVATE-TOKEN: $2" --header "Content-Type: application/json" --data '{"can_push": true}' $1/api/v4/projects/$3/deploy_keys/$4` echo "$response" }
Функция setRepositoryWriteAccess позволяет установить доступ на запись в конкретный репозиторий.
Миграция репозиториев включая все ветки
Основной скрипт проходит по предварительно составленному списку репозиториев, указанных в заданном файле. Создается соответствующий репозиторий в GitLab, а также выдаются привилегии для чтения и записи. После подготовительных действий выполняется запрос веток данного репозитория. Далее выполняется переключение на очередную ветку репозитория и отправка в GitLab.
# $1 - branch # $2 – separator getCanonicalBranchName() { local branch="" IFS=$2 read -r -a array <<< "$1" length=${#array[@]} for index in "${!array[@]}" do if [ $index -gt 0 ]; then if [ $index -eq 1 ]; then local branch="${array[index]}" else local branch="$branch/${array[index]}" fi fi done echo "$branch" } PSWD="$(dirname "$0")" cd $PSWD while IFS= read -r REPOSITORY do echo "==================== <$REPOSITORY> ====================" GITOLITE_REPO=$GITOLITE$REPOSITORY echo "Run cloning remote repository $GITOLITE_REPO" git clone $GITOLITE_REPO cd $REPOSITORY GITLAB_REPO=$GITLAB$REPOSITORY.git echo "Initialize remote gitgab repository $GITLAB_REPO" PROJECT=$(initRemoteRepository $GITLAB_URL $API_TOKEN $REPOSITORY) echo "Remote repository initialized identifier $PROJECT" echo "Add deploy key and enable write access to remote repository $REPOSITORY ($PROJECT)" response=$(enableRepositoryDeployKey $GITLAB_URL $API_TOKEN $PROJECT $DEPLOY_KEY_ID) response =$(setRepositoryWriteAccess $GITLAB_URL $API_TOKEN $PROJECT $DEPLOY_KEY_ID) echo "Add new remote url for repository $GITLAB_REPO" git remote add gitlab $GITLAB_REPO # push all branches IFS_BACK=$IFS IFS=$'\n' branches=$(git branch -r) for branchName in $branches; do trimBranchName=`echo $branchName | xargs` canonicalBranchName=$(getCanonicalBranchName $trimBranchName '/') echo "$trimBranchName ($canonicalBranchName) init push" git checkout -b $canonicalBranchName remotes/origin/$canonicalBranchName git push -f gitlab $canonicalBranchName done IFS=$IFS_BACK cd .. rm -r -f $REPOSITORY echo "==================== <$REPOSITORY> ====================" echo "" done < "$REPOSITORIES"
Сохраняем файл скрипта и запускаем, при необходимости изменив права доступа. Пример вывода работы данного скрипта можно посмотреть ниже.
REPOSITORIES="<repositories_file>"
GITOLITE="<gitolite_ssh_url>"
GITLAB_URL="<gitlab_api_url>"
GITLAB="<gitolite_ssh_url>"
API_TOKEN="<api_access_token>"
DEPLOY_KEY_ID="<deploy_key_identifier>"
# $1 - gitlab remote url
# $2 - api access token
# $3 - remote repository name
# $4 - remote repository namespace identifier
initRemoteRepository() {
local response=`curl -s -k -X POST --header "Private-Token: $2" --data "name=$3&namespace_id=$4&visibility=private&description=$3" $1/api/v4/projects`
local id=`echo $response | jq -r ".id"`
echo "$id"
}
# $1 - remote url
# $2 - api token
# $3 - repository identifier
# $4 - deploy key identifier
setRepositoryWriteAccess() {
local response=`curl -s -k -X PUT --header "PRIVATE-TOKEN: $2" --header "Content-Type: application/json" --data '{"can_push": true}' $1/api/v4/projects/$3/deploy_keys/$4`
echo "$response"
}
# $1 - remote url
# $2 - api token
# $3 - repository identifier
# $4 - deploy key identifier
enableRepositoryDeployKey() {
local response=`curl -s -k -X POST --header "Private-Token: $2" $1/api/v4/projects/$3/deploy_keys/$4/enable`
echo "$response"
}
# $1 - branch
# $2 – separator
getCanonicalBranchName() {
local branch=""
IFS=$2 read -r -a array <<< "$1"
length=${#array[@]}
for index in "${!array[@]}"
do
if [ $index -gt 0 ]; then
if [ $index -eq 1 ]; then
local branch="${array[index]}"
else
local branch="$branch/${array[index]}"
fi
fi
done
echo "$branch"
}
PSWD="$(dirname "$0")"
cd $PSWD
while IFS= read -r REPOSITORY
do
echo "==================== <$REPOSITORY> ===================="
GITOLITE_REPO=$GITOLITE$REPOSITORY
echo "Run cloning remote repository $GITOLITE_REPO"
git clone $GITOLITE_REPO
cd $REPOSITORY
GITLAB_REPO=$GITLAB$REPOSITORY.git
echo "Initialize remote gitgab repository $GITLAB_REPO"
PROJECT=$(initRemoteRepository $GITLAB_URL $API_TOKEN $REPOSITORY)
echo "Remote repository initialized identifier $PROJECT"
echo "Add deploy key and enable write access to remote repository $REPOSITORY ($PROJECT)"
response=$(enableRepositoryDeployKey $GITLAB_URL $API_TOKEN $PROJECT $DEPLOY_KEY_ID)
response=$(setRepositoryWriteAccess $GITLAB_URL $API_TOKEN $PROJECT $DEPLOY_KEY_ID)
echo "Add new remote url for repository $GITLAB_REPO"
git remote add gitlab $GITLAB_REPO
# push all branches
IFS_BACK=$IFS
IFS=$'\n'
branches=$(git branch -r)
for branchName in $branches; do
trimBranchName=`echo $branchName | xargs`
canonicalBranchName=$(getCanonicalBranchName $trimBranchName '/')
echo "$trimBranchName ($canonicalBranchName) init push"
git checkout -b $canonicalBranchName remotes/origin/$canonicalBranchName
git push -f gitlab $canonicalBranchName
done
IFS=$IFS_BACK
cd ..
rm -r -f $REPOSITORY
echo "==================== <$REPOSITORY> ===================="
echo ""
done < "$REPOSITORIES"
==================== <project> ====================
Run cloning remote repository git@skynet-uro.bank.srv:project
Cloning into 'project'...
remote: Counting objects: 62, done.
remote: Compressing objects: 100% (61/61), done.
remote: Total 62 (delta 21), reused 0 (delta 0)
Receiving objects: 100% (62/62), 15.57 KiB | 0 bytes/s, done.
Resolving deltas: 100% (21/21), done.
Current folder path: /app/migration/project
Initialize remote gitgab repository git@127.0.0.1:project.git
Remote repository initialized identifier 222
Enable deploy key write access to remote repository project (222)
Set write access to remote repository project (222)
Add new remote url for repository git@127.0.0.1:project.git
Push to new remote gitlab repository git@127.0.0.1:project.git
Counting objects: 55, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (38/38), done.
Writing objects: 100% (55/55), 14.65 KiB | 0 bytes/s, done.
Total 55 (delta 18), reused 50 (delta 16)
To git@127.0.0.1:project.git
* [new branch] master -> master
<--- gitlab/master (master) init push
fatal: A branch named 'master' already exists.
Everything up-to-date
---> gitlab/master (master) success pushed
<--- origin/v0.0.0 (v0.0.0) init push
Branch v0.0.0 set up to track remote branch v0.0.0 from origin.
Switched to a new branch 'v0.0.0'
Total 0 (delta 0), reused 0 (delta 0)
remote:
remote: To create a merge request for v0.0.0, visit:
remote: https://127.0.0.1:8082/project/merge_requests/new?merge_request%5Bsource_branch%5D=v0.0.0
remote:
To git@127.0.0.1:project.git
* [new branch] v0.0.0 -> v0.0.0
---> origin/v0.0.0 (v0.0.0) success pushed
<--- origin/v0.0.0-13 (v0.0.0-13) init push
Branch v0.0.0-13 set up to track remote branch v0.0.0-13 from origin.
Switched to a new branch 'v0.0.0-13'
Total 0 (delta 0), reused 0 (delta 0)
remote:
remote: To create a merge request for v0.0.0-13, visit:
remote: https://127.0.0.1:8082/project/merge_requests/new?merge_request%5Bsource_branch%5D=v0.0.0-13
remote:
To git@127.0.0.1:project.git
* [new branch] v0.0.0-13 -> v0.0.0-13
---> origin/v0.0.0-13 (v0.0.0-13) success pushed
<--- origin/v0.0.0-15 (v0.0.0-15) init push
Branch v0.0.0-15 set up to track remote branch v0.0.0-15 from origin.
Switched to a new branch 'v0.0.0-15'
Total 0 (delta 0), reused 0 (delta 0)
remote:
remote: To create a merge request for v0.0.0-15, visit:
remote: https://127.0.0.1:8082/project/merge_requests/new?merge_request%5Bsource_branch%5D=v0.0.0-15
remote:
To git@127.0.0.1:project.git
* [new branch] v0.0.0-15 -> v0.0.0-15
---> origin/v0.0.0-15 (v0.0.0-15) success pushed
<--- origin/v0.0.1 (v0.0.1) init push
Branch v0.0.1 set up to track remote branch v0.0.1 from origin.
Switched to a new branch 'v0.0.1'
Total 0 (delta 0), reused 0 (delta 0)
remote:
remote: To create a merge request for v0.0.1, visit:
remote: https://127.0.0.1:8082/project/merge_requests/new?merge_request%5Bsource_branch%5D=v0.0.1
remote:
To git@127.0.0.1:project.git
* [new branch] v0.0.1 -> v0.0.1
---> origin/v0.0.1 (v0.0.1) success pushed
<--- origin/v0.0.1-11 (v0.0.1-11) init push
Branch v0.0.1-11 set up to track remote branch v0.0.1-11 from origin.
Switched to a new branch 'v0.0.1-11'
Total 0 (delta 0), reused 0 (delta 0)
remote:
remote: To create a merge request for v0.0.1-11, visit:
remote: https://127.0.0.1:8082/project/merge_requests/new?merge_request%5Bsource_branch%5D=v0.0.1-11
remote:
To git@127.0.0.1:project.git
* [new branch] v0.0.1-11 -> v0.0.1-11
---> origin/v0.0.1-11 (v0.0.1-11) success pushed
<--- origin/v0.0.2 (v0.0.2) init push
Branch v0.0.2 set up to track remote branch v0.0.2 from origin.
Switched to a new branch 'v0.0.2'
Total 0 (delta 0), reused 0 (delta 0)
remote:
remote: To create a merge request for v0.0.2, visit:
remote: https://127.0.0.1:8082/project/merge_requests/new?merge_request%5Bsource_branch%5D=v0.0.2
remote:
To git@127.0.0.1:project.git
* [new branch] v0.0.2 -> v0.0.2
---> origin/v0.0.2 (v0.0.2) success pushed
<--- origin/v0.0.3 (v0.0.3) init push
Branch v0.0.3 set up to track remote branch v0.0.3 from origin.
Switched to a new branch 'v0.0.3'
Counting objects: 7, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 592 bytes | 0 bytes/s, done.
Total 4 (delta 3), reused 1 (delta 0)
remote:
remote: To create a merge request for v0.0.3, visit:
remote: https://127.0.0.1:8082/project/merge_requests/new?merge_request%5Bsource_branch%5D=v0.0.3
remote:
To git@127.0.0.1:project.git
* [new branch] v0.0.3 -> v0.0.3
---> origin/v0.0.3 (v0.0.3) success pushed
==================== <project> ====================