GitHub Actions - Elixir
Tags: elixir, ci, cd, ptbr
Published: 2020-04-07 01:55:45

O [Actions](https://github.com/features/actions) é uma ferramenta integrada aos seus repositórios do GitHub para automatizar os seus `workflows` de CI/CD (*continuous delivery* / *continuous deploy*). Você consegue rodar esses `workflows` baseado em diversos eventos do GitHub, como um novo *push* em uma *branch* a criação de uma nova *issue* ou uma nova *release* publicada.



# Contexto

Desde que o GitHub lançou a versão beta do [Actions](https://github.com/features/actions), a [Ingresse](https://www.ingresse.com/) vem a testando. No final do ano passado o Actions saiu da versão beta e a gente decidiu finalmente migrar nossos *workflows* para lá.

Já que `Elixir` é a nossa [linguagem principal](https://dev.to/ingresse/adotando-o-elixir-na-ingresse-362m) na Ingresse, começamos a migrar primeiro os *workflows* dos serviços em `Elixir` para o Actions e aqui tem algumas dicas de como a gente faz.



# Básico

---

Antes de tudo vou apresentar um pouco do básico do [GitHub Actions](https://github.com/features/actions), se você já tem familiaridade pode seguir passar direto essa seção.

---

O Actions vai ler os arquivos `.yml` ou `.yaml` dentro da pasta `.github/workflows` na raiz do seu projeto.

Primeiro vamos criar a pasta `.github` com a pasta `workflows` dentro:

```
mkdir -p .github/workflows
```

Agora vamos criar um arquivo que a gente pode chamar de `ci.yml`:

```
touch .github/workflows/ci.yml
```

Agora vamos adicionar o seguinte conteúdo nele:

```yml
name: Meu Workflow

on: push

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v1
```

Vamos aos detalhes do conteúdo desse arquivo.

## `name`

O nome do seu *workflow*. É um campo opcional.

---

## `on`

O nome do evento do GitHub que vai iniciar esse workflow.

#### Pode ser um evento apenas:

```yml
# Vai rodar sempre que alguém realizar um push no repositório
on: push
```

#### Pode ser uma lista de eventos:

```yml
# Vai rodar sempre que alguém realizar um push no repositório
# e quando for criado um novo pull request
on: [push, pull_request]
```

#### Pode ser customizado por evento:

```yml
on:
  # Vai rodar sempre que um pull request for criado
  pull_request:
    # Mas quando estiver apontando para a master
    branches:
      - master

  # Vai rodar sempre que uma release for criada
  release:
    # Mas se ela estiver com o status de publicada
    types: 
      - published
``` 

---

## `jobs`

Um *workflow* pode ter um ou mais *jobs*. Por padrão, múltiplos jobs rodam em paralelo.

---

## `jobs.<id-do-job>.run-on`

Em que tipo de máquina o *job* vai ser executado.

#### Pode ser em um tipo de máquina:

```yml
jobs:
  test:
    runs-on: ubuntu-latest
```

#### Podem ser em múltiplos tipos de máquinas, incluindo `ubuntu`, `macos` e `windows`:

```yml
jobs:
  test:
    strategy:
      matrix:
        platform: [ubuntu-latest, macos-latest, windows-latest]

    runs-on: ${{ matrix.platform }}
```

---

## `jobs.<id-do-job>.steps`

Um *job* pode ter uma ou mais tarefas que vão ser executadas. Uma tarefa pode executar comandos, rodar setups ou rodar outras actions. O próprio GitHub tem uma série de actions publicadas que facilitam muitos trabalhos. Nesse exemplo estamos usando a action `actions/checkout` na versão `v1` para clonar o repositório do projeto.

```yml
jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      # Primeira tarefa a ser executada vai ser clonar o repositório
      - uses: actions/checkout@v1
```

---



# Próximos passos

Bom agora que a gente já sabe um pouco mais sobe a estrutura básica de um *workflow* do GitHub Actions vamos começar a moldar ele para executar por exemplo uma tarefa de `mix format` e uma tarefa de `mix test` em um projeto `Elixir`.

Para evitar ter que instalar o `Elixir` no processo de build, vamos utilizar o atributo `container` para baixar uma imagem Docker já existente.

```yml
name: Meu Workflow

on: push

jobs:
  test:
    runs-on: ubuntu-latest

    container:
      image: elixir:1.10

    steps:
      - uses: actions/checkout@v1
``` 

## `jobs.<id-do-job>.container`

Em qual imagem Docker vai rodar as tarefas desse *job*.

```yml
jobs:
  test:
    container:
      image: elixir:1.10
```

---

Agora que nosso ambiente já tem `Elixir`, podemos criar as tarefas para executar o `mix format` e ` mix test`.

```yml
name: Meu Workflow

on: push

jobs:
  test:
    runs-on: ubuntu-latest

    container:
      image: elixir:1.10

    steps:
      - uses: actions/checkout@v1

      - name: Formatar
        run: mix format --check-formatted

      - name: Instalar as dependências
        run: mix deps.get

      - name: Testar
        run: mix test
```

Pronto já temos um *workflow* que clona nosso repositório, verifica se nosso código está formatado de acordo com as configurações do projeto, instala as dependências e roda nossos testes.

---

# Utilizando serviços

O cenário anterior funciona perfeitamente para quando a gente tem um `lib` escrita em `Elixir`, mas para casos que a gente esteja trabalhando em um projeto com [Phoenix](https://www.phoenixframework.org/) por exemplo, a gente vai precisar provavelmente conectar em um banco de dados para poder executar alguns testes.

Nesse momento a gente consegue utilizar os *services* para criar containers de um banco de dados [Postgres](https://www.postgresql.org/), por exemplo, para que os nossos *jobs* possam ter onde se conectar.

## `jobs.<id-do-job>.services`

```yml
services:
  postgres:
    image: postgres

    # Mapeia a porta 5432 no container Docker para a porta 5432 no container do Postgres
    ports:
      - 5432:5432

    # Configura os dados de acesso ao banco de teste
    env:
      POSTGRES_USER: usuario
      POSTGRES_PASSWORD: senha
      POSTGRES_DB: banco
```

Agora que o nosso *workflow* vai criar o banco de dados pra gente poder conectar, tem um detalhe que temos que alterar nas nossas tarefas que vão rodar para conectar nesse banco. No caso, a nossa tarefa `mix test` tem que receber uma variável de ambiente para poder conectar corretamente no banco criado pelo Actions.

```yml
- name: Testar
  run: mix test
   env:
     POSTGRES_HOST: postgres
     POSTGRES_PORT: $❴❴ job.services.postgres.ports[5672] ❵❵
```

O exemplo completo fica assim:

```yml
name: Meu Workflow

on: push

jobs:
  test:
    runs-on: ubuntu-latest

    container:
      image: elixir:1.10

    services:
      postgres:
        image: postgres

        ports:
          - 5432:5432

        env:
          POSTGRES_USER: usuario
          POSTGRES_PASSWORD: senha
          POSTGRES_DB: banco

    steps:
      - uses: actions/checkout@v1

      - name: Formatar
        run: mix format --check-formatted

      - name: Instalar as dependências
        run: mix deps.get

      - name: Testar
        run: mix test
        env:
          POSTGRES_HOST: postgres
          POSTGRES_PORT: $❴❴ job.services.postgres.ports[5432] ❵❵
```

Agora basta alterar as configurações da nosso ambiente de teste para receber utilizar as variáveis de ambiente:

```elixir
import Config

config :meu_app, Meu.Repo,
  adapter: Ecto.Adapters.Postgres,
  username: "usuario",
  password: "senha",
  database: "banco",
  port: String.to_integer(System.get_env("POSTGRES_PORT", "5432"))
  hostname: System.get_env("POSTGRES_HOST"),
  pool: Ecto.Adapters.SQL.Sandbox
``` 

---



# Docker Compose

Se você já utiliza [Docker Compose](https://docs.docker.com/compose/) nos seus projetos você não precisa criar os `services` no arquivo do Actions e pode criar esses serviços direto no seu arquivo do Docker Compose de teste.

Exemplo do arquivo `docker-compose-test.yml`:

```yml
version: "3.3"
services:
  meu_servico:
    build: .
    depends_on:
      - meu_postgres
    ports:
      - "4000:4000"
    environment:
      - MIX_ENV=test
    command: mix phx.server

  postgres:
    image: postgres
    container_name: meu_postgres
    environment:
      - POSTGRES_DB=banco
      - POSTGRES_USER=usuario
      - POSTGRES_PASSWORD=senha
    ports:
      - "5433:5432"
```

Para rodar os testes com o Docker Compose, o ideal é mudar a imagem que roda no Actions para uma imagem que tenha já tenha o `compose` instalado. 

```yml
jobs:
    container:
      image: docker/compose
```

O exemplo completo fica assim:

```yml
name: Meu Workflow

on: push

jobs:
  test:
    runs-on: ubuntu-latest

    container:
      image: docker/compose

    steps:
      - uses: actions/checkout@v1

      - name: Formatar
        run: docker-compose -f docker-compose-test.yml run meu_servico mix format --check-formatted

      - name: Testar
        run: docker-compose -f docker-compose-test.yml run meu_servico mix test
```

---


Espero que tenha ajudado vocês a entender um pouco melhor como rodar projetos `Elixir` no GitHub Actions. 

Até a próxima!