DEV Community

Stefano Martins
Stefano Martins

Posted on

Como continuar a execução de um build do Jenkins quando um stage falha

A grande maioria das pessoas irá trabalhar com esteiras relativamente lineares do Jenkins - ainda que estas esteiras chamem outras esteiras, que chamem outras esteiras, e outras, e assim por diante, criando verdadeiros monstros dos espaguetis voadores.

Entretanto há uma coisa que Jenkins - seja por design ou por qualquer outro motivo inexplicável - simplesmente não faz bem: manter controle sobre os status dos stages durante a execução do build. Uma das consequências disso é o fato de que o comportamento padrão do Jenkins é falhar uma esteira quando qualquer um dos seus stages falha. E cabe a você, a pessoa que está desenvolvendo a esteira, cuidar disso.

Durante este post, irei abordar alguns cenários comuns e algumas soluções possíveis para os mesmos.

Primeiro cenário: continuar um build mesmo quando um stage falha

Esta é uma pergunta muito comum na stack mais famosa do mundo. Imagine que você possui um stage que pode falhar, mas que não necessariamente impedirá a execução da esteira como um todo. Se você ama Java, você simplesmente irá adorar o quê vem a seguir.

Para o cenário, iremos trabalhar com a seguinte pipeline:

pipeline {
    agent any

    stages {
        stage('Primeiro stage') {
            steps {
                script {
                    git credentialsId: 'Bitbucket', url: 'https://bitbucket.org/stefano/bla.git' 
                }
            }
        }

        stage('Segundo stage') {
            steps {
                script {
                    print('Ooopsie, nunca vou rodar! :(')
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Esta pipeline intencionalmente irá falhar, uma vez que o repositório especificado na linha 8 não existe. Na apresentação visual da pipeline, teremos algo parecido com isto:

image

Primeira solução: utilizar um bloco try {} catch() {}

Podemos utilizar um bloco try {} catch() {} ao redor do trecho passível de erro.

pipeline {
    agent any

    stages {
        stage('Primeiro stage') {
            steps {
                script {
                    try {
                        git credentialsId: 'Bitbucket', url: 'https://bitbucket.org/stefano/bla.git' 
                    }
                    catch(Exception e) {
                        print("${e}")
                    }
                }
            }
        }

        stage('Segundo stage') {
            steps {
                script {
                    print('Ooopsie, nunca vou rodar! :(')
                    print('Opa pera! :)')
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

E a apresentação da pipeline:

image

Como pode-se observar, ambos os stages são executados, contudo o primeiro é apresentado com sucesso, ainda que o comando tenha apresentado falha. Isso é uma questão de semântica: houve um erro em tempo de execução da pipeline e houve uma tratativa quanto a ele, logo, há o entendimento de que está tudo bem e que o stage deve ser marcado como SUCCESS.

Segunda solução: Quando queremos marcar o stage como FAILED

Caso desejemos marcar aquele stage como FAILED, devemos utilizar um bloco catchError(), podendo utilizá-lo juntamente com um bloco try {} catch() {} ou sozinho. O quê o catchError() faz é pegar o resultado de um step (ou steps) que estão dentro do bloco e caso algum destes termine em erro, ele setará os status do stage ou o build conforme o seu desejo, normalmente sendo como SUCCESS ou FAILURE. Abaixo dois exemplos, um não aninhado e o outro com um bloco try {} catch() {}, assim como suas respectivas execuções:

pipeline {
    agent any

    stages {
        stage('Primeiro stage') {
            steps {
                script {
                    catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
                        git credentialsId: 'Bitbucket', url: 'https://bitbucket.org/stefano/bla.git' 
                    }
                }
            }
        }

        stage('Segundo stage') {
            steps {
                script {
                    print('Ooopsie, nunca vou rodar! :(')
                    print('Opa, vou sim! :)')
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

image

pipeline {
    agent any

    stages {
        stage('Primeiro stage') {
            steps {
                script {
                    try {
                        sh 'cat /tmp'
                    }
                    catch(Exception e) {
                        print("${e}")
                        catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
                            sh 'exit 1'
                        }
                    }
                }
            }
        }

        stage('Segundo stage') {
            steps {
                script {
                    print('Ooopsie, nunca vou rodar! :(')
                    print('Opa, vou sim!')
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

image

Segundo cenário: Execução de um stage de forma condicional

Imagine que você deseja que um stage seja executado condicionalmente, dependendo do status do anterior. A primeira solução que nos vêm à mente é trabalhar com blocos when {}. Infelizmente, ela não é viável, pois os mesmos são analisados no começo da execução da pipeline.

A segunda solução é criar algumas variáveis de ambiente em bloco environment {} e analisá-las de um bloco para o outro, o quê também não daria certo, pois não conseguimos trocar seus valores.

Chegamos então à uma solução possível que é: Arquivos texto. Abaixo um exemplo:

pipeline {
    agent any

    stages {
        stage("stage1") {
            steps {
                script {
                    try {
                        sh 'cat /tmp'
                        writeFile encoding: 'utf-8', file: 'stageStatus', text: 'SUCCESS'
                    }
                    catch(Exception e) {
                        writeFile encoding: 'utf-8', file: 'stageStatus', text: 'FAILED'
                        print("Ooopsie!")
                        catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
                            sh 'exit 1'
                        }
                    }
                }
            }
        }

        stage("stage2") {
            steps {
                script {
                    def previousStageStatus = readFile encoding: 'utf-8', file: 'stageStatus'
                    if (previousStageStatus == 'FAILED') {
                        print('The previous stage failed')
                    }
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Nele, estamos utilizando as funções writeFile() e readFile() para respectivamente escrever e ler o nosso arquivo texto, que conterá o status do primeiro stage e, caso seja FAILURE, o corpo do segundo stage será executado.

O único problema desta solução - que não é grande, pelo menos da minha perspectiva - é que não importa o quê aconteça, o segundo stage sempre será executado, ainda que ele não faça nada.

image
Pipeline quando o stage é disparado

image
Pipeline quando o stage não é disparado

Bom pessoal, por hoje é só. Espero que tenham gostado!

Abraços!

Top comments (0)