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

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

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

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

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

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

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

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

1 ответ

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

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

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

Для доступа к номеру сборки можно использовать переменные среды Дженкинса BUILD_NUMBER). Это доступно в процессе JVM, используя System.getenv("BUILD_NUMBER"), В этом случае процесс JVM может быть project.clj скрипт, который является кодом 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 при сборке № 42, манифест в результирующем JAR-файле будет содержать строку "Реализация-версия: 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 "s (defproject ...) вызов может привести к тому, что некоторые другие инструменты не будут работать, если они полагаются на жестко закодированную версию
  • семантика getImplementationVersion были немного оскорблены Действительно версия должна быть: pkg.getSpecificationVersion() + "." + pkg.getImplementationVersion(), но поскольку ни одно из этих значений не читает ничего, мы можем просто установить версию реализации. Обратите внимание, что для правильной работы потребуется также добавить "Specification-Version" в манифест.

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

Другие вопросы по тегам