Анализировать и возвращать вывод консоли Дженкинса

#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:

Есть несколько «подводных камней», с которыми я столкнулся, когда решал эту проблему.

  1. Единственным успешным способом, которым я мог получить доступ к выводам плагина ansible, было получение необработанного файла журнала. def log = currentBuild.rawBuild.getLog(100) В этом случае я извлек только последние 100 строк, так как я ищу только окно воспроизведения резюме. Для этого метода требуются специальные разрешения. В журнале консоли отобразится ошибка и будет предоставлена ссылка, по которой можно разрешить функции.

  2. Вывод ansible не должен быть раскрашен. colorized: false Раскрашенный вывод довольно сложно разобрать. «Журнал консоли» не показывает вам раскрашенную разметку, однако, если вы посмотрите на «consoleText», вы увидите это.

  3. При использовании регулярных выражений у вас, скорее всего, будет matcher объект, который не является сериализуемым. Чтобы использовать это в Jenkins, возможно, потребуется поместить его в функцию с тегом @NonCPS , который останавливает попытки Дженкинса сериализовать объект. У меня были смешанные результаты с необходимостью этого, поэтому я не совсем понимаю, где это требуется.

  4. Оператор регулярного выражения был для меня одной из самых сложных частей. Я придумал общий оператор, который можно легко изменить для разных сценариев, например, сбой или недостижимость. Мне также больше повезло с использованием регулярного выражения в стиле «косой черты» в 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
}