ocdp v1
This commit is contained in:
262
docs/features/ARTIFACT_MEDIATYPE_FILTER.md
Normal file
262
docs/features/ARTIFACT_MEDIATYPE_FILTER.md
Normal file
@ -0,0 +1,262 @@
|
||||
# Artifact MediaType Filter 功能实现
|
||||
|
||||
## 概述
|
||||
|
||||
实现了 artifact registries 的 mediaType 过滤功能,支持后端返回不同类型的制品,前端在部署时只获取 chart 类型的制品。
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 后端功能
|
||||
|
||||
1. **支持的 MediaType 过滤器**:
|
||||
- `all` - 返回所有类型的 artifacts(默认)
|
||||
- `image` - 只返回容器镜像(Docker/OCI)
|
||||
- `chart` - 只返回 Helm Charts
|
||||
- `other` - 返回未识别类型的 artifacts
|
||||
|
||||
2. **模糊匹配机制**:
|
||||
- 使用智能模糊匹配来识别 artifact 类型
|
||||
- 兼容不同版本的 media type 规范
|
||||
- 支持未来的新 media type 格式
|
||||
|
||||
3. **类型识别规则**:
|
||||
- **Helm Chart**:包含 `helm`, `cncf.helm`, `helm.chart`, `chart+json` 等关键词
|
||||
- **Docker Image**:包含 `docker`, `vnd.docker`, `docker.distribution` 等关键词
|
||||
- **OCI Image**:包含 `vnd.oci`, `oci.image`, `opencontainers`, `container.image` 等关键词
|
||||
|
||||
### 前端功能
|
||||
|
||||
1. **API 接口更新**:
|
||||
- `getTags()` 函数现在接受可选的 `mediaType` 参数
|
||||
- 默认获取所有类型(`"all"`)以支持客户端过滤切换
|
||||
|
||||
2. **部署场景**:
|
||||
- 前端在部署场景中只会使用 chart 类型
|
||||
- LaunchModal 组件专门用于部署 Helm Charts
|
||||
|
||||
3. **性能优化**:
|
||||
- 客户端缓存所有类型的 tags
|
||||
- 支持无需重新请求即可切换过滤器
|
||||
- 减少不必要的网络请求
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 后端实现
|
||||
|
||||
#### 1. OpenAPI 规范更新
|
||||
|
||||
```yaml
|
||||
# backend/docs/openapi.yaml
|
||||
parameters:
|
||||
- name: media_type
|
||||
in: query
|
||||
description: Filter artifacts by media type (all, image, chart, other)
|
||||
schema:
|
||||
type: string
|
||||
enum: [all, image, chart, other]
|
||||
default: all
|
||||
```
|
||||
|
||||
#### 2. REST Handler 更新
|
||||
|
||||
```go
|
||||
// backend/internal/adapter/input/http/rest/artifact_handler.go
|
||||
func (h *ArtifactHandler) ListArtifacts(w http.ResponseWriter, r *http.Request) {
|
||||
// 获取 mediaType 过滤参数
|
||||
mediaTypeFilter := r.URL.Query().Get("media_type")
|
||||
if mediaTypeFilter == "" {
|
||||
mediaTypeFilter = "all"
|
||||
}
|
||||
|
||||
artifacts, err := h.artifactService.ListArtifacts(
|
||||
r.Context(),
|
||||
registryID,
|
||||
repositoryName,
|
||||
mediaTypeFilter,
|
||||
)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. Domain Service 更新
|
||||
|
||||
```go
|
||||
// backend/internal/domain/service/artifact_service.go
|
||||
func (s *ArtifactService) ListArtifacts(
|
||||
ctx context.Context,
|
||||
registryID,
|
||||
repository,
|
||||
mediaTypeFilter string,
|
||||
) ([]*entity.Artifact, error) {
|
||||
// ...
|
||||
return s.ociClient.ListArtifacts(ctx, registry, repository, mediaTypeFilter)
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. OCI Client 实现
|
||||
|
||||
```go
|
||||
// backend/internal/adapter/output/oci/real/oci_client.go
|
||||
func (c *OCIClient) shouldIncludeArtifact(artifact *entity.Artifact, filter string) bool {
|
||||
if filter == "" || filter == "all" {
|
||||
return true
|
||||
}
|
||||
|
||||
switch filter {
|
||||
case "chart":
|
||||
return artifact.Type == entity.ArtifactTypeHelm
|
||||
case "image":
|
||||
return artifact.Type == entity.ArtifactTypeDocker ||
|
||||
artifact.Type == entity.ArtifactTypeOCI
|
||||
case "other":
|
||||
return artifact.Type == entity.ArtifactTypeUnknown
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 前端实现
|
||||
|
||||
#### 1. API 调用更新
|
||||
|
||||
```typescript
|
||||
// frontend/src/core/api/artifact.api.ts
|
||||
export async function getTags(
|
||||
registryId: string,
|
||||
repository: string,
|
||||
mediaType: string = "all"
|
||||
): Promise<Tag[]> {
|
||||
// REST mode
|
||||
const url = `/v1/registries/${registryId}/repositories/${encodeURIComponent(repository)}/artifacts?media_type=${mediaType}`;
|
||||
const response = await apiRequest<Tag[]>(url);
|
||||
return response;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 组件更新
|
||||
|
||||
所有调用 `getTags` 的组件都已更新为默认获取 `"all"` 类型:
|
||||
- `RegistryTreeExplorer.tsx` - 主浏览器组件
|
||||
- `RepositoryItem.tsx` - 仓库项组件
|
||||
- `RegistryCard.tsx` - 注册表卡片组件
|
||||
|
||||
## API 使用示例
|
||||
|
||||
### 获取所有类型的 artifacts(默认)
|
||||
|
||||
```bash
|
||||
GET /api/v1/registries/harbor-prod/repositories/charts%2Fvllm-serve/artifacts
|
||||
# 或
|
||||
GET /api/v1/registries/harbor-prod/repositories/charts%2Fvllm-serve/artifacts?media_type=all
|
||||
```
|
||||
|
||||
### 只获取 Helm Charts
|
||||
|
||||
```bash
|
||||
GET /api/v1/registries/harbor-prod/repositories/charts%2Fvllm-serve/artifacts?media_type=chart
|
||||
```
|
||||
|
||||
### 只获取容器镜像
|
||||
|
||||
```bash
|
||||
GET /api/v1/registries/harbor-prod/repositories/library%2Falpine/artifacts?media_type=image
|
||||
```
|
||||
|
||||
### 只获取未识别类型
|
||||
|
||||
```bash
|
||||
GET /api/v1/registries/harbor-prod/repositories/misc%2Fdata/artifacts?media_type=other
|
||||
```
|
||||
|
||||
## 测试场景
|
||||
|
||||
### 场景 1:混合仓库过滤
|
||||
|
||||
**仓库内容**:
|
||||
- `charts/vllm-serve:0.1.0` (Helm Chart)
|
||||
- `charts/vllm-serve:0.2.0` (Helm Chart)
|
||||
- `library/alpine:3.18` (Docker Image)
|
||||
- `library/alpine:latest` (Docker Image)
|
||||
|
||||
**测试**:
|
||||
```bash
|
||||
# 获取所有
|
||||
curl "http://localhost:8080/api/v1/registries/harbor-prod/repositories/charts%2Fvllm-serve/artifacts?media_type=all"
|
||||
# 返回:2 个 charts
|
||||
|
||||
# 只获取 charts
|
||||
curl "http://localhost:8080/api/v1/registries/harbor-prod/repositories/charts%2Fvllm-serve/artifacts?media_type=chart"
|
||||
# 返回:2 个 charts
|
||||
|
||||
# 只获取 images
|
||||
curl "http://localhost:8080/api/v1/registries/harbor-prod/repositories/library%2Falpine/artifacts?media_type=image"
|
||||
# 返回:2 个 images
|
||||
```
|
||||
|
||||
### 场景 2:前端部署流程
|
||||
|
||||
1. 用户浏览 artifact registries
|
||||
2. 前端默认显示 chart 过滤器(但获取所有类型以支持切换)
|
||||
3. 用户点击 "Launch" 按钮部署 Helm Chart
|
||||
4. LaunchModal 只处理 chart 类型的 artifacts
|
||||
|
||||
## 兼容性
|
||||
|
||||
### 向后兼容
|
||||
|
||||
- 不带 `media_type` 参数的请求默认返回所有类型
|
||||
- 前端组件可以正常工作,无需升级
|
||||
- 现有的 API 客户端不受影响
|
||||
|
||||
### 未来扩展
|
||||
|
||||
该实现支持未来添加新的 artifact 类型:
|
||||
1. 在后端 `entity.Artifact.SetType()` 中添加新的识别规则
|
||||
2. 在 `shouldIncludeArtifact()` 中添加新的过滤条件
|
||||
3. 前端自动支持新类型(通过 `type` 字段)
|
||||
|
||||
## 性能考虑
|
||||
|
||||
1. **后端过滤**:
|
||||
- 在 OCI client 层面进行过滤,减少内存使用
|
||||
- 避免传输不需要的数据
|
||||
|
||||
2. **前端缓存**:
|
||||
- 获取所有类型并缓存
|
||||
- 客户端快速切换过滤器
|
||||
- 减少重复的网络请求
|
||||
|
||||
3. **并发控制**:
|
||||
- 批量加载时使用并发限制(3个并发)
|
||||
- 避免过多同时请求
|
||||
|
||||
## 相关文件
|
||||
|
||||
### 后端文件
|
||||
- `backend/docs/openapi.yaml` - API 规范定义
|
||||
- `backend/internal/adapter/input/http/rest/artifact_handler.go` - HTTP handler
|
||||
- `backend/internal/domain/service/artifact_service.go` - 领域服务
|
||||
- `backend/internal/domain/repository/oci_client.go` - OCI 客户端接口
|
||||
- `backend/internal/adapter/output/oci/real/oci_client.go` - 真实 OCI 客户端实现
|
||||
- `backend/internal/adapter/output/oci/mock/oci_client_mock.go` - Mock OCI 客户端实现
|
||||
- `backend/internal/domain/entity/artifact.go` - Artifact 实体(类型识别逻辑)
|
||||
|
||||
### 前端文件
|
||||
- `frontend/src/core/api/artifact.api.ts` - API 客户端
|
||||
- `frontend/src/features/artifact/registries/components/RegistryTreeExplorer.tsx` - 主浏览器
|
||||
- `frontend/src/features/artifact/registries/components/RepositoryItem.tsx` - 仓库项
|
||||
- `frontend/src/features/artifact/registries/components/RegistryCard.tsx` - 注册表卡片
|
||||
- `frontend/src/features/artifact/registries/components/LaunchModal.tsx` - 部署模态框
|
||||
- `frontend/src/features/artifact/registries/utils/artifactType.ts` - 类型工具
|
||||
|
||||
## 总结
|
||||
|
||||
✅ 后端支持返回所有 artifact 制品
|
||||
✅ 支持通过 mediaType 参数过滤(image、chart、other、all)
|
||||
✅ 采用模糊匹配机制,兼容未来版本
|
||||
✅ 前端在部署时专注于 chart 类型
|
||||
✅ 性能优化:客户端缓存 + 服务端过滤
|
||||
✅ 完全向后兼容
|
||||
✅ 易于扩展新的 artifact 类型
|
||||
|
||||
225
docs/features/TESTING_MEDIATYPE_FILTER.md
Normal file
225
docs/features/TESTING_MEDIATYPE_FILTER.md
Normal file
@ -0,0 +1,225 @@
|
||||
# MediaType Filter 功能测试指南
|
||||
|
||||
## 快速测试
|
||||
|
||||
### 1. 启动服务
|
||||
|
||||
```bash
|
||||
# 启动后端(Mock 模式)
|
||||
cd backend
|
||||
make run-mock
|
||||
|
||||
# 启动前端(新终端)
|
||||
cd frontend
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### 2. API 测试
|
||||
|
||||
#### 测试默认行为(返回所有类型)
|
||||
|
||||
```bash
|
||||
curl "http://localhost:8080/api/v1/registries/harbor-bwgdi-prod/repositories/charts%2Fvllm-serve/artifacts"
|
||||
```
|
||||
|
||||
**预期结果**:返回所有 artifacts(Mock 数据中有 2 个 Helm Charts)
|
||||
|
||||
#### 测试 Chart 过滤
|
||||
|
||||
```bash
|
||||
curl "http://localhost:8080/api/v1/registries/harbor-bwgdi-prod/repositories/charts%2Fvllm-serve/artifacts?media_type=chart"
|
||||
```
|
||||
|
||||
**预期结果**:只返回 Helm Charts
|
||||
|
||||
#### 测试 Image 过滤
|
||||
|
||||
```bash
|
||||
curl "http://localhost:8080/api/v1/registries/harbor-bwgdi-prod/repositories/library%2Falpine/artifacts?media_type=image"
|
||||
```
|
||||
|
||||
**预期结果**:只返回 Docker/OCI 镜像(Mock 数据中有 2 个 Alpine 镜像)
|
||||
|
||||
#### 测试混合过滤
|
||||
|
||||
```bash
|
||||
# 测试获取所有类型
|
||||
curl "http://localhost:8080/api/v1/registries/harbor-bwgdi-prod/repositories/charts%2Fvllm-serve/artifacts?media_type=all"
|
||||
|
||||
# 测试只获取 image(应该返回空,因为这个 repo 只有 charts)
|
||||
curl "http://localhost:8080/api/v1/registries/harbor-bwgdi-prod/repositories/charts%2Fvllm-serve/artifacts?media_type=image"
|
||||
|
||||
# 测试只获取 chart(应该返回数据)
|
||||
curl "http://localhost:8080/api/v1/registries/harbor-bwgdi-prod/repositories/charts%2Fvllm-serve/artifacts?media_type=chart"
|
||||
```
|
||||
|
||||
### 3. 前端功能测试
|
||||
|
||||
#### 浏览器测试流程
|
||||
|
||||
1. **打开浏览器**:http://localhost:5173
|
||||
|
||||
2. **登录系统**:
|
||||
- 用户名:`admin`
|
||||
- 密码:`admin123`
|
||||
|
||||
3. **导航到 Artifact Registries**:
|
||||
- 点击左侧菜单的 "Artifact Registries"
|
||||
|
||||
4. **测试类型过滤**:
|
||||
- 观察默认过滤器设置为 "Chart"
|
||||
- 点击不同的过滤器按钮:Chart, Image, Other, All
|
||||
- 验证列表根据类型正确过滤
|
||||
|
||||
5. **测试部署功能**:
|
||||
- 展开一个 registry
|
||||
- 选择一个 chart repository(如 `charts/vllm-serve`)
|
||||
- 点击某个 tag 旁边的 "Launch" 按钮
|
||||
- 验证只有 chart 类型显示 Launch 按钮
|
||||
|
||||
#### 浏览器控制台验证
|
||||
|
||||
打开浏览器开发者工具(F12),在 Console 中查看:
|
||||
|
||||
```
|
||||
[OCI API] Fetching tags for harbor-bwgdi-prod/charts/vllm-serve (mediaType: all)
|
||||
[RESTful OCI] Got 2 tags for charts/vllm-serve (mediaType: all)
|
||||
```
|
||||
|
||||
### 4. Mock 数据说明
|
||||
|
||||
Mock 实现包含以下测试数据:
|
||||
|
||||
#### Helm Charts
|
||||
- `charts/vllm-serve:0.1.0` - ArtifactTypeHelm
|
||||
- `charts/vllm-serve:0.2.0` - ArtifactTypeHelm
|
||||
- `charts/nginx:1.0.0` - ArtifactTypeHelm
|
||||
- `charts/redis:6.2.0` - ArtifactTypeHelm
|
||||
|
||||
#### Docker Images
|
||||
- `library/alpine:3.18` - ArtifactTypeDocker
|
||||
- `library/alpine:latest` - ArtifactTypeDocker
|
||||
|
||||
### 5. 验证检查点
|
||||
|
||||
#### ✅ 后端验证
|
||||
|
||||
- [ ] API 接受 `media_type` 查询参数
|
||||
- [ ] 默认行为(无参数)返回所有类型
|
||||
- [ ] `media_type=chart` 只返回 Helm Charts
|
||||
- [ ] `media_type=image` 只返回容器镜像
|
||||
- [ ] `media_type=other` 只返回未知类型
|
||||
- [ ] `media_type=all` 返回所有类型
|
||||
|
||||
#### ✅ 前端验证
|
||||
|
||||
- [ ] 前端调用 API 时传递 `media_type` 参数
|
||||
- [ ] 默认获取所有类型(支持客户端过滤切换)
|
||||
- [ ] 浏览器界面显示正确的过滤结果
|
||||
- [ ] Launch Modal 只处理 chart 类型
|
||||
- [ ] 无需重新请求即可切换过滤器
|
||||
|
||||
### 6. 性能测试
|
||||
|
||||
#### 测试缓存机制
|
||||
|
||||
1. 打开浏览器开发者工具的 Network 标签
|
||||
2. 展开一个 registry,观察网络请求
|
||||
3. 切换过滤器(Chart → Image → All)
|
||||
4. **验证**:切换过滤器时不应该有新的网络请求(使用缓存)
|
||||
|
||||
#### 测试并发加载
|
||||
|
||||
1. 清除缓存并刷新页面
|
||||
2. 展开一个 registry
|
||||
3. **观察**:repositories 的 tags 按批次加载(每批 3 个并发)
|
||||
4. **验证**:Console 显示加载进度
|
||||
|
||||
### 7. 预期行为
|
||||
|
||||
#### 场景 1:查看 Helm Chart Repository
|
||||
|
||||
```
|
||||
Request: GET /api/v1/registries/harbor-prod/repositories/charts%2Fvllm-serve/artifacts?media_type=chart
|
||||
Response: [
|
||||
{
|
||||
"repositoryName": "charts/vllm-serve",
|
||||
"tag": "0.1.0",
|
||||
"type": "helm",
|
||||
"size": 12345678
|
||||
},
|
||||
{
|
||||
"repositoryName": "charts/vllm-serve",
|
||||
"tag": "0.2.0",
|
||||
"type": "helm",
|
||||
"size": 13456789
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### 场景 2:查看 Docker Image Repository
|
||||
|
||||
```
|
||||
Request: GET /api/v1/registries/harbor-prod/repositories/library%2Falpine/artifacts?media_type=image
|
||||
Response: [
|
||||
{
|
||||
"repositoryName": "library/alpine",
|
||||
"tag": "3.18",
|
||||
"type": "docker",
|
||||
"size": 2345678
|
||||
},
|
||||
{
|
||||
"repositoryName": "library/alpine",
|
||||
"tag": "latest",
|
||||
"type": "docker",
|
||||
"size": 2456789
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### 场景 3:错误的过滤器(应该过滤掉所有)
|
||||
|
||||
```
|
||||
Request: GET /api/v1/registries/harbor-prod/repositories/charts%2Fvllm-serve/artifacts?media_type=image
|
||||
Response: [] # 空数组,因为这个 repo 只有 charts
|
||||
```
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 问题 1:后端返回所有类型而不是过滤后的
|
||||
|
||||
**检查**:
|
||||
```bash
|
||||
# 验证参数是否正确传递
|
||||
curl -v "http://localhost:8080/api/v1/registries/harbor-prod/repositories/charts%2Fvllm-serve/artifacts?media_type=chart" | jq
|
||||
```
|
||||
|
||||
**解决方案**:检查 `artifact_handler.go` 中的参数解析
|
||||
|
||||
### 问题 2:前端过滤器不工作
|
||||
|
||||
**检查**:
|
||||
- 打开浏览器控制台查看错误
|
||||
- 检查网络请求是否包含 `media_type` 参数
|
||||
- 验证 `getTags()` 函数调用是否正确
|
||||
|
||||
### 问题 3:类型识别错误
|
||||
|
||||
**检查**:
|
||||
- 查看 `entity/artifact.go` 中的 `SetType()` 方法
|
||||
- 验证 mediaType 值是否匹配识别规则
|
||||
- 添加日志输出 artifact 的 MediaType 值
|
||||
|
||||
## 完成标志
|
||||
|
||||
当以下所有测试通过时,功能实现完成:
|
||||
|
||||
- ✅ 后端 API 接受并正确处理 `media_type` 参数
|
||||
- ✅ 不同类型的 artifacts 被正确过滤
|
||||
- ✅ 前端可以成功调用带参数的 API
|
||||
- ✅ 前端界面正确显示过滤结果
|
||||
- ✅ 部署功能只处理 chart 类型
|
||||
- ✅ 缓存机制正常工作
|
||||
- ✅ 无 linter 错误
|
||||
- ✅ 向后兼容(不带参数的请求正常工作)
|
||||
|
||||
Reference in New Issue
Block a user