{"id":54,"date":"2026-03-19T10:00:00","date_gmt":"2026-03-19T02:00:00","guid":{"rendered":"http:\/\/localhost:8888\/?p=54"},"modified":"2026-07-09T16:17:45","modified_gmt":"2026-07-09T08:17:45","slug":"runnergo%e9%9b%86%e6%88%90ci-cd%e5%ae%9e%e6%88%98","status":"publish","type":"post","link":"https:\/\/blog.runnergo.cn\/?p=54","title":{"rendered":"RunnerGo\u96c6\u6210CI\/CD\u5b9e\u6218 &#8211; \u6d4b\u8bd5\u6d41\u6c34\u7ebf\u81ea\u52a8\u5316\u90e8\u7f72\u6307\u5357"},"content":{"rendered":"<h1>RunnerGo\u96c6\u6210CI\/CD\u5b9e\u6218 &#8211; \u6d4b\u8bd5\u6d41\u6c34\u7ebf\u81ea\u52a8\u5316\u90e8\u7f72\u6307\u5357<\/h1>\n<p>&gt; \u4f5c\u8005\uff1aCI\u96c6\u6210\u4e13\u5bb6<br \/>\n&#8212;<\/p>\n<h2>\u524d\u8a00<\/h2>\n<p>\u5927\u5bb6\u597d\uff0c\u6211\u662fCI\u96c6\u6210\u4e13\u5bb6\u3002\u505aCI\/CD\u5b9e\u8df58\u5e74\uff0c\u6d4b\u8bd5\u96c6\u6210\u662f\u6700\u5173\u952e\u7684\u73af\u8282\u4e4b\u4e00\u3002RunnerGo\u63d0\u4f9b\u4e86\u5b8c\u5584\u7684API\u63a5\u53e3\uff0c\u53ef\u4ee5\u8f7b\u677e\u96c6\u6210\u5230\u5404\u79cdCI\/CD\u5e73\u53f0\u3002\u4eca\u5929\u5206\u4eabRunnerGo\u96c6\u6210CI\/CD\u7684\u5b9e\u6218\u7ecf\u9a8c\u3002<\/p>\n<p>&gt; \u672c\u6587\u7ae0\u53c2\u8003RunnerGo v2.3\u5b98\u65b9\u6587\u6863<\/p>\n<p>&#8212;<\/p>\n<h2>\u4e00\u3001RunnerGo API\u4ecb\u7ecd<\/h2>\n<h3>API\u80fd\u529b<\/h3>\n<p>RunnerGo\u63d0\u4f9b\u4e30\u5bcc\u7684API\u63a5\u53e3\uff1a<\/p>\n<p>| API | \u529f\u80fd |<br \/>\n|&#8212;&#8211;|&#8212;&#8212;|<br \/>\n| \u6267\u884c\u6d4b\u8bd5\u8ba1\u5212 | \u89e6\u53d1\u81ea\u52a8\u5316\/\u6027\u80fd\u6d4b\u8bd5 |<br \/>\n| \u67e5\u8be2\u6267\u884c\u72b6\u6001 | \u83b7\u53d6\u6d4b\u8bd5\u8fdb\u5ea6 |<br \/>\n| \u83b7\u53d6\u6d4b\u8bd5\u62a5\u544a | \u4e0b\u8f7d\u6d4b\u8bd5\u62a5\u544a |<br \/>\n| \u7ba1\u7406\u573a\u666f | \u521b\u5efa\/\u4fee\u6539\u6d4b\u8bd5\u573a\u666f |<\/p>\n<h3>API\u8ba4\u8bc1<\/h3>\n<p>RunnerGo\u4f7f\u7528Token\u8ba4\u8bc1\uff1a<\/p>\n<p><code>`<\/code>bash<\/p>\n<h1>\u83b7\u53d6Token<\/h1>\n<p>curl -X POST ${RUNNERGO_URL}\/api\/auth\/login<br \/>\n  -H &#8220;Content-Type: application\/json&#8221;<br \/>\n  -d &#8216;{&#8220;username&#8221;:&#8221;admin&#8221;,&#8221;password&#8221;:&#8221;password&#8221;}&#8217;<\/p>\n<h1>\u4f7f\u7528Token<\/h1>\n<p>curl -X GET ${RUNNERGO_URL}\/api\/plans<br \/>\n  -H &#8220;Authorization: Bearer ${TOKEN}&#8221;<br \/>\n<code>`<\/code><\/p>\n<p>&#8212;<\/p>\n<h2>\u4e8c\u3001Jenkins\u96c6\u6210<\/h2>\n<h3>\u521b\u5efaPipeline<\/h3>\n<p><code>`<\/code>groovy<br \/>\npipeline {<br \/>\n    agent any<\/p>\n<p>    environment {<br \/>\n        RUNNERGO_URL = &#8216;http:\/\/runnergo.example.com&#8217;<br \/>\n        RUNNERGO_TOKEN = credentials(&#8216;runnergo-token&#8217;)<br \/>\n        PLAN_ID = &#8216;plan-xxx-xxx&#8217;<br \/>\n    }<\/p>\n<p>    stages {<br \/>\n        stage(&#8216;RunnerGo\u81ea\u52a8\u5316\u6d4b\u8bd5&#8217;) {<br \/>\n            steps {<br \/>\n                script {<br \/>\n                    \/\/ \u89e6\u53d1\u6d4b\u8bd5<br \/>\n                    def response = sh(<br \/>\n                        script: &#8220;&#8221;&#8221;<br \/>\n                            curl -s -X POST ${RUNNERGO_URL}\/api\/plan\/run<br \/>\n                                -H &#8220;Authorization: Bearer ${RUNNERGO_TOKEN}&#8221;<br \/>\n                                -H &#8220;Content-Type: application\/json&#8221;<br \/>\n                                -d &#8216;{&#8220;plan_id&#8221;:&#8221;${PLAN_ID}&#8221;}&#8217;<br \/>\n                        &#8220;&#8221;&#8221;,<br \/>\n                        returnStdout: true<br \/>\n                    ).trim()<\/p>\n<p>                    def json = new groovy.json.JsonSlurper().parseText(response)<br \/>\n                    def reportId = json.report_id<\/p>\n<p>                    \/\/ \u7b49\u5f85\u6d4b\u8bd5\u5b8c\u6210<br \/>\n                    def status = &#8216;running&#8217;<br \/>\n                    while (status == &#8216;running&#8217;) {<br \/>\n                        sleep(time: 10, unit: &#8216;SECONDS&#8217;)<\/p>\n<p>                        def statusResponse = sh(<br \/>\n                            script: &#8220;&#8221;&#8221;<br \/>\n                                curl -s ${RUNNERGO_URL}\/api\/report\/${reportId}\/status<br \/>\n                                    -H &#8220;Authorization: Bearer ${RUNNERGO_TOKEN}&#8221;<br \/>\n                            &#8220;&#8221;&#8221;,<br \/>\n                            returnStdout: true<br \/>\n                        ).trim()<\/p>\n<p>                        def statusJson = new groovy.json.JsonSlurper().parseText(statusResponse)<br \/>\n                        status = statusJson.status<br \/>\n                    }<\/p>\n<p>                    \/\/ \u68c0\u67e5\u7ed3\u679c<br \/>\n                    if (status != &#8216;success&#8217;) {<br \/>\n                        error(&#8220;\u6d4b\u8bd5\u5931\u8d25&#8221;)<br \/>\n                    }<br \/>\n                }<br \/>\n            }<br \/>\n        }<br \/>\n    }<\/p>\n<p>    post {<br \/>\n        always {<br \/>\n            \/\/ \u83b7\u53d6\u62a5\u544a\u94fe\u63a5<br \/>\n            sh &#8220;&#8221;&#8221;<br \/>\n                curl -s ${RUNNERGO_URL}\/api\/report\/${reportId}<br \/>\n                    -H &#8220;Authorization: Bearer ${RUNNERGO_TOKEN}&#8221;<br \/>\n                    &gt; reports\/runnergo-report.json<br \/>\n            &#8220;&#8221;&#8221;<br \/>\n        }<br \/>\n    }<br \/>\n}<br \/>\n<code>`<\/code><\/p>\n<p>&#8212;<\/p>\n<h2>\u4e09\u3001GitLab CI\u96c6\u6210<\/h2>\n<p><code>`<\/code>yaml<\/p>\n<h1>.gitlab-ci.yml<\/h1>\n<p>stages:<br \/>\n  &#8211; build<br \/>\n  &#8211; test<\/p>\n<p>runnergo_test:<br \/>\n  stage: test<br \/>\n  image: curlimages\/curl:latest<br \/>\n  script:<br \/>\n    # \u89e6\u53d1\u6d4b\u8bd5<br \/>\n    &#8211; |<br \/>\n      RESPONSE=$(curl -s -X POST ${RUNNERGO_URL}\/api\/plan\/run<br \/>\n        -H &#8220;Authorization: Bearer ${RUNNERGO_TOKEN}&#8221;<br \/>\n        -H &#8220;Content-Type: application\/json&#8221;<br \/>\n        -d &#8216;{&#8220;plan_id&#8221;:&#8221;${PLAN_ID}&#8221;}&#8217;)<\/p>\n<p>      REPORT_ID=$(echo $RESPONSE | jq -r &#8216;.report_id&#8217;)<\/p>\n<p>    # \u7b49\u5f85\u6d4b\u8bd5\u5b8c\u6210<br \/>\n    &#8211; |<br \/>\n      while true; do<br \/>\n        STATUS=$(curl -s ${RUNNERGO_URL}\/api\/report\/${REPORT_ID}\/status<br \/>\n          -H &#8220;Authorization: Bearer ${RUNNERGO_TOKEN}&#8221; | jq -r &#8216;.status&#8217;)<\/p>\n<p>        if [ &#8220;$STATUS&#8221; != &#8220;running&#8221; ]; then<br \/>\n          break<br \/>\n        fi<\/p>\n<p>        sleep 10<br \/>\n      done<\/p>\n<p>    # \u68c0\u67e5\u7ed3\u679c<br \/>\n    &#8211; |<br \/>\n      if [ &#8220;$STATUS&#8221; != &#8220;success&#8221; ]; then<br \/>\n        echo &#8220;\u6d4b\u8bd5\u5931\u8d25&#8221;<br \/>\n        exit 1<br \/>\n      fi<\/p>\n<p>  variables:<br \/>\n    RUNNERGO_URL: &#8220;http:\/\/runnergo.example.com&#8221;<br \/>\n    RUNNERGO_TOKEN: ${RUNNERGO_API_TOKEN}<br \/>\n    PLAN_ID: &#8220;plan-xxx-xxx&#8221;<br \/>\n<code>`<\/code><\/p>\n<p>&#8212;<\/p>\n<h2>\u56db\u3001GitHub Actions\u96c6\u6210<\/h2>\n<p><code>`<\/code>yaml<\/p>\n<h1>.github\/workflows\/test.yml<\/h1>\n<p>name: RunnerGo Test<\/p>\n<p>on:<br \/>\n  push:<br \/>\n    branches: [main]<br \/>\n  pull_request:<br \/>\n    branches: [main]<\/p>\n<p>jobs:<br \/>\n  test:<br \/>\n    runs-on: ubuntu-latest<\/p>\n<p>    steps:<br \/>\n      &#8211; name: Trigger RunnerGo Test<br \/>\n        run: |<br \/>\n          RESPONSE=$(curl -s -X POST ${{ secrets.RUNNERGO_URL }}\/api\/plan\/run<br \/>\n            -H &#8220;Authorization: Bearer ${{ secrets.RUNNERGO_TOKEN }}&#8221;<br \/>\n            -H &#8220;Content-Type: application\/json&#8221;<br \/>\n            -d &#8216;{&#8220;plan_id&#8221;:&#8221;${{ secrets.PLAN_ID }}&#8221;}&#8217;)<\/p>\n<p>          REPORT_ID=$(echo $RESPONSE | jq -r &#8216;.report_id&#8217;)<br \/>\n          echo &#8220;report_id=$REPORT_ID&#8221; &gt;&gt; $GITHUB_ENV<\/p>\n<p>      &#8211; name: Wait for Test Completion<br \/>\n        run: |<br \/>\n          while true; do<br \/>\n            STATUS=$(curl -s ${{ secrets.RUNNERGO_URL }}\/api\/report\/${{ env.report_id }}\/status<br \/>\n              -H &#8220;Authorization: Bearer ${{ secrets.RUNNERGO_TOKEN }}&#8221; | jq -r &#8216;.status&#8217;)<\/p>\n<p>            if [ &#8220;$STATUS&#8221; != &#8220;running&#8221; ]; then<br \/>\n              echo &#8220;Test completed with status: $STATUS&#8221;<br \/>\n              if [ &#8220;$STATUS&#8221; != &#8220;success&#8221; ]; then<br \/>\n                exit 1<br \/>\n              fi<br \/>\n              break<br \/>\n            fi<\/p>\n<p>            sleep 10<br \/>\n          done<\/p>\n<p>      &#8211; name: Download Report<br \/>\n        if: always()<br \/>\n        run: |<br \/>\n          curl -s ${{ secrets.RUNNERGO_URL }}\/api\/report\/${{ env.report_id }}<br \/>\n            -H &#8220;Authorization: Bearer ${{ secrets.RUNNERGO_TOKEN }}&#8221;<br \/>\n            &gt; runnergo-report.json<\/p>\n<p>      &#8211; name: Upload Report<br \/>\n        if: always()<br \/>\n        uses: actions\/upload-artifact@v3<br \/>\n        with:<br \/>\n          name: runnergo-report<br \/>\n          path: runnergo-report.json<br \/>\n<code>`<\/code><\/p>\n<p>&#8212;<\/p>\n<h2>\u4e94\u3001\u8d28\u91cf\u95e8\u7981\u914d\u7f6e<\/h2>\n<h3>\u6d4b\u8bd5\u5931\u8d25\u963b\u65ad\u90e8\u7f72<\/h3>\n<p><code>`<\/code>groovy<br \/>\nstage(&#8216;\u8d28\u91cf\u68c0\u67e5&#8217;) {<br \/>\n    steps {<br \/>\n        script {<br \/>\n            \/\/ \u6267\u884cRunnerGo\u6d4b\u8bd5<br \/>\n            def success = triggerRunnerGoTest()<\/p>\n<p>            if (!success) {<br \/>\n                error(&#8216;\u8d28\u91cf\u95e8\u7981\u672a\u901a\u8fc7\uff0c\u963b\u65ad\u90e8\u7f72&#8217;)<br \/>\n            }<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<p>stage(&#8216;\u90e8\u7f72&#8217;) {<br \/>\n    \/\/ \u53ea\u6709\u8d28\u91cf\u68c0\u67e5\u901a\u8fc7\u624d\u6267\u884c<br \/>\n    steps {<br \/>\n        sh &#8216;deploy.sh&#8217;<br \/>\n    }<br \/>\n}<br \/>\n<code>`<\/code><\/p>\n<p>&#8212;<\/p>\n<h2>\u603b\u7ed3<\/h2>\n<p>RunnerGo CI\/CD\u96c6\u6210\u8981\u70b9\uff1a<\/p>\n<p>| \u73af\u8282 | \u914d\u7f6e |<br \/>\n|&#8212;&#8212;|&#8212;&#8212;|<br \/>\n| API\u8ba4\u8bc1 | Token\u83b7\u53d6\u548c\u4f7f\u7528 |<br \/>\n| Jenkins | Pipeline\u811a\u672c |<br \/>\n| GitLab CI | YAML\u914d\u7f6e |<br \/>\n| GitHub Actions | Workflow\u914d\u7f6e |<br \/>\n| \u8d28\u91cf\u95e8\u7981 | \u6d4b\u8bd5\u5931\u8d25\u963b\u65ad |<\/p>\n<p>&#8212;<\/p>\n<p><strong>\u672c\u7cfb\u5217\u6587\u7ae0\u5b8c\u7ed3\uff01\u611f\u8c22\u5927\u5bb6\u6301\u7eed\u5173\u6ce8RunnerGo\u535a\u5ba2\u3002<\/strong><\/p>\n<p>&gt; \u4f5c\u8005\uff1aCI\u96c6\u6210\u4e13\u5bb6\uff0c8\u5e74CI\/CD\u5b9e\u8df5\u7ecf\u9a8c\uff0c\u4e13\u6ce8\u6d4b\u8bd5\u81ea\u52a8\u5316\u96c6\u6210<\/p>\n","protected":false},"excerpt":{"rendered":"<p>RunnerGo\u96c6\u6210CI\/CD\u5b9e\u6218 &#8211; \u6d4b\u8bd5\u6d41\u6c34\u7ebf\u81ea\u52a8\u5316\u90e8\u7f72\u6307\u5357 &gt; \u4f5c\u8005\uff1aCI\u96c6\u6210\u4e13\u5bb6 &#8212; \u524d\u8a00 \u5927\u5bb6\u597d\uff0c\u6211\u662fCI\u96c6\u6210\u4e13\u5bb6\u3002\u505aCI\/CD\u5b9e\u8df58\u5e74\uff0c\u6d4b\u8bd5\u96c6\u6210\u662f\u6700\u5173\u952e\u7684\u73af\u8282&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-54","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/blog.runnergo.cn\/index.php?rest_route=\/wp\/v2\/posts\/54","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.runnergo.cn\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.runnergo.cn\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.runnergo.cn\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.runnergo.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=54"}],"version-history":[{"count":0,"href":"https:\/\/blog.runnergo.cn\/index.php?rest_route=\/wp\/v2\/posts\/54\/revisions"}],"wp:attachment":[{"href":"https:\/\/blog.runnergo.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=54"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.runnergo.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=54"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.runnergo.cn\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=54"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}