mirror of
https://github.com/firstBitMarksistskaya/jenkins-lib.git
synced 2025-01-20 11:54:30 +02:00
Merge branch 'feature-telegram' into develop
This commit is contained in:
commit
c14d85c20f
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. Применяется перед запуском каждой стадии сборки"
|
||||
|
@ -27,12 +27,12 @@ contributor(ctx) {
|
||||
method(name: 'library', type: 'Object', namedParams: [parameter(name: 'identifier', type: 'java.lang.String'), parameter(name: 'changelog', type: 'java.lang.Boolean'), parameter(name: 'retriever', type: 'Map'),], doc: 'Load a shared library on the fly')
|
||||
method(name: 'libraryResource', type: 'Object', params: [resource: 'java.lang.String'], doc: 'Load a resource file from a shared library')
|
||||
method(name: 'libraryResource', type: 'Object', namedParams: [parameter(name: 'resource', type: 'java.lang.String'), parameter(name: 'encoding', type: 'java.lang.String'),], doc: 'Load a resource file from a shared library')
|
||||
method(name: 'lock', type: 'Object', params: [resource: java.lang.String, body: 'Closure'], doc: 'Lock shared resource')
|
||||
method(name: 'lock', type: 'Object', params: [resource: String, body: 'Closure'], doc: 'Lock shared resource')
|
||||
method(name: 'lock', type: 'Object', params: [body: Closure], namedParams: [parameter(name: 'resource', type: 'java.lang.String'), parameter(name: 'extra', type: 'Map'), parameter(name: 'inversePrecedence', type: 'boolean'), parameter(name: 'label', type: 'java.lang.String'), parameter(name: 'quantity', type: 'int'), parameter(name: 'variable', type: 'java.lang.String'),], doc: 'Lock shared resource')
|
||||
method(name: 'mail', type: 'Object', namedParams: [parameter(name: 'subject', type: 'java.lang.String'), parameter(name: 'body', type: 'java.lang.String'), parameter(name: 'bcc', type: 'java.lang.String'), parameter(name: 'cc', type: 'java.lang.String'), parameter(name: 'charset', type: 'java.lang.String'), parameter(name: 'from', type: 'java.lang.String'), parameter(name: 'mimeType', type: 'java.lang.String'), parameter(name: 'replyTo', type: 'java.lang.String'), parameter(name: 'to', type: 'java.lang.String'),], doc: 'Mail')
|
||||
method(name: 'milestone', type: 'Object', params: [ordinal: 'java.lang.Integer'], doc: 'The milestone step forces all builds to go through in order')
|
||||
method(name: 'milestone', type: 'Object', namedParams: [parameter(name: 'ordinal', type: 'java.lang.Integer'), parameter(name: 'label', type: 'java.lang.String'),], doc: 'The milestone step forces all builds to go through in order')
|
||||
method(name: 'node', type: 'Object', params: [label: java.lang.String, body: 'Closure'], doc: 'Allocate node')
|
||||
method(name: 'node', type: 'Object', params: [label: String, body: 'Closure'], doc: 'Allocate node')
|
||||
method(name: 'nodesByLabel', type: 'Object', params: [label: 'java.lang.String'], doc: 'List of nodes by Label, by default excludes offline nodes.')
|
||||
method(name: 'nodesByLabel', type: 'Object', namedParams: [parameter(name: 'label', type: 'java.lang.String'), parameter(name: 'offline', type: 'boolean'),], doc: 'List of nodes by Label, by default excludes offline nodes.')
|
||||
method(name: 'properties', type: 'Object', params: [properties: 'Map'], doc: 'Set job properties')
|
||||
@ -55,26 +55,30 @@ contributor(ctx) {
|
||||
method(name: 'script', type: 'Object', params: [body: 'Closure'], doc: 'Run arbitrary Pipeline script')
|
||||
method(name: 'sleep', type: 'Object', params: [time: 'int'], doc: 'Sleep')
|
||||
method(name: 'sleep', type: 'Object', namedParams: [parameter(name: 'time', type: 'int'), parameter(name: 'unit', type: 'java.util.concurrent.TimeUnit'),], doc: 'Sleep')
|
||||
method(name: 'stage', type: 'Object', params: [name: java.lang.String, body: 'Closure'], doc: 'Stage')
|
||||
method(name: 'stage', type: 'Object', params: [name: String, body: 'Closure'], doc: 'Stage')
|
||||
method(name: 'stage', type: 'Object', params: [body: Closure], namedParams: [parameter(name: 'name', type: 'java.lang.String'), parameter(name: 'concurrency', type: 'java.lang.Integer'),], doc: 'Stage')
|
||||
method(name: 'options', type: 'Object', params: [body: 'Closure'], doc: 'Options')
|
||||
method(name: 'timeout', type: 'Object', params: [time: int, body: 'Closure'], doc: 'Enforce time limit')
|
||||
method(name: 'timeout', type: 'Object', params: [body: Closure], namedParams: [parameter(name: 'time', type: 'int'), parameter(name: 'activity', type: 'boolean'), parameter(name: 'unit', type: 'java.util.concurrent.TimeUnit'),], doc: 'Enforce time limit')
|
||||
method(name: 'timeout', type: 'Object', params: [body: Closure], namedParams: [parameter(name: 'time', type: 'java.lang.Integer'), parameter(name: 'activity', type: 'boolean'), parameter(name: 'unit', type: 'java.util.concurrent.TimeUnit'),], doc: 'Enforce time limit')
|
||||
method(name: 'timestamps', type: 'Object', params: [body: 'Closure'], doc: 'Timestamps')
|
||||
method(name: 'tool', type: 'Object', params: [name: 'java.lang.String'], doc: 'Use a tool from a predefined Tool Installation')
|
||||
method(name: 'tool', type: 'Object', namedParams: [parameter(name: 'name', type: 'java.lang.String'), parameter(name: 'type', type: 'java.lang.String'),], doc: 'Use a tool from a predefined Tool Installation')
|
||||
method(name: 'unstable', type: 'Object', params: [message: 'java.lang.String'], doc: 'Set stage result to unstable')
|
||||
method(name: 'waitUntil', type: 'Object', params: [body: 'Closure'], doc: 'Wait for condition')
|
||||
method(name: 'warnError', type: 'Object', params: [message: java.lang.String, body: 'Closure'], doc: 'Catch error and set build and stage result to unstable')
|
||||
method(name: 'warnError', type: 'Object', params: [message: String, body: 'Closure'], doc: 'Catch error and set build and stage result to unstable')
|
||||
method(name: 'warnError', type: 'Object', params: [body: Closure], namedParams: [parameter(name: 'message', type: 'java.lang.String'), parameter(name: 'catchInterruptions', type: 'boolean'),], doc: 'Catch error and set build and stage result to unstable')
|
||||
method(name: 'withCredentials', type: 'Object', params: [bindings: Map, body: 'Closure'], doc: 'Bind credentials to variables')
|
||||
method(name: 'withCredentials', type: 'Object', params: [bindings: List, body: 'Closure'], doc: 'Bind credentials to variables')
|
||||
method(name: 'string', type: 'Object', namedParams: [parameter(name: 'credentialsId', type: String), parameter(name: 'variable', type: String)], doc: 'Bind secret text credentials to variable')
|
||||
method(name: 'withEnv', type: 'Object', params: [overrides: Map, body: 'Closure'], doc: 'Set environment variables')
|
||||
method(name: 'ws', type: 'Object', params: [dir: java.lang.String, body: 'Closure'], doc: 'Allocate workspace')
|
||||
method(name: 'ws', type: 'Object', params: [dir: String, body: 'Closure'], doc: 'Allocate workspace')
|
||||
method(name: 'dockerFingerprintRun', type: 'Object', params: [containerId: 'java.lang.String'], doc: 'Advanced/Deprecated Record trace of a Docker image run in a container')
|
||||
method(name: 'dockerFingerprintRun', type: 'Object', namedParams: [parameter(name: 'containerId', type: 'java.lang.String'), parameter(name: 'toolName', type: 'java.lang.String'),], doc: 'Record trace of a Docker image run in a container')
|
||||
method(name: 'envVarsForTool', type: 'Object', namedParams: [parameter(name: 'toolId', type: 'java.lang.String'), parameter(name: 'toolVersion', type: 'java.lang.String'),], doc: 'Fetches the environment variables for a given tool in a list of \'FOO=bar\' strings suitable for the withEnv step.')
|
||||
method(name: 'getContext', type: 'Object', params: [type: 'Map'], doc: 'Advanced/Deprecated Get contextual object from internal APIs')
|
||||
method(name: 'podTemplate', type: 'Object', params: [body: Closure], namedParams: [parameter(name: 'label', type: 'java.lang.String'), parameter(name: 'name', type: 'java.lang.String'), parameter(name: 'activeDeadlineSeconds', type: 'int'), parameter(name: 'annotations', type: 'Map'), parameter(name: 'cloud', type: 'java.lang.String'), parameter(name: 'containers', type: 'Map'), parameter(name: 'envVars', type: 'Map'), parameter(name: 'idleMinutes', type: 'int'), parameter(name: 'imagePullSecrets', type: 'Map'), parameter(name: 'inheritFrom', type: 'java.lang.String'), parameter(name: 'instanceCap', type: 'int'), parameter(name: 'namespace', type: 'java.lang.String'), parameter(name: 'nodeSelector', type: 'java.lang.String'), parameter(name: 'nodeUsageMode', type: 'java.lang.String'), parameter(name: 'podRetention', type: 'Map'), parameter(name: 'serviceAccount', type: 'java.lang.String'), parameter(name: 'slaveConnectTimeout', type: 'int'), parameter(name: 'volumes', type: 'Map'), parameter(name: 'workingDir', type: 'java.lang.String'), parameter(name: 'workspaceVolume', type: 'Map'), parameter(name: 'yaml', type: 'java.lang.String'),], doc: 'Define a podTemplate to use in the kubernetes plugin')
|
||||
method(name: 'withContext', type: 'Object', params: [context: java.lang.Object, body: 'Closure'], doc: 'Advanced/Deprecated Use contextual object from internal APIs within a block')
|
||||
method(name: 'withContext', type: 'Object', params: [context: Object, body: 'Closure'], doc: 'Advanced/Deprecated Use contextual object from internal APIs within a block')
|
||||
method(name: 'httpRequest', type: 'jenkins.plugins.http_request.ResponseContentSupplier', params: [url:'java.lang.String'], doc: 'Perform an HTTP Request and return a response object')
|
||||
method(name: 'httpRequest', type: 'jenkins.plugins.http_request.ResponseContentSupplier', namedParams: [parameter(name: 'url', type: 'java.lang.String'), parameter(name: 'acceptType', type: 'Map'), parameter(name: 'authentication', type: 'java.lang.String'), parameter(name: 'consoleLogResponseBody', type: 'java.lang.Boolean'), parameter(name: 'contentType', type: 'jenkins.plugins.http_request.MimeType'), parameter(name: 'customHeaders', type: 'java.util.List'), parameter(name: 'formData', type: 'java.util.List'), parameter(name: 'httpMode', type: 'jenkins.plugins.http_request.HttpMode'), parameter(name: 'httpProxy', type: 'java.lang.String'), parameter(name: 'ignoreSslErrors', type: 'boolean'), parameter(name: 'multipartName', type: 'java.lang.String'), parameter(name: 'outputFile', type: 'java.lang.String'), parameter(name: 'proxyAuthentication', type: 'java.lang.String'), parameter(name: 'quiet', type: 'java.lang.Boolean'), parameter(name: 'requestBody', type: 'java.lang.String'), parameter(name: 'responseHandle', type: 'Map'), parameter(name: 'timeout', type: 'java.lang.Integer'), parameter(name: 'uploadFile', type: 'java.lang.String'), parameter(name: 'useNtlm', type: 'boolean'), parameter(name: 'useSystemProperties', type: 'java.lang.Boolean'), parameter(name: 'validResponseCodes', type: 'java.lang.String'), parameter(name: 'validResponseContent', type: 'java.lang.String'), parameter(name: 'wrapAsMultipart', type: 'boolean'), ], doc: 'Perform an HTTP Request and return a response object')
|
||||
property(name: 'docker', type: 'org.jenkinsci.plugins.docker.workflow.DockerDSL')
|
||||
property(name: 'pipeline', type: 'org.jenkinsci.plugins.pipeline.modeldefinition.ModelStepLoader')
|
||||
property(name: 'env', type: 'org.jenkinsci.plugins.workflow.cps.EnvActionImpl.Binder')
|
||||
@ -82,6 +86,17 @@ contributor(ctx) {
|
||||
property(name: 'currentBuild', type: 'org.jenkinsci.plugins.workflow.cps.RunWrapperBinder')
|
||||
property(name: 'scm', type: 'org.jenkinsci.plugins.workflow.multibranch.SCMVar')
|
||||
}
|
||||
|
||||
//Steps that require a options context
|
||||
def optionsCtx = context(scope: closureScope())
|
||||
contributor(optionsCtx) {
|
||||
def call = enclosingCall('options')
|
||||
if (call) {
|
||||
method(name: 'timestamps', type: 'Object', params: [], doc: 'Timestamps')
|
||||
method(name: 'timeout', type: 'Object', namedParams: [parameter(name: 'time', type: 'java.lang.Integer'), parameter(name: 'activity', type: 'boolean'), parameter(name: 'unit', type: 'java.util.concurrent.TimeUnit'),], doc: 'Enforce time limit')
|
||||
}
|
||||
}
|
||||
|
||||
//Steps that require a node context
|
||||
def nodeCtx = context(scope: closureScope())
|
||||
contributor(nodeCtx) {
|
||||
@ -92,7 +107,7 @@ contributor(nodeCtx) {
|
||||
method(name: 'containerLog', type: 'Object', params: [name: 'java.lang.String'], doc: 'Get container log from Kubernetes')
|
||||
method(name: 'containerLog', type: 'Object', namedParams: [parameter(name: 'name', type: 'java.lang.String'), parameter(name: 'limitBytes', type: 'int'), parameter(name: 'returnLog', type: 'boolean'), parameter(name: 'sinceSeconds', type: 'int'), parameter(name: 'tailingLines', type: 'int'),], doc: 'Get container log from Kubernetes')
|
||||
method(name: 'deleteDir', type: 'Object', params: [:], doc: 'Recursively delete the current directory from the workspace')
|
||||
method(name: 'dir', type: 'Object', params: [path: java.lang.String, body: 'Closure'], doc: 'Change current directory')
|
||||
method(name: 'dir', type: 'Object', params: [path: String, body: 'Closure'], doc: 'Change current directory')
|
||||
method(name: 'fileExists', type: 'Object', params: [file: 'java.lang.String'], doc: 'Verify if file exists in workspace')
|
||||
method(name: 'findFiles', type: 'Object', params: [:], doc: 'Find files in the workspace')
|
||||
method(name: 'findFiles', type: 'Object', namedParams: [parameter(name: 'excludes', type: 'java.lang.String'), parameter(name: 'glob', type: 'java.lang.String'),], doc: 'Find files in the workspace')
|
||||
@ -122,7 +137,7 @@ contributor(nodeCtx) {
|
||||
method(name: 'step', type: 'Object', params: [delegate: 'Map'], doc: 'General Build Step')
|
||||
method(name: 'svn', type: 'Object', params: [url: 'java.lang.String'], doc: 'Subversion')
|
||||
method(name: 'svn', type: 'Object', namedParams: [parameter(name: 'url', type: 'java.lang.String'), parameter(name: 'changelog', type: 'boolean'), parameter(name: 'poll', type: 'boolean'),], doc: 'Subversion')
|
||||
method(name: 'tee', type: 'Object', params: [file: java.lang.String, body: 'Closure'], doc: 'Tee output to file')
|
||||
method(name: 'tee', type: 'Object', params: [file: String, body: 'Closure'], doc: 'Tee output to file')
|
||||
method(name: 'tm', type: 'Object', params: [stringWithMacro: 'java.lang.String'], doc: 'Expand a string containing macros')
|
||||
method(name: 'touch', type: 'Object', params: [file: 'java.lang.String'], doc: 'Create a file (if not already exist) in the workspace, and set the timestamp')
|
||||
method(name: 'touch', type: 'Object', namedParams: [parameter(name: 'file', type: 'java.lang.String'), parameter(name: 'timestamp', type: 'java.lang.Long'),], doc: 'Create a file (if not already exist) in the workspace, and set the timestamp')
|
||||
@ -141,12 +156,12 @@ contributor(nodeCtx) {
|
||||
method(name: 'zip', type: 'Object', namedParams: [parameter(name: 'zipFile', type: 'java.lang.String'), parameter(name: 'archive', type: 'boolean'), parameter(name: 'dir', type: 'java.lang.String'), parameter(name: 'glob', type: 'java.lang.String'),], doc: 'Create Zip file')
|
||||
method(name: 'archive', type: 'Object', params: [includes: 'java.lang.String'], doc: 'Advanced/Deprecated Archive artifacts')
|
||||
method(name: 'archive', type: 'Object', namedParams: [parameter(name: 'includes', type: 'java.lang.String'), parameter(name: 'excludes', type: 'java.lang.String'),], doc: 'Archive artifacts')
|
||||
method(name: 'container', type: 'Object', params: [name: java.lang.String, body: 'Closure'], doc: 'Advanced/Deprecated Run build steps in a container')
|
||||
method(name: 'container', type: 'Object', params: [name: String, body: 'Closure'], doc: 'Advanced/Deprecated Run build steps in a container')
|
||||
method(name: 'container', type: 'Object', params: [body: Closure], namedParams: [parameter(name: 'name', type: 'java.lang.String'), parameter(name: 'shell', type: 'java.lang.String'),], doc: 'Run build steps in a container')
|
||||
method(name: 'dockerFingerprintFrom', type: 'Object', namedParams: [parameter(name: 'dockerfile', type: 'java.lang.String'), parameter(name: 'image', type: 'java.lang.String'), parameter(name: 'commandLine', type: 'java.lang.String'), parameter(name: 'toolName', type: 'java.lang.String'),], doc: 'Record trace of a Docker image used in FROM')
|
||||
method(name: 'unarchive', type: 'Object', params: [:], doc: 'Advanced/Deprecated Copy archived artifacts into the workspace')
|
||||
method(name: 'unarchive', type: 'Object', namedParams: [parameter(name: 'mapping', type: 'Map'),], doc: 'Copy archived artifacts into the workspace')
|
||||
method(name: 'withDockerContainer', type: 'Object', params: [image: java.lang.String, body: 'Closure'], doc: 'Advanced/Deprecated Run build steps inside a Docker container')
|
||||
method(name: 'withDockerContainer', type: 'Object', params: [image: String, body: 'Closure'], doc: 'Advanced/Deprecated Run build steps inside a Docker container')
|
||||
method(name: 'withDockerContainer', type: 'Object', params: [body: Closure], namedParams: [parameter(name: 'image', type: 'java.lang.String'), parameter(name: 'args', type: 'java.lang.String'), parameter(name: 'toolName', type: 'java.lang.String'),], doc: 'Run build steps inside a Docker container')
|
||||
method(name: 'withDockerRegistry', type: 'Object', params: [registry: Map, body: 'Closure'], doc: 'Advanced/Deprecated Sets up Docker registry endpoint')
|
||||
method(name: 'withDockerRegistry', type: 'Object', params: [body: Closure], namedParams: [parameter(name: 'registry', type: 'Map'), parameter(name: 'toolName', type: 'java.lang.String'),], doc: 'Sets up Docker registry endpoint')
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ 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.VRunner
|
||||
import ru.pulsar.jenkins.library.utils.VersionParser
|
||||
|
||||
@ -14,8 +15,6 @@ import static ru.pulsar.jenkins.library.configuration.Secrets.UNKNOWN_ID
|
||||
|
||||
class InitFromStorage implements Serializable {
|
||||
|
||||
final static REPO_SLUG_REGEXP = ~/(?m)^(?:[^:\/?#\n]+:)?(?:\/+[^\/?#\n]*)?\/?([^?\n]*)/
|
||||
|
||||
private final JobConfiguration config
|
||||
|
||||
InitFromStorage(JobConfiguration config) {
|
||||
@ -37,8 +36,7 @@ class InitFromStorage implements Serializable {
|
||||
String storageVersion = VersionParser.storage()
|
||||
String storageVersionParameter = storageVersion == "" ? "" : "--storage-ver $storageVersion"
|
||||
|
||||
EnvironmentAction env = steps.env();
|
||||
String repoSlug = computeRepoSlug(env.GIT_URL)
|
||||
String repoSlug = RepoUtils.getRepoSlug()
|
||||
|
||||
Secrets secrets = config.secrets
|
||||
|
||||
@ -61,14 +59,4 @@ class InitFromStorage implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
@NonCPS
|
||||
private static String computeRepoSlug(String text) {
|
||||
def matcher = text =~ REPO_SLUG_REGEXP
|
||||
String repoSlug = matcher != null && matcher.getCount() == 1 ? matcher[0][1] : ""
|
||||
if (repoSlug.endsWith(".git")) {
|
||||
repoSlug = repoSlug[0..-5]
|
||||
}
|
||||
repoSlug = repoSlug.replace('/', '_')
|
||||
return repoSlug
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import ru.pulsar.jenkins.library.configuration.JobConfiguration
|
||||
import ru.pulsar.jenkins.library.ioc.ContextRegistry
|
||||
import ru.pulsar.jenkins.library.utils.FileUtils
|
||||
import ru.pulsar.jenkins.library.utils.Logger
|
||||
import ru.pulsar.jenkins.library.utils.StringJoiner
|
||||
import ru.pulsar.jenkins.library.utils.VRunner
|
||||
|
||||
class SmokeTest implements Serializable {
|
||||
@ -61,14 +62,14 @@ class SmokeTest implements Serializable {
|
||||
FilePath pathToAllureReport = FileUtils.getFilePath("$env.WORKSPACE/$allureReport")
|
||||
String allureReportDir = FileUtils.getLocalPath(pathToAllureReport.getParent())
|
||||
|
||||
StringBuilder reportsConfigConstructor = new StringBuilder()
|
||||
StringJoiner reportsConfigConstructor = new StringJoiner(";")
|
||||
|
||||
if (options.publishToJUnitReport) {
|
||||
steps.createDir(junitReportDir)
|
||||
|
||||
String junitReportCommand = "ГенераторОтчетаJUnitXML{$junitReport}"
|
||||
|
||||
reportsConfigConstructor.append(junitReportCommand)
|
||||
reportsConfigConstructor.add(junitReportCommand)
|
||||
}
|
||||
|
||||
if (options.publishToAllureReport) {
|
||||
@ -76,10 +77,7 @@ class SmokeTest implements Serializable {
|
||||
|
||||
String allureReportCommand = "ГенераторОтчетаAllureXMLВерсия2{$allureReport}"
|
||||
|
||||
if (reportsConfigConstructor.length() > 0) {
|
||||
reportsConfigConstructor.append(';')
|
||||
}
|
||||
reportsConfigConstructor.append(allureReportCommand)
|
||||
reportsConfigConstructor.add(allureReportCommand)
|
||||
}
|
||||
|
||||
if (reportsConfigConstructor.length() > 0) {
|
||||
|
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}$');
|
||||
}
|
||||
}
|
24
src/ru/pulsar/jenkins/library/utils/RepoUtils.groovy
Normal file
24
src/ru/pulsar/jenkins/library/utils/RepoUtils.groovy
Normal file
@ -0,0 +1,24 @@
|
||||
package ru.pulsar.jenkins.library.utils
|
||||
|
||||
import com.cloudbees.groovy.cps.NonCPS
|
||||
|
||||
class RepoUtils implements Serializable {
|
||||
|
||||
private final static REPO_SLUG_REGEXP = ~/(?m)^(?:[^:\/?#\n]+:)?(?:\/+[^\/?#\n]*)?\/?([^?\n]*)/
|
||||
private static String REPO_SLUG;
|
||||
|
||||
@NonCPS
|
||||
static void computeRepoSlug(String text) {
|
||||
def matcher = text =~ REPO_SLUG_REGEXP
|
||||
String repoSlug = matcher != null && matcher.getCount() == 1 ? matcher[0][1] : ""
|
||||
if (repoSlug.endsWith(".git")) {
|
||||
repoSlug = repoSlug[0..-5]
|
||||
}
|
||||
|
||||
REPO_SLUG = repoSlug.replace('/', '_')
|
||||
}
|
||||
|
||||
static String getRepoSlug() {
|
||||
return REPO_SLUG
|
||||
}
|
||||
}
|
37
src/ru/pulsar/jenkins/library/utils/StringJoiner.groovy
Normal file
37
src/ru/pulsar/jenkins/library/utils/StringJoiner.groovy
Normal file
@ -0,0 +1,37 @@
|
||||
package ru.pulsar.jenkins.library.utils
|
||||
|
||||
class StringJoiner implements Serializable {
|
||||
private final String delimiter
|
||||
private StringBuilder value
|
||||
|
||||
StringJoiner(CharSequence delimiter) {
|
||||
this.delimiter = delimiter
|
||||
}
|
||||
|
||||
StringJoiner add(CharSequence newElement) {
|
||||
prepareBuilder().append(newElement)
|
||||
return this
|
||||
}
|
||||
|
||||
int length() {
|
||||
return (value != null ? value.length() : 0)
|
||||
}
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
if (value == null) {
|
||||
return ""
|
||||
} else {
|
||||
return value.toString()
|
||||
}
|
||||
}
|
||||
|
||||
private StringBuilder prepareBuilder() {
|
||||
if (value != null) {
|
||||
value.append(delimiter)
|
||||
} else {
|
||||
value = new StringBuilder()
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
import groovy.transform.Field
|
||||
import ru.pulsar.jenkins.library.configuration.JobConfiguration
|
||||
import ru.pulsar.jenkins.library.configuration.SourceFormat
|
||||
import ru.pulsar.jenkins.library.utils.RepoUtils
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@ -40,6 +41,7 @@ void call() {
|
||||
config = jobConfiguration() as JobConfiguration
|
||||
agent1C = config.v8AgentLabel()
|
||||
agentEdt = config.edtAgentLabel()
|
||||
RepoUtils.computeRepoSlug(env.GIT_URL)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -236,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…
x
Reference in New Issue
Block a user