Использование Ansible для изменения pom.xml

Я хочу изменить файлы pom.xml на лету, чтобы Maven использовал локально установленные JAR-файлы для определенного groupId. Итак, мне нужно:

  1. Измените/установите область действия каждой соответствующей зависимости на system.
  2. Добавьте systemPath к каждой соответствующей зависимости, указав на файл, имя которого будет функцией artifactId.

Например,

                <dependency>
                       <groupId>myGroup</groupId>
                       <artifactId>myGroup-agent-api</artifactId>
                       <version>3.1.38.13</version>
                       <scope>provided</scope>
                </dependency>

необходимо стать:

                <dependency>
                       <groupId>myGroup</groupId>
                       <artifactId>myGroup-agent-api</artifactId>
                       <version>3.1.38.13</version>
                       <scope>system</scope>
                       <systemPath>${application_path}/jar/agent-api.jar</systemPath>
                </dependency>

С первой частью я разобрался. Эта задача Ansible с использованием модуля xml должна изменять/устанавливать область действия:

- name: Set scope to system for myGroup if {{ module.name }} uses any
  xml:
    path: '{{ app_path }}/{{ module.name }}/pom.xml'
    namespaces:
      x: http://maven.apache.org/POM/4.0.0
    xpath: '//x:dependency/x:groupId[text() = "myGroup"]/../x:scope'
    value: system
  register: xmlfound
  when: java_files.matched
  failed_when:
    - xmlfound is failed
    - >-
      'in order to spawn nodes' not in xmlfound.msg

(Возиться с failed_when необходимо, потому что lxml бросает вызов xpath, если не может найти соответствующие зависимости.)

Но как мне достичь второй части — создания systemPath на основе artifactId каждой соответствующей зависимости?

Обновление: отвечая на вопросы в комментариях:

  1. Да, я совершенно уверен, что хочу использовать Ansible — изменение «механическое», добавление нового профиля не имеет особого смысла, так как это просто увеличит нагрузку на обслуживание разработчиков, которые создают свои собственные рабочие столы — а не сервер - и вам нужны файлы JAR, загруженные из репозитория. Под «на лету» я подразумеваю, что Ansible проверяет исходные коды из git, массирует pom.xml и вызывает Maven — локально, на каждом сервере, предназначенном для запуска этих программ.
  2. Да, я бы поставил add_children, но какой будет аргумент? systemPath каждой зависимости различен и является производным от artifactId.
  3. Спасибо за xpath. К сожалению, он по-прежнему вызывает ошибки для POM без единой соответствующей зависимости, поэтому мне все еще нужен хакерский подход failed_when...

Вы уверены, что хотите сделать это с помощью Ansible, а не профилей Maven, предназначенных для таких задач? И что вы подразумеваете под «на лету»?

Alexander Pletnev 10.04.2024 07:19

Используйте тот же модуль. См. add_children. Но прежде чем продолжить, обязательно прочитайте комментарий Александра выше.

Zeitounator 10.04.2024 08:31

Небольшой совет по поводу xpath: поместите предикат в x:dependent, чтобы вам не приходилось снова перемещаться вверх и вниз, вот так: //x:dependency[x:groupId[text() = "myGroup"]]/x:scope

Siebe Jongebloed 10.04.2024 11:12

> «Да, я совершенно уверен, что хочу использовать Ansible — изменение «механическое», добавление нового профиля не имеет большого смысла, так как это просто увеличит нагрузку на разработчиков, которые создают свои собственные рабочие столы — а не сервер" - желаемое поведение буквально является целью профилей Maven. Будучи определены и/или включены в settings.xml, они предоставят это автоматически. Я не уверен, что внедрение другого инструмента в процесс сборки для изменения источников (с настраиваемой обработкой ошибок) проще в обслуживании, чем встроенное стандартное решение.

Alexander Pletnev 13.04.2024 11:05
pom.xml здесь находится в компетенции разработчиков. Я просто автоматизирую сборки...
Mikhail T. 13.04.2024 19:46
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
163
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я бы предпочел решение Maven Profiles, упомянутое Александр Плетнев в комментарии к вопросу , поскольку это делает ваши сборки более независимыми от внешних инструментов/конфигураций:

Прежде всего, я бы сохранил вашу зависимость myGroup-agent-api в вашем:

  • внутренний удаленный репозиторий Maven с развертыванием:deploy-file
  • или в локальном репозитории Maven (если у вас нет внутреннего удаленного репозитория) с помощью install:install-file

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

Во-вторых, в вашем POM:


  <properties>
    <myGroup-agent-api.version>3.1.38.13</myGroup-agent-api.version>
  <properties>
  ...
  <profiles>
    <profile>
      <id>agent-provided</id>
      <!-- See https://maven.apache.org/guides/introduction/introduction-to-profiles.html#details-on-profile-activation
          "All profiles that are active by default are automatically
          deactivated when a profile in the POM is activated on the
          command line or through its activation config."
      --> 
      <activation>
        <activeByDefault>true</activeByDefault>
      </activation>
      <dependencies>
        <dependency>
          <groupId>myGroup</groupId>
          <artifactId>myGroup-agent-api</artifactId>
          <version>${myGroup-agent-api.version}</version>
          <scope>provided</scope>
        </dependency>
      <dependencies>
    </profile>

    <profile>
      <id>agent-repo</id>
      <dependencies>
        <dependency>
          <groupId>myGroup</groupId>
          <artifactId>myGroup-agent-api</artifactId>
          <version>${myGroup-agent-api.version}</version>   
        </dependency>
      <dependencies>
    </profile>
  </profiles>
  ...

Запустите mvn <phase> для сборки с зависимостями <scope>provided.

Запустите mvn <phase> -P agent-repo для сборки с разрешенной зависимостью из вашего репозитория. При этом профиль agent-provided будет деактивирован; см. комментарий в декларации POM выше.

Спасибо. Я знаю, как пользоваться профилями — просто не хочу. Я подумал, что может быть плагин Maven, который может выполнять замену автоматически, но если его нет, я с радостью помассирую pom.xml. Рассматриваемые файлы JAR поступают от поставщика и используются самим приложением. Хранение копии каждого где-либо приводит к потере как памяти, так и оперативной памяти времени выполнения.

Mikhail T. 10.04.2024 19:49

@МихаилТ. Ну, вы знаете, но потенциальные будущие читатели/искатели здесь, возможно, этого не делают. ;) Касательно «Хранения... где-то»: это не где-то, а там, где должны находиться зависимости Maven: в репозитории Maven. Насколько велики эти зависимости и как при этом тратится дополнительная «оперативная память»?

Gerold Broser 10.04.2024 19:55

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

Mikhail T. 10.04.2024 21:05
Ответ принят как подходящий

Отказ от ответственности: это только ответ на исходный вопрос. Чтобы решить эту проблему, используйте вместо этого профили Maven.

Рассмотрим следующий pom.xml пример:

<project xmlns = "http://maven.apache.org/POM/4.0.0"
         xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>your-group</groupId>
    <artifactId>your-artifact</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>myGroup</groupId>
            <artifactId>myGroup-agent-api</artifactId>
            <version>3.1.38.13</version>
            <classifier>yaml</classifier>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>my-artifact</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>myGroup</groupId>
            <artifactId>myGroup-another-api</artifactId>
            <version>3.5.79.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

Но как мне достичь второй части — создания systemPath на основе artifactId каждой соответствующей зависимости?

К сожалению, этот вариант использования кажется слишком сложным для модуля xml. Сначала вам нужно получить список этих зависимостей, и вот в чем проблема: список совпадений плоский. Таким образом, вам придется правильно воссоздать структуру, что является непростой задачей, особенно если ваши узлы dependency имеют нечетное количество вложенных узлов. Поскольку я не использовал XPath в течение многих лет, возможно, я использую неправильный, но вот о чем я говорю (учитывая простейший pom.xml, где 2 из 3 зависимостей находятся на артефактах с «myGroup» groupId):

# playbook.yaml
---
- name: Modify the pom.xml
  hosts: localhost
  connection: local
  gather_facts: false
  tasks:
    - name: Find the dependencies on myGroup modules
      xml:
        path: 'pom.xml'
        namespaces:
          x: http://maven.apache.org/POM/4.0.0
        xpath: '//x:dependency/x:groupId[text() = "myGroup"]/..//*'
        content: text
      register: xmlfound

    - name: Show the results
      debug:
        var: xmlfound
TASK [debug] **************************************************************************
ok: [localhost] => 
  xmlfound:
    actions:
      namespaces:
        x: http://maven.apache.org/POM/4.0.0
      state: present
      xpath: //x:dependency/x:groupId[text() = "myGroup"]/..//*
    changed: false
    count: 8
    failed: false
    matches:
    - '{http://maven.apache.org/POM/4.0.0}groupId': myGroup
    - '{http://maven.apache.org/POM/4.0.0}artifactId': myGroup-agent-api
    - '{http://maven.apache.org/POM/4.0.0}version': 3.1.38.13
    - '{http://maven.apache.org/POM/4.0.0}scope': provided
    - '{http://maven.apache.org/POM/4.0.0}groupId': myGroup
    - '{http://maven.apache.org/POM/4.0.0}artifactId': myGroup-another-api
    - '{http://maven.apache.org/POM/4.0.0}version': 3.5.79.0
    - '{http://maven.apache.org/POM/4.0.0}scope': provided
    msg: 8

И я даже не говорю об переборе всех POM в проектах, вложенных циклах и о том, что вам нужно использовать модуль win_xml, если у вас Windows-машины.

Вместо этого вы можете использовать фильтр ansible.utils.from_xml для загрузки всего файла, а затем найти зависимости для обработки с помощью фильтра selectattr Jinja:

# playbook.yaml
- name: Modify the pom.xml
  hosts: localhost
  connection: local
  gather_facts: false
  vars:
    vendor_group_id: myGroup
  tasks:
    - name: Read the pom.xml
      set_fact:
        current_pom: "{{ lookup('file', 'pom.xml') | ansible.utils.from_xml }}"

    - name: Detect the dependencies to process
      set_fact:
        dependencies_to_process: >-
          {{
            current_pom.project.dependencies.dependency 
            | selectattr('groupId', 'equalto', vendor_group_id)
          }}

Следующий шаг — использовать add_elements. Сложные моменты здесь заключаются в следующем:

  • он добавляет элементы только к последнему совпадению;
  • добавленные элементы нарушают форматирование. pretty_print: true помогает, но меняет отступ с 4 пробелов (типично для POM) на 2 пробела;
  • он не идемпотентен, поэтому в следующий раз, когда вы запустите плейбук, он снова добавит вложенный узел systemPath.

Чтобы преодолеть это, вам необходимо:

  • искать узлы artifactId вместо узлов groupId, перебирая список, построенный в предыдущей задаче;
  • добавлять вложенный узел systemPath только в том случае, если элемент списка его еще не содержит;
  • в конце восстановите форматирование, используя xmllint. Конечно, это можно (и вообще нужно) сделать с помощью встроенных модулей Ansible, таких как replace, но в данном случае это была бы слишком сложная задача. Самым простым решением было бы использовать фильтр ansible.utils.to_xml, но при этом комментарии, если они были, будут потеряны.

Вот почти (см. примечания ниже) полный идемпотентный пример (обратите внимание, что я использовал lstrip(vendor_group_id + "-") для определения пути — в вашем случае он может отличаться):

- name: Modify the pom.xml
  hosts: localhost
  connection: local
  gather_facts: false
  vars:
    vendor_group_id: myGroup
  tasks:
    - name: Read the pom.xml
      set_fact:
        current_pom: "{{ lookup('file', 'pom.xml') | ansible.utils.from_xml }}"

    - name: Replace the scope
      set_fact:
        dependencies_to_process: >-
          {{
            current_pom.project.dependencies.dependency
            | selectattr('groupId', 'equalto', vendor_group_id)
          }}

    - name: Set scope to system for myGroup if uses any
      xml:
        path: 'pom.xml'
        namespaces:
          x: http://maven.apache.org/POM/4.0.0
        xpath: '//x:dependency[x:groupId[text() = "{{ vendor_group_id }}"]]/x:scope'
        value: system

    - name: Set systemPath for myGroup
      xml:
        path: 'pom.xml'
        namespaces:
          x: http://maven.apache.org/POM/4.0.0
        xpath: '//x:dependency[x:artifactId[text() = "{{ item.artifactId }}"]]'
        pretty_print: true
        add_children:
          - systemPath: >-
              ${application_path}/jar/{{ item.artifactId.lstrip(vendor_group_id + "-") }}.jar
      loop: '{{ dependencies_to_process }}'
      register: add_children_result
      when: item.systemPath is not defined

    - name: Restore the indentation
      environment:
        XMLLINT_INDENT: '    '
      command: 'xmllint pom.xml --output pom.xml --format'
      when: add_children_result is defined and add_children_result.changed

ПРИМЕЧАНИЯ:

Возиться с failed_when необходимо, потому что lxml вызывает раздражение у xpath, если он не может найти соответствующие зависимости.

Я бы не сказал «необходимо», поскольку вы можете обработать список соответствующих зависимостей, как я предложил. Более того, вы наверняка захотите добавить scope, если ни одного набора не было.

Важно: ради простоты я даже не учел dependencyManagement. Как вы можете видеть, это решение уже слишком сложно даже для простого случая.

Спасибо, это очень похоже на то, что я реализовал. Я говорю «необходимо», когда речь идет о failed_when гимнастике, потому что задачи не выполняются для тех POM-файлов, которые не имеют ни одной зависимости с использованием mygroup...

Mikhail T. 13.04.2024 19:45

Поскольку мы уже перебираем список обнаруженных зависимостей, чтобы добавить элемент systemPath, мы можем применить тот же подход для scope. Это приводит к тому, что мы можем переместить оба действия в block и запускать их только тогда, когда список не пуст.

Alexander Pletnev 13.04.2024 20:17

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

Mikhail T. 13.04.2024 20:56

Это то, что я делаю с переменной dependencies_to_process — беру все зависимости и выбираю только те, которые имеют «myGroup» в качестве groupId. Я не стал добавлять проверку длины списка, чтобы упростить код, но ее можно легко добавить.

Alexander Pletnev 13.04.2024 20:59

Понятно... Да, это способ сделать это. Я до сих пор не понимаю, почему xml-модуль не может этого сделать. Меня также беспокоит пространство имен: что, если в следующем году Maven изменится на .../POM/4.0.1?

Mikhail T. 13.04.2024 21:01

> «Что, если в следующем году Maven перейдет на .../POM/4.0.1?» - Это еще одна веская причина придерживаться стандартного решения. Кстати, есть вероятность, что у вас даже не будет доступа к определению схемы в средах с ограниченным доступом, таких как серверы CI, поэтому вам все равно придется настроить xpath. Что касается «почему xml-модуль не может этого сделать» - в документации есть особое примечание: «Этот модуль не обрабатывает сложные выражения xpath, поэтому ограничьте селекторы xpath простыми выражениями». Если упростить — это всего лишь универсальный модуль, не предназначенный для выполнения сложных операций над Maven POM.

Alexander Pletnev 13.04.2024 21:31

Ограниченные CI-серверы — это место, где используется существующий pom.xml. Мы делаем это в первую очередь по инерции. Я строю на реальных серверах приложений, на которых будут работать программы: использую Ansible для извлечения кода из git, а затем собираю его локально.

Mikhail T. 13.04.2024 21:47

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

Alexander Pletnev 13.04.2024 22:32

Большинство Java-приложений развертываются «обычно» — в контейнерах Docker или Tomcat. Только не эти...

Mikhail T. 14.04.2024 00:13

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