Como continuar a execução de um build do Jenkins quando um stage falha
Tags: sre, devops, cicd, jenkins
Published: 2021-04-13 17:01:10

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](https://www.stackoverflow.com) 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:

```groovy
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! :(')
				}
			}
		}
	}
}
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mn3785cg0asutmczcszn.png)

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

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

```groovy
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! :)')
				}
			}
		}
	}
}
```
E a apresentação da pipeline:

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nftyjwfx6t50ve31e4py.png)

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:

```groovy
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! :)')
				}
			}
		}
	}
}
```

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sjpm1g8inyqvggnr97eq.png)

```groovy
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!')
				}
			}
		}
	}
}
```

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i7p8xh5dbghkcn5pq5rz.png)

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

```groovy
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')
                    }
                }
            }
        }
    }
}
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nrnmk897fnq3zfzex2ox.png)
*Pipeline quando o stage é disparado*

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i0ad5tuu1hmfi9znm3i6.png)
*Pipeline quando o stage não é disparado*

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

Abraços!