题图摄于西雅图
注:微信公众号不按照时间排序,请关注“亨利笔记”,并加星标以置顶,以免错过更新。
【编者注】本文介绍如何通过 API 与 Harbor 交互,用户可在此基础上开发各类管理工具或者把 Harbor 集成到其他系统中。Harbor API 在开发运维的自动化实践中有重要作用。
衡量一个软件成熟度的标准之一,是看该软件是否提供了丰富和完善的 API,能否方便、灵活地与其他系统集成,满足各种场景的需求。Harbor 提供了完整的RESTful API,以方便用户进行二次开发、系统集成和流程自动化等相关工作。Harbor 的代码实现了用户、项目、扫描、复制、Artifact 等核心管理功能。除此之外,Harbor 也集成了其他开源组件(如 Docker Distribution 等)来完成相应的功能,这些组件的 API 会通过 Harbor 暴露给用户。
根据功能组件的不同,Harbor 提供的 API 主要分为两类:核心管理 API 和 Registry API ,整体结构如下图所示。核心管理 API 的功能基本由 Harbor 项目实现,Registry API 的功能主要由 Docker Distribution 组件提供,通过 Harbor 透传 API 供外部调用。
◎用户管理(“/users”和“/usergroups”):覆盖用户和用户组相关的管理功能,包括用户和用户组的创建、修改、查找、删除等。
◎项目管理(“/projects”):覆盖项目相关的管理功能,包括项目的创建、修改、查找、获取概要、删除和项目元信息的管理等。
◎仓库管理(“/projects/{project_name}/repositories”):覆盖仓库相关的管理功能,包括仓库的修改、查找和删除等。
◎Artifact管理(“/projects/{project_name}/repositories/{repository_name}/artifacts”):覆盖Artifact相关的管理功能,包括Artifact查找、删除、添加;标签移除;附加属性获取;Tag管理等。
◎远程复制(“/replication”和“/registries”):覆盖远程复制相关的功能,包括仓库服务实例管理及远程复制策略的管理、执行等。
◎扫描(“/scanners”、“/projects/{project_id}/scanner”和“/projects/{project_name}/repositories/{repository_name}/artifacts/{reference}/scan”等):覆盖扫描相关的功能,包括扫描器管理、触发扫描和查看扫描结果等。
◎ 垃圾回收(“/system/gc”):覆盖垃圾回收相关的功能,包括触发垃圾回收和查看执行结果等。
◎ 项目配额(“/quotas”):覆盖项目配额相关的功能,包括项目配额的设置、更改和查看等。
◎Tag 保留(“/retentions”):覆盖Artifact保留策略相关的功能,包括保留策略的创建、修改、删除和执行等。
◎Artifact 管理(“/projects/{project_id}/immutabletagrules”):覆盖项目中不可变 Artifact 策略相关的功能,包括不可变策略的创建、修改、删除和执行等。
◎Webhook(“/projects/{project_id}/webhook”):覆盖 Webhook 相关的功能,包括Webhook的创建、修改和删除等。
◎系统配置(“/configurations”和“/systeminfo”):覆盖系统配置和基本信息相关的功能,包括系统配置的查看和修改等。
核心管理 API 符合 OpenAPI 2.0 规范,用户可以参考 GitHub 上 Harbor 官方代码仓库中的 Swagger 文档获取核心管理 API 的详细信息。查看某个特定版本的API 文档时,需要先切换到相应的代码分支,具体位置下表所示。
版 本 | 分 支 | 文档位置 |
---|---|---|
2.0 | release-2.0.0 | /api/v2.0 |
1.10 | release-1.10.0 | /api/harbor |
1.9及之前 | release-1.9.0等 | /docs/swagger.yaml |
也可以在 Harbor 界面中直接使用 API 控制中心功能,通过页面查看、测试和使用API,如下图:
接下说说 API 的使用方法。
Harbor 2.0 引入了 API 版本机制来更好地支持后续API的演进,如果代码的改动无法保证向前兼容,则将会被归入更高版本的 API 中。在一个特定的发行版中,Harbor 只会维护一个版本的API,所以如果用户使用了API,在升级时就要注意 API 的版本是否有所变动。用户可以发送请求 “GET /api/version” 获取所部署的Harbor 支持的API版本:
$ curl https://demo.goharbor.io/api/version
返回结果如下:
{"version":"v2.0"}
可以看到,当前 Harbor API 的版本为 v2.0,那么所有核心管理 API 都以 “/api/v2.0” 为前缀。
核心管理 API 采用 HTTP 进行基本认证(Basic Auth),在基本认证过程中,请求的HTTP头会包含Authorization字段,形式为 “Authorization: Basic <凭证>” ,该凭证是由用户和密码组合而成的,采用了 Base64 编码。
使用 cURL 命令以 Harbor 系统管理员 admin 的用户名和密码调用项目列表 API,代码如下:
$ curl -u admin:xxxxx https://demo.goharbor.io/api/v2.0/projects
返回结果如下:
[
{
"project_id": 1,
"owner_id": 1,
"name": "library",
"creation_time": "2020-04-30T20:46:40.359337Z",
"update_time": "2020-04-30T20:46:40.359337Z",
"deleted": false,
"owner_name": "",
"current_user_role_id": 1,
"current_user_role_ids": [
1
],
"repo_count": 0,
"chart_count": 0,
"metadata": {
"public": "true"
},
"cve_whitelist": {
"id": 0,
"project_id": 0,
"items": null,
"creation_time": "0001-01-01T00:00:00Z",
"update_time": "0001-01-01T00:00:00Z"
}
}
]
在请求 API 时,有可能会因为客户端或者服务器端发生错误而导致请求失败,在这种情况下,一种标准的API错误会被返回,用来说明错误发生的具体原因。
返回的 API 错误的格式是一个数组,数组中的每个元素都代表一个具体的错误信息,每个错误信息都由 HTTP 响应状态码和具体的错误内容两部分构成,而具体的错误内容又包含两个字段:错误码和错误信息。举例来说,当请求 Repository API 获取一个不存在的 Repository 时,请求如下:
$ curl -u admin:xxxxx https://demo.goharbor.io/api/v2.0/projects/library/repositories/hello-world
返回结果如下:
HTTP/1.1 404 Not Found
Server: nginx
Date: Sun, 03 May 2020 04:02:15 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 87
Connection: keep-alive
Set-Cookie: sid=9c31cb12979604d6df71b30536166dde; Path=/; Secure; HttpOnly
X-Request-Id: 544b8371-85f8-42b2-ab0f-7d06e38a681e
{
"errors": [{
"code": "NOT_FOUND",
"message": "repository library/hello-world not found"
}]
}
该响应的状态码为 404,具体的错误内容为
{"errors":[{"code":"NOT_FOUND", "message":"repository library/hello-world not found"}]},
在返回的错误数组(errors[])中只包含一个元素,在该元素中 “NOT_FOUND” 是错误码,“repository library/hello-world not found” 是错误信息。
从 Harbor 2.0 开始,部分 API 引入了对查询关键字“q”的支持,提供了一种通用的方式来过滤查询结果。
目前查询关键字“q”支持5种查询语法。
◎ 精确匹配:key=value。
◎ 模糊匹配:key=~value。在值前增加“~”来表示模糊匹配。
◎ 范围:key=[min~max]。通过指定最小值min与最大值max并以“~”分隔来表示范围,范围包含边界值。如果忽略最大值即key=[min~],则表示查询key大于等于min的所有结果;如果忽略最小值即key=[~max],则表示查询key小于等于max的所有结果。
◎ 或关系的集合:key={value1 value2 value3}。查询key等于所给值中任意一个值的所有结果,多个值之间以空格分隔,如tag={'v1' 'v2' 'v3'}。
◎ 与关系的集合:key=(value1 value2 value3)。查询key同时等于全部所给值的所有结果,多个值之间以空格分隔,如label=('L1' 'L2' 'L3')。
范围和集合的值可以是字符串(使用单引号或者双引号引用)、整数或者时间(时间格式示例如“2020-04-09 02:36:00”)。
在请求API时,所有查询条件都要放在查询关键字“q”中并以逗号分隔,如查询项目ID为1、名称包含“hello”且创建时间不早于2020-04-09 02:36:00的Repository,对应的API请求如下:
$ curl -u admin:xxxxx –globoff https://demo.goharbor.io/api/v2.0/projects/library/repositories?q=project_id=1,name=~hello,creation_time=[2020-04-09%2002:36:00~]
HTTP Basic Auth 的使用方式和核心管理 API 相同,使用 HTTP Basic Auth 认证方式获取 manifest 的 API 的请求如下:
$ curl -u admin:xxxxx https://demo.goharbor.io/v2/library/hello-world/manifests/latest
返回结果如下:
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 1510,
"digest": s"sha256:fce289e99eb9bca977dae136fbe2a82b6b7d4c372474c9235adc1741675f587e"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 977,
"digest": "sha256:1b930d010525941c1d56ec53b97bd057a67ae1865eebf042686d2a2d18271ced"
}
]
}
2.Bearer Token认证
还是以请求 manifest 为例,不带任何认证信息的请求如下:
$ curl -i https://demo.goharbor.io/v2/library/hello-world/manifests/latest
返回结果如下:
HTTP/1.1 401 Unauthorized
…
Www-Authenticate: Bearer realm="https://demo.goharbor.io/service/token",service="harbor-registry",scope="repository:library/hello-world:pull"
{
"errors": [{
"code": "UNAUTHORIZED",
"message": "unauthorized to access repository: library/hello-world, action: pull: unauthorized to access repository: library/hello-world, action: pull"
}]
}
响应状态码为401,在响应头“Www-Authenticate”中包含了认证服务的地址及所需申请的权限。
根据所需的权限(示例中是pull权限)发送获取Token的请求:
$ curl -u admin:xxxxx https://demo.goharbor.io/service/token?service=harbor-registry\&scope=repository:library/hello-world:pull
返回结果如下:
{
"token": "eyJ0eX…",
"access_token": "",
"expires_in": 1800,
"issued_at": "2020-08-04T12:52:28Z"
}
将获取的Token放在请求头部中再次请求 manifest:
$ curl -H "Authorization: Bearer eyJ0eX…" https://demo.goharbor.io/v2/library/hello-world/manifests/latest
返回结果如下:
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 1510,
"digest": "sha256:fce289e99eb9bca977dae136fbe2a82b6b7d4c372474c9235adc1741675f587e"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 977,
"digest": "sha256:1b930d010525941c1d56ec53b97bd057a67ae1865eebf042686d2a2d18271ced"
}
]
}
为了帮助读者理解Harbor 的 API 功能,下面以 Go 语言为例,综合运用 Harbor API 来获取Artifact仓库 “library/hello-world” 下的所有 Artifact,并把最近一次被拉取的时间大于一天的 Artifact 删除。具体实现代码如下(为节省篇幅,该段代码忽略了错误处理部分):
// 定义URL、用户名和密码
url := "https://demo.goharbor.io"
username := "admin"
password := "xxxxx"
// 获取API版本
resp, _ := http.Get(url + "/api/version")
defer resp.Body.Close()
type Version struct {
Version string `json:"version"`
}
encoder := json.NewDecoder(resp.Body)
version := &Version{}
_ = encoder.Decode(version)
// 获取“library/hello-world”下的所有Artifact
req, _ := http.NewRequest(http.MethodGet,
fmt.Sprintf("%s/api/%s/projects/library/repositories/hello-world/artifacts",
url, version.Version), nil)
req.SetBasicAuth(username, password)
resp, _ = http.DefaultClient.Do(req)
defer resp.Body.Close()
type Artifact struct {
Digest string `json:"digest"`
PullTime time.Time `json:"pull_time"`
}
encoder = json.NewDecoder(resp.Body)
artifacts := []*Artifact{}
_ = encoder.Decode(&artifacts)
t := time.Now().Add(-24 * time.Hour)
// 遍历获取到的所有Artifact
for _, artifact := range artifacts {
// 判断Artifact最近一次被拉取的时间是不是一天前
if artifact.PullTime.Before(t) {
// 如果是,则删除Artifact
req, _ := http.NewRequest(http.MethodDelete,
fmt.Sprintf("%s/api/%s/projects/library/repositories/hello-world/artifacts/%s",
url, version.Version, artifact.Digest), nil)
req.SetBasicAuth(username, password)
resp, _ = http.DefaultClient.Do(req)
defer resp.Body.Close()
}
}
更多 API 的使用说明,请参考新书《Harbor权威指南》。
要想了解云原生、区块链和人工智能等技术原理,请立即长按以下二维码,关注本公众号亨利笔记 ( henglibiji ),以免错过更新。