Harbor 全局标签管理实战:系统管理员创建、编辑与删除标签的完整流程与源码解析
【免费下载链接】harborAn open source trusted cloud native registry project that stores, signs, and scans content.项目地址: https://gitcode.com/GitHub_Trending/ha/harbor
本篇技术指南围绕 Harbor 测试用例 11-01:系统管理员管理全局级标签 展开,系统讲解系统管理员(System Admin)如何在 Harbor 中完成全局级别标签的创建(Create)、编辑(Update)与删除(Delete)全流程,并结合本仓库的 REST API 定义、Handler 实现、Manager/DAO 分层与数据库迁移脚本,从 UI 操作与源码原理两个层面讲透"标签"这一核心元数据能力。读完本文,你将掌握全局标签的完整 CRUD 操作、REST 调用方式及其底层数据模型与权限控制机制。
标签体系:Level 与 Scope 双维度理解
在深入测试步骤之前,先建立对 Harbor 标签体系的基本认知。从源码 src/pkg/label/model/model.go 可以看到,标签Label模型包含以下关键字段:
| 字段 | 类型 | 说明 |
|---|---|---|
ID | int64 | 标签唯一主键 |
Name | string | 标签名称,最长 128 字符,不能为空 |
Description | string | 标签描述 |
Color | string | 标签颜色,用于 UI 展示 |
Level | string | 标签级别:s(系统级)或u(用户级) |
Scope | string | 标签范围:g(全局)或p(项目) |
ProjectID | int64 | 标签所属项目 ID,全局标签为 0 |
CreationTime/UpdateTime | time.Time | 创建 / 更新时间,由 ORM 自动维护 |
Deleted | bool | 软删除标记 |
其中Level与Scope的取值常量定义在 src/common/const.go:
LabelLevelSystem = "s":系统级标签,由 Harbor 内部逻辑使用;LabelLevelUser = "u":用户级标签,即普通用户通过 UI/API 创建的标签;LabelScopeGlobal = "g":全局标签,对所有项目可见,仅系统管理员可管理;LabelScopeProject = "p":项目标签,隶属于某个具体项目,由项目成员按权限管理。
本文讨论的"全局级别标签"即Scope = "g"且Level = "u"的用户级全局标签,它与项目标签的本质区别在于:全局标签不绑定任何项目(ProjectID恒为 0),创建后全 Harbor 实例内所有项目均可引用。
模型上的Valid()校验方法(见 model.go)强制约束:名称非空且不超过 128 字符;Scope只能是g或p;当Scope = "p"时ProjectID必须大于 0——从侧面印证了全局标签ProjectID必须为 0 这一设计。
测试用例 11-01 的验证目标
测试用例 11-01-system-admin-manage-global-level-labels.md 属于 Harbor 测试用例集中的 Group11-Label 分组,其核心目的是:
验证系统管理员可以对全局级标签执行完整的 CRUD(创建、读取、更新、删除)管理操作。
该用例同时以"用户指南"作为参考文档,并以"必须有一个运行中且可访问的 Harbor 实例"作为环境前提——这意味着整套验证既可以在真实部署环境中手工执行,也可以作为功能回归测试的基准场景。
测试步骤:系统管理员管理全局标签
步骤 1:系统管理员登录 UI
系统管理员(admin 或具备系统管理员角色的用户)登录 Harbor Web 门户。由于全局标签的增删改涉及系统级资源,只有具备相应系统权限的账号才能执行后续操作——这一点在源码的权限校验中会得到印证(详见下文"源码级原理"章节)。
步骤 2:创建全局级标签
登录后,进入标签管理页面,选择"全局级别"(Global)维度,填写标签名称(必填)、描述与颜色后提交,创建全局标签。
对应的 REST 调用为POST /api/v2.0/labels,请求体为LabelJSON 对象,成功时返回201 Created,并在响应头Location中给出新建资源的 URL(见 swagger.yaml)。
一个可复制的curl示例:
curl -k -u "admin:<password>" -H "Content-Type: application/json" \ -X POST "https://<harbor-host>/api/v2.0/labels" \ -d '{ "name": "production-ready", "description": "标识已验证可上生产的镜像", "color": "#00AA00", "scope": "g" }'请求体中scope取值为g,表示创建全局标签。Level字段无需传参——服务端在CreateLabelHandler 中会强制将其置为LabelLevelUser(u),并把ProjectID归零。
步骤 3:编辑已创建的标签
在标签列表中找到步骤 2 创建的标签,点击编辑,修改其名称、描述或颜色后保存。
对应的 REST 调用为PUT /api/v2.0/labels/{label_id}(见 swagger.yaml),请求体仍为LabelJSON 对象:
curl -k -u "admin:<password>" -H "Content-Type: application/json" \ -X PUT "https://<harbor-host>/api/v2.0/labels/1" \ -d '{ "name": "production-ready-v2", "description": "标识已验证可上生产的镜像(v2)", "color": "#0088CC" }'需要注意:编辑操作只允许修改Name、Description与Color三个属性,Scope与ProjectID是不可变更的——这一限制直接由 Handler 实现决定(详见下文)。
步骤 4:删除已创建的标签
在标签列表中选中该标签并执行删除。删除前请注意:若该标签已被若干制品(artifact)引用,Harbor 会先自动解除标签与所有制品之间的引用关系,再删除标签本体。
对应的 REST 调用为DELETE /api/v2.0/labels/{label_id}:
curl -k -u "admin:<password>" \ -X DELETE "https://<harbor-host>/api/v2.0/labels/1"预期结果
测试用例对上述步骤给出了明确的预期:
- 步骤 2:全局标签创建成功;
- 步骤 3:全局标签更新成功;
- 步骤 4:全局标签删除成功。
可能存在的问题
用例标注Possible Problems: None,即该场景在标准部署下无已知阻塞性问题。实操中若遇到 401(未认证/权限不足)、404(标签不存在)或 409(同名标签冲突),均可从下述源码行为中找到根因。
源码级原理:一次标签 CRUD 的完整调用链
围绕测试用例中的三步操作,我们顺着Handler → Manager → DAO → 数据模型/数据库的分层结构,逐一拆解底层实现。
REST Handler 层:权限校验与业务编排
所有标签相关的 REST 端点由 src/server/v2.0/handler/label.go 中的labelAPI处理,内部依赖label.Mgr(标签管理器)与project.Ctl(项目控制器)。
创建(CreateLabel),见 label.go#L51-L73:
label.Level = common.LabelLevelUser if label.Scope == common.LabelScopeGlobal { label.ProjectID = 0 } if err := lAPI.requireAccess(ctx, label, rbac.ActionCreate); err != nil { ... } id, err := lAPI.labelMgr.Create(ctx, label)- 服务端强制把
Level置为u(用户级),防止客户端伪造系统级标签; - 全局标签(
Scope = g)的ProjectID被强制归零; - 在入库前先执行
requireAccess权限校验。
权限校验(requireAccess),见 label.go#L192-L203,是"只有系统管理员能管理全局标签"这一规则的具体落点:
switch label.Scope { case common.LabelScopeGlobal: return lAPI.RequireSystemAccess(ctx, action, rbac.ResourceLabel) case common.LabelScopeProject: return lAPI.RequireProjectAccess(ctx, label.ProjectID, action, subresources...) }即:全局标签的任意操作(Create/Read/Update/Delete)都要求系统级ResourceLabel权限,而项目标签则校验该项目内的标签资源权限。这正是测试用例中"系统管理员"这一角色的权限基础。
更新(UpdateLabel),见 label.go#L140-L171:
label.Name = labelData.Name label.Description = labelData.Description label.Color = labelData.Color if err := label.Valid(); err != nil { ... } if err := lAPI.labelMgr.Update(ctx, label); err != nil { ... }更新时仅将请求体中的Name、Description、Color回写到从数据库读取的原标签对象上,随后重新执行Valid()校验并落库——Scope、ProjectID、Level等字段天然不可被修改。
删除(DeleteLabel),见 label.go#L173-L190:
id := label.ID if err := lAPI.labelMgr.RemoveFromAllArtifacts(ctx, id); err != nil { ... } if err := lAPI.labelMgr.Delete(ctx, id); err != nil { ... }删除是"两步走":先调用RemoveFromAllArtifacts解除该标签与所有制品(artifact)的label_reference引用关系,再调用Delete删除标签记录,避免残留悬挂引用。
列表查询(ListLabels),见 label.go#L91-L138:scope参数必须是g或p(否则返回 400);查询条件固定携带Level = "u"与Scope;当scope = p时必须携带project_id,否则返回 400;名称支持模糊匹配(FuzzyMatchValue)。这意味着通过 API 查询全局标签时,请求形如:
curl -k -u "admin:<password>" \ "https://<harbor-host>/api/v2.0/labels?scope=g"Manager 层:标签与引用的统一管理
src/pkg/label/manager.go 定义Manager接口,覆盖标签本身的生命周期(Create/Get/Count/Update/Delete/List)以及与制品的引用关系(ListByArtifact/AddTo/RemoveFrom/RemoveAllFrom/RemoveFromAllArtifacts)。
其中RemoveFromAllArtifacts(见 manager.go#L131-L138)通过查询条件Keywords: {"LabelID": labelID}批量删除label_reference表中的引用记录,这正是删除标签时"先清引用、再删本体"的实现出处。该包通过全局实例var Mgr = New()(manager.go#L27-L28)供 Handler 层直接注入使用。
DAO 层:ORM 持久化与唯一约束
src/pkg/label/dao/dao.go 是数据访问实现:
Create(dao.go#L76-L88):ormer.Insert(label)插入记录,若触发数据库唯一约束冲突,则包装为"label %s already exists"错误——同名全局标签会被拒绝(409);Update(dao.go#L98-L112):先刷新UpdateTime再更新,同样处理重名冲突;Delete(dao.go#L114-L129):按主键物理删除,删除 0 行时报 404;ListByArtifact(dao.go#L143-L156):通过harbor_label与label_reference联表查询某制品上的全部标签。
数据模型与数据库表结构
标签持久化涉及两张表,均由 ORM 注册(model.go#L26-L29):
harbor_label:标签主表,对应Label模型;label_reference:标签与制品的引用关系表,对应Reference模型(LabelID+ArtifactID联合语义,见 model.go#L67-L74)。
建表 SQL 位于 make/migrations/postgresql/0001_initial_schema.up.sql,关键约束一目了然:
create table harbor_label ( id SERIAL NOT NULL, name varchar(128) NOT NULL, description text, color varchar(16), level char(1) NOT NULL, -- 's' 系统级 / 'u' 用户级 scope char(1) NOT NULL, -- 'g' 全局 / 'p' 项目 project_id int, creation_time timestamp default CURRENT_TIMESTAMP, update_time timestamp default CURRENT_TIMESTAMP, deleted boolean DEFAULT false NOT NULL, PRIMARY KEY(id), CONSTRAINT unique_label UNIQUE (name, scope, project_id) );其中UNIQUE (name, scope, project_id)保证了"同一作用域下标签名唯一":因为全局标签的project_id恒为 0,所以全局标签之间不允许重名——这与 DAO 层将唯一约束冲突包装为"already exists"错误的逻辑完全对应。表上还创建了harbor_label_update_time_at_modtime触发器,在UPDATE时自动刷新update_time。
harbor_resource_label表(见同文件后续定义)以及 2.0 版本迁移中引入的label_reference外键(见 0030_2.0.0_schema.up.sql),共同构成了"标签—资源"关联的演进基础:新版本中以 artifact 为粒度的label_reference替代了早期的资源标签表。
测试用例的价值:从手工验证到自动化回归
Group11-Label 目录下的测试用例(如本文件编号 11-01)是 Harbor 功能验证体系的一部分,与仓库中的 Robot Framework 用例(tests/robot-cases/)及 API 测试(tests/apitests/python/)相互补充。11-01 这类"单步 UI 操作 + 预期结果"的结构非常适合:
- 手工验收:升级 Harbor 版本后快速回归标签管理功能;
- 转化为自动化用例:可直接对照 swagger.yaml 中的
POST /labels、PUT /labels/{label_id}、DELETE /labels/{label_id}三个端点编写脚本化断言,与本文给出的curl示例一一对应; - 作为权限模型的验证锚点:确认系统级
ResourceLabel权限的校验链路(label.go#L192-L203)没有被后续改动破坏。
小结
通过测试用例 11-01,我们验证了 Harbor 系统管理员对全局级标签的完整管理能力:UI 上三步操作即可完成创建、编辑与删除,对应 REST 层为POST /labels、PUT /labels/{label_id}、DELETE /labels/{label_id}。从源码角度,Level(s/u)与Scope(g/p)双维度模型、Handler 层的系统权限强制校验、scope = g时ProjectID强制归零、更新仅允许修改名称/描述/颜色、删除前先解除制品引用等细节,共同构成了全局标签"仅系统管理员可管理、对所有项目可见、全局唯一命名"的行为边界。理解这条Handler → Manager → DAO → 数据库的调用链,不仅有助于排查标签相关的 400/404/409 错误,也能为你基于 Harbor 二次开发或编写自动化回归脚本提供直接依据。
【免费下载链接】harborAn open source trusted cloud native registry project that stores, signs, and scans content.项目地址: https://gitcode.com/GitHub_Trending/ha/harbor
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考