
💡 La imagen destacada muestra un pipeline típico de CI/CD en acción, parcialmente dibujado por OpenAI DALL-E, pero en este artículo vamos a desarrollar algo beneficioso
Contenidos
Este tutorial demuestra cómo desarrollar, probar y desplegar extensiones de CI para GitHub Actions, Azure Pipelines y CircleCI desde un único monorepo utilizando TypeScript y Node.js. Cubre la creación de un monorepo, compartir código entre acciones y tareas, y construir y publicar extensiones.
Tabla de Contenidos
Este es un tutorial relativamente corto sobre cómo desarrollar, probar y desplegar tus extensiones de CI para GitHub Actions, Azure Pipelines y CircleCI desde un único monorepo y está basado en la experiencia de crear las extensiones de CI de Qodana.
Comienza con las plantillas oficiales
Elijamos la pila de tecnologías para nuestras extensiones de CI.
OK, no voy a elegir. Solo te diré por qué usé TypeScript y node.js para las extensiones.
Ventajas de usar acciones basadas en JS
- Más flexible que los enfoques basados en bash/Dockerfile
- Diferentes bibliotecas (como actions/toolkit y microsoft/azure-pipelines-task-lib) con APIs más accesibles y fáciles de usar están disponibles de forma inmediata
- Escribir pruebas es relativamente simple
Desventajas
- JavaScript
¡Así que escribamos una acción basada en TypeScript!
GitHub Actions
Encontré la documentación de GitHub Actions más fácil de leer que la de Azure, así que recomendaría comenzar escribiendo y probando tus extensiones en GitHub usando la plantilla oficial actions/typescript-action. La plantilla mencionada proporciona un buen punto de partida; no voy a repetir los pasos aquí. Juega con ella, escribe algo sencillo y luego regresa aquí para los siguientes pasos.
Azure Pipelines
GitHub Actions está construido sobre infraestructura de Azure, por lo que portar tu acción de GitHub a Azure Pipelines debería ser relativamente fácil.
Entonces,
- la “action” se convierte en la “task”
- se empaqueta de forma un poco diferente, se distribuye y se instala de otra manera
Y la definición de una tarea task.json es la misma que la de una acción action.yml.
Por ejemplo, teniendo el siguiente 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'“Fácilmente” se traduce a la siguiente tarea de 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"
}
}
}De un ejemplo tan simple, se puede ver por qué sugerí empezar con GitHub Actions. Pero continuemos.
Para comenzar a desarrollar tu nueva y brillante tarea de Azure Pipelines, sugiero simplemente copiar el directorio de la acción y luego implementar los pasos de la documentación oficial de Azure – es bastante sencillo.
- Crear
vss-extension.json - Crear
task.jsony colocarlo en tu directoriodist(de hecho, es mejor nombrarlo según el nombre de la tarea) - Si usaste algún método de
@actions/coreo@actions/githuben tu acción, necesitas reemplazarlos con los métodos correspondientes deazure-pipelines-task-lib(ej.core.getInput→tl.getInput)
La API de azure-pipelines-task-lib es similar a @actions/core y otras bibliotecas @actions/*.
Por ejemplo, tenemos un método para obtener los parámetros de entrada:
export function getInputs(): Inputs {
return {
milliseconds: core.getInput('milliseconds'),
}
}Y lo mismo para Azure Pipelines:
export function getInputs(): Inputs {
return {
milliseconds: tl.getInput('milliseconds'),
}
}Para casos más reales, siéntete libre de explorar nuestra base de código de GitHub Actions de Qodana utils y los utils de la tarea de Azure Pipelines.
Crea el monorepo
Vamos a usar npm workspaces para gestionar el monorepo.
Coloca tu código de acción y tarea en subdirectorios (ej. github) de tu monorepo recién creado. Y luego crea un archivo package.json en el directorio raíz.
{
"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"
}
}
Así que la estructura del monorepo se ve así:
...
├── action.yaml
├── github/
├── azure/
└── package.jsonDespués de implementar la configuración del workspace, puedes ejecutar tareas y acciones desde el directorio raíz. Por ejemplo, para ejecutar la tarea build desde el directorio github, puedes usar el siguiente comando:
npm run -w github buildComparte código entre acciones y tareas
La parte más valiosa del uso del enfoque de monorepo comienza aquí: puedes compartir el código entre tus acciones y tareas.
Vamos a realizar los siguientes pasos:
- Crear un directorio
commonen la raíz del monorepo, un subproyecto para código compartido - Actualizar las configuraciones del compilador
tsconfig.jsonde todos los subdirectorios para construcciones de proyectos adecuadas
Primero, creemos el tsconfig base – tsconfig.base.json con la configuración base que se usará en todos los subproyectos:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"composite": true
},
"exclude": ["node_modules", "**/*.test.ts", "*/lib/**"]
}Luego crea un simple tsconfig.json en la raíz del proyecto:
{
"references": [
{ "path": "common" },
{ "path": "azure" },
{ "path": "github" }
],
"files": []
}Luego common/tsconfig.json:
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"outDir": "./lib",
"rootDir": "."
},
"files": ["include your files here or use typical include/exclude patterns"]
}Y finalmente, actualiza los archivos tsconfig.json en los subproyectos (son básicamente iguales, ej. github/tsconfig.json):
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"outDir": "./lib",
"rootDir": "./src"
},
"references": [
{ "path": "../common" }
]
}Ahora puedes usar el código compartido del directorio common en tus acciones y tareas. Por ejemplo, tenemos un archivo qodana.ts en el directorio common que contiene la función getQodanaUrl que devuelve la URL a la herramienta CLI de Qodana. Y la usamos tanto en acciones como en tareas.
Construye y publica
Ya tienes workflows de GitHub de la plantilla configurados para publicar tus acciones en los releases de tu repositorio. Para releases automatizados, usamos GH CLI, y tenemos un script simple que publica un changelog en los releases del repositorio:
#!/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}
doneY el workflow de GitHub que lo ejecuta:
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 }}Para los releases de tareas de Azure Pipelines, puedes usar el enfoque oficial de Azure. Aun así, también puedes hacer lo mismo en la infraestructura de GitHub Actions ya que su herramienta de publicación se puede instalar en cualquier lugar. Entonces, en nuestro caso, se resuelve con un simple trabajo de workflow de 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 }}Con esta configuración, cada release ocurre automáticamente en cada push de tag.
git tag -a v1.0.0 -m "v1.0.0" && git push origin v1.0.0
¿CircleCI?
Ah, sí, este artículo también mencionó el orb de CircleCI… La configuración de CircleCI es sencilla pero no admite extensiones de TypeScript, así que tienes que empaquetar tu código en una imagen de Docker o un binario y ejecutarlo allí. La única razón por la que está incluido en este post es que construimos nuestro orb con el enfoque de monorepo, que funciona bien.
Implementa la plantilla oficial de orb y colócala en tu monorepo, así que la estructura se ve así:
...
├── action.yaml
├── github/
├── azure/
├── src/ # orb source code here
└── package.jsonY recuerda hacer commit del directorio .circleci/ en tu repositorio para que CircleCI haga lint, pruebe y publique tu orb.
