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:
Ivan087
2026-05-12 16:15:14 +08:00
parent c5e51ed069
commit 7f238a3168
172 changed files with 15703 additions and 3162 deletions

View File

@ -21,6 +21,7 @@ import (
"k8s.io/client-go/rest"
"k8s.io/client-go/restmapper"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
// HelmClient 真实的 Helm 客户端实现
@ -36,39 +37,45 @@ func NewHelmClient() repository.HelmClient {
}
// getActionConfig 获取 Helm action configuration
func (h *HelmClient) getActionConfig(cluster *entity.Cluster, namespace string) (*action.Configuration, error) {
func (h *HelmClient) getActionConfig(cluster *entity.Cluster, namespace string) (*action.Configuration, func(), error) {
actionConfig := new(action.Configuration)
// 创建临时 kubeconfig 文件
kubeconfigContent := cluster.GetKubeConfig()
tmpDir, err := os.MkdirTemp("", "helm-kubeconfig-*")
if err != nil {
return nil, fmt.Errorf("failed to create temp dir: %w", err)
return nil, nil, fmt.Errorf("failed to create temp dir: %w", err)
}
cleanup := func() {
_ = os.RemoveAll(tmpDir)
}
kubeconfigPath := filepath.Join(tmpDir, "kubeconfig")
if err := os.WriteFile(kubeconfigPath, []byte(kubeconfigContent), 0600); err != nil {
return nil, fmt.Errorf("failed to write kubeconfig: %w", err)
cleanup()
return nil, nil, fmt.Errorf("failed to write kubeconfig: %w", err)
}
// 使用 kubeconfig 初始化 action config
if err := actionConfig.Init(
&kubeconfigGetter{kubeconfigPath: kubeconfigPath},
&kubeconfigGetter{kubeconfigPath: kubeconfigPath, namespace: namespace},
namespace,
os.Getenv("HELM_DRIVER"), // storage driver: configmap, secret, memory
func(format string, v ...interface{}) {
// Log function
},
); err != nil {
return nil, fmt.Errorf("failed to initialize action config: %w", err)
cleanup()
return nil, nil, fmt.Errorf("failed to initialize action config: %w", err)
}
return actionConfig, nil
return actionConfig, cleanup, nil
}
// kubeconfigGetter implements RESTClientGetter
type kubeconfigGetter struct {
kubeconfigPath string
namespace string
}
func (k *kubeconfigGetter) ToRESTConfig() (*rest.Config, error) {
@ -95,25 +102,30 @@ func (k *kubeconfigGetter) ToRESTMapper() (meta.RESTMapper, error) {
}
func (k *kubeconfigGetter) ToRawKubeConfigLoader() clientcmd.ClientConfig {
overrides := &clientcmd.ConfigOverrides{}
if k.namespace != "" {
overrides.Context = clientcmdapi.Context{Namespace: k.namespace}
}
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
&clientcmd.ClientConfigLoadingRules{ExplicitPath: k.kubeconfigPath},
&clientcmd.ConfigOverrides{},
overrides,
)
}
// Install 安装 Helm Chart
func (h *HelmClient) Install(ctx context.Context, cluster *entity.Cluster, instance *entity.Instance) error {
actionConfig, err := h.getActionConfig(cluster, instance.Namespace)
actionConfig, cleanup, err := h.getActionConfig(cluster, instance.Namespace)
if err != nil {
return err
}
defer cleanup()
install := action.NewInstall(actionConfig)
install.ReleaseName = instance.Name
install.Namespace = instance.Namespace
install.CreateNamespace = true
install.Wait = true
install.Timeout = 5 * time.Minute
install.Timeout = helmOperationTimeout()
// 加载 Chart从本地路径或 OCI registry
// 这里简化处理,假设 chart 已经被拉取到本地
@ -139,15 +151,16 @@ func (h *HelmClient) Install(ctx context.Context, cluster *entity.Cluster, insta
// Upgrade 升级 Helm Release
func (h *HelmClient) Upgrade(ctx context.Context, cluster *entity.Cluster, instance *entity.Instance) error {
actionConfig, err := h.getActionConfig(cluster, instance.Namespace)
actionConfig, cleanup, err := h.getActionConfig(cluster, instance.Namespace)
if err != nil {
return err
}
defer cleanup()
upgrade := action.NewUpgrade(actionConfig)
upgrade.Namespace = instance.Namespace
upgrade.Wait = true
upgrade.Timeout = 5 * time.Minute
upgrade.Timeout = helmOperationTimeout()
// 加载 Chart
chartPath := fmt.Sprintf("/tmp/charts/%s-%s.tgz", instance.Chart, instance.Version)
@ -172,14 +185,15 @@ func (h *HelmClient) Upgrade(ctx context.Context, cluster *entity.Cluster, insta
// Uninstall 卸载 Helm Release
func (h *HelmClient) Uninstall(ctx context.Context, cluster *entity.Cluster, releaseName, namespace string) error {
actionConfig, err := h.getActionConfig(cluster, namespace)
actionConfig, cleanup, err := h.getActionConfig(cluster, namespace)
if err != nil {
return err
}
defer cleanup()
uninstall := action.NewUninstall(actionConfig)
uninstall.Wait = true
uninstall.Timeout = 5 * time.Minute
uninstall.Timeout = helmOperationTimeout()
_, err = uninstall.Run(releaseName)
if err != nil {
@ -194,15 +208,16 @@ func (h *HelmClient) Uninstall(ctx context.Context, cluster *entity.Cluster, rel
// Rollback 回滚 Helm Release
func (h *HelmClient) Rollback(ctx context.Context, cluster *entity.Cluster, releaseName, namespace string, revision int) error {
actionConfig, err := h.getActionConfig(cluster, namespace)
actionConfig, cleanup, err := h.getActionConfig(cluster, namespace)
if err != nil {
return err
}
defer cleanup()
rollback := action.NewRollback(actionConfig)
rollback.Version = revision
rollback.Wait = true
rollback.Timeout = 5 * time.Minute
rollback.Timeout = helmOperationTimeout()
if err := rollback.Run(releaseName); err != nil {
return fmt.Errorf("failed to rollback release: %w", err)
@ -211,12 +226,25 @@ func (h *HelmClient) Rollback(ctx context.Context, cluster *entity.Cluster, rele
return nil
}
func helmOperationTimeout() time.Duration {
raw := os.Getenv("HELM_OPERATION_TIMEOUT")
if raw == "" {
return 15 * time.Minute
}
timeout, err := time.ParseDuration(raw)
if err != nil || timeout <= 0 {
return 15 * time.Minute
}
return timeout
}
// GetStatus 获取 Release 状态
func (h *HelmClient) GetStatus(ctx context.Context, cluster *entity.Cluster, releaseName, namespace string) (*entity.Instance, error) {
actionConfig, err := h.getActionConfig(cluster, namespace)
actionConfig, cleanup, err := h.getActionConfig(cluster, namespace)
if err != nil {
return nil, err
}
defer cleanup()
status := action.NewStatus(actionConfig)
rel, err := status.Run(releaseName)
@ -229,10 +257,11 @@ func (h *HelmClient) GetStatus(ctx context.Context, cluster *entity.Cluster, rel
// GetHistory 获取 Release 历史
func (h *HelmClient) GetHistory(ctx context.Context, cluster *entity.Cluster, releaseName, namespace string) ([]*entity.ReleaseHistory, error) {
actionConfig, err := h.getActionConfig(cluster, namespace)
actionConfig, cleanup, err := h.getActionConfig(cluster, namespace)
if err != nil {
return nil, err
}
defer cleanup()
history := action.NewHistory(actionConfig)
history.Max = 256
@ -259,10 +288,11 @@ func (h *HelmClient) GetHistory(ctx context.Context, cluster *entity.Cluster, re
// List 列出集群中的所有 Releases
func (h *HelmClient) List(ctx context.Context, cluster *entity.Cluster, namespace string) ([]*entity.Instance, error) {
actionConfig, err := h.getActionConfig(cluster, namespace)
actionConfig, cleanup, err := h.getActionConfig(cluster, namespace)
if err != nil {
return nil, err
}
defer cleanup()
list := action.NewList(actionConfig)
if namespace == "" {
@ -284,10 +314,11 @@ func (h *HelmClient) List(ctx context.Context, cluster *entity.Cluster, namespac
// GetValues 获取 Release 的 values
func (h *HelmClient) GetValues(ctx context.Context, cluster *entity.Cluster, releaseName, namespace string) (map[string]interface{}, error) {
actionConfig, err := h.getActionConfig(cluster, namespace)
actionConfig, cleanup, err := h.getActionConfig(cluster, namespace)
if err != nil {
return nil, err
}
defer cleanup()
getValues := action.NewGetValues(actionConfig)
values, err := getValues.Run(releaseName)