Gitlab CI/CD 在管道之间传递 artifacts/variables
Gitlab CI/CD Pass artifacts/variables between pipelines
tl;博士
我如何传递数据,例如$BUILD_VERSION
变量,在 Gitlab 中不同管道中的作业之间 CI?
所以(在我的例子中)这个:
Pipeline 1 on push ect. Pipeline 2 after merge
`building` job ... `deploying` job
│ ▲
└─────── $BUILD_VERSION ─────────┘
背景
考虑以下示例(完整 yml
下面):
building:
stage: staging
# only on merge requests
rules:
# execute when a merge request is open
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: always
- when: never
script:
- echo "BUILD_VERSION=1.2.3" > build.env
artifacts:
reports:
dotenv: build.env
deploying:
stage: deploy
# after merge request is merged
rules:
# execute when a branch was merged to staging
- if: $CI_COMMIT_BRANCH == $STAGING_BRANCH
when: always
- when: never
dependencies:
- building
script:
- echo $BUILD_VERSION
我有两个阶段,staging 和 deploy。 staging 中的 building
作业构建应用程序并创建“审查应用程序”(为简单起见,没有单独的构建阶段)。 deploy 中的 deploying
作业然后上传新应用程序。
每当打开合并请求时,包含 building
作业 运行 的管道。通过这种方式构建应用程序,开发人员可以单击合并请求中的“审查应用程序”图标。 deploying
作业在合并请求合并后立即变为 运行。思路如下:
*staging* stage (pipeline 1) *deploy* stage (pipeline 2)
<open merge request> -> `building` job (and show) ... <merge> -> `deploying` job
│ ▲
└───────────── $BUILD_VERSION ───────────────┘
我的问题是,staging/building
创建了一些数据,例如一个$BUILD_VERSION
。我想在 deploy/deploying
中有这个 $BUILD_VERSION
,例如通过 Gitlab 创建新版本 API.
所以我的问题是:如何将 $BUILD_VERSION
(和其他数据)从 staging/building
传递到 deploy /deploying
?
到目前为止我尝试了什么
artifacts.reports.dotenv
所描述的案例在 Pass an environment variable to another job 中的 gitlab 文档中处理得更少。下面显示的 yml
文件也深受此示例的启发。还是不行。
build.env
工件是在 building
中创建的,但是每当执行 deploying
作业时,build.env
文件都会被删除,如下面第 15 行所示:“删除 build.env”。我尝试将 build.env
添加到 .gitignore
但它仍然被删除。
经过数小时的搜索,我在 this gitlab issue comment and this Whosebug post 中发现 artifacts.reports.dotenv
不适用于 dependencies
或 needs
关键字。
删除 dependencies
无效。仅使用 needs
也不起作用。不允许同时使用两者。
有谁知道如何让它工作的方法吗?我觉得这就是它应该的工作方式。
将工件作为文件获取
Whosebug post Gitlab ci cd removes artifact for merge requests 的这个答案建议将 build.env
用作普通文件。我也试过这个。 (相关的)yml
如下:
building:
# ...
artifacts:
paths:
- build.env
deploying:
# ...
before_script:
- source build.env
结果同上。 build.env
被删除。然后 source build.env
命令失败,因为 build.env
不存在。 (build.env
是否在 .gitignore
中并不重要,两者都测试过)
从 API
获取工件
我还找到了 Whosebug post Use artifacts from merge request job in GitLab CI 的答案,建议将 API 与 $CI_JOB_TOKEN
一起使用。但是因为我需要非合并请求管道中的工件,所以我不能使用建议的 CI_MERGE_REQUEST_REF_PATH
.
我尝试使用 $CI_COMMIT_REF_NAME
。 yml
的(重要部分)是:
deploying:
# ...
script:
- url=$CI_API_V4_URL/projects/jobs/artifacts/$CI_COMMIT_REF_NAME/download?job=building
- echo "Downloading $url"
- 'curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --output $url'
# ...
但是这个 API 请求被拒绝并显示“404 Not Found”。由于 commit SHAs are not supported、$CI_COMMIT_BEFORE_SHA
或 $CI_COMMIT_SHA
也不起作用。
使用needs
更新:我在 gitlab 文档中找到了 Artifact downloads between pipelines in the same project 部分,这正是我想要的。但是:我无法让它工作。
从文档中复制 more less 后,yml
如下所示:
building:
# ...
artifacts:
paths:
- version
expire_in: never
deploying:
# ...
needs:
- project: $CI_PROJECT_PATH
job: building
ref: staging # building runs on staging branch, main doesn't work either
artifacts: true
现在 deploying
作业立即失败,我收到以下错误提示:
我尝试设置 artifacts.expire_in = never
(如图所示),但我仍然遇到同样的错误。同样在 设置 > CI/CD > Artifacts“保留最近成功作业的工件”被选中。所以神器应该存在。我在这里错过了什么?这应该根据文档工作!
我希望有人能帮助我获得 $BUILD_VERSION
到 deploying
的工作。如果除了我尝试过的方法之外还有其他方法,我很高兴听到它们。提前致谢。
例子.gitlab-ci.yml
:
stages:
- staging
- deploy
building:
tags:
- docker
image: bash
stage: staging
rules:
- if: ($CI_PIPELINE_SOURCE == "merge_request_event") && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "staging"
when: always
- when: never
script:
- echo "BUILD_VERSION=1.2.3" > build.env
artifacts:
reports:
dotenv: build.env
environment:
name: Example
url: https://example.com
deploying:
tags:
- docker
image: bash
stage: deploy
rules:
- if: $CI_COMMIT_BRANCH == "staging"
when: always
- when: never
dependencies:
- building
script:
echo $BUILD_VERSION
这是你可以通过文件传递的东西。
在构建作业中创建新变量:
variables:
CONFIG: "anyname"
然后在脚本中对文件执行 export/copy,例如:
- echo $BUILD_VERSION > $CI_PROJECT_DIR/$CONFIG
在工件中添加路径:
artifacts:
paths:
- $CONFIG
然后在部署作业中
variables:
CONFIG: "anyname"
并获取它
- source $CI_PROJECT_DIR/$CONFIG
为了让它工作,只需尝试解决传递的问题,保持依赖关系并保持工件只使用“需要”,避免在作业中清除工件
您不能使用 CI/CD 在完全不相关的管道之间传递工件。 “building”在定义合并请求的分支上 运行,而“deploying”在合并结果上 运行,这并不意味着“deploying”只是下一阶段.如果中间合并了另一个MR呢?如果有合并冲突怎么办?
换句话说,你不能因为构建了开发分支就跳过主分支的“构建”。让“构建”一直发生,并将“部署”限制在主分支。在此设置中,您可以轻松地将工件从“构建”传递到“部署”。
或者,如果您希望合并事件实际使用版本状态更新主分支,只需使用源代码控制的 VERSION 文件。这就是 git 的用途。合并时,main 将从分支中获取 VERSION。如果另一个分支首先进入,您将必须解决冲突,这是您应该做的。
根据 Gitlab 文档,如果尚未过期,应该可以通过 URL 下载任何作业的工件。
此外,您也可以使用 Gitlab API 从其他项目下载(未过期的)工件;并且您可以使用 Gitlab API 将作业的工件标记为保持不管过期策略,或删除工件。
不过我自己没试过。
对于您的情况,假设 'building' 和 'deploying' 作业都 运行 在 main
b运行ch 上,您有望通过工件像这样。
如果您有其他方法可以在 deploying
作业中找到 运行ch name X building
作业 运行 上的什么,那么您可以下载来自 b运行ch X 的人工制品,而不是像我下面做的那样总是来自 main
。
# Assuming
# domain: example.com
# namespace: mygroup
# project: myproject
building:
# on latest commit on `main`, because we need a predictable
# branch name to save/retrieve the artifact.
stage: staging
script:
- echo "BUILD_VERSION=1.2.3" > build.env
artifacts:
# 'paths', not 'reports'
paths:
- build.env
deploying:
# after merge request is merged
stage: deploy
dependencies:
- building
script:
# could use ${CI_PROJECT_URL} to get https://example.com/mygroup/myproj
- curl https://${CI_SERVER_HOST}/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}/-/jobs/artifacts/main/raw/build.env?job=building > build.env
- source build.env
- echo $BUILD_VERSION # should print '1.2.3'.
文档的相关部分,带有链接和摘录:
Access a branch or tag's latest job artifacts by URL
要浏览或下载 b运行ch 的最新作品,请使用这两个 url 之一。 [我认为 /file/
变体用于 Gitlab Pages 工件,但我不确定。 --埃斯蒂斯]
- 浏览工件:
https://example.com/<namespace>/<project>/-/jobs/artifacts/<ref>/browse?job=<job_name>
- 下载所有工件的 zip:
https://example.com/<namespace>/<project>/-/jobs/artifacts/<ref>/download?job=<job_name>
- 下载一个工件文件:
https://example.com/<namespace>/<project>/-/jobs/artifacts/<ref>/raw/<path/to/file>?job=<job_name>
- 下载一个工件文件(Gitlab Pages 相关?):
https://example.com/<namespace>/<project>/-/jobs/artifacts/<ref>/file/<path>?job=<job_name>
例如,要下载域 gitlab.com
、命名空间 gitlab-org
、项目 gitlab
、最新提交 main
b运行ch 的工件,作业 coverage
,文件路径 review/index.html
:
https://gitlab.com/gitlab-org/gitlab/-/jobs/artifacts/main/raw/review/index.html?job=coverage
Config setting: Keep artifacts from each branch's most recent succesful jobs
- 此选项默认启用
- AFAICT 它保留最近的工件
- 每个活动的 b运行ch 或标签 (a.k.a.a 'ref');
- 如果该引用上有多个管道 运行,则最后一个管道的工件会覆盖早期管道产生的工件。
- 所有其他工件仍受生成它们的
.gitlab-ci.yml
中的 expire_in
设置控制。
Gitlab API for job artifacts
使用Gitlab的好处API是如果你能拿到合适的token,你还可以从其他项目下载artifacts。如果您的脚本在 Gitlab 中 运行ning CI.
,则您需要数字项目 ID——即 $CI_PROJECT_ID
要下载工件档案:
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/${CI_PROJECT_ID}/jobs/artifacts/main/download?job=test"
要下载单个工件文件:
curl --location --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/${CI_PROJECT_ID}/jobs/artifacts/main/raw/some/release/file.pdf?job=pdf"
我假设我们一开始就知道要检索其工件的提交哈希。
这是计划:
commit hash --> job id --> artifact archive --> extract artifact
- Gitlab 的 GraphQL API 可以在 JSON 中获取项目的作业列表 + 每个作业的工件 urls。
- 您可以过滤 JSON 列表以获得您想要的提交 + 作业名。无法直接在 GraphQL 中执行,所以我在 Python.
中执行
- 然后打印作业 ID 或工件存档 url。在我们的例子中,我们直接抓取工件档案 URL;但其他人可能想使用工作 ID 作为其他 API 调用的输入。
首先,让我们只看一下 GraphQL 查询及其结果,以了解可用数据
GraphQL 查询:项目作业和工件
这是获取项目工作列表的查询。您可以将其粘贴到 Gitlab's GraphQL explorer.
中进行试用
query {
# FIXME: your project path goes here
project(fullPath: "gitlab-org/gitlab") {
# First page of jobs. To get the next page, change the head to
# jobs(after: "123_my_endCursor") { ... }
# You can find the endCursor in pageInfo
jobs {
pageInfo {
endCursor
startCursor
}
# No, we can't filter on `nodes(name: "my-job-name")`,
# nor on `edges{ node(name: "my-job-name") }`. :-(
nodes {
id
name
commitPath
artifacts {
edges {
node {
downloadPath
fileType
}
}
}
}
}
}
}
GraphQL 结果
GraphQL API 将 return JSON 如下所示。它包含用于分页的游标名称和作业列表。在此示例中,第一个作业没有工件,第二个作业有。实际上,此列表将包含 100 个职位。
{
"data": {
"project": {
"jobs": {
"pageInfo": {
"endCursor": "eyJpZCI6IjE1NDExMjgwNDAifQ",
"startCursor": "eyJpZCI6IjE1NDExNTY0NzEifQ"
},
"nodes": [
{
"id": "gid://gitlab/Ci::Build/1541156471",
"name": "review-docs-cleanup",
"refName": "refs/merge-requests/67466/merge",
"refPath": "/gitlab-org/gitlab/-/commits/refs/merge-requests/67466/merge",
"commitPath": "/gitlab-org/gitlab/-/commit/5ec616f5e8f3268c23ff06dc52ef098f76352a7f",
"artifacts": {
"edges": []
}
},
{
"id": "gid://gitlab/Ci::Build/1541128174",
"name": "static-analysis 4/4",
"refName": "refs/merge-requests/67509/merge",
"refPath": "/gitlab-org/gitlab/-/commits/refs/merge-requests/67509/merge",
"commitPath": "/gitlab-org/gitlab/-/commit/41f949d3a398968edb67e22526c93c2f5292c23d",
"artifacts": {
"edges": [
{
"node": {
"downloadPath": "/gitlab-org/gitlab/-/jobs/1541128174/artifacts/download?file_type=metadata",
"fileType": "METADATA"
}
},
{
"node": {
"downloadPath": "/gitlab-org/gitlab/-/jobs/1541128174/artifacts/download?file_type=archive",
"fileType": "ARCHIVE"
}
}
]
}
},
]
}
}
}
}
希望在实践中起作用的代码
请注意下面的脚本
- 不处理分页
- 没有从 CI 容器中 运行
- 初始 GraphQL API 请求脚本未经测试
- 下载和解压缩存档的最终命令未经测试
- 只测试了JSON -> 路径部分。那一点肯定有效。
get-jobs-as-json.sh
: (token, project name) --> joblist
#!/bin/sh
# Usage:
#
# GRAPHQL_TOKEN=mysecret get-jobs-as-json.sh gitlab-org/gitlab
#
# You can authorize your request by generating a personal access token to use
# as a bearer token.
# https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html
main() {
# We want curl to see ` \" `, so we type ` \\" ` when we define $QUERY.
# I type : \\"$group_and_project\\"
# QUERY contains: \"asdf/jkl\"
# I type : --data "{\"query\": \"$QUERY\"}
# Curl sees : '{"query": "...\"asdf/jkl\"...}"
group_and_project=""
QUERY="
query {
# Project path goes here
project(fullPath: \\"$group_and_project\\") {
# First page of jobs. To get the next page, change the head to
# jobs(after: \\"123_my_endCursor\\") { ... }
# You can find the endCursor in pageInfo
jobs {
pageInfo {
endCursor
startCursor
}
# No, you can't filter on nodes(name: \\"my-job-name\\"),
# nor on edges{ node(name: \\"my-job-name\\") }.
nodes {
id
name
refName
refPath
commitPath
artifacts {
edges {
node {
downloadPath
fileType
}
}
}
}
}
}
}
"
curl "https://gitlab.com/api/graphql"
--header "Authorization: Bearer $GRAPHQL_TOKEN" \
--header "Content-Type: application/json" \
--request POST \
--data "{\"query\": \"$QUERY\"}"
}
main ""
json2jobinfo.py
: (joblist, job name, commit SHA) --> (slug of) archive url
这是一个 Python 脚本,它将从标准输入读取作业列表 JSON,并打印您指定的作业 + 提交组合的工件存档路径。
#!/usr/bin/python3
# json2jobinfo.py
"""Read JSON from stdin, print archive path of job with correct (jobname, commit) combo.
The commit SHA does not have to be the full hash, just the start is enough.
Usage:
json2jobinfo.py JOBNAME GITHASH
Example:
json2jobinfo.py 'static-analysis 4/4' 41f949
json2jobinfo.py 'static-analysis 4/4' 41f949d3a398968edb67e22526c93c2f5292c23d
"""
import sys, json
from pprint import pprint
from typing import List, Dict, Tuple
def matches_sha(commitPath: str, pattern: str) -> bool:
"""True if this commitPath's commit hash starts with {pattern}"""
commit_part = commitPath.split('/')[-1]
# print(commit_part)
# print(pattern)
return commit_part.startswith(pattern)
def filter_commit_job(jobs: List[Dict], jobname: str, commit: str) -> List[Dict]:
"""Given list of job dicts, find job with correct jobname and commit SHA"""
return [
j for j in jobs
if matches_sha(j['commitPath'], commit)
and j['name'] == jobname
]
def get_archive_url(job: Dict) -> str:
"""Given job dict, return download path of 'ARCHIVE' artifact"""
archive = [
arti for arti in job['artifacts']['edges']
if arti['node']['fileType'] == 'ARCHIVE'
][0]
return archive['node']['downloadPath']
def main_sans_io(graphql_reply: str, jobname: str, commit: str) -> Tuple[str, str]:
"""Return job id, artifact archive download path"""
jobs = json.loads(graphql_reply)['data']['project']['jobs']['nodes']
job = filter_commit_job(jobs, jobname, commit)[0]
job_id = job['id'].split('/')[-1]
archive_url = get_archive_url(job)
return job_id, archive_url
def main(args):
"""Read stdin; look for job with correct jobname and commit; print
download path of artifacts archive"""
if len(args) == 3:
jobname, commit = args[1], args[2]
else:
# hardcoded for example purposes
jobname = 'static-analysis 4/4'
commit = '41f949d3a398968edb67e22526c93c2f5292c23d'
graphql_reply = sys.stdin.read()
job_id, job_archive_url = main_sans_io(graphql_reply, jobname, commit)
print(job_archive_url)
# If you want to see the json, instead:
# pprint(job)
if __name__ == '__main__':
main(sys.argv)
组合用法:
# First, ensure $GRAPHQL_TOKEN contains your personal access token
# Save current directory
oldpwd=$(pwd)
# cd to a temporary directory
cd $(mktemp -d)
zip_path=$( \
./get-jobs-as-json.sh gitlab-org/gitlab \
| ./json2jobinfo.py 'static-analysis 4/4' 41f949 \
)
curl \
--location \
--header "PRIVATE-TOKEN: <your_access_token>" \
$zip_path > archive.zip
unzip archive.zip
# Extract the file we want
cp FILE/YOU/WANT $oldpwd
# Go back to where we were
cd $oldpwd
理想情况下,上面的代码将被折叠成一个单独的 Python 脚本,该脚本在一个地方接受 5 个输入,并产生 1 个输出:(token, API URL, job name, commit sha, artefact path) -> artefact file
。欢迎编辑。现在,我使用了 shell 以及 Python.
另外,理想情况下,有人会尝试上面的代码并留下评论是否可以正常工作。我可能会自己测试一下。但不是今天。
tl;博士
我如何传递数据,例如$BUILD_VERSION
变量,在 Gitlab 中不同管道中的作业之间 CI?
所以(在我的例子中)这个:
Pipeline 1 on push ect. Pipeline 2 after merge
`building` job ... `deploying` job
│ ▲
└─────── $BUILD_VERSION ─────────┘
背景
考虑以下示例(完整 yml
下面):
building:
stage: staging
# only on merge requests
rules:
# execute when a merge request is open
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: always
- when: never
script:
- echo "BUILD_VERSION=1.2.3" > build.env
artifacts:
reports:
dotenv: build.env
deploying:
stage: deploy
# after merge request is merged
rules:
# execute when a branch was merged to staging
- if: $CI_COMMIT_BRANCH == $STAGING_BRANCH
when: always
- when: never
dependencies:
- building
script:
- echo $BUILD_VERSION
我有两个阶段,staging 和 deploy。 staging 中的 building
作业构建应用程序并创建“审查应用程序”(为简单起见,没有单独的构建阶段)。 deploy 中的 deploying
作业然后上传新应用程序。
每当打开合并请求时,包含 building
作业 运行 的管道。通过这种方式构建应用程序,开发人员可以单击合并请求中的“审查应用程序”图标。 deploying
作业在合并请求合并后立即变为 运行。思路如下:
*staging* stage (pipeline 1) *deploy* stage (pipeline 2)
<open merge request> -> `building` job (and show) ... <merge> -> `deploying` job
│ ▲
└───────────── $BUILD_VERSION ───────────────┘
我的问题是,staging/building
创建了一些数据,例如一个$BUILD_VERSION
。我想在 deploy/deploying
中有这个 $BUILD_VERSION
,例如通过 Gitlab 创建新版本 API.
所以我的问题是:如何将 $BUILD_VERSION
(和其他数据)从 staging/building
传递到 deploy /deploying
?
到目前为止我尝试了什么
artifacts.reports.dotenv
所描述的案例在 Pass an environment variable to another job 中的 gitlab 文档中处理得更少。下面显示的 yml
文件也深受此示例的启发。还是不行。
build.env
工件是在 building
中创建的,但是每当执行 deploying
作业时,build.env
文件都会被删除,如下面第 15 行所示:“删除 build.env”。我尝试将 build.env
添加到 .gitignore
但它仍然被删除。
经过数小时的搜索,我在 this gitlab issue comment and this Whosebug post 中发现 artifacts.reports.dotenv
不适用于 dependencies
或 needs
关键字。
删除 dependencies
无效。仅使用 needs
也不起作用。不允许同时使用两者。
有谁知道如何让它工作的方法吗?我觉得这就是它应该的工作方式。
将工件作为文件获取
Whosebug post Gitlab ci cd removes artifact for merge requests 的这个答案建议将 build.env
用作普通文件。我也试过这个。 (相关的)yml
如下:
building:
# ...
artifacts:
paths:
- build.env
deploying:
# ...
before_script:
- source build.env
结果同上。 build.env
被删除。然后 source build.env
命令失败,因为 build.env
不存在。 (build.env
是否在 .gitignore
中并不重要,两者都测试过)
从 API
获取工件我还找到了 Whosebug post Use artifacts from merge request job in GitLab CI 的答案,建议将 API 与 $CI_JOB_TOKEN
一起使用。但是因为我需要非合并请求管道中的工件,所以我不能使用建议的 CI_MERGE_REQUEST_REF_PATH
.
我尝试使用 $CI_COMMIT_REF_NAME
。 yml
的(重要部分)是:
deploying:
# ...
script:
- url=$CI_API_V4_URL/projects/jobs/artifacts/$CI_COMMIT_REF_NAME/download?job=building
- echo "Downloading $url"
- 'curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --output $url'
# ...
但是这个 API 请求被拒绝并显示“404 Not Found”。由于 commit SHAs are not supported、$CI_COMMIT_BEFORE_SHA
或 $CI_COMMIT_SHA
也不起作用。
使用needs
更新:我在 gitlab 文档中找到了 Artifact downloads between pipelines in the same project 部分,这正是我想要的。但是:我无法让它工作。
从文档中复制 more less 后,yml
如下所示:
building:
# ...
artifacts:
paths:
- version
expire_in: never
deploying:
# ...
needs:
- project: $CI_PROJECT_PATH
job: building
ref: staging # building runs on staging branch, main doesn't work either
artifacts: true
现在 deploying
作业立即失败,我收到以下错误提示:
我尝试设置 artifacts.expire_in = never
(如图所示),但我仍然遇到同样的错误。同样在 设置 > CI/CD > Artifacts“保留最近成功作业的工件”被选中。所以神器应该存在。我在这里错过了什么?这应该根据文档工作!
我希望有人能帮助我获得 $BUILD_VERSION
到 deploying
的工作。如果除了我尝试过的方法之外还有其他方法,我很高兴听到它们。提前致谢。
例子.gitlab-ci.yml
:
stages:
- staging
- deploy
building:
tags:
- docker
image: bash
stage: staging
rules:
- if: ($CI_PIPELINE_SOURCE == "merge_request_event") && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "staging"
when: always
- when: never
script:
- echo "BUILD_VERSION=1.2.3" > build.env
artifacts:
reports:
dotenv: build.env
environment:
name: Example
url: https://example.com
deploying:
tags:
- docker
image: bash
stage: deploy
rules:
- if: $CI_COMMIT_BRANCH == "staging"
when: always
- when: never
dependencies:
- building
script:
echo $BUILD_VERSION
这是你可以通过文件传递的东西。
在构建作业中创建新变量:
variables:
CONFIG: "anyname"
然后在脚本中对文件执行 export/copy,例如:
- echo $BUILD_VERSION > $CI_PROJECT_DIR/$CONFIG
在工件中添加路径:
artifacts:
paths:
- $CONFIG
然后在部署作业中
variables:
CONFIG: "anyname"
并获取它
- source $CI_PROJECT_DIR/$CONFIG
为了让它工作,只需尝试解决传递的问题,保持依赖关系并保持工件只使用“需要”,避免在作业中清除工件
您不能使用 CI/CD 在完全不相关的管道之间传递工件。 “building”在定义合并请求的分支上 运行,而“deploying”在合并结果上 运行,这并不意味着“deploying”只是下一阶段.如果中间合并了另一个MR呢?如果有合并冲突怎么办?
换句话说,你不能因为构建了开发分支就跳过主分支的“构建”。让“构建”一直发生,并将“部署”限制在主分支。在此设置中,您可以轻松地将工件从“构建”传递到“部署”。
或者,如果您希望合并事件实际使用版本状态更新主分支,只需使用源代码控制的 VERSION 文件。这就是 git 的用途。合并时,main 将从分支中获取 VERSION。如果另一个分支首先进入,您将必须解决冲突,这是您应该做的。
根据 Gitlab 文档,如果尚未过期,应该可以通过 URL 下载任何作业的工件。
此外,您也可以使用 Gitlab API 从其他项目下载(未过期的)工件;并且您可以使用 Gitlab API 将作业的工件标记为保持不管过期策略,或删除工件。
不过我自己没试过。
对于您的情况,假设 'building' 和 'deploying' 作业都 运行 在 main
b运行ch 上,您有望通过工件像这样。
如果您有其他方法可以在 deploying
作业中找到 运行ch name X building
作业 运行 上的什么,那么您可以下载来自 b运行ch X 的人工制品,而不是像我下面做的那样总是来自 main
。
# Assuming
# domain: example.com
# namespace: mygroup
# project: myproject
building:
# on latest commit on `main`, because we need a predictable
# branch name to save/retrieve the artifact.
stage: staging
script:
- echo "BUILD_VERSION=1.2.3" > build.env
artifacts:
# 'paths', not 'reports'
paths:
- build.env
deploying:
# after merge request is merged
stage: deploy
dependencies:
- building
script:
# could use ${CI_PROJECT_URL} to get https://example.com/mygroup/myproj
- curl https://${CI_SERVER_HOST}/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}/-/jobs/artifacts/main/raw/build.env?job=building > build.env
- source build.env
- echo $BUILD_VERSION # should print '1.2.3'.
文档的相关部分,带有链接和摘录:
Access a branch or tag's latest job artifacts by URL
要浏览或下载 b运行ch 的最新作品,请使用这两个 url 之一。 [我认为 /file/
变体用于 Gitlab Pages 工件,但我不确定。 --埃斯蒂斯]
- 浏览工件:
https://example.com/<namespace>/<project>/-/jobs/artifacts/<ref>/browse?job=<job_name>
- 下载所有工件的 zip:
https://example.com/<namespace>/<project>/-/jobs/artifacts/<ref>/download?job=<job_name>
- 下载一个工件文件:
https://example.com/<namespace>/<project>/-/jobs/artifacts/<ref>/raw/<path/to/file>?job=<job_name>
- 下载一个工件文件(Gitlab Pages 相关?):
https://example.com/<namespace>/<project>/-/jobs/artifacts/<ref>/file/<path>?job=<job_name>
例如,要下载域 gitlab.com
、命名空间 gitlab-org
、项目 gitlab
、最新提交 main
b运行ch 的工件,作业 coverage
,文件路径 review/index.html
:
https://gitlab.com/gitlab-org/gitlab/-/jobs/artifacts/main/raw/review/index.html?job=coverage
Config setting: Keep artifacts from each branch's most recent succesful jobs
- 此选项默认启用
- AFAICT 它保留最近的工件
- 每个活动的 b运行ch 或标签 (a.k.a.a 'ref');
- 如果该引用上有多个管道 运行,则最后一个管道的工件会覆盖早期管道产生的工件。
- 所有其他工件仍受生成它们的
.gitlab-ci.yml
中的expire_in
设置控制。
Gitlab API for job artifacts
使用Gitlab的好处API是如果你能拿到合适的token,你还可以从其他项目下载artifacts。如果您的脚本在 Gitlab 中 运行ning CI.
,则您需要数字项目 ID——即 $CI_PROJECT_ID要下载工件档案:
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/${CI_PROJECT_ID}/jobs/artifacts/main/download?job=test"
要下载单个工件文件:
curl --location --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/${CI_PROJECT_ID}/jobs/artifacts/main/raw/some/release/file.pdf?job=pdf"
我假设我们一开始就知道要检索其工件的提交哈希。
这是计划:
commit hash --> job id --> artifact archive --> extract artifact
- Gitlab 的 GraphQL API 可以在 JSON 中获取项目的作业列表 + 每个作业的工件 urls。
- 您可以过滤 JSON 列表以获得您想要的提交 + 作业名。无法直接在 GraphQL 中执行,所以我在 Python. 中执行
- 然后打印作业 ID 或工件存档 url。在我们的例子中,我们直接抓取工件档案 URL;但其他人可能想使用工作 ID 作为其他 API 调用的输入。
首先,让我们只看一下 GraphQL 查询及其结果,以了解可用数据
GraphQL 查询:项目作业和工件
这是获取项目工作列表的查询。您可以将其粘贴到 Gitlab's GraphQL explorer.
中进行试用query {
# FIXME: your project path goes here
project(fullPath: "gitlab-org/gitlab") {
# First page of jobs. To get the next page, change the head to
# jobs(after: "123_my_endCursor") { ... }
# You can find the endCursor in pageInfo
jobs {
pageInfo {
endCursor
startCursor
}
# No, we can't filter on `nodes(name: "my-job-name")`,
# nor on `edges{ node(name: "my-job-name") }`. :-(
nodes {
id
name
commitPath
artifacts {
edges {
node {
downloadPath
fileType
}
}
}
}
}
}
}
GraphQL 结果
GraphQL API 将 return JSON 如下所示。它包含用于分页的游标名称和作业列表。在此示例中,第一个作业没有工件,第二个作业有。实际上,此列表将包含 100 个职位。
{
"data": {
"project": {
"jobs": {
"pageInfo": {
"endCursor": "eyJpZCI6IjE1NDExMjgwNDAifQ",
"startCursor": "eyJpZCI6IjE1NDExNTY0NzEifQ"
},
"nodes": [
{
"id": "gid://gitlab/Ci::Build/1541156471",
"name": "review-docs-cleanup",
"refName": "refs/merge-requests/67466/merge",
"refPath": "/gitlab-org/gitlab/-/commits/refs/merge-requests/67466/merge",
"commitPath": "/gitlab-org/gitlab/-/commit/5ec616f5e8f3268c23ff06dc52ef098f76352a7f",
"artifacts": {
"edges": []
}
},
{
"id": "gid://gitlab/Ci::Build/1541128174",
"name": "static-analysis 4/4",
"refName": "refs/merge-requests/67509/merge",
"refPath": "/gitlab-org/gitlab/-/commits/refs/merge-requests/67509/merge",
"commitPath": "/gitlab-org/gitlab/-/commit/41f949d3a398968edb67e22526c93c2f5292c23d",
"artifacts": {
"edges": [
{
"node": {
"downloadPath": "/gitlab-org/gitlab/-/jobs/1541128174/artifacts/download?file_type=metadata",
"fileType": "METADATA"
}
},
{
"node": {
"downloadPath": "/gitlab-org/gitlab/-/jobs/1541128174/artifacts/download?file_type=archive",
"fileType": "ARCHIVE"
}
}
]
}
},
]
}
}
}
}
希望在实践中起作用的代码
请注意下面的脚本
- 不处理分页
- 没有从 CI 容器中 运行
- 初始 GraphQL API 请求脚本未经测试
- 下载和解压缩存档的最终命令未经测试
- 只测试了JSON -> 路径部分。那一点肯定有效。
get-jobs-as-json.sh
: (token, project name) --> joblist
#!/bin/sh
# Usage:
#
# GRAPHQL_TOKEN=mysecret get-jobs-as-json.sh gitlab-org/gitlab
#
# You can authorize your request by generating a personal access token to use
# as a bearer token.
# https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html
main() {
# We want curl to see ` \" `, so we type ` \\" ` when we define $QUERY.
# I type : \\"$group_and_project\\"
# QUERY contains: \"asdf/jkl\"
# I type : --data "{\"query\": \"$QUERY\"}
# Curl sees : '{"query": "...\"asdf/jkl\"...}"
group_and_project=""
QUERY="
query {
# Project path goes here
project(fullPath: \\"$group_and_project\\") {
# First page of jobs. To get the next page, change the head to
# jobs(after: \\"123_my_endCursor\\") { ... }
# You can find the endCursor in pageInfo
jobs {
pageInfo {
endCursor
startCursor
}
# No, you can't filter on nodes(name: \\"my-job-name\\"),
# nor on edges{ node(name: \\"my-job-name\\") }.
nodes {
id
name
refName
refPath
commitPath
artifacts {
edges {
node {
downloadPath
fileType
}
}
}
}
}
}
}
"
curl "https://gitlab.com/api/graphql"
--header "Authorization: Bearer $GRAPHQL_TOKEN" \
--header "Content-Type: application/json" \
--request POST \
--data "{\"query\": \"$QUERY\"}"
}
main ""
json2jobinfo.py
: (joblist, job name, commit SHA) --> (slug of) archive url
这是一个 Python 脚本,它将从标准输入读取作业列表 JSON,并打印您指定的作业 + 提交组合的工件存档路径。
#!/usr/bin/python3
# json2jobinfo.py
"""Read JSON from stdin, print archive path of job with correct (jobname, commit) combo.
The commit SHA does not have to be the full hash, just the start is enough.
Usage:
json2jobinfo.py JOBNAME GITHASH
Example:
json2jobinfo.py 'static-analysis 4/4' 41f949
json2jobinfo.py 'static-analysis 4/4' 41f949d3a398968edb67e22526c93c2f5292c23d
"""
import sys, json
from pprint import pprint
from typing import List, Dict, Tuple
def matches_sha(commitPath: str, pattern: str) -> bool:
"""True if this commitPath's commit hash starts with {pattern}"""
commit_part = commitPath.split('/')[-1]
# print(commit_part)
# print(pattern)
return commit_part.startswith(pattern)
def filter_commit_job(jobs: List[Dict], jobname: str, commit: str) -> List[Dict]:
"""Given list of job dicts, find job with correct jobname and commit SHA"""
return [
j for j in jobs
if matches_sha(j['commitPath'], commit)
and j['name'] == jobname
]
def get_archive_url(job: Dict) -> str:
"""Given job dict, return download path of 'ARCHIVE' artifact"""
archive = [
arti for arti in job['artifacts']['edges']
if arti['node']['fileType'] == 'ARCHIVE'
][0]
return archive['node']['downloadPath']
def main_sans_io(graphql_reply: str, jobname: str, commit: str) -> Tuple[str, str]:
"""Return job id, artifact archive download path"""
jobs = json.loads(graphql_reply)['data']['project']['jobs']['nodes']
job = filter_commit_job(jobs, jobname, commit)[0]
job_id = job['id'].split('/')[-1]
archive_url = get_archive_url(job)
return job_id, archive_url
def main(args):
"""Read stdin; look for job with correct jobname and commit; print
download path of artifacts archive"""
if len(args) == 3:
jobname, commit = args[1], args[2]
else:
# hardcoded for example purposes
jobname = 'static-analysis 4/4'
commit = '41f949d3a398968edb67e22526c93c2f5292c23d'
graphql_reply = sys.stdin.read()
job_id, job_archive_url = main_sans_io(graphql_reply, jobname, commit)
print(job_archive_url)
# If you want to see the json, instead:
# pprint(job)
if __name__ == '__main__':
main(sys.argv)
组合用法:
# First, ensure $GRAPHQL_TOKEN contains your personal access token
# Save current directory
oldpwd=$(pwd)
# cd to a temporary directory
cd $(mktemp -d)
zip_path=$( \
./get-jobs-as-json.sh gitlab-org/gitlab \
| ./json2jobinfo.py 'static-analysis 4/4' 41f949 \
)
curl \
--location \
--header "PRIVATE-TOKEN: <your_access_token>" \
$zip_path > archive.zip
unzip archive.zip
# Extract the file we want
cp FILE/YOU/WANT $oldpwd
# Go back to where we were
cd $oldpwd
理想情况下,上面的代码将被折叠成一个单独的 Python 脚本,该脚本在一个地方接受 5 个输入,并产生 1 个输出:(token, API URL, job name, commit sha, artefact path) -> artefact file
。欢迎编辑。现在,我使用了 shell 以及 Python.
另外,理想情况下,有人会尝试上面的代码并留下评论是否可以正常工作。我可能会自己测试一下。但不是今天。