Налаштування 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.
  • Далі процес генерації ключа GPG описаний дуже сухо. Детальніше дивитись розділ Налаштування GnuPG для підпису артефактів
  • Якщо ви використовуєте Linux консоль для генерації GPG ключа (gnupg/gnupg2), необхідно встановити rng-tools для створення ентропії. А якщо ні, то генерація ключа може проходити дуже довго.
  • Сервіси для зберігання публічних GPG ключів

До змісту

Налаштування deploy-проекту в GitLab

  • Насамперед необхідно створити та налаштувати проект, у якому зберігатиметься pipeline, для деплою артефактів. Свій проект я назвав просто і нехитро. розгортання
  • Після створення репозиторію необхідно обмежити доступ на зміну репозиторію.
    Переходимо в проект -> Settings -> Repository -> Protected Branches. Видаляємо всі правила і додаємо єдине правило з Wildcard * з правом на push і merge тільки для користувачів за участю Maintainers. Це правило буде працювати для всіх користувачів як даного проекту, так і групи, в яку цей проект входить.
    Налаштування GitLab CI для завантаження java проекту в maven central
  • Якщо мейнтейнерів кілька, то найкращим рішенням обмежитиме доступ до проекту в принципі.
    Переходимо в проект -> 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 у значенні.

До змісту

GitLab Runner

У цьому розділі описано конфігурацію для запуску завдань на 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.

До змісту

Встановлюємо gitlab runner

  • Створюємо нову групу runner
    sudo groupadd runner
  • Створюємо директорію для maven кешу та навішуємо права групи runner
    Цей пункт можна пропустити, якщо ви не плануєте запускати кілька раннерів на одній машині.

    mkdir -p /usr/cache/.m2/repository
    chown -R :runner /usr/cache
    chmod -R 770 /usr/cache
  • Створюємо користувача gitlab-deployer і додаємо до групи runner
    useradd -m -d /home/gitlab-deployer gitlab-deployer
    usermod -a -G runner gitlab-deployer
  • Додаємо у файл /etc/ssh/sshd_config наступний рядок
    AllowUsers root@* [email protected]
  • Перезавантажуємо sshd
    systemctl restart sshd
  • Встановлюємо пароль для користувача gitlab-deployer (можна простий, оскільки діє обмеження для localhost)
    passwd gitlab-deployer
  • Встановлюємо GitLab Runner (Linux x86-64)
    sudo wget -O /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64
    sudo chmod +x /usr/local/bin/gitlab-runner
    ln -s /usr/local/bin/gitlab-runner /etc/alternatives/gitlab-runner
    ln -s /etc/alternatives/gitlab-runner /usr/bin/gitlab-runner
  • Переходимо на сайт gitlab.com -> deploy-project -> Settings -> CI/CD -> Runners -> Specific Runners і копіюємо registration token

скрін

Налаштування GitLab CI для завантаження java проекту в maven central

  • Реєструємо раннер
    gitlab-runner register --config /etc/gitlab-runner/gitlab-deployer-config.toml

Процес

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

скрін

Налаштування GitLab CI для завантаження java проекту в maven central

  • додаємо окремий сервіс /etc/systemd/system/gitlab-deployer.service
    [Unit]
    Description=GitLab Deploy Runner
    After=syslog.target network.target
    ConditionFileIsExecutable=/usr/local/bin/gitlab-runner
    [Service]
    StartLimitInterval=5
    StartLimitBurst=10
    ExecStart=/usr/local/bin/gitlab-runner "run" "--working-directory" "/home/gitlab-deployer" "--config" "/etc/gitlab-runner/gitlab-deployer-config.toml" "--service" "gitlab-deployer" "--syslog" "--user" "gitlab-deployer"
    Restart=always
    RestartSec=120
    [Install]
    WantedBy=multi-user.target
  • Запускаємо сервіс.
    systemctl enable gitlab-deployer.service
    systemctl start gitlab-deployer.service
    systemctl status gitlab-deployer.service
  • Перевіряємо, що раннер запущено.

Приклад

Налаштування GitLab CI для завантаження java проекту в maven central

До змісту

Генерація ключів GPG

  • З цієї ж машинки заходимо по ssh під користувачем gitlab-deployer (це важливо для генерації GPG ключа)
    ssh [email protected]
  • Генеруємо ключ відповідаючи на запитання. Я використав власні ім'я та пошту.
    Обов'язково вказуємо пароль для ключа. Даним ключем підписуватимуться артефакти.

    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

  • Заходимо під користувачем gitlab-deployer
    su gitlab-deployer 
  • Створюємо диреторію maven Сховище і лінкуємо з кешем (не помилитеся)
    Цей пункт можна пропустити, якщо ви не плануєте запустити кілька раннерів на одній машині.

    mkdir -p ~/.m2/repository
    ln -s /usr/cache/.m2/repository /home/gitlab-deployer/.m2/repository
  • Створюємо майстер ключ
    mvn --encrypt-master-password password
    {hnkle5BJ9HUHUMP+CXfGBl8dScfFci/mpsur/73tR2I=}
  • Створюємо файл ~/.m2/settings-security.xml
    <settingsSecurity>
    <master>{hnkle5BJ9HUHUMP+CXfGBl8dScfFci/mpsur/73tR2I=}</master>
    </settingsSecurity>
  • Шифруємо пароль від облікового запису Sonatype
    mvn --encrypt-password SONATYPE_PASSWORD
    {98Wv5+u+Tn0HX2z5G/kR4R8Z0WBgcDBgi7d12S/un+SCU7uxzaZGGmJ8Cu9pAZ2J}
  • Створюємо файл ~/.m2/settings.xml
    <settings>  
    <profiles>
        <profile>
            <id>env</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <gpg.passphrase>GPG_SECRET_KEY_PASSPHRASE</gpg.passphrase>
            </properties>
        </profile>
    </profiles>
    <servers>
        <server>
            <id>sonatype</id>
            <username>SONATYPE_USERNAME</username>
            <password>{98Wv5+u+Tn0HX2z5G/kR4R8Z0WBgcDBgi7d12S/un+SCU7uxzaZGGmJ8Cu9pAZ2J}</password>
        </server>
    </servers>
    </settings>

де,
GPG_SECRET_KEY_PASSPHRASE - пароль від GPG ключа
SONATYPE_USERNAME — логін облікового запису sonatype

На цьому налаштування раннера завершено, можна переходити до розділу GitLab CI

До змісту

Shared Runner

Генерація ключів GPG

  • Насамперед необхідно створити GPG ключ. Для цього встановлюємо gnupg.
    yum install -y gnupg
  • Генеруємо ключ відповідаючи на запитання. Я використав власні ім'я та пошту. Обов'язково вказуємо пароль для ключа.
    gpg --gen-key 
  • Виводимо інформацію за ключем
    gpg --list-keys -a
    pub   rsa3072 2019-04-24 [SC] [expires: 2021-04-23]
      2D0D1706366FC4AEF79669E24D09C55BBA3FD728
    uid           [ultimate] tttemp <[email protected]>
    sub   rsa3072 2019-04-24 [E] [expires: none]
  • Завантажуємо наш публічний ключ на сервер ключів
    gpg --keyserver keys.gnupg.net --send-key 2D0D1706366FC4AEF79669E24D09C55BBA3FD728
    gpg: sending key 2D0D1706366FC4AEF79669E24D09C55BBA3FD728 to hkp server keys.gnupg.net
  • Отримуємо приватний ключ
    gpg --export-secret-keys --armor 2D0D1706366FC4AEF79669E24D09C55BBA3FD728
    -----BEGIN PGP PRIVATE KEY BLOCK-----
    lQWGBFzAqp8BDADN41CPwJ/gQwiKEbyA902DKw/WSB1AvZQvV/ZFV77xGeG4K7k5
    ...
    =2Wd2
    -----END PGP PRIVATE KEY BLOCK-----
  • Переходимо налаштування проекту -> Settings -> CI/CD -> Variables і зберігаємо приватний ключ у змінній GPG_SECRET_KEY
    Налаштування GitLab CI для завантаження java проекту в maven central

До змісту

Налаштування Maven

  • Створюємо майстер ключ
    mvn --encrypt-master-password password
    {hnkle5BJ9HUHUMP+CXfGBl8dScfFci/mpsur/73tR2I=}
  • Переходимо налаштування проекту -> Settings -> CI/CD -> Variables і зберігаємо у змінній SETTINGS_SECURITY_XML наступні рядки:
    <settingsSecurity>
    <master>{hnkle5BJ9HUHUMP+CXfGBl8dScfFci/mpsur/73tR2I=}</master>
    </settingsSecurity>
  • Шифруємо пароль від облікового запису Sonatype
    mvn --encrypt-password SONATYPE_PASSWORD
    {98Wv5+u+Tn0HX2z5G/kR4R8Z0WBgcDBgi7d12S/un+SCU7uxzaZGGmJ8Cu9pAZ2J}
  • Переходимо налаштування проекту -> Settings -> CI/CD -> Variables і зберігаємо у змінній SETTINGS_XML наступні рядки:
    <settings>  
    <profiles>
        <profile>
            <id>env</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <gpg.passphrase>GPG_SECRET_KEY_PASSPHRASE</gpg.passphrase>
            </properties>
        </profile>
    </profiles>
    <servers>
        <server>
            <id>sonatype</id>
            <username>sonatype_username</username>
            <password>{98Wv5+u+Tn0HX2z5G/kR4R8Z0WBgcDBgi7d12S/un+SCU7uxzaZGGmJ8Cu9pAZ2J}</password>
        </server>
    </servers>
    </settings>

де,
GPG_SECRET_KEY_PASSPHRASE - пароль від GPG ключа
SONATYPE_USERNAME — логін облікового запису sonatype

До змісту

Deploy docker image

  • Створюємо досить простий Dockerfile для запуску завдань на deploy із потрібною версією Java. Нижче наведено приклад для alpine.
    FROM java:8u111-jdk-alpine
    RUN apk add gnupg maven git --update-cache 
    --repository http://dl-4.alpinelinux.org/alpine/edge/community/ --allow-untrusted && 
    mkdir ~/.m2/
  • Збираємо контейнер для вашого проекту
    docker build -t registry.gitlab.com/group/deploy .
  • Аутентифікуємось та завантажуємо контейнер у registry.
    docker login -u USER -p PASSWORD registry.gitlab.com
    docker push registry.gitlab.com/group/deploy

До змісту

GitLab CI

Deploy project

Додаємо у корінь 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

До змісту

Java проект

У java проектах, які передбачається завантажувати в публічні репозиторії, необхідно додати 2 кроки на завантаження Release і Snapshot версій.

.gitlab-ci.yml

stages:
  - build
  - test
  - verify
  - deploy

<...>

Release:
  extends: .trigger_deploy
  # Запускать задачу только пo тегу.
  only:
    - tags

Snapshot:
  extends: .trigger_deploy
  # Запускаем задачу на публикацию SNAPSHOT версии вручную
  when: manual
  # Не запускать задачу, если проставлен тег.
  except:
    - tags

.trigger_deploy:
  stage: deploy
  variables:
    # Отключаем клонирование текущего проекта
    GIT_STRATEGY: none
    # Ссылка на триггер deploy-задачи
    URL: "https://gitlab.com/api/v4/projects/<deploy project ID>/trigger/pipeline"
    # Переменные deploy-задачи
    POST_DATA: "
      token=${DEPLOY_TOKEN}&
      ref=master&
      variables[DEPLOY]=${DEPLOY}&
      variables[DEPLOY_CI_REPOSITORY_URL]=${CI_REPOSITORY_URL}&
      variables[DEPLOY_CI_PROJECT_NAME]=${CI_PROJECT_NAME}&
      variables[DEPLOY_CI_COMMIT_SHA]=${CI_COMMIT_SHA}&
      variables[DEPLOY_CI_COMMIT_TAG]=${CI_COMMIT_TAG}
      "
  script:
    # Не использую cURL, так как с флагами --fail --show-error
    # он не выводит тело ответа, если HTTP код 400 и более 
    - wget --content-on-error -qO- ${URL} --post-data ${POST_DATA}

У цьому рішенні я пішов трохи далі і вирішив використати один CI шаблон для java проектів.

Більш детально

Я створив окремий проект gitlab-ci у якому розмістив шаблон CI для java проектів common.yml.

common.yml

stages:
  - build
  - test
  - verify
  - deploy

variables:
  SONAR_ARGS: "
  -Dsonar.gitlab.commit_sha=${CI_COMMIT_SHA} 
  -Dsonar.gitlab.ref_name=${CI_COMMIT_REF_NAME} 
  "

.build_java_project:
  stage: build
  tags:
    - touchbit-shell
  variables:
    SKIP_TEST: "false"
  script:
    - mvn clean
    - mvn package -DskipTests=${SKIP_TEST}
  artifacts:
    when: always
    expire_in: 30 day
    paths:
      - "*/target/reports"

.build_sphinx_doc:
  stage: build
  tags:
    - touchbit-shell
  variables:
    DOCKERFILE: .indirect/docs/Dockerfile
  script:
    - docker build --no-cache -t ${CI_PROJECT_NAME}/doc -f ${DOCKERFILE} .

.junit_module_test_run:
  stage: test
  tags:
    - touchbit-shell
  variables:
    MODULE: ""
  script:
    - cd ${MODULE}
    - mvn test
  artifacts:
    when: always
    expire_in: 30 day
    paths:
      - "*/target/reports"

.junit_test_run:
  stage: test
  tags:
    - touchbit-shell
  script:
    - mvn test
  artifacts:
    when: always
    expire_in: 30 day
    paths:
    - "*/target/reports"

.sonar_review:
  stage: verify
  tags:
    - touchbit-shell
  dependencies: []
  script:
    - >
      if [ "$CI_BUILD_REF_NAME" == "master" ]; then
        mvn compile sonar:sonar -Dsonar.login=$SONAR_LOGIN $SONAR_ARGS
      else
        mvn compile sonar:sonar -Dsonar.login=$SONAR_LOGIN $SONAR_ARGS -Dsonar.analysis.mode=preview
      fi

.trigger_deploy:
  stage: deploy
  tags:
    - touchbit-shell
  variables:
    URL: "https://gitlab.com/api/v4/projects/10345765/trigger/pipeline"
    POST_DATA: "
      token=${DEPLOY_TOKEN}&
      ref=master&
      variables[DEPLOY]=${DEPLOY}&
      variables[DEPLOY_CI_REPOSITORY_URL]=${CI_REPOSITORY_URL}&
      variables[DEPLOY_CI_PROJECT_NAME]=${CI_PROJECT_NAME}&
      variables[DEPLOY_CI_COMMIT_SHA]=${CI_COMMIT_SHA}&
      variables[DEPLOY_CI_COMMIT_TAG]=${CI_COMMIT_TAG}
      "
  script:
  - wget --content-on-error -qO- ${URL} --post-data ${POST_DATA}

.trigger_release_deploy:
  extends: .trigger_deploy
  only:
    - tags

.trigger_snapshot_deploy:
  extends: .trigger_deploy
  when: manual
  except:
    - tags

В результаті в самих java проектах .gitlab-ci.yml виглядає досить компактно і не багатослівно

.gitlab-ci.yml

include: https://gitlab.com/TouchBIT/gitlab-ci/raw/master/common.yml

Shields4J:
  extends: .build_java_project

Sphinx doc:
  extends: .build_sphinx_doc
  variables:
    DOCKERFILE: .docs/Dockerfile

Sonar review:
  extends: .sonar_review
  dependencies:
    - Shields4J

Release:
  extends: .trigger_release_deploy

Snapshot:
  extends: .trigger_snapshot_deploy

До змісту

Конфігурація pom.xml

Дуже детально цю тему описано Googolplex в Налаштування мавену для автоматичного підпису та завантаження артефактів у snapshot- та staging-репозиторіїтому я опишу деякі нюанси використання плагінів. Також я опишу як легко і невимушено можна використовувати nexus-staging-maven-plugin, якщо ви не хочете або не можете використовувати org.sonatype.oss:oss-parent як батько для свого проекту.

maven-install-plugin

Встановлює модулі локальний репозиторій.
Дуже корисний для локальної перевірки рішень в інших проектах, а також контрольною сумою.

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-install-plugin</artifactId>
  <executions>
    <execution>
      <id>install-project</id>
      <!-- Если у вас многомодульный проект с деплоем родительского помика -->
      <phase>install</phase>
      <!-- Явно указываем файлы для локальной установки -->
      <configuration>
        <file>target/${project.artifactId}-${project.version}.jar</file>
```target/${project.artifactId}-${project.version}-sources.jar</sources>
        <pomFile>dependency-reduced-pom.xml</pomFile>
        <!-- Принудительное обновление метаданных проекта -->
        <updateReleaseInfo>true</updateReleaseInfo>
        <!-- Контрольные суммы для проверки целостности -->
        <createChecksum>true</createChecksum>
      </configuration>
    </execution>
  </executions>
</plugin>

До змісту

maven-javadoc-плагін

Генерація javadoc для проекту.

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-javadoc-plugin</artifactId>
  <executions>
    <execution>
      <goals>
        <goal>jar</goal>
      </goals>
      <!-- Генерация javadoc должна быть после фазы генерации ресурсов -->
      <phase>prepare-package</phase>
      <configuration>
        <!-- Очень помогает в публичных проектах -->
        <failOnError>true</failOnError>
        <failOnWarnings>true</failOnWarnings>
        <!-- Убирает ошибку поиска документации в target директории -->
        <detectOfflineLinks>false</detectOfflineLinks>
      </configuration>
    </execution>
  </executions>
</plugin>

Якщо у вас є модуль, який не містить java (наприклад, тільки ресурси)
Або ви не хочете в принципі генерувати javadoc, то на допомогу maven-jar-plugin

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-jar-plugin</artifactId>
  <executions>
    <execution>
      <id>empty-javadoc-jar</id>
      <phase>generate-resources</phase>
      <goals>
        <goal>jar</goal>
      </goals>
      <configuration>
        <classifier>javadoc</classifier>
        <classesDirectory>${basedir}/javadoc</classesDirectory>
      </configuration>
    </execution>
  </executions>
</plugin>

До змісту

maven-gpg-plugin

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-gpg-plugin</artifactId>
  <executions>
    <execution>
      <id>sign-artifacts</id>
      <!-- Сборка будет падать, если отсутствует GPG ключ -->
      <!-- Подписываем артефакты только на фазе deploy -->
      <phase>deploy</phase>
      <goals>
        <goal>sign</goal>
      </goals>
    </execution>
  </executions>
</plugin>

До змісту

nexus-staging-maven-plugin

Конфігурація:

<project>
  <!-- ... -->
  <build>
    <plugins>
      <!-- ... -->
      <plugin>
        <groupId>org.sonatype.plugins</groupId>
        <artifactId>nexus-staging-maven-plugin</artifactId>
      </plugin>
    </plugins>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.sonatype.plugins</groupId>
          <artifactId>nexus-staging-maven-plugin</artifactId>
          <extensions>true</extensions>
          <configuration>
            <serverId>sonatype</serverId>
            <nexusUrl>https://oss.sonatype.org/</nexusUrl>
            <!-- Обновляем метаданные, чтобы пометить артефакт как release -->
            <!-- Не влияет на snapshot версии -->
            <updateReleaseInfo>true</updateReleaseInfo>
          </configuration>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-deploy-plugin</artifactId>
          <configuration>
            <!-- Отключаем плагин -->
            <skip>true</skip>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
  <distributionManagement>
    <snapshotRepository>
      <id>sonatype</id>
      <name>Nexus Snapshot Repository</name>
      <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
    </snapshotRepository>
    <repository>
      <id>sonatype</id>
      <name>Nexus Release Repository</name>
      <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
    </repository>
  </distributionManagement>
</project>

Якщо у вас багатомодульний проект, і вам не потрібно завантажувати певний модуль в репозиторій, то в pom.xml даного модуля необхідно додати nexus-staging-maven-plugin з прапором skipNexusStagingDeployMojo

<build>
  <plugins>
    <plugin>
      <groupId>org.sonatype.plugins</groupId>
      <artifactId>nexus-staging-maven-plugin</artifactId>
      <configuration>
        <skipNexusStagingDeployMojo>true</skipNexusStagingDeployMojo>
      </configuration>
    </plugin>
  </plugins>
</build>

Після завантаження версії snapshot/release доступні в staging репозиторії

<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

До змісту

Результат

Публікація SNAPSHOT версії

При складанні проекту є можливість ручного запуску завдання на завантаження SNAPSHOT версії в nexus

Налаштування GitLab CI для завантаження java проекту в maven central

При запуску цієї задачі тригериться відповідне завдання у проекті deploy (приклад).

Підрізаний лог

Running with gitlab-runner 11.10.0 (3001a600)
  on Deploy runner JSKWyxUw
Using Shell executor...
Running on ih1174328.vds.myihor.ru...
Skipping Git repository setup
Skipping Git checkout
Skipping Git submodules setup
$ rm -rf .* *
$ git config --global credential.helper store
$ echo "https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com" >> ~/.git-credentials
$ git clone ${DEPLOY_CI_REPOSITORY_URL} .
Cloning into 'shields4j'...
$ git checkout ${DEPLOY_CI_COMMIT_SHA}
Note: checking out '850f86aa317194395c5387790da1350e437125a7'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
  git checkout -b new_branch_name
HEAD is now at 850f86a... skip deploy test-core
$ for pom in $(find . -name pom.xml); do # collapsed multi-line command
$ if [[ "${DEPLOY_CI_COMMIT_TAG}" != "" ]]; then # collapsed multi-line command
[INFO] Scanning for projects...
[INFO] Inspecting build with total of 4 modules...
[INFO] Installing Nexus Staging features:
[INFO]   ... total of 4 executions of maven-deploy-plugin replaced with nexus-staging-maven-plugin
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO] 
[INFO] Shields4J                                                          [pom]
[INFO] test-core                                                          [jar]
[INFO] Shields4J client                                                   [jar]
[INFO] TestNG listener                                                    [jar]
[INFO] 
[INFO] --------------< org.touchbit.shields4j:shields4j-parent >---------------
[INFO] Building Shields4J 1.0.0                                           [1/4]
[INFO] --------------------------------[ pom ]---------------------------------
[INFO] 
[INFO] --- versions-maven-plugin:2.5:set (default-cli) @ shields4j-parent ---
[INFO] Searching for local aggregator root...
[INFO] Local aggregation root: /home/gitlab-deployer/JSKWyxUw/0/TouchBIT/deploy/shields4j
[INFO] Processing change of org.touchbit.shields4j:shields4j-parent:1.0.0 -> 1.0.0-SNAPSHOT
[INFO] Processing org.touchbit.shields4j:shields4j-parent
[INFO]     Updating project org.touchbit.shields4j:shields4j-parent
[INFO]         from version 1.0.0 to 1.0.0-SNAPSHOT
[INFO] 
[INFO] Processing org.touchbit.shields4j:client
[INFO]     Updating parent org.touchbit.shields4j:shields4j-parent
[INFO]         from version 1.0.0 to 1.0.0-SNAPSHOT
[INFO]     Updating dependency org.touchbit.shields4j:test-core
[INFO]         from version 1.0.0 to 1.0.0-SNAPSHOT
[INFO] 
[INFO] Processing org.touchbit.shields4j:test-core
[INFO]     Updating parent org.touchbit.shields4j:shields4j-parent
[INFO]         from version 1.0.0 to 1.0.0-SNAPSHOT
[INFO] 
[INFO] Processing org.touchbit.shields4j:testng
[INFO]     Updating parent org.touchbit.shields4j:shields4j-parent
[INFO]         from version 1.0.0 to 1.0.0-SNAPSHOT
[INFO]     Updating dependency org.touchbit.shields4j:client
[INFO]         from version 1.0.0 to 1.0.0-SNAPSHOT
[INFO]     Updating dependency org.touchbit.shields4j:test-core
[INFO]         from version 1.0.0 to 1.0.0-SNAPSHOT
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] 
[INFO] Shields4J 1.0.0 .................................... SUCCESS [  0.992 s]
[INFO] test-core .......................................... SKIPPED
[INFO] Shields4J client ................................... SKIPPED
[INFO] TestNG listener 1.0.0 .............................. SKIPPED
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.483 s
[INFO] Finished at: 2019-04-21T02:40:42+03:00
[INFO] ------------------------------------------------------------------------
$ mvn clean deploy -DskipTests=${SKIP_TESTS}
[INFO] Scanning for projects...
[INFO] Inspecting build with total of 4 modules...
[INFO] Installing Nexus Staging features:
[INFO]   ... total of 4 executions of maven-deploy-plugin replaced with nexus-staging-maven-plugin
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO] 
[INFO] Shields4J                                                          [pom]
[INFO] test-core                                                          [jar]
[INFO] Shields4J client                                                   [jar]
[INFO] TestNG listener                                                    [jar]
[INFO] 
[INFO] --------------< org.touchbit.shields4j:shields4j-parent >---------------
[INFO] Building Shields4J 1.0.0-SNAPSHOT                                  [1/4]
[INFO] --------------------------------[ pom ]---------------------------------
...
DELETED
...
[INFO]  * Bulk deploy of locally gathered snapshot artifacts finished.
[INFO] Remote deploy finished with success.
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] 
[INFO] Shields4J 1.0.0-SNAPSHOT ........................... SUCCESS [  2.375 s]
[INFO] test-core .......................................... SUCCESS [  3.929 s]
[INFO] Shields4J client ................................... SUCCESS [  3.815 s]
[INFO] TestNG listener 1.0.0-SNAPSHOT ..................... SUCCESS [ 36.134 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 47.629 s
[INFO] Finished at: 2019-04-21T02:41:32+03:00
[INFO] ------------------------------------------------------------------------

В результаті в nexus завантажено версію 1.0.0-СНАПШОТ.

Всі snapshot версії можна видалити з репозиторію на сайті oss.sonatype.org під своїм обліковим записом.

Налаштування GitLab CI для завантаження java проекту в maven central

До змісту

Публікація release версії

При встановленні тега автоматично тригериться відповідне завдання в проекті deploy на завантаження релізної версії в nexus (приклад).

Налаштування GitLab CI для завантаження java проекту в maven central

Найприємніше, що автоматично спрацьовує 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 &lt;a href=http://keys.gnupg.net:11371/&gt;http://keys.gnupg.net:11371/&lt;/a&gt;. 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] ------------------------------------------------------------------------

У результаті нам залишається єдиний вибір. Або видалити цю версію або опублікувати.

Налаштування GitLab CI для завантаження java проекту в maven central

Після релізу, через деякий час артефакти виявляться в Налаштування GitLab CI для завантаження java проекту в maven central

офтоп

Для мене було відкриттям, що 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 раннер.

До змісту

Джерело: habr.com