我们有一个带有自定义CI / CD流程的项目。 当开发人员完成任务并将其更改注入到开发人员\ qa中时,该构建会自动启动,这会将新版本的应用程序置于测试环境中。 在理想的环境中,测试人员会自动了解已完成的任务以及在什么环境下部署它们。 在这种情况下,工作流将变得连续,不间断并且需要较少的通信,从而分散了工作量。 实际上,一切并不那么乐观。
一天早上,团队负责人问我:“您可以为TFS做这样的事情,以便附加到构建的标签在通过此构建后挂在指定的标签上吗?”
我决定为该任务实施build \ release任务。 此外,所有构建任务的源都在
github上 ,并且所有信息都可用。
我们的任务标记了任务的颜色,该颜色已完成但未经测试。 因此,开发人员将立即注意到是否忘记放置所需的标签,并且质量检查人员立即看到需要检查的内容。 这样可以可视化任务状态并加快项目工作。
在本文中,我将讨论扩展包装中必要逻辑的实现。 因此,如果您对如何创建这样的插件感兴趣,欢迎来到cat。
对于最不耐烦的人:
github和
市场上现成的扩展。

Azure DevOps能够创建筛选器,使您可以用不同的颜色为板上的蒙版着色。

我们对以下任务感兴趣:
对于项目b和c,标签最合适,但手动设置它们令人作呕,每个人都忘记这样做。 因此,我们将编写一个扩展程序,该扩展程序在部署后将自动附加它们。
因此,我们需要定制的build \ release步骤来减少人为因素(开发人员忘记放下标签)并帮助进行质量检查(您可以立即看到需要检查的内容)。
先决条件
要开发扩展,我们需要:
- 最喜欢的IDE
- 安装了TypeScript + node.js + npm(现在我已经安装了3.5.1 \ 12.4 \ 6.9.0版本)
- tfx-cli-包装扩展库(npm i -g tfx-cli)。
请注意-g标志的存在
Microsoft有一些很好的
文档 ,它们只在顶部,它们讲述了如何创建某种扩展。 另外,以同样的方式,有一个用于创建build \ release任务的停靠点。
我认为,从详细介绍或解释某些观点的角度来看,这两篇文章都有缺点,因此我将依靠它们,着眼于那些在我看来并不十分明显的观点。
一般来说,您可以使用相当多的语言编写build \ release步骤。 我将以TypeScript为例。
为什么选择TypeScript?
构建步骤'a的第一个版本是用PowerShell'e编写的,只有我们的团队和几个人都知道。 几乎立即,我们将面临一个事实,如果您尝试将任务添加到在docker build代理上运行的构建中,则将没有PowerShell,并且该任务将无法正常工作。 此外,人们不时引发各种错误,这些错误归因于PowerShell kooky。 因此得出结论-解决方案应该是跨平台的。
项目结构
|--- README.md |--- images |---extension-icon.png |--- TaskFolder ( build\release step'a) |--- vss-extension.json ( )
接下来,我们需要安装该库以实现构建步骤'a
- cd TaskFolder
- npm初始化
- npm install azure-pipelines-task-lib --save && npm install @类型/节点--save-dev && npm install @类型/ q --save-dev
- tsc --init
开发扩展
首先,在TaskFolder内部,我们需要创建task.json文件-这是构建步骤本身的清单文件。 它包含服务信息(版本,创建者,描述),启动和配置所有输入的环境,我们将在以后的表格中看到它。
我建议在
文档中更详细地研究其结构。
在我们的例子中,表单上将有2个input'a-我们将添加到工作项的标签,以及管道类型(构建或发布)的选择。
"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" } ]
顾名思义,在下面的代码中,我们将引用每个输入的值。
在TaskFolder中创建index.ts并编写第一段代码
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();
值得注意的是,TFS在现有的REST API上有非常丰富的文档,但是就目前而言,我们要做的就是将工作项附加到构建中。
安装库以方便查询执行
npm install request --save && npm install request-promise-native --save
将其添加到index.ts
import * as request from "request-promise-native";
我们实现了该功能,该功能将从当前版本中获取附加的工作项
关于授权的一点点
要访问REST API,我们需要获取accessToken
const accessToken = tl.getEndpointAuthorization('SystemVssConnection', true).parameters.AccessToken;
接下来,将标头授权设置为“ Bearer $ {accessToken}”
我们返回到绑定到构建的工作项。
可以从环境变量获取URL Azure DevOps服务器和TeamProject名称,如下所示
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; }
作为对URL的GET请求的响应
${collectionUrl}/${teamProject}/_apis/build/builds/${buildId}/workitems
我们得到了这种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" } ] }
对于每个URL,通过相同的REST API,您可以获取工作项上的数据。
目前,我们的运行方法如下。
从发行版获取工作项的方法几乎与已描述的方法相同。
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); }
下一步是获取每个接收到的工作项的当前标记集,并添加我们指定的标记集。
让我们添加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); } }
让我们考虑为工作项添加标签。
首先,我们需要获取在表单上指示的标签。
const tagFromInput = tl.getInput('tagToAdd');
因为 退回两步,我们收到了每个工作项的API网址,然后借助它们的帮助,我们可以轻松地请求当前标签的列表:
const uri = workItem.url + "?fields=System.Tags&api-version=2.0"; const getOptions = createGetRequestOptions(uri) const result = await request.get(getOptions);
作为响应,我们得到以下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" }
我们采用所有旧标签,并在其中添加一个新标签:
const currentTags = result.fields['System.Tags']; let newTags = ''; if (currentTags !== undefined) { newTags = currentTags + ";" + tagFromInput; } else { newTags = tagFromInput; }
我们向工作项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 }
组装和包装扩展'a
为了让发生的一切变得美丽,我建议将tsconfig.json添加到compileOptions
"outDir": "dist"
。 现在,如果我们在TaskFolder中执行
tsc
命令,我们将得到dist文件夹,其中将是index.js,它将进入最终包。
因为 我们的index.js位于dist文件夹中,然后我们也将其复制到最终包中,我们需要对task.json进行一些修复:
"execution": { "Node": { "target": "dist/index.js" } }
在“文件”部分的vss-extension.json中,您必须显式声明将复制到最终包中的内容。
"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" } ]
最后一步-我们需要打包扩展程序。
为此,运行命令:
tfx extension create --manifest-globs ./vss-extension.json
执行后,我们得到一个* .vsix文件,该文件将安装在TFS中。
PS * .vsix文件本质上是一个普通的存档,例如,您可以轻松地通过7-zip打开它,然后查看您真正需要的所有内容。
添加一些美丽
如果在将生成步骤添加到管道中时选择生成步骤时想要生成图像,则应将该文件放置在task.json旁边,并命名为icon.png。 您无需对task.json本身进行任何更改。
您可以在vss-extension.json中添加一个部分:
"icons": { "default": "images/logo.png" }
该图像将显示在本地扩展库中。
安装扩展
- 转到tfs_server_url / _gallery /管理
- 点击上传扩展程序
- 指定路径或拖放以抛出先前收到的* .vsix文件
- 验证通过后,在扩展的上下文菜单中,选择查看扩展,在打开的页面上,选择要在其中安装的集合。
- 扩展后,您可以开始使用它。
使用构建步骤
- 打开您需要的管道
- 去添加构建步骤
- 我们正在寻找扩展

- 我们指出所有必要的设置

- 享受生活:)
结论
在本文中,我展示了如何为Azure DevOps制作插件,该插件会自动为发布任务添加正确的标记。 同事将其构建到管道中,该管道可在Windows和Linux构建代理上运行。
有了这个插件,我们可以更轻松地处理任务并在项目上进行持续的工作。 开发人员不再因无关紧要的事情而分心,并且质量检查人员可以快速了解新的测试任务。
我再次想起下载
链接 :
link我们期待收到反馈和修改建议:)
聚苯乙烯
还有一种想法可以将指定标签移除到插件中。 如果测试人员发现错误并不得不再次部署任务,则可以摆脱“已验证”标签)。