mirror of
https://github.com/firstBitMarksistskaya/jenkins-lib.git
synced 2025-01-07 13:23:36 +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. Запуск валидации проекта средствами EDT и конвертация отчета в формате generic issues.
|
||||||
1. Запуск статического анализа для SonarQube.
|
1. Запуск статического анализа для SonarQube.
|
||||||
1. Публикация результатов junit и Allure в интерфейс Jenkins.
|
1. Публикация результатов junit и Allure в интерфейс Jenkins.
|
||||||
|
1. Рассылка результатов сборки на почту и в Telegram.
|
||||||
1. Конфигурирование логгера запускаемых oscript-приложений.
|
1. Конфигурирование логгера запускаемых oscript-приложений.
|
||||||
|
|
||||||
## Подключение
|
## Подключение
|
||||||
@ -109,9 +110,11 @@ pipeline1C()
|
|||||||
* Исходники конфигурации ожидаются в каталоге `src/cf` (`srcDir`).
|
* Исходники конфигурации ожидаются в каталоге `src/cf` (`srcDir`).
|
||||||
* Формат исходников - выгрузка из Конфигуратора (`sourceFormat`).
|
* Формат исходников - выгрузка из Конфигуратора (`sourceFormat`).
|
||||||
* Ветка по умолчанию (для комбинированного режима загрузки конфигурации) - "main" (`defaultBranch`).
|
* Ветка по умолчанию (для комбинированного режима загрузки конфигурации) - "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_PATH` - путь к хранилищу конфигурации (для `secrets` -> `storagePath`);
|
||||||
* `STORAGE_USER` - параметры авторизации в хранилище вида "username with password" (для `secrets` -> `storage`).
|
* `STORAGE_USER` - параметры авторизации в хранилище вида "username with password" (для `secrets` -> `storage`).
|
||||||
|
* `TELEGRAM_CHAT_ID` - идентификатор чата Telegram для рассылки уведомлений и результате сборки вида "secret text" (для `secrets` -> `telegramChatId`).
|
||||||
|
* Секрет `TELEGRAM_BOT_TOKEN` задается глобально на весь сервер Jenkins, либо может быть переопределен (`secrets` -> `telegramBotToken`)
|
||||||
* Все "шаги" по умолчанию выключены (`stages`).
|
* Все "шаги" по умолчанию выключены (`stages`).
|
||||||
* Если в корне репозитория существует файл `packagedef`, то в шагах, работающих с информационной базой, будет выполнена попытка установки локальных зависимостей средствами `opm`.
|
* Если в корне репозитория существует файл `packagedef`, то в шагах, работающих с информационной базой, будет выполнена попытка установки локальных зависимостей средствами `opm`.
|
||||||
* Если после установки локальных зависимостей в каталоге `oscript_modules/bin` существует файл `vrunner`, то для выполнения команд работы с информационной базой будет использоваться он, а не глобально установленный `vrunner` из `PATH`.
|
* Если после установки локальных зависимостей в каталоге `oscript_modules/bin` существует файл `vrunner`, то для выполнения команд работы с информационной базой будет использоваться он, а не глобально установленный `vrunner` из `PATH`.
|
||||||
@ -150,3 +153,16 @@ pipeline1C()
|
|||||||
* Шаг анализа не дожидается окончания фонового задания на сервере SonarQube и не анализирует результат прохождения Порога качества (`sonarqube` -> `waitForQualityGate`).
|
* Шаг анализа не дожидается окончания фонового задания на сервере SonarQube и не анализирует результат прохождения Порога качества (`sonarqube` -> `waitForQualityGate`).
|
||||||
Для этого необходимо заполнить параметр (`sonarqube` -> `infoBaseUpdateModuleName`). Если параметр не заполнен, версия передается из корня конфигурации.
|
Для этого необходимо заполнить параметр (`sonarqube` -> `infoBaseUpdateModuleName`). Если параметр не заполнен, версия передается из корня конфигурации.
|
||||||
* Если выполнялась валидация EDT, результаты валидации в формате `generic issues` передаются утилите `sonar-scanner` как значение параметра `sonar.externalIssuesReportPaths`.
|
* Если выполнялась валидация 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-build-step", "2.12")
|
||||||
dependency("org.jenkins-ci.plugins", "pipeline-utility-steps", "2.8.0")
|
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", "git", "4.4.4")
|
||||||
|
dependency("org.jenkins-ci.plugins", "http_request", "1.15")
|
||||||
dependency("org.6wind.jenkins", "lockable-resources", "2.7")
|
dependency("org.6wind.jenkins", "lockable-resources", "2.7")
|
||||||
dependency("ru.yandex.qatools.allure", "allure-jenkins-plugin", "2.28.1")
|
dependency("ru.yandex.qatools.allure", "allure-jenkins-plugin", "2.28.1")
|
||||||
val declarativePluginsVersion = "1.6.0"
|
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-declarative-agent", "1.1.1")
|
||||||
dependency("org.jenkinsci.plugins", "pipeline-model-definition", declarativePluginsVersion)
|
dependency("org.jenkinsci.plugins", "pipeline-model-definition", declarativePluginsVersion)
|
||||||
dependency("org.jenkinsci.plugins", "pipeline-model-extensions", 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",
|
"defaultBranch": "main",
|
||||||
"secrets": {
|
"secrets": {
|
||||||
"storagePath": "UNKNOWN_ID",
|
"storagePath": "UNKNOWN_ID",
|
||||||
"storage": "UNKNOWN_ID"
|
"storage": "UNKNOWN_ID",
|
||||||
|
"telegramBotToken": "UNKNOWN_ID",
|
||||||
|
"telegramChatId": "UNKNOWN_ID"
|
||||||
},
|
},
|
||||||
"stages": {
|
"stages": {
|
||||||
"initSteps": false,
|
"initSteps": false,
|
||||||
@ -15,7 +17,9 @@
|
|||||||
"bdd": false,
|
"bdd": false,
|
||||||
"syntaxCheck": false,
|
"syntaxCheck": false,
|
||||||
"edtValidate": false,
|
"edtValidate": false,
|
||||||
"smoke": false
|
"smoke": false,
|
||||||
|
"email": false,
|
||||||
|
"telegram": false
|
||||||
},
|
},
|
||||||
"timeout": {
|
"timeout": {
|
||||||
"smoke": 240,
|
"smoke": 240,
|
||||||
@ -77,5 +81,52 @@
|
|||||||
"removeSupport": true,
|
"removeSupport": true,
|
||||||
"supportLevel": 0
|
"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": ""
|
"logosConfig": ""
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,14 @@
|
|||||||
"storage" : {
|
"storage" : {
|
||||||
"type" : "string",
|
"type" : "string",
|
||||||
"description" : "Данные авторизации в хранилище конфигурации"
|
"description" : "Данные авторизации в хранилище конфигурации"
|
||||||
|
},
|
||||||
|
"telegramChatId" : {
|
||||||
|
"type" : "string",
|
||||||
|
"description" : "Идентификатор telegram-чата для отправки уведомлений"
|
||||||
|
},
|
||||||
|
"telegramBotToken" : {
|
||||||
|
"type" : "string",
|
||||||
|
"description" : "Токен авторизации telegram-бота для отправки уведомлений"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -66,6 +74,14 @@
|
|||||||
"bdd" : {
|
"bdd" : {
|
||||||
"type" : "boolean",
|
"type" : "boolean",
|
||||||
"description" : "Запуск BDD сценариев включен"
|
"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" : {
|
"logosConfig" : {
|
||||||
"type" : "string",
|
"type" : "string",
|
||||||
"description" : "Конфигурация библиотеки logos. Применяется перед запуском каждой стадии сборки"
|
"description" : "Конфигурация библиотеки logos. Применяется перед запуском каждой стадии сборки"
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
package ru.pulsar.jenkins.library
|
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.pipeline.utility.steps.fs.FileWrapper
|
||||||
import org.jenkinsci.plugins.workflow.support.actions.EnvironmentAction
|
import org.jenkinsci.plugins.workflow.support.actions.EnvironmentAction
|
||||||
|
import org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper
|
||||||
|
|
||||||
interface IStepExecutor {
|
interface IStepExecutor {
|
||||||
|
|
||||||
@ -69,7 +73,9 @@ interface IStepExecutor {
|
|||||||
|
|
||||||
def catchError(Closure body)
|
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)
|
def error(String errorMessage)
|
||||||
|
|
||||||
@ -78,4 +84,16 @@ interface IStepExecutor {
|
|||||||
def junit(String testResults, boolean allowEmptyResults)
|
def junit(String testResults, boolean allowEmptyResults)
|
||||||
|
|
||||||
def installLocalDependencies()
|
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
|
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.pipeline.utility.steps.fs.FileWrapper
|
||||||
import org.jenkinsci.plugins.workflow.support.actions.EnvironmentAction
|
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
|
import ru.yandex.qatools.allure.jenkins.config.ResultsConfig
|
||||||
|
|
||||||
class StepExecutor implements IStepExecutor {
|
class StepExecutor implements IStepExecutor {
|
||||||
@ -153,10 +157,22 @@ class StepExecutor implements IStepExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
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
|
@Override
|
||||||
def error(String errorMessage) {
|
def error(String errorMessage) {
|
||||||
steps.error errorMessage
|
steps.error errorMessage
|
||||||
@ -183,4 +199,40 @@ class StepExecutor implements IStepExecutor {
|
|||||||
def installLocalDependencies() {
|
def installLocalDependencies() {
|
||||||
steps.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.BeanUtilsBean
|
||||||
import org.apache.commons.beanutils.ConvertUtilsBean
|
import org.apache.commons.beanutils.ConvertUtilsBean
|
||||||
import ru.pulsar.jenkins.library.IStepExecutor
|
import ru.pulsar.jenkins.library.IStepExecutor
|
||||||
|
import ru.pulsar.jenkins.library.configuration.notification.email.EmailExtConfiguration
|
||||||
import ru.pulsar.jenkins.library.ioc.ContextRegistry
|
import ru.pulsar.jenkins.library.ioc.ContextRegistry
|
||||||
|
|
||||||
|
import static java.util.Collections.emptySet
|
||||||
|
|
||||||
class ConfigurationReader implements Serializable {
|
class ConfigurationReader implements Serializable {
|
||||||
|
|
||||||
private static ObjectMapper mapper
|
private static ObjectMapper mapper
|
||||||
@ -62,12 +65,21 @@ class ConfigurationReader implements Serializable {
|
|||||||
"sonarQubeOptions",
|
"sonarQubeOptions",
|
||||||
"smokeTestOptions",
|
"smokeTestOptions",
|
||||||
"syntaxCheckOptions",
|
"syntaxCheckOptions",
|
||||||
"resultsTransformOptions"
|
"resultsTransformOptions",
|
||||||
|
"notificationsOptions",
|
||||||
|
"emailNotificationOptions",
|
||||||
|
"alwaysEmailOptions",
|
||||||
|
"successEmailOptions",
|
||||||
|
"failureEmailOptions",
|
||||||
|
"unstableEmailOptions",
|
||||||
|
"recipientProviders",
|
||||||
|
"telegramNotificationOptions"
|
||||||
).toSet()
|
).toSet()
|
||||||
|
|
||||||
mergeObjects(baseConfiguration, configurationToMerge, nonMergeableSettings)
|
mergeObjects(baseConfiguration, configurationToMerge, nonMergeableSettings)
|
||||||
mergeInitInfoBaseOptions(baseConfiguration.initInfoBaseOptions, configurationToMerge.initInfoBaseOptions);
|
mergeInitInfoBaseOptions(baseConfiguration.initInfoBaseOptions, configurationToMerge.initInfoBaseOptions)
|
||||||
mergeBddOptions(baseConfiguration.bddOptions, configurationToMerge.bddOptions);
|
mergeBddOptions(baseConfiguration.bddOptions, configurationToMerge.bddOptions)
|
||||||
|
mergeNotificationsOptions(baseConfiguration.notificationsOptions, configurationToMerge.notificationsOptions)
|
||||||
|
|
||||||
return baseConfiguration;
|
return baseConfiguration;
|
||||||
}
|
}
|
||||||
@ -84,10 +96,16 @@ class ConfigurationReader implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nonMergeableSettings.forEach({ key ->
|
nonMergeableSettings.forEach({ key ->
|
||||||
|
if (!baseObject.hasProperty(key)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (objectToMerge == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
mergeObjects(
|
mergeObjects(
|
||||||
baseObject[key],
|
baseObject[key],
|
||||||
objectToMerge[key],
|
objectToMerge[key],
|
||||||
Collections.emptySet()
|
nonMergeableSettings
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -107,4 +125,53 @@ class ConfigurationReader implements Serializable {
|
|||||||
}
|
}
|
||||||
baseObject.vrunnerSteps = objectToMerge.vrunnerSteps.clone()
|
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("Настройки трансформации результатов анализа")
|
@JsonPropertyDescription("Настройки трансформации результатов анализа")
|
||||||
ResultsTransformOptions resultsTransformOptions;
|
ResultsTransformOptions resultsTransformOptions;
|
||||||
|
|
||||||
|
@JsonProperty("notifications")
|
||||||
|
@JsonPropertyDescription("Настройки рассылки результатов сборки")
|
||||||
|
NotificationsOptions notificationsOptions;
|
||||||
|
|
||||||
@JsonProperty("logosConfig")
|
@JsonProperty("logosConfig")
|
||||||
@JsonPropertyDescription("Конфигурация библиотеки logos. Применяется перед запуском каждой стадии сборки")
|
@JsonPropertyDescription("Конфигурация библиотеки logos. Применяется перед запуском каждой стадии сборки")
|
||||||
String logosConfig;
|
String logosConfig;
|
||||||
@ -81,6 +85,7 @@ class JobConfiguration implements Serializable {
|
|||||||
", syntaxCheckOptions=" + syntaxCheckOptions +
|
", syntaxCheckOptions=" + syntaxCheckOptions +
|
||||||
", smokeTestOptions=" + smokeTestOptions +
|
", smokeTestOptions=" + smokeTestOptions +
|
||||||
", resultsTransformOptions=" + resultsTransformOptions +
|
", resultsTransformOptions=" + resultsTransformOptions +
|
||||||
|
", notificationOptions=" + notificationsOptions +
|
||||||
", logosConfig='" + logosConfig + '\'' +
|
", 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("Данные авторизации в хранилище конфигурации")
|
@JsonPropertyDescription("Данные авторизации в хранилище конфигурации")
|
||||||
String storage
|
String storage
|
||||||
|
|
||||||
|
@JsonPropertyDescription("Идентификатор telegram-чата для отправки уведомлений")
|
||||||
|
String telegramChatId
|
||||||
|
|
||||||
|
@JsonPropertyDescription("Токен авторизации telegram-бота для отправки уведомлений")
|
||||||
|
String telegramBotToken
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@NonCPS
|
@NonCPS
|
||||||
String toString() {
|
String toString() {
|
||||||
return "Secrets{" +
|
return "Secrets{" +
|
||||||
"storagePath='" + storagePath + '\'' +
|
"storagePath='" + storagePath + '\'' +
|
||||||
", storage='" + storage + '\'' +
|
", storage='" + storage + '\'' +
|
||||||
|
", telegramChatId='" + telegramChatId + '\'' +
|
||||||
|
", telegramBotToken='" + telegramBotToken + '\'' +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,12 @@ class StageFlags implements Serializable {
|
|||||||
@JsonPropertyDescription("Запуск BDD сценариев включен")
|
@JsonPropertyDescription("Запуск BDD сценариев включен")
|
||||||
Boolean bdd
|
Boolean bdd
|
||||||
|
|
||||||
|
@JsonPropertyDescription("Выполнять рассылку результатов сборки на email")
|
||||||
|
Boolean email
|
||||||
|
|
||||||
|
@JsonPropertyDescription("Выполнять рассылку результатов сборки в telegram")
|
||||||
|
Boolean telegram
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@NonCPS
|
@NonCPS
|
||||||
String toString() {
|
String toString() {
|
||||||
@ -34,6 +40,8 @@ class StageFlags implements Serializable {
|
|||||||
", smoke=" + smoke +
|
", smoke=" + smoke +
|
||||||
", initSteps=" + initSteps +
|
", initSteps=" + initSteps +
|
||||||
", bdd=" + bdd +
|
", 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() {
|
def run() {
|
||||||
steps = ContextRegistry.getContext().getStepExecutor()
|
|
||||||
|
|
||||||
Logger.printLocation()
|
Logger.printLocation()
|
||||||
|
|
||||||
|
if (config == null) {
|
||||||
|
Logger.println("jobConfiguration is not initialized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
steps = ContextRegistry.getContext().getStepExecutor()
|
||||||
|
|
||||||
safeUnstash('init-allure')
|
safeUnstash('init-allure')
|
||||||
safeUnstash('bdd-allure')
|
safeUnstash('bdd-allure')
|
||||||
if (config.stageFlags.smoke && config.smokeTestOptions.publishToAllureReport) {
|
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().getBdd()).isEqualTo(120);
|
||||||
assertThat(jobConfiguration.getTimeoutOptions().getZipInfoBase()).isEqualTo(123);
|
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
|
@Test
|
||||||
|
@ -31,5 +31,26 @@
|
|||||||
"publishToAllureReport": false,
|
"publishToAllureReport": false,
|
||||||
"publishToJUnitReport": true
|
"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"
|
"logosConfig": "logger.rootLogger=DEBUG"
|
||||||
}
|
}
|
@ -238,6 +238,7 @@ void call() {
|
|||||||
always {
|
always {
|
||||||
node('agent') {
|
node('agent') {
|
||||||
saveResults config
|
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