- PVSM.RU - https://www.pvsm.ru -
В статье решается задача управления описанием сборки для большого количества однотипных приложений. Чтобы в проекте заработал GitLab CI, нужно в репозиторий добавить файл .gitlab-ci.yml
. Но что, если в сотне репозиториев это файл с одинаковым содержимым? Даже если разложить его по репозиториям один раз, то как его потом изменять? А что, если одного .gitlab-ci.yml
мало для сборки — нужны Dockerfile
или Dappfile
, разные скрипты и структура YAML-файлов для Helm? Как обновлять их?
С чего начать решение задачи по сборке сотни однотипных приложений? Конечно же, посмотреть, можно ли GitLab CI указать использовать .gitlab-ci.yml
из другого репозитория или компоновать .gitlab-ci.yml
из файлов в других репозиториях…
В поисках такой возможности сразу всплывают следующие issues:
Видно, что возможность иметь какой-то общий .gitlab-ci.yml
интересует сообщество. Решение добавить include секций из файла в другом репозитории кажется очень простым: оно основано на многолетней практике программирования и будет понятно любому. Однако include как концепция хорошо работает в случае с деревом исходников, но в случае нескольких Git-репозиториев в этом решении можно увидеть такие минусы:
.gitlab-ci.yml
, хранить его и пересобирать уже на основе него..gitlab-ci.yml
того проекта, который собирается, но истории изменений не будет видно.Для случая с однотипными приложениями добавляются ещё два минуса:
.gitlab-ci.yml
остаётся.Решение с include — это pull-модель, т.е. проект при сборке вытягивает часть конфигурации CI. Если заменить pull на push, то получится так:
.gitlab-ci.yml
и другие файлы, необходимые для сборки;
Этот вариант работает следующим образом: в проекте common-ci-config хранится общий для сотни других проектов файл .gitlab-ci.yml
. При изменении этого файла от пользователя gitlab-ci-distributor рассылаются коммиты в другие проекты.
Для файлов сборки можно выбрать: либо добавлять их в коммит, либо в .gitlab-ci.yml
у проектов, в задачу сборки, добавить git clone
проекта common-ci-config.
Плюсы такого подхода:
.gitlab-ci.yml
и кто его изменил. Пропадает проблема хранения эффективного .gitlab-ci.yml
, т.к. в каждом проекте всегда видно полную версию без include..gitlab-ci.yml
с помощью вызова скрипта. То есть, если нужно, чтобы .gitlab-ci.yml
при сборке всегда использовал последнюю версию конфигурации тестирования, то тестирование выносится в скрипт в проекте common-ci-config.Итак, проблема обозначена и есть вариант решения. Для продолжения нужно рассказать о GitLab API (документация на сайте GitLab [4]). Понадобятся следующие методы:
Методы API можно вызывать с помощью curl, а JSON, который приходит в ответ, обрабатывать с помощью jq (документация по фильтрам [9]).
Для вызова методов понадобится создать access token. Об этом будет дальше в статье, а пока — пример того, как получать список проектов в группе:
$ curl -s --header "PRIVATE-TOKEN: $TOKEN" https://gitlab.example.com/api/v4/groups/group-of-alike-projects/projects?simple=true |
jq -r '.[] | "(.path_with_namespace)t(.id)"'
group-of-alike-projects/project-pasiphae 7
group-of-alike-projects/project-megaclite 6
group-of-alike-projects/project-helike 5
group-of-alike-projects/project-erinome 4
group-of-alike-projects/project-callisto 3
group-of-alike-projects/project-aitne 2
group-of-alike-projects/project-adrastea 1
Вызов методов API невозможен без авторизации. GitLab предлагает авторизацию через access tokens. Чтобы получить такой токен, нужно создать отдельного пользователя, которому будут даны права на управление нужными репозиториями. Пусть это будет пользователь gitlab-ci-distributor:
Далее нужно стать этим пользователем и создать access token:
Для доступа к проектам, где нужно управлять сборочными файлами, нужно добавить пользователя gitlab-ci-distributor в группу:
Общие для проектов файлы будут храниться в проекте сommon-ci-config. Проект нужно создать в отдельной группе — например, infra. В настройках проекта добавляется секретная переменная со значением полученного токена:
Описанные действия выполняются администратором один раз. Далее вся настройка производится через файлы в репозитории common-ci-config.
Теперь можно протестировать работу с API через GitLab CI. Для этого в проект common-ci-config добавляется простой .gitlab-ci.yml
:
stages:
- distribute
distribute:
stage: distribute
script:
- ./distribute.sh
… и скрипт distribute.sh
, который пока покажет информацию о коммите и проекты из выбранной группы:
#!/usr/bin/env bash
curl -s --header "PRIVATE-TOKEN: $DISTRIBUTOR_TOKEN" https://gitlab.example.com/api/v4/projects/infra%2Fcommon-ci-config/repository/commits/$CI_COMMIT_SHA | jq '.'
curl -s --header "PRIVATE-TOKEN: $DISTRIBUTOR_TOKEN" https://gitlab.example.com/api/v4/groups/group-of-alike-projects/projects?simple=true |
jq -r '.[] | "(.path_with_namespace)t(.id)"'
Результат выполнения задания distribute:
Running with gitlab-runner 10.1.0 (c1ecf97f)
on gitlab (d82a6d8f)
Using Shell executor...
Running on gitlab...
Fetching changes...
HEAD is now at 08dcc92 Initial .gitlab-ci.yml and distribute.sh
Checking out 08dcc92a as master...
Skipping Git submodules setup
$ ./distribute.sh
{
"id": "08dcc92abf0d951194ad1ffcc23deeb875855320",
"short_id": "08dcc92a",
"title": "Initial .gitlab-ci.yml and distribute.sh",
"created_at": "2017-10-25T16:35:15.000+03:00",
"parent_ids": [
"d9bdea91d081025c2af658209f23f684c96b5cee"
],
"message": "Initial .gitlab-ci.yml and distribute.shn",
"author_name": "root root",
"author_email": "root.root@gitlab.example.com",
"authored_date": "2017-10-25T16:35:15.000+03:00",
"committer_name": "root root",
"committer_email": "root.root@gitlab.example.com",
"committed_date": "2017-10-25T16:35:15.000+03:00",
"stats": {
"additions": 0,
"deletions": 0,
"total": 0
},
"status": "running",
"last_pipeline": {
"id": 2,
"sha": "08dcc92abf0d951194ad1ffcc23deeb875855320",
"ref": "master",
"status": "running"
}
}
group-of-alike-projects/project-pasiphae 7
group-of-alike-projects/project-megaclite 6
group-of-alike-projects/project-helike 5
group-of-alike-projects/project-erinome 4
group-of-alike-projects/project-callisto 3
group-of-alike-projects/project-aitne 2
group-of-alike-projects/project-adrastea 1
Job succeeded
Скрипт будет распространять общий файл .gitalb-ci.yml
. Чтобы не путать его с .gitlab-ci.yml
проекта common-ci-config, файл расположен в директории common
. В файле описывается простое автоматическое задание:
# common/.gitlab-ci.yml
stages:
- build
build:
stage: build
script:
- echo Building project $CI_PROJECT_PATH
В скрипте distribute.sh
уже есть получение информации о коммите и получение списка проектов. Чтобы в проекты попадал красивый коммит, нужно выделить имя и почту автора и полное сообщение коммита. Также нужно добавить цикл по полученным проектам и для каждого проекта вызвать метод, создающий коммит.
Доработанный distribute.sh
:
#!/usr/bin/env bash
COMMIT_INFO=$(curl -s --header "PRIVATE-TOKEN: $DISTRIBUTOR_TOKEN" https://gitlab.example.com/api/v4/projects/infra%2Fcommon-ci-config/repository/commits/$CI_COMMIT_SHA)
# Сообщение коммита может быть многострочным, поэтому jq без -r
MESSAGE=$(echo "$COMMIT_INFO" | jq '.message')
AUTHOR_NAME=$(echo "$COMMIT_INFO" | jq -r '.author_name')
AUTHOR_EMAIL=$(echo "$COMMIT_INFO" | jq -r '.author_email')
CONTENT=$(base64 -w0 common/.gitlab-ci.yml)
PAYLOAD=$(cat <<- JSON
{
"branch": "master",
"commit_message": $MESSAGE,
"author_name": "$AUTHOR_NAME",
"author_email": "$AUTHOR_EMAIL",
"actions": [
{ "action": "update",
"file_path": ".gitlab-ci.yml",
"content": "$CONTENT",
"encoding": "base64"
}
]
}
JSON
)
echo "$PAYLOAD"
curl -s --header "PRIVATE-TOKEN: $DISTRIBUTOR_TOKEN" https://gitlab.example.com/api/v4/groups/group-of-alike-projects/projects?simple=true |
jq -r '.[] | "(.path_with_namespace)t(.id)"' |
while read project
do
name=`echo $project | awk '{print $1}'`
id=`echo $project | awk '{print $2}'`
echo Update project $name
curl -s --request POST --header "PRIVATE-TOKEN: $DISTRIBUTOR_TOKEN"
--header "Content-Type: application/json"
--data "$PAYLOAD" https://gitlab.example.com/api/v4/projects/$id/repository/commits
done
echo Stop
Результат выполнения задания distribute:
В проекте group-of-alike-projects/project-pasiphae коммит будет выглядеть так:
Результат выполнения задания build в проекте group-of-alike-projects/project-pasiphae:
Видно, что пользователь, который запускает задание, — gitlab-ci-distributor. Но при этом автор коммита — пользователь, который сделал коммит в common-ci-config.
Скрипт distribute.sh
добавляет коммиты сразу в несколько проектов. Это приводит к созданию новых pipeline и одновременному запуску заданий на сборку. Такой эффект не всегда нужен. Чтобы коммит, обновляющий .gitlab-ci.yml
, не запускал сборку, можно вначале задания поставить условие с предупреждающим сообщением:
script:
- 'if [ "x$GITLAB_USER_NAME" == "xgitlab-ci-distributor" ] ; then echo -e "33[0;31mnnАвтоматическая сборка после обновления .gitlab-ci.yml отключена.nn33[0m"; exit 1; fi'
Внимание! Переменная GITLAB_USER_NAME
появилась в GitLab 10.0 (релиз [10] от 22 сентября 2017). В более ранних версиях есть только GITLAB_USER_ID
и для условия придётся использовать ID пользователя. Этот ID можно узнать, например, выполнив задание со script: [export]
или с таким запросом к API:
curl -s --header "PRIVATE-TOKEN: $DISTRIBUTOR_TOKEN" https://gitlab.example.com/api/v4/users?username=gitlab-ci-distributor | jq '.[] | .id'
Результат:
Если запустить это задание ещё раз, но от обычного пользователя, то всё выполнится успешно:
В целом данной информации достаточно для того, чтобы дальше самостоятельно экспериментировать с массовым управлением проектами.
Для простоты экспериментов и повторения того, что описано в статье, можно установить GitLab в виртуальной машине, например, с помощью проекта gitlab-vagrant [11]. Учтите, что придётся исправить Vagrantfile
: сменить базовый образ на ubuntu/xenial64
и увеличить память vb.memory = "3072"
. А после запуска добавить gitlab-runner по инструкции [12].
При разработке решения использовались следующие ресурсы:
Читайте также в нашем блоге (и подписывайтесь, чтобы не пропустить новые публикации!):
Автор: diafour
Источник [16]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/git/266688
Ссылки в тексте:
[1] Add 'include' key to .gitlab-ci.yml: https://gitlab.com/gitlab-org/gitlab-runner/issues/1258
[2] Import/include common CI/CD content from one project into another's `.gitlab-ci.yml` (EE only?): https://gitlab.com/gitlab-org/gitlab-ce/issues/20868
[3] Add includes capability in gitlab-ci YAML format: https://gitlab.com/gitlab-org/gitlab-ce/issues/22771
[4] документация на сайте GitLab: https://docs.gitlab.com/ee/api/README.html
[5] изменение одного файла: https://docs.gitlab.com/ee/api/repository_files.html#create-new-file-in-repository
[6] конструктор коммита с несколькими файлами: https://docs.gitlab.com/ee/api/commits.html#create-a-commit-with-multiple-files-and-actions
[7] Получение списка проектов: https://docs.gitlab.com/ee/api/groups.html#list-a-group-s-projects
[8] Информация о текущем коммите: https://docs.gitlab.com/ee/api/commits.html#get-a-single-commit
[9] документация по фильтрам: https://stedolan.github.io/jq/manual/
[10] релиз: https://about.gitlab.com/2017/09/22/gitlab-10-0-released/
[11] gitlab-vagrant: https://github.com/rgl/gitlab-vagrant
[12] инструкции: https://docs.gitlab.com/runner/install/linux-manually.html
[13] Gitlab CI variables: http://docs.gitlab.com/ce/ci/variables/README.html
[14] GitLab CI для непрерывной интеграции и доставки в production. Часть 1: наш пайплайн: https://habrahabr.ru/company/flant/blog/332712/
[15] GitLab CI для непрерывной интеграции и доставки в production. Часть 2: преодолевая трудности: https://habrahabr.ru/company/flant/blog/332842/
[16] Источник: https://habrahabr.ru/post/340996/
Нажмите здесь для печати.