diff --git a/README.md b/README.md index 450818e3..51bceea3 100644 --- a/README.md +++ b/README.md @@ -46,12 +46,12 @@ 1. Запуск юнит-тестов с помощью фреймворка YAXUnit с сохранением результатов в формате jUnit и Allure. 1. Запуск синтаксического контроля средствами конфигуратора и сохранение результатов в виде отчета jUnit. 1. Валидация проекта средствами EDT и трансформация отчета EDT в формат BSL LS с помощью `edt-ripper` или Generic Issue с помощью `stebi`. - 1. Запуск статического анализа для SonarQube. 1. Публикация результатов junit и Allure в интерфейс Jenkins. 1. Рассылка результатов сборки на почту и в Telegram. 1. Конфигурирование логгера запускаемых oscript-приложений. 1. Замер покрытия при выполнении тестов. +1. Возможность сохранить информационную базу в виде артефакта сборки после выполнения шагов инициализации и\или после выполнения сценарных тестов. ## Подключение @@ -168,6 +168,7 @@ pipeline1C() * Если разработка ведется с использованием подсистемы [БСП "Обновление версии ИБ"](https://its.1c.ru/db/bsp315doc#content:4:1:issogl1_обновление_версии_иб), то в значение параметра `sonar.projectVersion=$configurationVersion` утилиты `sonar-scanner` можно передавать версию из созданного общего модуля. Для этого необходимо заполнить параметр (`sonarqube` -> `infoBaseUpdateModuleName`). Если параметр не заполнен, версия передается из корня конфигурации. * По умолчанию шаг анализа не дожидается окончания фонового задания на сервере SonarQube и не анализирует результат прохождения Порога качества (`sonarqube` -> `waitForQualityGate`). * Если выполнялась валидация EDT, результаты валидации передаются утилите `sonar-scanner` как значение параметра `sonar.externalIssuesReportPaths` при использовании `stebi` или как значение параметра `sonar.bsl.languageserver.reportPaths` при использовании `edt-ripper`. +* Информационная база по умолчанию не сохраняется в виде артефакта сборки. * Рассылка уведомлений: * Электронная почта: * Для отправки используется плагин [`email-ext`](https://plugins.jenkins.io/email-ext). Шаблоны сообщений конфигурируются в настройках плагина. @@ -312,3 +313,12 @@ jobConfiguration.json * При изменении портов отладки в jobConfiguration.json не забывайте менять порты в настройках соответствующих шагов (и наоборот) * Настоятельно рекомендуется использовать не "постоянные" агенты Jenkins, а контейнеры docker. При выполнении билдов в контейнерах можно использовать исключительно стандартный порт 1550. + +## Сохранение ИБ в виде артефакта сборки + +Параметры `initInfobase` -> `archiveInfobase` и `bdd` -> `archiveInfobase` отвечают за сохранение информационной базы в виде артефакта сборки после выполнения соответствующих этапов. +Можно управлять тем, при каких статусах сборки ИБ будет сохранена, см. `onAlways`, `onFailure`, `onUnstable`, `onSuccess`. + +Имя файла формируется следующим образом: +* для шага `initInfoBase`: '1Cv8.1CD.zip' +* для шага `bdd`: '1Cv8.1CD.bdd.zip' diff --git a/resources/globalConfiguration.json b/resources/globalConfiguration.json index 82688c94..5c5fdc7d 100644 --- a/resources/globalConfiguration.json +++ b/resources/globalConfiguration.json @@ -45,12 +45,24 @@ "additionalInitializationSteps": [], "templateDBPath": "", "vrunnerSettings": "./tools/vrunner.json", + "archiveInfobase": { + "onAlways": false, + "onFailure": false, + "onUnstable": false, + "onSuccess": false + }, "extensions": [] }, "bdd": { "vrunnerSteps": [ "vanessa --settings ./tools/vrunner.json" ], + "archiveInfobase": { + "onAlways": false, + "onFailure": false, + "onUnstable": false, + "onSuccess": false + }, "coverage": false, "dbgsPort": 1550 }, @@ -58,7 +70,7 @@ "sonarQubeInstallation": "", "useSonarScannerFromPath": true, "sonarScannerToolName": "sonar-scanner", - "infoBaseUpdateModuleName" : "", + "infoBaseUpdateModuleName": "", "branchAnalysisConfiguration": "fromEnv", "waitForQualityGate": false }, diff --git a/resources/schema.json b/resources/schema.json index cae865f1..ab935556 100644 --- a/resources/schema.json +++ b/resources/schema.json @@ -1,6 +1,27 @@ { "$schema" : "http://json-schema.org/draft-07/schema#", "definitions" : { + "ArchiveInfobaseOptions" : { + "type" : "object", + "properties" : { + "onAlways" : { + "type" : "boolean", + "description" : "Сохранять всегда" + }, + "onFailure" : { + "type" : "boolean", + "description" : "Сохранять при падении сборки" + }, + "onSuccess" : { + "type" : "boolean", + "description" : "Сохранять при успешной сборке" + }, + "onUnstable" : { + "type" : "boolean", + "description" : "Сохранять при нестабильной сборке" + } + } + }, "EmailExtConfiguration" : { "type" : "object", "properties" : { @@ -28,6 +49,13 @@ "bdd" : { "type" : "object", "properties" : { + "archiveInfobase" : { + "allOf" : [ { + "$ref" : "#/definitions/ArchiveInfobaseOptions" + }, { + "description" : "Настройки сохранения базы после выполнения всех шагов\n " + } ] + }, "coverage" : { "type" : "boolean", "description" : "Выполнять замер покрытия", @@ -80,6 +108,13 @@ "type" : "string" } }, + "archiveInfobase" : { + "allOf" : [ { + "$ref" : "#/definitions/ArchiveInfobaseOptions" + }, { + "description" : "Настройки сохранения базы после выполнения всех шагов\n " + } ] + }, "extensions" : { "description" : "Массив расширений для загрузки в конфигурацию.", "type" : "array", @@ -381,12 +416,12 @@ "publishToAllureReport" : { "type" : "boolean", "description" : "Выполнять публикацию результатов в отчет Allure.\n По умолчанию выключено.\n ", - "default": false + "default" : false }, "publishToJUnitReport" : { "type" : "boolean", "description" : "Выполнять публикацию результатов в отчет JUnit.\n По умолчанию включено.\n ", - "default": true + "default" : true }, "vrunnerSettings" : { "type" : "string", diff --git a/src/JobConfigurationSchemaGenerator.java b/src/JobConfigurationSchemaGenerator.java index 8a7246e8..4fa0be19 100644 --- a/src/JobConfigurationSchemaGenerator.java +++ b/src/JobConfigurationSchemaGenerator.java @@ -33,7 +33,8 @@ public static void main(String[] args) { writer.write(jsonSchema.toPrettyString()); System.out.println(jsonSchema.toPrettyString()); } catch (IOException e) { - e.printStackTrace(); + //noinspection CallToPrintStackTrace + e.printStackTrace(); } } diff --git a/src/ru/pulsar/jenkins/library/IStepExecutor.groovy b/src/ru/pulsar/jenkins/library/IStepExecutor.groovy index 8a2f6e8c..9fdd8a76 100644 --- a/src/ru/pulsar/jenkins/library/IStepExecutor.groovy +++ b/src/ru/pulsar/jenkins/library/IStepExecutor.groovy @@ -10,6 +10,7 @@ import org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper import ru.pulsar.jenkins.library.configuration.JobConfiguration import ru.pulsar.jenkins.library.configuration.StepCoverageOptions import ru.pulsar.jenkins.library.steps.Coverable +import sp.sd.fileoperations.FileOperation interface IStepExecutor { @@ -34,6 +35,10 @@ interface IStepExecutor { boolean fileExists(String file) + void fileOperations(List fileOperations) + + void fileDeleteOperation(String includes) + void echo(message) def cmd(String script, boolean returnStatus, boolean returnStdout) @@ -89,6 +94,8 @@ interface IStepExecutor { @SuppressWarnings('unused') def zip(String dir, String zipFile, String glob) + def zip(String dir, String zipFile, String glob, boolean archive) + def unzip(String dir, String zipFile) @SuppressWarnings('unused') @@ -120,4 +127,5 @@ interface IStepExecutor { def brokenTestsSuspects() RunWrapper currentBuild() + } \ No newline at end of file diff --git a/src/ru/pulsar/jenkins/library/StepExecutor.groovy b/src/ru/pulsar/jenkins/library/StepExecutor.groovy index d02bbfc9..50372f85 100644 --- a/src/ru/pulsar/jenkins/library/StepExecutor.groovy +++ b/src/ru/pulsar/jenkins/library/StepExecutor.groovy @@ -11,6 +11,7 @@ import ru.pulsar.jenkins.library.configuration.JobConfiguration import ru.pulsar.jenkins.library.configuration.StepCoverageOptions import ru.pulsar.jenkins.library.steps.Coverable import ru.yandex.qatools.allure.jenkins.config.ResultsConfig +import sp.sd.fileoperations.FileOperation class StepExecutor implements IStepExecutor { @@ -55,6 +56,16 @@ class StepExecutor implements IStepExecutor { steps.fileExists file } + @Override + void fileOperations(List fileOperations) { + steps.fileOperations fileOperations + } + + @Override + void fileDeleteOperation(String includes) { + steps.fileDeleteOperation includes: includes, excludes: '', useDefaultExcludes: true + } + @Override FileWrapper[] findFiles(String glob, String excludes = '') { steps.findFiles glob: glob, excludes: excludes @@ -196,6 +207,11 @@ class StepExecutor implements IStepExecutor { steps.zip dir: dir, zipFile: zipFile, glob: glob, overwrite: true } + @Override + def zip(String dir, String zipFile, String glob = '', boolean archive) { + steps.zip dir: dir, zipFile: zipFile, glob: glob, overwrite: true, archive: archive + } + @Override def unzip(String dir, String zipFile, quiet = true) { steps.unzip dir: dir, zipFile: zipFile, quiet: quiet diff --git a/src/ru/pulsar/jenkins/library/configuration/ArchiveInfobaseOptions.groovy b/src/ru/pulsar/jenkins/library/configuration/ArchiveInfobaseOptions.groovy new file mode 100644 index 00000000..00b8da1f --- /dev/null +++ b/src/ru/pulsar/jenkins/library/configuration/ArchiveInfobaseOptions.groovy @@ -0,0 +1,31 @@ +package ru.pulsar.jenkins.library.configuration + +import com.cloudbees.groovy.cps.NonCPS +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonPropertyDescription + +@JsonIgnoreProperties(ignoreUnknown = true) +class ArchiveInfobaseOptions implements Serializable { + + @JsonPropertyDescription("Сохранять всегда") + Boolean onAlways = false + @JsonPropertyDescription("Сохранять при успешной сборке") + Boolean onSuccess = false + @JsonPropertyDescription("Сохранять при падении сборки") + Boolean onFailure = false + @JsonPropertyDescription("Сохранять при нестабильной сборке") + Boolean onUnstable = false + + @Override + @NonCPS + String toString() { + return "ArchiveInfobaseOptions{" + + "onAlways=" + onAlways + + ", onSuccess=" + onSuccess + + ", onFailure=" + onFailure + + ", onUnstable=" + onUnstable + + '}'; + } +} + + diff --git a/src/ru/pulsar/jenkins/library/configuration/BddOptions.groovy b/src/ru/pulsar/jenkins/library/configuration/BddOptions.groovy index d82a1788..0f3687f2 100644 --- a/src/ru/pulsar/jenkins/library/configuration/BddOptions.groovy +++ b/src/ru/pulsar/jenkins/library/configuration/BddOptions.groovy @@ -16,11 +16,16 @@ class BddOptions extends StepCoverageOptions implements Serializable { 'vanessa --settings ./tools/vrunner.json' ] + @JsonPropertyDescription("""Настройки сохранения базы после выполнения всех шагов + """) + ArchiveInfobaseOptions archiveInfobase + @Override @NonCPS String toString() { return "BddOptions{" + "vrunnerSteps=" + vrunnerSteps + + "archiveInfobase=" + archiveInfobase + "coverage=" + coverage + "dbgsPort=" + dbgsPort + '}' diff --git a/src/ru/pulsar/jenkins/library/configuration/ConfigurationReader.groovy b/src/ru/pulsar/jenkins/library/configuration/ConfigurationReader.groovy index 477fbfa3..c113fd70 100644 --- a/src/ru/pulsar/jenkins/library/configuration/ConfigurationReader.groovy +++ b/src/ru/pulsar/jenkins/library/configuration/ConfigurationReader.groovy @@ -69,6 +69,7 @@ class ConfigurationReader implements Serializable { "yaxunitOptions", "syntaxCheckOptions", "resultsTransformOptions", + "archiveInfobase", "notificationsOptions", "emailNotificationOptions", "alwaysEmailOptions", diff --git a/src/ru/pulsar/jenkins/library/configuration/InitInfoBaseOptions.groovy b/src/ru/pulsar/jenkins/library/configuration/InitInfoBaseOptions.groovy index 32f7ca9f..3d8c0e50 100644 --- a/src/ru/pulsar/jenkins/library/configuration/InitInfoBaseOptions.groovy +++ b/src/ru/pulsar/jenkins/library/configuration/InitInfoBaseOptions.groovy @@ -40,6 +40,10 @@ class InitInfoBaseOptions implements Serializable { """) String templateDBPath + @JsonPropertyDescription("""Настройки сохранения базы после выполнения всех шагов + """) + ArchiveInfobaseOptions archiveInfobase + @JsonPropertyDescription("Массив расширений для загрузки в конфигурацию.") Extension[] extensions @@ -80,6 +84,7 @@ class InitInfoBaseOptions implements Serializable { ", vrunnerSettings=" + vrunnerSettings + ", templateDBPath=" + templateDBPath + ", additionalInitializationSteps=" + additionalInitializationSteps + + ", archiveInfobase=" + archiveInfobase + ", extensions=" + extensions + '}' } diff --git a/src/ru/pulsar/jenkins/library/steps/Yaxunit.groovy b/src/ru/pulsar/jenkins/library/steps/Yaxunit.groovy index 317059e0..7bffcc1b 100644 --- a/src/ru/pulsar/jenkins/library/steps/Yaxunit.groovy +++ b/src/ru/pulsar/jenkins/library/steps/Yaxunit.groovy @@ -82,7 +82,7 @@ class Yaxunit implements Serializable, Coverable { } if (options.publishToAllureReport) { - String allureReport = "./build/out/allure/yaxunit/junit.xml" + String allureReport = "./build/out/allure/yaxunit/allure.xml" FilePath pathToAllureReport = FileUtils.getFilePath("$env.WORKSPACE/$allureReport") String allureReportDir = FileUtils.getLocalPath(pathToAllureReport.getParent()) diff --git a/src/ru/pulsar/jenkins/library/steps/ZipInfobase.groovy b/src/ru/pulsar/jenkins/library/steps/ZipInfobase.groovy new file mode 100644 index 00000000..26535c15 --- /dev/null +++ b/src/ru/pulsar/jenkins/library/steps/ZipInfobase.groovy @@ -0,0 +1,68 @@ +package ru.pulsar.jenkins.library.steps + +import hudson.model.Result +import ru.pulsar.jenkins.library.IStepExecutor +import ru.pulsar.jenkins.library.configuration.ArchiveInfobaseOptions +import ru.pulsar.jenkins.library.configuration.JobConfiguration +import ru.pulsar.jenkins.library.ioc.ContextRegistry +import ru.pulsar.jenkins.library.utils.Logger + +class ZipInfobase implements Serializable { + + private final JobConfiguration config + private final String stage + + ZipInfobase(JobConfiguration config, String stage) { + this.config = config + this.stage = stage + } + + def run() { + IStepExecutor steps = ContextRegistry.getContext().getStepExecutor() + + Logger.printLocation() + + def currentBuild = steps.currentBuild() + def currentResult = Result.fromString(currentBuild.getCurrentResult()) + + def archiveInfobaseOptions = getArchiveInfobaseOptionsForStage(config, stage) + + def archiveName + if (stage == 'initInfoBase') { + archiveName = "1Cv8.1CD.zip" + } else { + archiveName = "1Cv8.1CD.${stage}.zip" + } + + // опция отвечает только за то, будет ли файл сохранен в виде артефакта + def archiveInfobase = false + if (archiveInfobaseOptions.onAlways + || (archiveInfobaseOptions.onFailure && (currentResult == Result.FAILURE || currentResult == Result.ABORTED)) + || (archiveInfobaseOptions.onUnstable && currentResult == Result.UNSTABLE) + || (archiveInfobaseOptions.onSuccess && currentResult == Result.SUCCESS)) { + archiveInfobase = true + } + + if (steps.fileExists(archiveName)) { + steps.fileOperations([steps.fileDeleteOperation(archiveName)]) + } + steps.zip('build/ib', archiveName, '1Cv8.1CD', archiveInfobase) + steps.stash(archiveName, archiveName, false) + } + + private static ArchiveInfobaseOptions getArchiveInfobaseOptionsForStage(JobConfiguration config, String stageName) { + + def defaultOptions = new ArchiveInfobaseOptions() + if (!stageName) { + return defaultOptions + } + + try { + return config."${stageName}Options".archiveInfobase + } catch(MissingPropertyException | NullPointerException e) { + Logger.println("Ошибка при получении настроек архивации для этапа ${stageName}: ${e.message}") + Logger.println(e.toString()) + return defaultOptions + } + } +} diff --git a/test/unit/groovy/ru/pulsar/jenkins/library/configuration/ConfigurationReaderTest.java b/test/unit/groovy/ru/pulsar/jenkins/library/configuration/ConfigurationReaderTest.java index 07e5c37a..27a56c4c 100644 --- a/test/unit/groovy/ru/pulsar/jenkins/library/configuration/ConfigurationReaderTest.java +++ b/test/unit/groovy/ru/pulsar/jenkins/library/configuration/ConfigurationReaderTest.java @@ -2,7 +2,6 @@ import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import ru.pulsar.jenkins.library.configuration.sonarqube.GenericIssueFormat; import ru.pulsar.jenkins.library.utils.TestUtils; @@ -66,6 +65,7 @@ void testCreateJobConfigurationObject() throws IOException { assertThat(jobConfiguration.getYaxunitOptions().getDbgsPort()).isEqualTo(1550); assertThat(jobConfiguration.getInitInfoBaseOptions().getRunMigration()).isFalse(); + assertThat(jobConfiguration.getInitInfoBaseOptions().getArchiveInfobase().getOnAlways()).isTrue(); assertThat(jobConfiguration.getInitInfoBaseOptions().getAdditionalInitializationSteps()).contains("vanessa --settings ./tools/vrunner.first.json"); assertThat(jobConfiguration.getBddOptions().getVrunnerSteps()).contains("vanessa --settings ./tools/vrunner.json"); diff --git a/test/unit/resources/jobConfiguration.json b/test/unit/resources/jobConfiguration.json index 48dfb6fd..ef17112d 100644 --- a/test/unit/resources/jobConfiguration.json +++ b/test/unit/resources/jobConfiguration.json @@ -48,7 +48,10 @@ "initInfoBase" ] } - ] + ], + "archiveInfobase": { + "onAlways": true + } }, "sonarqube": { "sonarQubeInstallation": "qa", diff --git a/vars/pipeline1C.groovy b/vars/pipeline1C.groovy index fbd54449..70d40138 100644 --- a/vars/pipeline1C.groovy +++ b/vars/pipeline1C.groovy @@ -144,7 +144,7 @@ void call() { timeout(time: config.timeoutOptions.zipInfoBase, unit: TimeUnit.MINUTES) { printLocation() - zipInfobase() + zipInfobase config, 'initInfoBase' } } } @@ -237,6 +237,16 @@ void call() { } } } + + stage('Архивация ИБ') { + steps { + timeout(time: config.timeoutOptions.zipInfoBase, unit: TimeUnit.MINUTES) { + printLocation() + + zipInfobase config, 'bdd' + } + } + } } } diff --git a/vars/zipInfobase.groovy b/vars/zipInfobase.groovy index 14bc94ae..7c6faa1a 100644 --- a/vars/zipInfobase.groovy +++ b/vars/zipInfobase.groovy @@ -1,7 +1,10 @@ -def call() { - if (fileExists('1Cv8.1CD.zip')) { - fileOperations([fileDeleteOperation(includes: '1Cv8.1CD.zip')]) - } - zip dir: 'build/ib', glob: '1Cv8.1CD', zipFile: '1Cv8.1CD.zip' - stash name: "1Cv8.1CD.zip", includes: "1Cv8.1CD.zip", allowEmpty: false +import ru.pulsar.jenkins.library.configuration.JobConfiguration +import ru.pulsar.jenkins.library.ioc.ContextRegistry +import ru.pulsar.jenkins.library.steps.ZipInfobase + +def call(JobConfiguration config, String stageName) { + ContextRegistry.registerDefaultContext(this) + + def zipInfobase = new ZipInfobase(config, stageName) + zipInfobase.run() }