或者如何在一晚上的轻松编码中为您的项目获得漂亮的徽章
可能,每个在某个时候至少拥有一个宠物项目的开发人员都对带有状态、代码覆盖率、nuget 中的包版本的漂亮徽章有一种渴望……这种渴望促使我写了这篇文章。 在准备编写它时,我在我的一个项目中得到了这种美感:
本文将引导您完成 GitLab 中 .Net Core 类库项目的持续集成和交付的基本设置,将文档发布到 GitLab Pages,并将构建的包推送到 Azure DevOps 中的私有提要。
使用 VS Code 作为扩展的开发环境
简短介绍
CD - 是不是你刚推的时候,一切都已经落在了客户身上?
什么是 CI / CD 以及为什么需要它 - 您可以轻松地用谷歌搜索。 在 GitLab 中查找有关配置管道的完整文档
- 开发人员向存储库发送提交,通过站点创建合并请求, 或者以其他方式,显式或隐式启动管道,
- 所有任务都是从配置中选择的,其条件允许它们在给定的上下文中启动,
- 任务按阶段组织,
- 阶段依次执行 - 即 平行于 本阶段所有任务完成,
- 如果该阶段失败(即该阶段的至少一项任务失败),则管道停止(几乎总是),
- 如果所有阶段都成功完成,则管道被认为是成功的。
因此,我们有:
- 管道 - 一组任务,分为多个阶段,您可以在其中构建、测试、打包代码、将完成的构建部署到云服务等,
- 阶段 (阶段)——流水线组织单元,包含1+任务,
- 任务 (工作) 是流水线中的一个工作单元。 它由脚本(强制性)、启动条件、发布/缓存工件的设置等组成。
因此,设置 CI/CD 时的任务归结为创建一组任务,这些任务实现构建、测试和发布代码和工件的所有必要操作。
开始之前:为什么?
- 为什么选择 Gitlab?
因为当需要为宠物项目创建私有存储库时,他们在 GitHub 上付费,我很贪心。 存储库已经免费了,但到目前为止,这还不足以成为我转向 GitHub 的理由。
- 为什么不是 Azure DevOps 管道?
因为那里的设置是基本的 - 甚至不需要命令行知识。 与外部 git 提供程序集成 - 只需单击几下,导入 SSH 密钥以将提交发送到存储库 - 即使不从模板也可以轻松配置管道。
起始位置:你拥有什么,你想要什么
我们有:
- GitLab 中的存储库。
我们想要:
- 为每个合并请求自动组装和测试,
- 为每个合并请求构建包并推送到主服务器,前提是提交消息中有特定行,
- 将构建的包发送到 Azure DevOps 中的私有源,
- 在 GitLab Pages 中组装文档和出版物,
- 徽章!11
所描述的要求有机地落在以下管道模型上:
- 第 1 阶段 - 组装
- 我们收集代码,将输出文件发布为工件
- 第 2 阶段 - 测试
- 我们从构建阶段获取工件,运行测试,收集代码覆盖率数据
- 第 3 阶段 - 提交
- 任务 1 - 构建 nuget 包并将其发送到 Azure DevOps
- 任务 2 - 我们从源代码中的 xmldoc 中收集站点并将其发布在 GitLab Pages 中
让我们开始吧!
收集配置
准备账目
-
在中创建一个帐户
微软Azure -
去吧
Azure开发运营 -
我们新建一个项目
- 名称 - 任何
- 可见性 - 任何
-
当您单击“创建”按钮时,将创建项目并且您将被重定向到它的页面。 在此页面上,您可以通过转到项目设置来禁用不必要的功能(左侧列表中的下方链接 -> 概述 -> Azure DevOps 服务块)
-
转到 Atrifacts,单击创建提要
- 输入源名称
- 选择可见性
- 取消勾选 包括来自公共公共资源的包,这样源就不会变成转储 nuget 克隆
-
点击 Connect to feed,选择 Visual Studio,从 Machine Setup 块中复制 Source
-
转到帐户设置,选择个人访问令牌
-
创建一个新的访问令牌
- 名称 - 任意
- 组织 - 当前
- 有效期最长为 1 年
- 范围 - 打包/读写
-
复制创建的令牌 - 模态窗口关闭后,该值将不可用
-
转到 GitLab 中的存储库设置,选择 CI / CD 设置
-
展开变量块,添加一个新的
- 名称 - 任何不带空格的(将在命令 shell 中可用)
- 值 - 来自第 9 段的访问令牌
- 选择掩码变量
这样就完成了预配置。
准备配置框架
默认情况下,GitLab 中的 CI/CD 配置使用该文件 .gitlab-ci.yml
从存储库的根目录。 您可以在存储库设置中设置此文件的任意路径,但在这种情况下没有必要。
从扩展名中可以看出,该文件包含格式为 YAML
. 文档详细说明了哪些键可以包含在配置的顶层,以及每个嵌套级别。
首先,让我们在配置文件中添加一个指向 docker 镜像的链接,任务将在其中执行。 为此我们发现
image: mcr.microsoft.com/dotnet/core/sdk:3.1
现在,当从 Microsoft 图像存储库启动管道时,将下载指定的图像,其中将执行配置中的所有任务。
下一步是添加 阶段的。 默认情况下,GitLab 定义了 5 个阶段:
.pre
- 执行到所有阶段,.post
- 在所有阶段之后执行,build
- 先后.pre
阶段,test
- 第二阶段,deploy
——第三阶段。
但是,没有什么能阻止您明确声明它们。 步骤的列出顺序会影响它们的执行顺序。 为了完整起见,让我们添加到配置中:
stages:
- build
- test
- deploy
对于调试,获取有关执行任务的环境的信息是有意义的。 让我们添加一组全局命令,这些命令将在每个任务之前执行 before_script
:
before_script:
- $PSVersionTable.PSVersion
- dotnet --version
- nuget help | select-string Version
它仍然需要添加至少一个任务,以便在发送提交时,管道将启动。 现在,让我们添加一个空任务来演示:
dummy job:
script:
- echo ok
我们开始验证,我们收到一条消息说一切都很好,我们提交,我们推送,我们在网站上查看结果......我们得到一个脚本错误 - bash: .PSVersion: command not found
. 跆拳道?
一切都是合乎逻辑的——默认情况下,runners(负责执行任务脚本,由 GitLab 提供)使用 bash
执行命令。 您可以通过在任务描述中明确指定执行管道运行器应具有的标签来解决此问题:
dummy job on windows:
script:
- echo ok
tags:
- windows
伟大的! 管道现在正在运行。
细心的读者在重复指定的步骤后,会注意到任务已在该阶段完成 test
,虽然我们没有指定阶段。 你可能猜到了 test
是默认步骤。
让我们通过添加上述所有任务来继续创建配置框架:
build job:
script:
- echo "building..."
tags:
- windows
stage: build
test and cover job:
script:
- echo "running tests and coverage analysis..."
tags:
- windows
stage: test
pack and deploy job:
script:
- echo "packing and pushing to nuget..."
tags:
- windows
stage: deploy
pages:
script:
- echo "creating docs..."
tags:
- windows
stage: deploy
我们得到了一个不是特别实用但仍然正确的管道。
设置触发器
由于没有为任何任务指定触发器过滤器,管道将 十分 每次提交被推送到存储库时执行。 由于这通常不是所需的行为,我们将为任务设置触发器过滤器。
过滤器可以配置为两种格式: only/except
允许您通过触发器配置过滤器(merge_request
,例如 - 设置每次创建拉取请求以及每次将提交发送到作为合并请求源的分支时执行的任务)和分支名称(包括使用正则表达式); rules
允许您自定义一组条件,并可选择根据先前任务的成功更改任务执行条件(when
在 GitLab CI/CD 中
让我们回忆一下一组要求——仅针对合并请求进行组装和测试,打包并发送到 Azure DevOps——针对合并请求并推送到主服务器,文档生成——针对推送到主服务器。
首先,让我们通过添加仅在合并请求时触发的规则来设置代码构建任务:
build job:
# snip
only:
- merge_request
现在让我们设置打包任务以触发合并请求并将提交添加到主服务器:
pack and deploy job:
# snip
only:
- merge_request
- master
如您所见,一切都简单明了。
您还可以将任务设置为仅在使用特定目标或源分支创建合并请求时触发:
rules:
- if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"
在条件下,您可以使用 rules
不符合规则 only/except
.
配置工件保存
在任务期间 build job
我们将构建可以在后续任务中重复使用的工件。 为此,您需要将路径添加到任务配置,将需要在以下任务中保存和重用的文件添加到密钥 artifacts
build job:
# snip
artifacts:
paths:
- path/to/build/artifacts
- another/path
- MyCoolLib.*/bin/Release/*
路径支持通配符,这无疑使它们更容易设置。
如果一个任务创建了工件,那么每个后续任务都将能够访问它们——它们将位于与从原始任务收集的存储库根目录相关的相同路径上。 Artifacts 也可以在网站上下载。
现在我们已经准备好配置框架(并经过测试),我们可以继续为任务实际编写脚本。
我们写脚本
也许,曾几何时,在遥远的星系中,从命令行构建项目(包括 .net 上的项目)是一件痛苦的事情。 现在您可以在 3 个团队中构建、测试和发布项目:
dotnet build
dotnet test
dotnet pack
自然地,由于存在一些细微差别,我们会使命令稍微复杂一些。
- 我们想要一个发布版本,而不是调试版本,所以我们添加到每个命令
-c Release
- 在测试的时候,我们想要收集代码覆盖率数据,所以我们需要在测试库中包含一个覆盖率分析器:
- 将包添加到所有测试库
coverlet.msbuild
:dotnet add package coverlet.msbuild
从项目文件夹 - 添加到测试运行命令
/p:CollectCoverage=true
- 在测试任务配置中添加一个键来获取覆盖率结果(见下文)
- 将包添加到所有测试库
- 将代码打包到 nuget 包中时,设置包的输出目录:
-o .
收集代码覆盖率数据
运行测试后,Coverlet 将运行统计信息打印到控制台:
Calculating coverage result...
Generating report 'C:Usersxxxsourcereposmy-projectmyProject.testscoverage.json'
+-------------+--------+--------+--------+
| Module | Line | Branch | Method |
+-------------+--------+--------+--------+
| project 1 | 83,24% | 66,66% | 92,1% |
+-------------+--------+--------+--------+
| project 2 | 87,5% | 50% | 100% |
+-------------+--------+--------+--------+
| project 3 | 100% | 83,33% | 100% |
+-------------+--------+--------+--------+
+---------+--------+--------+--------+
| | Line | Branch | Method |
+---------+--------+--------+--------+
| Total | 84,27% | 65,76% | 92,94% |
+---------+--------+--------+--------+
| Average | 90,24% | 66,66% | 97,36% |
+---------+--------+--------+--------+
GitLab 允许您指定一个正则表达式来获取统计信息,然后可以以徽章的形式获取这些信息。 正则表达式在任务设置中使用键指定 coverage
; 表达式必须包含一个捕获组,其值将传递给徽章:
test and cover job:
# snip
coverage: /|s*Totals*|s*(d+[,.]d+%)/
在这里,我们从具有总线路覆盖率的线路中获取统计信息。
发布包和文档
这两项行动都安排在管道的最后阶段 - 由于组装和测试已经通过,我们可以与世界分享我们的发展。
首先,考虑发布到包源:
-
如果项目没有nuget配置文件(
nuget.config
), 创建一个新的:dotnet new nugetconfig
为了什么: 该图像可能没有对全局(用户和机器)配置的写入权限。 为了不捕获错误,我们只需创建一个新的本地配置并使用它。
- 让我们在本地配置中添加一个新的包源:
nuget sources add -name <name> -source <url> -username <organization> -password <gitlab variable> -configfile nuget.config -StorePasswordInClearText
name
- 本地源名称,不重要url
- 来自“准备账户”阶段的来源 URL,第 6 页organization
- Azure DevOps 中的组织名称gitlab variable
- 带有添加到 GitLab 的访问令牌的变量名称(“准备帐户”,第 11 页)。 自然地,在格式$variableName
-StorePasswordInClearText
-绕过拒绝访问错误的黑客攻击(我不是第一个踩到这个耙子的人 )- 如果出现错误,添加可能会有用
-verbosity detailed
- 将包发送到源:
nuget push -source <name> -skipduplicate -apikey <key> *.nupkg
- 我们从当前目录发送所有包,所以
*.nupkg
. name
- 从上面的步骤。key
- 任何线路。 在 Azure DevOps 中,在“连接到提要”窗口中,示例始终是行az
.-skipduplicate
- 当试图发送一个没有这个密钥的已经存在的包时,源将返回一个错误409 Conflict
; 使用 键,将跳过发送。
- 我们从当前目录发送所有包,所以
现在让我们设置文档的创建:
- 首先,在存储库的 master 分支中,我们初始化 docfx 项目。 为此,从根目录运行命令
docfx init
并以交互方式设置构建文档的关键参数。 最小项目设置的详细说明这里 .- 配置的时候一定要指定输出目录
..public
- 默认情况下,GitLab 将存储库根目录中的公共文件夹的内容作为页面的来源。 因为该项目将位于存储库中嵌套的文件夹中 - 将输出添加到路径中的上一层。
- 配置的时候一定要指定输出目录
- 让我们将更改推送到 GitLab。
- 将任务添加到管道配置
pages
(GitLab Pages 站点发布任务的保留字):- 脚本:
nuget install docfx.console -version 2.51.0
- 安装 docfx; 指定版本以确保软件包安装路径正确。.docfx.console.2.51.0toolsdocfx.exe .docfx_projectdocfx.json
- 收集文件
- 节点工件:
- 脚本:
pages:
# snip
artifacts:
paths:
- public
关于docfx的抒情题外话
以前,在设置项目时,我将文档的代码源指定为解决方案文件。 主要缺点是还为测试项目创建了文档。 如果不需要,您可以将此值设置为节点 metadata.src
:
{
"metadata": [
{
"src": [
{
"src": "../",
"files": [
"**/*.csproj"
],
"exclude":[
"*.tests*/**"
]
}
],
// --- snip ---
},
// --- snip ---
],
// --- snip ---
}
metadata.src.src: "../"
- 我们相对于位置向上一层docfx.json
, 因为在模式中,向上搜索目录树不起作用。metadata.src.files: ["**/*.csproj"]
- 全局模式,我们从所有目录收集所有 C# 项目。metadata.src.exclude: ["*.tests*/**"]
- 全局模式,从文件夹中排除所有内容.tests
在名字里
小计
只需半小时和几杯咖啡即可创建如此简单的配置,这将允许您检查代码是否已构建且测试是否通过,构建新包,更新文档并以漂亮的方式取悦眼睛项目自述文件中的徽章与每个合并请求并发送给主人。
最终.gitlab-ci.yml
image: mcr.microsoft.com/dotnet/core/sdk:3.1
before_script:
- $PSVersionTable.PSVersion
- dotnet --version
- nuget help | select-string Version
stages:
- build
- test
- deploy
build job:
stage: build
script:
- dotnet build -c Release
tags:
- windows
only:
- merge_requests
- master
artifacts:
paths:
- your/path/to/binaries
test and cover job:
stage: test
tags:
- windows
script:
- dotnet test -c Release /p:CollectCoverage=true
coverage: /|s*Totals*|s*(d+[,.]d+%)/
only:
- merge_requests
- master
pack and deploy job:
stage: deploy
tags:
- windows
script:
- dotnet pack -c Release -o .
- dotnet new nugetconfig
- nuget sources add -name feedName -source https://pkgs.dev.azure.com/your-organization/_packaging/your-feed/nuget/v3/index.json -username your-organization -password $nugetFeedToken -configfile nuget.config -StorePasswordInClearText
- nuget push -source feedName -skipduplicate -apikey az *.nupkg
only:
- master
pages:
tags:
- windows
stage: deploy
script:
- nuget install docfx.console -version 2.51.0
- $env:path = "$env:path;$($(get-location).Path)"
- .docfx.console.2.51.0toolsdocfx.exe .docfxdocfx.json
artifacts:
paths:
- public
only:
- master
说到徽章
因为他们,一切终究是开始了!
具有管道状态和代码覆盖率的徽章在 Gtntral 管道块的 CI/CD 设置中的 GitLab 中可用:
我创建了一个带有平台文档链接的徽章
![Пример с Shields.io](https://img.shields.io/badge/custom-badge-blue)
Azure DevOps Artifacts 还允许您为具有最新版本的包创建徽章。 为此,在 Azure DevOps 站点的源代码中,您需要单击为所选包创建徽章并复制降价标记:
增添美感
突出显示常见的配置片段
在编写配置和搜索文档时,我发现了 YAML 的一个有趣特性——重用片段。
从任务设置可以看出,都是需要tag的 windows
在跑步者处,并在将合并请求发送到 master/created 时触发(文档除外)。 让我们将其添加到我们将重用的片段中:
.common_tags: &common_tags
tags:
- windows
.common_only: &common_only
only:
- merge_requests
- master
现在我们可以插入之前在任务描述中声明的片段:
build job:
<<: *common_tags
<<: *common_only
片段名称必须以点开头,以免被解释为任务。
包版本控制
创建包时,编译器会检查命令行开关,如果没有,则检查项目文件; 当它找到 Version 节点时,它会将其值作为正在构建的包的版本。 事实证明,为了使用新版本构建包,您需要在项目文件中更新它或将其作为命令行参数传递。
让我们再添加一个愿望清单——让版本中的次要两个数字成为软件包的年份和构建日期,并添加预发布版本。 当然,您可以将此数据添加到项目文件中并在每次提交之前进行检查 - 但您也可以在管道中执行此操作,从上下文中收集包版本并将其通过命令行参数传递。
让我们同意,如果提交消息包含这样一行 release (v./ver./version) <version number> (rev./revision <revision>)?
, 然后我们将从这一行获取包的版本,用当前日期补充它并将它作为参数传递给命令 dotnet pack
. 在没有线路的情况下,我们根本不会收集包裹。
以下脚本解决了这个问题:
# регулярное выражение для поиска строки с версией
$rx = "releases+(v.?|ver.?|version)s*(?<maj>d+)(?<min>.d+)?(?<rel>.d+)?s*((rev.?|revision)?s+(?<rev>[a-zA-Z0-9-_]+))?"
# ищем строку в сообщении коммита, передаваемом в одной из предопределяемых GitLab'ом переменных
$found = $env:CI_COMMIT_MESSAGE -match $rx
# совпадений нет - выходим
if (!$found) { Write-Output "no release info found, aborting"; exit }
# извлекаем мажорную и минорную версии
$maj = $matches['maj']
$min = $matches['min']
# если строка содержит номер релиза - используем его, иначе - текущий год
if ($matches.ContainsKey('rel')) { $rel = $matches['rel'] } else { $rel = ".$(get-date -format "yyyy")" }
# в качестве номера сборки - текущие месяц и день
$bld = $(get-date -format "MMdd")
# если есть данные по пререлизной версии - включаем их в версию
if ($matches.ContainsKey('rev')) { $rev = "-$($matches['rev'])" } else { $rev = '' }
# собираем единую строку версии
$version = "$maj$min$rel.$bld$rev"
# собираем пакеты
dotnet pack -c Release -o . /p:Version=$version
将脚本添加到任务 pack and deploy job
并在提交消息中存在给定字符串的情况下严格观察包的组装。
在总
在花了大约半小时或一个小时编写配置、在本地 powershell 中调试以及可能有几次不成功的启动之后,我们得到了一个用于自动执行日常任务的简单配置。
当然,GitLab CI / CD 比阅读本指南后看起来更广泛和多方面 -
自动检测、构建、测试、部署和监控您的应用程序
现在的计划是配置一个用于将应用程序部署到 Azure 的管道,使用 Pulumi 并自动确定目标环境,这将在下一篇文章中介绍。
来源: habr.com