1
0
mirror of https://github.com/firstBitMarksistskaya/jenkins-lib.git synced 2024-12-11 11:19:08 +02:00

Merge branch 'develop'

This commit is contained in:
Nikita Gryzlov 2021-11-08 20:37:05 +03:00
commit 0829836d61
No known key found for this signature in database
GPG Key ID: C3CAA2980494E7E6
35 changed files with 584 additions and 109 deletions

5
.gitignore vendored
View File

@ -1,5 +1,10 @@
.idea/
.gradle/
build/
bin/
lib/
*.iml
.classpath
.project
.settings/

View File

@ -21,19 +21,20 @@
1. Для шага подготовки требуется любой агент с меткой `agent`.
1. Для запуска шага анализа SonarQube требуется агент с меткой `sonar`.
1. Для запуска шага валидации EDT требуется агент с меткой `edt` (для собственно валидации) и агент с меткой `oscript` (для трансформации результатов с помощью библиотеки [stebi](https://github.com/Stepa86/stebi)).
1. Для запуска шагов, работающих с EDT (валидация, трансформация формата исходников) требуется агент с меткой `edt` и агент с меткой `oscript` (для трансформации результатов с помощью библиотеки [stebi](https://github.com/Stepa86/stebi)).
1. Для запуска шагов, работающих с 1С (подготовка, синтаксический контроль и т.д.) требуется агент с меткой, совпадающей со значением в поле `v8version` файла конфигурации.
1. В качестве ИБ используется файловая база, создаваемая в `./build/ib` на основании конфигурации из хранилища без пользователей. При необходимости вы можете создать пользователей на фазе инициализации ИБ.
1. Stage "Дымовые тесты" пока пустой.
1. Запуск `vrunner` на текущий момент происходит из локального каталога `oscript_modules`. Предполагается наличие в корне репозитория файла `packagedef`, в котором бы была указана зависимость от `vanessa-runner`
1. В качестве ИБ используется файловая база, создаваемая в каталоге `./build/ib`. При необходимости вы можете создать пользователей на фазе инициализации ИБ.
1. Шаг "Дымовые тесты" пока пустой.
## Возможности
1. Все шаги можно запустить на базе docker-образов из форка репозитория onec-docker. См. [памятку по слоям и последовательности сборки](https://github.com/firstBitSemenovskaya/onec-docker/blob/feature/first-bit/Layers.md)
1. Подготовка информационной базы по версии из хранилища конфигурации.
1. Все шаги можно запустить на базе docker-образов из форка репозитория onec-docker. См. [памятку по слоям и последовательности сборки](https://github.com/firstBitSemenovskaya/onec-docker/blob/feature/first-bit/Layers.md).
1. Поддержка как формата выгрузки из Конфигуратора, так и формата EDT.
1. Подготовка информационной базы по версии из хранилища конфигурации, из исходных файлов конфигурации, комбинированный режим (основная ветка - из хранилища, остальные - из исходников).
1. Запуск ИБ в режиме выполнения обработчиков обновления БСП.
1. Дополнительные шаги инициализации данных в ИБ.
1. Трансформация кода из формата конфигуратора в формат EDT (только если включен шаг `edtValidate`).
1. Трансформация кода из формата конфигуратора в формат EDT.
1. Трансформация кода из формата EDT в формат конфигуратора.
1. Запуск BDD сценариев с сохранением результатов в формате Allure.
1. Запуск синтаксического контроля средствами конфигуратора и сохранение результатов в виде отчета jUnit.
1. Запуск валидации проекта средствами EDT и конвертация отчета в формате generic issues.
@ -99,22 +100,41 @@ pipeline1C()
В библиотеке применяется принцип "соглашения по конфигурации" (convention over configuration): конфигурационный файл
содержит ряд настроек "по умолчанию". При соблюдении определенных правил структуры репозитория можно сократить
количество переопределений конфигурационного файла до минимума.
количество переопределений конфигурационного файла до минимума. Ниже представлены имеющиеся соглашения и способы их переопределения.
* Общее:
* Исходники конфигурации ожидаются в каталоге `src/cf`.
* TODO: Имена "секретов" (jenkins credentials) по умолчанию высчитываются как `GROUP_REPO_KEY`, где `GROUP` и `REPO` - это группа проектов и имя проектов (например, `firstBitSemenovskaya` и `jenkins-lib`), а `KEY` - ключ секрета:
* `STORAGE_PATH` - путь к хранилищу конфигурации;
* `STORAGE_USER` - параметры авторизации в хранилище вида "username with password".
* Все "шаги" по умолчанию выключены.
* В качестве маски версии платформы используется строка "8.3" (`v8version`).
* Исходники конфигурации ожидаются в каталоге `src/cf` (`srcDir`).
* Формат исходников - выгрузка из Конфигуратора (`sourceFormat`).
* Ветка по умолчанию (для комбинированного режима загрузки конфигурации) - "main" (`defaultBranch`).
* Имена "секретов" (jenkins credentials, `secrets`) по умолчанию высчитываются из пути к git-репозиторию (без учета домена, с заменой `/` на `_`) с прибавлением ключа секрета. Например, для репозитория https://github.com/firstBitSemenovskaya/jenkins-lib секрет с адресом хранилища будет выглядеть как `firstBitSemenovskaya_jenkins-lib_STORAGE_PATH`. Ключи секретов:
* `STORAGE_PATH` - путь к хранилищу конфигурации (для `secrets` -> `storagePath`);
* `STORAGE_USER` - параметры авторизации в хранилище вида "username with password" (для `secrets` -> `storage`).
* Все "шаги" по умолчанию выключены (`stages`).
* Если в корне репозитория существует файл `packagedef`, то в шагах, работающих с информационной базой, будет выполнена попытка установки локальных зависимостей средствами `opm`.
* Если после установки локальных зависимостей в каталоге `oscript_modules/bin` сушествует файл `vrunner`, то для выполнения команд работы с информационной базой будет использоваться он, а не глобально установленный `vrunner` из `PATH`.
* Результаты в формате `allure` ожидаются в каталоге `build/out/allure` или его подкаталогах.
* Инициализация:
* Если включен шаг `initSteps`, то будет выполняться запуск ИБ с целью запуска обработчиков обновления из БСП. (`initInfobase` -> `runMigration`)
* Информационная база инициализируется только в том случае, если в сборочной линии включены шаги, работающие с базой (например, `bdd` или `syntaxCheck`).
* Информационная база инициализируется конфигурацией из хранилища конфигурации (`initInfobase` -> `initMethod`).
* Если выбран метод инициализации ИБ из хранилища конфигурации и в каталоге исходников есть файл `VERSION` (артефакт от работы утилиты `gitsync`), то из хранилища будет загружена версия конфигурации с номером из файла `VERSION`.
* Первичный запуск информационной базы:
* Если информационная база нужна для запуска в режиме "Предприятие" (например, для шагов `bdd` или `smoke`), то будет запущен шаг "Миграция ИБ".
* После загрузки конфигурации в ИБ будет выполняться запуск ИБ с целью запуска обработчиков обновления из БСП (`initInfobase` -> `runMigration`).
* Если в настройках шага инициализации не заполнен массив дополнительных шагов миграции (`initInfobase` -> `additionalInitializationSteps`), но в каталоге `tools` присутствуют файлы с именами, удовлетворяющими шаблону `vrunner.init*.json`, то автоматически выполняется запуск `vrunner vanessa` с передачей найденных файлов в качестве значения настроек (параметр `--settings`) в порядке лексиграфической сортировки имен файлов.
* BDD:
* Если в конфигурационном файле проекта не заполнена настройка `bdd` -> `vrunnerSteps`, то автоматически выполняется запуск `vrunner vanessa --settings tools/vrunner.json`.
* Синтаксический контроль:
* Если в репозитории существует файл `tools/vrunner.json`, то синтаксический контроль конфигурации с помощью конфигуратора будет выполняться с передачей файла в параметры запуска `vrunner syntax-check --settings tools/vrunner.json`.
* TODO: Значение параметра `--mode` из конфигурационного файла vrunner имеют приоритет над `syntaxCheck` -> `checkModes`. Значение из `jobConfiguration.json` передается только в том случае, если параметр отсутствует в конфигурационном файле `vrunner`.
* Если в репозитории существует файл `tools/vrunner.json`, то синтаксический контроль конфигурации с помощью конфигуратора будет выполняться с передачей файла в параметры запуска `vrunner syntax-check --settings tools/vrunner.json` (`syntaxCheck` -> `vrunnerSettings`).
* Применяется группировка ошибок по метаданным (`syntaxCheck` -> `groupErrorsByMetadata`).
* Выгрузка результатов в формат `jUnit` осуществляется в файл `./build/out/jUnit/syntax.xml` (`syntaxCheck` -> `pathToJUnitReport`).
* Если в репозитории существует файл `./tools/syntax-check-exception-file.txt`, то команде запука синтаксического контроля конфигурации данный файл будет передаваться как файл с исключениями сообщений об ошибках (параметр `--exception-file`) (`syntaxCheck` -> `exceptionFile`).
* Конфигурационный файл по умолчанию уже содержит ряд "режимов проверки" для синтаксического контроля конфигурации (`syntaxCheck` -> `checkModes`).
* Трансформация результатов валидации EDT:
* По умолчанию из результатов анализа исключаются замечания, сработавшие на модулях с включенным запретом редактирования (желтый куб с замком)
* По умолчанию из результатов анализа исключаются замечания, сработавшие на модулях с включенным запретом редактирования (желтый куб с замком) (параметры `resultsTransform` -> `removeSupport` и `resultsTransform` -> `supportLevel`).
* Анализ SonarQube:
* Предполагается наличие единственной настройки `SonarQube installation` (`sonarqube` -> `sonarQubeInstallation`).
* Используется `sonar-scanner` из переменной окружения `PATH` (`sonarqube` -> `useSonarScannerFromPath`).
* Если использование `sonar-scanner` из переменной окружения `PATH` выключено, предполагается наличие настроенного глобального инструмента `SonarQube Scanner` с идентификатором инструмента `sonar-scanner` (`sonarqube` -> `sonarScannerToolName`).
* Версия из корня конфигурации передается утилите `sonar-scanner` как значение параметра `sonar.projectVersion=$configurationVersion`.
* Если выполнялась валидация EDT, результаты валидации в формате `generic issues` передаются утилите `sonar-scanner` как значение параметра `sonar.externalIssuesReportPaths`.

View File

@ -1,6 +1,9 @@
{
"$schema": "schema.json",
"v8version": "8.3",
"srcDir": "src/cf",
"sourceFormat": "designer",
"defaultBranch": "main",
"secrets": {
"storagePath": "UNKNOWN_ID",
"storage": "UNKNOWN_ID"
@ -14,6 +17,7 @@
"smoke": false
},
"initInfobase": {
"initMethod": "fromStorage",
"runMigration": true,
"additionalInitializationSteps": []
},
@ -30,6 +34,7 @@
"syntaxCheck": {
"groupErrorsByMetadata": true,
"pathToJUnitReport": "./build/out/jUnit/syntax.xml",
"exceptionFile": "./tools/syntax-check-exception-file.txt",
"checkModes": [
"-ThinClient",
"-WebClient",

View File

@ -8,7 +8,16 @@
},
"srcDir" : {
"type" : "string",
"description" : "Путь к корневому каталогу с исходниками конфигурации"
"description" : "Путь к корневому каталогу с исходниками конфигурации, в случае хранения исходников в формате EDT, необходимо указать путь к проекту"
},
"sourceFormat" : {
"type" : "string",
"description" : "Формат исходников конфигурации",
"enum" : [ "edt", "designer" ]
},
"defaultBranch" : {
"type" : "string",
"description" : "Имя ветки по умолчанию"
},
"secrets" : {
"type" : "object",
@ -61,6 +70,11 @@
"id" : "urn:jsonschema:ru:pulsar:jenkins:library:configuration:InitInfobaseOptions",
"description" : "Настройки шага инициализации ИБ",
"properties" : {
"initMethod" : {
"type" : "string",
"description" : "\n Способ инициализации информационной базы.\n Поддерживается три варианта:\n * fromStorage - инициализация информационной базы из хранилища конфигурации;\n * fromSource - инициализация информационной базы из исходников конфигурации;\n * defaultBranchFromStorage - инициализация основной ветки из хранилища конфигурации, остальных - из исходников конфигурации.\n По умолчанию содержит значение \"fromStorage\".",
"enum" : [ "fromStorage", "fromSource", "defaultBranchFromStorage" ]
},
"runMigration" : {
"type" : "boolean",
"description" : "Запустить миграцию ИБ"
@ -127,6 +141,10 @@
"type" : "string"
}
},
"exceptionFile" : {
"type" : "string",
"description" : "Путь к файлу с указанием пропускаемых ошибок.\n Формат файла: в каждой строке файла указан текст пропускаемого исключения или его часть\n Кодировка: UTF-8\n "
},
"vrunnerSettings" : {
"type" : "string",
"description" : "Путь к конфигурационному файлу vanessa-runner.\n По умолчанию содержит значение \"./tools/vrunner.json\".\n "

View File

@ -19,6 +19,8 @@ interface IStepExecutor {
String readFile(String file, String encoding)
boolean fileExists(String file)
void echo(message)
int cmd(String script, boolean returnStatus)
@ -27,12 +29,24 @@ interface IStepExecutor {
void tool(String toolName)
def withCredentials(List bindings, Closure body)
def string(String credentialsId, String variable)
def usernamePassword(String credentialsId, String usernameVariable, String passwordVariable)
void withSonarQubeEnv(String installationName, Closure body)
EnvironmentAction env()
def dir(String path, Closure body)
void createDir(String path)
void deleteDir()
void deleteDir(String path)
def withEnv(List<String> strings, Closure body)
def archiveArtifacts(String path)

View File

@ -37,6 +37,11 @@ class StepExecutor implements IStepExecutor {
steps.readFile encoding: encoding, file: file
}
@Override
boolean fileExists(String file) {
steps.fileExists file
}
@Override
FileWrapper[] findFiles(String glob, String excludes = '') {
steps.findFiles glob: glob, excludes: excludes
@ -64,6 +69,23 @@ class StepExecutor implements IStepExecutor {
}
}
@Override
def withCredentials(List bindings, Closure body) {
steps.withCredentials(bindings) {
body()
}
}
@Override
def string(String credentialsId, String variable) {
return steps.string(credentialsId: credentialsId, variable: variable)
}
@Override
def usernamePassword(String credentialsId, String usernameVariable, String passwordVariable) {
return steps.usernamePassword(credentialsId: credentialsId, usernameVariable: usernameVariable, passwordVariable: passwordVariable)
}
@Override
EnvironmentAction env() {
return steps.env
@ -74,6 +96,25 @@ class StepExecutor implements IStepExecutor {
steps.createDir(path)
}
@Override
def dir(String path, Closure body) {
steps.dir(path) {
body()
}
}
@Override
void deleteDir() {
steps.deleteDir()
}
@Override
void deleteDir(String path) {
steps.dir(path) {
steps.deleteDir()
}
}
@Override
def withEnv(List<String> strings, Closure body) {
steps.withEnv(strings) {

View File

@ -3,16 +3,31 @@ package ru.pulsar.jenkins.library.configuration
import com.cloudbees.groovy.cps.NonCPS
import com.fasterxml.jackson.databind.MapperFeature
import com.fasterxml.jackson.databind.ObjectMapper
import org.apache.commons.beanutils.BeanUtils
import org.apache.commons.beanutils.BeanUtilsBean
import org.apache.commons.beanutils.ConvertUtilsBean
import ru.pulsar.jenkins.library.IStepExecutor
import ru.pulsar.jenkins.library.ioc.ContextRegistry
class ConfigurationReader implements Serializable {
private static ObjectMapper mapper
private static BeanUtilsBean beanUtilsBean;
static {
mapper = new ObjectMapper()
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
beanUtilsBean = new BeanUtilsBean(new ConvertUtilsBean() {
@Override
@NonCPS
Object convert(String value, Class clazz) {
if (clazz.isEnum()) {
return Enum.valueOf(clazz, value);
} else {
return super.convert(value, clazz);
}
}
});
}
private static final String DEFAULT_CONFIGURATION_RESOURCE = 'globalConfiguration.json'
@ -57,13 +72,13 @@ class ConfigurationReader implements Serializable {
@NonCPS
private static <T extends Object> void mergeObjects(T baseObject, T objectToMerge, Set<String> nonMergeableSettings) {
BeanUtils.describe(objectToMerge).entrySet().stream()
beanUtilsBean.describe(objectToMerge).entrySet().stream()
.filter({ e -> e.getValue() != null })
.filter({ e -> e.getKey() != "class" })
.filter({ e -> e.getKey() != "metaClass" })
.filter({ e -> !nonMergeableSettings.contains(e.getKey()) })
.forEach { e ->
BeanUtils.setProperty(baseObject, e.getKey(), e.getValue());
beanUtilsBean.setProperty(baseObject, e.getKey(), e.getValue());
}
nonMergeableSettings.forEach({ key ->

View File

@ -0,0 +1,16 @@
package ru.pulsar.jenkins.library.configuration
import com.fasterxml.jackson.annotation.JsonProperty
enum InitInfobaseMethod {
@JsonProperty("fromStorage")
FROM_STORAGE,
@JsonProperty("fromSource")
FROM_SOURCE,
@JsonProperty("defaultBranchFromStorage")
DEFAULT_BRANCH_FROM_STORAGE
}

View File

@ -7,6 +7,15 @@ import com.fasterxml.jackson.annotation.JsonPropertyDescription
@JsonIgnoreProperties(ignoreUnknown = true)
class InitInfobaseOptions implements Serializable {
@JsonPropertyDescription("""
Способ инициализации информационной базы.
Поддерживается три варианта:
* fromStorage - инициализация информационной базы из хранилища конфигурации;
* fromSource - инициализация информационной базы из исходников конфигурации;
* defaultBranchFromStorage - инициализация основной ветки из хранилища конфигурации, остальных - из исходников конфигурации.
По умолчанию содержит значение "fromStorage".""")
InitInfobaseMethod initMethod = InitInfobaseMethod.FROM_STORAGE;
@JsonPropertyDescription("Запустить миграцию ИБ")
boolean runMigration = true
@ -20,7 +29,8 @@ class InitInfobaseOptions implements Serializable {
@NonCPS
String toString() {
return "InitInfobaseOptions{" +
"runMigration=" + runMigration +
"initMethod=" + initMethod +
", runMigration=" + runMigration +
", additionalInitializationSteps=" + additionalInitializationSteps +
'}';
}

View File

@ -1,22 +1,31 @@
package ru.pulsar.jenkins.library.configuration
import com.cloudbees.groovy.cps.NonCPS
import com.fasterxml.jackson.annotation.JsonEnumDefaultValue
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.annotation.JsonPropertyDescription
import ru.pulsar.jenkins.library.IStepExecutor
import ru.pulsar.jenkins.library.ioc.ContextRegistry
@JsonIgnoreProperties(ignoreUnknown = true)
class JobConfiguration implements Serializable {
@JsonPropertyDescription("Версия платформы 1С:Предприятие в формате 8.3.хх.хххх.")
String v8version
@JsonPropertyDescription("Путь к корневому каталогу с исходниками конфигурации")
@JsonPropertyDescription("Путь к корневому каталогу с исходниками конфигурации, в случае хранения исходников в формате EDT, необходимо указать путь к проекту")
String srcDir
@JsonPropertyDescription("Формат исходников конфигурации")
SourceFormat sourceFormat;
@JsonProperty("stages")
@JsonPropertyDescription("Включение этапов сборок")
StageFlags stageFlags;
@JsonPropertyDescription("Имя ветки по умолчанию. Значение по умолчанию - main.")
String defaultBranch
@JsonPropertyDescription("Идентификаторы сохраненных секретов")
Secrets secrets;
@ -50,6 +59,8 @@ class JobConfiguration implements Serializable {
return "JobConfiguration{" +
"v8version='" + v8version + '\'' +
", srcDir='" + srcDir + '\'' +
", sourceFormat=" + sourceFormat +
", defaultBranch=" + defaultBranch +
", stageFlags=" + stageFlags +
", secrets=" + secrets +
", initInfobaseOptions=" + initInfobaseOptions +
@ -60,4 +71,15 @@ class JobConfiguration implements Serializable {
", logosConfig=" + logosConfig +
'}';
}
boolean infobaseFromFiles(){
IStepExecutor steps = ContextRegistry.getContext().getStepExecutor()
def env = steps.env();
String branchName = env.BRANCH_NAME;
def initMethod = initInfobaseOptions.initMethod
return sourceFormat == SourceFormat.EDT ||
(initMethod == InitInfobaseMethod.FROM_SOURCE) ||
(initMethod == InitInfobaseMethod.DEFAULT_BRANCH_FROM_STORAGE && branchName != defaultBranch)
}
}

View File

@ -7,6 +7,8 @@ import com.fasterxml.jackson.annotation.JsonPropertyDescription
@JsonIgnoreProperties(ignoreUnknown = true)
class Secrets implements Serializable {
public static final String UNKNOWN_ID = "UNKNOWN_ID"
@JsonPropertyDescription("Путь к хранилищу конфигурации")
String storagePath

View File

@ -0,0 +1,12 @@
package ru.pulsar.jenkins.library.configuration
import com.fasterxml.jackson.annotation.JsonProperty
enum SourceFormat {
@JsonProperty("edt")
EDT,
@JsonProperty("designer")
DESIGNER
}

View File

@ -20,6 +20,12 @@ class SyntaxCheckOptions implements Serializable {
@JsonPropertyDescription("Режимы проверки конфигурации")
String[] checkModes
@JsonPropertyDescription("""Путь к файлу с указанием пропускаемых ошибок.
Формат файла: в каждой строке файла указан текст пропускаемого исключения или его часть
Кодировка: UTF-8
""")
String exceptionFile = "./tools/syntax-check-exception-file.txt"
@JsonPropertyDescription("""Путь к конфигурационному файлу vanessa-runner.
По умолчанию содержит значение "./tools/vrunner.json".
""")

View File

@ -4,6 +4,7 @@ import ru.pulsar.jenkins.library.IStepExecutor
import ru.pulsar.jenkins.library.configuration.JobConfiguration
import ru.pulsar.jenkins.library.ioc.ContextRegistry
import ru.pulsar.jenkins.library.utils.Logger
import ru.pulsar.jenkins.library.utils.VRunner
class Bdd implements Serializable {
@ -29,16 +30,11 @@ class Bdd implements Serializable {
steps.createDir('build/out')
// TODO: удалить после выхода VAS 1.0.35
steps.httpRequest(
'https://cloud.svc.pulsar.ru/index.php/s/WKwmqpFXSjfYjAH/download',
'oscript_modules/vanessa-automation-single/vanessa-automation-single.epf'
)
steps.catchError {
config.bddOptions.vrunnerSteps.each {
Logger.println("Шаг запуска сценариев командой ${it}")
steps.cmd("oscript_modules/bin/vrunner ${it} --ibconnection \"/F./build/ib\"")
String vrunnerPath = VRunner.getVRunnerPath();
VRunner.exec("$vrunnerPath ${it} --ibconnection \"/F./build/ib\"")
}
}
}

View File

@ -6,7 +6,7 @@ import ru.pulsar.jenkins.library.configuration.JobConfiguration
import ru.pulsar.jenkins.library.ioc.ContextRegistry
import ru.pulsar.jenkins.library.utils.Logger
class EdtTransform implements Serializable {
class DesignerToEdtFormatTransformation implements Serializable {
public static final String PROJECT_NAME = 'temp'
public static final String WORKSPACE = 'build/edt-workspace'
@ -15,7 +15,7 @@ class EdtTransform implements Serializable {
private final JobConfiguration config;
EdtTransform(JobConfiguration config) {
DesignerToEdtFormatTransformation(JobConfiguration config) {
this.config = config
}
@ -34,7 +34,7 @@ class EdtTransform implements Serializable {
def workspaceDir = "$env.WORKSPACE/$WORKSPACE"
def configurationRoot = new File(env.WORKSPACE, config.srcDir).getAbsolutePath()
steps.createDir(workspaceDir)
steps.deleteDir(workspaceDir)
Logger.println("Конвертация исходников из формата конфигуратора в формат EDT")

View File

@ -0,0 +1,57 @@
package ru.pulsar.jenkins.library.steps
import ru.pulsar.jenkins.library.IStepExecutor
import ru.pulsar.jenkins.library.configuration.JobConfiguration
import ru.pulsar.jenkins.library.configuration.SourceFormat
import ru.pulsar.jenkins.library.ioc.ContextRegistry
import ru.pulsar.jenkins.library.utils.Constants
import ru.pulsar.jenkins.library.utils.Logger
class EdtToDesignerFormatTransformation implements Serializable {
public static final String WORKSPACE = 'build/edt-workspace'
public static final String CONFIGURATION_DIR = 'build/cfg'
public static final String CONFIGURATION_ZIP = 'build/cfg.zip'
public static final String CONFIGURATION_ZIP_STASH = 'cfg-zip'
private final JobConfiguration config;
EdtToDesignerFormatTransformation(JobConfiguration config) {
this.config = config
}
def run() {
IStepExecutor steps = ContextRegistry.getContext().getStepExecutor()
Logger.printLocation()
if (config.sourceFormat != SourceFormat.EDT) {
Logger.println("SRC is not in EDT format. No transform is needed.")
return
}
def env = steps.env();
def srcDir = config.srcDir
def projectDir = new File("$env.WORKSPACE/$srcDir").getCanonicalPath()
def workspaceDir = "$env.WORKSPACE/$WORKSPACE"
def configurationRoot = "$env.WORKSPACE/$CONFIGURATION_DIR"
steps.deleteDir(workspaceDir)
steps.deleteDir(configurationRoot)
Logger.println("Конвертация исходников из формата EDT в формат Конфигуратора")
def ringCommand = "ring edt workspace export --workspace-location '$workspaceDir' --project '$projectDir' --configuration-files '$configurationRoot'"
def ringOpts =[Constants.DEFAULT_RING_OPTS]
steps.withEnv(ringOpts) {
steps.cmd(ringCommand)
}
steps.zip(CONFIGURATION_DIR, CONFIGURATION_ZIP)
steps.stash(CONFIGURATION_ZIP_STASH, CONFIGURATION_ZIP)
}
}

View File

@ -2,6 +2,7 @@ package ru.pulsar.jenkins.library.steps
import ru.pulsar.jenkins.library.IStepExecutor
import ru.pulsar.jenkins.library.configuration.JobConfiguration
import ru.pulsar.jenkins.library.configuration.SourceFormat
import ru.pulsar.jenkins.library.ioc.ContextRegistry
import ru.pulsar.jenkins.library.utils.Logger
@ -26,26 +27,34 @@ class EdtValidate implements Serializable {
return
}
steps.unstash(EdtTransform.WORKSPACE_ZIP_STASH)
steps.unzip(EdtTransform.WORKSPACE, EdtTransform.WORKSPACE_ZIP)
def env = steps.env();
def resultFile = "$env.WORKSPACE/$RESULT_FILE"
def workspaceLocation = "$env.WORKSPACE/$EdtTransform.WORKSPACE"
String workspaceLocation = "$env.WORKSPACE/$DesignerToEdtFormatTransformation.WORKSPACE"
String projectList;
steps.createDir(new File(resultFile).getParent())
if (config.sourceFormat == SourceFormat.DESIGNER) {
steps.unstash(DesignerToEdtFormatTransformation.WORKSPACE_ZIP_STASH)
steps.unzip(DesignerToEdtFormatTransformation.WORKSPACE, DesignerToEdtFormatTransformation.WORKSPACE_ZIP)
projectList = "--project-name-list $DesignerToEdtFormatTransformation.PROJECT_NAME"
} else {
String projectDir = new File("$env.WORKSPACE/$config.srcDir").getCanonicalPath()
projectList = "--project-list '$projectDir'"
}
def resultFile = "$env.WORKSPACE/$RESULT_FILE"
Logger.println("Выполнение валидации EDT")
def ringCommand = "ring edt workspace validate --workspace-location '$workspaceLocation' --file '$resultFile' --project-name-list $EdtTransform.PROJECT_NAME"
def ringCommand = "ring edt workspace validate --workspace-location '$workspaceLocation' --file '$resultFile' $projectList"
def ringOpts = ['RING_OPTS=-Dfile.encoding=UTF-8 -Dosgi.nl=ru -Duser.language=ru']
steps.withEnv(ringOpts) {
steps.catchError {
steps.cmd(ringCommand)
}
}
steps.archiveArtifacts("$EdtTransform.WORKSPACE/.metadata/.log")
steps.archiveArtifacts("$DesignerToEdtFormatTransformation.WORKSPACE/.metadata/.log")
steps.archiveArtifacts(RESULT_FILE)
steps.stash(RESULT_STASH, RESULT_FILE)
}

View File

@ -0,0 +1,49 @@
package ru.pulsar.jenkins.library.steps
import ru.pulsar.jenkins.library.IStepExecutor
import ru.pulsar.jenkins.library.configuration.JobConfiguration
import ru.pulsar.jenkins.library.configuration.SourceFormat
import ru.pulsar.jenkins.library.ioc.ContextRegistry
import ru.pulsar.jenkins.library.utils.Logger
import ru.pulsar.jenkins.library.utils.VRunner
class InitFromFiles implements Serializable {
private final JobConfiguration config;
InitFromFiles(JobConfiguration config) {
this.config = config
}
def run() {
IStepExecutor steps = ContextRegistry.getContext().getStepExecutor()
Logger.printLocation()
if (!config.infobaseFromFiles()) {
Logger.println("init infoBase from files is disabled")
return
}
steps.installLocalDependencies();
Logger.println("Распаковка файлов")
String srcDir;
if (config.sourceFormat == SourceFormat.EDT) {
def env = steps.env();
srcDir = "$env.WORKSPACE/$EdtToDesignerFormatTransformation.CONFIGURATION_DIR"
steps.unstash(EdtToDesignerFormatTransformation.CONFIGURATION_ZIP_STASH)
steps.unzip(srcDir, EdtToDesignerFormatTransformation.CONFIGURATION_ZIP)
} else {
srcDir = config.srcDir;
}
Logger.println("Выполнение загрузки конфигурации из файлов")
String vrunnerPath = VRunner.getVRunnerPath();
def initCommand = "$vrunnerPath init-dev --src $srcDir --ibconnection \"/F./build/ib\""
VRunner.exec(initCommand)
}
}

View File

@ -0,0 +1,74 @@
package ru.pulsar.jenkins.library.steps
import com.cloudbees.groovy.cps.NonCPS
import org.jenkinsci.plugins.workflow.support.actions.EnvironmentAction
import ru.pulsar.jenkins.library.IStepExecutor
import ru.pulsar.jenkins.library.configuration.JobConfiguration
import ru.pulsar.jenkins.library.configuration.Secrets
import ru.pulsar.jenkins.library.ioc.ContextRegistry
import ru.pulsar.jenkins.library.utils.Logger
import ru.pulsar.jenkins.library.utils.VRunner
import ru.pulsar.jenkins.library.utils.VersionParser
import static ru.pulsar.jenkins.library.configuration.Secrets.UNKNOWN_ID
class InitFromStorage implements Serializable {
final static REPO_SLUG_REGEXP = ~/(?m)^(?:[^:\/?#\n]+:)?(?:\/+[^\/?#\n]*)?\/?([^?\n]*)/
private final JobConfiguration config
InitFromStorage(JobConfiguration config) {
this.config = config
}
def run() {
IStepExecutor steps = ContextRegistry.getContext().getStepExecutor()
Logger.printLocation()
if (config.infobaseFromFiles()) {
Logger.println("init infoBase from storage is disabled")
return
}
steps.installLocalDependencies()
String storageVersion = VersionParser.storage()
String storageVersionParameter = storageVersion == "" ? "" : "--storage-ver $storageVersion"
EnvironmentAction env = steps.env();
String repoSlug = computeRepoSlug(env.GIT_URL)
Secrets secrets = config.secrets
String storageCredentials = secrets.storage == UNKNOWN_ID ? repoSlug + "_STORAGE_USER" : secrets.storage
String storagePath = secrets.storagePath == UNKNOWN_ID ? repoSlug + "_STORAGE_PATH" : secrets.storagePath
steps.withCredentials([
steps.usernamePassword(
storageCredentials,
'RUNNER_STORAGE_USER',
'RUNNER_STORAGE_PWD'
),
steps.string(
storagePath,
'RUNNER_STORAGE_NAME'
)
]) {
String vrunnerPath = VRunner.getVRunnerPath()
VRunner.exec "$vrunnerPath init-dev --storage $storageVersionParameter --ibconnection \"/F./build/ib\""
}
}
@NonCPS
private static String computeRepoSlug(String text) {
def matcher = text =~ REPO_SLUG_REGEXP
String repoSlug = matcher != null && matcher.getCount() == 1 ? matcher[0][1] : ""
if (repoSlug.endsWith(".git")) {
repoSlug = repoSlug[0..-5]
}
repoSlug = repoSlug.replace('/', '_')
return repoSlug
}
}

View File

@ -5,6 +5,7 @@ import ru.pulsar.jenkins.library.IStepExecutor
import ru.pulsar.jenkins.library.configuration.JobConfiguration
import ru.pulsar.jenkins.library.ioc.ContextRegistry
import ru.pulsar.jenkins.library.utils.Logger
import ru.pulsar.jenkins.library.utils.VRunner
class InitInfobase implements Serializable {
@ -26,21 +27,17 @@ class InitInfobase implements Serializable {
return
}
// TODO: удалить после выхода VAS 1.0.35
steps.httpRequest(
'https://cloud.svc.pulsar.ru/index.php/s/WKwmqpFXSjfYjAH/download',
'oscript_modules/vanessa-automation-single/vanessa-automation-single.epf'
)
List<String> logosConfig = ["LOGOS_CONFIG=$config.logosConfig"]
steps.withEnv(logosConfig) {
String vrunnerPath = VRunner.getVRunnerPath();
if (config.initInfobaseOptions.runMigration) {
Logger.println("Запуск миграции ИБ")
// Запуск миграции
steps.catchError {
steps.cmd('oscript_modules/bin/vrunner run --command "ЗапуститьОбновлениеИнформационнойБазы;ЗавершитьРаботуСистемы;" --execute \\$runnerRoot/epf/ЗакрытьПредприятие.epf --ibconnection "/F./build/ib"')
VRunner.exec(vrunnerPath + ' run --command "ЗапуститьОбновлениеИнформационнойБазы;ЗавершитьРаботуСистемы;" --execute \\$runnerRoot/epf/ЗакрытьПредприятие.epf --ibconnection "/F./build/ib"')
}
} else {
Logger.println("Шаг миграции ИБ выключен")
@ -52,12 +49,12 @@ class InitInfobase implements Serializable {
files = files.sort new OrderBy( { it.name })
files.each {
Logger.println("Первичная инициализация файлом ${it.path}")
steps.cmd("oscript_modules/bin/vrunner vanessa --settings ${it.path} --ibconnection \"/F./build/ib\"")
VRunner.exec("$vrunnerPath vanessa --settings ${it.path} --ibconnection \"/F./build/ib\"")
}
} else {
config.initInfobaseOptions.additionalInitializationSteps.each {
Logger.println("Первичная инициализация командой ${it}")
steps.cmd("oscript_modules/bin/vrunner ${it} --ibconnection \"/F./build/ib\"")
VRunner.exec("$vrunnerPath ${it} --ibconnection \"/F./build/ib\"")
}
}
}

View File

@ -2,9 +2,12 @@ package ru.pulsar.jenkins.library.steps
import ru.pulsar.jenkins.library.IStepExecutor
import ru.pulsar.jenkins.library.configuration.JobConfiguration
import ru.pulsar.jenkins.library.configuration.SourceFormat
import ru.pulsar.jenkins.library.ioc.ContextRegistry
import ru.pulsar.jenkins.library.utils.Logger
import java.nio.file.Paths
class ResultsTransformer implements Serializable {
public static final String RESULT_STASH = 'edt-generic-issue'
@ -35,11 +38,12 @@ class ResultsTransformer implements Serializable {
def edtValidateFile = "$env.WORKSPACE/$EdtValidate.RESULT_FILE"
def genericIssueFile = "$env.WORKSPACE/$RESULT_FILE"
steps.cmd("stebi convert $edtValidateFile $genericIssueFile $config.srcDir")
String srcDir = config.sourceFormat == SourceFormat.DESIGNER ? config.srcDir : Paths.get(config.srcDir, "src")
steps.cmd("stebi convert $edtValidateFile $genericIssueFile $srcDir")
if (config.resultsTransformOptions.removeSupport) {
def supportLevel = config.resultsTransformOptions.supportLevel
steps.cmd("stebi transform --remove_support $supportLevel --src $config.srcDir $genericIssueFile")
steps.cmd("stebi transform --remove_support $supportLevel --src $srcDir $genericIssueFile")
}
steps.archiveArtifacts(RESULT_FILE)

View File

@ -2,6 +2,7 @@ package ru.pulsar.jenkins.library.steps
import ru.pulsar.jenkins.library.IStepExecutor
import ru.pulsar.jenkins.library.configuration.JobConfiguration
import ru.pulsar.jenkins.library.configuration.SourceFormat
import ru.pulsar.jenkins.library.ioc.ContextRegistry
import ru.pulsar.jenkins.library.utils.Logger
import ru.pulsar.jenkins.library.utils.VersionParser
@ -13,7 +14,11 @@ class SonarScanner implements Serializable {
SonarScanner(JobConfiguration config) {
this.config = config
this.rootFile = "$config.srcDir/Configuration.xml"
if (config.sourceFormat == SourceFormat.EDT){
this.rootFile = "$config.srcDir/src/Configuration/Configuration.mdo"
} else {
this.rootFile = "$config.srcDir/Configuration.xml"
}
}
def run() {
@ -39,7 +44,13 @@ class SonarScanner implements Serializable {
String sonarCommand = "$sonarScannerBinary -Dsonar.branch.name=$env.BRANCH_NAME"
String configurationVersion = VersionParser.configuration(rootFile)
String configurationVersion
if (config.sourceFormat == SourceFormat.EDT) {
configurationVersion = VersionParser.edt(rootFile)
} else {
configurationVersion = VersionParser.configuration(rootFile)
}
if (configurationVersion) {
sonarCommand += " -Dsonar.projectVersion=$configurationVersion"
}

View File

@ -0,0 +1,6 @@
package ru.pulsar.jenkins.library.utils
final class Constants {
public static final String DEFAULT_RING_OPTS = "RING_OPTS=-Dfile.encoding=UTF-8 -Dosgi.nl=ru -Duser.language=ru"
}

View File

@ -0,0 +1,30 @@
package ru.pulsar.jenkins.library.utils
import ru.pulsar.jenkins.library.IStepExecutor
import ru.pulsar.jenkins.library.ioc.ContextRegistry
class VRunner {
static final String DEFAULT_VRUNNER_OPTS = "RUNNER_NOCACHEUSE=1"
static String getVRunnerPath() {
IStepExecutor steps = ContextRegistry.getContext().getStepExecutor()
String vrunnerBinary = steps.isUnix() ? "vrunner" : "vrunner.bat";
String vrunnerPath = "oscript_modules/bin/$vrunnerBinary";
if (!steps.fileExists(vrunnerPath)) {
vrunnerPath = vrunnerBinary;
}
return vrunnerPath;
}
static int exec(String command, boolean returnStatus = false) {
IStepExecutor steps = ContextRegistry.getContext().getStepExecutor()
steps.withEnv([DEFAULT_VRUNNER_OPTS]) {
return steps.cmd(command, returnStatus)
} as int
}
}

View File

@ -10,18 +10,26 @@ class VersionParser implements Serializable {
final static VERSION_REGEXP = ~/(?i)<version>(.*)<\/version>/
static String configuration(rootFile = 'src/cf/Configuration.xml') {
return extractVersionFromFile(rootFile, VERSION_REGEXP)
}
IStepExecutor steps = ContextRegistry.getContext().getStepExecutor()
def configurationText = steps.readFile(rootFile, 'UTF-8');
return version(configurationText, VERSION_REGEXP)
static String edt(rootFile = 'src/Configuration/Configuration.mdo') {
return extractVersionFromFile(rootFile, VERSION_REGEXP)
}
static String storage(versionFile = 'src/cf/VERSION') {
return extractVersionFromFile(versionFile, VERSION_REGEXP)
}
private static String extractVersionFromFile(String filePath, Pattern regexp) {
IStepExecutor steps = ContextRegistry.getContext().getStepExecutor()
def storageVersionText = steps.readFile(versionFile, 'UTF-8')
return version(storageVersionText, VERSION_REGEXP)
if (!steps.fileExists(filePath)) {
return ""
}
def configurationText = steps.readFile(filePath, 'UTF-8');
return version(configurationText, regexp)
}
@NonCPS

View File

@ -73,5 +73,6 @@ class jobConfigurationTest {
def run = rule.buildAndAssertSuccess(workflowJob)
rule.assertLogContains("v8version='8.3.12.1500'", run)
rule.assertLogContains("sonarScannerToolName='sonar-scanner'", run)
rule.assertLogContains("initMethod=FROM_SOURCE", run)
}
}

View File

@ -1,3 +1,6 @@
{
"v8version": "8.3.12.1500"
"v8version": "8.3.12.1500",
"initInfobase": {
"initMethod": "fromSource"
}
}

View File

@ -34,6 +34,11 @@ public class TestUtils {
return FileUtils.readFileToString(new File(file), encoding);
});
when(steps.fileExists(anyString())).thenAnswer(invocation -> {
String file = invocation.getArgument(0);
return new File(file).exists();
});
return steps;
}

View File

@ -1,10 +1,10 @@
import ru.pulsar.jenkins.library.configuration.JobConfiguration
import ru.pulsar.jenkins.library.ioc.ContextRegistry
import ru.pulsar.jenkins.library.steps.EdtTransform
import ru.pulsar.jenkins.library.steps.DesignerToEdtFormatTransformation
def call(JobConfiguration config) {
ContextRegistry.registerDefaultContext(this)
def edtTransform = new EdtTransform(config)
def edtTransform = new DesignerToEdtFormatTransformation(config)
edtTransform.run()
}

View File

@ -0,0 +1,10 @@
import ru.pulsar.jenkins.library.configuration.JobConfiguration
import ru.pulsar.jenkins.library.ioc.ContextRegistry
import ru.pulsar.jenkins.library.steps.EdtToDesignerFormatTransformation
def call(JobConfiguration config) {
ContextRegistry.registerDefaultContext(this)
def edtBackTransform = new EdtToDesignerFormatTransformation(config)
edtBackTransform.run()
}

10
vars/initFromFiles.groovy Normal file
View File

@ -0,0 +1,10 @@
import ru.pulsar.jenkins.library.configuration.JobConfiguration
import ru.pulsar.jenkins.library.ioc.ContextRegistry
import ru.pulsar.jenkins.library.steps.InitFromFiles
def call(JobConfiguration config) {
ContextRegistry.registerDefaultContext(this)
def initFromFiles = new InitFromFiles(config)
initFromFiles.run()
}

View File

@ -1,22 +1,10 @@
import ru.pulsar.jenkins.library.configuration.JobConfiguration
import ru.pulsar.jenkins.library.utils.VersionParser
import ru.pulsar.jenkins.library.ioc.ContextRegistry
import ru.pulsar.jenkins.library.steps.InitFromStorage
def call(JobConfiguration jobConfiguration) {
def call(JobConfiguration config) {
ContextRegistry.registerDefaultContext(this)
def storageVersion = VersionParser.storage()
def storageVersionParameter = storageVersion == "" ? "" : "--storage-ver $storageVersion"
withCredentials([
usernamePassword(
credentialsId: jobConfiguration.secrets.storage,
passwordVariable: 'STORAGE_PSW',
usernameVariable: 'STORAGE_USR'
),
string(
credentialsId: jobConfiguration.secrets.storagePath,
variable: 'STORAGE_PATH'
)
]) {
cmd "oscript_modules/bin/vrunner init-dev --storage --storage-name $STORAGE_PATH --storage-user $STORAGE_USR --storage-pwd $STORAGE_PSW $storageVersionParameter --ibconnection \"/F./build/ib\""
}
def initFromStorage = new InitFromStorage(config)
initFromStorage.run()
}

View File

@ -1,3 +1,10 @@
import ru.pulsar.jenkins.library.utils.Logger
def call() {
if (!fileExists("packagedef")) {
return
}
Logger.println("Установка локальных зависимостей OneScript")
cmd("opm install -l")
}

View File

@ -1,6 +1,7 @@
/* groovylint-disable NestedBlockDepth */
import groovy.transform.Field
import ru.pulsar.jenkins.library.configuration.JobConfiguration
import ru.pulsar.jenkins.library.configuration.SourceFormat
import java.util.concurrent.TimeUnit
@ -49,16 +50,33 @@ void call() {
}
stages {
stage('Трансформация из формата EDT') {
agent {
label 'edt'
}
when {
beforeAgent true
expression { config.stageFlags.needInfobase() && config.sourceFormat == SourceFormat.EDT }
}
steps {
edtToDesignerFormatTransformation config
}
}
stage('Создание ИБ') {
steps {
printLocation()
installLocalDependencies()
createDir('build/out')
// Создание базы загрузкой конфигурации из хранилища
initFromStorage config
script {
if (config.infobaseFromFiles()){
// Создание базы загрузкой из файлов
initFromFiles config
}
else{
// Создание базы загрузкой конфигурации из хранилища
initFromStorage config
}
}
}
}
@ -91,10 +109,10 @@ void call() {
}
when {
beforeAgent true
expression { config.stageFlags.edtValidate }
expression { config.sourceFormat == SourceFormat.DESIGNER && config.stageFlags.edtValidate}
}
steps {
edtTransform config
designerToEdtFormatTransformation config
}
}
}
@ -103,15 +121,28 @@ void call() {
stage('Проверка качества') {
parallel {
stage('EDT контроль') {
agent {
label 'edt'
}
when {
beforeAgent true
expression { config.stageFlags.edtValidate }
}
steps {
edtValidate config
stages {
stage('Валидация EDT') {
agent {
label 'edt'
}
steps {
edtValidate config
}
}
stage('Трансформация результатов') {
agent {
label 'oscript'
}
steps {
transform config
}
}
}
}
@ -158,19 +189,6 @@ void call() {
}
}
stage('Трансформация результатов') {
agent {
label 'oscript'
}
when {
beforeAgent true
expression { config.stageFlags.edtValidate }
}
steps {
transform config
}
}
stage('SonarQube') {
agent {
label 'sonar'

View File

@ -2,6 +2,7 @@ import hudson.FilePath
import ru.pulsar.jenkins.library.configuration.JobConfiguration
import ru.pulsar.jenkins.library.ioc.ContextRegistry
import ru.pulsar.jenkins.library.utils.FileUtils
import ru.pulsar.jenkins.library.utils.VRunner
def call(JobConfiguration config) {
@ -27,7 +28,8 @@ def call(JobConfiguration config) {
String outPath = pathToJUnitReport.getParent()
createDir(outPath)
String command = 'oscript_modules/bin/vrunner syntax-check --ibconnection "/F./build/ib"'
String vrunnerPath = VRunner.getVRunnerPath();
String command = "$vrunnerPath syntax-check --ibconnection \"/F./build/ib\""
// Временно убрал передачу параметра.
// См. https://github.com/vanessa-opensource/vanessa-runner/issues/361
@ -49,8 +51,12 @@ def call(JobConfiguration config) {
command += " --mode $checkModes"
}
if (!options.exceptionFile.empty && fileExists(options.exceptionFile)) {
command += " --exception-file $options.exceptionFile"
}
// Запуск синтакс-проверки
cmd(command, true)
VRunner.exec(command, true)
junit allowEmptyResults: true, testResults: FileUtils.getLocalPath(pathToJUnitReport)