В процессе разработки программного обеспечения обычно используется несколько сред: среды для разработки, тестирования и промышленного использования. В этой статье поговорим о том, как переносить артефакты Adaptavist ScriptRunner между средами Atlassian Jira.
Исходный код разработанного в этой статье плагина можно посмотреть здесь.
При разработке программного обеспечения с помощью Adaptavist ScriptRunner создаются следующие артефакты:
- cкрипты
- объекты бизнес-процессов
- скриптовые поля
- лисенеры
- REST методы
- скриптовые фрагменты (script fragments)
- бехейворы (behaviours)
- кастомные JQL функции
В статье будем говорить о скриптах, а под объектами будем понимать скриптовые поля, лисенеры, REST методы, скриптовые фрагменты.
Так как мы используем несколько сред Jira, то после того, как программное обеспечение разработано в среде разработки, его необходимо перенести на остальные среды.
Программное обеспечение, разрабатываемое в ScriptRunner, можно перенести следующим образом:
- Вручную.
- Сохранить все скрипты в системе контроля версий, создать план развертывания в одном из серверов непрерывной интеграции и доставки и переносить скрипты автоматически при изменении ветки в репозитории. Но в этом случае объекты ScriptRunner будут все-равно переноситься вручную.
- Сделать скриптовый плагин, который будет содержать все скрипты и объекты 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 том виде, как он выглядит после модификации. Я написал комментарии к наиболее важным частям.
<?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.
package ru.matveev.alexey.main.listeners
import org.slf4j.LoggerFactory;
def log = LoggerFactory.getLogger(this.getClass())
log.debug("listener {} executed", this.getClass())
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();
}
package ru.matveev.alexey.main.scriptedfields
import org.slf4j.LoggerFactory;
def log = LoggerFactory.getLogger(this.getClass())
log.debug("scripted field {} executed", this.getClass())
package ru.matveev.alexey.main.scripts
import org.slf4j.LoggerFactory;
def log = LoggerFactory.getLogger(this.getClass())
log.debug("script {} executed", this.getClass())
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.
!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 в нашем плагине.
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).
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 и пользоваться нашими скриптами и объектами.
Автор: aleme