
💡 Зображення на обкладинці показує типовий CI/CD конвеєр у дії, частково намальований OpenAI DALL-E, але в цій статті ми збираємося розробити щось корисне
Зміст
Цей туторіал демонструє, як розробляти, тестувати та розгортати розширення для CI для GitHub Actions, Azure Pipelines та CircleCI з єдиного монорепозиторію, використовуючи TypeScript та Node.js. Він охоплює створення монорепозиторію, спільне використання коду між actions та tasks, а також збірку та публікацію розширень.
Зміст
Це відносно короткий туторіал про те, як розробляти, тестувати та розгортати свої розширення для CI для GitHub Actions, Azure Pipelines та CircleCI з єдиного монорепозиторію, і він базується на досвіді створення розширень Qodana для CI.
Почніть з офіційних шаблонів
Давайте виберемо технологічний стек для наших розширень CI.
Гаразд, я не буду вибирати. Я просто розповім вам, чому я використовував TypeScript та node.js для розширень.
Переваги використання actions на основі JS
- Більш гнучкі, ніж підходи на основі bash/Dockerfile
- Різні бібліотеки (як actions/toolkit та microsoft/azure-pipelines-task-lib) з більш доступними та простими у використанні API доступні з коробки
- Написання тестів відносно просте
Недоліки
- JavaScript
Тож давайте напишемо action на основі TypeScript!
GitHub Actions
Я виявив, що документація GitHub Actions простіша для читання, ніж Azure, тому я рекомендую почати писати та тестувати свої розширення на GitHub, використовуючи офіційний шаблон actions/typescript-action. Згаданий шаблон надає хорошу відправну точку; я не буду повторювати кроки тут. Пограйтеся з ним, напишіть щось просте, а потім поверніться сюди для наступних кроків.
Azure Pipelines
GitHub Actions побудовано на інфраструктурі Azure, тому портування вашого GitHub action до Azure Pipelines має бути відносно простим.
Отже,
- “action” стає “task”
- він упаковується трохи інакше, розповсюджується та встановлюється по-іншому
І визначення task task.json таке саме, як у action action.yml.
Наприклад, маючи наступний action.yml:
name: 'Your name here'
description: 'Provide a description here'
author: 'Your name or organization here'
inputs:
milliseconds: # change this
required: true
description: 'input description here'
default: 'default value if applicable'
runs:
using: 'node16'
main: 'dist/index.js'“Легко” перекладається в наступну задачу Azure:
{
"$schema": "https://raw.githubusercontent.com/Microsoft/azure-pipelines-task-lib/master/tasks.schema.json",
"id": "822d6cb9-d4d1-431b-9513-e7db7d718a49",
"name": "YourTaskNameHere",
"friendlyName": "Your name here",
"description": "Provide a description here",
"helpMarkDown": "Provide a longer description here",
"author": "Your name or organization here",
"version": {
"Major": 1,
"Minor": 0,
"Patch": 0
},
"instanceNameFormat": "YourTaskNameHere",
"inputs": [
{
"name": "milliseconds",
"type": "string",
"label": "label name here",
"defaultValue": "default value if applicable",
"required": true,
"helpMarkDown": "input description here"
}
],
"execution": {
"Node10": {
"target": "index.js"
}
}
}З такого простого прикладу можна побачити, чому я запропонував почати з GitHub Actions. Але давайте продовжимо.
Щоб почати розробляти свою нову яскраву задачу для Azure Pipelines, я пропонію просто скопіювати директорію action, а потім виконати кроки з офіційної документації Azure – це досить просто.
- Створіть
vss-extension.json - Створіть
task.jsonта помістіть його в директоріюdist(насправді краще назвати його за назвою задачі) - Якщо ви використовували будь-які методи з
@actions/coreабо@actions/githubу вашому action, вам потрібно замінити їх відповідними методами зazure-pipelines-task-lib(наприклад,core.getInput→tl.getInput)
API azure-pipelines-task-lib схожий на @actions/core та інші бібліотеки @actions/*.
Наприклад, у нас є метод для отримання вхідних параметрів:
export function getInputs(): Inputs {
return {
milliseconds: core.getInput('milliseconds'),
}
}І те саме для Azure Pipelines:
export function getInputs(): Inputs {
return {
milliseconds: tl.getInput('milliseconds'),
}
}Для більш реальних випадків не соромтеся досліджувати нашу кодову базу Qodana GitHub Actions utils та Azure Pipelines task utils.
Створіть монорепозиторій
Ми збираємося використовувати npm workspaces для управління монорепозиторієм.
Помістіть свій код action та task в піддиректорії (наприклад, github) вашого щойно створеного монорепозиторію. А потім створіть файл package.json у кореневій директорії.
{
"name": "@org/ci",
"version": "1.0.0",
"description": "Common code for CI extensions",
"license": "Apache-2.0",
"workspaces": [
"github",
"azure"
],
"devDependencies": {
"typescript": "latest",
"eslint": "latest",
"eslint-plugin-github": "latest",
"eslint-plugin-jest": "latest",
"prettier": "latest",
"ts-node": "latest"
}
}
Тож структура монорепозиторію виглядає так:
...
├── action.yaml
├── github/
├── azure/
└── package.jsonПісля реалізації налаштування workspace ви можете запускати задачі та actions з кореневої директорії. Наприклад, щоб запустити задачу build з директорії github, ви можете використовувати наступну команду:
npm run -w github buildСпільне використання коду між actions та tasks
Найцінніша частина використання підходу з монорепозиторієм починається тут: ви можете спільно використовувати код між вашими actions та tasks.
Ми збираємося виконати наступні кроки:
- Створити директорію
commonу корені монорепозиторію, підпроєкт для спільного коду - Оновити конфігурації компілятора
tsconfig.jsonз усіх піддиректорій для правильної збірки проєкту
Спочатку давайте створимо базовий tsconfig – tsconfig.base.json з базовими налаштуваннями, які будуть використовуватися в усіх підпроєктах:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"composite": true
},
"exclude": ["node_modules", "**/*.test.ts", "*/lib/**"]
}Потім створіть простий tsconfig.json у корені проєкту:
{
"references": [
{ "path": "common" },
{ "path": "azure" },
{ "path": "github" }
],
"files": []
}Потім common/tsconfig.json:
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"outDir": "./lib",
"rootDir": "."
},
"files": ["include your files here or use typical include/exclude patterns"]
}І нарешті, оновіть файли tsconfig.json у підпроєктах (вони в основному однакові, наприклад, github/tsconfig.json):
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"outDir": "./lib",
"rootDir": "./src"
},
"references": [
{ "path": "../common" }
]
}Тепер ви можете використовувати спільний код з директорії common у ваших actions та tasks. Наприклад, у нас є файл qodana.ts у директорії common, який містить функцію getQodanaUrl, яка повертає URL до інструменту Qodana CLI. І ми використовуємо його як в actions, так і в tasks.
Збірка та публікація
У вас вже є GitHub workflows з шаблону, налаштовані для публікації ваших actions у релізи вашого репозиторію. Для автоматизованих релізів ми використовуємо GH CLI, і у нас є простий скрипт, який публікує changelog у релізи репозиторію:
#!/usr/bin/env bash
previous_tag=0
for current_tag in $(git tag --sort=-creatordate)
do
if [ "$previous_tag" != 0 ];then
printf "## Changelog\n"
git log ${current_tag}...${previous_tag} --pretty=format:'* %h %s' --reverse | grep -v Merge
printf "\n"
break
fi
previous_tag=${current_tag}
doneІ GitHub workflow, який його запускає:
name: 'Release'
on:
push:
tags:
- '*'
permissions:
contents: write
jobs:
github:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- run: |
./changelog.sh > changelog.md
gh release create ${GITHUB_REF##*/} -F changelog.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}Для релізів задач Azure Pipelines ви можете використовувати офіційний підхід від Azure. Але також ви можете зробити те саме на інфраструктурі GitHub actions, оскільки їхній інструмент публікації може бути встановлений будь-де. Отже, у нашому випадку це вирішується простою задачею GitHub workflow:
azure:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set Node.js 12.x
uses: actions/[email protected]
with:
node-version: 12.x
- name: Install dependencies
run: npm ci && cd vsts/QodanaScan && npm ci && npm i -g tfx-cli
- name: Package and publish
run: |
cd vsts && npm run azure
mv JetBrains.qodana-*.vsix qodana.vsix
tfx extension publish --publisher JetBrains --vsix qodana.vsix -t $AZURE_TOKEN
env:
AZURE_TOKEN: ${{ secrets.AZURE_TOKEN }}З таким налаштуванням кожен реліз відбувається автоматично при кожному push тега.
git tag -a v1.0.0 -m "v1.0.0" && git push origin v1.0.0
CircleCI?
Ах, так, ця стаття згадувала також orb CircleCI… Налаштування CircleCI є простим, але не підтримує розширення TypeScript, тому вам доведеться упакувати свій код у Docker-образ або бінарник і запустити його там. Єдина причина, чому він включений у цей пост, полягає в тому, що ми будуємо наш orb за допомогою підходу з монорепозиторієм, який добре працює.
Реалізуйте офіційний шаблон orb і помістіть його у ваш монорепозиторій, щоб структура виглядала так:
...
├── action.yaml
├── github/
├── azure/
├── src/ # orb source code here
└── package.jsonІ не забудьте закомітити директорію .circleci/ у ваш репозиторій, щоб CircleCI міг перевіряти, тестувати та публікувати ваш orb.
