CI/CD 模式:Jenkins
完整的声明性 Jenkins 文件,用于进行身份验证、打包、发布、部署 UiPath 解决方案以及运行 Test Manager 测试。
此页面为您提供完整的声明性Jenkinsfile ,用于安装 CLI、使用外部应用程序进行身份验证、打包并发布 UiPath 解决方案,将其部署到 Orchestrator,以及运行 Test Manager 套件。将其放置在存储库的根目录中,创建匹配的 Jenkins 凭据条目,然后运行管道。
有关基本原则(身份验证、缓存、工具预安装、版本固定),请参阅操作方法:从 CI 部署到 Orchestrator 。本页重点介绍 Jenkins 语法,包括 Jenkins 专属符号( withCredentials 、 stash / unstash 、智能体选择)。
先决条件
在复制 Jenkins 文件之前:
- 在 UiPath 中使用管道所需的
OR.*作用域创建外部应用程序。请参阅身份验证 — 流程2 。 - 将密码存储在 Jenkins 凭据中:
- “管理 Jenkins”→“凭据”→“系统”→“全局凭据(无限制)” 。
- 添加两个 ID 为
UIPATH_CLIENT_ID和UIPATH_CLIENT_SECRET“密码”文本条目。 - 添加 ID 为
UIPATH_TENANT(租户名称;不是严格的机密信息,但凭据是最可移植的位置)的“纯文本”或“密码”文本条目。
- 智能体要求。管道假定智能体使用 Node.js 22+ 版本标记为
linux,npm在路径上。如果您在 Windows 上或在容器中运行,请参阅下方的替代智能体选项。 - 如果您需要测试阶段,请配置 Test Manager 项目和测试集。添加
TEST_SET_KEY和PROJECT_KEY作为额外凭据(或管道参数)。
Jenkins 文件
pipeline {
agent none
options {
timestamps()
buildDiscarder(logRotator(numToKeepStr: '30'))
disableConcurrentBuilds()
}
environment {
CLI_VERSION = '1.0.0'
SOLUTION_NAME = 'my-solution'
SOLUTION_DIR = 'my-solution'
OUTPUT_DIR = 'dist'
SOLUTION_VERSION = "1.2.0-ci.${env.BUILD_NUMBER}"
// npm global prefix for a user-local, no-sudo install
NPM_PREFIX = "${env.WORKSPACE}/.npm-global"
}
stages {
stage('Build') {
agent { label 'linux' }
steps {
checkout scm
sh '''#!/usr/bin/env bash
set -euo pipefail
mkdir -p "$NPM_PREFIX"
npm config set prefix "$NPM_PREFIX"
export PATH="$NPM_PREFIX/bin:$PATH"
if ! command -v uip >/dev/null; then
npm install -g "@uipath/cli@${CLI_VERSION}"
fi
uip --version
mkdir -p "$OUTPUT_DIR"
uip solution pack "$SOLUTION_DIR" "$OUTPUT_DIR" \
--name "$SOLUTION_NAME" \
--version "$SOLUTION_VERSION"
'''
// Carry the .zip to the Deploy stage, which runs on a fresh agent.
stash name: 'solution-zip', includes: "${OUTPUT_DIR}/*.zip"
archiveArtifacts artifacts: "${OUTPUT_DIR}/*.zip", fingerprint: true
}
}
stage('Deploy') {
agent { label 'linux' }
steps {
unstash 'solution-zip'
withCredentials([
string(credentialsId: 'UIPATH_CLIENT_ID', variable: 'UIPATH_CLIENT_ID'),
string(credentialsId: 'UIPATH_CLIENT_SECRET', variable: 'UIPATH_CLIENT_SECRET'),
string(credentialsId: 'UIPATH_TENANT', variable: 'UIPATH_TENANT')
]) {
sh '''#!/usr/bin/env bash
set -euo pipefail
mkdir -p "$NPM_PREFIX"
npm config set prefix "$NPM_PREFIX"
export PATH="$NPM_PREFIX/bin:$PATH"
if ! command -v uip >/dev/null; then
npm install -g "@uipath/cli@${CLI_VERSION}"
fi
uip login \
--client-id env.UIPATH_CLIENT_ID \
--client-secret env.UIPATH_CLIENT_SECRET \
--tenant "$UIPATH_TENANT"
ARTIFACT=$(find "$OUTPUT_DIR" -maxdepth 1 -name "*.zip" | head -1)
uip solution publish "$ARTIFACT"
uip solution deploy run \
--name "${SOLUTION_NAME}-${BUILD_NUMBER}" \
--package-name "$SOLUTION_NAME" \
--package-version "$SOLUTION_VERSION" \
--folder-name MySolution \
--parent-folder-path Shared
'''
}
}
}
stage('Test') {
when {
expression { return env.TEST_SET_KEY?.trim() }
}
agent { label 'linux' }
steps {
withCredentials([
string(credentialsId: 'UIPATH_CLIENT_ID', variable: 'UIPATH_CLIENT_ID'),
string(credentialsId: 'UIPATH_CLIENT_SECRET', variable: 'UIPATH_CLIENT_SECRET'),
string(credentialsId: 'UIPATH_TENANT', variable: 'UIPATH_TENANT'),
string(credentialsId: 'TEST_SET_KEY', variable: 'TEST_SET_KEY'),
string(credentialsId: 'PROJECT_KEY', variable: 'PROJECT_KEY')
]) {
sh '''#!/usr/bin/env bash
set -euo pipefail
mkdir -p "$NPM_PREFIX"
npm config set prefix "$NPM_PREFIX"
export PATH="$NPM_PREFIX/bin:$PATH"
if ! command -v uip >/dev/null; then
npm install -g "@uipath/cli@${CLI_VERSION}"
fi
uip login \
--client-id env.UIPATH_CLIENT_ID \
--client-secret env.UIPATH_CLIENT_SECRET \
--tenant "$UIPATH_TENANT"
EXECUTION_ID=$(uip tm testsets run \
--test-set-key "$TEST_SET_KEY" \
--output-filter "Data.ExecutionId" \
--output plain)
echo "started execution $EXECUTION_ID"
if ! uip tm wait \
--execution-id "$EXECUTION_ID" \
--project-key "$PROJECT_KEY" \
--timeout 1800; then
code=$?
case "$code" in
2) echo "test run did not finish within 30 minutes" >&2; exit 2 ;;
*) echo "wait failed (exit $code)" >&2; exit "$code" ;;
esac
fi
uip tm report get \
--execution-id "$EXECUTION_ID" \
--project-key "$PROJECT_KEY"
FAILED=$(uip tm report get \
--execution-id "$EXECUTION_ID" \
--project-key "$PROJECT_KEY" \
--output-filter "Data.Failed" \
--output plain)
if [ "$FAILED" -gt 0 ]; then
echo "$FAILED test case(s) failed" >&2
exit 1
fi
'''
}
}
}
}
post {
always {
// Best-effort cleanup; uip logout only matters on a persistent agent.
node('linux') {
sh '''
if command -v uip >/dev/null; then
uip logout || true
fi
'''
}
}
}
}
pipeline {
agent none
options {
timestamps()
buildDiscarder(logRotator(numToKeepStr: '30'))
disableConcurrentBuilds()
}
environment {
CLI_VERSION = '1.0.0'
SOLUTION_NAME = 'my-solution'
SOLUTION_DIR = 'my-solution'
OUTPUT_DIR = 'dist'
SOLUTION_VERSION = "1.2.0-ci.${env.BUILD_NUMBER}"
// npm global prefix for a user-local, no-sudo install
NPM_PREFIX = "${env.WORKSPACE}/.npm-global"
}
stages {
stage('Build') {
agent { label 'linux' }
steps {
checkout scm
sh '''#!/usr/bin/env bash
set -euo pipefail
mkdir -p "$NPM_PREFIX"
npm config set prefix "$NPM_PREFIX"
export PATH="$NPM_PREFIX/bin:$PATH"
if ! command -v uip >/dev/null; then
npm install -g "@uipath/cli@${CLI_VERSION}"
fi
uip --version
mkdir -p "$OUTPUT_DIR"
uip solution pack "$SOLUTION_DIR" "$OUTPUT_DIR" \
--name "$SOLUTION_NAME" \
--version "$SOLUTION_VERSION"
'''
// Carry the .zip to the Deploy stage, which runs on a fresh agent.
stash name: 'solution-zip', includes: "${OUTPUT_DIR}/*.zip"
archiveArtifacts artifacts: "${OUTPUT_DIR}/*.zip", fingerprint: true
}
}
stage('Deploy') {
agent { label 'linux' }
steps {
unstash 'solution-zip'
withCredentials([
string(credentialsId: 'UIPATH_CLIENT_ID', variable: 'UIPATH_CLIENT_ID'),
string(credentialsId: 'UIPATH_CLIENT_SECRET', variable: 'UIPATH_CLIENT_SECRET'),
string(credentialsId: 'UIPATH_TENANT', variable: 'UIPATH_TENANT')
]) {
sh '''#!/usr/bin/env bash
set -euo pipefail
mkdir -p "$NPM_PREFIX"
npm config set prefix "$NPM_PREFIX"
export PATH="$NPM_PREFIX/bin:$PATH"
if ! command -v uip >/dev/null; then
npm install -g "@uipath/cli@${CLI_VERSION}"
fi
uip login \
--client-id env.UIPATH_CLIENT_ID \
--client-secret env.UIPATH_CLIENT_SECRET \
--tenant "$UIPATH_TENANT"
ARTIFACT=$(find "$OUTPUT_DIR" -maxdepth 1 -name "*.zip" | head -1)
uip solution publish "$ARTIFACT"
uip solution deploy run \
--name "${SOLUTION_NAME}-${BUILD_NUMBER}" \
--package-name "$SOLUTION_NAME" \
--package-version "$SOLUTION_VERSION" \
--folder-name MySolution \
--parent-folder-path Shared
'''
}
}
}
stage('Test') {
when {
expression { return env.TEST_SET_KEY?.trim() }
}
agent { label 'linux' }
steps {
withCredentials([
string(credentialsId: 'UIPATH_CLIENT_ID', variable: 'UIPATH_CLIENT_ID'),
string(credentialsId: 'UIPATH_CLIENT_SECRET', variable: 'UIPATH_CLIENT_SECRET'),
string(credentialsId: 'UIPATH_TENANT', variable: 'UIPATH_TENANT'),
string(credentialsId: 'TEST_SET_KEY', variable: 'TEST_SET_KEY'),
string(credentialsId: 'PROJECT_KEY', variable: 'PROJECT_KEY')
]) {
sh '''#!/usr/bin/env bash
set -euo pipefail
mkdir -p "$NPM_PREFIX"
npm config set prefix "$NPM_PREFIX"
export PATH="$NPM_PREFIX/bin:$PATH"
if ! command -v uip >/dev/null; then
npm install -g "@uipath/cli@${CLI_VERSION}"
fi
uip login \
--client-id env.UIPATH_CLIENT_ID \
--client-secret env.UIPATH_CLIENT_SECRET \
--tenant "$UIPATH_TENANT"
EXECUTION_ID=$(uip tm testsets run \
--test-set-key "$TEST_SET_KEY" \
--output-filter "Data.ExecutionId" \
--output plain)
echo "started execution $EXECUTION_ID"
if ! uip tm wait \
--execution-id "$EXECUTION_ID" \
--project-key "$PROJECT_KEY" \
--timeout 1800; then
code=$?
case "$code" in
2) echo "test run did not finish within 30 minutes" >&2; exit 2 ;;
*) echo "wait failed (exit $code)" >&2; exit "$code" ;;
esac
fi
uip tm report get \
--execution-id "$EXECUTION_ID" \
--project-key "$PROJECT_KEY"
FAILED=$(uip tm report get \
--execution-id "$EXECUTION_ID" \
--project-key "$PROJECT_KEY" \
--output-filter "Data.Failed" \
--output plain)
if [ "$FAILED" -gt 0 ]; then
echo "$FAILED test case(s) failed" >&2
exit 1
fi
'''
}
}
}
}
post {
always {
// Best-effort cleanup; uip logout only matters on a persistent agent.
node('linux') {
sh '''
if command -v uip >/dev/null; then
uip logout || true
fi
'''
}
}
}
}
演练
顶级设置
- 顶部的
agent none声明该管道没有默认智能体;每个阶段都使用agent { label 'linux' }选择自己的阶段。这样,您就可以将构建放在高容量智能体上,将部署放在锁定智能体上(如果您的基础架构将其分开)。 options—timestamps()到每个控制台行添加[2026-04-24T10:30:12]前缀,这使得调试长运行容易得多。disableConcurrentBuilds()可防止两个部署竞争到同一个 Orchestrator 文件夹。environment块— 与其他菜单的模式相同:固定CLI_VERSION,根据BUILD_NUMBER计算SOLUTION_VERSION,并将 npm 的全局前缀路由到工作区中,因此安装是按内部版本进行的,不需要sudo。
构建阶段
checkout scm— Jenkins 在声明性管道中明确需要此权限。- 安装步骤—
if ! command -v uip防护措施与其他平台相同。工作区本地 npm 前缀 ($NPM_PREFIX) 意味着每次构建都从干净开始;不在运行之间缓存。如果您的 Jenkins 智能体是持久性的,并且您要缓存 CLI,请使用作业缓存器插件或将NPM_PREFIX设置为智能体上的路径,而不是${env.WORKSPACE}。 - 打包步骤— 带显式版本的直接
uip solution pack。 stash name: 'solution-zip'— 这是 Jenkins 专属的部分。与 GitHub Actions 工件或 Azure 管道publish不同,stash作用域为管道运行,并可以在各个阶段保留,无需工作区来保留。存储使用通配符 (${OUTPUT_DIR}/*.zip),因此它会选取uip solution pack生成的任何文件名。需要.zip每个阶段都会在开始时调用unstash 'solution-zip'。请参阅stash/unstash上的 Jenkins 文档。archiveArtifacts— 内部版本页面上也会显示.zip的副本,供人工下载。fingerprint: true可让 Jenkins 跨作业跟踪它。
部署阶段
unstash 'solution-zip'将.zip还原到工作区。与新的智能体结合使用,可以将“构建上下文”与“部署上下文”清晰分离。withCredentials会将三个凭据条目绑定到对sh块可见的环境变量。关闭是凭据安全边界 — 值在控制台日志中进行掩码处理,不会泄露到该代码块以外的任何步骤。将其用于与 Orchestrator 对话的每个uip调用。uip login使用env.UIPATH_CLIENT_ID/env.UIPATH_CLIENT_SECRET—env.VAR_NAME前缀读取由withCredentials设置的变量。请参阅身份验证 — env.VAR_NAME 前缀。切勿将密码直接插入命令行 (--client-secret "$UIPATH_CLIENT_SECRET") 中 — 这会将密码嵌入到呈现的 Shell 命令中,在某些日志记录配置下,还会嵌入到控制台输出中。- 发布 + 部署—
uip solution publish,然后是uip solution deploy run。--name "${SOLUTION_NAME}-${BUILD_NUMBER}"使每个部署的记录可追踪至其 Jenkins 内部版本,但--folder-name/--parent-folder-path是静态的,并且deploy run始终在新文件夹中创建新部署;它从不更新现有副本。在针对已部署的包重复运行时,它会停止并指向升级路径(除非--yes,这将部署第二个经冲突重命名的副本)。该预检需要用户会话,因此在此管道使用的客户端凭据登录名下完全跳过此检查 — 此处的每个绿色构建都会创建一个新文件夹。要就地更新现有部署,请使用uip solution deploy upgrade <deployment-key>(找到包含uip solution deploy list --folder-path Shared的密钥)。
测试阶段
由when { expression { return env.TEST_SET_KEY?.trim() } }把控 — 如果您不设置TEST_SET_KEY凭据(或将其留空),则会跳过该阶段。shell 块是“操作方法:从 CLI 运行测试” 中的规范“启动”→“等待”→“验证” 模式:
uip tm testsets run启动并返回ExecutionId。uip tm wait将阻止,直到执行达到终止状态。在wait上,退出代码2意味着超时(不是身份验证失败,而是代码的特定域重用)。uip tm report get读取Data.Failed,并且在任何测试失败时,shell 退出1。
发布 {always}
“尽最大努力” uip logout 。在临时智能体上没有必要;在持久性智能体上,它将清除工作区本地的.uipath/文件夹,以便同一工作区上的后续作业不会重用过时的会话。如果从未安装uip (安装前构建失败),则|| true会发生错误。
常见变体
智能体变体
- Windows 代理— 将
sh替换为bat,将 bash 脚本转换为cmd.exe或 PowerShell。env.前缀和每个uip命令相同;只有周围的 Shell 会发生变化。请参阅安装 UiPath CLI — Windows 。 - Docker 代理—
agent { docker { image 'node:22' } }在每次构建时为您提供一个全新的节点运行时,且代理配置为零。如果您需要在有根映像中安装无需 sudo 的全局 npm,请添加args '-u root';否则,如上所述,将 NPM 前缀配置为$WORKSPACE/.npm-global并跳过权限调整。 - Kubernetes 插件— 将 Pod 模板与
node:22容器一起使用,并将/root/.npm-global装载作为 PVC,以在构建之间缓存 CLI。
跨环境提升
重用相同的stash ,将每个环境变成自己的阶段:
stage('Deploy stage') {
agent { label 'linux' }
steps {
unstash 'solution-zip'
withCredentials([string(credentialsId: 'UIPATH_STAGE_CLIENT_ID', variable: 'UIPATH_CLIENT_ID'), ...]) {
sh '...deploy to stage tenant...'
}
}
}
stage('Approval') {
steps {
input message: 'Deploy to production?', ok: 'Deploy'
}
}
stage('Deploy prod') {
agent { label 'linux' }
steps {
unstash 'solution-zip'
withCredentials([string(credentialsId: 'UIPATH_PROD_CLIENT_ID', variable: 'UIPATH_CLIENT_ID'), ...]) {
sh '...deploy to prod tenant...'
}
}
}
stage('Deploy stage') {
agent { label 'linux' }
steps {
unstash 'solution-zip'
withCredentials([string(credentialsId: 'UIPATH_STAGE_CLIENT_ID', variable: 'UIPATH_CLIENT_ID'), ...]) {
sh '...deploy to stage tenant...'
}
}
}
stage('Approval') {
steps {
input message: 'Deploy to production?', ok: 'Deploy'
}
}
stage('Deploy prod') {
agent { label 'linux' }
steps {
unstash 'solution-zip'
withCredentials([string(credentialsId: 'UIPATH_PROD_CLIENT_ID', variable: 'UIPATH_CLIENT_ID'), ...]) {
sh '...deploy to prod tenant...'
}
}
}
input步骤会暂停管道,直到人工单击“部署” 。使用 Jenkins 的矩阵授权策略来限制可以审批的人员。《操作方法:打包并发布解决方案 — 跨租户推广一个包》中更深入的介绍。
回滚
添加参数化作业 — Jenkins → 新建项目 → 管道 → 此项目已参数化 → 添加参数(字符串)→“ROLLBACK_VERSION” — 然后添加一个受保护的阶段。使用 deploy upgrade 而不是 deploy run — deploy run 将创建一个全新的文件夹,而不是接触您要回滚的部署:
stage('Rollback') {
when { expression { return params.ROLLBACK_VERSION?.trim() } }
agent { label 'linux' }
steps {
withCredentials([/* UIPATH_* credentials */]) {
sh '''#!/usr/bin/env bash
set -euo pipefail
# …install + login…
DEPLOYMENT_KEY=$(uip solution deploy list --folder-path Shared \
--output-filter "Data[?Name=='MySolution'].Key | [0]" --output plain)
uip solution deploy upgrade "$DEPLOYMENT_KEY" --version "$ROLLBACK_VERSION"
'''
}
}
environment {
ROLLBACK_VERSION = "${params.ROLLBACK_VERSION}"
}
}
stage('Rollback') {
when { expression { return params.ROLLBACK_VERSION?.trim() } }
agent { label 'linux' }
steps {
withCredentials([/* UIPATH_* credentials */]) {
sh '''#!/usr/bin/env bash
set -euo pipefail
# …install + login…
DEPLOYMENT_KEY=$(uip solution deploy list --folder-path Shared \
--output-filter "Data[?Name=='MySolution'].Key | [0]" --output plain)
uip solution deploy upgrade "$DEPLOYMENT_KEY" --version "$ROLLBACK_VERSION"
'''
}
}
environment {
ROLLBACK_VERSION = "${params.ROLLBACK_VERSION}"
}
}
使用“使用参数构建”从构建页面触发。deploy upgrade 保留部署的现有配置 — 不会将以其已设置的资产/凭据值重置为包默认值。有关破坏性回滚(卸载并删除工件),请参阅操作方法:打包并发布解决方案 — 回滚。
常见错误
withCredentials作用域。凭据仅在关闭中可用。如果在uip login内部调用withCredentials并在外部调用uip solution publish,则第二个命令没有会话。将整个 CLI 块放在一个withCredentials中。- 更改智能体前查看
stash。仅当存储步骤在智能体切换之前运行完成时,在一个阶段中声明的stash才能在后续阶段使用。如果Build阶段的 Shell 块在stash之前以非零值退出,则后续阶段将在unstash失败。将stash放在sh块的外部(如本秘钥中所示),可确保无论 Shell 结果如何,它都可以在清洁智能体上运行。 || true在错误的时间进行。uip logout || true中的post是安全的,因为唯一的故障模式是“从未安装 uip”。不要在早期阶段加入|| true— 它会隐藏真正的故障。sh严格模式。以#!/usr/bin/env bash和set -euo pipefail开头每个sh块的 bash 脚本。默认 Jenkins shell 为/bin/sh,它不支持pipefail。请参阅脚本编写模式—严格 shell 选项。- Github 中的多行字符串Shell 主体首选三单引号字符串 (
'''...''') — 它们不会插值$VAR,因此变量将由 bash 展开,而不是由 Git 展开。这避免了“变量为空”的常见错误。对于您希望 Git 在管道编译时填充的值,请显式使用${env.SOME_VAR}。
另请参阅
- 操作方法:从 CI 部署到 Orchestrator — 与平台无关的指南。
- 操作方法:打包并发布解决方案— 版本控制和回滚。
- 操作方法:从 CLI 运行测试— 启动 → 等待 → 验证模式。
- CI/CD 秘钥:Azure Pipelines 、 GitHub Actions 、 GitLab CI — 其他平台中的相同管道。