company_banner

Как разворачивать артефакты Adaptavist ScriptRunner

  • Tutorial
В процессе разработки программного обеспечения обычно используется несколько сред: среды для разработки, тестирования и промышленного использования. В этой статье поговорим о том, как переносить артефакты Adaptavist ScriptRunner между средами Atlassian Jira.

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

При разработке программного обеспечения с помощью Adaptavist ScriptRunner создаются следующие артефакты:

  1. cкрипты
  2. объекты бизнес-процессов
  3. скриптовые поля
  4. лисенеры
  5. REST методы
  6. скриптовые фрагменты (script fragments)
  7. бехейворы (behaviours)
  8. кастомные JQL функции

В статье будем говорить о скриптах, а под объектами будем понимать скриптовые поля, лисенеры, REST методы, скриптовые фрагменты.

Так как мы используем несколько сред Jira, то после того, как программное обеспечение разработано в среде разработки, его необходимо перенести на остальные среды.

Программное обеспечение, разрабатываемое в ScriptRunner, можно перенести следующим образом:

  1. Вручную.
  2. Сохранить все скрипты в системе контроля версий, создать план развертывания в одном из серверов непрерывной интеграции и доставки и переносить скрипты автоматически при изменении ветки в репозитории. Но в этом случае объекты ScriptRunner будут все-равно переноситься вручную.
  3. Сделать скриптовый плагин, который будет содержать все скрипты и объекты ScriptRunner. При установке данного плагина в Jira скрипты и объекты ScriptRunner будут автоматически разворачиваться.

В этой статье мы рассмотрим третий способ разворачивания скриптов и объектов ScriptRunner — разворачивание через скриптовый плагин.

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

Мы попробуем разработать свой плагин, который будет устанавливать скрипты и объекты Scriptrunner для Jira 7.9.0 и использовать версию ScriptRunner 5.3.9.

Создаем плагин


Открываем терминал и вводим следующую команду:

atlas-create-jira-plugin

На вопросы в терминале отвечаем вот так:

Define value for groupId: : ru.matveev.alexey.scriptrunner 
Define value for artifactId: : scriptrunner-plugin 
Define value for version: 1.0.0-SNAPSHOT: : 
Define value for package: ru.matveev.alexey.scriptrunner: : 
Confirm properties configuration: 
groupId: ru.matveev.alexey.scriptrunner 
artifactId: scriptrunner-plugin version: 1.0.0-SNAPSHOT 
package: ru.matveev.alexey.scriptrunner 
Y: : Y

Изменяем pom.xml


Ниже привожу pom.xml том виде, как он выглядит после модификации. Я написал комментарии к наиболее важным частям.

pom.xml
<?xml version="1.0" encoding="UTF-8"?>

<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 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>
<!--
Добавил родительский pom.xml, который делает всю магию. Я взял этот pom.xml из примера скриптового плагина от ScriptRunner. 
-->
 <parent>
   <groupId>com.adaptavist.pom</groupId>
   <artifactId>scriptrunner-jira-standard</artifactId>
   <version>10</version>
   <relativePath/>
 </parent>

 <groupId>ru.matveev.alexey.scriptrunner</groupId>
 <artifactId>scriptrunner-plugin</artifactId>
 <version>1.0.0-SNAPSHOT</version>

 <organization>
 <name>Example Company</name>
 <url>http://www.example.com/</url>
 </organization>

 <name>scriptrunner-plugin</name>
 <description>This is the ru.matveev.alexey.scriptrunner:scriptrunner-plugin plugin for Atlassian JIRA.</description>
 <packaging>atlassian-plugin</packaging>

 <dependencies>
<!--
Я исключил несколько зависимостей из зависимости ниже, так как плагин не хотел собираться для версии ScriptRunner выше 5.3.0.
-->
   <dependency>
     <groupId>com.onresolve.jira.groovy</groupId>
     <artifactId>groovyrunner</artifactId>
     <version>${scriptrunner.version}</version>
     <scope>provided</scope>
     <exclusions>
       <exclusion>
         <groupId>com.onresolve.scriptrunner.platform</groupId>
         <artifactId>scriptrunner-test-libraries-jira</artifactId>
       </exclusion>
       <exclusion>
         <groupId>jndi</groupId>
         <artifactId>jndi</artifactId>
       </exclusion>
       <exclusion>
         <groupId>jta</groupId>
         <artifactId>jta</artifactId>
       </exclusion>
       <exclusion>
         <groupId>is.origo.jira</groupId>
         <artifactId>tempo-plugin</artifactId>
       </exclusion>
       <exclusion>
         <groupId>com.tempoplugin</groupId>
         <artifactId>tempo-core</artifactId>
       </exclusion>
       <exclusion>
         <groupId>groovyrunner</groupId>
         <artifactId>test</artifactId>
       </exclusion>
       <exclusion>
         <groupId>com.atlassian.plugin.automation</groupId>
         <artifactId>automation-api</artifactId>
       </exclusion>
     </exclusions>
   </dependency>
   <dependency>
     <groupId>com.atlassian.plugin</groupId>
     <artifactId>atlassian-spring-scanner-annotation</artifactId>
     <version>${atlassian.spring.scanner.version}</version>
     <scope>provided</scope>
   </dependency>

   <dependency>
     <groupId>javax.inject</groupId>
     <artifactId>javax.inject</artifactId>
     <version>1</version>
     <scope>provided</scope>
   </dependency>
 </dependencies>

 <build>
   <plugins>
     <plugin>
       <groupId>com.atlassian.maven.plugins</groupId>
       <artifactId>maven-jira-plugin</artifactId>
       <version>${amps.version}</version>
       <extensions>true</extensions>
       <configuration>
         <productVersion>${jira.version}</productVersion>
         <productDataVersion>${jira.version}</productDataVersion>
<!--
Увеличил память JVM, потому что Jira 7.9.0 не запускается с параметрами по умолчанию.
-->
         <jvmArgs>-Xms512M -Xmx1g</jvmArgs>
         <enableQuickReload>true</enableQuickReload>
         <enableFastdev>false</enableFastdev>
         <applications>
<!--
Добавил Jira Software, что бы Jira Software запускалась по команде atlas-run.
-->
           <application>
             <applicationKey>jira-software</applicationKey>
             <version>${jira.version}</version>
           </application>
<!--
Добавил Jira Service Desk, чтобы Jira Service Desk запускалсь по команде atlas-run.
-->
           <application>
             <applicationKey>jira-servicedesk</applicationKey>
             <version>${jira.servicedesk.application.version}</version>
           </application>
         </applications>
         <instructions>
           <Atlassian-Plugin-Key>${atlassian.plugin.key}</Atlassian-Plugin-Key>
           <Export-Package>
             ru.matveev.alexey.scriptrunner.api,
           </Export-Package>
           <Import-Package>
             org.springframework.osgi.*;resolution:="optional",
             org.eclipse.gemini.blueprint.*;resolution:="optional",
             *
           </Import-Package>
           <Spring-Context>*</Spring-Context>
         </instructions>
       </configuration>
     </plugin>

     <plugin>
       <groupId>com.atlassian.plugin</groupId>
       <artifactId>atlassian-spring-scanner-maven-plugin</artifactId>
       <version>${atlassian.spring.scanner.version}</version>
       <executions>
         <execution>
           <goals>
             <goal>atlassian-spring-scanner</goal>
           </goals>
           <phase>process-classes</phase>
         </execution>
       </executions>
       <configuration>
         <scannedDependencies>
           <dependency>
             <groupId>com.atlassian.plugin</groupId>
             <artifactId>atlassian-spring-scanner-external-jar</artifactId>
           </dependency>
         </scannedDependencies>
         <verbose>false</verbose>
       </configuration>
     </plugin>
   </plugins>
 </build>
 <properties>
   <jira.version>7.9.0</jira.version>
   <jira.servicedesk.application.version>3.12.0</jira.servicedesk.application.version>
   <scriptrunner.version>5.3.9</scriptrunner.version>
   <amps.version>6.3.6</amps.version>
   <plugin.testrunner.version>1.2.3</plugin.testrunner.version>
   <atlassian.spring.scanner.version>2.0.0</atlassian.spring.scanner.version>
 <atlassian.plugin.key>${project.groupId}.${project.artifactId}</atlassian.plugin.key>
   <testkit.version>6.3.11</testkit.version>
   <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 </properties>

 <repositories>
<!-- 
Репозиторий обязателен, чтобы найти родительский pom и зависимости ScriptRunner.
 -->
   <repository>
     <id>adaptavist-external</id>
     <url>https://nexus.adaptavist.com/content/repositories/external</url>
     <snapshots>
       <enabled>false</enabled>
     </snapshots>
     <releases>
       <enabled>true</enabled>
       <checksumPolicy>fail</checksumPolicy>
     </releases>
   </repository>
 </repositories>
</project>

Удаляем директории в созданном плагине


Удаляем директории src/main/java и src/test.

Добавляем скрипты


Создадим несколько скриптов, которые будем использовать в объектах ScriptRunner.

src/main/resources/ru/matveev/alexey/main/listeners/listener.groovy
package ru.matveev.alexey.main.listeners

import org.slf4j.LoggerFactory;

def log = LoggerFactory.getLogger(this.getClass())
log.debug("listener {} executed", this.getClass())


src/main/resources/ru/matveev/alexey/main/rest/rest.groovy
package ru.matveev.alexey.main.rest

import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.json.JsonBuilder
import groovy.transform.BaseScript

import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response

@BaseScript CustomEndpointDelegate delegate

doSomething(httpMethod: "GET", groups: ["jira-administrators"]) { MultivaluedMap queryParams, String body ->
    return Response.ok(new JsonBuilder([abc: 42]).toString()).build();
}


src/main/resources/ru/matveev/alexey/main/scriptedfields/scriptedfield.groovy
package ru.matveev.alexey.main.scriptedfields

import org.slf4j.LoggerFactory;
def log = LoggerFactory.getLogger(this.getClass())
log.debug("scripted field {} executed", this.getClass())

src/main/resources/ru/matveev/alexey/main/scripts/script.groovy
package ru.matveev.alexey.main.scripts

import org.slf4j.LoggerFactory;
def log = LoggerFactory.getLogger(this.getClass())
log.debug("script {} executed", this.getClass())


src/main/resources/ru/matveev/alexey/main/webfragments/webfragments.groovy
package ru.matveev.alexey.main.scriptedfields

import org.slf4j.LoggerFactory;
def log = LoggerFactory.getLogger(this.getClass())
log.debug("scripted field {} executed", this.getClass()) 


Протестируем скрипты


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

Открываем терминал, переходим в директорию плагина и запускаем команду:

atlas-run

После того, как Jira запустилась, нужно зайти в браузер по адресу localhost:8080/jira и войти в Jira под учетной записью admin:admin.

Для того, чтобы мы увидели логи наших скриптов, мы должны установить уровень DEBUG для пакета ru.matveev.

Заходим в System → Logging and Profiling и нажимаем на кнопку Configure. В поле Package name вводим ru.matveev, в поле Logging level выбираем DEBUG и нажимаем на кнопку Add.



Теперь попробуем выполнить один из наших скриптов. Переходим в Add-ons → Script Console и в закладке File вводим ru/matveev/alexey/main/scripts/script.groovy. Мы также видим, что в ScriptRunner появились каталоги скриптов (script roots) с нашими скриптами.



Нажимаем на кнопку Run.



Мы видим, что наш скрипт успешно выполнился.

Создаем лисенер, скриптовый фрагмент и REST метод


Устанавливаем наш плагин в среду разработки и начинаем создавать объекты.

Лисенер 1:



Лисенер 2:



REST-модуль:



Скриптовый фрагмент:



После того, как объекты созданы, мы должны выгрузить описания этих объектов и положить их в файл scriptrunner.yaml в нашем плагине.

Переходим в Add-ons → Built-in scripts → Export Configuration, выбираем все созданные нами объекты и нажимаем на кнопку Run.



Копируем все содержимое прямоугольника, выделенного красным цветом, и сохраняем в файл scriptrunner.yaml.

src/main/resources/scriptrunner.yaml
!descriptor
fragmentConfigItems:
- FIELD_DO_WHAT: NAVIGATE
 FIELD_KEY: ru-matveev-alexey-web-item
 FIELD_LINK_CONDITION:
 - ''
 - ''
 FIELD_LINK_DESTINATION: ''
 FIELD_MENU_LABEL: ''
 FIELD_NOTES: Web Item
 FIELD_SECTION: add-attachments-link
 FIELD_STYLE_CLASS: ''
 FIELD_WEIGHT: ''
 canned-script: com.onresolve.scriptrunner.canned.jira.fragments.CustomWebItem
 id: '520053084'
restConfigItems:
- FIELD_INLINE_SCRIPT: ''
 FIELD_NOTES: REST endpoint
 FIELD_SCRIPT_FILE: ru/matveev/alexey/main/rest/rest.groovy
 canned-script: com.onresolve.scriptrunner.canned.common.rest.CustomRestEndpoint
 id: '-168713291'
scriptListeners:
- FIELD_FUNCTION_ID: cf09831f83bc75ec27076557034b952dfc727040
 FIELD_INLINE_SCRIPT: ''
 FIELD_LISTENER_NOTES: Custom Listener
 canned-script: com.onresolve.scriptrunner.canned.jira.workflow.listeners.CustomListener
 clazz: ru/matveev/alexey/main/listeners/listener.groovy
 events:
 - 1
 id: '-586588827'
 params: '{"FIELD_LISTENER_NOTES":"Custom Listener","projects":"","events":"1","FIELD_INLINE_SCRIPT":"","clazz":"ru/matveev/alexey/main/listeners/listener.groovy","FIELD_FUNCTION_ID":"cf09831f83bc75ec27076557034b952dfc727040","canned-script":"com.onresolve.scriptrunner.canned.jira.workflow.listeners.CustomListener","id":"-268926325"}'
 projects:
 - ''
- FIELD_CONDITION: []
 FIELD_FUNCTION_ID: ''
 FIELD_LISTENER_NOTES: Add the current user as a watcher
 canned-script: com.onresolve.scriptrunner.canned.jira.workflow.postfunctions.AddWatcher
 events:
 - 1
 params: '{"FIELD_LISTENER_NOTES":"Add the current user as a watcher","projects":"","events":"1","FIELD_CONDITION":["",""],"FIELD_FUNCTION_ID":"","canned-script":"com.onresolve.scriptrunner.canned.jira.workflow.postfunctions.AddWatcher"}'
 projects:
 - ''


Добавляем скриптовое поле


Бехейворы и скриптовые поля не могут быть добавлены через scriptrunner.yaml. В этой статье мы создадим скриптовое поле. Скриптовое поле будем создавать через Upgrade Task. Upgrade Task это функциональность плагина, которая позволяет запускать код при старте плагина. Код будет выполнен только в том случае, если он еще не выполнялся. Для того, чтобы написать Upgrade Task необходимо создать класс, который будет имплементировать интерфейс PluginUpgradeTask. Кроме того класс должен быть объявлен как публичный сервис.

Сначала создадим AbstractUpgradeTask.groovy класс (код позаимствован из примера скриптового плагина от ScriptRunner). Данный класс имплементирует метод getPluginKey, который содержит интерфейс PluginUpgradeTask. Метод имплементирован в отдельном классе, потому что он универсально работает для всех Upgrade Task в нашем плагине.

src/main/groovy/ru/matveev/alexey/scriptedfields/AbstractUpgradeTask.groovy
package ru.matveev.alexey.scriptedfields

import com.atlassian.plugin.osgi.util.OsgiHeaderUtil
import groovy.util.logging.Log4j;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;

@Log4j
abstract class AbstractUpgradeTask {
    public String getPluginKey() {
        Bundle bundle = FrameworkUtil.getBundle(AbstractUpgradeTask.class);
        return OsgiHeaderUtil.getPluginKey(bundle);
    }
}


Теперь создадим класс, который имплементирует интерфейс PluginUpgradeTask (код позаимствован из примера скриптового плагина от ScriptRunner).

src/main/groovy/ru/matveev/alexey/scriptedfields/CreateScriptFieldUpgradeTask.groovy
package ru.matveev.alexey.scriptedfields

import com.atlassian.jira.issue.context.GlobalIssueContext
import com.atlassian.plugin.spring.scanner.annotation.export.ExportAsService
import com.atlassian.sal.api.message.Message
import com.atlassian.sal.api.upgrade.PluginUpgradeTask
import com.onresolve.scriptrunner.runner.ScriptRunnerImpl
import com.onresolve.scriptrunner.test.ScriptFieldCreationInfo
import groovy.util.logging.Log4j

import javax.inject.Named

@Log4j
@Named
@ExportAsService
class CreateScriptFieldUpgradeTask extends AbstractUpgradeTask implements PluginUpgradeTask {


    @Override
    int getBuildNumber() {
         return 1
    }

    @Override
    String getShortDescription() {
         return "This upgrade task creates a scripted field"
    }

    @Override
    Collection<Message> doUpgrade() throws Exception {
         def scriptFieldCreation = ScriptFieldCreationInfo.Builder.newBuilder()
                .setName("TestScriptFieldSimpleNumberX")
                .setSearcherKey(ScriptRunnerImpl.PLUGIN_KEY + ":exactnumber")
                .setTemplate("float")
                .setContexts([GlobalIssueContext.instance])
                .setScriptFile("ru/matveev/alexey/main/scriptedfields/scriptedfield.groovy")
                .build()
        scriptFieldCreation.create()
        return null
    }

}


Тестируем перенос объектов


Собираем наш плагин командой atlas-mvn package и устанавливаем собранный плагин в среду тестирования.

Сморим создались ли объекты. Скриптовое поле:



Лисенеры:



REST-метод:



Скриптовый фрагмент:



Мы видим, что все объекты созданы. Теперь мы можем устанавливать наш плагин в любой инстанс Jira и пользоваться нашими скриптами и объектами.
Райффайзенбанк 177,77
Развеиваем мифы об IT в банках
Поделиться публикацией
Комментарии 2

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое