#jenkins #groovy #ansible #continuous-integration
#Дженкинс #groovy #ansible #непрерывная интеграция
Вопрос:
В Jenkins я хотел бы проанализировать раздел вывода ansible playbook «Play Recap» для поиска ошибочных имен хостов. Я хочу поместить информацию в электронное письмо или другое уведомление. Это также можно использовать для запуска другого задания Дженкинса.
В настоящее время я отправляю ansible-playbook в качестве задания jenkins для развертывания программного обеспечения в нескольких системах. Я использую сценарий конвейера Дженкинса, который необходимо было реализовать для правильного применения sshagent.
pipeline {
agent any
options {
ansiColor('xterm')
}
stages {
stage("setup environment") {
steps {
deleteDir()
} //steps
} //stage - setup environment
stage("clone the repo") {
environment {
GIT_SSH_COMMAND = "ssh -o StrictHostKeyChecking=no"
} //environment
steps {
sshagent(['my_git']) {
sh "git clone ssh://git@github.com/~usr/ansible.git"
} //sshagent
} //steps
} //stage - clone the repo
stage("run ansible playbook") {
steps {
sshagent (credentials: ['apps']) {
withEnv(['ANSIBLE_CONFIG=ansible.cfg']) {
dir('ansible') {
ansiblePlaybook(
becomeUser: null,
colorized: true,
credentialsId: 'apps',
disableHostKeyChecking: true,
forks: 50,
hostKeyChecking: false,
inventory: 'hosts',
limit: 'production:amp;*generic',
playbook: 'demo_play.yml',
sudoUser: null,
extras: '-vvvvv'
) //ansiblePlaybook
} //dir
} //withEnv
} //sshagent
} //steps
} //stage - run ansible playbook
} //stages
post {
failure {
emailext body: "Please go to ${env.BUILD_URL}/consoleText for more details.",
recipientProviders: [[$class: 'DevelopersRecipientProvider'], [$class: 'RequesterRecipientProvider']],
subject: "${env.JOB_NAME}",
to: 'our.dev.team@gmail.com',
attachLog: true
office365ConnectorSend message:"A production system appears to be unreachable.",
status:"Failed",
color:"f00000",
factDefinitions: [[name: "Credentials ID", template: "apps"],
[name: "Build Duration", template: "${currentBuild.durationString}"],
[name: "Full Name", template: "${currentBuild.fullDisplayName}"]],
webhookUrl:'https://outlook.office.com/webhook/[really long alphanumeric key]/IncomingWebhook/[another super-long alphanumeric key]'
} //failure
} //post
} //pipeline
Существует несколько плагинов Jenkins для анализа вывода консоли, но ни один из них не позволит мне захватывать и использовать текст. Я посмотрел на анализатор журналов и поиск текста.
Единственное, что у меня есть, это использование groovy для написания этого сценария.
https://devops.stackexchange.com/questions/5363/jenkins-groovy-to-parse-console-output-and-mark-build-failure
Примером «Воспроизведения резюме» в выводе консоли является:
PLAY RECAP **************************************************************************************************************************************************
some.host.name : ok=25 changed=2 unreachable=0 failed=1 skipped=2 rescued=0 ignored=0
some.ip.address : ok=22 changed=2 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
Я пытаюсь получить либо список, либо строку с разделителями для каждого хоста, который выходит из строя. Хотя в случае списка мне нужно выяснить, как отправлять несколько уведомлений.
Если кто-нибудь может помочь мне с полным решением, я был бы очень признателен за вашу помощь.
Комментарии:
1. Пожалуйста, добавьте код, который вы пробовали, и как он не удался (например, ошибки, трассировки стека, журналы, …), Чтобы мы могли его улучшить.
2. Привет @cfrick, я не получаю никакой ошибки. И инструменты, которые я нашел, не имеют возможностей, которые я ищу. Я просто надеюсь, что кто-нибудь поможет мне извлечь информацию из вывода консоли, чтобы я мог использовать ее в электронном письме и сообщении.
Ответ №1:
Вопрос: «Проанализируйте раздел вывода ansible playbook’Play Recap'».
Ответ: используйте обратный вызов json и проанализируйте вывод с помощью jq. Например
shell> ANSIBLE_STDOUT_CALLBACK=json ansible-playbook pb.yml | jq .stats
Комментарии:
1. jq, безусловно, классный инструмент, которого я раньше не видел. Я не уверен, что смогу применить его в своем скрипте конвейера Дженкинса. Я обновил свое описание, чтобы включить эти детали. Большое вам спасибо за то, что показали мне что-то новое!
Ответ №2:
Есть несколько «подводных камней», с которыми я столкнулся, когда решал эту проблему.
-
Единственным успешным способом, которым я мог получить доступ к выводам плагина ansible, было получение необработанного файла журнала.
def log = currentBuild.rawBuild.getLog(100)
В этом случае я извлек только последние 100 строк, так как я ищу только окно воспроизведения резюме. Для этого метода требуются специальные разрешения. В журнале консоли отобразится ошибка и будет предоставлена ссылка, по которой можно разрешить функции. -
Вывод ansible не должен быть раскрашен.
colorized: false
Раскрашенный вывод довольно сложно разобрать. «Журнал консоли» не показывает вам раскрашенную разметку, однако, если вы посмотрите на «consoleText», вы увидите это. -
При использовании регулярных выражений у вас, скорее всего, будет
matcher
объект, который не является сериализуемым. Чтобы использовать это в Jenkins, возможно, потребуется поместить его в функцию с тегом@NonCPS
, который останавливает попытки Дженкинса сериализовать объект. У меня были смешанные результаты с необходимостью этого, поэтому я не совсем понимаю, где это требуется. -
Оператор регулярного выражения был для меня одной из самых сложных частей. Я придумал общий оператор, который можно легко изменить для разных сценариев, например, сбой или недостижимость. Мне также больше повезло с использованием регулярного выражения в стиле «косой черты» в groovy, которое помещает косую черту на обоих концах инструкции без необходимости каких-либо кавычек. Вы заметите, что часть «сбой» отличается
failed=([1-9]|[1-9][0-9])
, так что она соответствует только оператору, в котором сбой не равен нулю.
/([0-9a-zA-Z.-] )(?=[ ]*:[ ]*ok=([0-9]|[1-9][0-9])[ ]*changed=([0-9]|[1-9][0-9])[ ]*unreachable=([0-9]|[1-9][0-9])[ ]*failed=([1-9]|[1-9][0-9]))/
Вот полный код конвейера, который я придумал.
pipeline {
agent any
options {
ansiColor('xterm')
}
stages {
stage("setup environment") {
steps {
deleteDir()
} //steps
} //stage - setup environment
stage("clone the repo") {
environment {
GIT_SSH_COMMAND = "ssh -o StrictHostKeyChecking=no"
} //environment
steps {
sshagent(['my_git']) {
sh "git clone ssh://git@github.com/~usr/ansible.git"
} //sshagent
} //steps
} //stage - clone the repo
stage("run ansible playbook") {
steps {
sshagent (credentials: ['apps']) {
withEnv(['ANSIBLE_CONFIG=ansible.cfg']) {
dir('ansible') {
ansiblePlaybook(
becomeUser: null,
colorized: false,
credentialsId: 'apps',
disableHostKeyChecking: true,
forks: 50,
hostKeyChecking: false,
inventory: 'hosts',
limit: 'production:amp;*generic',
playbook: 'demo_play.yml',
sudoUser: null,
extras: '-vvvvv'
) //ansiblePlaybook
} //dir
} //withEnv
} //sshagent
} //steps
} //stage - run ansible playbook
} //stages
post {
failure {
script {
problem_hosts = get_the_hostnames()
}
emailext body: "${problem_hosts} has failed. Please go to ${env.BUILD_URL}/consoleText for more details.",
recipientProviders: [[$class: 'DevelopersRecipientProvider'], [$class: 'RequesterRecipientProvider']],
subject: "${env.JOB_NAME}",
to: 'our.dev.team@gmail.com',
attachLog: true
office365ConnectorSend message:"${problem_hosts} has failed.",
status:"Failed",
color:"f00000",
factDefinitions: [[name: "Credentials ID", template: "apps"],
[name: "Build Duration", template: "${currentBuild.durationString}"],
[name: "Full Name", template: "${currentBuild.fullDisplayName}"]],
webhookUrl:'https://outlook.office.com/webhook/[really long alphanumeric key]/IncomingWebhook/[another super-long alphanumeric key]'
} //failure
} //post
} //pipeline
//@NonCPS
def get_the_hostnames() {
// Get the last 100 lines of the log
def log = currentBuild.rawBuild.getLog(100)
print log
// GREP the log for the failed hostnames
def matches = log =~ /([0-9a-zA-Z.-] )(?=[ ]*:[ ]*ok=([0-9]|[1-9][0-9])[ ]*changed=([0-9]|[1-9][0-9])[ ]*unreachable=([0-9]|[1-9][0-9])[ ]*failed=([1-9]|[1-9][0-9]))/
def hostnames = null
// if any matches occurred
if (matches) {
// iterate over the matches
for (int i = 0; i < matches.size(); i ) {
// if there is a name, concatenate it
// else populate it
if (hostnames?.trim()) {
hostnames = hostnames " " matches[i]
} else {
hostnames = matches[i][0]
} // if/else
} // for
} // if
if (!hostnames?.trim()) {
hostnames = "No hostnames identified."
}
return hostnames
}