Как стать автором
Обновить
0
Учи.ру team
интерактивная образовательная платформа

Инкрементация и сохранение сборки на CI

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

Привет, Хабр! Меня зовут Паша Филимонов и я Android-разработчик в Учи.ру. Наша небольшая команда занимается разработкой мобильных приложений — «Учи.ру» и «Учи.ру для учителей». Мы сторонники автоматизации и считаем, что настройка CI/CD необходима  для улучшения процессов разработки продукта. Каждый раз, приступая к новой сборке приложения, нам приходилось вручную прописывать ее код версии. Это довольно рутинная задача, которую мы решили автоматизировать. Рассказываю, как мы это сделали.

Как раньше строились процессы

Наши билды собираются с помощью сервисов Github Actions. У нас есть 3 основных типа сборки: 

  • stage — для тестирования фичи; 

  • release candidate — stage сборка — релиз кандидат для регрессионного тестирования;

  • release — готовая сборка для публикации в Play Store.

Когда мы делали новую версию приложений, то вручную прописывали новый воркфлоу в гитхабе, указывая необходимые параметры:

  • versionName — версия приложения, в которую пойдет новый функционал;

  • versionCode — код версии (инкремент последней сборки);

  • releaseNotes — описание того, что изменилось в этой сборке.

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

После того как сборка была готова, в канал нашей команды в Slack приходило соответствующее уведомление с вышеуказанными параметрами и автором инициализации воркфлоу.

Здесь и возникает проблема: каждый раз, инициируя новую сборку, нам нужно знать versionCode предыдущей

Самый просто способ его выяснить — открыть канал в Slack, посмотреть код версии последней сборки и в уме прибавить к нему единицу. Это задача постоянно отнимала время, поэтому мы решили автоматизировать процесс. К тому же мы не застрахованы от race condition — ситуации, когда несколько человек могут создать сборку с одинаковыми значениями кода версии.

Так выглядел наш набор параметров, необходимых для создания сборки: поля Version code и Version name являлись обязательными для заполнения.

К чему хотим прийти

Мы решили избавиться от ручного указания номера и кода версии при создании новой сборки. 

Для этого нам было необходимо определить мастер-систему (хранилище), в которой будут храниться актуальные данные о последней сборке. Это позволило бы автоматически получать данные о сборке и вычислять номер новой.

При этом мы хотели оставить возможность указывать номер и код версии вручную, чтобы при проблемах с доступом в хранилище можно было самостоятельно указывать необходимые данные и не останавливать процесс разработки. А также в частном порядке создавать сборки с нужными версиями, не меняя ничего в хранилище.

Выбор хранилища

Один из вариантов решения — хранить эти данные в файле в текущем проекте. Но этот подход заведомо обречен, поскольку feature-ветки не будут знать про изменения версии в других ветках. А при объединении веток будут постоянные конфликты из-за изменений в одном файле с версией.

Поэтому в первую очередь было необходимо найти хранилище, которое:

  • бесплатное;

  • имеет возможность записывать/читать данные без ограничений (или с адекватными лимитами);

  • обеспечивает безопасное хранение данных;

  • позволяет просматривать и редактировать данные в реальном времени;

  • простое во внедрении.

Нам важно, чтобы внедряемая база данных требовала минимальных усилий для поддержания ее работоспособности.

Вот какие решения мы рассматривали.

Варианты хранилища:

  1. Попросить DBA «поднять» отдельную базу данных для наших артефактов

Отличный вариант для тех, у кого есть такая возможность.

Плюсы:

  • артефакты будут храниться в рамках вашей  компании;

  • неограниченный круг возможностей в проектировании.

Минусы:

  • зависимость от других отделов/людей, у которых не всегда будет время на наши нужды.

2. Использовать отдельный репозиторий

Плюсы:

  • прост в использовании.

Минусы:

  • неудобство ручного редактирования данных. Для изменения данных вручную, придется коммитить и пушить в этот репозиторий, что также может привести к конфликтам.

  1. Использовать Github gists

Плюсы:

  • прост в использовании.

Минусы:

  • Github gists могут быть привязаны только к пользователю. Чтобы не привязывать их к определенному члену команды, потребуется создать дополнительный аккаунт с доступом к репозиторию.

  1. Внедрить стороннюю онлайн базу данных

Плюсы:

  • независимое хранилище.

Минусы:

  • дополнительная точка отказа;

  • большинство сервисов предлагают свои услуги, которые требуют инвестиций.

  1. Использовать Firebase

Его можно отнести к предыдущему варианту, но я выделю его отдельно, потому как в нашем проекте мы активно используем сервисы от Firebase, и он не станет для нас новой точкой отказа.

Плюсы:

  • несколько способов доступа к данным (с помощью cUrl и пользовательский интерфейс);

  • бесплатный пакет;

  • прост в подключении и использовании.

Минусы:

  • лимиты в бесплатной версии, но для нашей задачи их хватит с головой (на момент публикации статьи: 1 гб. на хранение данных, 10 гб. в месяц на загрузку данных).

Пример данных по последней сборке в интерактивном режиме просмотра
Пример данных по последней сборке в интерактивном режиме просмотра

На чем мы остановились

Так как мы активно используем сервисы Firebase, мы решили, что интегрировать их базы данных будет рационально: нам не придется подключать к процессу сторонний сервис.

Firebase предоставляет два типа баз данных: Realtime Database и Cloud Firestore. Различия между ними можете прочитать здесь и здесь

На текущий момент наши потребности просты: записывать и читать данные строго для последней сборки. Поэтому мы остановились на самом подходящем варианте — Realtime Database. Бесплатный план имеет лимиты, но в нашем случае нам этого хватает. Документация по Realtime Database и Cloud Firestore.

Пример, как можно обновить данные в Realtime Database с помощью cUrl:

curl --show-error --fail --request PATCH --url
"<https://example.firebaseio.com/build_info/stage.json>"
--data-raw "{\\"version_code\\":12}"

Как внедряли автоматизацию

Основной флоу на CI/CD у нас уже был настроен. Нам оставалось добавить получение, инкрементацию и сохранение сборки в Firebase.

Мы создаем повторно используемый воркфлоу, чтобы переиспользовать его логику в двух приложениях — «Учи.ру» и «Учи.ру для учителя». Затем объявляем выходные параметры VERSION_NAME и VERSION_CODE, связываем их с выходными параметрами задачи InitAppVersion. На вход передаем Uri базы данных, к которой будем обращаться за данными.

name: Initialize app version
on:
  workflow_call:
    secrets:
      databaseUri:
        required: true
    outputs:
      VERSION_NAME:
        value: ${{ jobs.InitAppVersion.outputs.versionName }}
      VERSION_CODE:
        value: ${{ jobs.InitAppVersion.outputs.versionCode }}

Объявляем задачу, которая будет выполнять инициализацию значений текущей версии, а также — выходные параметры этой задачи:

jobs:
  InitAppVersion:
    name: Init app version
    runs-on: ubuntu-latest
    outputs:
      versionName: ${{ steps.finish.outputs.versionName }}
      versionCode: ${{ steps.finish.outputs.versionCode }}

Далее разбиваем задачу на шаги: 

  1. Получаем из базы последние значения versionName и versionCode и объявляем их как выходные параметры этого шага. Он выполняется, если нет введенных вручную номера или кода версии. Все запросы к хранилищу мы выполняем с помощью cURL запросов.

- name: Pull build info from realtime database
        id: pull
        if: ${{ github.event.inputs.versionName == '' ||
github.event.inputs.versionCode == '' }}
        run: |
          curl --show-error --fail --request GET --url "${{ 
secrets.databaseUri }}" > response.json
          echo '::set-output name=lastVersionName::'$(jq -r 
'.version_name' response.json)
          echo '::set-output name=lastVersionCode::'$(jq -r 
'.version_code' response.json)
  1. Записываем данные в переменные окружения. Введенные вручную значения мы оставляем приоритетными, чтобы у нас всегда была возможность задать руками версию приложения.

#     Устанавливаем значение номера версии
- name: Initial version name
  run: echo "newVersionName=${{ 
github.event.inputs.versionName || 
steps.pull.outputs.lastVersionName }}" >> $GITHUB_ENV
#     Устанавливаем значение номера билда
- name: Initial version code
        env:
          versionCode: $((${{ 
steps.pull.outputs.lastVersionCode }}+1))
        run: echo "newVersionCode=${{ 
github.event.inputs.versionCode || env.versionCode }}" >> 
$GITHUB_ENV
  1. Сохраняем новое значение кода версии в БД только в том случае, если код версии не был введен вручную. Мы не сохраняем номер версии (versionName) в БД из рабочего процесса (подробнее ниже).

- name: Push version code to realtime database
  if: ${{ github.event.inputs.versionCode == '' }}
  run: curl --show-error --fail --request PATCH --url "${{ 
secrets.databaseUri }}" --data-raw "{\\"version_code\\":${{ 
env.newVersionCode }}}"
  1. Инициализируем выходные параметры задачи.

- name: Setup outputs
  id: finish
  run: |
    echo "::set-output name=versionName::${{ 
env.newVersionName }}"
    echo "::set-output name=versionCode::${{ 
env.newVersionCode }}"

Так выглядит наш набор параметров, необходимых для создания сборки, после изменений:

Теперь поля Version code и Version name стали необязательными: заполнять их нет необходимости. Данные автоматически подтягиваются из БД, если не введены вручную.

Однако все еще осталась нерешенной проблема с сохранением номера версии: при работе над несколькими релизами одновременно придется постоянно указывать номер вручную, поскольку в БД хранится только одно его значение.

Что насчет релизных сборок

Релизные сборки также подверглись небольшим изменениям. Теперь перед тем как запустить релизную сборку, мы создаем Release Candidate(RC). После этого — тег для коммита, для которого запускается сборка вида: v1.1(256). Ее название не имеет значения, главное — она должна содержать номер (versionName) и код (versionCode) версии.

При создании релизной сборки из того же коммита, что и RC, мы можем получить тег. Затем вытащить из него нужную информацию по номеру и коду версии и присвоить их релизной сборке. Важно лишь указать то, как мы получаем эти данные из тега:

Например, для версии v1.1(256):

steps:
  - uses: actions/checkout@v2
    with:
      fetch-depth: 0
#     Получаем тэг, если хоть одно поле пустое
- name: Fetch tag
    id: tag
    if: ${{ github.event.inputs.versionName == '' ||
github.event.inputs.versionCode == '' }}
    run: echo "::set-output name=tagName::$(git describe 
-- abbrev=0 --tags)"
#     Устанавливаем значение номера версии
- name: Initial version name
    run: |
      if ${{ github.event.inputs.versionName != '' }}; then
        echo "newVersionName=${{
github.event.inputs.versionName }}" >> $GITHUB_ENV
      else
        echo "newVersionName=$(echo "${{ 
steps.tag.outputs.tagName }}" | sed 's/.*version//; s/(.*//')" 
>> $GITHUB_ENV # получаем значение между буквой v и началом 
открытия скобок: "1.1"
fi
#     Устанавливаем значение номера билда
- name: Initial version code
    run: |
      if ${{ github.event.inputs.versionCode != '' }}; then
        echo "newVersionCode=${{ github.event.inputs.versionCode }}" >> $GITHUB_ENV
      else
      echo "newVersionCode=$(echo "${{ steps.tag.outputs.tagName }}" | sed 's/.*(//; s/).*//')" >> $GITHUB_ENV # получаем значение в скобках: "256"
      fi

Таким образом, нам удалось решить проблему ручной инкрементации сборки, избавиться от проблем при асинхронных действиях разработчиков и делегировать рутинные операции машине. Для этого мы использовали инструменты автоматизации от Firebase. В статье мы поделились и альтернативными вариантами решения этой проблемы.

В будущем мы планируем решить проблему с сохранением номера версии, а также тщательно проработать идею формирования changelog’а из описания пул-реквестов или номера задач в Jira.

Полный код воркфлоу на Github.


Если ты разделяешь наш подход к аналитике, присоединяйся, чтобы вместе с нами развивать школьный EdTech.

Теги:
Хабы:
0
Комментарии10

Публикации

Изменить настройки темы

Информация

Сайт
uchi.ru
Дата регистрации
Дата основания
Численность
501–1 000 человек
Местоположение
Россия

Истории