Github Actions - Introdução com Java
Tags: github, actions, devops, java
Published: 2021-10-02 20:37:05

Github Actions nos permite automatizar, customizar e executar workflows do desenvolvimento de software diretamente no repositório do projeto.

Podemos:

- rodar o build continuamente
- executar testes automatizados a cada push
- executar processo de CI completo
- executar processo de CD para diversas clouds e ambientes

O Github Actions possui uma ampla biblioteca de Actions que nos permites executar diversos tipos de ações (desde simples build da aplicação até o build da sua imagem Docker e deploy no seu cluster Kubernetes na AWS).

Os jobs do Github Actions são orientados à eventos, isso significa que você pode configurar a execução de determinado job a partir de uma determinada ação (push, PR, merge) no seu repositório.

O Github Actions possui uma camada free que nos disponibiliza um tempo de execução mensal dos jobs e um período de retenção dos logs de forma limitada, detalhes podem ser acessados [aqui](https://docs.github.com/en/actions/learn-github-actions/usage-limits-billing-and-administration#usage-limits).
No plano Enterprise do Github, é possível adicionar processo de aprovação em ambientes no workflow a fim de limitar quem pode fazer o quê em determinados ambientes.

## Componentes

### Workflows

Um workflow é um procedimento declarado e automatizado que adicionamos no repositório através de um arquivo YAML.
Um workflow é composto de um ou mais jobs que podem ser agendados ou iniciados por um ou mais eventos.

### Events

Um evento é uma ação específica que pode disparar um ou mais jobs dos workflows declarados no repositório.
Essa ação pode ser um push, PR ou merge efetuado no repositório.

Na [documentação do Github](https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows), tem a lista de ações disponíveis que podemos utilizar para disparar um workflow.

### Job

Um job é composto por um ou mais steps que executam em um mesmo runner de forma sequencial a partir de determinado evento.
Abaixo segue o diagrama mostrando a estrutura do job:

![Fonte: documentação do Github](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iz52vci84k5apxzu2c3x.png)

Um workflow com mais de um job pode rodá-los em paralelos ou de forma sequencial, caso seja configurado.

![Fonte: documentação do Github](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l0counwj1a3gsvlktiax.png)

### Actions

Action são comandos isolados que são combinados em steps para criar um job.
Uma Action é o menor nível portável de um workflow, podemos criar e reutilizar Actions através de nossos repositórios ou compartilhar com a comunidade.
Um Action sempre estará dentro de um step para poder ser executado no workflow.

### Runner

Um runner é um server que tem a runner application do Github Actions instalada.
Podemos utilizar um runner hospedado pelo Github ou em nossa própria infraestrutura.
Um runner busca jobs disponíveis, roda-os e reporta o progresso, logs e resultados para o repositório no Github.

## Estrutura do Workflow

O workflow é declarado em um arquivo YAML com um esquema de declaração do Github Actions para definir os eventos, jobs e seus steps.
Esse arquivo YAML fica armazenado junto à aplicação no repositório do Github e em seu próprio diretório, localizado em `.github/workflows/`.

Quando ocorre um push no repositório, o Github verifica se existe a pasta de workflows e, caso exista algum arquivo YAML, é criado os workflows no repositório.

### Componentes do Workflow

Abaixo seguem alguns campos que podem ser utilizados no arquivo de workflow.

- `name`: campo opcional que define o nome do workflow que será exibido no repositório.
- `on`: campo que define os eventos que irão disparar o seu workflow.
- `jobs`: campo que define os jobs deste workflow. O job tem os seguintes campos:
  - `runs-on`: define o runner do job.
  - `steps`: declara os steps do job. O step tem os seguintes campos:
    - `uses`: define qual action será utilizada neste step do job.
    - `with`: podemos configurar alguns atributos que são utilizadas pela action definida.
    - `run`: define o commando que será executado no runner.

Na [documentação do Github](https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions) podemos ver toda sintaxe disponível para construir um workflow.

### Primeiro Exemplo

Abaixo segue um exemplo de workflow (arquivo `.github/workflows/sample.yml`):

```yml
name: Github Exemplo

on: [push]

jobs:
  check-bats-version:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: '14'
      - run: npm install -g bats
      - run: bats -v
```

No workflow declaramos, em `on: [push]`, que deverá rodar sempre que ocorrer um push em qualquer branch do repositório.
Também é possível limitar as branches que queremos utilizar no disparo do workflow, exemplo:

```yml
on:
  push:
    branches:
      - main # executa nos pushes na main
      - 'releases/**' # executa nas branches com prefixo `releases/`
```

Na [documentação do Github](https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows) podemos ver a variedade de opções de ações que podemos utilizar para disparar um workflow.

No workflow também definimos o nosso workflow a executar os seguintes passos:

- `uses: actions/checkout@v2`: efetua o git clone do nosso repositório;
- `uses: actions/setup-node@v2`: utiliza um action para configurar o Nodejs v14;
- `run: npm install -g bats`: executa o Npm install para instalar o bats globalmente;
- `run: bats -v`: executa o bats instalado anteriomente.

Perceba que os steps do job são executados sequencialmente e os steps seguintes dependem do step anterior.
Isso mostra que o estado do nosso runner é compartilhado dentro do job, logo, podemos instalar e configurar o runner para utilizar tudo que o nosso projeto dependa para fazer build e publicação.

Alguns exemplos:

- configurar o Docker
- configurar credenciais da nossa cloud
- configurar versões das linguagens e ferramentas

Na aba Actions do repositório no Github é possível visualizar a execução desse workflow.

![Resultado do workflow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7sg806gbjk9n9d4ueopt.png)

## Exemplo de Workflow Completo

Para concretizar melhor o poder de introduzir o Github Actions no seu ciclo de vida de desenvolvimento de software.
Vamos visualizar um exemplo de uma aplicação Java onde queremos definir um CI que faça o build, teste e publicação do nosso artefato gerado.

O nosso projeto está utilizando o Maven como gerenciado de dependências e de build. Nele temos configurado os plugins necessários para executar os testes e gerar um relatório do resultado da execução e da cobertura em HTML. Também temos configurado o nosso repositório de artefatos para utilizar o Github Artifacts.

Passos executados pelo workflow:

- faça o build da aplicação
- rode os testes da aplicação
- armazene o resultado dos testes para podemos consultar posteriomente
- efetuar publicação do nosso artefado construído para o repositório

### Workflow

Abaixo segue o arquivo que define nosso workflow com os comentários demonstrados suas capactidades:

```yml
name: Java Workflow Example - Build, Test and Deploy

# configuramos para rodar apenas quando houver um push na master
on:
  push:
    branches:
      - master

jobs:
  # `build` é o nome do job
  build:
    # nome para exibir no repositório
    name: Build, Test and Deploy

    # utilizamos uma instância do ubuntu como runner
    runs-on: ubuntu-latest

    steps:
      # utilizamos a action checkout para fazer o git clone no runner
      - uses: actions/checkout@v2

      # utilizamos a action setup-java para configurar a versão necessária do JDK
      - name: Set up JDK 11
        uses: actions/setup-java@v2
        with:
          distribution: 'adopt'
          java-version: 11
          architecture: x64

      # aqui podemos configurar um cache para cachear as dependências do projeto,
      # isso vai permitir que as próximas execuções do workflow sejam mais rápidas
      # visto que as dependências já estarão disponibilizadas para o runner
      # nota: utilizamos um hash do arquivo POM.xml, então quando o alterarmos,
      # as dependências são baixadas novamente
      - name: Cache Maven packages
        uses: actions/cache@v2
        with:
          path: ~/.m2
          key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
          restore-keys: ${{ runner.os }}-m2

      # aqui apenas definimos o commando para executar no runner
      - name: Maven Test
        run: mvn test

      # aqui criamos a pasta para armazenar o relatório dos testes
      - run: mkdir coverage-report && cp -r target/site/jacoco/* coverage-report

      # aqui utilizamos um action para registrar o resultado do teste no nosso workflow,
      # caso ocorra alguma falha em algum teste, ficará explicito nos logs de execução aba Actions
      - name: Publish Test Report
        if: ${{ always() }}
        uses: scacap/action-surefire-report@v1

      # aqui utilizamos um action para fazer a publicação do relatório de testes,
      # esse relatório pode ser baixado nos detalhes de execução do workflow
      - uses: actions/upload-artifact@v2
        with:
          name: Test Coverage Report
          path: coverage-report

      # fazemos construção e publicação do artefato construído
      - name: Maven Build and Publish
        run: mvn -B deploy
        env:
          # com estamos utilizado o Github Artifact, passamos o token do repositório para fazer upload
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      # copiamos o artefato construído para uma pasta específica
      - run: mkdir dist && cp target/*.jar dist

      # aqui fazemos o publicação do artefato para que possa ser baixado nos detalhes de execução do workflow
      - uses: actions/upload-artifact@v2
        with:
          name: Package
          path: dist
```

O workflow acima é bem útil visto que tem todo nosso ciclo de desenvolvimento junto ao repositório, facilitando a visualização e entendimento de todos.

Após executar o workflow podemos ver o seu resultado, como no exemplo abaixo:

![Resultado do workflow CI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xsf2pquhj97ehsnj57fz.png)

E o Github também nos disponibiliza algumas páginas que nos permite construir alguns dashboards para incluir no README do nosso repositório, facilitando a visualização do histórico e estado atual do projeto.

![Exemplo de dashboard de status](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g3stc8fxk4lgvefzcvy6.png)

O projeto de exemplo utilizado aqui pode ser acessado no seus [repositório no Github](https://github.com/wesleyegberto/gh-actions-java), nele está o projeto Java e outros workflows que utilizei em diferentes testes.

## Conclusão

O Github Actions é uma ferramenta bem interessante, muito versátil e de fácil customização e utilização.
A grande vantagem que vejo é a enorme biblioteca de Actions disponbilizada pela comunidade e por fornecedores (Oracle, Microsoft, Google, Github, etc) que nos permitem fazer qualquer passo necessário.

E por estar toda definida com uma sintaxe de fácil leitura e junto ao projeto, o ciclo de desenvolvimento fica claro para todos que estão na equipe, desde o estagiário até o sênior, o que facilita o onboarding e o compartilhamento de informações no time.


## Referências

- [Projeto de Exemplo](https://github.com/wesleyegberto/gh-actions-java)
- [Documentação do Github](https://docs.github.com/en/actions)
- [Cobranças e Limites](https://docs.github.com/en/actions/learn-github-actions/usage-limits-billing-and-administration#usage-limits)