refactor: full-stack restructure with multi-tenancy, workspace management, and K8s diagnostics
- Add Workspace domain (entity, repository, service, handler, DTO) - Add multi-tenant K8s client with tenant binding and quota management - Add K8s diagnostics client (instance diagnostics) - Add authorization middleware (authz package) - Restructure frontend to feature-based architecture (features/) - Add User Management page in configuration - Add AccessDenied page and route guards - Refactor shared components (form inputs, layout, UI) - Update Tailwind config for new design system - Add comprehensive documentation (docs/, tasks/, plans) - Improve cluster service with better kubeconfig handling - Add tests for crypto, config, helm client, tenant binding
This commit is contained in:
@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
||||
"github.com/ocdp/cluster-service/internal/domain/entity"
|
||||
"github.com/ocdp/cluster-service/internal/domain/repository"
|
||||
)
|
||||
@ -13,7 +13,7 @@ import (
|
||||
// OCIClientMock OCI Registry 客户端 Mock 实现
|
||||
type OCIClientMock struct {
|
||||
// Mock 数据存储
|
||||
repositories map[string][]string // registryID -> []repositoryName
|
||||
repositories map[string][]string // registryID -> []repositoryName
|
||||
artifacts map[string]map[string][]*entity.Artifact // registryID -> repository -> []artifact
|
||||
}
|
||||
|
||||
@ -23,10 +23,10 @@ func NewOCIClientMock() repository.OCIClient {
|
||||
repositories: make(map[string][]string),
|
||||
artifacts: make(map[string]map[string][]*entity.Artifact),
|
||||
}
|
||||
|
||||
|
||||
// 初始化一些测试数据
|
||||
mock.initMockData()
|
||||
|
||||
|
||||
return mock
|
||||
}
|
||||
|
||||
@ -38,18 +38,18 @@ func (c *OCIClientMock) initMockData() {
|
||||
// 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
|
||||
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",
|
||||
@ -57,14 +57,14 @@ func (c *OCIClientMock) initArtifactsForRegistry(registryID string) {
|
||||
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
|
||||
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",
|
||||
@ -72,36 +72,36 @@ func (c *OCIClientMock) initArtifactsForRegistry(registryID string) {
|
||||
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
|
||||
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
|
||||
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",
|
||||
@ -109,18 +109,18 @@ func (c *OCIClientMock) initArtifactsForRegistry(registryID string) {
|
||||
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
|
||||
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",
|
||||
@ -128,14 +128,14 @@ func (c *OCIClientMock) initArtifactsForRegistry(registryID string) {
|
||||
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
|
||||
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",
|
||||
},
|
||||
@ -144,7 +144,7 @@ func (c *OCIClientMock) initArtifactsForRegistry(registryID string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *OCIClientMock) ListRepositories(ctx context.Context, registry *entity.Registry) ([]string, error) {
|
||||
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 {
|
||||
@ -156,10 +156,20 @@ func (c *OCIClientMock) ListRepositories(ctx context.Context, registry *entity.R
|
||||
"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
|
||||
}
|
||||
|
||||
@ -170,20 +180,20 @@ func (c *OCIClientMock) ListArtifacts(ctx context.Context, registry *entity.Regi
|
||||
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":
|
||||
@ -200,7 +210,7 @@ func (c *OCIClientMock) ListArtifacts(ctx context.Context, registry *entity.Regi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
@ -211,19 +221,19 @@ func (c *OCIClientMock) GetArtifact(ctx context.Context, registry *entity.Regist
|
||||
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
|
||||
}
|
||||
|
||||
@ -232,11 +242,11 @@ func (c *OCIClientMock) GetValuesSchema(ctx context.Context, registry *entity.Re
|
||||
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#",
|
||||
@ -262,12 +272,23 @@ func (c *OCIClientMock) GetValuesSchema(ctx context.Context, registry *entity.Re
|
||||
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
|
||||
}
|
||||
@ -281,4 +302,3 @@ func (c *OCIClientMock) CheckHealth(ctx context.Context, registry *entity.Regist
|
||||
// Mock 实现,总是返回健康
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user