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

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

Исходный код разработанного в этой статье плагина можно посмотреть здесь.
При разработке программного обеспечения с помощью Adaptivist 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




    4.0.0

 
   com.adaptavist.pom
   scriptrunner-jira-standard
   10
   
 

 ru.matveev.alexey.scriptrunner
 scriptrunner-plugin
 1.0.0-SNAPSHOT

 
 Example Company
 http://www.example.com/
 

 scriptrunner-plugin
 This is the ru.matveev.alexey.scriptrunner:scriptrunner-plugin plugin for Atlassian JIRA.
 atlassian-plugin

 

   
     com.onresolve.jira.groovy
     groovyrunner
     ${scriptrunner.version}
     provided
     
       
         com.onresolve.scriptrunner.platform
         scriptrunner-test-libraries-jira
       
       
         jndi
         jndi
       
       
         jta
         jta
       
       
         is.origo.jira
         tempo-plugin
       
       
         com.tempoplugin
         tempo-core
       
       
         groovyrunner
         test
       
       
         com.atlassian.plugin.automation
         automation-api
       
     
   
   
     com.atlassian.plugin
     atlassian-spring-scanner-annotation
     ${atlassian.spring.scanner.version}
     provided
   

   
     javax.inject
     javax.inject
     1
     provided
   
 

 
   
     
       com.atlassian.maven.plugins
       maven-jira-plugin
       ${amps.version}
       true
       
         ${jira.version}
         ${jira.version}

         -Xms512M -Xmx1g
         true
         false
         

           
             jira-software
             ${jira.version}
           

           
             jira-servicedesk
             ${jira.servicedesk.application.version}
           
         
         
           ${atlassian.plugin.key}
           
             ru.matveev.alexey.scriptrunner.api,
           
           
             org.springframework.osgi.*;resolution:="optional",
             org.eclipse.gemini.blueprint.*;resolution:="optional",
             *
           
           *
         
       
     

     
       com.atlassian.plugin
       atlassian-spring-scanner-maven-plugin
       ${atlassian.spring.scanner.version}
       
         
           
             atlassian-spring-scanner
           
           process-classes
         
       
       
         
           
             com.atlassian.plugin
             atlassian-spring-scanner-external-jar
           
         
         false
       
     
   
 
 
   7.9.0
   3.12.0
   5.3.9
   6.3.6
   1.2.3
   2.0.0
 ${project.groupId}.${project.artifactId}
   6.3.11
   UTF-8
 

 

   
     adaptavist-external
     https://nexus.adaptavist.com/content/repositories/external
     
       false
     
     
       true
       fail
     
   
 



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


Удаляем директории 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.

aunuqbwfzoe5svom4zeocuqxhze.jpeg

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

6uqju58qa_gw3kutbuvvsviyxlg.jpeg

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

85cnpb74xzeho6cnj0r7jxkky7g.jpeg

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

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


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

Лисенер 1:

kkanvcqm3ewkd_fpjeqhtlk6cms.png

Лисенер 2:

cpfikqe7mynbcgeap7illcrp5mk.png

REST-модуль:

jpdjdcmhr1spyzx0obmf9dauacm.png

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

s4sd_k8vtbsb-lz5zhgloastgqs.png

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

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

98_hc4vknjiitqjpst15usy6q2u.jpeg

Копируем все содержимое прямоугольника, выделенного красным цветом, и сохраняем в файл 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 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 и устанавливаем собранный плагин в среду тестирования.

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

vtmbgyr5mjyyoy5m1aqzlqajsqg.jpeg

Лисенеры:

gr-dgcin4ny1m7od8nwhsm-td4m.jpeg

REST-метод:

_wcd9eqa5ncmssnmupnbjtxvrhg.jpeg

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

cwomiijdr5sipvmvj0uh_4nr_1g.jpeg

Мы видим, что все объекты созданы. Теперь мы можем устанавливать наш плагин в любой инстанс Jira и пользоваться нашими скриптами и объектами.

Habrahabr.ru прочитано 10816 раз