Créer une extension Azure DevOps

Nous avons un projet avec un processus CI / CD personnalisé. Lorsque le développeur termine la tâche et injecte ses modifications dans develop \ qa, la génération se lance automatiquement, ce qui place la nouvelle version de l'application dans l'environnement de test. Dans un monde idéal, le testeur apprend automatiquement les tâches qui ont été effectuées et sur quel environnement elles sont déployées. Dans ce cas, le flux de travail devient continu, ininterrompu et nécessite moins de communication, distrayant du travail concentré. En pratique, tout n'est pas si rose.

Donc, un matin, le chef d'équipe m'a demandé: "Pouvez-vous faire une telle chose pour TFS afin que les balises qui sont attachées à la build suspendent la balise spécifiée après avoir traversé cette build?"

J'ai décidé d'implémenter ma tâche build \ release pour la tâche. De plus, les sources de toutes les tâches de build sont sur github , et toutes les informations sont disponibles.

Notre tâche marque la couleur de la tâche, qui est terminée, mais non testée. Grâce à cela, le développeur remarquera immédiatement s'il a oublié de mettre la balise souhaitée et QA voit immédiatement ce qui doit être vérifié. Cela visualise l'état des tâches et accélère le travail sur le projet.

Dans cet article je parlerai de l'implémentation de la logique nécessaire, le packaging en extension. Donc, si vous êtes intéressé par la façon de créer un tel plugin, bienvenue sur cat.

Pour les plus impatients: github et une extension prête sur le marché .



Azure DevOps a la possibilité de créer des filtres qui vous permettent de colorer les masques sur la carte de différentes couleurs.




Nous sommes intéressés par des tâches qui:

  • terminĂ©
  • versĂ© dans l'environnement de test
  • non encore vĂ©rifiĂ© QA

Pour les éléments, les balises b et c conviennent le mieux, mais les définir manuellement est dégoûtant, et tout le monde oublie de le faire. Nous allons donc écrire une extension qui, après le déploiement, les apposera automatiquement.

Ainsi, nous avons besoin de l'étape de build / release personnalisée pour réduire le facteur humain (le développeur a oublié de mettre une balise) et pour aider l'AQ (vous pouvez immédiatement voir ce qui doit être vérifié).

Prérequis


Pour développer l'extension, nous avons besoin de:

  1. IDE préféré
  2. installé TypeScript + node.js + npm (maintenant j'ai installé les versions 3.5.1 \ 12.4 \ 6.9.0)
  3. tfx-cli - bibliothèque pour l'empaquetage extension'a (npm i -g tfx-cli).

Notez la présence du drapeau -g

Microsoft a une bonne documentation dans laquelle ils sont juste en haut et expliquent comment créer une sorte d'extension. De plus, de la même manière, il existe un dock pour créer une tâche build \ release.

À mon avis, il y a des inconvénients dans les deux articles du point de vue de détailler ou d'expliquer certains points, donc je vais m'appuyer sur eux, en me concentrant sur ces points qui, en réalité, me semblaient pas tout à fait évidents.

De manière générale, vous pouvez écrire l'étape build \ release dans un assez grand nombre de langues. Je vais donner un exemple sur TypeScript.

Pourquoi TypeScript?


La toute première version de build step'a a été écrite en PowerShell'e, seule notre équipe et quelques personnes l'ont connue. Presque immédiatement, nous avons été confrontés au fait que si vous essayez d'ajouter une tâche à la build qui s'exécute sur l'agent de construction Docker, il n'y aura pas de PowerShell et la tâche ne fonctionnera tout simplement pas. De plus, de temps en temps, divers types d'erreurs ont décollé des gens, qui ont été attribués à PowerShell kooky. D'où la conclusion - la solution doit être multiplateforme.

Structure du projet


|--- README.md |--- images |---extension-icon.png |--- TaskFolder (     build\release step'a) |--- vss-extension.json ( ) 

Ensuite, nous devons installer la bibliothèque pour la mise en œuvre de l'étape de construction

  1. cd TaskFolder
  2. npm init
  3. npm install azure-pipelines-task-lib --save && npm install @ types / node --save-dev && npm install @ types / q --save-dev
  4. tsc --init

Extension de développement


Tout d'abord, à l'intérieur du TaskFolder, nous devons créer le fichier task.json - c'est le fichier manifeste de l'étape de construction elle-même. Il contient des informations de service (version, créateur, description), un environnement de lancement et de configuration de toutes les entrées, que nous verrons dans le futur sur le formulaire.

Je propose d'étudier sa structure plus en détail dans la documentation .
Dans notre cas, il y aura 2 entrées'a sur le formulaire - la balise que nous ajouterons aux éléments de travail, et le choix du type de pipeline (build ou release).

 "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" } ] 

Par nom, dans le code ci-dessous, nous ferons référence à la valeur de chacune des entrées.
Créez un index.ts dans TaskFolder et écrivez le premier morceau de code

 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(); 

Il convient de noter que TFS possède une documentation très riche sur l'API REST existante, mais pour le moment, tout ce que nous devons faire est d'obtenir les éléments de travail attachés à la build.

Installez une bibliothèque pour une exécution facile des requêtes

 npm install request --save && npm install request-promise-native --save 

Ajoutez-le Ă  index.ts

 import * as request from "request-promise-native"; 

Nous implémentons la fonction, qui à partir de la version actuelle obtiendra les éléments de travail attachés

Un peu sur l'autorisation


Pour accéder à l'API REST, nous devons obtenir accessToken

 const accessToken = tl.getEndpointAuthorization('SystemVssConnection', true).parameters.AccessToken; 

Ensuite, définissez l'autorisation d'en-tête sur "Bearer $ {accessToken}"

Nous revenons à la réception des éléments de travail liés à la génération.

Le serveur Azure DevOps et le nom TeamProject peuvent ĂŞtre obtenus Ă  partir des variables d'environnement comme suit

 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; } 

En réponse à une demande GET par URL

 ${collectionUrl}/${teamProject}/_apis/build/builds/${buildId}/workitems 

nous obtenons ce genre 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" } ] } 

Pour chaque URL, via la même API REST, vous pouvez obtenir des données sur l'élément de travail.

Pour le moment, notre méthode d'exécution est la suivante.

La méthode d'obtention des éléments de travail à partir de la version est presque identique à celle déjà décrite.

 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); } 

L'étape suivante consiste à obtenir l'ensemble actuel de balises pour chacun des éléments de travail reçus et à ajouter celui que nous avons spécifié.

Ajoutons la méthode run:

 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); } } 

Envisageons d'ajouter une balise aux éléments de travail.

Tout d'abord, nous devons obtenir la balise que nous avons indiquée sur le formulaire.

 const tagFromInput = tl.getInput('tagToAdd'); 

Parce que 2 étapes en arrière, nous avons reçu des URL vers l'API de chaque élément de travail, puis avec leur aide, nous pouvons facilement demander une liste des balises actuelles:

 const uri = workItem.url + "?fields=System.Tags&api-version=2.0"; const getOptions = createGetRequestOptions(uri) const result = await request.get(getOptions); 

En réponse, nous obtenons ce 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" } 

Nous prenons toutes les anciennes balises et nous en ajoutons une nouvelle:

 const currentTags = result.fields['System.Tags']; let newTags = ''; if (currentTags !== undefined) { newTags = currentTags + ";" + tagFromInput; } else { newTags = tagFromInput; } 

Nous envoyons une demande de correctif à l'api de l'élément de travail:

 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 } 

Assemblage et extension d'emballage'a


Pour la beauté de tout ce qui se passe, je propose d'ajouter tsconfig.json à compilerOptions
 "outDir": "dist" 
. Maintenant, si nous exécutons la commande tsc à l'intérieur du TaskFolder, nous obtenons le dossier dist, à l'intérieur duquel sera index.js, qui ira au package final.

Parce que notre index.js est situé dans le dossier dist, puis nous le copierons également dans le package final, nous devons corriger un peu task.json:

 "execution": { "Node": { "target": "dist/index.js" } } 

Dans vss-extension.json dans la section des fichiers, vous devez déclarer explicitement ce qui sera copié dans le package 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" } ] 

Dernière étape - nous devons emballer notre extension.

Pour ce faire, exécutez la commande:

  tfx extension create --manifest-globs ./vss-extension.json 

Après l'exécution, nous obtenons un fichier * .vsix, qui sera ensuite installé dans TFS.

Le fichier PS * .vsix est essentiellement une archive ordinaire, vous pouvez facilement l'ouvrir via 7-zip, par exemple, et voir que tout ce dont vous avez besoin est vraiment à l'intérieur.

Ajoutez de la beauté


Si vous souhaitez avoir une image lorsque vous sélectionnez votre étape de génération tout en l'ajoutant au pipeline, ce fichier doit être placé à côté de task.json et nommé icon.png. Vous n'avez pas besoin d'apporter de modifications à task.json lui-même.

Vous pouvez ajouter une section Ă  vss-extension.json:

 "icons": { "default": "images/logo.png" } 

Cette image sera affichée dans la galerie d'extensions locales.

Installer l'extension


  1. Accédez à tfs_server_url / _gallery / manage
  2. Cliquez sur Télécharger l'extension
  3. Spécifiez le chemin ou le glisser-déposer pour lancer le fichier * .vsix reçu plus tôt
  4. Une fois la vérification réussie, dans le menu contextuel de l'extension, sélectionnez afficher l'extension, sur la page qui s'ouvre, sélectionnez la collection dans laquelle vous souhaitez l'installer
  5. Après cette extension, vous pouvez commencer à l'utiliser.

Utilisation de build step'a


  1. Ouvrez le pipeline dont vous avez besoin
  2. Allez ajouter l'étape de construction
  3. Nous recherchons une extension


  4. Nous indiquons tous les paramètres nécessaires

  5. Profitez de la vie :)

Conclusion


Dans cet article, j'ai montré comment créer un plug-in pour Azure DevOps, qui place automatiquement la bonne balise dans les tâches de la version. Ses collègues l'ont intégré au pipeline, qui s'exécute à la fois sur Windows et sur les agents de génération Linux.

Grâce à ce plugin, il est devenu plus facile pour nous de travailler avec des tâches et de construire un travail continu sur le projet. Les développeurs ne sont plus distraits par des choses étrangères et le contrôle qualité apprend rapidement les nouvelles tâches de test.

Encore une fois, je me souviens du lien de téléchargement: lien

Nous attendons avec impatience vos commentaires et suggestions de révision :)

PS

Il y a aussi une idée d'ajouter la possibilité de supprimer les balises spécifiées dans le plugin. Si le testeur a trouvé un bogue et que vous devez à nouveau déployer la tâche, vous pouvez vous débarrasser des balises "Vérifié").

Source: https://habr.com/ru/post/fr461389/


All Articles