mirror of
https://github.com/firstBitMarksistskaya/jenkins-lib.git
synced 2025-01-05 13:10:28 +02:00
Добавление отправки результатов сборки в почту и телеграм
This commit is contained in:
parent
2822b6c323
commit
25facc91fc
18
README.md
18
README.md
@ -39,6 +39,7 @@
|
||||
1. Запуск валидации проекта средствами EDT и конвертация отчета в формате generic issues.
|
||||
1. Запуск статического анализа для SonarQube.
|
||||
1. Публикация результатов junit и Allure в интерфейс Jenkins.
|
||||
1. Рассылка результатов сборки на почту и в Telegram.
|
||||
1. Конфигурирование логгера запускаемых oscript-приложений.
|
||||
|
||||
## Подключение
|
||||
@ -109,9 +110,11 @@ pipeline1C()
|
||||
* Исходники конфигурации ожидаются в каталоге `src/cf` (`srcDir`).
|
||||
* Формат исходников - выгрузка из Конфигуратора (`sourceFormat`).
|
||||
* Ветка по умолчанию (для комбинированного режима загрузки конфигурации) - "main" (`defaultBranch`).
|
||||
* Имена "секретов" (jenkins credentials, `secrets`) по умолчанию высчитываются из пути к git-репозиторию (без учета домена, с заменой `/` на `_`) с прибавлением ключа секрета. Например, для репозитория https://github.com/firstBitSemenovskaya/jenkins-lib секрет с адресом хранилища будет выглядеть как `firstBitSemenovskaya_jenkins-lib_STORAGE_PATH`. Ключи секретов:
|
||||
* Имена большинства "секретов" (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`).
|
||||
* `TELEGRAM_CHAT_ID` - идентификатор чата Telegram для рассылки уведомлений и результате сборки вида "secret text" (для `secrets` -> `telegramChatId`).
|
||||
* Секрет `TELEGRAM_BOT_TOKEN` задается глобально на весь сервер Jenkins, либо может быть переопределен (`secrets` -> `telegramBotToken`)
|
||||
* Все "шаги" по умолчанию выключены (`stages`).
|
||||
* Если в корне репозитория существует файл `packagedef`, то в шагах, работающих с информационной базой, будет выполнена попытка установки локальных зависимостей средствами `opm`.
|
||||
* Если после установки локальных зависимостей в каталоге `oscript_modules/bin` существует файл `vrunner`, то для выполнения команд работы с информационной базой будет использоваться он, а не глобально установленный `vrunner` из `PATH`.
|
||||
@ -150,3 +153,16 @@ pipeline1C()
|
||||
* Шаг анализа не дожидается окончания фонового задания на сервере SonarQube и не анализирует результат прохождения Порога качества (`sonarqube` -> `waitForQualityGate`).
|
||||
Для этого необходимо заполнить параметр (`sonarqube` -> `infoBaseUpdateModuleName`). Если параметр не заполнен, версия передается из корня конфигурации.
|
||||
* Если выполнялась валидация EDT, результаты валидации в формате `generic issues` передаются утилите `sonar-scanner` как значение параметра `sonar.externalIssuesReportPaths`.
|
||||
* Рассылка уведомлений:
|
||||
* Электронная почта:
|
||||
* Для отправки используется плагин [`email-ext`](https://plugins.jenkins.io/email-ext). Шаблоны сообщений конфигурируются в настройках плагина.
|
||||
* Уведомления о результатах сборки по умолчанию рассылаются только при полном падении сборочной линии (`notifications` -> `email` -> `onAlways`, `onFailure`, `onUnstable`, `onSuccess`).
|
||||
* Лог сборки прикладывается к письму при полном падении сборочной линии и при отправке в режиме "всегда отправлять" (`notifications` -> `email` -> `*options` -> `attachLog`).
|
||||
* В качестве получателей писем (`notifications` -> `email` -> `*options` -> `recipientProviders`) в различных режимах отправки используются:
|
||||
* всегда - разработчики и запустивший сборку;
|
||||
* при падении - разработчики, запустивший сборку и подозреваемый в причине падения сборки;
|
||||
* при успехе - разработчики и запустивший сборку;
|
||||
* при нестабильной сборке (упавшие тесты) - разработчики и запустивший сборку.
|
||||
* Прямые получатели уведомлений не заполнены (`notifications` -> `email` -> `*options` -> `directRecipients`).
|
||||
* Telegram:
|
||||
* Уведомления о результатах сборки по умолчанию рассылаются всегда (`notifications` -> `telegram` -> `onAlways`, `onFailure`, `onUnstable`, `onSuccess`).
|
@ -80,6 +80,7 @@ sharedLibrary {
|
||||
dependency("org.jenkins-ci.plugins", "pipeline-build-step", "2.12")
|
||||
dependency("org.jenkins-ci.plugins", "pipeline-utility-steps", "2.8.0")
|
||||
dependency("org.jenkins-ci.plugins", "git", "4.4.4")
|
||||
dependency("org.jenkins-ci.plugins", "http_request", "1.15")
|
||||
dependency("org.6wind.jenkins", "lockable-resources", "2.7")
|
||||
dependency("ru.yandex.qatools.allure", "allure-jenkins-plugin", "2.28.1")
|
||||
val declarativePluginsVersion = "1.6.0"
|
||||
@ -87,5 +88,6 @@ sharedLibrary {
|
||||
dependency("org.jenkinsci.plugins", "pipeline-model-declarative-agent", "1.1.1")
|
||||
dependency("org.jenkinsci.plugins", "pipeline-model-definition", declarativePluginsVersion)
|
||||
dependency("org.jenkinsci.plugins", "pipeline-model-extensions", declarativePluginsVersion)
|
||||
dependency("io.jenkins.blueocean", "blueocean-pipeline-api-impl", "1.25.3")
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,9 @@
|
||||
"defaultBranch": "main",
|
||||
"secrets": {
|
||||
"storagePath": "UNKNOWN_ID",
|
||||
"storage": "UNKNOWN_ID"
|
||||
"storage": "UNKNOWN_ID",
|
||||
"telegramBotToken": "UNKNOWN_ID",
|
||||
"telegramChatId": "UNKNOWN_ID"
|
||||
},
|
||||
"stages": {
|
||||
"initSteps": false,
|
||||
@ -15,7 +17,9 @@
|
||||
"bdd": false,
|
||||
"syntaxCheck": false,
|
||||
"edtValidate": false,
|
||||
"smoke": false
|
||||
"smoke": false,
|
||||
"email": false,
|
||||
"telegram": false
|
||||
},
|
||||
"timeout": {
|
||||
"smoke": 240,
|
||||
@ -77,5 +81,52 @@
|
||||
"removeSupport": true,
|
||||
"supportLevel": 0
|
||||
},
|
||||
"notifications": {
|
||||
"email": {
|
||||
"onAlways": false,
|
||||
"onFailure": true,
|
||||
"onUnstable": false,
|
||||
"onSuccess": false,
|
||||
"alwaysOptions": {
|
||||
"attachLog": true,
|
||||
"directRecipients": [],
|
||||
"recipientProviders": [
|
||||
"developers",
|
||||
"requestor"
|
||||
]
|
||||
},
|
||||
"failureOptions": {
|
||||
"attachLog": true,
|
||||
"directRecipients": [],
|
||||
"recipientProviders": [
|
||||
"developers",
|
||||
"requestor",
|
||||
"brokenBuildSuspects"
|
||||
]
|
||||
},
|
||||
"successOptions": {
|
||||
"attachLog": false,
|
||||
"directRecipients": [],
|
||||
"recipientProviders": [
|
||||
"developers",
|
||||
"requestor"
|
||||
]
|
||||
},
|
||||
"unstableOptions": {
|
||||
"attachLog": false,
|
||||
"directRecipients": [],
|
||||
"recipientProviders": [
|
||||
"developers",
|
||||
"requestor"
|
||||
]
|
||||
}
|
||||
},
|
||||
"telegram": {
|
||||
"onAlways": true,
|
||||
"onFailure": false,
|
||||
"onUnstable": false,
|
||||
"onSuccess": false
|
||||
}
|
||||
},
|
||||
"logosConfig": ""
|
||||
}
|
||||
|
@ -35,6 +35,14 @@
|
||||
"storage" : {
|
||||
"type" : "string",
|
||||
"description" : "Данные авторизации в хранилище конфигурации"
|
||||
},
|
||||
"telegramChatId" : {
|
||||
"type" : "string",
|
||||
"description" : "Идентификатор telegram-чата для отправки уведомлений"
|
||||
},
|
||||
"telegramBotToken" : {
|
||||
"type" : "string",
|
||||
"description" : "Токен авторизации telegram-бота для отправки уведомлений"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -66,6 +74,14 @@
|
||||
"bdd" : {
|
||||
"type" : "boolean",
|
||||
"description" : "Запуск BDD сценариев включен"
|
||||
},
|
||||
"email" : {
|
||||
"type" : "boolean",
|
||||
"description" : "Выполнять рассылку результатов сборки на email"
|
||||
},
|
||||
"telegram" : {
|
||||
"type" : "boolean",
|
||||
"description" : "Выполнять рассылку результатов сборки в telegram"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -257,6 +273,93 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"notifications" : {
|
||||
"type" : "object",
|
||||
"id" : "urn:jsonschema:ru:pulsar:jenkins:library:configuration:NotificationsOptions",
|
||||
"description" : "Настройки рассылки результатов сборки",
|
||||
"properties" : {
|
||||
"email" : {
|
||||
"type" : "object",
|
||||
"id" : "urn:jsonschema:ru:pulsar:jenkins:library:configuration:notification:EmailNotificationOptions",
|
||||
"description" : "Настройки рассылки результатов сборки через email",
|
||||
"properties" : {
|
||||
"onAlways" : {
|
||||
"type" : "boolean",
|
||||
"description" : "Отправлять всегда"
|
||||
},
|
||||
"onSuccess" : {
|
||||
"type" : "boolean",
|
||||
"description" : "Отправлять при успешной сборке"
|
||||
},
|
||||
"onFailure" : {
|
||||
"type" : "boolean",
|
||||
"description" : "Отправлять при падении сборки"
|
||||
},
|
||||
"onUnstable" : {
|
||||
"type" : "boolean",
|
||||
"description" : "Отправлять при нестабильной сборке"
|
||||
},
|
||||
"alwaysOptions" : {
|
||||
"type" : "object",
|
||||
"id" : "urn:jsonschema:ru:pulsar:jenkins:library:configuration:notification:email:EmailExtConfiguration",
|
||||
"properties" : {
|
||||
"attachLog" : {
|
||||
"type" : "boolean"
|
||||
},
|
||||
"directRecipients" : {
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"recipientProviders" : {
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "string",
|
||||
"enum" : [ "developers", "requestor", "brokenBuildSuspects", "brokenTestsSuspects" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"successOptions" : {
|
||||
"type" : "object",
|
||||
"$ref" : "urn:jsonschema:ru:pulsar:jenkins:library:configuration:notification:email:EmailExtConfiguration"
|
||||
},
|
||||
"failureOptions" : {
|
||||
"type" : "object",
|
||||
"$ref" : "urn:jsonschema:ru:pulsar:jenkins:library:configuration:notification:email:EmailExtConfiguration"
|
||||
},
|
||||
"unstableOptions" : {
|
||||
"type" : "object",
|
||||
"$ref" : "urn:jsonschema:ru:pulsar:jenkins:library:configuration:notification:email:EmailExtConfiguration"
|
||||
}
|
||||
}
|
||||
},
|
||||
"telegram" : {
|
||||
"type" : "object",
|
||||
"id" : "urn:jsonschema:ru:pulsar:jenkins:library:configuration:notification:TelegramNotificationOptions",
|
||||
"description" : "Настройки рассылки результатов сборки через telegram",
|
||||
"properties" : {
|
||||
"onAlways" : {
|
||||
"type" : "boolean",
|
||||
"description" : "Отправлять всегда"
|
||||
},
|
||||
"onSuccess" : {
|
||||
"type" : "boolean",
|
||||
"description" : "Отправлять при успешной сборке"
|
||||
},
|
||||
"onFailure" : {
|
||||
"type" : "boolean",
|
||||
"description" : "Отправлять при падении сборки"
|
||||
},
|
||||
"onUnstable" : {
|
||||
"type" : "boolean",
|
||||
"description" : "Отправлять при нестабильной сборке"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"logosConfig" : {
|
||||
"type" : "string",
|
||||
"description" : "Конфигурация библиотеки logos. Применяется перед запуском каждой стадии сборки"
|
||||
|
@ -1,7 +1,11 @@
|
||||
package ru.pulsar.jenkins.library
|
||||
|
||||
import jenkins.plugins.http_request.HttpMode
|
||||
import jenkins.plugins.http_request.MimeType
|
||||
import jenkins.plugins.http_request.ResponseContentSupplier
|
||||
import org.jenkinsci.plugins.pipeline.utility.steps.fs.FileWrapper
|
||||
import org.jenkinsci.plugins.workflow.support.actions.EnvironmentAction
|
||||
import org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper
|
||||
|
||||
interface IStepExecutor {
|
||||
|
||||
@ -69,7 +73,9 @@ interface IStepExecutor {
|
||||
|
||||
def catchError(Closure body)
|
||||
|
||||
def httpRequest(String url, String outputFile, String responseHandle, boolean wrapAsMultipart)
|
||||
ResponseContentSupplier httpRequest(String url, String outputFile, String responseHandle, boolean wrapAsMultipart)
|
||||
|
||||
ResponseContentSupplier httpRequest(String url, HttpMode httpMode, MimeType contentType, String requestBody, String validResponseCodes, boolean consoleLogResponseBody)
|
||||
|
||||
def error(String errorMessage)
|
||||
|
||||
@ -78,4 +84,16 @@ interface IStepExecutor {
|
||||
def junit(String testResults, boolean allowEmptyResults)
|
||||
|
||||
def installLocalDependencies()
|
||||
|
||||
def emailext(String subject, String body, String to, List recipientProviders, boolean attachLog)
|
||||
|
||||
def developers()
|
||||
|
||||
def requestor()
|
||||
|
||||
def brokenBuildSuspects()
|
||||
|
||||
def brokenTestsSuspects()
|
||||
|
||||
RunWrapper currentBuild()
|
||||
}
|
@ -1,7 +1,11 @@
|
||||
package ru.pulsar.jenkins.library
|
||||
|
||||
import jenkins.plugins.http_request.HttpMode
|
||||
import jenkins.plugins.http_request.MimeType
|
||||
import jenkins.plugins.http_request.ResponseContentSupplier
|
||||
import org.jenkinsci.plugins.pipeline.utility.steps.fs.FileWrapper
|
||||
import org.jenkinsci.plugins.workflow.support.actions.EnvironmentAction
|
||||
import org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper
|
||||
import ru.yandex.qatools.allure.jenkins.config.ResultsConfig
|
||||
|
||||
class StepExecutor implements IStepExecutor {
|
||||
@ -153,10 +157,22 @@ class StepExecutor implements IStepExecutor {
|
||||
}
|
||||
|
||||
@Override
|
||||
def httpRequest(String url, String outputFile, String responseHandle = 'NONE', boolean wrapAsMultipart = false) {
|
||||
ResponseContentSupplier httpRequest(String url, String outputFile, String responseHandle = 'NONE', boolean wrapAsMultipart = false) {
|
||||
steps.httpRequest responseHandle: responseHandle, outputFile: outputFile, url: url, wrapAsMultipart: wrapAsMultipart
|
||||
}
|
||||
|
||||
@Override
|
||||
ResponseContentSupplier httpRequest(String url, HttpMode httpMode, MimeType contentType, String requestBody, String validResponseCodes, boolean consoleLogResponseBody) {
|
||||
steps.httpRequest(
|
||||
url: url,
|
||||
httpMode: httpMode,
|
||||
contentType: contentType,
|
||||
requestBody: requestBody,
|
||||
validResponseCodes: validResponseCodes,
|
||||
consoleLogResponseBody: consoleLogResponseBody
|
||||
)
|
||||
}
|
||||
|
||||
@Override
|
||||
def error(String errorMessage) {
|
||||
steps.error errorMessage
|
||||
@ -183,4 +199,40 @@ class StepExecutor implements IStepExecutor {
|
||||
def installLocalDependencies() {
|
||||
steps.installLocalDependencies()
|
||||
}
|
||||
|
||||
@Override
|
||||
def emailext(String subject, String body, String to, List recipientProviders, boolean attachLog) {
|
||||
steps.emailext(
|
||||
subject: subject,
|
||||
body: body,
|
||||
to: to,
|
||||
recipientProviders: recipientProviders,
|
||||
attachLog: attachLog,
|
||||
)
|
||||
}
|
||||
|
||||
@Override
|
||||
def developers() {
|
||||
steps.developers()
|
||||
}
|
||||
|
||||
@Override
|
||||
def requestor() {
|
||||
steps.requestor()
|
||||
}
|
||||
|
||||
@Override
|
||||
def brokenBuildSuspects() {
|
||||
steps.brokenBuildSuspects()
|
||||
}
|
||||
|
||||
@Override
|
||||
def brokenTestsSuspects() {
|
||||
steps.brokenTestsSuspects()
|
||||
}
|
||||
|
||||
@Override
|
||||
RunWrapper currentBuild() {
|
||||
steps.currentBuild
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,11 @@ import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import org.apache.commons.beanutils.BeanUtilsBean
|
||||
import org.apache.commons.beanutils.ConvertUtilsBean
|
||||
import ru.pulsar.jenkins.library.IStepExecutor
|
||||
import ru.pulsar.jenkins.library.configuration.notification.email.EmailExtConfiguration
|
||||
import ru.pulsar.jenkins.library.ioc.ContextRegistry
|
||||
|
||||
import static java.util.Collections.emptySet
|
||||
|
||||
class ConfigurationReader implements Serializable {
|
||||
|
||||
private static ObjectMapper mapper
|
||||
@ -62,12 +65,21 @@ class ConfigurationReader implements Serializable {
|
||||
"sonarQubeOptions",
|
||||
"smokeTestOptions",
|
||||
"syntaxCheckOptions",
|
||||
"resultsTransformOptions"
|
||||
"resultsTransformOptions",
|
||||
"notificationsOptions",
|
||||
"emailNotificationOptions",
|
||||
"alwaysEmailOptions",
|
||||
"successEmailOptions",
|
||||
"failureEmailOptions",
|
||||
"unstableEmailOptions",
|
||||
"recipientProviders",
|
||||
"telegramNotificationOptions"
|
||||
).toSet()
|
||||
|
||||
mergeObjects(baseConfiguration, configurationToMerge, nonMergeableSettings)
|
||||
mergeInitInfoBaseOptions(baseConfiguration.initInfoBaseOptions, configurationToMerge.initInfoBaseOptions);
|
||||
mergeBddOptions(baseConfiguration.bddOptions, configurationToMerge.bddOptions);
|
||||
mergeInitInfoBaseOptions(baseConfiguration.initInfoBaseOptions, configurationToMerge.initInfoBaseOptions)
|
||||
mergeBddOptions(baseConfiguration.bddOptions, configurationToMerge.bddOptions)
|
||||
mergeNotificationsOptions(baseConfiguration.notificationsOptions, configurationToMerge.notificationsOptions)
|
||||
|
||||
return baseConfiguration;
|
||||
}
|
||||
@ -84,10 +96,16 @@ class ConfigurationReader implements Serializable {
|
||||
}
|
||||
|
||||
nonMergeableSettings.forEach({ key ->
|
||||
if (!baseObject.hasProperty(key)) {
|
||||
return
|
||||
}
|
||||
if (objectToMerge == null) {
|
||||
return
|
||||
}
|
||||
mergeObjects(
|
||||
baseObject[key],
|
||||
objectToMerge[key],
|
||||
Collections.emptySet()
|
||||
nonMergeableSettings
|
||||
)
|
||||
})
|
||||
}
|
||||
@ -107,4 +125,53 @@ class ConfigurationReader implements Serializable {
|
||||
}
|
||||
baseObject.vrunnerSteps = objectToMerge.vrunnerSteps.clone()
|
||||
}
|
||||
|
||||
|
||||
private static void mergeNotificationsOptions(NotificationsOptions baseObject, NotificationsOptions objectToMerge) {
|
||||
if (objectToMerge == null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (objectToMerge.telegramNotificationOptions != null) {
|
||||
|
||||
mergeObjects(
|
||||
baseObject.telegramNotificationOptions,
|
||||
objectToMerge.telegramNotificationOptions,
|
||||
emptySet()
|
||||
)
|
||||
}
|
||||
|
||||
def emailNotificationOptionsBase = baseObject.emailNotificationOptions
|
||||
def emailNotificationOptionsToMerge = objectToMerge.emailNotificationOptions
|
||||
|
||||
if (emailNotificationOptionsToMerge != null) {
|
||||
mergeEmailExtConfiguration(
|
||||
emailNotificationOptionsBase.successEmailOptions,
|
||||
emailNotificationOptionsToMerge.successEmailOptions
|
||||
)
|
||||
mergeEmailExtConfiguration(
|
||||
emailNotificationOptionsBase.failureEmailOptions,
|
||||
emailNotificationOptionsToMerge.failureEmailOptions
|
||||
)
|
||||
mergeEmailExtConfiguration(
|
||||
emailNotificationOptionsBase.unstableEmailOptions,
|
||||
emailNotificationOptionsToMerge.unstableEmailOptions
|
||||
)
|
||||
mergeEmailExtConfiguration(
|
||||
emailNotificationOptionsBase.alwaysEmailOptions,
|
||||
emailNotificationOptionsToMerge.alwaysEmailOptions
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@NonCPS
|
||||
private static void mergeEmailExtConfiguration(EmailExtConfiguration baseObject, EmailExtConfiguration objectToMerge) {
|
||||
if (objectToMerge != null && objectToMerge.recipientProviders != null) {
|
||||
baseObject.recipientProviders = objectToMerge.recipientProviders.clone()
|
||||
}
|
||||
|
||||
if (objectToMerge != null && objectToMerge.directRecipients != null) {
|
||||
baseObject.directRecipients = objectToMerge.directRecipients.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,6 +59,10 @@ class JobConfiguration implements Serializable {
|
||||
@JsonPropertyDescription("Настройки трансформации результатов анализа")
|
||||
ResultsTransformOptions resultsTransformOptions;
|
||||
|
||||
@JsonProperty("notifications")
|
||||
@JsonPropertyDescription("Настройки рассылки результатов сборки")
|
||||
NotificationsOptions notificationsOptions;
|
||||
|
||||
@JsonProperty("logosConfig")
|
||||
@JsonPropertyDescription("Конфигурация библиотеки logos. Применяется перед запуском каждой стадии сборки")
|
||||
String logosConfig;
|
||||
@ -81,6 +85,7 @@ class JobConfiguration implements Serializable {
|
||||
", syntaxCheckOptions=" + syntaxCheckOptions +
|
||||
", smokeTestOptions=" + smokeTestOptions +
|
||||
", resultsTransformOptions=" + resultsTransformOptions +
|
||||
", notificationOptions=" + notificationsOptions +
|
||||
", logosConfig='" + logosConfig + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
@ -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.JsonProperty
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyDescription
|
||||
import ru.pulsar.jenkins.library.configuration.notification.EmailNotificationOptions
|
||||
import ru.pulsar.jenkins.library.configuration.notification.TelegramNotificationOptions
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
class NotificationsOptions implements Serializable {
|
||||
|
||||
@JsonProperty("email")
|
||||
@JsonPropertyDescription("Настройки рассылки результатов сборки через email")
|
||||
EmailNotificationOptions emailNotificationOptions;
|
||||
|
||||
@JsonProperty("telegram")
|
||||
@JsonPropertyDescription("Настройки рассылки результатов сборки через telegram")
|
||||
TelegramNotificationOptions telegramNotificationOptions;
|
||||
|
||||
@Override
|
||||
@NonCPS
|
||||
String toString() {
|
||||
return "NotificationOptions{" +
|
||||
"emailNotificationOptions=" + emailNotificationOptions +
|
||||
", telegramNotificationOptions=" + telegramNotificationOptions +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,12 +15,20 @@ class Secrets implements Serializable {
|
||||
@JsonPropertyDescription("Данные авторизации в хранилище конфигурации")
|
||||
String storage
|
||||
|
||||
@JsonPropertyDescription("Идентификатор telegram-чата для отправки уведомлений")
|
||||
String telegramChatId
|
||||
|
||||
@JsonPropertyDescription("Токен авторизации telegram-бота для отправки уведомлений")
|
||||
String telegramBotToken
|
||||
|
||||
@Override
|
||||
@NonCPS
|
||||
String toString() {
|
||||
return "Secrets{" +
|
||||
"storagePath='" + storagePath + '\'' +
|
||||
", storage='" + storage + '\'' +
|
||||
", telegramChatId='" + telegramChatId + '\'' +
|
||||
", telegramBotToken='" + telegramBotToken + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,12 @@ class StageFlags implements Serializable {
|
||||
@JsonPropertyDescription("Запуск BDD сценариев включен")
|
||||
Boolean bdd
|
||||
|
||||
@JsonPropertyDescription("Выполнять рассылку результатов сборки на email")
|
||||
Boolean email
|
||||
|
||||
@JsonPropertyDescription("Выполнять рассылку результатов сборки в telegram")
|
||||
Boolean telegram
|
||||
|
||||
@Override
|
||||
@NonCPS
|
||||
String toString() {
|
||||
@ -34,6 +40,8 @@ class StageFlags implements Serializable {
|
||||
", smoke=" + smoke +
|
||||
", initSteps=" + initSteps +
|
||||
", bdd=" + bdd +
|
||||
", email=" + email +
|
||||
", telegram=" + telegram +
|
||||
'}';
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,46 @@
|
||||
package ru.pulsar.jenkins.library.configuration.notification
|
||||
|
||||
import com.cloudbees.groovy.cps.NonCPS
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyDescription
|
||||
import ru.pulsar.jenkins.library.configuration.notification.email.EmailExtConfiguration
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
class EmailNotificationOptions implements Serializable {
|
||||
|
||||
@JsonPropertyDescription("Отправлять всегда")
|
||||
Boolean onAlways
|
||||
@JsonPropertyDescription("Отправлять при успешной сборке")
|
||||
Boolean onSuccess
|
||||
@JsonPropertyDescription("Отправлять при падении сборки")
|
||||
Boolean onFailure
|
||||
@JsonPropertyDescription("Отправлять при нестабильной сборке")
|
||||
Boolean onUnstable
|
||||
|
||||
@JsonProperty("alwaysOptions")
|
||||
EmailExtConfiguration alwaysEmailOptions
|
||||
@JsonProperty("successOptions")
|
||||
EmailExtConfiguration successEmailOptions
|
||||
@JsonProperty("failureOptions")
|
||||
EmailExtConfiguration failureEmailOptions
|
||||
@JsonProperty("unstableOptions")
|
||||
EmailExtConfiguration unstableEmailOptions
|
||||
|
||||
@Override
|
||||
@NonCPS
|
||||
String toString() {
|
||||
return "EmailNotificationOptions{" +
|
||||
"onAlways=" + onAlways +
|
||||
", onSuccess=" + onSuccess +
|
||||
", onFailure=" + onFailure +
|
||||
", onUnstable=" + onUnstable +
|
||||
", alwaysEmailOptions=" + alwaysEmailOptions +
|
||||
", successEmailOptions=" + successEmailOptions +
|
||||
", failureEmailOptions=" + failureEmailOptions +
|
||||
", unstableEmailOptions=" + unstableEmailOptions +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,31 @@
|
||||
package ru.pulsar.jenkins.library.configuration.notification
|
||||
|
||||
import com.cloudbees.groovy.cps.NonCPS
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyDescription
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
class TelegramNotificationOptions implements Serializable {
|
||||
|
||||
@JsonPropertyDescription("Отправлять всегда")
|
||||
Boolean onAlways
|
||||
@JsonPropertyDescription("Отправлять при успешной сборке")
|
||||
Boolean onSuccess
|
||||
@JsonPropertyDescription("Отправлять при падении сборки")
|
||||
Boolean onFailure
|
||||
@JsonPropertyDescription("Отправлять при нестабильной сборке")
|
||||
Boolean onUnstable
|
||||
|
||||
@Override
|
||||
@NonCPS
|
||||
String toString() {
|
||||
return "TelegramNotificationOptions{" +
|
||||
"onAlways=" + onAlways +
|
||||
", onSuccess=" + onSuccess +
|
||||
", onFailure=" + onFailure +
|
||||
", onUnstable=" + onUnstable +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,21 @@
|
||||
package ru.pulsar.jenkins.library.configuration.notification.email
|
||||
|
||||
import com.cloudbees.groovy.cps.NonCPS
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
class EmailExtConfiguration implements Serializable {
|
||||
Boolean attachLog;
|
||||
String[] directRecipients
|
||||
RecipientProvider[] recipientProviders
|
||||
|
||||
@Override
|
||||
@NonCPS
|
||||
String toString() {
|
||||
return "EmailExtConfiguration{" +
|
||||
"attachLog=" + attachLog +
|
||||
", directRecipients=" + directRecipients +
|
||||
", recipientProviders=" + recipientProviders +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package ru.pulsar.jenkins.library.configuration.notification.email
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
|
||||
enum RecipientProvider {
|
||||
|
||||
@JsonProperty("developers")
|
||||
DEVELOPERS,
|
||||
@JsonProperty("requestor")
|
||||
REQUESTOR,
|
||||
@JsonProperty("brokenBuildSuspects")
|
||||
BROKEN_BUILD_SUSPECTS,
|
||||
@JsonProperty("brokenTestsSuspects")
|
||||
BROKEN_TESTS_SUSPECTS
|
||||
}
|
95
src/ru/pulsar/jenkins/library/steps/EmailNotification.groovy
Normal file
95
src/ru/pulsar/jenkins/library/steps/EmailNotification.groovy
Normal file
@ -0,0 +1,95 @@
|
||||
package ru.pulsar.jenkins.library.steps
|
||||
|
||||
import hudson.model.Result
|
||||
import ru.pulsar.jenkins.library.IStepExecutor
|
||||
import ru.pulsar.jenkins.library.configuration.JobConfiguration
|
||||
import ru.pulsar.jenkins.library.configuration.notification.email.EmailExtConfiguration
|
||||
import ru.pulsar.jenkins.library.configuration.notification.email.RecipientProvider
|
||||
import ru.pulsar.jenkins.library.ioc.ContextRegistry
|
||||
import ru.pulsar.jenkins.library.utils.Logger
|
||||
import ru.pulsar.jenkins.library.utils.StringJoiner
|
||||
|
||||
class EmailNotification implements Serializable {
|
||||
|
||||
private final JobConfiguration config;
|
||||
|
||||
EmailNotification(JobConfiguration config) {
|
||||
this.config = config
|
||||
}
|
||||
|
||||
def run() {
|
||||
|
||||
Logger.printLocation()
|
||||
|
||||
if (!config.stageFlags.email) {
|
||||
Logger.println("Email notifications are disabled")
|
||||
return
|
||||
}
|
||||
|
||||
IStepExecutor steps = ContextRegistry.getContext().getStepExecutor()
|
||||
|
||||
def options = config.notificationsOptions.emailNotificationOptions
|
||||
|
||||
def currentBuild = steps.currentBuild()
|
||||
def currentResult = Result.fromString(currentBuild.getCurrentResult())
|
||||
|
||||
EmailExtConfiguration configuration = null;
|
||||
if (options.onAlways) {
|
||||
configuration = options.alwaysEmailOptions
|
||||
} else if (options.onFailure && (currentResult == Result.FAILURE || currentResult == Result.ABORTED)) {
|
||||
configuration = options.failureEmailOptions
|
||||
} else if (options.onUnstable && currentResult == Result.UNSTABLE) {
|
||||
configuration = options.unstableEmailOptions
|
||||
} else if (options.onSuccess && currentResult == Result.SUCCESS) {
|
||||
configuration = options.successEmailOptions
|
||||
}
|
||||
|
||||
sendEmail(configuration)
|
||||
|
||||
}
|
||||
|
||||
private static void sendEmail(EmailExtConfiguration configuration) {
|
||||
|
||||
if (configuration == null) {
|
||||
Logger.println("Unknown build result. Can't send an email!")
|
||||
return
|
||||
}
|
||||
|
||||
IStepExecutor steps = ContextRegistry.getContext().getStepExecutor()
|
||||
|
||||
String subject = '$DEFAULT_SUBJECT'
|
||||
String body = '$DEFAULT_CONTENT'
|
||||
|
||||
StringJoiner toJoiner = new StringJoiner(",")
|
||||
configuration.directRecipients.each {
|
||||
toJoiner.add(it)
|
||||
}
|
||||
String to = toJoiner.toString()
|
||||
|
||||
List recipientProviders = new ArrayList();
|
||||
configuration.recipientProviders.each {
|
||||
switch (it) {
|
||||
case RecipientProvider.BROKEN_BUILD_SUSPECTS:
|
||||
recipientProviders.add(steps.brokenBuildSuspects())
|
||||
break
|
||||
case RecipientProvider.BROKEN_TESTS_SUSPECTS:
|
||||
recipientProviders.add(steps.brokenTestsSuspects())
|
||||
break
|
||||
case RecipientProvider.DEVELOPERS:
|
||||
recipientProviders.add(steps.developers())
|
||||
break
|
||||
case RecipientProvider.REQUESTOR:
|
||||
recipientProviders.add(steps.requestor())
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
steps.emailext(
|
||||
subject,
|
||||
body,
|
||||
to,
|
||||
recipientProviders,
|
||||
configuration.attachLog
|
||||
)
|
||||
}
|
||||
}
|
@ -17,10 +17,16 @@ class PublishAllure implements Serializable {
|
||||
}
|
||||
|
||||
def run() {
|
||||
steps = ContextRegistry.getContext().getStepExecutor()
|
||||
|
||||
Logger.printLocation()
|
||||
|
||||
if (config == null) {
|
||||
Logger.println("jobConfiguration is not initialized")
|
||||
return
|
||||
}
|
||||
|
||||
steps = ContextRegistry.getContext().getStepExecutor()
|
||||
|
||||
safeUnstash('init-allure')
|
||||
safeUnstash('bdd-allure')
|
||||
if (config.stageFlags.smoke && config.smokeTestOptions.publishToAllureReport) {
|
||||
|
30
src/ru/pulsar/jenkins/library/steps/SendNotifications.groovy
Normal file
30
src/ru/pulsar/jenkins/library/steps/SendNotifications.groovy
Normal file
@ -0,0 +1,30 @@
|
||||
package ru.pulsar.jenkins.library.steps
|
||||
|
||||
import ru.pulsar.jenkins.library.configuration.JobConfiguration
|
||||
import ru.pulsar.jenkins.library.utils.Logger
|
||||
|
||||
class SendNotifications implements Serializable {
|
||||
|
||||
private final JobConfiguration config;
|
||||
|
||||
SendNotifications(JobConfiguration config) {
|
||||
this.config = config
|
||||
}
|
||||
|
||||
def run() {
|
||||
|
||||
Logger.printLocation()
|
||||
|
||||
if (config == null) {
|
||||
Logger.println("jobConfiguration is not initialized")
|
||||
return
|
||||
}
|
||||
|
||||
def emailNotification = new EmailNotification(config);
|
||||
emailNotification.run()
|
||||
|
||||
def telegramNotification = new TelegramNotification(config);
|
||||
telegramNotification.run();
|
||||
|
||||
}
|
||||
}
|
251
src/ru/pulsar/jenkins/library/steps/TelegramNotification.groovy
Normal file
251
src/ru/pulsar/jenkins/library/steps/TelegramNotification.groovy
Normal file
@ -0,0 +1,251 @@
|
||||
package ru.pulsar.jenkins.library.steps
|
||||
|
||||
import com.cloudbees.groovy.cps.NonCPS
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import hudson.model.Result
|
||||
import hudson.scm.ChangeLogSet
|
||||
import io.jenkins.blueocean.rest.impl.pipeline.FlowNodeWrapper
|
||||
import io.jenkins.blueocean.rest.impl.pipeline.PipelineNodeGraphVisitor
|
||||
import io.jenkins.blueocean.rest.model.BlueRun
|
||||
import io.jenkins.cli.shaded.org.apache.commons.lang.time.DurationFormatUtils
|
||||
import jenkins.plugins.http_request.HttpMode
|
||||
import jenkins.plugins.http_request.MimeType
|
||||
import org.jenkinsci.plugins.workflow.actions.TimingAction
|
||||
import org.jenkinsci.plugins.workflow.graph.BlockStartNode
|
||||
import org.jenkinsci.plugins.workflow.job.WorkflowRun
|
||||
import org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper
|
||||
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.RepoUtils
|
||||
import ru.pulsar.jenkins.library.utils.StringJoiner
|
||||
|
||||
import static ru.pulsar.jenkins.library.configuration.Secrets.UNKNOWN_ID
|
||||
|
||||
class TelegramNotification implements Serializable {
|
||||
|
||||
private final JobConfiguration config;
|
||||
|
||||
TelegramNotification(JobConfiguration config) {
|
||||
this.config = config
|
||||
}
|
||||
|
||||
def run() {
|
||||
|
||||
Logger.printLocation()
|
||||
|
||||
if (!config.stageFlags.telegram) {
|
||||
Logger.println("Telegram notifications are disabled")
|
||||
return
|
||||
}
|
||||
|
||||
IStepExecutor steps = ContextRegistry.getContext().getStepExecutor()
|
||||
|
||||
def options = config.notificationsOptions.telegramNotificationOptions
|
||||
|
||||
def currentBuild = steps.currentBuild()
|
||||
def currentResult = Result.fromString(currentBuild.getCurrentResult())
|
||||
|
||||
String message = getMessage(currentBuild)
|
||||
|
||||
if (options.onAlways) {
|
||||
sendMessage(message)
|
||||
} else if (options.onFailure && (currentResult == Result.FAILURE || currentResult == Result.ABORTED)) {
|
||||
sendMessage(message)
|
||||
} else if (options.onUnstable && currentResult == Result.UNSTABLE) {
|
||||
sendMessage(message)
|
||||
} else if (options.onSuccess && currentResult == Result.SUCCESS) {
|
||||
sendMessage(message)
|
||||
} else {
|
||||
Logger.println("Unknown build result! Can't send a message to telegram")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void sendMessage(message) {
|
||||
IStepExecutor steps = ContextRegistry.getContext().getStepExecutor()
|
||||
def env = steps.env();
|
||||
|
||||
String repoSlug = RepoUtils.getRepoSlug()
|
||||
|
||||
Secrets secrets = config.secrets
|
||||
|
||||
String telegramChatIdCredentials = secrets.telegramChatId == UNKNOWN_ID ? repoSlug + "_TELEGRAM_CHAT_ID" : secrets.telegramChatId
|
||||
String telegramBotTokenCredentials = secrets.telegramBotToken == UNKNOWN_ID ? "TELEGRAM_BOT_TOKEN" : secrets.telegramBotToken
|
||||
|
||||
steps.withCredentials([
|
||||
steps.string(telegramBotTokenCredentials, 'TOKEN'),
|
||||
steps.string(telegramChatIdCredentials, 'CHAT_ID')
|
||||
]) {
|
||||
|
||||
def mapper = new ObjectMapper()
|
||||
|
||||
def body = [
|
||||
chat_id : env.CHAT_ID,
|
||||
text : message,
|
||||
disable_web_page_preview: true,
|
||||
parse_mode : 'MarkdownV2'
|
||||
]
|
||||
|
||||
def bodyString = mapper.writeValueAsString(body)
|
||||
String url = "https://api.telegram.org/bot${env.TOKEN}/sendMessage"
|
||||
|
||||
steps.echo(message)
|
||||
steps.echo(bodyString)
|
||||
|
||||
steps.httpRequest(
|
||||
url,
|
||||
HttpMode.POST,
|
||||
MimeType.APPLICATION_JSON_UTF8,
|
||||
bodyString,
|
||||
'200',
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private static String getMessage(RunWrapper currentBuild) {
|
||||
|
||||
IStepExecutor steps = ContextRegistry.getContext().getStepExecutor()
|
||||
def env = steps.env();
|
||||
|
||||
def currentResult = Result.fromString(currentBuild.getCurrentResult())
|
||||
|
||||
def messageJoiner = new StringJoiner('\n\n')
|
||||
|
||||
def displayName = escapeStringForMarkdownV2(currentBuild.fullDisplayName)
|
||||
String header = "[$displayName]($env.BUILD_URL)"
|
||||
messageJoiner.add(header)
|
||||
|
||||
String result = ""
|
||||
if (currentResult == Result.SUCCESS) {
|
||||
result = "✅ Сборка прошла успешно!"
|
||||
} else if (currentResult == Result.FAILURE) {
|
||||
result = "❌ Сборка завершилась с ошибкой!"
|
||||
} else if (currentResult == Result.ABORTED) {
|
||||
result = "🛑 Сборка прервана!"
|
||||
} else if (currentResult == Result.UNSTABLE) {
|
||||
result = "💩 Есть упавшие тесты!"
|
||||
}
|
||||
|
||||
result = escapeStringForMarkdownV2(result)
|
||||
messageJoiner.add(result)
|
||||
|
||||
String stageResults = getStageResultsMessage(currentBuild)
|
||||
if (stageResults.length() > 0) {
|
||||
stageResults = escapeStringForMarkdownV2(stageResults)
|
||||
messageJoiner.add(stageResults)
|
||||
}
|
||||
|
||||
def duration = "Длительность сборки: ${currentBuild.getDurationString()}".replace(" and counting", "")
|
||||
duration = escapeStringForMarkdownV2(duration)
|
||||
messageJoiner.add(duration)
|
||||
|
||||
def changeSet = getChangeSet(currentBuild)
|
||||
steps.echo(changeSet)
|
||||
if (changeSet.length() > 0) {
|
||||
changeSet = 'Изменения с последней сборки:\n\n' + changeSet
|
||||
messageJoiner.add(changeSet)
|
||||
}
|
||||
|
||||
String buildUrl = "[Лог сборки](${env.BUILD_URL}console)"
|
||||
messageJoiner.add(buildUrl)
|
||||
|
||||
steps.echo(messageJoiner.toString())
|
||||
|
||||
return messageJoiner.toString()
|
||||
}
|
||||
|
||||
@NonCPS
|
||||
private static String getChangeSet(RunWrapper currentBuild) {
|
||||
String changeSetText = ''
|
||||
|
||||
int counter = 0
|
||||
currentBuild.changeSets.each { changeSet ->
|
||||
changeSetText += "Набор изменений \\#${++counter}:\n"
|
||||
changeSet.items.each { ChangeLogSet.Entry entry ->
|
||||
String commit = ''
|
||||
def commitId = entry.commitId;
|
||||
if (commitId != null) {
|
||||
if (isValidSHA1(commitId)) {
|
||||
commitId = commitId.substring(0, 7)
|
||||
}
|
||||
|
||||
def link = changeSet.browser?.getChangeSetLink(entry)
|
||||
if (link != null) {
|
||||
commit = "[$commitId]($link)"
|
||||
} else {
|
||||
commit = commitId
|
||||
}
|
||||
}
|
||||
|
||||
def author = escapeStringForMarkdownV2(entry.author.displayName)
|
||||
def authorLink = entry.author.absoluteUrl
|
||||
|
||||
def message = escapeStringForMarkdownV2(entry.getMsgAnnotated())
|
||||
changeSetText += "\\* $commit $message \\([$author]($authorLink)\\)\n"
|
||||
}
|
||||
changeSetText += '\n'
|
||||
}
|
||||
return changeSetText.trim()
|
||||
}
|
||||
|
||||
@NonCPS
|
||||
private static String getStageResultsMessage(RunWrapper currentBuild) {
|
||||
def visitor = new PipelineNodeGraphVisitor(currentBuild.rawBuild as WorkflowRun)
|
||||
def stages = visitor.pipelineNodes.findAll { it.type != FlowNodeWrapper.NodeType.STEP }
|
||||
|
||||
def stageResultMessage = ""
|
||||
for (FlowNodeWrapper stage in stages) {
|
||||
if (stage.status.result == BlueRun.BlueRunResult.SUCCESS || stage.status.result == BlueRun.BlueRunResult.NOT_BUILT) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
long duration
|
||||
def endNode = stage.node.getExecution().getEndNode(stage.node as BlockStartNode)
|
||||
if (endNode != null) {
|
||||
def startTime = TimingAction.getStartTime(stage.node)
|
||||
def endTime = TimingAction.getStartTime(endNode)
|
||||
|
||||
duration = endTime - startTime
|
||||
} else {
|
||||
duration = stage.timing.totalDurationMillis
|
||||
}
|
||||
|
||||
def time = DurationFormatUtils.formatDuration(duration, "H:mm:ss")
|
||||
stageResultMessage += "$stage.displayName: $stage.status.result, затрачено времени $time \n"
|
||||
}
|
||||
|
||||
return stageResultMessage.trim()
|
||||
}
|
||||
|
||||
@NonCPS
|
||||
private static String escapeStringForMarkdownV2(String incoming) {
|
||||
return incoming.replace('_', '\\-')
|
||||
.replace('*', '\\*')
|
||||
.replace('[', '\\[')
|
||||
.replace(']', '\\]')
|
||||
.replace('(', '\\(')
|
||||
.replace(')', '\\)')
|
||||
.replace('~', '\\~')
|
||||
.replace('`', '\\`')
|
||||
.replace('>', '\\>')
|
||||
.replace('#', '\\#')
|
||||
.replace('+', '\\+')
|
||||
.replace('-', '\\-')
|
||||
.replace('=', '\\=')
|
||||
.replace('|', '\\|')
|
||||
.replace('{', '\\{')
|
||||
.replace('}', '\\}')
|
||||
.replace('.', '\\.')
|
||||
.replace('!', '\\!')
|
||||
}
|
||||
|
||||
@NonCPS
|
||||
private static boolean isValidSHA1(String s) {
|
||||
return s.matches('^[a-fA-F0-9]{40}$');
|
||||
}
|
||||
}
|
@ -63,6 +63,18 @@ class ConfigurationReaderTest {
|
||||
|
||||
assertThat(jobConfiguration.getTimeoutOptions().getBdd()).isEqualTo(120);
|
||||
assertThat(jobConfiguration.getTimeoutOptions().getZipInfoBase()).isEqualTo(123);
|
||||
|
||||
assertThat(jobConfiguration.getNotificationsOptions().getEmailNotificationOptions().getOnAlways()).isTrue();
|
||||
assertThat(jobConfiguration.getNotificationsOptions().getEmailNotificationOptions().getOnSuccess()).isFalse();
|
||||
assertThat(jobConfiguration.getNotificationsOptions().getEmailNotificationOptions().getAlwaysEmailOptions().getAttachLog()).isTrue();
|
||||
assertThat(jobConfiguration.getNotificationsOptions().getEmailNotificationOptions().getAlwaysEmailOptions().getRecipientProviders()).hasSize(2);
|
||||
assertThat(jobConfiguration.getNotificationsOptions().getEmailNotificationOptions().getAlwaysEmailOptions().getDirectRecipients()).hasSize(2);
|
||||
|
||||
assertThat(jobConfiguration.getNotificationsOptions().getEmailNotificationOptions().getFailureEmailOptions().getDirectRecipients()).isEmpty();
|
||||
assertThat(jobConfiguration.getNotificationsOptions().getEmailNotificationOptions().getFailureEmailOptions().getRecipientProviders()).hasSize(1);
|
||||
|
||||
assertThat(jobConfiguration.getNotificationsOptions().getTelegramNotificationOptions().getOnAlways()).isFalse();
|
||||
assertThat(jobConfiguration.getNotificationsOptions().getTelegramNotificationOptions().getOnFailure()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -31,5 +31,26 @@
|
||||
"publishToAllureReport": false,
|
||||
"publishToJUnitReport": true
|
||||
},
|
||||
"notifications": {
|
||||
"email": {
|
||||
"onAlways": true,
|
||||
"alwaysOptions": {
|
||||
"attachLog": true,
|
||||
"directRecipients": [
|
||||
"1@1.com",
|
||||
"2@1.com"
|
||||
]
|
||||
},
|
||||
"failureOptions": {
|
||||
"recipientProviders": [
|
||||
"developers"
|
||||
]
|
||||
}
|
||||
},
|
||||
"telegram": {
|
||||
"onAlways": false,
|
||||
"onFailure": true
|
||||
}
|
||||
},
|
||||
"logosConfig": "logger.rootLogger=DEBUG"
|
||||
}
|
@ -238,6 +238,7 @@ void call() {
|
||||
always {
|
||||
node('agent') {
|
||||
saveResults config
|
||||
sendNotifications(config)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
10
vars/sendNotifications.groovy
Normal file
10
vars/sendNotifications.groovy
Normal 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.SendNotifications
|
||||
|
||||
def call(JobConfiguration config) {
|
||||
ContextRegistry.registerDefaultContext(this)
|
||||
|
||||
def sendNotifications = new SendNotifications(config)
|
||||
sendNotifications.run()
|
||||
}
|
Loading…
Reference in New Issue
Block a user