package mock import ( "context" "fmt" "strings" "time" "github.com/ocdp/cluster-service/internal/domain/entity" "github.com/ocdp/cluster-service/internal/domain/repository" ) // OCIClientMock OCI Registry 客户端 Mock 实现 type OCIClientMock struct { // Mock 数据存储 repositories map[string][]string // registryID -> []repositoryName artifacts map[string]map[string][]*entity.Artifact // registryID -> repository -> []artifact } // NewOCIClientMock 创建 Mock 实现 func NewOCIClientMock() repository.OCIClient { mock := &OCIClientMock{ repositories: make(map[string][]string), artifacts: make(map[string]map[string][]*entity.Artifact), } // 初始化一些测试数据 mock.initMockData() return mock } func (c *OCIClientMock) initMockData() { // Note: This method intentionally left empty // Mock data will be generated dynamically per registry to support any registry ID } // initArtifactsForRegistry initializes mock artifacts for a given registry ID func (c *OCIClientMock) initArtifactsForRegistry(registryID string) { c.artifacts[registryID] = make(map[string][]*entity.Artifact) // vllm-serve artifacts (OCI 格式的 Helm Chart) c.artifacts[registryID]["charts/vllm-serve"] = []*entity.Artifact{ { RegistryID: registryID, Repository: "charts/vllm-serve", Tag: "0.1.0", Digest: "sha256:abc123def456", Type: entity.ArtifactTypeChart, Size: 12345678, MediaType: "application/vnd.oci.image.manifest.v1+json", ConfigType: "application/vnd.cncf.helm.config.v1+json", // Helm Chart 的 config type Annotations: map[string]string{ "org.opencontainers.image.title": "vllm-serve", "org.opencontainers.image.version": "0.1.0", }, CreatedAt: time.Now().Add(-24 * time.Hour), }, { RegistryID: registryID, Repository: "charts/vllm-serve", Tag: "0.2.0", Digest: "sha256:xyz789uvw012", Type: entity.ArtifactTypeChart, Size: 13456789, MediaType: "application/vnd.oci.image.manifest.v1+json", ConfigType: "application/vnd.cncf.helm.config.v1+json", // Helm Chart 的 config type Annotations: map[string]string{ "org.opencontainers.image.title": "vllm-serve", "org.opencontainers.image.version": "0.2.0", }, CreatedAt: time.Now(), }, } // nginx artifacts (OCI 格式的 Helm Chart) c.artifacts[registryID]["charts/nginx"] = []*entity.Artifact{ { RegistryID: registryID, Repository: "charts/nginx", Tag: "1.0.0", Digest: "sha256:nginx123456", Type: entity.ArtifactTypeChart, Size: 5678901, MediaType: "application/vnd.oci.image.manifest.v1+json", ConfigType: "application/vnd.cncf.helm.config.v1+json", // Helm Chart 的 config type Annotations: map[string]string{ "org.opencontainers.image.title": "nginx", }, CreatedAt: time.Now().Add(-48 * time.Hour), }, } // redis artifacts (OCI 格式的 Helm Chart) c.artifacts[registryID]["charts/redis"] = []*entity.Artifact{ { RegistryID: registryID, Repository: "charts/redis", Tag: "6.2.0", Digest: "sha256:redis789abc", Type: entity.ArtifactTypeChart, Size: 8901234, MediaType: "application/vnd.oci.image.manifest.v1+json", ConfigType: "application/vnd.cncf.helm.config.v1+json", // Helm Chart 的 config type Annotations: map[string]string{ "org.opencontainers.image.title": "redis", "org.opencontainers.image.version": "6.2.0", }, CreatedAt: time.Now().Add(-72 * time.Hour), }, } // alpine artifacts (Docker Image) c.artifacts[registryID]["library/alpine"] = []*entity.Artifact{ { RegistryID: registryID, Repository: "library/alpine", Tag: "3.18", Digest: "sha256:alpine123", Type: entity.ArtifactTypeImage, Size: 2345678, MediaType: "application/vnd.docker.distribution.manifest.v2+json", ConfigType: "application/vnd.docker.container.image.v1+json", // Docker Image 的 config type Annotations: map[string]string{ "org.opencontainers.image.title": "alpine", "org.opencontainers.image.version": "3.18", }, CreatedAt: time.Now().Add(-96 * time.Hour), }, { RegistryID: registryID, Repository: "library/alpine", Tag: "latest", Digest: "sha256:alpine456", Type: entity.ArtifactTypeImage, Size: 2456789, MediaType: "application/vnd.docker.distribution.manifest.v2+json", ConfigType: "application/vnd.docker.container.image.v1+json", // Docker Image 的 config type Annotations: map[string]string{ "org.opencontainers.image.title": "alpine", }, CreatedAt: time.Now().Add(-24 * time.Hour), }, } } func (c *OCIClientMock) ListRepositories(ctx context.Context, registry *entity.Registry, artifactType string) ([]string, error) { // Check if we have cached data for this registry repos, exists := c.repositories[registry.ID] if !exists { // Generate mock data dynamically for any registry repos = []string{ "charts/vllm-serve", "charts/nginx", "charts/redis", "library/alpine", } c.repositories[registry.ID] = repos // Also initialize artifacts for this registry c.initArtifactsForRegistry(registry.ID) } if strings.EqualFold(strings.TrimSpace(artifactType), "chart") { chartRepos := make([]string, 0) for _, repo := range repos { artifacts, _ := c.ListArtifacts(ctx, registry, repo, "chart") if len(artifacts) > 0 { chartRepos = append(chartRepos, repo) } } return chartRepos, nil } return repos, nil } func (c *OCIClientMock) ListArtifacts(ctx context.Context, registry *entity.Registry, repository, mediaTypeFilter string) ([]*entity.Artifact, error) { regArtifacts, exists := c.artifacts[registry.ID] if !exists { // Initialize artifacts for this registry if not exists c.initArtifactsForRegistry(registry.ID) regArtifacts = c.artifacts[registry.ID] } artifacts, exists := regArtifacts[repository] if !exists { return []*entity.Artifact{}, nil } // 应用 mediaType 过滤 if mediaTypeFilter == "" || mediaTypeFilter == "all" { return artifacts, nil } filtered := make([]*entity.Artifact, 0) filter := strings.ToLower(strings.TrimSpace(mediaTypeFilter)) for _, artifact := range artifacts { switch filter { case "chart": if artifact.Type == entity.ArtifactTypeChart { filtered = append(filtered, artifact) } case "image": if artifact.Type == entity.ArtifactTypeImage { filtered = append(filtered, artifact) } case "other": if artifact.Type == entity.ArtifactTypeOther { filtered = append(filtered, artifact) } } } return filtered, nil } func (c *OCIClientMock) GetArtifact(ctx context.Context, registry *entity.Registry, repository, reference string) (*entity.Artifact, error) { regArtifacts, exists := c.artifacts[registry.ID] if !exists { // Initialize artifacts for this registry if not exists c.initArtifactsForRegistry(registry.ID) regArtifacts = c.artifacts[registry.ID] } artifacts, exists := regArtifacts[repository] if !exists { return nil, entity.ErrArtifactNotFound } // 根据 tag 或 digest 查找 for _, artifact := range artifacts { if artifact.Tag == reference || artifact.Digest == reference { return artifact, nil } } return nil, entity.ErrArtifactNotFound } func (c *OCIClientMock) GetValuesSchema(ctx context.Context, registry *entity.Registry, repository, reference string) (string, error) { artifact, err := c.GetArtifact(ctx, registry, repository, reference) if err != nil { return "", err } if !artifact.IsChart() { return "", fmt.Errorf("not a helm chart") } // 返回 Mock values schema mockSchema := `{ "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "replicaCount": { "type": "integer", "default": 1 }, "image": { "type": "object", "properties": { "repository": { "type": "string" }, "tag": { "type": "string" } } } } }` return mockSchema, nil } func (c *OCIClientMock) GetValuesYAML(ctx context.Context, registry *entity.Registry, repository, reference string) (string, error) { artifact, err := c.GetArtifact(ctx, registry, repository, reference) if err != nil { return "", err } if !artifact.IsChart() { return "", fmt.Errorf("not a helm chart") } return "replicaCount: 1\nimage:\n repository: nginx\n tag: latest\nservice:\n type: ClusterIP\n", nil } func (c *OCIClientMock) PullArtifact(ctx context.Context, registry *entity.Registry, repository, reference, destPath string) error { _, err := c.GetArtifact(ctx, registry, repository, reference) if err != nil { return err } // Mock 实现,不实际下载 return nil } func (c *OCIClientMock) PushArtifact(ctx context.Context, registry *entity.Registry, repository, tag, sourcePath string) error { // Mock 实现,不实际上传 return nil } func (c *OCIClientMock) CheckHealth(ctx context.Context, registry *entity.Registry) error { // Mock 实现,总是返回健康 return nil }