Стадии git
git:
- add: <absolute path in git repository>
  to: <absolute path inside image>
  owner: <owner>
  group: <group>
  includePaths:
  - <path or glob relative to path in add>
  excludePaths:
  - <path or glob relative to path in add>
  stageDependencies:
    install:
    - <path or glob relative to path in add>
    beforeSetup:
    - <path or glob relative to path in add>
    setup:
    - <path or glob relative to path in add>
git:
- url: <git repo url>
  branch: <branch name>
  commit: <commit>
  tag: <tag>
  add: <absolute path in git repository>
  to: <absolute path inside image>
  owner: <owner>
  group: <group>
  includePaths:
  - <path or glob relative to path in add>
  excludePaths:
  - <path or glob relative to path in add>
  stageDependencies:
    install:
    - <path or glob relative to path in add>
    beforeSetup:
    - <path or glob relative to path in add>
    setup:
    - <path or glob relative to path in add>

Что такое git mapping?

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

werf добавляет файлы из Git-репозитория в образ, копируя их с помощью git archive, либо накладывая Git-патч. При повторных сборках и появлении изменений в Git-репозитории werf добавляет patch к собранному ранее образу, чтобы в конечном образе отразить изменения файлов и папок. Более подробно механизм переноса файлов и накладывания патчей рассматриваются в следующей секции.

Конфигурация git mapping поддерживает фильтры, что позволяет сформировать практически любую файловую структуру в образе, используя произвольное количество git mappings. Также вы можете указать группу и владельца конечных файлов в образе, что освобождает от необходимости делать это отдельной командой (chown).

В werf реализована поддержка сабмодулей Git (git submodules), и если werf определяет, что какая-то часть git mapping является сабмодулем, то принимаются соответствующие меры, чтобы обрабатывать изменения в сабмодулях корректно.

Все Git-сабмодули проекта связаны с конкретным коммитом, поэтому все разработчики, работающие с репозиторием с сабмодулями, получают одинаковое содержимое. werf не инициализирует и не обновляет сабмодули, а использует соответствующие связанные коммиты.

Пример добавления файлов из каталога /src локального Git-репозитория в каталог /app собираемого образа и добавления кода PhantomJS из удаленного репозитория в каталог /src/phantomjs собираемого образа:

git:
- add: /src
  to: /app
- url: https://github.com/ariya/phantomjs
  add: /
  to: /src/phantomjs

Синтаксис

Для добавления кода из локального Git-репозитория используется следующий синтаксис:

  • add — (необязательный параметр) путь к директории или файлу, содержимое которого (которой) нужно добавить в образ. Указывается абсолютный путь относительно корня репозитория, т.е. он должен начинаться с /. По умолчанию копируется все содержимое репозитория, отсутствие параметра add равносильно указанию add: /;
  • to — путь внутри образа, куда будет скопировано соответствующее содержимое;
  • owner — имя или ID пользователя-владельца файлов в образе;
  • group — имя или ID группы-владельца файлов в образе;
  • excludePaths — список исключений (маска) при рекурсивном копировании файлов и папок. Указывается относительно пути, указанного в add;
  • includePaths — список масок файлов и директорий для рекурсивного копирования. Указывается относительно пути, указанного в add;
  • stageDependencies — список масок файлов и директорий для указания зависимости пересборки стадии от их изменений. Позволяет указать, при изменении каких файлов и директорий необходимо принудительно пересобирать конкретную пользовательскую стадию. Более подробно рассматривается здесь.

При использовании удаленных репозиториев дополнительно используются следующие параметры:

  • url — адрес удаленного репозитория;
  • branch, tag, commit — имя ветки, тега или коммита соответственно. По умолчанию — ветка master.

По умолчанию использование директивы branch запрещено гитерминизмом (подробнее об этом в статье).

Использование git mapping

Копирование директорий

Параметр add определяет источник, путь в Git-репозитории, откуда файлы рекурсивно копируются в образ и помещаются по адресу, указанному в параметре to. Если параметр не определен, то по умолчанию используется значение /, т.е. копируется весь репозиторий. Пример простейшей конфигурации, добавляющей содержимое всего локального Git-репозитория в образ в каталог /app.

git:
- add: /
  to: /app
git repository files tree
image files tree

Также можно указать несколько git mappings:

git:
- add: /src
  to: /app/src
- add: /assets
  to: /static
git repository files tree
image files tree

Следует отметить, что конфигурация git mapping не похожа, например, на копирование типа cp -r /src /app. Параметр add указывает на содержимое каталога, которое будет рекурсивно копироваться из репозитория. Поэтому, если каталог /assets со всем содержимым из репозитория должен быть скопирован в каталог /app/assets образа, то имя assets вы должны указать два раза. Либо, как вариант, вы можете использовать фильтр (например, параметр includePaths).

Примеры обоих вариантов, которые вы можете использовать для достижения одинакового результата:

git:
- add: /assets
  to: /app/assets

либо

git:
- add: /
  to: /app
  includePaths: assets

Изменение владельца

При добавлении файла из Git-репозитория вы можете указать имя и/или группу владельца файлов в образе. Добавляемым файлам и каталогам в образе при копировании будут установлены соответствующие права. Пользователь и группа могут быть указаны как именем, так и числовым ID (userid, groupid).

Пример использования:

git:
- add: /src/index.php
  to: /app/index.php
  owner: www-data

Если указан только параметр owner, как в приведенном примере, то группой-владельцем устанавливается основная группа указанного пользователя в системе.

В результате в каталог /app образа будет добавлен файл index.php и ему будут установлены следующие права:

index.php owned by www-data user and group

Если значения параметра owner или group не числовые ID, а текстовые (т.е. названия соответственно пользователя и группы), то соответствующие пользователь и группа должны существовать в системе. Их нужно добавить заранее при необходимости (к примеру, на стадии beforeInstall), иначе при сборке возникнет ошибка.

git:
- add: /src/index.php
  to: /app/index.php
  owner: wwwdata

Использование фильтров

Парамеры фильтров includePaths и excludePaths используются при составлении списка файлов для добавления. Эти параметры содержат набор путей или масок, применяемых соответственно для включения и исключения файлов и каталогов при добавлении в образ.

Фильтр excludePaths работает следующим образом: каждая маска списка применяется к каждому файлу, найденному по пути add. Если путь файла удовлетворяет хотя бы одной маске, то он исключается из списка файлов на добавление. Если путь файла не удовлетворяет ни одной маске, то он добавляется в образ.

Фильтр includePaths работает наоборот — если путь файла удовлетворяет хотя бы одной маске, то он добавляется в образ.

Конфигурация git mapping может содержать оба фильтра. В этом случае файл добавляется в образ, если его путь удовлетворяет хотя бы одной маске includePaths и не удовлетворяет ни одной маске excludePaths.

Пример:

git:
- add: /src
  to: /app
  includePaths:
  - '**/*.php'
  - '**/*.js'
  excludePaths:
  - '**/*-dev.*'
  - '**/*-test.*'

В приведенном примере добавляются .php и .js файлы из каталога /src, исключая файлы с суффиксом -dev. или -test. в имени файла.

Последний шаг в алгоритме с добавлением суффикса**/* сделан для удобства — вам достаточно указать название каталога в параметрах git mapping, чтобы все его содержимое удовлетворяло шаблону параметра.

Маска может содержать следующие шаблоны:

  • * — удовлетворяет любому файлу. Шаблон включает . и исключает /.
  • ** — удовлетворяет директории со всем ее содержимым, рекурсивно.
  • ? — удовлетворяет любому одному символу в имени файла (аналогично regexp-шаблону /.{1}/).
  • [set] — удовлетворяет любому символу из указанного набора символов. Аналогично использованию в regexp-шаблонах, включая указание диапазонов типа [^a-z].
  • \ — экранирует следующий символ.

Маска, которая начинается с шаблона * или **, должна быть взята в одинарные или двойные кавычки в werf.yaml:

  • "*.rb" — двойные кавычки;
  • '**/*' — одинарные кавычки.

Примеры фильтров:

add: /src
to: /app
includePaths:
# Удовлетворяет всем PHP-файлам, расположенным конкретно в каталоге /src.
- '*.php'

# Удовлетворяет всем PHP файлам рекурсивно, начиная с каталога /src
# (также удовлетворяет файлам *.php, т.к. '.' включается шаблон **).
- '**/*.php'

# Удовлетворяет всем файлам в каталоге /src/module1 рекурсивно.
- module1

Фильтр includePaths может применяться для копирования одного файла без изменения имени. Пример:

git:
- add: /src
  to: /app
  includePaths: index.php

Наложение путей копирования

Если вы определяете несколько git mappings, вы должны учитывать, что при наложении путей в образе в параметре to вы можете столкнуться с невозможностью добавления файлов. Пример:

git:
- add: /src
  to: /app
- add: /assets
  to: /app/assets

Чтобы избежать ошибок сборки, werf определяет возможные наложения касающиеся фильтров includePaths и excludePaths, и если такое наложение присутствует, то werf пытается разрешить самые простые конфликты, неявно добавляя соответствующий параметр excludePaths в git mapping. Однако, такое поведение может все-таки привести к неожиданным результатам, поэтому лучше всего избегать наложения путей при определении git mappings.

В примере выше werf в итоге неявно добавит параметр excludePaths, и итоговая конфигурация будет следующей:

git:
- add: /src
  to: /app
  excludePaths:  # werf добавил этот фильтр, чтобы исключить конфликт наложения результирующих путей
  - assets       # между /src/assets и /assets
- add: /assets
  to: /app/assets

Работа с удаленными репозиториями

werf может использовать удаленные репозитории в качестве источника файлов. Для указания адреса внешнего репозитория используется параметр url. werf поддерживает работу с удаленными репозиториями по протоколам https и git+ssh.

HTTPS

Синтаксис для работы по протоколу https:

git:
- url: https://[USERNAME[:PASSWORD]@]repo_host/repo_path[.git/]

Указание логина и пароля при доступе по https опционально.

Пример доступа к репозиторию из pipeline GitLab CI с использованием переменных окружения:

git:
- url: https://{{ env "CI_REGISTRY_USER" }}:{{ env "CI_JOB_TOKEN" }}@registry.gitlab.company.name/common/helper-utils.git

В приведенном примере используется метод env библиотеки Sprig для доступа к переменным окружения.

Git, SSH

Доступ к удаленному репозиторию с помощью протокола git защищается с использованием доступа поверх SSH. Это распространенная практика, используемая в частности GitHub, Bitbucket, GitLab, Gogs, Gitolite и т.д. Обычно адрес репозитория выглядит следующим образом:

git:
- url: git@gitlab.company.name:project_group/project.git

Для работы с удаленными репозиториями по SSH необходимо понимать, как werf находит SSH-ключи (читай далее подробнее).

Работа с SSH-ключами

SSH-ключи для доступа предоставляются через SSH-agent. SSH-agent — это демон, который работает через файловый сокет, путь к которому хранится в переменной окружения SSH_AUTH_SOCK. werf монтирует этот файловый сокет во все сборочные контейнеры и устанавливает переменную окружения SSH_AUTH_SOCK. Т.e. соединение с удаленным Git-репозиторием устанавливается с использованием ключей, зарегистрированных в запущенном SSH-агенте.

werf использует следующий алгоритм для определения запущенного SSH-агента:

  • werf запущен с ключом --ssh-key (одним или несколькими):
    • Запускается временный SSH-агент, в который добавляются указанные при запуске werf ключи. Эти ключи используются при всех операциях с удаленными репозиториями.
    • Уже запущенный SSH-агент игнорируется.
  • werf запущен без указания ключа --ssh-key и есть запущенный SSH-агент:
    • Используется переменная окружения SSH_AUTH_SOCK, ключи добавляются в соответствующий SSH-агент и используются далее при всех операциях работы с удаленными репозиториями.
  • werf запущен без указания ключа --ssh-key и нет запущенного SSH-агента:
    • Если существует файл ~/.ssh/id_rsa, запускается временный SSH-агент, в который добавляется ключ из файла ~/.ssh/id_rsa.
  • Если ни один из вариантов не применим, то SSH-агент не запускается и при операциях с внешними Git-репозиториями не используются никакие SSH-ключи. Сборка образа, с объявленными удаленными репозиториями в git mapping, завершится с ошибкой.

Подробнее про gitArchive, gitCache, gitLatestPatch

Далее будет более подробно рассмотрен процесс добавления файлов в целевой образ. Как упоминалось ранее, Docker-образ состоит из набора слоёв. Чтобы понимать, какие слои создает werf, представим последовательную сборку трех коммитов: 1, 2 и 3:

  • Сборка коммита 1. Исходя из конфигурации git mapping, все соответствующие файлы добавляются в один слой. Сам процесс добавления выполняется с помощью git archive. Получившийся слой соответствует стадии gitArchive.
  • Сборка коммита 2. Накладывается патч с изменениями файлов, в результате чего получается еще один слой. Получившийся слой соответствует стадии gitLatestPatch.
  • Сборка коммита 3. Файлы уже добавлены, и werf накладывает патч, обновляя слой gitLatestPatch.

Сборки для этих коммитов можно представить ​​следующим образом:

  gitArchive gitLatestPatch
Commit No. 1 is made, build at 10:00 files as in commit No. 1 -
Commit No. 2 is made, build at 10:05 files as in commit No. 1 files as in commit No. 2
Commit No. 3 is made, build at 10:15 files as in commit No. 1 files as in commit No. 3

Со временем количество коммитов растет, и размер патча между коммитом №1 и текущим может стать довольно большим. Это еще больше увеличит размер последнего слоя и общий размер стадий. Чтобы предотвратить неконтролируемый рост последнего слоя, werf предоставляет дополнительный промежуточный этап — gitCache. Когда gitLatestPatch diff становится слишком большим, большая часть его diff объединяется с gitCache diff, тем самым уменьшая размер стадии gitLatestPatch.

Rebase и git-стадии

Каждая Git-стадия хранит служебные лейблы с SHA-коммитами, которые использовались при сборке стадии. Эти коммиты будут использоваться при сборке следующей Git-стадии при создании патчей (по сути это git diff COMMIT_FROM_PREVIOUS_GIT_STAGE LATEST_COMMIT для каждого git-mapping).

Если в стадии сохранён коммит, который отсутствует в Git-репозитории (например, после выполнения rebase), werf пересоберёт эту стадию, используя актуальный коммит.

Git worktree

Для корректной работы сборщика stapel werf’у требуется полная Git-история проекта, чтобы работать в наиболее эффективном режиме. Поэтому по умолчанию werf выполняет fetch истории для текущего Git-проекта, когда это требуется. Это означает, что werf может автоматически сконвертировать shallow-clone репозитория в полный clone и скачать обновлённый список веток и тегов из origin в процессе очистки образов.

Поведение по умолчанию описывается следующими настройками:

gitWorktree:
  forceShallowClone: false
  allowUnshallow: true
  allowFetchOriginBranchesAndTags: true

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

gitWorktree:
  forceShallowClone: true
  allowUnshallow: false