Налаштування GitLab CI для завантаження java проекту в maven central
Ця стаття розрахована на java розробників, у яких виникла потреба швидко публікувати свої продукти в репозиторіях sonatype та/або maven central з використанням GitLab. У цій статті я розповім про налаштування gitlab-runner, gitlab-ci та maven-plugin для вирішення цього завдання.
передумови:
Безпечне зберігання mvn та GPG ключів.
Безпечне виконання публічних CI завдань.
Завантаження артефактів (release/snapshot) у громадські репозиторії.
Автоматична перевірка release-версій для публікації в maven central.
Загальне рішення щодо завантаження артефактів у репозиторій для кількох проектів.
Детальний опис механізму публікації артефактів у Maven Central через Sonatype OSS Repository Hosting Service вже описано в цій статті користувачем Googolplex, тому в потрібних місцях посилатимуся на цю статтю.
Попередньо реєструємось у Sonatype JIRA і заводимо тикет на відкриття репозиторію (детальніше читати розділ Створюємо тикет на Sonatype JIRA). Після відкриття репозиторію пара логін/пароль від JIRA (далі обліковий запис Sonatype) буде використовуватися для завантаження артефактів Sonatype nexus.
Якщо ви використовуєте Linux консоль для генерації GPG ключа (gnupg/gnupg2), необхідно встановити rng-tools для створення ентропії. А якщо ні, то генерація ключа може проходити дуже довго.
Насамперед необхідно створити та налаштувати проект, у якому зберігатиметься pipeline, для деплою артефактів. Свій проект я назвав просто і нехитро. розгортання
Після створення репозиторію необхідно обмежити доступ на зміну репозиторію.
Переходимо в проект -> Settings -> Repository -> Protected Branches. Видаляємо всі правила і додаємо єдине правило з Wildcard * з правом на push і merge тільки для користувачів за участю Maintainers. Це правило буде працювати для всіх користувачів як даного проекту, так і групи, в яку цей проект входить.
Якщо мейнтейнерів кілька, то найкращим рішенням обмежитиме доступ до проекту в принципі.
Переходимо в проект -> Settings -> General -> Visibility, project features, permissions і виставляємо Project visibility на значення приватний.
У мене є проект у публічному доступі, тому що я використовую власний GitLab Runner і доступ на зміну репозиторію є тільки у мене. Та й власне не в моїх інтересах світити приватну інформацію в публічних pipeline-логах.
Посилення правил зміну репозиторію
Переходимо в проект -> Settings -> Repository -> Push Rules і встановлюємо прапори Committer restriction, Check whether author is a GitLab user. Також рекомендую налаштувати підпис комітів, та встановити прапор Reject unsigned commits.
Далі потрібно налаштувати тригер для запуску завдань
Переходимо в проект -> Settings -> CI/CD -> Pipeline triggers і створюємо новий trigger-token
Цей токен можна відразу додати до загальної конфігурації змінних для групи проектів.
Переходимо до групи -> Settings -> CI/CD -> Variables і додаємо змінну DEPLOY_TOKEN з trigger-token у значенні.
У цьому розділі описано конфігурацію для запуску завдань на deploy з використанням власного (Specific) та публічного (Shared) раннера.
Specific Runner
Я використовую власні раннери, тому що насамперед це зручно, швидко, дешево.
Для раннера рекомендую лінуксову VDS із 1 CPU, 2 GB RAM, 20 GB HDD. Ціна питання ~3000₽ на рік.
Мій раннер
Для раннера я взяв VDS 4 CPU, 4 GB RAM, 50 GB SSD. Обійшлася ~11000₽ і жодного разу не пошкодував.
У мене в цілому 7 машинок. 5 на aruba та 2 на ihor.
Отже, у нас є раннер. Тепер ми його налаштовуватимемо.
Заходимо на машинку SSH і встановлюємо java, git, maven, gnupg2.
Створюємо директорію для maven кешу та навішуємо права групи runner
Цей пункт можна пропустити, якщо ви не плануєте запускати кілька раннерів на одній машині.
Runtime platform arch=amd64 os=linux pid=17594 revision=3001a600 version=11.10.0
Running in system-mode.
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
https://gitlab.com/
Please enter the gitlab-ci token for this runner:
REGISTRATION_TOKEN
Please enter the gitlab-ci description for this runner:
[ih1174328.vds.myihor.ru]: Deploy Runner
Please enter the gitlab-ci tags for this runner (comma separated):
deploy
Registering runner... succeeded runner=ZvKdjJhx
Please enter the executor: docker-ssh, parallels, virtualbox, docker-ssh+machine, kubernetes, docker, ssh, docker+machine, shell:
shell
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!
Перевіряємо, що раннер зареєстрований. Переходимо на сайт gitlab.com -> deploy-project -> Settings -> CI/CD -> Runners -> Specific Runners -> Runners activated for this project
скрін
додаємо окремий сервіс /etc/systemd/system/gitlab-deployer.service
Генеруємо ключ відповідаючи на запитання. Я використав власні ім'я та пошту.
Обов'язково вказуємо пароль для ключа. Даним ключем підписуватимуться артефакти.
gpg --gen-key
перевіряємо
gpg --list-keys -a
/home/gitlab-deployer/.gnupg/pubring.gpg
----------------------------------------
pub 4096R/00000000 2019-04-19
uid Petruha Petrov <[email protected]>
sub 4096R/11111111 2019-04-19
Завантажуємо наш публічний ключ на сервер ключів
gpg --keyserver keys.gnupg.net --send-key 00000000
gpg: sending key 00000000 to hkp server keys.gnupg.net
Створюємо диреторію maven Сховище і лінкуємо з кешем (не помилитеся)
Цей пункт можна пропустити, якщо ви не плануєте запустити кілька раннерів на одній машині.
Додаємо у корінь deploy-проекту файл .gitlab-ci.yml
У скрипті представлено два взаємовиключні завдання на деплой. Specific Runner або Shared Runner відповідно.
.gitlab-ci.yml
stages:
- deploy
Specific Runner:
extends: .java_deploy_template
# Задача будет выполняться на вашем shell-раннере
tags:
- deploy
Shared Runner:
extends: .java_deploy_template
# Задача будет выполняться на публичном docker-раннере
tags:
- docker
# Образ из раздела GitLab Runner -> Shared Runner -> Docker
image: registry.gitlab.com/group/deploy-project:latest
before_script:
# Импортируем GPG ключ
- printf "${GPG_SECRET_KEY}" | gpg --batch --import
# Сохраняем maven конфигурацию
- printf "${SETTINGS_SECURITY_XML}" > ~/.m2/settings-security.xml
- printf "${SETTINGS_XML}" > ~/.m2/settings.xml
.java_deploy_template:
stage: deploy
# Задача сработает по триггеру, если передана переменная DEPLOY со значением java
only:
variables:
- $DEPLOY == "java"
variables:
# отключаем клонирование текущего проекта
GIT_STRATEGY: none
script:
# Предоставляем возможность хранения пароля в незашифрованном виде
- git config --global credential.helper store
# Сохраняем временные креды пользователя gitlab-ci-token
# Токен работает для всех публичных проектов gitlab.com и для проектов группы
- echo "https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com" >> ~/.git-credentials
# Полностью чистим текущую директорию
- rm -rf .* *
# Клонируем проект который, будем деплоить в Sonatype Nexus
- git clone ${DEPLOY_CI_REPOSITORY_URL} .
# Переключаемся на нужный коммит
- git checkout ${DEPLOY_CI_COMMIT_SHA} -f
# Если хоть один pom.xml содержит параметр autoReleaseAfterClose валим сборку.
# В противном случае есть риск залить сырые артефакты в maven central
- >
for pom in $(find . -name pom.xml); do
if [[ $(grep -q autoReleaseAfterClose "$pom" && echo $?) == 0 ]]; then
echo "File $pom contains prohibited setting: <autoReleaseAfterClose>";
exit 1;
fi;
done
# Если параметр DEPLOY_CI_COMMIT_TAG пустой, то принудительно ставим SNAPSHOT-версию
- >
if [[ "${DEPLOY_CI_COMMIT_TAG}" != "" ]]; then
mvn versions:set -DnewVersion=${DEPLOY_CI_COMMIT_TAG}
else
VERSION=$(mvn -q -Dexec.executable=echo -Dexec.args='${project.version}' --non-recursive exec:exec)
if [[ "${VERSION}" == *-SNAPSHOT ]]; then
mvn versions:set -DnewVersion=${VERSION}
else
mvn versions:set -DnewVersion=${VERSION}-SNAPSHOT
fi
fi
# Запускаем задачу на сборку и деплой артефактов
- mvn clean deploy -DskipTests=true
Якщо у вас багатомодульний проект, і вам не потрібно завантажувати певний модуль в репозиторій, то в pom.xml даного модуля необхідно додати nexus-staging-maven-plugin з прапором skipNexusStagingDeployMojo
<repositories>
<repository>
<id>SonatypeNexus</id>
<url>https://oss.sonatype.org/content/groups/staging/</url>
<!-- Не надо указывать флаги snapshot/release для репозитория -->
</repository>
</repositories>
Ще плюси
Дуже багатий список цілей для роботи з nexus репозиторієм (mvn help:describe -Dplugin=org.sonatype.plugins:nexus-staging-maven-plugin).
Автоматична перевірка релізу на можливість завантаження в maven central
При встановленні тега автоматично тригериться відповідне завдання в проекті deploy на завантаження релізної версії в nexus (приклад).
Найприємніше, що автоматично спрацьовує close release у nexus.
[INFO] Performing remote staging...
[INFO]
[INFO] * Remote staging into staging profile ID "9043b43f77dcc9"
[INFO] * Created staging repository with ID "orgtouchbit-1037".
[INFO] * Staging repository at https://oss.sonatype.org:443/service/local/staging/deployByRepositoryId/orgtouchbit-1037
[INFO] * Uploading locally staged artifacts to profile org.touchbit
[INFO] * Upload of locally staged artifacts finished.
[INFO] * Closing staging repository with ID "orgtouchbit-1037".
Waiting for operation to complete...
.........
[INFO] Remote staged 1 repositories, finished with success.
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] Shields4J 1.0.0 .................................... SUCCESS [ 9.603 s]
[INFO] test-core .......................................... SUCCESS [ 3.419 s]
[INFO] Shields4J client ................................... SUCCESS [ 9.793 s]
[INFO] TestNG listener 1.0.0 .............................. SUCCESS [01:23 min]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 01:47 min
[INFO] Finished at: 2019-04-21T04:05:46+03:00
[INFO] ------------------------------------------------------------------------
І якщо щось пішло не так, то завдання обов'язково завалиться
[INFO] Performing remote staging...
[INFO]
[INFO] * Remote staging into staging profile ID "9043b43f77dcc9"
[INFO] * Created staging repository with ID "orgtouchbit-1038".
[INFO] * Staging repository at https://oss.sonatype.org:443/service/local/staging/deployByRepositoryId/orgtouchbit-1038
[INFO] * Uploading locally staged artifacts to profile org.touchbit
[INFO] * Upload of locally staged artifacts finished.
[INFO] * Closing staging repository with ID "orgtouchbit-1038".
Waiting for operation to complete...
.......
[ERROR] Rule failure while trying to close staging repository with ID "orgtouchbit-1039".
[ERROR]
[ERROR] Nexus Staging Rules Failure Report
[ERROR] ==================================
[ERROR]
[ERROR] Repository "orgtouchbit-1039" failures
[ERROR] Rule "signature-staging" failures
[ERROR] * No public key: Key with id: (1f42b618d1cbe1b5) was not able to be located on <a href=http://keys.gnupg.net:11371/>http://keys.gnupg.net:11371/</a>. Upload your public key and try the operation again.
...
[ERROR] Cleaning up local stage directory after a Rule failure during close of staging repositories: [orgtouchbit-1039]
[ERROR] * Deleting context 9043b43f77dcc9.properties
[ERROR] Cleaning up remote stage repositories after a Rule failure during close of staging repositories: [orgtouchbit-1039]
[ERROR] * Dropping failed staging repository with ID "orgtouchbit-1039" (Rule failure during close of staging repositories: [orgtouchbit-1039]).
[ERROR] Remote staging finished with a failure: Staging rules failure!
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] Shields4J 1.0.0 .................................... SUCCESS [ 4.073 s]
[INFO] test-core .......................................... SUCCESS [ 2.788 s]
[INFO] Shields4J client ................................... SUCCESS [ 3.962 s]
[INFO] TestNG listener 1.0.0 .............................. FAILURE [01:07 min]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
У результаті нам залишається єдиний вибір. Або видалити цю версію або опублікувати.
Після релізу, через деякий час артефакти виявляться в
офтоп
Для мене було відкриттям, що maven індексує інші громадські репозиторії.
Довелося підкинути robots.txt, тому що він проіндексував мій старий репозиторій.
Окремий deploy-проект, в якому можна реалізувати кілька CI завдань на завантаження артефактів у публічні репозиторії для різних мов розробки.
Deploy-проект ізольований від стороннього втручання та може бути змінений лише користувачами за участю Owner та Maintainer.
Окремий Specific Runner з гарячим кешем для запуску тільки deploy завдань.
Публікація snapshot/release версій у публічному репозиторії.
Автоматична перевірка release версії на готовність до публікації в maven central.
Захист від автоматичної публікації "сирих" версій у maven central.
Складання та публікація snapshot версій "по кліку".
Єдиний репозиторій для отримання версій snapshot/release.
Загальний пайплайн на складання/тестування/публікацію java проекту.
Налаштування GitLab CI не така складна тема, як здається на перший погляд. Достатньо кілька разів налаштувати CI "під ключ" і ось, ти вже далеко не дилетант у цій справі. Тим більше, GitLab документація дуже надмірна. Не бійтеся робити перший крок. Дорога виникає під кроками того, хто йде (не пам'ятаю хто сказав 🙂).
Радий фідбеку.
У наступній статті розповім про те, як налаштувати GitLab CI для конкурентного запуску завдань з інтеграційними тестами (із запуском сервісів, що тестуються, за допомогою docker-compose), якщо у вас є тільки один shell раннер.