Послушайте!
Ведь, если звезды зажигают —
значит — это кому-нибудь нужно?
(В.В. Маяковский)

Предисловие

Так уж получилось, что какое-то время назад на одной из своих работ мне пришлось настроить средства коллективной разработки кода для одной группы программистов на Fortran'е. Сначала они сами настраивали Gitosis, потом Trac, но всё время чего-то не хватало. Постоянно были проблемы с одновременным вливанием кода, а также с тем, что называется code review… В общем, эта группа разработчиков доросла до полноценной системы совместной разработки с CI/CD. Поскольку группа небогатая, на момент установки системы совместной разработки в распоряжении был слабенький двухъядерный сервер с 2 ГБайт ОЗУ. По этой причине выбор пал на связку Gogs + Drone. Маленькие, написанные на Go, практически без особых "фич" программы. Зато сразу появилась возможность после очередного вливания кода проверять его сборку и тестировать, правильно ли он работает.

Вот так выглядит текущая используемая в "боевом" режиме версия Drone 0.8.

Обновление

Вот, прошло несколько лет, и в текущем, 2021 году возник вопрос о переходе на более новые версии программного обеспечения. В резульате небольшого исследования было решено переходить на связку Gitea и Drone последних версий. Переход с Gogs на Gitea делается не очень сложно, но выходит за рамки данной статьи. Обновление Drone с версии 0.8 на 1.x делается посложнее, и, видимо, этому переходу стоило бы посвятить отдельную статью. Но, опять же, цель сейчас — в другом. Пока мы испытывали тестовую связку Gitea + Drone 1.10 вышел Drone 2.0 (13 мая 2021 года), и надо переходить на него.

Новый внешний вид Drone 2.0

Вместе с выходом новой версии у Drone CI произошло несколько важных изменений. Во-первых, Drone CI теперь стал Harness CI, и при этом приобрёл новый внешний вид:

Как видно на снимке экрана, в журналах сборки теперь используются цвета терминала (белый на чёрном). Поскольку разработчики уже привыкли к другой цветовой схеме, сразу же встал вопрос: а почему нельзя сделать так, чтобы цвета были, как раньше — чёрный на белом фоне? Немного поисследовав вопрос, было найдено, что в Drone UI цветовая гамма прибита гвоздями вкомпилирована внутрь бинарника и поменять её можно только полностью всё пересобрав.

Битва за белый фон

Первое, что удалось сделать, это поиграться с настройками CSS прямо в браузере. В результате стало ясно, что желаемое достижимо, и картинка с белым фоном была продемонстрирована руководителю группы разработчиков. После того, как было дано добро, стал изучать, как изменения в CSS можно внедрить в Drone UI. С первого раза это оказалось не так просто, поскольку Drone собирается с уже выложенными на GitHub файлами из проекта Drone UI. Это означает, что в файле описания сборки (.drone.yml) нет шагов по сборке Drone UI. Поиски публично доступных сборок Drone UI не дал никаких результатов. Зато была найдена статья на китайском языке, в которой пошагово описано, как и что нужно делать. Вот — пара команд, которые предлагаются в этой статье:

go get -v -insecure xxx.com/xxx/drone-ui
sed -i '' 's/github.com\/drone\/drone-ui/xxx.com\/xxx\/drone-ui/' ./handler/web/{logout,pages,web}.go

Посмотрев на это, решил искать дальше, можно ли подменить штатный модуль на Go его локальной копией (то есть, не использовать выкладывание результата сборки на какой-либо внешний ресурс), и наткнулся на прекрасную обучающую статью на английском о том, как использовать локально созданные модули. Всех, кто интересуется этой темой — смотрите сюда.

Совместив знания из обеих статей (на китайском и на английском), было решено делать локальную сборку Drone с подменой Drone UI на изменённую локальную версию. Это тоже оказалось не так-то просто, поскольку изначально было неизвестно, с какой версией Node.js, с какими параметрами и как всё должно собираться. Всё это пришлось подбирать простым перебором.

Начнём, пожалуй, с модификации стадий сборки. Изначально в drone их четыре: клонирование исходных текстов (clone), тестирование кода на Go (test), сборка бинарника drone-server (build) и сборка Docker-контейнера с последующим выкладыванием в реестр (publish). Вот — часть файла .drone.yml, используемого для штатной сборки в проекте drone:

---
kind: pipeline
type: docker
name: linux-amd64

platform:
  arch: amd64
  os: linux

steps:
- name: test
  image: golang:1.14.4
  commands:
  - go test ./...
  
- name: build
  image: golang:1.14.4
  commands:
  - sh scripts/build.sh
  environment:
    GOARCH: amd64
    GOOS: linux

- name: publish
  image: plugins/docker:18
  settings:
    auto_tag: true
    auto_tag_suffix: linux-amd64
    dockerfile: docker/Dockerfile.server.linux.amd64
    repo: drone/drone
    username:
      from_secret: docker_username
    password:
      from_secret: docker_password
  when:
    event:
    - push
    - tag

Очевидно, что не хватает шагов по клонированию, изменению под наши нужды, сборке и подмене Drone UI.

Начнём с клонирования. Добавляем в наш репозиторий шаг клонирования drone:

- name: clone
  image: drone/git
  commands:
  - export DRONE_TAG=v2.0.0
  - export DRONE_COMMIT_REF=refs/tags/$DRONE_TAG
  - export DRONE_REMOTE_URL=https://github.com/drone/drone.git
  - clone

и шаг клонирования drone-ui:

- name: clone drone-ui
  image: drone/git
  commands:
  - export DRONE_COMMIT_BRANCH=drone2
  - export DRONE_COMMIT_REF=refs/heads/$DRONE_COMMIT_BRANCH
  - export DRONE_COMMIT_SHA=d96f1e26d4482663535cfc913f650956c914f27f
  - export DRONE_REMOTE_URL=https://github.com/drone/drone-ui.git
  - export DRONE_WORKSPACE=$DRONE_WORKSPACE_BASE/web
  - clone

Тут ничего сложного. Штатное, пусть и нестандартное, использование плагина drone/git.

Далее — клонирование патча:

- name: clone patch
  image: drone/git
  commands:
  - export DRONE_WORKSPACE=$DRONE_WORKSPACE_BASE/patch
  - mkdir -p $DRONE_WORKSPACE
  - clone

Тоже ничего сложного. Конечно, этот шаг можно было возложить на автоматику Drone, но было решено сделать выделенным, поскольку в корне $DRONE_WORKSPACE_BASE лежит репозиторий drone, а патч мы клонируем в каталог patch. При этом, конечно, выключен неявный шаг клонирования нашего репозитория:

clone:
  disable: true

Патч был сделан в локальной копии исходников drone-ui по результатам онлайн-подмены цветовых параметров CSS в браузере.

Следующий шаг — наложение патча:

- name: patch drone-ui
  image: node:15.14.0
  commands:
  - cd $DRONE_WORKSPACE_BASE/web
  - patch -p1 < ../patch/drone-ui-2.0-customize.patch

Сразу отмечу, что здесь был использован каталог web для размещения drone-ui, и именно в этом каталоге мы его будем собирать и далее отсюда будем использовать (подменять) при сборке бинарника drone-server.

Далее — шаг сборки drone-ui:

- name: build drone-ui
  image: node:15.14.0
  commands:
  - cd $DRONE_WORKSPACE_BASE/web
  - npm install
  - npm run build
  environment:
    CI: false

Здесь пришлось перебирать версии Node.js, чтобы исходники drone-ui собрались. Кроме того, пришлось выставить переменную CI в значение false, чтобы предупреждения при сборке (warnings) не воспринимались как ошибки.

Собрать drone-ui — это ещё не всё. Теперь надо сгенерировать файл dist_gen.go, используемый в сборке drone. Для этого добавляем шаг генерирования этого файла:

- name: generate drone-ui
  image: golang:1.14.4
  commands:
  - cd $DRONE_WORKSPACE_BASE/web
  - go get github.com/bradrydzewski/togo
  - rm -vf dist/dist_gen.go
  - go generate ./dist

Здесь важно отметить две команды. Первая предназначена для скачивания утилиты togo, используемой для генерации файла dist_gen.go:

go get github.com/bradrydzewski/togo

Вторая служит, собственно, для генерации файла dist_gen.go:

go generate ./dist

Эта команда взята (с небольшими изменениями) из статьи на китайском языке. А чтобы уж точно удостовериться в том, что файл будет сгенерирован, мы его предварительно удаляем:

rm -vf dist/dist_gen.go

Теперь перейдём к следующему шагу — подмене в исходниках drone модуля drone-ui:

- name: replace drone-ui
  image: golang:1.14.4
  commands:
  - cd $DRONE_WORKSPACE_BASE/web
  - go mod init github.com/drone/drone-ui
  - cd $DRONE_WORKSPACE_BASE
  - go mod edit -replace=github.com/drone/drone-ui=./web

Здесь используется команда инициализации модуля github.com/drone/drone-ui в каталоге web:

go mod init github.com/drone/drone-ui

Эта команда взята из обучающей статьи на английском языке.

Вторая использованная здесь команда, собственно, подменяет модуль drone-ui на тот, что находится в каталоге web:

go mod edit -replace=github.com/drone/drone-ui=./web

Похожая команда присутствует как в статье на китайском, так и в обучающей статье на английском. Нужно было лишь правильным образом указать имя каталога — ./web. Если его указать без ./, будет производиться поиск модуля с именем web.

На этом этапе в сборке полностью подменён модуль drone-ui и в него внесены все необходимые нам изменения. Дальше добавляются имеющиеся в базовом репозитории drone шаги тестирования и сборки:

- name: test
  image: golang:1.14.4
  commands:
  - export GOPATH=$DRONE_WORKSPACE_BASE/go
  - go test ./...

- name: build
  image: golang:1.14.4
  commands:
  - export GOPATH=$DRONE_WORKSPACE_BASE/go
  - sh scripts/build.sh
  - strip -s -R .comment release/linux/$GOARCH/drone-server
  environment:
    GOARCH: amd64
    GOOS: linux

Единственное важное добавление на этих шагах — команда

export GOPATH=$DRONE_WORKSPACE_BASE/go

Благодаря этой команде скачивание всех модулей происходит один раз — на шаге тестирования, что позволяет слегка сократить время сборки. Дело в том, что переменная DRONE_WORKSPACE_BASE обычно указывает на каталог /drone/src, содержимое которого сохраняется на всех шагах в рамках одного пайплайна Drone CI.

Кроме этого, добавлена также команда

strip -s -R .comment release/linux/$GOARCH/drone-server

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

И в завершение — шаг сборки Docker-контейнера и выкладывания его на внешний реестр:

- name: publish
  image: plugins/docker:18
  settings:
    tags: [ latest, "2", "2.0", "2.0.0" ]
    dockerfile: docker/Dockerfile.server.linux.amd64
    repo: drone/drone-server-customized
    username:
      from_secret: docker_username
    password:
      from_secret: docker_password
    dry_run: true
  when:
    repo:
    - tkushnir/drone-server-customized
    branch:
    - main
    event:
      exclude:
      - pull_request

Здесь нужно обратить внимание на опцию dry_run: true, запрещающую реальное выкладывание готового контейнера в реестр. Результат работы всего пайплайна можно посмотреть тут.

Результат работы аналогичного пайплайна на локальном сборочном сервере дал возможность запустить из локального реестра кастомизированную версию Drone 2.0 с белым фоном у журналов сборки:

Теперь мы испытываем связку последних версий Gitea и Drone и в скором времени переведём их в боевой режим.

Немного о будущем

Конечно же, всего этого можно было бы избежать, если бы была штатная возможность изменять цветовые схемы в Drone. Либо такая возможность появится в самом приложении, либо её придётся туда кому-то дописать. Не знаю, буду ли я этим заниматься, тем более, что с Vue и Node.js я знаком не слишком плотно.

По ходу обновления с Drone 0.8 на Drone 1.10 и далее на Drone 2.0 обнаружилось, что перестают быть видны старые шаги (steps) сборок, которые были сделаны ещё в версии 0.8 и были нормально перенесены в версию 1.10 штатной утилитой. Эта проблема решилась довольно просто. Если буду описывать шаги перехода от Drone 0.8 на Drone 2.0, поделюсь и этой наработкой.

Возможно, имеет смысл также поделиться теми проблемами (и их решениями), которые возникли при переходе от Gogs к последним версиям Gitea. Изначально виделось, что этот переход будет совсем простым, но оказалось, что и тут не без подводных камней.