- TL;DR
- Что входит в состав конвейера?
- Что входит в конвейер GitLab?
- Наша цель
- Пошаговое руководство
- Шаг 1: Создайте свой пакет
- Шаг 2: Добавьте файл игнорирования npm
- Шаг 3: Добавьте файл конфигурации CI/CD
- Шаг 4: Добавьте задание «install»
- Шаг 5: Добавить задание «lint»
- Шаг 6: Добавьте задание «test»
- Шаг 7: Добавьте задание «опубликовать»
- Шаг 8: Зафиксировать и отправить
- Заключение
TL;DR
Посмотрите на конфигурационный файл .gitlab-ci.yml
в моем недавнем проекте safe-env-vars. Здесь показано, как я настроил встроенные CI/CD конвейеры GitLab для автоматической сборки и публикации пакета в npm. В этом посте мы подробно расскажем о шагах, необходимых для настройки.
Что входит в состав конвейера?
Задолго до того, как GitHub присоединился к партии с GitHub Actions, на GitLab можно было запускать рабочие нагрузки непрерывной интеграции и развертывания (CI/CD). Вот уже более десяти лет мы можем использовать тот же инструмент, на котором размещены наши репозитории кода, для запуска конвейеров. Раньше я был большим поклонником Circle CI, особенно когда они представили поддержку контейнеров Docker, но уже несколько лет мне не приходилось пользоваться их услугами.
Но почему нас в первую очередь волнуют конвейеры CI/CD? Ну, непрерывная интеграция (CI) и непрерывное развертывание (CD) необходимы для обеспечения качества и гибкости программного обеспечения в распределенных командах. Они позволяют нам проводить автоматические проверки качества, выполнять наши наборы тестов, сканировать уязвимости безопасности, публиковать и развертывать наши приложения и выполнять множество других важных задач. Они также помогают экономить время на небольших проектах, таких как пакеты npm!
На высоком уровне конвейеры CI/CD охватывают следующие области:
- Сборка — установка зависимостей, компиляция кода или приложения и все остальное, необходимое для подготовки проекта к тестированию и развертыванию.
- Проверка — Выполнение статического анализа вашей кодовой базы, активов и скомпилированных артефактов для обеспечения их соответствия стандартам качества и безопасности.
- Тестирование — Выполнение набора тестов для подтверждения того, что все работает так, как ожидалось, и в проект не закрались ошибки.
- Развертывание — публикация пакета или развертывание приложения в облаке.
- Отчетность — предоставление информации об ошибках сборки, скорости разработки, уязвимостях безопасности и многом другом.
Что входит в конвейер GitLab?
Недавно я написал статью о переменных окружения, в которой обсуждался опубликованный мной пакет npm под названием safe-env-vars — этот пакет автоматически собирается и публикуется с помощью GitLab CI/CD (и он с открытым исходным кодом), поэтому, если вы хотите пропустить его, почему бы не просмотреть его репозиторий кода. Основной интерес для желающих пропустить 🏃💨 представляет файл .gitlab-ci.yml
, который сообщает GitLab о заданиях, которые мы хотим выполнить в конвейере.
Прежде чем мы перейдем к деталям, необходимо определить несколько терминов, которые использует GitLab:
- Конвейер — единый процесс CI/CD, состоящий из нескольких этапов и заданий, обычно запускаемый коммитом или слиянием в определенную ветку. Вы можете иметь различные конвейеры для разных целей, например, для проверки изменений в запросе на слияние или для проведения облачного развертывания.
- Этап — группа связанных заданий, которые должны быть успешно выполнены, прежде чем GitLab перейдет к следующему этапу конвейера. Если дальнейших этапов нет, конвейер будет успешно завершен.
- Задание — определенное вами задание, которое обычно выполняется в собственном идемпотентном контейнере (например,
npm run build
,npm publish
и т. д.). Несколько заданий в рамках одного этапа могут выполняться параллельно, поскольку они обычно независимы друг от друга.
Конвейеры GitLab очень мощные и могут быть настроены множеством различных способов. Некоторые возможности ограничены платными и корпоративными уровнями цен, но все функции, которые мы будем использовать ниже, доступны бесплатно, если у вас осталось несколько минут конвейера! На момент написания статьи GitLab предоставляет 400 бесплатных минут в месяц для выполнения конвейера, что более чем достаточно для наших нужд.
Наша цель
Итак, что же мы хотим сделать? Когда я создавал пакет safe-env-vars, у меня было две цели:
- Автоматически публиковать мой пакет в npm каждый раз, когда я фиксирую или сливаюсь с веткой
main
, чтобы мне не нужно было делать это вручную. - Выполнить несколько проверок качества кода, чтобы выявить любые глупые ошибки. Я не хотел тратить много времени на настройку этих проверок, поэтому решил ограничиться линтингом кода и юнит-тестированием.
Наш конвейер будет состоять из трех этапов
и четырех работ
:
Название этапа | Название задания | Описание задания |
---|---|---|
Подготовить | Установить | Запускает npm install для установки ключевых зависимостей разработки, таких как TypeScript и Jest, а также для сборки нашего пакета с помощью компилятора TS. |
Проверить | Lint | Запускает ESLint для проверки соответствия нашего кода согласованным правилам линтинга. |
Проверить | Запускает Jest, чтобы проверить, что все модульные тесты прошли и ничего не сломалось. | |
Опубликовать | Опубликовать | Запускает npm publish для публикации нашего пакета в реестре npm. |
Наш конечный конвейер будет выглядеть примерно так:
Пошаговое руководство
Шаг 1: Создайте свой пакет
Перед настройкой конвейера вам необходимо создать пакет с подходящим файлом package.json
. Помимо включения обычных свойств, таких как зависимые элементы, автор и так далее, вам нужно убедиться, что свойство "main"
указывает на файл JavaScript, который является точкой входа в ваш пакет.
Для проектов TypeScript вам понадобится несколько дополнительных битов, но вы можете игнорировать их, если работаете с ванильным JavaScript (но зачем 😭):
- Свойство
"types"
должно указывать на точку входа для экспортируемых определений типов. - Свойство
"scripts.build"
должно вызывать компилятор TS, что-то вроде"build": "npx tsc --outDir ./dist"
. - Свойство
"scripts.prepublishOnly"
должно выглядеть как"prepublishOnly": "npm run build"
, чтобы автоматически собрать ваш пакет перед публикацией с помощьюnpm publish
.
В итоге вы можете получить файл package.json
, похожий на этот:
{
"name": "my-package",
"version": "1.0.0",
"main": "dist/src/main.js",
"types": "index.d.ts",
"scripts": {
"build": "npx tsc --outDir ./dist",
"lint": "npx eslint . --ext .ts,.tsx,.js,.jsx",
"prepublishOnly": "npm run build",
"test": "npx jest --watch",
"test:cov": "npx jest --coverage --verbose --runInBand"
},
"dependencies": {...},
"devDependencies": {...},
}
Я предпочитаю, чтобы в package.json были определены дополнительные скрипты для линтинга (lint
), тестирования (test
) и тестирования с покрытием (test:cov
).
Шаг 2: Добавьте файл игнорирования npm
В корне хранилища кода вам нужно добавить файл .npmignore
и в нем перечислить все пути, которые вы хотите исключить из опубликованного пакета. Это важно, потому что есть некоторые файлы, которые вам не нужно включать, и некоторые, которые вы определенно не должны включать.
Синтаксис очень похож на .gitignore
, и в итоге у вас может получиться что-то вроде следующего. Это гарантирует, что каталог src
не будет включен (для проектов TypeScript мы хотим публиковать только каталог dist
). Здесь также перечислены различные конфигурационные файлы, которые не важны для использования пакета в приложениях и просто добавляют ненужный вес в каталог node_modules.
src/
.gitlab-ci.yml
.eslintignore
.gitignore
.eslintrc.js
.gitmodules
.prettierrc.js
jest.config.mjs
tsconfig.json
dist/**/*.spec.*
Шаг 3: Добавьте файл конфигурации CI/CD
В корне вашего репозитория кода создайте пустой файл .gitlab-ci.yml
со следующим содержимым (полная ссылка на файл конфигурации GitLab доступна здесь: https://docs.gitlab.com/ee/ci/yaml):
image: node:16
variables:
ARTIFACT_DOWNLOAD_ATTEMPTS: 2 # Number of retries for restoring saved artifacts.
FORCE_COLOR: '1' # Whether to force colour output in compatible tooling.
GET_SOURCES_ATTEMPTS: 2 # Number of retries for git commands.
GIT_STRATEGY: fetch # How to pull submodules.
GIT_SUBMODULE_STRATEGY: recursive # How to treat nested git submodules.
RESTORE_CACHE_ATTEMPTS: 2 # Number of retries for restoring cached files.
stages:
- prepare
- verify
- publish
Приведенный выше YAML состоит из трех частей:
- Строка
image
указывает образ и тег Docker, который будет использоваться (по умолчанию) для каждого задания, в нашем случае Node.js версии 16.x. При необходимости это можно изменить в каждом отдельном задании. Продвинутые разработчики могут захотеть создать и опубликовать свои собственные образы Docker с любыми дополнительными зависимостями, которые требует их конвейер. - В словаре
variables
перечислены переменные окружения, которые необходимо сделать доступными для каждого задания. Они также могут быть настроены для каждого задания. Я начал с некоторых полезных переменных, которые настраивают поведение конвейера (см. комментарии рядом с каждой из них, чтобы узнать, что они делают). Например, переменнаяFORCE_COLOR
используется многими (но не всеми) инструментами, чтобы заставить их выводить цветовые коды ANSI, даже если они не могут определить устройство TTY. - Наконец, массив
stages
перечисляет имена каждого из трех этапов, на которых будут располагаться наши задания. Далее мы определим каждое из заданий.
Шаг 4: Добавьте задание «install»
В нижней части вашего конфигурационного файла добавьте следующий ключ верхнего уровня, install
, который определяет задание с именем «install». Свойство install.stage
определяет, к какой стадии принадлежит задание, в данном случае к стадии «prepare».
install:
stage: prepare
interruptible: true
retry: 1
dependencies: []
artifacts:
name: 'deps_$CI_COMMIT_REF_SLUG'
paths:
- node_modules/
when: on_success
expire_in: 12h
script:
- npm config set -- '//registry.npmjs.org/:_authToken' "${NPM_AUTH_TOKEN}"
- npm install --no-progress --no-audit --no-fund --verbose
only:
- merge_requests
- develop
- main
Полный справочник ключевых слов на сайте GitLab объяснит все различные свойства, определенные выше, но важно отметить следующие:
- «artifacts» — указывает GitLab, какой путь (пути) сохранять между заданиями, в данном случае это каталог
node_modules
, поскольку мы будем запускатьnpm install
только в этом задании, и сохранять установленные зависимости для использования в последующих заданиях для экономии времени. - «script» — Строки сценария оболочки для выполнения задания, в данном случае для установки токена аутентификации NPM, позволяющего публикацию в реестре. Переменная окружения
NPM_AUTH_TOKEN
должна быть установлена в пользовательском интерфейсе GitLab для вашего проекта или группы, а не внесена в репозиторий кода! Вы можете найти нужную страницу в GitLab, нажав CI/CD в левом меню любого проекта или группы и перейдя в раздел «переменные». - «only» — Массив, перечисляющий события, которые могут вызвать включение задания в конвейер, в данном случае мы выполняем задание для запросов на слияние и для коммитов/мергеров в ветках
develop
иmain
. Если ни одно из условий не совпадает, задание не будет включено в конвейер.
Шаг 5: Добавить задание «lint»
Добавьте следующее задание, lint
, в нижней части файла конфигурации:
# Lints the codebase.
lint:
stage: verify
interruptible: true
retry: 1
dependencies:
- install
script:
- npm run lint
only:
- merge_requests
- develop
- main
Здесь мы имеем аналогичный набор свойств, определяющих, что делает наше задание, включая stage
, на этот раз группирующий наше задание в стадию verify
, и script
, который просто запускает скрипт npm run lint
, который мы определили в нашем package.json.
Однако здесь есть новое свойство, на которое следует обратить внимание. Для выполнения нашего скрипта lint необходимо установить ESLint и связанные с ним зависимости. Каждое задание CI/CD выполняется в новом контейнере и поэтому не имеет изменений, внесенных в предыдущее задание install
, таких как каталог node_modules
.
Чтобы принести наши установленные node_modules
в это задание lint
, нам нужно указать массив dependencies
и перечислить в нем наше задание install
. Это заставит GitLab извлечь все артефакты, указанные в задании install (т.е. node_modules
), и поместить их в те же места, где они были раньше. Что касается npm, то каталог node_modules
всегда был там!
Шаг 6: Добавьте задание «test»
Добавьте следующее задание, test
, в нижней части конфигурационного файла:
# Runs the test suite.
test:
stage: verify
interruptible: true
retry: 1
dependencies:
- install
script:
- npm run test:cov
only:
- merge_requests
- develop
- main
И снова наше задание указывает задание install
в качестве зависимости, чтобы каталог node_modules
был снова доступен, и мы указываем простой bash-скрипт, который запускает npm run test:cov
— это просто запускает нашу программу тестирования, Jest, с включенным покрытием. Мы также помещаем это задание в стадию verify
, и оно будет выполняться параллельно с заданием lint
для экономии времени.
Вы могли заметить, что мы указываем свойство interruptible: true
, которое просто говорит GitLab, что ему разрешено завершить наш конвейер во время выполнения этого задания, если это необходимо. Это полезно, когда новый конвейер хочет начать работу (т.е. что-то новое было зафиксировано в той же ветке), и в противном случае ему пришлось бы ждать завершения работы текущего конвейера, что экономит нам время и минуты работы конвейера!
Шаг 7: Добавьте задание «опубликовать»
Добавьте следующее задание, test
, в нижней части конфигурационного файла:
# Publishes the package to npm.
publish:
stage: publish
interruptible: false
retry: 1
dependencies:
- install
script:
- npm config set -- '//registry.npmjs.org/:_authToken' "${NPM_AUTH_TOKEN}"
- npm publish --verbose
resource_group: 'deployment-$CI_COMMIT_REF_SLUG'
rules:
- if: '$CI_DEPLOY_FREEZE != null'
when: never
- if: '$CI_COMMIT_REF_NAME != "main" && $CI_COMMIT_REF_NAME != "develop"'
when: never
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_COMMIT_REF_NAME == "develop"'
when: never
- when: on_success
Здесь мы установили свойство interruptible
в false
, потому что мы не хотим, чтобы GitLab остановил конвейер на полпути к публикации, оставив нас в ситуации, когда мы не можем быть уверены, в каком состоянии находится наш пакет… была ли эта версия развернута или нет? 🙀📦
Мы также указываем retry: 1
, что говорит GitLab повторить задание еще один раз, если оно не удалось. Это может быть полезно в случаях, когда вы полагаетесь на побочные эффекты для успешного выполнения задания, например, npm publish
делает HTTP-запросы к реестру npm, которые могут не сработать из-за временных сбоев в сети или проблем на стороне npm. В таких случаях мы хотим автоматически повторить попытку, если проблема разрешится сама собой.
Другим импортируемым свойством здесь является rules
, где мы перечисляем массив объектов. Каждый объект определяет правило, когда должно выполняться наше задание публикации. Мы используем это свойство вместо only
, потому что нам нужно определить гораздо более сложное поведение, чем позволяет only
.
В приведенном выше коде мы указали четыре правила, и будет применено первое правило, которое совпадет:
- Если установлена переменная окружения
CI_DEPLOY_FREEZE
, никогда не выполнять задание публикации. - Если ветка, на которой мы работаем, не
main
илиdevelop
, никогда не выполняйте задание публикации. - Если мы находимся в области действия запроса на слияние, никогда не выполняйте задание на публикацию.
- В противном случае, если остальная часть конвейера прошла успешно, выполните задание публикации.
Синтаксис для rules
очень мощный и позволяет нам задать сложный набор условий, когда задание должно выполняться (или нет). Здесь я могу описать гораздо больше, чем нужно, поэтому рекомендую вам обратиться к документации GitLab: https://docs.gitlab.com/ee/ci/yaml.
Шаг 8: Зафиксировать и отправить
Когда ваш пакет и конфигурационный файл GitLab CI/CD готовы, вы можете зафиксировать и отправить изменения наверх. Если все прошло по плану, в пользовательском интерфейсе GitLab для вашего репозитория должен появиться конвейер в разделе CI/CD в левом меню. Щелкнув на конвейере, вы можете проследить за выполнением заданий и просмотреть журналы вывода для каждого отдельного задания.
Ниже показан пример четырех запущенных конвейеров. Первый конвейер в верхней части списка является самым последним и был запущен на ветке main
. Второй конвейер внизу был запущен по запросу на слияние и не включает задание publish
, поскольку правила, которые мы настроили, исключают его включение в конвейеры по запросу на слияние.
Щелчок на первом конвейере показывает этапы и задания, которые были выполнены. Поскольку этот конвейер был запущен на ветке main
, наши правила заставили включить задание publish
, и пакет был впоследствии (и автоматически) опубликован в npm. Счастливые дни! 🥳
Отсюда, если мы нажмем на задание test
, то увидим фрагмент вывода, выдаваемого Jest во время выполнения задания. Есть также некоторые другие результаты журнала, которые здесь не показаны для краткости.
Заключение
В заключение хочу сказать, что создать мощный CI/CD конвейер в GitLab очень просто, особенно если все, что вы делаете, это публикуете пакет в реестре npm. Конвейеры — это отличный способ проверки качества и надежности вашего кода, пакета или приложения, а также фантастическая экономия времени при автоматической публикации и развертывании. Для настройки конвейера в GitLab все, что вам нужно, это файл .gitlab-ci.yml
в корне вашего репозитория. Ссылку на ключевые слова для этого файла вы можете найти на сайте GitLab.