Criando Tasks Customizadas para Pipelines no Azure DevOps

Alex Alves
9 min readJan 19, 2021

Vamos falar um pouco de Azure DevOps?

Quem aí trabalha com esta ferramenta, seja na sua empresa ou equipe, deve ter percebido o valor e o poder dela. Você consegue fazer basicamente tudo nesta ferramenta, como gerenciamento de equipe, controle de repositórios de código e Continuous Integration/Continuous Deployment (CI/CD).

Com relação ao CI/CD, conseguimos criar nossas Pipelines utilizando tanto o modo clássico (arrastar caixinhas) quanto em YAML, além de poder fazer pipelines diferentes ou juntas. E é sobre isso que vamos aprofundar hoje!

O CI/CD é, simplesmente, a criação de pipelines que ficam responsáveis por analisar a integração de sua aplicação e realizar a publicação da mesma, de forma automatizada.

Para isso, você cria “steps”, que são os passos com as instruções que você deseja executar, como: execução de Testes Unitários, execução do Sonar e publicação da sua aplicação em algum local. Para a criação destes “steps” temos diversas opções e variedades, tanto já inclusas dentro do Azure DevOps quanto no Marketplace, como: plugins para publicação nos providers AWS, GCP ou MS (onde seria necessário o fornecimento algumas informações para a conexão e deploy).

Mas, e se o plugin de CI/CD que você precisa ainda não existir 🤔?

Foi justamente o que aconteceu comigo recentemente! E para passar o caminho das pedras para vocês, resolvi contar como eu solucionei esse desafio 😎!

Contexto/Desafio 📜

🚨 Alerta 🚨 Todo bom desenvolvedor e toda empresa consciente, sabe que testes é uma das partes mais importantes de um software e é algo indispensável de se ter. Afinal de conta, qual vai ser a sua garantia de integridade de um software que, constantemente, tem evoluções, correções e alterações sem possuir testes? Por feeling, sorte, destino? Sinto muito, mas as pessoas e, principalmente, as empresas que querem evoluir de maneira expressiva, não devem contar com esses fatores incertos.

Depois deste sermão 😁, vamos a real situação. Como podem perceber, eu sou uma pessoa que curte fazer diversos testes para tudo que eu desenvolvo, sejam Unitários, de Integração, e2e, de Carga, etc. Os testes unitários são os que tem menor custo e devem, sempre, existir o máximo possível no seu software. Assim, neles eu possuo um Code Coverage (cobertura de código), ou seja, sei qual o percentual do meu código (e por consequência, todos os fluxos) está coberto por testes. Ou seja, sei o percentual de código que eu consigo garantir uma integridade ✔.

Até aí tudo bem, tudo certo! Até recebi uma nova demanda na XP Investimentos (onde trabalho atualmente) do meu Tech Lead, Anuar Mamede:

“Alex, será que conseguimos ajudar o outro Squad (que estava desenvolvendo uma ferramenta que consolida alguns dados da pipeline) e desenvolver algo que calcule os testes integrados a partir de testes feitos no Postman?”

A ideia inicial seria responder a pergunta: “diante dos N endpoints que eu possuo em minha API, quais deles possui algum teste integrado feito no Postman?” Mas, porque não evoluir para algo mais genérico, e responder: “quantos endpoints foi testados?”, a partir de padrões do arquivo JUnit, gerado pelos testes integrados, e pelo Swagger.json da aplicação.

Solução 💡

(Pausa para o reconhecimento👏🏻) Primeiramente, gostaria de ressaltar que me baseei na ideia do meu amigo de trabalho, Alisson Jonck. Ele fez uma ferramenta similar para gerar a cobertura de testes integrados também, só que focado para testes feitos na ferramenta SupertTest.

Inicialmente, eu já estava tentando pensar em algo para incluir nas pipelines de CI/CD do Azure DevOps, para gerar os dados de maneira automatizada e funcionando de maneira genérica. Então fui para os desafios:

1️⃣ “Como descobrir os endpoints existentes em uma API?”.
Foi aí que eu reaproveitei a ideia do Alisson, onde ele conseguiu descobrir os endpoints via uma ferramenta de documentação de API chamada Swagger. Esta ferramenta, além de te dar uma rota onde você consegue, visualmente, obter todos os endpoints detalhados da sua aplicação, ele provê um JSON que contém essas informações e que é utilizado para gerar a parte visual!

2️⃣ “Como descobrir o que os seus testes de integração testaram de fato?” 😬
Como meus olhares já estavam para o Azure DevOps, eu percebi que quando você executa algum teste é gerado um arquivo contendo as informações dos resultados de cada teste! Bom, já era um grande começo! Nesse caso, resolvi estudar esse arquivo (do tipo JUnit) e, dependendo da ferramenta (Cypress Supertest, Postman, …) que executava os testes, ele era gerado de uma maneira sutilmente diferente. Com isso, identifiquei alguns padrões e então tinha um caminho: teria que achar esse arquivo, lê-lo e extrair as informações que eu precisaria🙏.

3️⃣ Com o padrão dos arquivos JUnit identificados, eu precisaria impor alguns pequenos padrões para a escrita dos testes integrados para que a extração de informações fosse mais confiável e fácil.

4️⃣ E descobrindo todos os endpoints de uma API e o que os testes integrados estavam testando, bastaria eu fazer um simples calculo para encontrar a cobertura de testes integrados 🚀.

5️⃣ Finalmente, com o caminho identificado, só bastava partir para o desenvolvimento! E como eu gostaria de deixar isso em uma pipeline, o desafio foi aprender a criar a minha própria task/step customizado, contendo todos os passos anteriores.

Show me the Code! 💻

🚨 Alerta 🚨 Antes de iniciarmos com os códigos, vale ressaltar que não irei falar sobre os scripts para compilação, a parte de CI/CD, e não irei detalhar totalmente o desenvolvimento da task customizada.

Sem mais enrolação… vamos lá! 🤓

Ferramentas 🛠

Uma custom task deve ser desenvolvida em NodeJS. Com isso, você deve baixá-lo para sua utilização. Utilizei, também, o VS Code para desenvolvimento, mas fique à vontade em utilizar sua ferramenta favorita, desde que suporte NodeJS/Javascript!

Iniciando o projeto 🐱‍👤

Com as ferramentas (NodeJS e VS Code) já instalados na máquina, vamos lá! São apenas 5 etapas.

  1. Crie o projeto:

Vá ao local onde ficará seu projeto, crie uma pasta chamada de “buildAndReleaseTask” (ou algum nome de sua preferência), entre nela, abra o terminal e digite:

npm init

Nas informações solicitadas, após digitar o comando, forneça ou deixe as informações padrões sugeridas. Esse comando irá criar o famoso arquivo package.json.:

2. Instale alguns pacotes que iremos utilizar para e desenvolvimento:

npm i azure-pipelines-task-lib
— npm i
@types/node
— npm i @types/q
— npm i typescript
— npm i request
— npm i xml2js

3. Crie o arquivo de configuração para o Typescript (ainda no mesmo diretório):

— tsc --init

Com isso, será criado o arquivo tsconfig.json, onde você poderá alterá-lo de acordo com as configurações que você deseja para o transpilador.

4. Crie o arquivo da Task

Esse é um arquivo que será muito importante para nós, o task.json. Ele conterá as informações e configurações que nossa task conterá, como nome e descrição da task e argumentos de entrada. Cole o código abaixo no seu arquivo:

Vamos entender um pouco este JSON:

  • groups: é a sessão que você define os grupos que terá na sua custom task. Dentro do grupo poderá ter N entradas de dados e você pode defini-lo. Ele seria como um accordion do bootstrap (para quem conhece bootstrap) e você consegue definir se esse agrupamento aparecerá expandido ou não. É preciso definir um nome, como se fosse um ID, e o nome de exibição o qual pode ser qualquer coisa.
  • inputs: é a sessão que iremos informar quais serão nossas entradas de dados. Conterá um tipo (string ou radio), se ela pertencerá a um grupo ou não, se ela será obrigatória ou não, qual o nome de apresentação, o label para descrição, qual será o valor padrão (caso tenha), e uma descrição de ajuda.
  • execution: nessa sessão definiremos qual engine que nossa custom task será executada e qual o arquivo será nosso startup. Além disso, pode conter alguns scripts customizados.

5. Crie o arquivo index.ts (ainda dentro do diretório)

E a estrutura final ficará assim:

No projeto original eu resolvi empregar a Programação Orientada a Objetos (POO), então eu criei algumas classes modelos, utilitários e de configurações de ambiente. Como falei acima, não irei entrar no detalhe delas… vamos seguir, vou explicar a estrutura e um pouco do desenvolvimento do index.ts!

O desenvolvimento 👨‍💻👩‍💻

Com toda a estrutura pronta, vamos colocar algumas linhas de código neste projeto. Em 2 etapas.

1. Importe as bibliotecas (que já instalamos):

  • request: para consultarmos o JSON do swagger via requisição http;
  • fs: para podermos abrir/ler o arquivo JUnit;
  • xml2js/parser: para podermos “decifrar” o arquivo JUnit.xml, onde facilitará localizarmos as tags que serão necessitadas;
  • task: responsável por criar a task customizada. Com essa lib conseguiremos, por exemplo, ler os argumentos de entrada e lançar exceções.

2. Crie uma função central:

E dentro desta função run() colocaremos todo o código (coração) da task:

Explicando um pouco melhor,

a) tratando erros:
Primeiramente, envolvemos nossa lógica em um bloco try…catch, para caso ocorra algum erro na execução. Caso ocorra algum erro, informamos que a task falhou, conforme mostrado na linha 25.

b) recuperando dados:
Nas linhas 10 a 16 é onde recuperamos todos os dados de entrada que solicitamos no task.json. Para essa recuperação dos dados é utilizado uma função onde informamos o nome do input (o mesmo que informamos no task.json) e se é ou não obrigatório.

c) acrescente sua lógica:
Após isso, toda as demais lógicas é por conta do desenvolvedor! Fica à “mão livre”. Como tinha falado, não entrarei no detalhe da lógica, pois não é o foco agora, deixei apenas os comentários do fluxo que segui.

Build e Deploy 🙌

E por fim, finalmente vamos publicar todo esse código! Em 4 etapas.

  1. Crie /faça login na sua conta

Para criar uma conta no Visual Studio Market Place, clicamos em Publish Extensions e será solicitado algumas informações:

Preencha tudo que se pede. Após isso, será criado um gerenciador de extensões.

2. Build

Agora vamos gerar o build da nossa extensão. Voltando para a nossa IDE, digite o seguinte comando:

— tsc

Com isso, será compilado o arquivo index.js a partir do index.ts.

3. Crie o manifesto

Agora, no diretório acima da pasta “buildAndReleaseTask” vamos criar nosso manifesto, que contém os dados do seu plugin. Para isso, crie o arquivo vss-extension.json e coloque o seguinte conteúdo nele:

Vamos ressaltar alguns pontos importantes:

  • publisher: é o ID que você informou ao criar o gerenciador de publicações.
  • categories: como estamos fazendo algo para o Azure DevOps e, especificamente, para as Pipelines, vamos informar isso.
  • branding: é a cor que você gostaria de expor na página da sua extensão no VS Market Place.
  • public: se sua extensão estará ou não visível.
  • tags: algumas palavras-chaves para que pessoas consigam achar a extensão mais facilmente e/ou ela aparecer nas pesquisas.
  • content.details: você pode colocar informações na página inicial e, para isso, você consegue apontar um arquivo e utilizar o Markdown para isso.
  • files.path: onde estará localizado o código .js da sua extensão
  • repository: isto é opcional, mas você pode colocar onde se encontra o código fonte da mesma para que mais pessoas usem ela como base e/ou contribuam com a evolução da task
  • contributions.properties.name: local onde está os arquivos .js.

Feito isso, ainda no diretório raiz, vamos criar a extensão .vsix do manifesto:

— tfx extension create --manifest-globs vss-extension.json --rev-version

4. Faça o upload

No VS Market Place, clique no update para abrir o modal de upload da extensão:

🚨 Pontos de atenção:

  • Sempre certifique a versão que está no seu arquivo vss-manifest.json e task.json.
  • Você consegue publicar via linha de comando também, para ser um pouco menos moroso.
  • Você consegue avançar isso até para um CI/CD completamente automatizado, via GitHub Actions.

Com isso terminamos de construir e publicar nossa extensão para o Azure DevOps Pipelines. ✨

E para resumir, o que é possível fazer com essas extensões customizadas?
- Descobrir o coverage de testes integrados a partir de testes feitos em Cypress, SuperTest e/ou Postman.
- Enviar os dados gerados para qualquer endpoint (respeitando o payload a ser passado). Para você conseguir trabalhar melhor estes dados.

Apesar do artigo ser grande, podemos perceber que em poucos passos conseguimos desenvolver e realizar o deploy facilmente, sem muitos segredos. Acredito que os desafios sejam descobrir quais diferentes tipos de entrada podem ser utilizadas para a custom task no arquivo task.json, e como fazer.

Abaixo deixo algumas referências que utilizei para o desenvolvimento, e deixo um link para um repositório que contém várias custom tasks, que peguei de base para construir o meu arquivo task.json.

E se desejar ver o código original, acesse no GitHub! 🚀🚀

Se gostou, deixe seu like 👏!
Se tem algum dúvida, faça um comentário e vamos debater 🤓!

--

--

Alex Alves

Bachelor in Computer Science, MBA in Software Architecture and .NET Developer.