Русский
Русский
English
Статистика
Реклама

Maven

Перевод Автоматизация семантического управления версиями с помощью Maven (SemVer GitFlow Maven)

05.10.2020 10:14:40 | Автор: admin

Вы используете семантический подход к управлению версиями? Вы используете gitflow? Скорее всего, вы знакомы с процессом корректировки версий, создания веток, слияния с master/dev, повторной корректировки версий, борьбы с конфликтами слияния,


В этой статье я кратко объясню процесс выпуска, который мы, по сути, используем для наших библиотек, и то, как мы его автоматизировали. Обычно мы используем подход CI/CD, используя номера сборок, но для наших библиотек мы решили использовать семантическое управление версиями. Я столкнулся с утомительным процессом выпуска, который сопровождает это в нескольких компаниях, и теперь я наконец нашел решение.


Эта статья посвящена Maven, но есть и много альтернатив Gradle.


Пример проекта можно найти на нашей странице GitHub.


Семантическое управление версиями и Git


Семантическое управление версиями это система классификации ваших выпусков. Я уверен, что вы видели такие номера версий, как 1.6.4, 1.7.10, 1.12.2 и другие. Эти цифры обозначают MAJOR.MINOR.PATCH (МАЖОРНАЯ.МИНОРНАЯ.ПАТЧ)


Кроме того, существуют версии SNAPSHOT, которые выглядят одинаково, но с добавлением -SNAPSHOT в конце, например 1.14.4-SNAPSHOT.


Типичный процесс выпуска состоит из следующих шагов:


  1. Создайте release ветку из ветки разработки (здесь и далее ветка разработки development branch).


  2. Измените версию во всех файлах pom.xml с SNAPSHOT (1.2.3-SNAPSHOT) на non-SNAPSHOT (1.2.3) в release ветке.


  3. Увеличьте версию SNAPSHOT на ветке develop(1.2.4-SNAPSHOT).


  4. Когда все будет сделано для release, объедините release ветку с master веткой. Это актуальный релиз.


  5. Слейте master ветку или ветку release обратно в ветку разработки.



Есть несколько вариантов этого процесса с разными таймингами / стратегиями слияния, но он сводится к следующему: версия должна быть изменена на два разных значения в двух разных ветвях, но обе ветки должны иметь одну и ту же историю, чтобы предотвратить конфликты слияния.


Хотя эти шаги не являются сложными сами по себе, они утомительны и подвержены ошибкам, когда вам приходится выполнять их часто. В то же время они требуют много размышлений, чтобы предотвратить merge конфликты.


Каковы были мои цели?


Я хочу автоматизировать весь процесс, чтобы не беспокоиться об ошибках.


Текущее состояние master ветви никогда не должно содержать версию SNAPSHOT.


Я хочу подключить его к конвейерам CI, но с ручным запуском.


Я никогда не хочу иметь дело с merge конфликтами.


Я также хочу использовать его для hotfixes (которые происходят из master ветки).


Плагин gitflow-maven


Возможно, вы знаете плагин версий maven, который легко позволяет устанавливать версии внутри ваших файлов pom.xml. Однако для этого мне необходимо вручную ввести новую версию. Я хочу, чтобы мое решение автоматически выводило следующую версию на основе текущей версии.


К счастью, уже есть инструменты, помогающие достичь моих целей. Поскольку проблема довольно распространена, несколько человек пытались ее решить. Однако из всех найденных мною подключаемых модулей и инструментов поддерживается только один, при этом предоставляющий достаточно параметров конфигурации: gitflow-maven-plugin.


В нем есть именно то, что мне нужно:


Увеличение номеров версий.


Создание release ветки.


Создание ветки hotfix.


Объединение (Merging) веток.


Кроме того, выпуски возможны непосредственно из ветки разработки без дополнительной ветки выпуска. Это очень удобно, поскольку наша компания в любом случае использует CI/CD, что означает, что ветвь разработки всегда находится в готовом к выпуску состоянии.


Все это можно сделать, выполнив цели (goals) maven. Нам нужно только указать плагину, как себя вести и какое число увеличивать.


Некоторые примеры:


Следующие команды показывают несколько примеров, которые я использовал при оценке плагина.


$ mvn gitflow:release-start -B

Создает ветку release из ветки разработки без ввода данных пользователем (-B Batch Mode)


$ mvn gitflow:release

Создает релиз прямо из ветки разработки. Плагин объединяет текущее состояние разработки с master веткой и увеличивает номер версии в ветке разработки.


В этом примере нет флага пакетной обработки, что означает, что пользователю предлагается ввести всю недостающую информацию.


$ mvn gitflow:hotfix-start -B

$ mvn gitflow:hotfix-finish -B -DhotfixVersion=1.8.9b 

На первом этапе создает ветку hotfix из master ветки, затем выпускает исправление с версией исправления 1.8.9b на втором этапе. Все цели плагина могут быть выполнены в любой ветке. Из-за этого я должен указать плагину, какое исправление я хочу выпустить.


Конфигурация


Добавить зависимости плагина достаточно просто, просто добавьте следующее в плагины сборки poms:


<build>    <plugins>        <plugin>            <groupId>com.amashchenko.maven.plugin</groupId>            <artifactId>gitflow-maven-plugin</artifactId>            <version>1.13.0</version>            <configuration>                <!-- optional configuration -->            </configuration>        </plugin>    </plugins></build>

Последнюю версию можно найти на GitHub или в maven central.


Дополнительная настройка хорошо документирована на странице плагинов GitHub. Вот пример конфигурации с выделением некоторых параметров конфигурации:


<configuration>    <!-- We use maven wrapper in all our projects instead of a local maven installation -->    <mvnExecutable>./mvnw</mvnExecutable>    <!-- Dont push to the git remote. Very useful for testing locally -->    <pushRemote>true</pushRemote>    <!-- Set to true to immediately bump the development version when creating a release branch -->    <commitDevelopmentVersionAtStart>false</commitDevelopmentVersionAtStart>    <!-- Which digit to increas in major.minor.patch versioning, the values being 0.1.2 respectively.         By default the rightmost number is increased.         Pass in the number via parameter or profile to allow configuration,         since everything set in the file can't be overwritten via command line -->    <versionDigitToIncrement>${gitflowDigitToIncrement}</versionDigitToIncrement>    <!-- Execute mvn verify before release -->    <preReleaseGoals>verify</preReleaseGoals>    <preHotfixGoals>verify</preHotfixGoals>    <!-- Configure branches -->    <gitFlowConfig>        <productionBranch>master</productionBranch>        <!-- default is develop, but we use development -->        <developmentBranch>development</developmentBranch>    </gitFlowConfig></configuration>

Это отличный плагин, все работает безупречно, когда я запускаю его локально. Есть несколько избранных настроек, чтобы работать с Gitlab CI.


Автоматизация с помощью Gitlab CI


Мы запускаем конвейер CI/CD с использованием Gitlab CI для наших проектов, поэтому каждый новый commit в нашей ветке разработки приводит к созданию snapshot, а merge для master к release.


Как описано выше, моя цель автоматизировать разработку, master merge и связанные с этим обновления версий, а также hotfixes.


Я использую конвейеры Gitlab CI, чтобы упростить процесс и в то же время сделать его доступным для моих коллег. Вот пример:



Вот как выглядит конвейер ветвления разработки в моем примере с дополнительными этапами release. Поэтому, если я хочу создать release, я просто запускаю этап release, и он автоматически устанавливает версию без snapshot, объединяет (merge) ее с master и переводит версию в ветке разработки на следующую версию snapshot. Без каких-либо дополнительных действий с моей стороны.


Заставляем git работать в Gitlab CI


Если вы пробовали использовать команды git внутри Gitlab CI, то знаете: это не совсем просто, поскольку у исполнителей CI нет учетных данных git.


Для простоты я использую токены доступа с правами write_repository. Я назначил для этого специального пользователя, чтобы видеть, какие коммиты были созданы автоматически.


Я ввожу токен через переменную среды GITLAB_TOKEN, которую я установил как protected, и помечаю ветки development, release/* и hotfix/* как защищенные (protected). Таким образом, только сборки на тех ветках имеют доступ к токену.


Я вручную устанавливаю git remote на runner, чтобы среда CI могла отправлять данные в репозиторий. Я использую переменные, предоставленные Gitlab, поэтому мне не нужно ничего жестко кодировать:


$ git remote set-url --push origin "https://oauth2:${GITLAB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git"

С помощью этого git команды могут выполняться с использованием учетных данных введенного токена доступа. Git также требует некоторой информации о пользователе, выполняющем действия git. Желательно, чтобы пользователь git соответствовал пользователю, чей токен доступа мы используем. В моем примере:


$ git config user.name "Gitlab CI"$ git config user.email gitlab-ci@viesure.io

Благодаря этому я могу легко найти все коммиты git, сделанные CI. Весь фрагмент кода доступен в репозитории примеров.


Собираем все вместе


Теперь, когда я могу получить доступ к git из CI, мне нужно настроить плагин gitflow. Я столкнулся с некоторыми особенностями.


Для простоты я использую предопределенные переменные, чтобы решить, какую цифру версии я хочу увеличить:


MINOR для релизов


PATCH для hotfixes


Можно также передать переменную при выполнении конвейера или получить ее другим способом.


Все цели (goals) передаются с параметром -B, который означает пакетный режим и означает, что пользовательский ввод не будет запрашиваться.


Автоматический Release


$ ./mvnw gitflow: release -B -DgitflowDigitToIncrement = $RELEASE_DIGIT

Полный процесс выпуска относительно прост. Плагин объединяет ветку разработки в master без SNAPSHOT внутри версии, а затем увеличивает версию для разработки. Я просто вызываю цель (goal ) maven и сообщаю ей, какую цифру увеличивать.


Ручная разблокировка


$ ./mvnw gitflow: release-start -B -DgitflowDigitToIncrement = $RELEASE_DIGIT

$ git push origin HEAD

Выпуск вручную начинается с создания ветки выпуска. Технически цифра не нужна, так как увеличение выполняется при слиянии, но если опция увеличения версии для разработки при разветвлении активна, она будет работать с ней из коробки (например, если она позже изменится, или другие проекты используйте разные настройки). Обратите внимание, что мне нужно нажать ветку вручную, поскольку плагин предназначен для локального использования.


$ git symbolic-ref refs/heads/$CI_COMMIT_REF_NAME refs/remotes/origin/$CI_COMMIT_REF_NAME$ ./mvnw gitflow:release-finish -B -DgitflowDigitToIncrement=$RELEASE_DIGIT

Завершение release приносит с собой первый обходной путь. Git хранит ссылку (сокращенно ref) на HEAD всех ваших веток. Однако Gitlab CI не устанавливает все эти ссылки при проверке репозитория. Плагин использует эти ссылки на HEAD для проверки веток. Тем не менее, у него есть правильные локальные ссылки, поэтому я просто создаю недостающую ссылку HEAD.


После этого цель окончательного выпуска объединяет ветвь выпуска в master ветку и ветку разработки, увеличивая версию для разработки.


Исправление (Hotfix)


Исправление следует тому же процессу, что и выпуск вручную, за исключением того, что оно запускается на мастере, а не на разработке.


$ ./mvnw gitflow:hotfix-start -B -DgitflowDigitToIncrement=$HOTFIX_DIGIT$ git push origin HEAD

Hotfix-start создает ветку hotfix, которая уже содержит увеличенную версию.


$ export CURRENT_VERSION=${CI_COMMIT_REF_NAME/hotfix\/}$ git symbolic-ref refs/heads/$CI_COMMIT_REF_NAME refs/remotes/origin/$CI_COMMIT_REF_NAME$ ./mvnw gitflow:hotfix-finish -B -DgitflowDigitToIncrement=$HOTFIX_DIGIT -DhotfixVersion=$CURRENT_VERSION

Hotfix-finish объединяет его в master ветку и ветку разработки. Однако для исправления требуется еще один параметр: версия исправления. Причина этого опять же в том, что плагин разработан для локального использования, где у вас может быть несколько веток. Он не ожидает выполнения цели внутри ветки исправления.


Как сказано выше, hotfix-start уже увеличивает номер версии, чтобы обозначить версию исправления. Таким образом, имя ветки уже указывает версию исправления.


Я просто извлекаю версию из имени текущей ветки. Поскольку задание конвейера исправления определено для ветвей исправления, оно будет выполняться только в указанной ветке.


Резюме


Это оно. Благодаря этому вы всего в одном клике от всех изменений версий и ветвлений, которые вам когда-либо понадобятся!


Потребовалось немного усилий, чтобы заставить взаимодействия git работать внутри бегунов CI и обойти отсутствующие ссылки. Но теперь, когда это работает, я очень доволен результатом.


Мне больше никогда не придется думать о порядке, в котором мне нужно разветвлять и увеличивать версии. CI позаботится об этом за меня.


Вы можете найти полный сценарий Gitlab CI и пример проекта со всеми приведенными выше фрагментами кода в нашем репозитории GitHub.


Ссылки


Подробнее..

Перевод Автоматизация рабочего процесса Java-проекта с помощью модифицированной модели ветвления Gitflow

06.10.2020 10:05:04 | Автор: admin

Автоматизация рабочего процесса Java-проекта с помощью модифицированной модели ветвления Gitflow


Ключевые выводы


Gitflow это совместная модель ветвления, которая пытается использовать мощность, скорость и простоту ветвления Git. Этот метод хорошо работал в ситуации, которую мы описываем здесь, но другие отмечали, что использование Gitflow сопряжено со своими проблемами.


Документация по использованию Gitflow в размещения в источнике информации в лучшем случае является нечеткой.


Функции изолированы внутри ветвей. Вы можете управлять своими собственными изменениями функций изолированно. Этот подход отличается от разработки на основе магистралей, когда каждый разработчик фиксируется в основной ветке не реже одного раза в 24 часа.


Разделение функций с использованием изолированных ветвей позволяет вам решить, какие функции включать в каждый релиз. Компромисс здесь может заключаться в сложных слияниях.


Обновление от 13 февраля 2019 г.: первоначальное оформление этой статьи вызвало большой отклик, в основном положительный, а иногда и не очень. Основным предметом разногласий было использование нами термина непрерывная доставка в среде, где релиз выполняются вручную. Если вы работаете в команде, которая развертывает сотни релизов в день, наша структура может вам не подойти. Однако, если вы, как и мы, работаете в жестко регулируемой отрасли, такой как финансы, где релиз более контролируемы, и вы хотите максимально использовать возможности ветвления, автоматической интеграции, автоматического размещения и управления версиями, то это решение может работать для вас так же, как и для нас


Много лет назад я был на технологической конференции, где наткнулся на новомодную штуковину на выставке под названием Git. Я узнал, что это инструмент управления версиями нового поколения, и моя первая реакция была такой: Зачем нам это, у нас уже есть SVN? Это было тогда. Сегодня группы разработчиков массово переходят на Git, и вокруг промежуточного программного обеспечения и плагинов возникла огромная экосистема.


Gitflow это совместная модель ветвления, которая пытается использовать мощность, скорость и простоту ветвления Git. Как было написано ранее в InfoQ, этот подход действительно сопряжен со своим набором проблем, особенно в отношении непрерывной интеграции, но это была именно та проблема, которую мы стремились решить. Gitflow был представлен Винсентом Дриссеном в его блоге 2010 года Успешная модель ветвления Git. Gitflow упрощает совместную разработку, позволяя командам изолировать новую разработку от завершенной работы в отдельных ветвях, позволяя вам выбирать функции для релиза, при этом поощряя частые коммиты и автоматическое тестирование. Мы обнаружили, что это дает более чистый код, продвигая регулярные проверки кода во время слияния, даже проверки собственного кода, тем самым выявляя ошибки, возможности для рефакторинга и оптимизации.


Но когда дело доходит до внедрения Gitflow в автоматизированном конвейере развертывания, подробности становятся очень специфичными для вашей среды разработки, и появляются бесконечные возможности. Следовательно, документация является разреженной. Учитывая известные названия ветвей master, development, feature и т.д., какие ветви мы строим, какие тестируем, какие развертываем в нашей команде в виде Snapshot, какие развертывают выпуски и как автоматизируют развертывания в Dev, UAT, Prod и т.д.?


Эти вопросы часто задают на конференциях, и в этой статье мы хотели бы поделиться решением, которое мы разработали в ходе нашей собственной работы в крупной компании, занимающейся финансовыми технологиями.


Описанный здесь проект использует Java и Maven, но мы считаем, что любая среда может быть адаптирована аналогичным образом. Мы используем GitLab CI с настраиваемыми сценариями запуска, но также можно использовать Jenkins или плагин GitHub CI; мы используем Jira для отслеживания проблем, IntelliJ IDEA в качестве нашей IDE, Nexus в качестве репозитория зависимостей, и мы используем Ansible для нашего автоматического развертывания, но их можно заменить любые аналогичные инструменты.


Эволюция


В допотопные времена разработчики тратили недели или месяцы на создание функции приложения, откуда они передавали завершенную работу интегратору благонамеренному и преданному человечеству парню, который брал все такие функции, интегрировал их., разрешал конфликты и подготавливал к релизу. Процесс интеграции был пугающим, чреватым ошибками, непредсказуемым по графику и последствиям, породив заслуженное название интеграционный ад. Затем на рубеже веков Кент Бек выпустил свою книгу Объяснение экстремального программирования, в которой отстаивал концепцию непрерывной интеграции; практика, когда каждый разработчик создает и интегрирует код в основную ветвь и запускает тесты в автоматическом режиме каждые несколько часов каждый день. Вскоре после этого появился Круиз-Контроль Мартина Фаулера Thoughtworks с открытым исходным кодом, один из первых в истории инструментов автоматизации CI.


Вход в Gitflow


Gitflow рекомендует использовать feature ветки для разработки отдельных функций и отдельные ветки для интеграции и релиза. Следующий рисунок, перепечатанный из блога Винсента Дриссена, теперь хорошо знаком командам разработчиков Git.



Как пользователи Git, все мы знакомы с веткой под названием master; это основная ветка или trunk, создаваемая Git по умолчанию при первой инициализации любого проекта Git. Прежде чем принять Gitflow, вы, скорее всего, делали коммит в своей основной ветке.


Запуск Gitflow


Чтобы запустить проект с помощью Gitflow, существует однократный шаг инициализации, на котором вы создаете ответвление от master под названием develop. С этого момента develop становится всеобъемлющей ветвью, где весь ваш код размещается и тестируется, по сути, становясь вашей основной ветвью интеграции.



Как разработчик, вы никогда не будете делать коммит непосредственно в ветке develop, и вы никогда не будете делать коммит непосредственно в ветке master. Master стабильная ветка, которая содержит только те работы, которые готовы к производству, либо выпущены, либо готовы к релизу. Если что-то находится в мастере, то это прошлый или будущий производственный релиз.


Бранч develop именуется "нестабильной". Возможно, это название немного ошибочно он стабилен тем, что содержит код, предназначенный для выпуска; и он должен быть скомпилирован и испытания должны пройти. Он называется так только потому, что он содержит работу, которая может быть или не быть завершенной, и, следовательно, "нестабильной".


Итак, где мы работаем? Вот где начинает материализоваться остальная часть картины:


Вы получаете новую задачу от Jira, над которой нужно работать. Сразу же вы разветвляете функциональную ветку, обычно от разработки, если она находится в стабильной точке, или от master:



Мы договорились о том, что наши функциональные ветки называются feat-, за которыми следует номер проблемы Jira. (Если существует более одной проблемы Jira, просто используйте задачу Epic или Parent либо один из основных номеров задач, за которым следует очень краткое описание функции.) Например, feat-SDLC-123-add-name-field. Префикс feat- обеспечивает шаблон, который CI-сервер может использовать, чтобы идентифицировать это как ветвь функции. Скоро мы увидим, почему это важно. В этом примере SDLC-123 это номер нашей проблемы Jira, который дает нам визуальную ссылку на основную проблему, а оставшееся описание дает нам краткое описание этой функции.


Теперь разработка идет параллельно: все работают над своими функциональными ветками одновременно, некоторые команды работают над одной и той же ветвью, выполняя эту функцию, другие работают над разными функциями. Мы обнаружили, что за счет частого слияния с ветвью разработки команда сократила время, затрачиваемое на ад слияния (merge hell).


Релизы, Snapshots и общие репозитории


Давайте проясним это несколькими словами. На большинстве предприятий есть репозиторий с одной зависимостью, например Sonatype Nexus. Это репо содержит два вида двоичных файлов. Бинарные файлы SNAPSHOT обычно именуются с использованием версии semver (разделенных точками из трех частей), за которыми следует слово -SNAPSHOT (например, 1.2.0-SNAPSHOT). Версии исполняемых файлов релиза имеют одно и то же имя, кроме суффикса -SNAPSHOT (например, 1.2.0). Сборки моментальных Snapshot уникальны тем, что каждый раз, когда вы создаете двоичный файл с этой версией Snapshot, он заменяет любой предыдущий двоичный файл с таким же именем. Сборки релизов не такие; как только вы создадите сборку релиза, вы можете сообщить, что двоичный файл, связанный с этой версией, никогда не будет изменен в Nexus.


Теперь представьте, что вы работаете над функцией X, а ваша партнерская команда работает над функцией Y. Вы оба одновременно ответили на разработку, так что у вас обоих одна и та же базовая версия в вашем POM (скажем, 1.2.0-SNAPSHOT). Теперь предположим, что вы запустили свою сборку и развернули ветку функций в Nexus, а вскоре после этого ваша компаньонская команда запустила свою сборку и развернула ее в Nexus. В таком сценарии вы никогда не узнаете, какой двоичный файл функции был в Nexus, поскольку 1.2.0-SNAPSHOT будет относиться к двум различным двоичным файлам, соответствующим двум отдельным ветвям функций (или более, если таких ветвей функций больше!) Это очень часто возникающий конфликт.


GitLab CI


Тем не менее, мы поручаем командам совершать коммиты чаще и быстрее! Итак, как нам избежать таких конфликтов? Необходимо указать GitLab CI, чтобы он построил ошибку, но не развертывал ее в Nexus, связав ветки feat- с этапом жизненного цикла проверки Maven (который создается локально и запускает все тесты), а не этапом развертывания Maven (который будет отправлять двоичный Snapshot в Nexus).


GitLab CI настраивается путем определения файла (с именем .gitlab-ci.yml) в корне проекта, который содержит точные шаги выполнения CI / CD. Прелесть этой функции в том, что сценарий запуска затем связывается с вашим коммитом, поэтому вы можете изменять его в зависимости от комитта или ветки.


Мы настроили GitLab CI со следующим заданием, содержащим регулярное выражение и скрипт для создания веток функций:


feature-build:  stage:     build  script:    - mvn clean verify sonar:sonar  only:    - /^feat-\w+$/

Каждый коммит запускает ваши тесты изолированно, гарантируя, что ваша текущая работа функции ничего не сломала, и позволила вам добавлять тесты в код, который вы изменили.


Разработка, управляемая покрытием


Сейчас самое время обсудить тестовое покрытие. Идея IntelliJ имеет режим выполнения покрытия, который позволяет вам запускать тестовый код с покрытием (либо в режиме отладки, либо в режиме выполнения) и закрашивает поля в зеленый или розовый цвет, в зависимости от того, был ли этот код покрыт или нет. Вы также можете (и должны) добавить в Maven плагин покрытия (например, Jacoco), чтобы вы могли получать отчеты о покрытии как часть вашей сборки интеграции. Если вы используете среду IDE, которая не окрашивает поля, вы можете использовать эти отчеты, чтобы найти участки непокрытого кода.



[Одно замечание по этому поводу к сожалению, все еще есть много профессиональных команд разработчиков, которые, хотя и проповедуют непреодолимую ортодоксальность автоматизации и разработки, тем не менее по тем или иным причинам упускают возможность расширить охват тестированием. Теперь мы не будем теми, кто скажет таким командам вернуться и добавить тесты к каждому открытому фрагменту кода; но, как хорошие граждане-разработчики, мы считаем своим долгом ввести тесты хотя бы для кода, который мы добавили или изменили. Сопоставляя поля с цветовым кодом покрытия и введенный нами код, мы можем быстро определить возможности для введения новых тестов.]


Тесты выполняются как часть сборки Maven. На этапе тестирования Maven выполняются модульные тесты (обозначенные именем, которое начинается с Test-something.java или заканчивается Test.java, Tests.java или TestCase.java). Maven verifyphase (требуется плагин Maven Failsafe) также выполняет интеграционные тесты. Вызов mvn verify запускает сборку, за которой следует конвейер этапов жизненного цикла, включая тестирование и проверку. Мы также рекомендуем установить SonarQube и плагин Maven SonarQube для статического анализа кода на этапе тестирования. В нашей модели каждый коммит или слияние ветки выполняет все эти тесты.


Интеграция нашей работы


Вернемся к Gitflow. Сейчас мы проделали еще немного работы над нашей функцией, мы фиксировали нашу ветку функций, но мы хотим убедиться в интеграции, что она хорошо сочетается со всеми другими коммитами командных функций. Таким образом, согласно политике мы соглашаемся, что все команды разработчиков объединяются в ветку разработки не реже одного раза в день.


У нас также есть политика, которую мы применяем внутри GitLab, которую мы не можем объединить в разработку без проверки кода в форме запроса на слияние:




В зависимости от вашей политики SDLC вы можете заставить разработчиков провести проверку кода с кем-то еще, снабдив свои слияния списком утверждающих. Или вы можете применить более расслабленную стратегию, разрешив разработчикам выполнять свои собственные проверки кода после просмотра своего собственного запроса на слияние. Эта стратегия прекрасно работает, поскольку побуждает разработчиков хотя бы пересматривать свой собственный код, но, как и любая система, она сопряжена с очевидными рисками. Обратите внимание, что, поскольку двоичный файл никогда не будет развернут на Nexus или иным образом предоставлен, версия POM, содержащаяся в ветке разработки, не имеет значения. Вы можете назвать его 0.0.0-SNAPSHOT или просто оставить исходную версию POM, откуда она была разветвлена.


Наконец, по прошествии некоторого количества дней функция будет готова, она будет полностью объединена с версией для разработки и объявлена стабильной, и есть ряд таких функций, которые готовы к релизу. Помните, что на этом этапе мы запускали наши проверочные тесты для каждого коммита, но мы еще не развернули даже SNAPSHOT для Nexus. Это наш следующий шаг


На этом этапе мы разветвляем ветвь релиза от develop. Но в небольшом отступлении от традиционного Gitflow мы не называем это релизом; мы называем ветвь по номеру версии релиза. В нашем случае мы используем семантическое управление версиями из трех частей, поэтому, если это основной релиз (новые функции или критические изменения), мы увеличиваем основной (первый) номер, второстепенный релиз мы увеличиваем второстепенный (второй) номер, а если патч, то третий. Таким образом, если предыдущий релиз был 1.2.0, следующий релиз может быть 1.2.1, а версия моментального Snapshot pom будет 1.2.1-SNAPSHOT. Таким образом, наша ветка будет называться соответственно 1.2.1.


Настройка конвейера


Мы настроили наш конвейер GitLab CI так, чтобы он распознавал создание ветки релиза (ветка релиза идентифицируется по semver номеру, разделенному точкой из трех частей; на языке регулярных выражений: \d+\.\d+\.\d+). Средство выполнения CI/CD сконфигурировано для извлечения имени релиза из имени ветки и для использования плагина версии для изменения версии POM, чтобы включить SNAPSHOT, соответствующий этому имени ветки (1.2.1-SNAPSHOT в нашем примере).


release-build:  stage:    build  script:     - mvn versions:set -DnewVersion=${CI_COMMIT_REF_NAME}-SNAPSHOT    # now commit the version to the release branch    - git add .    - git commit -m "create snapshot [ci skip]"    - git push    # Deploy the binary to Nexus:    - mvn deploy  only:    - /^\d+\.\d+\.\d+$/  except:    - tags

Обратите внимание на [ci skip] в коммит сообщении. Это очень важно для предотвращения зацикливания, когда каждый коммит запускает новый запуск и новый коммит!


После того, как исполнитель CI вносит изменения в POM, он фиксирует и отправляет обновленный файл pom.xml (теперь содержащий версию, которая соответствует имени ветки). Теперь POM удаленной ветви релиза содержит правильную версию SNAPSHOT для этой ветви.


GitLab CI, все еще идентифицирующий эту ветвь релиза по семантическому шаблону управления версиями (/^\d+\.\d+\.\d+$/, например 1.2.1) своего имени, распознает, что в ветке произошло событие push. Средство выполнения GitLab выполняет mvn deploy для создания сборки SNAPSHOT и развертывания в Nexus. Теперь Ansible развертывает его на сервере разработки, где он доступен для тестирования. Этот шаг выполняется для всех нажатий на ветку релиза. Таким образом, небольшие изменения, которые разработчики вносят в релиз-кандидат, запускают сборку SNAPSHOT, релиз SNAPSHOT для Nexus и развертывание этого артефакта SNAPSHOT на серверах разработки.


Мы пропустили сценарии развертывания Ansible, потому что они очень индивидуальны для вашей конкретной модели развертывания. Они могут выполнять любые действия, необходимые для развертывания артефактов, включая перезапуск служб после установки новых артефактов, обновление расписаний cron и изменение файлов конфигурации приложения. Вам нужно будет определить развертывание Ansible специально для ваших конкретных нужд.


И, наконец, мы объединяемся в master, заставляя Git пометить релиз номером версии semver из имени исходной ветки релиза, развернуть весь пакет на Nexus и запустить тесты сонара.


Обратите внимание, что в GitLab CI все, что вам нужно для следующего шага работы, нужно обозначить как артефакты. В этом случае мы собираемся развернуть наш артефакт jar с помощью Ansible, поэтому мы обозначили его как артефакт GitLab CI.


master-branch-build:  stage:    build  script:    # Remove the -SNAPSHOT from the POM version    - mvn versions:set -DremoveSnapshot    # use the Maven help plugin to determine the version. Note the grep -v at the end, to prune out unwanted log lines.    - export FINAL_VERSION=$(mvn --non-recursive help:evaluate -Dexpression=project.version | grep -v '\[.*')    # Stage and commit the binaries (again using [ci skip] in the comment to avoid cycles)    - git add .    - git commit -m "Create release version [ci skip]"    # Tag the release    - git tag -a ${FINAL_VERSION} -m "Create release version"    - git push     - mvn sonar:sonar deploy  artifacts:    paths:    # list our binaries here for Ansible deployment in the master-branch-deploy stage    - target/my-binaries-*.jar  only:    - mastermaster-branch-deploy:  stage:    deploy  dependencies:    - master-branch-build  script:   # "We would deploy artifacts (target/my-binaries-*.jar) here, using ansible  only:    - master

Устранение ошибок


Во время тестирования могут быть обнаружены исправления ошибок. Они исправляются прямо в ветке релиза, а затем снова объединяются для разработки. (Develop всегда содержит все, что было или будет выпущено.)



Наконец, ветвь релиза утверждается, и она объединяется с главной. Master имеет принудительную политику GitLab, чтобы никогда не принимать слияния, кроме как из ветки релиза. Бегун GitLab проверяет объединенный код в master, у которого все еще есть версия SNAPSHOT ветки релиза. Средство выполнения GitLab снова использует плагин версий Maven для выполнения версий: установите цель с помощью набора параметров removeSnapshot. Эта цель удалит -SNAPSHOT из версии POM, и runner GitLab отправит это изменение на удаленный master, пометит релиз, увеличит версию POM до следующей версии SNAPSHOT и развернет ее на Nexus. Это развернуто в UAT для тестирования QA и UAT. Как только артефакт будет одобрен для релиза в производство, группы производственного обслуживания возьмут артефакт релиза и развернут его в производственной среде. (Этот шаг также можно автоматизировать с помощью Ansible, в зависимости от вашей корпоративной политики.)



Патчи и hotfix


Мы должны упомянуть еще об одном побочном рабочем процессе, и это касается патчей или хотфиксов. Они запускаются, когда проблема обнаруживается в производственной среде или во время тестирования артефакта релиза, например, ошибка или проблема с производительностью. Hotfix аналогичны веткам релиза. Они названы по названию релиза, как и ветка релиза. Единственная разница в том, что они разветвлены не от разработки, а от masterа.



Проделана работа по завершению хотфикса. Как и ветка релиза, hotfix запускает развертывание Nexus SNAPSHOT и развертывание в UAT. Как только это сертифицировано, оно снова объединяется с разработкой, а затем сливается с masterом для подготовки к релизу. Master запустит сборку релиза и развернет двоичный файл релиза на Nexus.


Заключение


Мы можем суммировать все это в следующей схеме:



Итак, у нас есть Gitflow. Мы призываем команды разработчиков любого размера изучить и опробовать эту стратегию. Мы считаем, что он имеет следующие достоинства:


Feature изолированы. Легко управлять своими собственными изменениями Feature изолированно, с обычным предупреждением о ветвлении Feature, которое может затруднить интеграцию команды в очень активную Feature, или коммиты не часто объединяются


Разделение Feature, которое позволяет вам выбрать, какие Feature включить в релиз. Альтернативный подход к этому постоянно релизить код, связанный с Feature, которые скрыты за флагами Feature.


Процесс интеграции и слияния привел к тому, что наша команда провела более дисциплинированную проверку кода, что помогло продвинуть чистое кодирование.


Автоматическое тестирование, развертывание и релиз для всех сред, которые соответствуют требованиям наших команд и предпочтительному способу работы.


Наш подход может быть отходом от некоторых общепринятых норм в этой сфере и, как таковой, вызвал некоторые дискуссии в социальных сетях. Действительно, первоначальная версия этой статьи вызвала анализ и обсуждение подхода Стива Смита. Мы намерены поделиться своим пониманием того, как мы работаем, и предупреждаем, что описанный здесь процесс не подойдет для всех команд и не для всех способов работы.


Нам интересно узнать о вашем опыте работы с Gitflow и конвейерами развертывания, поэтому, пожалуйста, оставляйте свои комментарии ниже.


Дополнительная информация


Для более традиционной обработки Gitflow с использованием Atlassian Bamboo и BitBucket см. Здесь.


Существует также отличный плагин Gitflow Maven, который активно поддерживается Алексом Мащенко, который работает так же, как плагин релиза Maven на Gitflow. Это может быть адаптировано к нашей предлагаемой реализации Gitflow.


Об авторах


Виктор Граци работает в Nomura Securities над разработкой приложений для корпоративной инфраструктуры. Победитель Oracle Java, Виктор также работал ведущим редактором очереди Java в InfoQ и был членом комитета по выполнению процессов сообщества Java.


Брайан Гарднер недавний релизник Технологического института Стивенса, где он получил степень бакалавра и магистра в области компьютерных наук. В настоящее время Брайан работает в Nomura инженером-программистом в группе разработки инфраструктуры. В основном он проводит свой день, работая над серверными службами Spring Boot или над конвейерами больших данных с помощью Apache Spark.

Подробнее..

Прости, OpenShift, мы недостаточно ценили тебя и принимали как должное

15.10.2020 12:13:13 | Автор: admin
Этот пост написан поскольку у наших сотрудников было довольно много разговоров с клиентами о разработке приложений на Kubernetes и о специфике такой разработки на OpenShift.



Начинаем мы обычно с тезиса, что Kubernetes это просто Kubernetes, а OpenShift это уже Kubernetes-платформа, как Microsoft AKS или Amazon EKS. У каждой из этих платформ есть свои плюсы, ориентированные на ту или иную целевую аудиторию. И после этого разговор уже перетекает в сравнение сильных и слабых сторон конкретных платформ.

В общем, мы думали написать этот пост с выводом типа Слушайте, да без разницы, где запускать код, на OpenShift или на AKS, на EKS, на каком-то кастомном Kubernetes, да на каком-угодно-Kubernetes (для краткости назовем его КУК) это реально просто, и там, и там.

Затем мы планировали взять простейший Hello World и на его примере показать, что общего и в чем различия между КУК и Red Hat OpenShift Container Platform (далее, OCP или просто OpenShift).

Однако по ходу написания этого поста, мы поняли, что так давно и сильно привыкли использовать OpenShift, что просто не осознаем, как он вырос и превратился в потрясающую платформу, которая стала гораздо больше, чем просто Kubernetes-дистрибутив. Мы привыкли воспринимать зрелость и простоту OpenShift как должное, упуская из виду его великолепие.

В общем, пришла пора деятельного раскаяния, и сейчас мы пошагово сравним ввод в строй своего Hello World на КУК и на OpenShift, и сделаем это максимально объективно (ну разве что выказывая иногда личное отношение к предмету). Если вам интересно сугубо субъективное мнение по этому вопросу, то его можно прочитать здесь (EN). А в этом посте мы будем придерживаться фактов и только фактов.

Кластеры


Итак, для нашего Hello World нужны кластеры. Сразу скажем нет всяким публичным облакам, чтобы не платить за сервера, реестры, сети, передачу данных и т.д. Соответственно, мы выбираем простой одноузловой кластер на Minikube (для КУК) и Code Ready Containers (для кластера OpenShift). Оба этих варианта реально просты в установке, но потребуют довольно много ресурсов на вашем ноуте.



Сборка на КУК-е


Итак, поехали.

Шаг 1 собираем наш контейнерный образ


Начнем я с того, что развернем наш Hello World на minikube. Для этого потребуется:

  1. 1. Установленный Docker.
  2. 2. Установленный Git.
  3. 3. Установленный Maven (вообще-то в этом проекте используется mvnw-бинарник, так что можно обойтись и без этого).
  4. 4. Собственно, сам исходник, т.е. клон репозитория github.com/gcolman/quarkus-hello-world.git

Первым делом надо создать проект Quarkus. Не пугайтесь, если никогда не работали с сайтом Quarkus.io это легко. Просто выбираете компоненты, которые хотите использовать в проекте (RestEasy, Hibernate, Amazon SQS, Camel и т.д.), а дальше Quarkus уже сам, без какого-либо вашего участия, настраивает архетип maven и выкладывает всё на github. То есть буквально один клик мышью и готово. За это мы и любим Quarkus.



Самый простой способ собрать наш Hello World в контейнерный образ использовать расширения quarkus-maven для Dockerа, которые и проделают всю необходимую работу. С появлением Quarkus это стало действительно легко и просто: добавляете расширение container-image-docker и можете создавать образы командами maven.

./mvnw quarkus:add-extension -Dextensions=container-image-docker

И, наконец, выполняем сборку нашего образа, используя Maven. В результате наш исходный код превращается в готовый контейнерный образ, который уже можно запускать в среде исполнения контейнеров.



./mvnw -X clean package -Dquarkus.container-image.build=true

Вот, собственно, и всё, теперь можно запускать контейнер командой docker run, подмапив наш сервис на порт 8080, чтобы к нему можно было обращаться.

docker run -i  rm -p 8080:8080 gcolman/quarkus-hello-world




После того, как контейнерный инстанс запустился, остается только проверить командой curl, что наш сервис работает:



Итак, всё работает, и это было действительно легко и просто.

Шаг 2 отправляем наш контейнер в репозиторий контейнерных образов


Пока что созданный нами образ хранится локально, в нашем локальном контейнерном хранилище. Если мы хотим использовать этот образ в своей среде КУК, то его надо положить в какой-то другой репозиторий. В Kubernetes нет таких функций, поэтому мы будем использовать dockerhub. Потому что, во-первых, он бесплатный, а во-вторых, (почти) все так делают.

Это тоже очень просто, и нужен здесь только аккаунт на dockerhub.

Итак, ставим dockerhub и отправляем туда наш образ.



Шаг 3 запускаем Kubernetes


Есть много способов собрать конфигурацию kubernetes для запуска нашего Hello World, но мы будем использовать наипростейший из них, уж такие мы люди

Для начала запускаем кластер minikube:

minikube start

Шаг 4 развертываем наш контейнерный образ


Теперь надо преобразовать наш код и контейнерный образ в конфигурации kubernetes. Иначе говоря, нам нужен pod и deployment definition с указанием на наш контейнерный образ на dockerhub. Один из самых простых способов это сделать запустить команду create deployment, указав на наш образ:



kubectl create deployment hello-quarkus  image =gcolman/quarkus-hello-world:1.0.0-SNAPSHOT

Этой командной мы сказали нашей КУК создать deployment configuration, которая должна содержать спецификацию podа для нашего контейнерного образа. Эта команда также применит эту конфигурацию к нашему кластеру minikube, и создаст deployment, который скачает наш контейнерный образ и запустит pod в кластере.

Шаг 5 открываем доступ к нашему сервису


Теперь, когда у нас есть развернутый контейнерный образ, пора подумать, как сконфигурировать внешний доступ к этому Restful-сервису, который, собственно, и запрограммирован в нашем коде.

Тут есть много способов. Например, можно использовать команду expose, чтобы автоматически создавать соответствующие Kubernetes-компоненты, такие как services и endpoints. Собственно, так мы и сделаем, выполнив команду expose для нашего deployment-объекта:

kubectl expose deployment hello-quarkus  type=NodePort  port=8080

Давайте на минутку остановимся на опции type команды expose.

Когда мы делаем expose и создаем компоненты, необходимые для запуска нашего сервиса, нам, среди прочего, надо, чтобы снаружи можно было подключиться к службе hello-quarkus, которая сидит внутри нашей программно-определяемой сети. И параметр type позволяет нам создать и подключить такие вещи, как балансировщики нагрузки, чтобы маршрутизировать трафик в эту сеть.

Например, прописав type=LoadBalancer, мы автоматически инициализируем балансировщик нагрузки в публичном облаке, чтобы подключаться к нашему кластеру Kubernetes. Это, конечно, замечательно, но надо понимать, что такая конфигурация будет жестко привязана к конкретному публичному облаку и ее будет сложнее переносить между Kubernetes-инстансам в разных средах.

В нашем примере type=NodePort, то есть обращение к нашему сервису идет по IP-адресу узла и номеру порта. Такой вариант позволяет не использовать никакие публичные облака, но требует ряд дополнительных шагов. Во-первых, нужен свой балансировщик нагрузки, поэтому мы развернем в своем кластере балансирощик нагрузки NGINX.

Шаг 6 ставим балансировщик нагрузки


У minikube есть ряд платформенных функций, облегчающих создание необходимых для доступа извне компонентов, вроде ingress-контроллеров. Minikube идет в комплекте с ingress-контроллером Nginx, и нам остается только включить его и настроить.

minikube addons enable ingress

Теперь мы всего одной командой создам ingress-контроллер Nginx, который будет работать внутри нашего кластера minikube:

ingress-nginx-controller-69ccf5d9d8-j5gs9 1/1 Running 1 33m

Шаг 7 Настраиваем ingress


Теперь нам надо настроить ingress-контроллер Nginx, чтобы он воспринимал запросы hello-quarkus.





И, наконец, нам надо применить эту конфигурацию.



kubectl apply -f ingress.yml




Поскольку мы делаем все это на своем компе, то просто добавляем IP-адрес своей ноды в файл /etc/ hosts, чтобы направлять http-запросы к нашему minikube на балансировщик нагрузки NGINX.

192.168.99.100 hello-quarkus.info

Всё, теперь наш сервис minikube доступен снаружи через ingress-контроллер Nginx.



Ну что, это же было легко, да? Или не очень?





Запуск на OpenShift (Code Ready Containers)


А теперь посмотрим, как это все делается на Red Hat OpenShift Container Platform (OCP).

Как в случае с minikube, мы выбираем схему с одноузловым кластером OpenShift в форме Code Ready Containers (CRC). Раньше это называлось minishift и базировалось на проекте OpenShift Origin, а теперь это CRC и построено на Red Hatовской OpenShift Container Platform.

Здесь мы, извините, не можем сдержаться и не сказать: OpenShift прекрасен!

Изначально мы думали написать, что разработка на OpenShift ничем не отличается от разработки на Kubernetes. И по сути так оно и есть. Но в процессе написания этого поста мы вспомнили, сколько лишних движений приходится совершать, когда у вас нет OpenShift, и поэтому он, повторимся, прекрасен. Мы любим, когда всё делается легко, и то, как легко по сравнению с minikube наш пример развертывается и запускается на OpenShift, собственно, и подвигло нас написать этот пост.

Давайте пробежимся по процессу и посмотрим, что нам потребуется сделать.

Итак, в примере с minikube мы начинали с Docker Стоп, нам больше не надо, чтобы на машине был установлен Docker.

И локальный git нам не нужен.
И Maven не нужен.
И не надо руками создавать контейнерный образ.
И не надо искать какой-нибудь репозиторий контейнерных образов.
И не надо устанавливать ingress-контроллер.
И конфигурировать ingress тоже не надо.


Вы поняли, да? Чтобы развернуть и запустить наше приложение на OpenShift, не нужно ничего из вышеперечисленного. А сам процесс выглядит следующим образом.

Шаг 1 Запускаем свой кластер OpenShift


Мы используем Code Ready Containers от Red Hat, который по сути есть тот же Minikube, но только с полноценным одноузловым кластером Openshift.

crc start

Шаг 2 Выполняем сборку и развертывание приложения в кластере OpenShift


Именно на этом шаге простота и удобство OpenShift проявляются во всей красе. Как и во всех дистрибутивах Kubernetes, у нас есть много способов запустить приложение в кластере. И, как и в случае КУК, мы специально выбираем самый наипростейший.

OpenShift всегда строился как платформа для создания и запуска контейнерных приложений. Сборка контейнеров всегда была неотъемлемой частью этой платформы, поэтому здесь есть куча дополнительных Kubernetes-ресурсов для соответствующих задач.

Мы будем использовать OpenShift'овский процесс Source 2 Image (S2I), у которого есть несколько различных способов для того, чтобы взять наш исходник (код или двоичные файлы) и превратить его в контейнерный образ, запускаемый в кластере OpenShift.

Для этого нам понадобятся две вещи:

  • Наш исходный код в репозитории git
  • Builder-образ, на основании которого будет выполняться сборка.

Существует множество таких образов, сопровождаемых как силами Red Hat, так и на уровне сообщества, и мы воспользуемся образом OpenJDK, ну поскольку я же собираю Java-приложение.

Запустить S2I-сборку можно, как и из графической консоли OpenShift Developer, так и из командной строки. Мы воспользуемся командой new-app, указав ей, где брать builder-образ и наш исходный код.



oc new-app registry.access.redhat.com/ubi8/openjdk-11:latest~https://github.com/gcolman/quarkus-hello-world.git

Всё, наше приложение создано. При этом процесс S2I выполнил следующие вещи:

  • Создал служебный build-pod для всяких вещей, связанных со сборкой приложения.
  • Создал конфиг OpenShift Build.
  • Скачал builder-образ во внутренний docker-реестр OpenShift.
  • Клонировал Hello World в локальный репозиторий.
  • Увидел, что там есть maven pom, и поэтому скомпилировал приложение с помощью maven.
  • Создал новый контейнерный образ, содержащий скомпилированное Java-приложение, и положил этот образ во внутренний реестр контейнеров.
  • Создал Kubernetes Deployment со спецификациями podа, сервиса и т.д.
  • Запустил deploy контейнерного образа.
  • Удалил служебный build-pod.

В этом списке много всего, но главное, что вся сборка происходит исключительно внутри OpenShift, внутренний Docker-реестр находится внутри OpenShift, а процесс сборки создает все компоненты Kubernetes и запускает их в кластере.

Если визуально отслеживать запуск S2I в консоли, то можно видно, как при выполнении сборки запускается build pod.



А теперь взглянем логи builder podа: во-первых, там видно, как maven делает свою работу и скачивает зависимости для сборки нашего java-приложения.



После того, как закончена maven-сборка, запускается сборка контейнерного образа, и затем этот собранный образ отправляется во внутренний репозиторий.



Всё, процесс сборки завершен. Теперь убедимся, что в кластере запустились podы и сервисы нашего приложения.

oc get service




Вот и всё. И всего одна команда. Нам остается только сделать expose этого сервиса для доступа извне.

Шаг 3 делаем expose сервиса для доступа извне


Как и в случае КУК, на платформе OpenShift нашему Hello World тоже нужен роутер, чтобы направлять внешний трафик на сервис внутри кластера. В OpenShift это делает очень просто. Во-первых, в кластере по умолчанию установлен компонент маршрутизации HAProxy (его можно поменять на тот же NGINX). Во-вторых, здесь есть специальные и допускающие широкие возможности настройки ресурсы, которые называются Routes и напоминают Ingress-объекты в старом добром Kubernetes (на самом деле OpenShiftовкие Routes сильно повлияли на дизайн Ingress-объектов, которые теперь можно использовать и в OpenShift), но для нашего Hello World, да и почти во всех остальных случаях, нам хватит стандартного Route без дополнительной настройки.

Чтобы создать для Hello World маршрутизируемый FQDN (да, в OpenShiift есть свой DNS для маршрутизации по именам сервисов), мы просто выполним expose для нашего сервиса:



oc expose service quarkus-hello-world

Если посмотреть только что созданный Route, то там можно найти FQDN и другие сведения по маршрутизации:

oc get route




И наконец, обращаемся к нашему сервису из браузера:



А вот теперь это было действительно легко!


Мы любим Kubernetes и всё, что позволяет делать эта технология, а также мы любим простоту и легкость. Kubernetes создавался, чтобы невероятно упростить эксплуатацию распределенных масштабируемых контейнеров, но вот для ввода в строй приложений его простоты сегодня уже недостаточно. И здесь в игру вступает OpenShift, который идет в ногу со временем и предлагает Kubernetes, ориентированный в первую очередь на разработчика. Была вложена масса усилий, чтобы заточить платформу OpenShift именно под разработчика, включая создание таких инструментов, как S2I, ODI, Developer Portal, OpenShift Operator Framework, интеграция с IDE, Developer Catalogues, интеграция с Helm, мониторинг и многие другие.

Надеемся, что эта статься была для вас интересной и полезной. А найти дополнительные ресурсы, материалы и другие полезные для разработки на платформе OpenShift вещи можно на портале Red Hat Developers.

Перевод, источник: itnext.io/im-sorry-openshift-i-ve-taken-you-for-granted-the-evidence-dd7a7d471fa1
Подробнее..

Настройка GitLab CI CD для Java приложения

19.02.2021 14:12:20 | Автор: admin

Из-за прекращения поддержи Bitbucket Setver пришлось переехать на GitLab.


В Bitbucket Server не было встроенного CI/CD, поэтому использовали Teamcity. Из-за проблемы интеграции Teamcity с GitLab, мы попробовали GitLab Pipline. И остались довольны.


Disclamer: У меня не так много опыта в CI/CD, так что статья скорее для новичков. Буду рад услышать конструктивную критику с предложениями по оптимизации скрипта сборки :)


Коротко о Gitlab Pipline


Если говорить простыми словами: сборка делится на задачи. Какие-то задачи выполняются последовательно и передают результаты следующим задачам, какие-то задачи распаралеливаются: например можно одновременно деплоить на сервер и в nexus.


Раннеры и задачи


Не обязательно один раннер отвечает за все задачи в сборке. Если на проекте два раннера, то первую задачу может взять один ранер, после ее выполнения вторую задачу возьмет другой раннер.


Раннеры бывают разных типов. Мы рассмотрим executor docker. Для каждой задачи создается новый чистый контейнер. Но между контейнерами можно передавать промежуточные результаты это называется кэширование.


Кэширование и его особенности


Механизм кэширования разрабатывал какой-то одаренный человек. Поэтому сразу разобраться, как оно работает, будет не просто. В отдельной статье можно прочитать о кэшировании подробнее.


Каждый раннер хранит кэш в папке /cache. Для каждого проекта в этой папке создается еще папка. Сам кэш хранится в виде zip архива.


Из-за наличия у каждого раннера своей папки для кэша, возникает проблема. Если один раннер кэшировал папку в первой задаче, а второй раннер взялся за выполнение второй задачи, то у него не окажется кэша из первой задачи. Чуть далее рассмотрим как это исправить.


Можно определить только один набор папок для кэша. Также только для всего набора можно выбрать политику кэширования. Например если вы уверены, что задача не изменит кэш, то можно его не загружать. Но нельзя запретить загружать например две папки из четырех.


Нет возможности установить глобальный кэш и добавить к нему дополнительные папки для конкретных задач.


Так же стоит знать, что кэш не удаляется после окончания сборки. При следующей такой же сборке старый кэш будет доступен.


Эти особенности усложняют создание инструкций для CI CD.


Артефакты


Помимо кэша между сборками можно передавать артефакты.


Артефакт это файлы, которые считаются законченным продуктом сборки. Например .jar файлы приложения.


В отличие от кэша, артефакт передается между раннерами. Из-за этого многие злоупотребляют использованием артефактов там, где стоит использовать кэш.


Следуйте следующим правилам:


  1. Если текущее задание может выполняться самостоятельно, но в присутствии контента работа будет идти быстрее, используйте кэш.
  2. Если текущее задание зависит от результатов предыдущего, то есть не может выполняться само по себе, используйте артефакты и зависимости.

Установка Gitlab Runner


Перед установкой ранера создадим папку с конфигурацией, чтобы не приходилось для изменений лезть в контейнер.


mkdir ~/runner_name

Само создание ранера выполняется в одну команду. Можно создать сколько угодно ранеров.


docker run -d --name gitlab-runner-name \  --restart always \  -v /var/run/docker.sock:/var/run/docker.sock \  -v /home/user/runner_name:/etc/gitlab-runner:z \  gitlab/gitlab-runner:latest

Мало создать раннер, теперь его нужно зарегистрировать в GitLab. Зарегистрировать можно на уровне всего GitLab, тогда сборки будут выполняться для любого проекта; на уровне группы выполнятся только для группы, и на уровне проекта.


Заходим в контейнер.


sudo docker exec -ti gitlab-runner-name bash

Внутри контейнера выполним команду регистрации. Регистрация происходит в интерактивном режиме.


gitlab-runner register

Отвечаем на вопросы:


Runtime platform                                    arch=amd64 os=linux pid=27 revision=888ff53t version=13.8.0Running in system-mode.                            Enter the GitLab instance URL (for example, https://gitlab.com/):http://git.company.name/Enter the registration token:vuQ6bcjuEPqc8dVRRhgYEnter a description for the runner:[c6558hyonbri]: runner_twoEnter tags for the runner (comma-separated):Registering runner... succeeded                     runner=YJt3v3QgEnter an executor: parallels, shell, virtualbox, docker+machine, kubernetes, custom, docker, docker-ssh+machine, docker-ssh, ssh:dockerEnter the default Docker image (for example, ruby:2.6):maven:3.3.9-jdk-8Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded! 

Тут все просто:


  1. Адрес вашего gitlab.
  2. Токен авторизации. Посмотреть его можно в настройках гурппы/проекта в разделе CI/CD Runners.
  3. Название раннера.
  4. Теги ранера, можно пропустить нажав Enter.
  5. Исполнитель сборки. Вводим docker.
  6. Образ, который будет использоваться по умолчанию, если не установлен другой.

После этого в настройках проекта можно посмотреть доступные раннеры.


Добавленный ранер GitLab


После регистрации, в папке /home/user/runner_name появится файл с настройками конфигурации config.toml. Нам нужно добавить docker volume для кэширования промежуточных результатов.


volumes = ["gitlab-runner-builds:/builds", "gitlab-runner-cache:/cache"]

Проблема кэширования.
В начале статьи я рассказал о проблеме кеширования. Ее можно решить с помощью монтирования одного volume к разным раннерам. То есть во втором своем раннере так же укажите volumes = ["gitlab-runner-cache:/cache"]. Таким образом разные раннеры будут иметь единый кэш.


В итоге файл конфигурации выглядит так:


concurrent = 1check_interval = 0[session_server]  session_timeout = 1800[[runners]]  name = "runner_name"  url = "gitlab_url"  token = "token_value"  executor = "docker"  [runners.custom_build_dir]  [runners.cache]    [runners.cache.s3]    [runners.cache.gcs]    [runners.cache.azure]  [runners.docker]    tls_verify = false    image = "maven:3.3.9-jdk-8"    privileged = false    disable_entrypoint_overwrite = false    oom_kill_disable = false    disable_cache = false    volumes = ["gitlab-runner-builds:/builds", "gitlab-runner-cache:/cache"]    shm_size = 0

После изменения перезапускаем раннер.


docker restart gitlab-runner-name

Что хотим получить от CI CD?


У нас на проекте было 3 контура:


  • dev-сервер, на него все попадает сразу после MR;
  • пре-прод-сервер, на него все попадает перед попаданием на прод, там проходит полное регресс тестирование;
  • прод-сервер, собственно сама прод среда.

Что нам было необходимо от нашего CI/CD:


  • Запуск unit-тестов для всех MergeRequest
  • При мерже в dev ветку повторный запуск тестов и автоматический деплой на dev-сервер.
  • Автоматическая сборка, тестирвоание и деплой веток формата release/* на пре-прод-сервер.
  • При мерже в master ничего не происходит. Релиз собирается при обнаружении тега формата release-*. Одновременно с деплоем на прод-сервер будет происходить загрузка в корпоративный nexus.
  • Бонусом настроим уведомления о статусе деплоя в Telegram.

Настройка GitLab CI для Maven


Что делать если у вас Gradle? Загляните в оригинал статьи, там я рассказываю про настройку и для Gradle. Она не сильно отличается.


Создайте в корне проекта файл .gitlab-ci.yml.


Настройку буду объяснять блоками, в конце прикреплю единый файл.


Вы можете указать нужный образ, вместо дефолтного образа у раннера.


image: maven:latest

Устанавливаем место хранения папки .m2 через переменные среды variables. Это понадобится, когда будем настраивать кэширование.


// ... ... ... ... ...variables:  MAVEN_OPTS: "-Dmaven.repo.local=./.m2/repository"// ... ... ... ... ...

Далее указываются этапы сборки. Они позволяют группировать задачи для одновременного выполнения.


// ... ... ... ... ...stages:  - build  - test  - package  - deploy  - notify// ... ... ... ... ...

  • build стадия сборки.
  • test стадия тестирования.
  • package стадия упаковки в jar.
  • deploy стадия деплоя на сервер.
  • notify стадия уведомления о провале.

Указываем непосредственно задачи для каждой стадии.


Сборка Build


// ... ... ... ... ...build:  stage: build  only:    - dev    - merge_requests    - /^release\/.*$/  except:    - tags  script:    - 'mvn --settings $MAVEN_SETTINGS compile'  cache:    paths:      - ./target      - ./.m2// ... ... ... ... ...

Раздел script выполняет linux команды.


Переменная GitLab CI/CD $MAVEN_SETTINGS необходима для передачи файла settings.xml, если вы используете нестандартные настройки, например корпоративные репозитории. Переменная создается в настройках CI/CD для группы/проекта. Тип переменной File.


Раздел only указывает для каких веток и тегов выполнять задачу. Чтобы не собирать каждую запушенную ветку устанавливаем: dev, merge_requests и ветки формата /release/*.


Раздел only не разделяет ветка это или тег. Поэтому мы указываем параметр except, который исключает теги. Из-за этого поведения за сборку на прод отвечают отдельные задачи. В противном случае, если бы кто-то создал тег формата release/*, то он бы запустил сборку.


Для защиты от случайного деплоя в прод джуном, рекомендую установить защиту на ветки dev, master, /release/*, а так же на теги release-*. Делается это в настройках проекта GitLab.


Раздел cache отвечает за кэширование. Чтобы каждый раз не выкачивать зависимости добавляем в кэш папку ./target и ./.m2.


Запуск unit тестов test


Создаем задачу для запука тестирования.


// ... ... ... ... ...test:  stage: test  only:    - dev    - merge_requests    - /^release\/.*$/  except:    - tags  script:    - 'mvn --settings $MAVEN_SETTINGS test'  cache:    paths:      - ./target      - ./.m2// ... ... ... ... ...

Ничем не отличается от стадии сборки, только команда мавена test. Кэш необходимо указать и в этой задаче, чтобы получить его и отправить дальше.


Упаковка package


Следом добавляем задачу упаковки с выключенными тестами. Она уже выполняется только для веток dev и release/*. Упаковывать Merge Request смысла нет.


// ... ... ... ... ...package:  stage: package  only:    - dev    - /^release\/.*$/  except:    - tags  script:    - 'mvn --settings $MAVEN_SETTINGS package -Dmaven.test.skip=true'  artifacts:    paths:      - target/*.jar  cache:    policy: pull    paths:      - ./target      - ./.m2// ... ... ... ... ...

Обратите внимание на policy: pull и artifacts. В следующих задачах не нужны исходники и зависимости, так что policy: pull отключает кэширование. Для сохранения результатов сборки используем artifacts, который сохраняет .jar файлы и передает их следующим задачам.


Деплой deploy


Теперь осталось задеплоить артефакт на dev-сервер с помощью ssh и scp.


// ... ... ... ... ...deploy_dev_server:  stage: deploy  only:    - dev  except:    - tags  before_script:    - which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )    - eval $(ssh-agent -s)    - echo "$SSH_PRIVATE_KEY" | ssh-add -    - mkdir -p ~/.ssh    - chmod 700 ~/.ssh    - ssh-keyscan $DEV_HOST >> ~/.ssh/known_hosts    - chmod 644 ~/.ssh/known_hosts  script:    - ssh $DEV_USER@$DEV_HOST "[ ! -f $DEV_APP_PATH/app_name.jar ] || mv $DEV_APP_PATH/app_name.jar $DEV_APP_PATH/app_name-build-$CI_PIPELINE_ID.jar"    - scp target/app_name.jar $DEV_USER@$DEV_HOST:$DEV_APP_PATH/    - ssh $DEV_USER@$DEV_HOST "sudo systemctl stop app_name_service && sudo systemctl start app_name_service"    - sh ci-notify.sh // ... ... ... ... ...

Не забудьте создать переменные в GitLab CI CD:


  • $DEV_USER пользователь системы, от чьего имени будет происходить деплой.
  • $DEV_HOST ip адрес сервера.
  • $DEV_APP_PATH путь до папки приложения.
  • $SSH_PRIVATE_KEY приватный ключ.

Раздел before_script отвечает за выполнение настроек перед основным скриптом. Мы проверяем наличие ssh-agent, устанавливаем его при отсутствии. После чего добавляем приватный ключ, устанавливаем правильные права на папки.


В разделе script происходит деплой на сервер:


  1. Проверяем наличие старого jar и переименовываем его. $CI_PIPELINE_ID это глобальный номер сборки Pipeline.
  2. Копируем новый jar на сервер.
  3. Останавливаем и запускаем службу, отвечающую за приложение.
  4. Отправляем уведомление об успехе в телеграм. Об этом ниже.

Как создавать службы linux для Spring Boot приложения, я написал в отдельной статье.


На пре-прод делаем по аналогии, только меняем переменные на $PRE_PROD_*.


// ... ... ... ... ...deploy_pre_prod:  stage: deploy  only:    - /^release\/.*$/  except:    - tags  before_script:    - which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )    - eval $(ssh-agent -s)    - echo "$SSH_PRIVATE_KEY" | ssh-add -    - mkdir -p ~/.ssh    - chmod 700 ~/.ssh    - ssh-keyscan $PRE_PROD_HOST >> ~/.ssh/known_hosts    - chmod 644 ~/.ssh/known_hosts  script:    - ssh $PRE_PROD_USER@$PRE_PROD_HOST "[ ! -f $PRE_PROD_APP_PATH/app_name.jar ] || mv $PRE_PROD_APP_PATH/app_name.jar $PRE_PROD_APP_PATH/app_name-build-$CI_PIPELINE_ID.jar"    - scp target/app_name.jar $DEV_USER@$PRE_PROD_HOST:$PRE_PROD_APP_PATH/    - ssh $PRE_PROD_USER@$PRE_PROD_HOST "sudo systemctl stop app_name_service && sudo systemctl start app_name_service"    - sh ci-notify.sh // ... ... ... ... ...

Настройка деплоя на прод


Для прод-деплоя можно объединить сборку, тестирование и упаковку в одну задачу.


// ... ... ... ... ...package_prod:  stage: package  only:    - /^release-.*$/  except:    - branches  script:    - 'mvn --settings $MAVEN_SETTINGS package'  artifacts:    paths:      - target/*.jar// ... ... ... ... ...

Мы защищаемся от срабатывания на ветки формата release-*, нам нужно срабатывание только по тегу.


Деплой аналогичен пре-проду, изменяется только only и переменные.


Дополнительно появляется задача с отправкой артефакта в корпоративный nexus. Деплой на сервер и в нексус работает параллельно.


// ... ... ... ... ...deploy_prod:  stage: deploy  only:    - /^release-.*$/  except:    - branches  ...deploy_nexus_server:  stage: deploy  only:    - /^release-.*$/  except:    - branches  script:    - 'mvn --settings $MAVEN_SETTINGS deploy -Dmaven.test.skip=true'// ... ... ... ... ...

Итоговый .gitlab-ci.yml:


image: maven:latestvariables:  MAVEN_OPTS: "-Dmaven.repo.local=./.m2/repository"stages:  - build  - test  - package  - deploy  - notifybuild:  stage: build  only:    - dev    - merge_requests    - /^release\/.*$/  except:    - tags  script:    - 'mvn --settings $MAVEN_SETTINGS compile'  cache:    paths:      - ./target      - ./.m2test:  stage: test  only:    - dev    - merge_requests    - /^release\/.*$/  except:    - tags  script:    - 'mvn --settings $MAVEN_SETTINGS test'  cache:    paths:      - ./target      - ./.m2package:  stage: package  only:    - dev    - /^release\/.*$/  except:    - tags  script:    - 'mvn --settings $MAVEN_SETTINGS package -Dmaven.test.skip=true'  artifacts:    paths:      - target/*.jar  cache:    policy: pull    paths:      - ./target      - ./.m2deploy_dev_server:  stage: deploy  only:    - dev  except:    - tags  before_script:    - which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )    - eval $(ssh-agent -s)    - echo "$SSH_PRIVATE_KEY" | ssh-add -    - mkdir -p ~/.ssh    - chmod 700 ~/.ssh    - ssh-keyscan $DEV_HOST >> ~/.ssh/known_hosts    - chmod 644 ~/.ssh/known_hosts  script:    - ssh $DEV_USER@$DEV_HOST "[ ! -f $DEV_APP_PATH/app_name.jar ] || mv $DEV_APP_PATH/app_name.jar $DEV_APP_PATH/app_name-build-$CI_PIPELINE_ID.jar"    - scp target/app_name.jar $DEV_USER@$DEV_HOST:$DEV_APP_PATH/    - ssh $DEV_USER@$DEV_HOST "sudo systemctl stop app_name_service && sudo systemctl start app_name_service"deploy_pre_prod:  stage: deploy  only:    - /^release\/.*$/  except:    - tags  before_script:    - which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )    - eval $(ssh-agent -s)    - echo "$SSH_PRIVATE_KEY" | ssh-add -    - mkdir -p ~/.ssh    - chmod 700 ~/.ssh    - ssh-keyscan $PRE_PROD_HOST >> ~/.ssh/known_hosts    - chmod 644 ~/.ssh/known_hosts  script:    - ssh $PRE_PROD_USER@$PRE_PROD_HOST "[ ! -f $PRE_PROD_APP_PATH/app_name.jar ] || mv $PRE_PROD_APP_PATH/app_name.jar $PRE_PROD_APP_PATH/app_name-build-$CI_PIPELINE_ID.jar"    - scp target/app_name.jar $DEV_USER@$DEV_HOST:$DEV_APP_PATH/    - ssh $PRE_PROD_USER@$PRE_PROD_HOST "sudo systemctl stop app_name_service && sudo systemctl start app_name_service"package_prod:  stage: package  only:    - /^release-.*$/  except:    - branches  script:    - 'mvn --settings $MAVEN_SETTINGS package'  artifacts:    paths:      - target/*.jardeploy_prod:  stage: deploy  only:    - /^release-.*$/  except:    - branches  before_script:    - which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )    - eval $(ssh-agent -s)    - echo "$SSH_PRIVATE_KEY" | ssh-add -    - mkdir -p ~/.ssh    - chmod 700 ~/.ssh    - ssh-keyscan $PROD_HOST >> ~/.ssh/known_hosts    - chmod 644 ~/.ssh/known_hosts  script:    - ssh $PROD_USER@$PROD_HOST "[ ! -f $PROD_APP_PATH/app_name.jar ] || mv $PROD_APP_PATH/app_name.jar $PROD_APP_PATH/app_name-build-$CI_PIPELINE_ID.jar"    - scp target/app_name.jar $PROD_USER@$PROD_HOST:$PROD_APP_PATH/    - ssh $PROD_USER@$PROD_HOST "sudo systemctl stop app_name_service && sudo systemctl start app_name_service"

Бонус: уведомления о деплое в Telegram


Полезная фишка для уведомления менеджеров. После сборки в телеграм отправляется статус сборки, название проекта и ветка сборки.


В задачах деплоя последней командой пропишите:


// ... ... ... ... ...script:  - ...  - sh ci-notify.sh // ... ... ... ... ...

Сам файл необходимо добавить в корень проекта, рядом с файлом .gitlab-ci.yml.


Содержимое файла:


#!/bin/bashTIME="10"URL="http://personeltest.ru/aways/api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendMessage"TEXT="Deploy status: $1%0A-- -- -- -- --%0ABranch:+$CI_COMMIT_REF_SLUG%0AProject:+$CI_PROJECT_TITLE"curl -s --max-time $TIME -d "chat_id=$TELEGRAM_CHAT_ID&disable_web_page_preview=1&text=$TEXT" $URL >/dev/null

Скрипт отправляет запрос к API Telegram, через curl. Параметром скрипта передается emoji статуса билда.


Не забудьте добавить новые параметры в CI/CD:


  • $TELEGRAM_BOT_TOKEN токен бота в телеграмм. Получить его можно при создании своего бота.
  • $TELEGRAM_CHAT_ID идентификатор чата или беседы, в которую отправляется сообщение. Узнать идентификатор можно с помощью специального бота.

Необходимо создать задачи для уведомления о падении сборки.


// ... ... ... ... ...notify_error:  stage: notify  only:    - dev    - /^release\/.*$/  script:    - sh ci-notify.sh   when: on_failurenotify_error_release:  stage: notify  only:    - /^release-.*$/  except:    - branches  script:    - sh ci-notify.sh   when: on_failure// ... ... ... ... ...

По сложившейся традиции у нас отдельная задача для прод-среды. Благодаря параметру when: on_failure задача отрабатывает только, когда одна из предыдущих завершилась неудачно.


Удобно, что теперь и код и сборка находятся в одном месте. Несмотря на недостатки GitLab CI, пользоваться им в целом удобно.

Подробнее..
Категории: Gitlab , Devops , Java , Gitlab-ci , Maven , Nexus , Gitlab-runner

Организация разработки в изолированной сети как управлять зависимостями?

30.07.2020 10:07:11 | Автор: admin

Всем привет,


Наша компания занимается разработкой CUBA Open Source Java фреймворка для разработки корпоративных приложений. Платформа CUBA это целая экосистема, которая включает в себя сам фреймворк и разнообразные аддоны, предоставляющие прикладной функционал, готовый к использованию в несколько кликов. За последние несколько лет фреймворк сильно набрал популярность и сейчас используется более 20 000 разработчиками по всему земному шару. С ростом популярности мы сталкивались с множеством интересных кейсов и в этой статье хотим затронуть один из них. Возможно, этот материал поможет в вашей практике, особенно если вы работаете в организации, в которой вопросы безопасности больно бьют по рукам разработчиков.


image


Конкретно мы поговорим про тот случай, когда в компании лимитирован доступ к всемирной паутине настолько, что вы не можете пользоваться публичными репозиториями артефактов, например таким, как maven central. Представьте себе, что мы (или вы) поставляем наш фреймворк в такой закрытый контур. Как же нам предоставить все необходимые артефакты, от которых мы зависим, включая транзитивные зависимости? Как потом их обновлять вместе с версиями фреймворка?


В данной статье я хочу рассказать о нашем новом инструменте CUBA SDK консольной утилите, которая позволяет определять все транзитивные зависимости для Maven-библиотек и управлять ими в удаленных репозиториях. Также в статье мы рассмотрим пример, который позволит вам использовать наши наработки для любого Java приложения с применением Maven-зависимостей.


Проблема загрузки транзитивных зависимостей для внутренних репозиториев


Практически любой современный JVM проект, задействующий Apache Maven или Gradle в качестве инструмента сборки, имеет большое количество транзитивных зависимостей для используемых библиотек или фреймворков. При наличии доступного репозитория в сети эти зависимости автоматически разрешаются с помощью менеджера зависимостей.


Но что делать в случае, если публичные репозитории недоступны из внутренней сети?


Возможные варианты решения проблемы


Казалось бы, все просто берем Nexus и проксируем обращение к репозиториям через него. Действительно, это решение в большинстве случаев может подойти, но не в тех случаях, где к безопасности предъявляются беспрецедентные требования. К таким организациям относятся предприятия военной промышленности, министерства, центробанк и прочие государственные институты. Выход в интернет в таких местах строго регламентирован и зачастую не позволяет использовать традиционное проксирование.


Что же нам остается?


  • Вариант 0. Упрашивание безопасников.
  • Вариант 1. Шлюз.
  • Вариант 2. Ручное управление зависимостями.

Вариант 0 рассматривать не будем, рассмотрим вариант 1 и 2.


Для варианта со шлюзом: в организации должен быть шлюзовой компьютер, который одновременно может быть подключен или к внешней, или к внутренней сети. После загрузки и проверки необходимых библиотек из внешней сети шлюз переключается во внутреннюю сеть, и после этого библиотеки загружаются во внутренние репозитории. Добавление новых зависимостей или обновление существующих для одного проекта обычно занимает продолжительное время, так как нужно получить доступ к шлюзу, загрузить необходимые артефакты, проверить, что все транзитивные зависимости были загружены корректно, и затем выгрузить зависимости во внутренний репозиторий. Если проектов в организации много, то работа команды разработки может простаивать в ожидании, пока необходимые зависимости станут доступны из внутренних репозиториев.


Для варианта с ручным управлением зависимостями при обновлении или добавлении компонентов придется каждый раз проверять список зависимостей для библиотек и сравнивать с уже загруженными библиотеками.


Как видим из доступных примеров, добавление новых или обновление существующих зависимостей это довольно сложное действие.


Если не рассматривать систему безопасности в организациях, то проблема доступности репозиториев актуальна и для разработчиков, например, разработчик планирует поработать в самоизоляции в глухой деревне у бабушки, но интернет там не ловит. Чтобы подготовиться для таких случаев, всегда можно попробовать использовать оффлайн плагины для Gradle или Maven. Но если проектов несколько, то для каждого проекта нужно будет


  • настроить оффлайн плагин
  • на всякий случай добавить в проект все зависимости, которые потенциально могут пригодиться, чтобы оффлайн плагины смогли их корректно добавить в свой кэш
  • выгрузить все зависимости в локальную папку

Это не очень удобно, так как в проекте будут лишние зависимости и конфигурации, которые потом нужно будет не забыть удалить из проекта. И все равно останется проблема с созданием нового проекта, потому что может не хватать нужных модулей.


Как мы предлагаем решать эти проблемы?


CUBA SDK


Среди пользователей платформы CUBA есть организации в которых по соображениям безопасности нет доступа к внешней сети или доступ ограничен.
Мы решили облегчить жизнь таким пользователям и сделать консольное приложение CUBA SDK, которое могло бы определять все зависимости для аддонов и платформы CUBA.


В чем отличие CUBA SDK от оффлайн плагинов для Gradle или Maven?
Главное отличие CUBA SDK не кэширует зависимости конкретного проекта, а позволяет синхронизировать артефакты между внешними и внутренними репозиториями, чтобы разработчикам было комфортно создавать и разрабатывать приложения в закрытой сети.
CUBA SDK не требует проекта и позволяет создать необходимый оффлайн стек фреймворков, аддонов и библиотек со всеми зависимостями.


Для разработчика это может быть полезно, если он работает с несколькими проектами или только планирует начать новый проект и сразу не знает, какие модули будут использоваться в его проекте. С помощью SDK эти модули могут быть заранее загружены в локальный или внутренний репозиторий.


Для организаций это может быть полезно для централизованной синхронизации внутренних репозиториев.


image


CUBA SDK позволяет с помощью нескольких консольных команд определять все зависимости для артефактов и заливать их в нужные репозитории. Для полностью закрытых сетей можно использовать команды импорта и экспорта или установить CUBA SDK на шлюз.


Преимущества использования CUBA SDK:


  • автоматически собирает все зависимости с исходным кодом для загружаемых библиотек
  • определяет зависимости для платформы и аддонов CUBA Platform
  • проверяет и устанавливает новые версии библиотек
  • может работать одновременно с несколькими репозиториями для поиска артефактов, включая локальные maven репозитории
  • имеет встроенный Nexus OSS репозиторий артефактов
  • даёт возможность загрузки артефактов одновременно в несколько репозиториев, включая локальные maven
  • производит импорт и экспорт артефактов со всеми зависимостями
  • предоставляет интерактивный режим с подсказками для установки платформы и аддонов CUBA Platform
  • использует механизмы Gradle для определения зависимостей
  • не зависит от IDE
  • может быть установлен на CI сервере

Команды SDK


Полный список доступных команд можно посмотреть на странице GitHub.


CUBA SDK изначально поддерживает три типа компонентов: CUBA Framework, CUBA addon и библиотека, которая может быть загружена через maven координаты. Этот список может быть расширен для других типов компонентов через плагины для CUBA SDK.


Установка компонента в удаленный репозиторий может быть выполнена через команду install. При создании SDK мы предусмотрели вариант, когда SDK может быть установлен на шлюзовом компьютере или на переносном носителе, в этом случае установку компонентов можно сделать через команды resolve и push.


resolve просто определяет и скачивает все зависимости в локальный кэш SDK
push заливает уже скачанные артефакты с зависимостями в настроенные target репозитории


Для работы с репозиториями в SDK есть встроенный менеджер репозиториев.
Менеджер репозиториев поддерживает локальные и удаленные репозитории, которые в SDK разделены на две группы:


  • source это репозитории, которые будут использоваться для поиска артефактов
  • target репозитории, в которые нужно будет залить эти артефакты

SDK сам может использоваться как репозиторий, для этого с помощью команды setup-nexus SDK скачивает, устанавливает и настраивает репозиторий Nexus OSS. Для запуска и остановки репозитория используются команды start и stop.


Для проверки и установки обновлений достаточно выполнить команду check-updates.


Определение зависимостей


Самая главная проблема, которую должен был решать SDK это корректное определение и сбор зависимостей для компонентов. При разработке мы попробовали несколько подходов для определения транзитивных зависимостей компонентов. Изначально возникла идея, что можно просто распарсить .pom файлы и составить дерево зависимостей. Но идея парсить файлы вручную оказалась не очень хорошей, тем более что Apache Maven все это уже умеет делать из коробки.


Maven как менеджер зависимостей


Поэтому мы решили использовать Apache Maven для определения транзитивных зависимостей компонентов.


Для этого в CUBA SDK скачивается дистрибутив maven в домашнюю папку SDK и через Java Runtime запускаются команды.


Например, с помощью команды


dependency:resolve -Dtransitive=true -DincludeParents=true -DoverWriteSnapshots=true -Dclassifier=<classifier> -f pom.xml

мы определяли все зависимости для компонентов, которые описаны в pom.xml, и эти компоненты автоматически скачивались в локальный кэш maven, после чего с помощью команды


org.apache.maven.plugins:maven-deploy-plugin:3.0.0-M1:deploy-file -Durl=<repository URL>

артефакты заливались в нужный репозиторий.


Следующая команда позволяет загрузить библиотеку в локальный репозиторий.


org.apache.maven.plugins:maven-dependency-plugin:3.1.1:get -Dartifact=<maven coordinates>

Для выполнения команд Maven в приложении CUBA SDK сгенерировался settings.xml файл. Он содержал список всех репозиториев, которые должны были использоваться для загрузки и выгрузки артефактов.


Gradle как менеджер зависимостей


В первой версии приложения зависимости определялись корректно, но довольно медленно, и при тестировании функционала стали возникать конфликты при определении зависимостей для некоторых аддонов CUBA Platform, хотя при сборке проекта через Gradle таких проблем не возникало.


Поэтому мы решили попробовать переписать всю логику определения зависимостей на Gradle. Для этого мы создали build.gradle скрипт, который содержал необходимые задачи по загрузке и определению зависимостей для компонентов.


Для вызова задач Gradle используется Gradle Tooling API.


Для определения пути к зависимостями через Gradle мы используем artifact resolution query API. Следующий код позволяет получить путь к исходникам библиотеки:


 def component = project.dependencies.createArtifactResolutionQuery()            .forComponents(artifact.id.componentIdentifier)            .withArtifacts(JvmLibrary, SourcesArtifact)            .execute()            .resolvedComponents[0] def sourceFile = component?.getArtifacts(SourcesArtifact)[0]?.file

Таким образом, мы получили пути ко всем файлам в локальном кэше Gradle и сохраняли их в хранилище SDK.


Для загрузки в локальный кэш и определения зависимостей для компонентов мы добавляем компоненты в конфигурацию и через lenientConfiguration получаем все зависимости.


project.ext.properties["toResolve"].tokenize(';').each {            dependencies.add 'extraLibs', it        }        def resolved = [:]        configurations.all.collect {            if (it.canBeResolved) {                it.resolvedConfiguration.lenientConfiguration.artifacts.each { art ->                    try {                        ...                    } catch (e) {                        logger.error("Error: " + e.getMessage(), e)                        logger.error("could not find pom for {}", art.file)                    }                }            }        }

Мы используем lenientConfiguration, чтобы Gradle скрипт пропускал не найденные зависимости и не падал в случае если компонент не найден в репозиториях.


Для загрузки артефактов в репозитории SDK использует PublishToMavenRepository задачу Gradle.


task publishArtifact(type: PublishToMavenRepository) {    doLast {        if (project.ext.hasProperty("toUpload")) {            def toUpload = new JsonSlurper().parseText(project.ext.properties["toUpload"])            def descriptors = new JsonSlurper().parseText(project.ext.properties["descriptors"])            artifactId toUpload.artifactId            groupId toUpload.groupId            version toUpload.version            descriptors.each { descriptor ->                artifact(descriptor.filePath) {                    classifier descriptor.classifier.type                    extension descriptor.classifier.extenstion                }            }        }    }}

В результате использования Gradle мы избавились от конфликтов при определении транзитивных зависимостей и заметно ускорили работу приложения.


Сборка проекта


Для сборки CUBA SDK мы использовали тот же подход, что и для CUBA CLI. Мы с помощью инструмента jlink собирали все необходимые модули в кастомную JRE, которая поставляется вместе с приложением. Это позволило сделать SDK независимым от установленной на компьютерах пользователей Java. Пример такой сборки можно посмотреть в CLI Core Sample проекте.


Поддержка сторонних плагинов


Так как CUBA SDK построен на основе библиотеки CLI Core, CUBA SDK поддерживает сторонние плагины. С помощью системы плагинов сейчас в SDK реализованы maven и gradle менеджеры зависимостей компонентов и провайдеры для CUBA компонентов.


Рассмотрим пример, как мы можем расширить функционал SDK с помощью плагина. В данном примере мы напишем провайдер для стартеров Spring Boot из хорошо известного Spring Initializr.


Для начала создадим новый проект, для примера возьмем плагин для CUBA CLI, как описано здесь, и добавим зависимости:


implementation "com.haulmont.cli.core:cli-core:1.0.0"implementation "com.haulmont.cli.sdk:cuba-sdk:1.0.1"

Создадим новый провайдер для стартеров spring boot SpringBootProvider, который наследуем от BintraySearchComponentProvider. BintraySearchComponentProvider позволяет автоматически находить доступные версии компонентов, используя Bintray API.


class SpringBootProvider : BintraySearchComponentProvider() {   var springComponentsInfo: SpringComponentsInfo? = null   override fun getType() = "boot-starter"   override fun getName() = "Spring boot starter" ...   override fun load() {       springComponentsInfo = Gson().fromJson(readSpringFile(), SpringComponentsInfo::class.java)   }   private fun readSpringFile(): String {       return SpringComponentsPlugin::class.java.getResourceAsStream("spring-components.json")           .bufferedReader()           .use { it.readText() }   }

Этот провайдер будет искать доступные компоненты из файла spring-components.json, который является json версией yml файла приложения Spring Initializr.


Для маппинга из json в объекты создадим простые data классы:


data class SpringComponent(   val name: String,   val id: String,   val groupId: String?,   val artifactId: String?,   val description: String?,   val starter: Boolean? = true)data class SpringComponentCategory(   val name: String,   val content: List<SpringComponent>)data class SpringInitializr(   val dependencies: List<SpringComponentCategory>)data class SpringComponentsInfo(   val initializr: SpringInitializr)

Для того, чтобы добавить наш провайдер к остальным провайдерам SDK, нужно в init событии плагина зарегистрировать провайдер:


class SpringBootComponentsPlugin : CliPlugin {   private val componentRegistry: ComponentRegistry by sdkKodein.instance<ComponentRegistry>()   @Subscribe   fun onInit(event: InitPluginEvent) {       val bootProvider = SpringBootProvider()       componentRegistry.addProviders(bootProvider)       bootProvider.load()   }}

Все готово. Теперь, чтобы установить наш плагин в терминале или через IDE, нужно выполнить команду gradle installPlugin.


Запустим SDK
image


Видим, что наш плагин успешно загрузился. Теперь проверим, что вся наша логика работает с помощью команды resolve boot-starter:


image


image


Как видим, подсказки для компонентов и их версий успешно заработали.


Если в вашем хранилище артефактов артефакты и зависимости хранятся отлично от maven репозиториев, то через плагины можно использовать свою реализацию интерфейса ArtifactManager, в которой будет логика работы с вашим хранилищем.


Исходный код тестового плагина можно найти на странице GitHub.


Заключение


В первую очередь, CUBA SDK будет полезен организациям, у которых по соображениям безопасности есть ограничения на доступ к внешней сети.


Если вы самоизолировались в глухой деревне или летите 8 часов в самолете и не готовы платить по 300 евро за 10 мегабайт трафика, то CUBA SDK отличное решение, которое позволит собрать актуальный стек используемых библиотек и фреймворков локально у вас на компьютере.

Подробнее..

Перевод Создаем Gatling скрипты с помощью VS Code

06.07.2020 20:09:26 | Автор: admin

Перевод статьи подготовлен в преддверии старта курса Нагрузочное тестирование.





Предисловие


Недавно, благодаря комментарию одного из студентов, изучающих мой курс Gatling Fundamentals, я узнал о том, что вы можете создавать Gatling скрипты с помощью Visual Studio Code. Я, честно говоря, понятия не имел, что это возможно, но был приятно удивлен, обнаружив, насколько хорошо это работает!


В этом посте мы рассмотрим, как настроить вашу среду разработки Gatling скриптов в VS Code. Мы рассмотрим инструменты сборки Maven и SBT.


Установка Metals


Первое, что нужно сделать, планируете ли вы работать с Maven или SBT, это установить плагин Scala Metals внутри VS Code. Этот плагин позволит языковому серверу Scala работать в VS Code и предоставит типичные функции, которые вы ожидаете от современного IDE.



Установите плагин из VS Code самым стандартным способом, перейдя на вкладку Extensions и выполнив поиск Scala (Metals):



Имея установленный Metals, давайте сначала посмотрим, как запустить Gatling в VS Code с помощью Maven.


Gatling VScode с Maven


Во-первых, вам нужно установить Maven вы можете скачать Maven с официального сайта и следовать инструкциям по установке на его странице.


Затем установите плагин Maven for Java внутри VS Code:



По-прежнему внутри VS Code, откройте Command Pallette (View > Command Pallette) и выберите Maven: Update Maven Archetype Catalog:



Как и следовало ожидать, это обновит каталог доступных архетипов Maven.


Теперь мы хотим создать новый проект Gatling из архетипа Gatling Maven. Для этого сначала откройте Command Pallette и выберите Maven: Create Maven Project. При выборе архетипа, нажмите more. Введите Gatling, после чего должен появиться архетип Gatling. Дальше смотрите видео ниже:



Сохраните проект в подходящем месте на вашем компьютере. Затем откройте проект как обычно в VS Code. Возможно, на этом этапе вам придется импортировать сборку. Для этого перейдите на вкладку Metals в VS Code и нажмите Import Build:



Это заставит Maven собрать ваш проект.


Теперь самым ординарным способом добавьте свой код Gatling симуляции. Если вы просто следуете моему примеру и вам нужен пример Gatling скрипта, вы можете использовать этот базовый скрипт ниже:


package computerdatabaseimport io.gatling.core.Predef._import io.gatling.http.Predef._import scala.concurrent.duration._class BasicSimulation extends Simulation {  val httpProtocol = http    .baseUrl("http://computer-database.gatling.io") // Здесь находится корень для всех относительных URL    .acceptHeader(      "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"// Вот общие заголовки    .acceptEncodingHeader("gzip, deflate")    .acceptLanguageHeader("en-US,en;q=0.5")    .userAgentHeader(      "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0"    )  val scn =    scenario("Scenario Name") // A scenario is a chain of requests and pauses      .exec(        http("request_1")          .get("/")      )      .pause(7) // Note that Gatling has recorder real time pauses  setUp(scn.inject(atOnceUsers(1)).protocols(httpProtocol))}

Чтобы запустить скрипт, откройте терминал в VS Code, и введите mvn gatling:test. Если вы хотите запустить определенный тестовый сценарий, вы можете вместо этого выполнить


mvn gatling:test -Dgatling.simulationClass=computerdatabase.BasicSimulation

Советую вам узнать больше о плагине Gatling Maven.


Gatling VScode с SBT


Если вы предпочитаете запускать и создавать свои Gatling проекты с помощью Scala Build Tool (SBT), я считаю, что проще всего сначала клонировать проект Gatling SBT Plugin Demo.


Как только вы клонировали проект, откройте его как обычно в VS Code. Перейдите на вкладку Metals в VS Code и нажмите Import Build:



VS Code теперь должен собрать ваш проект Gatling с помощью SBT.


Чтобы запустить все тесты в вашем проекте, откройте терминал и введите sbt gatling:test. Или же чтобы запустить конкретный тестовый скрипт, вы можете выполнить команду sbt gatling:testOnly computerdatabase.BasicSimulation.


Вы можете узнать больше о плагине Gatling SBT в его документации.


Резюме


В этой статье мы узнали, как использовать Visual Studio Code для создания наших Gatling скриптов. Мы рассмотрели, как создать и запустить Gatling проект с инструментами сборки Maven и SBT.


Несмотря на то, что IntelliJ IDEA остается моей предпочтительной средой разработки для разработки кода Scala и Gatling, здорово иметь возможность использовать и более популярный VS Code!




Узнать подробнее о курсе Нагрузочное тестирование



Подробнее..

Публикуем либку в maven central

11.01.2021 00:10:33 | Автор: admin

Предисловие


Решил я что то сделать полезное. Был проект в котором мы должны были вместо старого сервиса сделать новый, но лучше. Сервис большой получился, 10к строк бизнесс-правил+код на джаве 15к строк. Сейчас уже раза в два больше. Но так как логики много и никто толком уже многого не помнит (за тем нас и призвали, что бы были тесты, доки и минимум багов) надо было сравнить старый сервис и новый. Что бы наши тестеры не умерли от старости, пытаясь все проверить я подумал и написал сервис, который дергал апи старого и нового, сравнивал и сливал разницу ответов в базу, что бы тестеры проверили и мы пофиксили разницу. Но вот проблема, на входе у нас много параметров и возможных их значений, это же миллиарды комбинаций будут, проверять несколько месяцев с полной нагрузкой, да и похожих много.
В итоге сначала была рождена идея взять и сократить возможные изменения параметров и вообще выкинуть некоторые из перебора, а потом была взята на вооружение Pairwise testing теория и она позволила закинуть все параметры и все значенеия и все проверить целиком.


Эта нехитрая теория утверждает, что если мы создадим тесткейсы где каждая пара параметров встречается хотя бы однажды, то так покроем чуть ли не 90% багов. При этом можно все так подобрать, что тест кейсов будут не миллиарды, а может несколько тысяч. Отличный результат! В итоге мы нашли сотни расхождений и даже кучу багов в легаси сервисе, а так же надоумлили сочинителей бизнесс-логики подумать о том, о чем они ранее вообще не задумывались, так как мы им просто подкинули новые интересные тест кейсы в которых было непонятно "как правильно".
Код написан, все работает, все счастливы, но выкидывать генератор тест кейсов как то не хотелось, потому я сел на выходных и написал либку, которая использовала тот же алгоритм, что и в рабочем сервисе, но была полностью оторвана от всего кроме lombok и написана в нерабочее время :) Потому, захотелось ее опубликовать под Apache 2.0 лицензией в мавен централе. Но это было настолько муторно и плохо описано, что я решил оставить себе памятку на хабре, что бы потом если вдруг, не мучиться второй раз.
Если вдруг надо, то либка опубликована тут: https://github.com/3DRaven/pairwiser
Итак, поехали.


Регистрируемся на sonatype.org


Напрямую в Maven Central опубликовать ничего не выйдет просто так, потому я воспользовался промежуточным сервисом sonatype.org Сначала нужно там зарегистрироваться.
У них есть документация как все зарелизить


https://central.sonatype.org/pages/releasing-the-deployment.html


Есть дока, которую надо обязательно посмотреть с требованиями к проекту и особенно к pom.xml


https://central.sonatype.org/pages/requirements.html


Готовим проект


Для этого нам понадобится: gnupg, maven release plugin да собственно и все. Причем в принципе без второго обойтись можно, да еще и релизы он делает в той же ветке git репы, в которой версию прибавил. Во всяком случае без возни. Так что второе по желанию. Возиться я не стал и его все же исползовал.


GNUpg


Это много кому известная штука для генерации ключей и много чего еще. И главная засада была в том, что ключи, которые по инструкции сделать можно из командной строки у меня потом не принял ни один сервер ключей публичных. Принимают и либо ошибку выдают, либо ключей там нет в итоге. Потому, вместо того что бы мучится с этим я рекомендую просто использовать GUI утилитку, хотя сначала я все делал с консоли и по докам. В итоге взял gpa и в два клика сделал себе ключи, которые сервера прожевали.
Там все просто:


  1. Открыть диспетчер ключей.
    Imgur
  2. Создание нового ключа Ctrl+N.
  3. Далее заполняем свое имя и это все.
  4. Это важный этап, что бы sonatype.org мог проверить после публикации подпись вашей либки, надо что бы ваш публичный ключ был на сервере публичных ключей. Для этого в меню "Сервер" выбираем "Отправить ключи".
    Imgur
    Ваш публичный ключ будет опубликован и его можно любой желающий взять что бы проверить подпись.

Готовим settings.xml


В этом файле хранятся глобальные настройки Maven. В него нам надо добавить пароль к нашей связке ключей и репозиторию sonatype.org (вашему аккаунту там), которую мы сделали на прошлом шаге.


<settings xmlns="http://personeltest.ru/away/maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://personeltest.ru/away/www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://personeltest.ru/away/maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">  <localRepository/>  <interactiveMode/>  <usePluginRegistry/>  <offline/>  <servers>    <server>      <id>ossrh</id>      <username>3DRaven</username>      <password>passwordForYourAccountInSonata</password>    </server>  </servers>  <mirrors/>  <proxies/>  <profiles>    <profile>      <id>ossrh</id>      <activation>        <activeByDefault>true</activeByDefault>      </activation>      <properties>        <gpg.executable>gpg2</gpg.executable>        <gpg.passphrase>passwordForGPGKey</gpg.passphrase>      </properties>    </profile>  </profiles>  <activeProfiles/></settings>

Обратите внимние на ossrh этот айдишник мы встретим потом в помнике.


Готовим pom.xml


Далее нужно настроить помник проекта так что бы он подписал проект, сделал javadoc, собрал source. Это обязательные требования к публикации. Пример будет кусками, в помнике либки можете полный глянуть


Обязательные элементы


<description>Generate array of minimal size of pairwise tests cover all pairs of changing params</description>    <url>https://github.com/3DRaven/pairwiser</url>    <inceptionYear>2020</inceptionYear>    <licenses>        <license>            <name>Apache License, Version 2.0</name>            <url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>            <distribution>repo</distribution>            <comments>A business-friendly OSS license</comments>        </license>    </licenses>    <developers>        <developer>            <id>3DRaven</id>            <name>Renat Eskenin</name>            <email>email</email>        </developer>    </developers>    <scm>        <connection>scm:git:ssh://git@github.com:3DRaven/pairwiser.git</connection>        <developerConnection>scm:git:ssh://git@github.com:3DRaven/pairwiser.git</developerConnection>        <tag>HEAD</tag>        <url>https://github.com/3DRaven/pairwiser</url>    </scm>    <issueManagement>        <system>GitHub</system>        <url>https://github.com/3DRaven/pairwiser/issues</url>    </issueManagement>    <distributionManagement>        <snapshotRepository>            <id>ossrh</id>            <url>https://oss.sonatype.org/content/repositories/snapshots</url>        </snapshotRepository>        <repository>            <id>ossrh</id>            <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>        </repository>    </distributionManagement>

Самое важное тут это distributionManagement. В нем указан как сервер, на который будет опубликован итоговый артефакт, так и id ossrh по которому будет найден в settings.xml логин и пароль на сервере. Далее нам нужно что бы наш артефакт был подписан, собран javadoc и source. Если три эти плагина не поместить именно в profiles раздел, то релиз плагин их запускать не будет. Так что они вписаны отдельно.


<profiles>        <!-- GPG Signature on release -->        <profile>            <id>release-sign-artifacts</id>            <activation>                <property>                    <name>performRelease</name>                    <value>true</value>                </property>            </activation>            <build>                <plugins>                    <plugin>                        <groupId>org.apache.maven.plugins</groupId>                        <artifactId>maven-gpg-plugin</artifactId>                        <version>${maven.gpg.plugin.version}</version>                        <executions>                            <execution>                                <id>sign-artifacts</id>                                <phase>verify</phase>                                <goals>                                    <goal>sign</goal>                                </goals>                            </execution>                        </executions>                    </plugin>                    <plugin>                        <groupId>org.apache.maven.plugins</groupId>                        <artifactId>maven-source-plugin</artifactId>                        <version>${maven.source.plugin.version}</version>                        <executions>                            <execution>                                <id>attach-sources</id>                                <goals>                                    <goal>jar-no-fork</goal>                                </goals>                            </execution>                        </executions>                    </plugin>                    <plugin>                        <groupId>org.apache.maven.plugins</groupId>                        <artifactId>maven-javadoc-plugin</artifactId>                        <version>${maven.javadoc.plugin.version}</version>                        <executions>                            <execution>                                <id>attach-javadocs</id>                                <goals>                                    <goal>jar</goal>                                </goals>                            </execution>                        </executions>                    </plugin>                </plugins>            </build>        </profile>    </profiles>

Так же использован плагин от sonatype.org, который публикует staging репозитрий на их сайте. Этот репозиторий видите только вы. После его публикации он будет в статусе Open и на нем запустятся автопроверки. Если они все пройдут, то можно будет его "закрыть", это такой статус репозитория у них. Среди проверок будет и проверка подписи и проверки требований sonatype.org к проектам.


<plugin>    <groupId>org.sonatype.plugins</groupId>    <artifactId>nexus-staging-maven-plugin</artifactId>    <version>${nexus.staging.maven.plugin.version}</version>    <extensions>true</extensions>    <configuration>        <serverId>ossrh</serverId>        <nexusUrl>https://oss.sonatype.org/</nexusUrl>        <autoReleaseAfterClose>true</autoReleaseAfterClose>    </configuration></plugin>

Просим опубликовать наш проект


На этом этапе нужно в sonatype.org сделать задачку, о том, что вы хотите свой проект опубликовать. Jira задачку типа такой https://issues.sonatype.org/browse/OSSRH-57701


Публикуем staging репозиторий


Эти команды включат релиз плагин, он подпишет ваш проект, поднимет версию, соберет исходники и доки, а так же запустит отправку проекта на sonatype.org


mvn release:clean release:prepare mvn release:perform

Если все хорошо, после того как ваш репозиторий создан тут https://oss.sonatype.org/#stagingRepositories все проверки прошли, вы можете его выбрать и нажать "Close".


Просим зарелизить наш проект


На этом этапе нужно в задачке, которую мы ранее сделали https://issues.sonatype.org/browse/OSSRH-57701 написать, что хотите релизиться. Дело в том, что синк релиз репозитория с Maven Central они включают по требованию. Так как у вас уже есть свой стеджинг (спрятанный и видимый только вами) репозиторий вы можете идти далее. Нужно его "Закрыть", нажав Close. После закрытия, можно нажать Release и Drop. С этого момента ваш стейджинг репозиторий попадет в релизный репозиторий и будет удален из стейджинга. Через десять минут после этого, если синк с централом для вашей репы включен, он будет доступен в Maven Central


Ура! :)


Краткий итог


  1. Регаемся на sonatype.org
  2. Просим их ваш проект опубликовать
  3. Делаем ключи для подписи и публикуем их
  4. Закидываем логопасы в settings.xml
  5. Настраиваем pom.xml
  6. Запускаем публикацию
  7. Заходим в sonatype.org и закрываем, релизим и дропаем свой репозиторий
  8. PROFIT!!!
Подробнее..
Категории: Java , Maven , Sonatype nexus , Pairwise

Кастомная (де) сериализация даты и времени в Spring

24.02.2021 18:11:35 | Автор: admin

Представьте довольно распространённую ситуацию: ваше приложение взаимодействует с клиентами, которые находятся в разных часовых поясах. Вам часто приходится работать с датами, а для корректной работы системы, они передаются с указанием часового пояса отправителя. При этом, вам нужно:

  1. При получении запроса привести дату к серверному времени и работать с ней, а также сохранять в базу данных в таком виде

  2. В ответ возвращать дату и время с указанием серверного часового пояса

Для решения этой задачи Spring предоставляет удобный механизм для написания кастомной сериализации и десериализации. Главным его преимуществом является возможность вынести преобразования дат (и других типов данных) в отдельный конфигурационный класс и не вызывать методы преобразования каждый раз в исходном коде.

Десериализация

Для того, чтобы Spring понимал, что именно наш класс нужно использовать для (де)сериализации, его необходимо пометить аннотацией @JsonComponent

Ну а чтобы код был максимально кратким, я буду использовать внутренний статический класс, который должен быть унаследован от JsonDeserializerи параметризован нужным нам типом данных. Поскольку JsonDeserializerэто абстрактный класс, нам необходимо переопределить его абстрактный метод deserialize()

@JsonComponentpublic class CustomDateSerializer {        public static class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {            @Override        public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) {            return null;        }    }}

Из параметров метода получаем переданную клиентом строку, проверяем её на null и получаем из неё объект класса ZonedDateTime

public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) {    String date = jsonParser.getText();    if (date.isEmpty() || isNull(date) {        return null;    }    ZonedDateTime userDateTime = ZonedDateTime.parse(date);}

Для получения разницы во времени, у переменной userDateTime нужно вызвать метод withZoneSameInstant() и передать в него текущую серверную таймзону. Нам остаётся лишь преобразовать полученную дату кLocalDateTime

Для надежности, предположим, что нам может прийти дата без таймзоны, либо вообще некорректные данные. Для этого случая, добавим обработку исключений. Полностью десериализатор будет выглядеть так

public static class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {    @Override    public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {        String date = jsonParser.getText();        if (date.isEmpty()) {            return null;        }        try {            ZonedDateTime userDateTime = ZonedDateTime.parse(date);            ZonedDateTime serverTime = userDateTime.withZoneSameInstant(ZoneId.systemDefault());            return serverTime.toLocalDateTime();        } catch (DateTimeParseException e) {            try {                return LocalDateTime.parse(date);            } catch (DateTimeParseException ex) {                throw new IllegalArgumentException("Error while parsing date", ex);            }        }    }}

Предположим, что серверное времяUTC+03. Таким образом, когда клиент передаёт дату 2021-01-21T22:00:00+07:00, в нашем контроллере мы уже можем работать с серверным временем

public class Subscription {    private LocalDateTime startDate;      // standart getters and setters}
@RestController public class TestController {    @PostMapping  public void process(@RequestBody Subscription subscription) {    // к этому моменту поле startDate объекта subscription будет равно 2021-01-21T18:00  }}

Сериализация

С сериализацией алгоритм действий похожий. Нам нужно унаследовать класс от JsonSerializer, параметризовать его и переопределить абстрактный метод serialize()

Внутри метода мы проверим нашу дату на null, добавим к ней серверную таймзону и получим из неё строку. Остаётся лишь отдать её клиенту

public static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {    @Override    public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {        if (isNull(localDateTime)) {            return;        }        OffsetDateTime timeUtc = localDateTime.atOffset(ZoneOffset.systemDefault().getRules().getOffset(LocalDateTime.now()));        jsonGenerator.writeString(timeUtc.toString());    }}

Круто? Можно в прод? Не совсем. В целом, этот код будет работать, но могут начаться проблемы, если серверная таймзона будет равна UTC+00. Дело в том, что конкретно для этого часового пояса id таймзоны отличается от стандартного формата. Посмотрим в документацию класса ZoneOffset

Таким образом, имея серверную таймзону UTC+03, на выходе мы получим строку следующего вида: 2021-02-21T18:00+03:00.Но если же оно UTC+00, то получим 2021-02-21T18:00Z

Поскольку мы работаем со строкой, нам не составит труда немного изменить код, дабы на выходе мы всегда получали дату в одном формате. Объявим две константы одна из них будет равна дефолтному id UTC+00, а вторая которую мы хотим отдавать клиенту, и добавим проверку - если серверное время находится в нулевой таймзоне, то заменим Z на +00:00. В итоге наш сериализотор будет выглядеть следующим образом

public static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {    private static final String UTC_0_OFFSET_ID = "Z";    private static final String UTC_0_TIMEZONE = "+00:00";    @Override    public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {        if (!isNull(localDateTime)) {            String date;            OffsetDateTime timeUtc = localDateTime.atOffset(ZoneOffset.systemDefault().getRules().getOffset(LocalDateTime.now()));            if (UTC_0_OFFSET_ID.equals(timeUtc.getOffset().getId())) {                date = timeUtc.toString().replace(UTC_0_OFFSET_ID, UTC_0_TIMEZONE);            } else {                date = timeUtc.toString();            }            jsonGenerator.writeString(date);        }    }}

Итого

Благодаря встроенным механизмам спринга мы получили возможность автоматически конвертировать дату и время в требуемом формате, без каких-либо явных вызовов методов в коде

Полностью исходный код можно посмотреть здесь

Подробнее..
Категории: Java , Spring , Json , Maven , Spring-boot , Serialization , Date , Deserialization

Перевод Как найти все битые ссылки на странице с помощью Selenium

02.06.2021 14:09:29 | Автор: admin

Когда вам нужно проверить все ссылки в вашем проекте, вы можете сделать это с помощью Postman или любого другого инструмента тестирования API, но есть более простой способ. Когда вы используете инструменты тестирования API, вам нужно написать все соединения ссылок один за другим, а когда ссылки меняются, вам нужно снова редактировать все тесты один за другим.

Теперь с помощью этого java-кода вы можете проверить все ссылки. Эти ссылки могут быть ссылками pdf, изображения, видео или фотографии.

Шаг 1: В HTML мы связываем ссылки с помощью этого кода: <a href="Adress"></a> это означает, что мы должны собрать все ссылки на веб-странице на основе <a>. Для этого мы используем этот код:

List<WebElement> allLinks = driver.findElements(By.tagName(LINKS_TAG));

LINKS_TAG - это "a". В конце страницы я добавлю весь код.

Шаг 2: Определение и проверка URL-адреса

String urlLink = link.getAttribute(LINKS_ATTRIBUTE);

LINKS_ATTRIBUTE - это "href"

Шаг 3: Отправка HTTP-запроса и считывание кодов HTTP-ответов

Мы создаем HttpConnection с параметром URL. Я добавил также Connection Timeout.

URL url = new URL(urlLink);HttpURLConnection httpURLConnect=(HttpURLConnection)url.openConnection();httpURLConnect.setConnectTimeout(5000);httpURLConnect.connect();
  • Информационные коды ответов: 100-199

  • Коды успешного ответа: 200-299

  • Редирект коды: 300-399

  • Коды ошибок клиента: 400-499

  • Коды ошибок сервера: 500-599

В принципе, мы можем сказать, что если код ответа больше или равен 400, то в этом случае соединение прервано.

import org.openqa.selenium.By;import org.openqa.selenium.WebDriver;import org.openqa.selenium.WebElement;import org.openqa.selenium.chrome.ChromeDriver;import org.openqa.selenium.chrome.ChromeOptions;import org.testng.annotations.AfterClass;import org.testng.annotations.BeforeTest;import org.testng.annotations.Test;import java.net.HttpURLConnection;import java.net.URL;import java.util.List;public class FindAllBrokenLinks {    public final String DRIVER_PATH = "Drivers/chromedriver";    public final String DRIVER_TYPE = "webdriver.chrome.driver";    public WebDriver driver;    public final String BASE_URL = "https://www.bbc.com/";    public final String LINKS_ATTRIBUTE = "href";    public final String LINKS_TAG = "a";    @BeforeTest    public void beforeTest(){        ChromeOptions options = new ChromeOptions();        options.addArguments("--disable-notifications","--ignore-certificate-errors","--disable-extensions");        System.setProperty(DRIVER_TYPE,DRIVER_PATH);        driver = new ChromeDriver(options);        driver.manage().window().maximize();        driver.get(BASE_URL);    }    @Test    public void FindAllBrokenLinks() throws Exception{        List<WebElement> allLinks = driver.findElements(By.tagName(LINKS_TAG));        for(WebElement link:allLinks){            try {                String urlLink = link.getAttribute(LINKS_ATTRIBUTE);                URL url = new URL(urlLink);                HttpURLConnection httpURLConnect=(HttpURLConnection)url.openConnection();                httpURLConnect.setConnectTimeout(5000);                httpURLConnect.connect();                if(httpURLConnect.getResponseCode()>=400)                {                    System.out.println(urlLink+" - "+httpURLConnect.getResponseMessage()+"is a broken link");                }                else{                    System.out.println(urlLink+" - "+httpURLConnect.getResponseMessage());                }            }catch (Exception e) {            }        }    }    @AfterClass    public void CloseDriver(){        driver.close();    }}

Я использовал URL веб-страницы BBC в качестве базового URL, но запуск этого кода занял 1 минуту и 49 секунд. :) Возможно, вам стоит выбрать другой сайт.

Вот некоторые результаты тестов:

https://www.bbc.com/sport OK

https://www.bbc.com/reel OK

https://www.bbc.com/worklife OK

https://www.bbc.com/travel Временно приостановил работу

https://www.bbc.com/future OK

https://www.bbc.com/culture OK

https://www.bbc.com/culture/music OK

http://www.bbc.co.uk/worldserviceradio/ Не доступен

http://www.bbc.co.uk/programmes/p00wf2qw Не доступен

https://www.bbc.com/news/world-europe-57039362 OK


Перевод подготовлен в рамках набора учащихся на курс "Java QA Automation Engineer". Если вам интересно узнать о курсе подробнее, а также познакомиться с преподавателем, приглашаем на день открытых дверей онлайн.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru