Как получить доступ во время выполнения к номеру версии запущенного приложения Clojure? - программирование
Подтвердить что ты не робот

Как получить доступ во время выполнения к номеру версии запущенного приложения Clojure?

У меня есть веб-сервис, написанный в Clojure, который непрерывно доставляется. Чтобы наши инструменты автоматического развертывания знали, какая версия базы данных была развернута, веб-служба должна предоставить способ запроса, какая версия. Версия объявлена ​​как часть настройки проекта в инструменте Leiningen, например:

(defproject my-web-service "1.2-SNAPSHOT"
  ; ... rest of project.clj
  )

Кодовая база упакована как файл JAR.

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

Для любого конкретного JAR, который был построен и развернут, я хочу как-то разрешить запрос номера версии (например, с HTTP-запросом, но это деталь реализации). Ответ должен содержать строку 1.2.42.

Как мне сделать этот номер версии доступным для запущенного приложения?

(Возможный дубликат, хотя он не включает аспект Дженкинса: Вставить строку версии из проекта leiningen в приложение)

4b9b3361

Ответ 1

Один из способов доступа к этому номеру версии - через файл MANIFEST.MF, который хранится в файле JAR. Это позволит получить доступ во время выполнения через класс Java java.lang.Package. Для этого требуются следующие три шага:

  • Передача номера сборки Jenkins в Leiningen для включения в объявление project.clj defproject.
  • Поручить Leiningen построить MANIFEST.MF со значением для Implementation-Version.
  • Вызов Package#getImplementationVersion(), чтобы получить доступ к String, содержащему номер версии.

1 - Получение номера сборки Jenkins

Для доступа к номеру сборки (красиво названный BUILD_NUMBER) можно использовать переменные среды Jenkins . Это доступно в процессе JVM, используя System.getenv("BUILD_NUMBER"). В этом случае процесс JVM может быть leiningen project.clj script, который является Clojure кодом, который может вызывать (System/getenv "BUILD_NUMBER"). Следуя приведенному выше примеру, возвращаемая строка будет "42".

2 - Установка версии в MANIFEST.MF

При создании JAR Leiningen будет включать файл MANIFEST.MF по умолчанию. Он также имеет опцию конфигурации, которая позволяет устанавливать произвольные пары ключ-значение в этом файле. Поэтому, когда мы можем получить доступ к номеру сборки Jenkins в Clojure, мы можем объединить это с объявлением статической версии, чтобы установить Implementation-Version в манифесте. Соответствующие части project.clj выглядят следующим образом:

(def feature-version "1.2")
(def build-version (or (System/getenv "BUILD_NUMBER") "HANDBUILT"))
(def release-version (str feature-version "." build-version))
(def project-name "my-web-service")

(defproject project-name feature-version
  :uberjar-name ~(str project-name "-" release-version ".jar")
  :manifest {"Implementation-Version" ~release-version}

  ... )

В этом примере стоит отметить пару деталей. (if-let ...) при определении build-version заключается в том, чтобы позволить разработчикам локально создавать JAR, не требуя эмуляции переменных среды Jenkins. Конфигурация :uberjar-name позволяет создать JAR файл, который называется с использованием соглашений Maven/Ivy. Итоговый файл в этом примере будет my-web-service-1.2.42.jar.

С этой конфигурацией, когда Leiningen вызывается Jenkins при построении числа 42, манифест в полученном JAR будет содержать строку "Version-Version: 1.2.42".

3 - Доступ к версии во время выполнения

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

(ns version-namespace
  (:gen-class))

(defn implementation-version []
  (-> (eval 'version-namespace) .getPackage .getImplementationVersion))

Обратите внимание, что для вызова getImplementationVersion() нам нужен экземпляр Package, и для этого нам понадобится экземпляр java.lang.Class. Следовательно, мы гарантируем, что класс Java генерируется из этого пространства имен (вызов (:gen-class)) (мы можем затем получить доступ к методу getPackage из этого класса.

Результатом этой функции является строка, например. "1.2.42".

Предостережения

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

  • динамическая установка строки версии, определенной в вызове project.clj (defproject ...), может привести к тому, что некоторые другие инструменты не будут работать, если они полагаются на версию, жестко обозначенную
  • семантика getImplementationVersion слегка злоупотреблялась. На самом деле версия должна быть: pkg.getSpecificationVersion() + "." + pkg.getImplementationVersion(), но поскольку ничто иное не читает ни одно из этих значений, мы можем уйти, просто установив версию реализации. Обратите внимание, что для правильного выполнения этого требования требуется также добавить манифеста "Спецификация-версия".

С помощью приведенных выше шагов мое текущее приложение Clojure может получить доступ к номеру версии, который соответствует сборке Jenkins, которая упаковала код.