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

от автора

В октябре 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.

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


ссылка на оригинал статьи https://habr.com/ru/post/481846/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *