Как стать автором
Обновить

Использование GitHub CI для Elixir проектов

Время на прочтение 3 мин
Количество просмотров 2.9K

В октябре Github запустил actions, которые позволяют выполнять CI, не отходя от кассы, в которой этот самый код хранится. Это действительно очень удобно. Как только кто-то отправляет pull request, или просто загружает новые изменения на сервер, или что-то еще специальное (список событий, к которым можно прикрутить actions может быть найден в официальной документации), сборка запускается. Также поддерживаются запланированные повторяющиеся задачи (по принципу cron).


Можно создавать конвейеры действий, названные рабочими процессами (workflows). И все это прекрасно, и смахивает на светлое будущее — за исключением документации.


Мне потребовалось более часа, чтобы выяснить, как создать контейнер со сторонними сервисами для тестирования приложения. Вот что мне удалось узнать. Пожалуйста, обратите внимание, что официальная документация — откровенно неуклюжая, неполная, а зачастую — и просто ошибочная.


Стандартное действие CI (action) использует конфигурационные файлы с синтаксисом, очень похожим на тот, который используется CircleCI. Это просто старый добрый YAML, позволяющий настроить целевую ОС, среду, команды для выполнения и т. д. Сами действия получают уникальные имена, что позволяет ссылаться на другие действия и зависеть от них.


Кроме того, конфигурация позволяет указать службы (services). Сервисы должны быть запущены где-то в облаке, и GH будет сопоставлять порты контейнера с портами, которые эти сервисы выставляют наружу, согласно конфигурации. Эта часть слабо освещена в официальной документации, и даже то, что описано, содержит ошибки.


Вот рабочий пример конфигурации для проекта Elixir, требующего для тестирования сервисов RabbitMQ и Redis.


name: Tests for My Project

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest

    container:
      image: elixir:1.9.1-slim

    services:
      rabbitmq:
        image: rabbitmq
        ports:
        - 5672:5672
        env:
          RABBITMQ_USER: guest
          RABBITMQ_PASSWORD: guest
          RABBITMQ_VHOST: "/"
      redis:
        image: redis
        ports:
        - 6379:6379

    steps:
    - uses: actions/checkout@v1
    - name: Install Dependencies
      run: |
        MIX_ENV=ci mix local.rebar --force
        MIX_ENV=ci mix local.hex --force
        MIX_ENV=ci mix deps.get
    - name: Run All Tests
      run: |
        MIX_ENV=ci mix test
      env:
        RABBITMQ_HOST: rabbitmq
        RABBITMQ_PORT: ${{ job.services.rabbitmq.ports[5672] }}
        REDIS_HOST: redis
        REDIS_PORT: ${{ job.services.redis.ports[6379] }}

Как можно видеть, тесты будут запущены на Ubuntu, используя Elixir v1.9.1. Сервисы описаны под ключом services, и здесь начинается чистый детектив. Физический порт, к которому будет привязан сервисный порт, случайным образом выбирается механизмом контейнеров во время выполнения и хранится во внутренней переменной оболочки с именем job.services.rabbitmq.ports[5672]. rabbitmq — это имя сервиса, как указано в этом файле в разделе services и 5672 — исходный порт. Внутренняя переменная имеет синтаксис ${{ foo }} и передается в переменную окружения RABBITMQ_PORT (последний блок настройки, под ключом env). В RABBITMQ_HOST — нужно положить имя службы, в точности как под ключом services. Теперь приложение может считывать переменные среды как обычно и порты будут правильно прокинуты.


Вот так мы будем читать эти переменные среды из окружения (это конфиг для Elixir, для других языков будет что-то очень похожее).


import Config

config :my_app,
  rabbitmq: [
    host: System.get_env("RABBITMQ_HOST"),
    password: "guest",
    port: String.to_integer(System.get_env("RABBITMQ_PORT", "5672")),
    username: "guest",
    virtual_host: "/",
    x_message_ttl: "4000"
  ]

В файле проекта я создаю специальную среду (environment) :ci, чтобы различать конфигурацию для тестов, выполняемых в локальной среде, и для тех же тестов, выполняемых где-то там в облаке.


Кроме того, в CI конвейере я запускаю dialyzer на своих исходниках. Поскольку он выполняется в контейнере, задача занимает некоторое время, потому что приходится перестраивать plts с нуля каждый раз. Именно поэтому я делаю это один раз в день, используя schedule вариант конфигурации.


name: Dialyzer for My Project

on:
  schedule:
  - cron: "0 1 * * *"

jobs:
  build:
    runs-on: ubuntu-latest

    container:
      image: elixir:1.9.1-slim

    steps:
    - uses: actions/checkout@v1
    - name: Install Dependencies
      run: |
        MIX_ENV=ci mix local.rebar --force
        MIX_ENV=ci mix local.hex --force
        MIX_ENV=ci mix deps.get
    - name: Run All Tests
      run: |
        MIX_ENV=ci mix code_quality

здесь code_quality — псевдоним задачи (task alias), объявленный в mix.exs


defp aliases do
  [
    code_quality: ["format", "credo --strict", "dialyzer"]
  ]
end

Вот, в ообщем, и все, что нам нужно для счастливого тестирования проекта с внешними зависимостями в новом рабочем процессе Github.


Удачной непрерывной интеграции!

Теги:
Хабы:
+5
Комментарии 1
Комментарии Комментарии 1

Публикации

Истории

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн
PG Bootcamp 2024
Дата 16 апреля
Время 09:30 – 21:00
Место
Минск Онлайн
EvaConf 2024
Дата 16 апреля
Время 11:00 – 16:00
Место
Москва Онлайн