
💡 L’image de présentation montre un pipeline CI/CD typique en action, partiellement dessiné par OpenAI DALL-E, mais dans cet article, nous allons développer quelque chose de bénéfique
Contenu
Ce tutoriel démontre comment développer, tester et déployer des extensions CI pour GitHub Actions, Azure Pipelines et CircleCI depuis un seul monorepo en utilisant TypeScript et Node.js. Il couvre la création d’un monorepo, le partage de code entre actions et tâches, ainsi que la construction et la publication d’extensions.
Table des matières
Ceci est un tutoriel relativement court sur comment développer, tester et déployer vos extensions CI pour GitHub Actions, Azure Pipelines et CircleCI depuis un seul monorepo et est basé sur l’expérience de création des extensions CI Qodana.
Commencer avec les templates officiels
Choisissons la pile technologique pour nos extensions CI.
OK, je ne choisirai pas. Je vais juste vous dire pourquoi j’ai utilisé TypeScript et node.js pour les extensions.
Avantages de l’utilisation d’actions basées sur JS
- Plus flexible que les approches basées sur bash/Dockerfile
- Différentes bibliothèques (comme actions/toolkit et microsoft/azure-pipelines-task-lib) avec des APIs plus accessibles et faciles à utiliser sont disponibles prêtes à l’emploi
- L’écriture de tests est relativement simple
Inconvénients
- JavaScript
Alors écrivons une action basée sur TypeScript !
GitHub Actions
J’ai trouvé la documentation de GitHub Actions plus facile à lire qu’Azure, donc je recommanderais de commencer à écrire et tester vos extensions sur GitHub en utilisant le template officiel actions/typescript-action. Le template mentionné fournit un bon point de départ ; je ne répéterai pas les étapes ici. Jouez avec, écrivez quelques trucs simples, puis revenez ici pour les prochaines étapes.
Azure Pipelines
GitHub Actions est construit sur l’infrastructure Azure, donc porter votre action GitHub vers Azure Pipelines devrait être relativement facile.
Donc,
- l‘“action” devient la “tâche”
- elle est empaquetée un peu différemment, distribuée et installée d’une autre manière
Et la définition d’une tâche task.json est la même que celle de l’action action.yml.
Par exemple, en ayant le action.yml suivant :
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'Se traduit “facilement” en la tâche Azure suivante :
{
"$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"
}
}
}À partir d’un exemple aussi simple, on peut voir pourquoi j’ai suggéré de commencer avec GitHub Actions. Mais continuons.
Pour commencer à développer votre nouvelle tâche Azure Pipelines brillante, je suggère simplement de copier le répertoire d’action puis d’implémenter les étapes de la documentation officielle Azure – c’est assez simple.
- Créer
vss-extension.json - Créer
task.jsonet le placer dans votre répertoiredist(en fait mieux de le nommer d’après le nom de la tâche) - Si vous avez utilisé des méthodes de
@actions/coreou@actions/githubdans votre action, vous devez les remplacer par les méthodes correspondantes deazure-pipelines-task-lib(par exemplecore.getInput→tl.getInput)
L’API de azure-pipelines-task-lib est similaire à @actions/core et autres bibliothèques @actions/*.
Par exemple, nous avons une méthode pour obtenir les paramètres d’entrée :
export function getInputs(): Inputs {
return {
milliseconds: core.getInput('milliseconds'),
}
}Et la même pour Azure Pipelines :
export function getInputs(): Inputs {
return {
milliseconds: tl.getInput('milliseconds'),
}
}Pour des cas plus réels, n’hésitez pas à explorer notre base de code GitHub Actions Qodana utils et Azure Pipelines task utils.
Créer le monorepo
Nous allons utiliser npm workspaces pour gérer le monorepo.
Placez votre code d’action et de tâche dans des sous-répertoires (par exemple github) de votre monorepo nouvellement créé. Ensuite, créez un fichier package.json dans le répertoire racine.
{
"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"
}
}
Donc la structure du monorepo ressemble à ceci :
...
├── action.yaml
├── github/
├── azure/
└── package.jsonAprès avoir implémenté la configuration du workspace, vous pouvez exécuter des tâches et des actions depuis le répertoire racine. Par exemple, pour exécuter la tâche build depuis le répertoire github, vous pouvez utiliser la commande suivante :
npm run -w github buildPartager le code entre actions et tâches
La partie la plus précieuse de l’utilisation de l’approche monorepo commence ici : vous pouvez partager le code entre vos actions et tâches.
Nous allons effectuer les étapes suivantes :
- Créer un répertoire
commonà la racine du monorepo, un sous-projet pour le code partagé - Mettre à jour les configurations du compilateur
tsconfig.jsonde tous les sous-répertoires pour des builds de projet appropriés
Tout d’abord, créons le tsconfig de base – tsconfig.base.json avec les paramètres de base qui vont être utilisés dans tous les sous-projets :
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"composite": true
},
"exclude": ["node_modules", "**/*.test.ts", "*/lib/**"]
}Ensuite, créez un simple tsconfig.json à la racine du projet :
{
"references": [
{ "path": "common" },
{ "path": "azure" },
{ "path": "github" }
],
"files": []
}Puis common/tsconfig.json :
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"outDir": "./lib",
"rootDir": "."
},
"files": ["include your files here or use typical include/exclude patterns"]
}Et enfin, mettez à jour les fichiers tsconfig.json dans les sous-projets (ils sont fondamentalement les mêmes, par exemple github/tsconfig.json) :
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"outDir": "./lib",
"rootDir": "./src"
},
"references": [
{ "path": "../common" }
]
}Maintenant vous pouvez utiliser le code partagé du répertoire common dans vos actions et tâches. Par exemple, nous avons un fichier qodana.ts dans le répertoire common qui contient la fonction getQodanaUrl qui retourne l’URL de l’outil CLI Qodana. Et nous l’utilisons dans les actions et les tâches.
Construire et publier
Vous avez déjà des workflows GitHub du template configurés pour publier vos actions dans les releases de votre dépôt. Pour les releases automatisées, nous utilisons GH CLI, et nous avons un script simple qui publie un changelog dans les releases du dépôt :
#!/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}
doneEt le workflow GitHub qui l’exécute :
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 }}Pour les releases de tâches Azure Pipelines, vous pouvez utiliser l’approche officielle d’Azure. Néanmoins, vous pouvez également faire la même chose sur l’infrastructure GitHub Actions car leur outil de publication peut être installé n’importe où. Donc, dans notre cas, c’est résolu par un simple job de workflow GitHub :
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 }}Avec cette configuration, chaque release se produit automatiquement à chaque push de tag.
git tag -a v1.0.0 -m "v1.0.0" && git push origin v1.0.0
CircleCI ?
Ah oui, cet article mentionnait également l’orb CircleCI… La configuration CircleCI est simple mais ne prend pas en charge les extensions TypeScript, vous devez donc empaqueter votre code dans une image Docker ou un binaire et l’exécuter là-bas. La seule raison pour laquelle c’est inclus dans cet article est que nous construisons notre orb avec l’approche monorepo, ce qui fonctionne bien.
Implémentez le template d’orb officiel et placez-le dans votre monorepo, donc la structure ressemble à ceci :
...
├── action.yaml
├── github/
├── azure/
├── src/ # orb source code here
└── package.jsonEt n’oubliez pas de committer le répertoire .circleci/ dans votre dépôt pour que CircleCI puisse linter, tester et publier votre orb.
