UiPath Documentation
uipath-cli
latest
false
UiPath CLI 用户指南
重要 :
请注意,此内容已使用机器翻译进行了本地化。 新发布内容的本地化可能需要 1-2 周的时间才能完成。

CI/CD 模式:Jenkins

完整的声明性 Jenkins 文件,用于进行身份验证、打包、发布、部署 UiPath 解决方案以及运行 Test Manager 测试。

此页面为您提供完整的声明性Jenkinsfile ,用于安装 CLI、使用外部应用程序进行身份验证、打包并发布 UiPath 解决方案,将其部署到 Orchestrator,以及运行 Test Manager 套件。将其放置在存储库的根目录中,创建匹配的 Jenkins 凭据条目,然后运行管道。

有关基本原则(身份验证、缓存、工具预安装、版本固定),请参阅操作方法:从 CI 部署到 Orchestrator 。本页重点介绍 Jenkins 语法,包括 Jenkins 专属符号( withCredentials 、 stash / unstash 、智能体选择)。

先决条件​

在复制 Jenkins 文件之前:

  1. 在 UiPath 中使用管道所需的OR.*作用域创建外部应用程序。请参阅身份验证 — 流程2 。
  2. 将密码存储在 Jenkins 凭据中:
    • “管理 Jenkins”→“凭据”→“系统”→“全局凭据(无限制)” 。
    • 添加两个 ID 为UIPATH_CLIENT_ID和UIPATH_CLIENT_SECRET “密码”文本条目。
    • 添加 ID 为UIPATH_TENANT (租户名称;不是严格的机密信息,但凭据是最可移植的位置)的“纯文本”或“密码”文本条目。
  3. 智能体要求。管道假定智能体使用 Node.js 22+ 版本标记为 linux,npm 在路径上。如果您在 Windows 上或在容器中运行,请参阅下方的替代智能体选项。
  4. 如果您需要测试阶段,请配置 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 运行测试” 中的规范“启动”→“等待”→“验证” 模式:

  1. uip tm testsets run启动并返回ExecutionId 。
  2. uip tm wait将阻止,直到执行达到终止状态。在wait上,退出代码2意味着超时(不是身份验证失败,而是代码的特定域重用)。
  3. 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} 。

另请参阅​

此页面有帮助吗?

连接

需要帮助? 支持

想要了解详细内容? UiPath Academy

有问题? UiPath 论坛

保持更新