Tenemos un proyecto con un proceso de CI / CD personalizado. Cuando el desarrollador finaliza la tarea e inyecta sus cambios en desarrollo \ qa, la compilación se inicia automáticamente, lo que coloca la nueva versión de la aplicación en el entorno de prueba. En un mundo ideal, el probador aprende automáticamente sobre las tareas que se completaron y en qué entorno se implementan. En este caso, el flujo de trabajo se vuelve continuo, ininterrumpido y requiere menos comunicación, lo que distrae el trabajo concentrado. En la práctica, no todo es tan color de rosa.
Y una mañana, el líder del equipo me preguntó: "¿Se puede hacer algo así para TFS para que las etiquetas que se adjuntan a la compilación cuelguen la etiqueta especificada después de pasar por esta compilación?"
Decidí implementar mi tarea build \ release para la tarea. Además, las fuentes de todas las tareas de compilación están en
github , y toda la información está disponible.
Nuestra tarea marca el color de la tarea, que se completa, pero no se prueba. Gracias a esto, el desarrollador notará de inmediato si olvidó poner la etiqueta deseada, y el control de calidad ve de inmediato lo que debe verificarse. Esto visualiza el estado de las tareas y acelera el trabajo en el proyecto.
En este artículo hablaré sobre la implementación de la lógica necesaria, el empaquetado en extensión. Entonces, si está interesado en cómo crear dicho complemento, bienvenido a cat.
Para los más impacientes:
github y una extensión lista en el
mercado .

Azure DevOps tiene la capacidad de crear filtros que le permiten colorear las máscaras en el tablero en diferentes colores.

Estamos interesados en tareas que:
- completado
- vertido en el entorno de prueba
- QA aún no verificado
Para los artículos, las etiquetas byc son las más adecuadas, pero configurarlas manualmente es desagradable, y todos se olvidan de hacerlo. Entonces, escribiremos una extensión que, después de la implementación, las colocará automáticamente.
Por lo tanto, necesitamos el paso de compilación / lanzamiento personalizado para reducir el factor humano (el desarrollador olvidó poner una etiqueta) y para ayudar al control de calidad (puede ver de inmediato lo que debe verificarse).
Prerrequisitos
Para desarrollar la extensión, necesitamos:
- IDE favorito
- instalado TypeScript + node.js + npm (ahora he instalado las versiones 3.5.1 \ 12.4 \ 6.9.0)
- tfx-cli: biblioteca para empaquetar la extensión 'a (npm i -g tfx-cli).
Tenga en cuenta la presencia de la bandera -g
Microsoft tiene una buena
documentación en la que están en la parte superior y le dicen cómo crear algún tipo de extensión. Además, de la misma manera, hay un dock para crear una tarea de compilación / liberación.
En mi opinión, hay inconvenientes en ambos artículos desde el punto de vista de detallar o explicar ciertos puntos, por lo que confiaré en ellos, centrándome en aquellos puntos que en realidad no me parecían del todo evidentes.
En términos generales, puede escribir el paso build / release en una cantidad bastante grande de idiomas. Daré un ejemplo en TypeScript.
¿Por qué TypeScript?
La primera versión de build step'a fue escrita en PowerShell'e, solo nuestro equipo y un par de personas lo sabían. Casi de inmediato, nos enfrentamos con el hecho de que si intentas agregar una tarea a la compilación que se ejecuta en el agente de compilación de Docker, entonces no habrá PowerShell y la tarea simplemente no funcionará. Además, de vez en cuando, varios tipos de errores despegaban de las personas, que se atribuían a PowerShell kooky. De ahí la conclusión: la solución debe ser multiplataforma.
Estructura del proyecto
|--- README.md |--- images |---extension-icon.png |--- TaskFolder ( build\release step'a) |--- vss-extension.json ( )
A continuación, necesitamos instalar la biblioteca para la implementación del paso de compilación'a
- cd TaskFolder
- npm init
- npm install azure-pipelines-task-lib --save && npm install @ types / node --save-dev && npm install @ types / q --save-dev
- tsc --init
Extensión de desarrollo
En primer lugar, dentro del TaskFolder, necesitamos crear el archivo task.json: este es el archivo de manifiesto para el paso de compilación en sí. Contiene información de servicio (versión, creador, descripción), un entorno para el lanzamiento y la configuración de todas las entradas, que veremos en el futuro en el formulario.
Propongo estudiar su estructura con más detalle en la
documentación .
En nuestro caso, habrá 2 input'a en el formulario: la etiqueta que agregaremos a los elementos de trabajo y la elección del tipo de canalización (compilación o lanzamiento).
"inputs": [ { "name": "pipelineType", "type": "pickList", "label": "Specify type of pipeline", "helpMarkDown": "Specify whether task is used for build or release", "required": true, "defaultValue": "Build", "options":{ "Build": "Build", "Release": "Release" } }, { "name": "tagToAdd", "type": "string", "label": "Tag to add to work items", "defaultValue": "", "required": true, "helpMarkDown": "Specify a tag that will be added to work items" } ]
Por nombre, en el siguiente código nos referiremos al valor de cada una de las entradas.
Cree un index.ts en TaskFolder y escriba el primer fragmento de código
import * as tl from 'azure-pipelines-task-lib/task'; async function run() { try { const pipelineType = tl.getInput('pipelineType'); } catch (err) { tl.setResult(tl.TaskResult.Failed, err.message); } } run();
Vale la pena señalar que TFS tiene una documentación muy rica en la API REST existente, pero por ahora, todo lo que tenemos que hacer es adjuntar elementos de trabajo a la compilación.
Instale una biblioteca para una fácil ejecución de consultas
npm install request --save && npm install request-promise-native --save
Agréguelo a index.ts
import * as request from "request-promise-native";
Implementamos la función, que a partir de la compilación actual obtendrá los elementos de trabajo adjuntos
Un poco sobre autorización
Para acceder a la API REST, necesitamos obtener accessToken
const accessToken = tl.getEndpointAuthorization('SystemVssConnection', true).parameters.AccessToken;
A continuación, establezca la autorización del encabezado en "Bearer $ {accessToken}"
Volvemos a recibir elementos de trabajo vinculados a la compilación.
El servidor Url Azure DevOps y el nombre de TeamProject se pueden obtener de las variables de entorno de la siguiente manera
const collectionUrl = process.env["SYSTEM_TEAMFOUNDATIONCOLLECTIONURI"]; const teamProject = process.env["SYSTEM_TEAMPROJECT"];
async function getWorkItemsFromBuild() { const buildId = process.env["BUILD_BUILDID"]; const uri = `${collectionUrl}/${teamProject}/_apis/build/builds/${buildId}/workitems`; const options = createGetRequestOptions(uri); const result = await request.get(options); return result.value; }
function createGetRequestOptions(uri: string): any { let options = { uri: uri, headers: { "authorization": `Bearer ${accessToken}`, "content-type": "application/json" }, json: true }; return options; }
Como respuesta a una solicitud GET por URL
${collectionUrl}/${teamProject}/_apis/build/builds/${buildId}/workitems
tenemos este tipo de JSON
{ "count": 3, "value": [ { "id": "55402", "url": "https://.../_apis/wit/workItems/55402" }, { "id": "59777", "url": "https://.../_apis/wit/workItems/59777" }, { "id": "60199", "url": "https://.../_apis/wit/workItems/60199" } ] }
Para cada url, a través de la misma API REST, puede obtener datos sobre el elemento de trabajo.
Por el momento, nuestro método de ejecución es el siguiente.
El método para obtener elementos de trabajo de la versión es casi idéntico al descrito anteriormente.
async function run() { try { const pipelineType = tl.getInput('pipelineType'); const workItemsData = pipelineType === "Build" ? await getWorkItemsFromBuild() : await getWorkItemsFromRelease(); catch (err) { tl.setResult(tl.TaskResult.Failed, err.message); }
El siguiente paso es obtener el conjunto actual de etiquetas para cada uno de los elementos de trabajo recibidos y agregar el que especificamos.
Agreguemos el método de ejecución:
async function run() { try { const pipelineType = tl.getInput('pipelineType'); const workItemsData = pipelineType === "Build" ? await getWorkItemsFromBuild() : await getWorkItemsFromRelease(); workItemsData.forEach(async (workItem: any) => { await addTagToWorkItem(workItem); }); } catch (err) { tl.setResult(tl.TaskResult.Failed, err.message); } }
Consideremos agregar una etiqueta a los elementos de trabajo.
Primero, necesitamos obtener la etiqueta que indicamos en el formulario.
const tagFromInput = tl.getInput('tagToAdd');
Porque Dos pasos atrás recibimos las URL de la API de cada elemento de trabajo, luego, con su ayuda, podemos solicitar fácilmente una lista de etiquetas actuales:
const uri = workItem.url + "?fields=System.Tags&api-version=2.0"; const getOptions = createGetRequestOptions(uri) const result = await request.get(getOptions);
En respuesta, obtenemos este JSON:
{ "id": 55402, "rev": 85, "fields": { "System.Tags": "added-to-prod-package; test-tag" }, "_links": { "self": { "href": "https://.../_apis/wit/workItems/55402" }, "workItemUpdates": { "href": "https://.../_apis/wit/workItems/55402/updates" }, "workItemRevisions": { "href": "https://.../_apis/wit/workItems/55402/revisions" }, "workItemHistory": { "href": "https://.../_apis/wit/workItems/55402/history" }, "html": { "href": "https://..../web/wi.aspx?pcguid=e3c978d9-6ea1-406f-987d-5b03e24973a1&id=55402" }, "workItemType": { "href": "https://.../602fd27d-4e0d-4aec-82a0-dcf55c8eef73/_apis/wit/workItemTypes" }, "fields": { "href": "https://.../_apis/wit/fields" } }, "url": "https://.../_apis/wit/workItems/55402" }
Tomamos todas las etiquetas antiguas y les agregamos una nueva:
const currentTags = result.fields['System.Tags']; let newTags = ''; if (currentTags !== undefined) { newTags = currentTags + ";" + tagFromInput; } else { newTags = tagFromInput; }
Enviamos una solicitud de parche al elemento de trabajo api:
const patchOptions = getPatchRequestOptions(uri, newTags); await request.patch(patchOptions) function getPatchRequestOptions(uri: string, newTags: string): any { const options = { uri: uri, headers: { "authorization": `Bearer ${accessToken}`, "content-type": "application/json-patch+json" }, body: [{ "op": "add", "path": "/fields/System.Tags", "value": newTags }], json: true }; return options }
Montaje y embalaje extension'a
Por la belleza de todo lo que sucede, propongo agregar tsconfig.json a compilerOptions
"outDir": "dist"
. Ahora, si ejecutamos el comando
tsc
dentro del TaskFolder, obtenemos la carpeta dist, dentro de la cual estará index.js, que irá al paquete final.
Porque nuestro index.js se encuentra en la carpeta dist y luego lo copiaremos también al paquete final, necesitamos arreglar un poco task.json:
"execution": { "Node": { "target": "dist/index.js" } }
En vss-extension.json en la sección de archivos, debe declarar explícitamente lo que se copiará en el paquete final.
"files": [ { "path": "TaskFolder/dist", "packagePath": "TaskFolder/dist" }, { "path": "TaskFolder/node_modules", "packagePath": "TaskFolder/node_modules" }, { "path": "TaskFolder/icon.png", "packagePath": "TaskFolder/icon.png" }, { "path": "TaskFolder/task.json", "packagePath": "TaskFolder/task.json" } ]
Último paso: necesitamos empacar nuestra extensión.
Para hacer esto, ejecute el comando:
tfx extension create --manifest-globs ./vss-extension.json
Después de la ejecución, obtenemos un archivo * .vsix, que se instalará en TFS.
El archivo PS * .vsix es esencialmente un archivo ordinario, puede abrirlo fácilmente a través de 7-zip, por ejemplo, y ver que todo lo que necesita está realmente dentro.
Añade algo de belleza
Si desea tener una imagen cuando selecciona su paso de compilación mientras lo agrega a la tubería, este archivo debe colocarse junto a task.json y llamado icon.png. No es necesario que realice ningún cambio en task.json.
Puede agregar una sección a vss-extension.json:
"icons": { "default": "images/logo.png" }
Esta imagen se mostrará en la galería de extensiones locales.
Instalar extensión
- Vaya a tfs_server_url / _gallery / manage
- Haga clic en Subir extensión
- Especifique la ruta o arrastre y suelte para lanzar el archivo * .vsix recibido anteriormente
- Después de la verificación, en el menú contextual de la extensión, seleccione ver extensión, en la página que se abre, seleccione la colección en la que desea instalarla
- Después de esta extensión, puede comenzar a usarla.
Usando build step'a
- Abre la tubería que necesitas
- Ir a agregar compilación step'a
- Estamos buscando extensión

- Indicamos todas las configuraciones necesarias

- Disfruta la vida :)
Conclusión
En este artículo, mostré cómo hacer un complemento para Azure DevOps, que coloca automáticamente la etiqueta correcta para las tareas para el lanzamiento. Los colegas lo incorporaron a la tubería, que se ejecuta tanto en Windows como en los agentes de construcción de Linux.
Gracias a este complemento, nos ha resultado más fácil trabajar con tareas y desarrollar un trabajo continuo en el proyecto. Los desarrolladores ya no se distraen con cosas extrañas, y el control de calidad aprende rápidamente sobre nuevas tareas de prueba.
Una vez más, recuerdo el
enlace de descarga:
enlaceEsperamos comentarios y sugerencias para la revisión :)
PS
También existe la idea de agregar la capacidad de eliminar las etiquetas especificadas en el complemento. Si el probador encontró un error y tiene que implementar la tarea nuevamente, puede deshacerse de las etiquetas "Verificadas".