package entity import ( "log" "strings" "time" "unicode" "gopkg.in/yaml.v3" ) // InstanceStatus 实例状态 type InstanceStatus string const ( StatusDeployed InstanceStatus = "deployed" StatusUninstalled InstanceStatus = "uninstalled" StatusSuperseded InstanceStatus = "superseded" StatusFailed InstanceStatus = "failed" StatusPending InstanceStatus = "pending-install" StatusUpgrading InstanceStatus = "pending-upgrade" StatusRollingBack InstanceStatus = "pending-rollback" StatusTerminating InstanceStatus = "pending-delete" StatusUnknown InstanceStatus = "unknown" ) // InstanceOperation 实例操作类型 type InstanceOperation string const ( OperationNone InstanceOperation = "" OperationInstall InstanceOperation = "install" OperationUpgrade InstanceOperation = "upgrade" OperationRollback InstanceOperation = "rollback" OperationDelete InstanceOperation = "delete" OperationSync InstanceOperation = "sync" ) // Instance Helm 应用实例领域实体 type Instance struct { ID string WorkspaceID string // 所属 workspace OwnerID string // 创建者用户 ID ClusterID string RegistryID string ChartReferenceID string // 引用的 Chart 引用 ValuesTemplateID string // 使用的 Values 模板 Name string // Helm Release Name Namespace string Repository string // OCI Repository (e.g., charts/app) Chart string // Chart Name Version string // Chart Version Description string Values map[string]interface{} // Helm Values (JSON) ValuesYAML string // Helm Values (YAML format) UserOverrideYAML string // 用户额外覆盖的配置 Status InstanceStatus StatusReason string LastOperation InstanceOperation LastError string Revision int // Helm Release Revision // 资源使用统计(Helm 安装时从集群获取并更新) CPURequested float64 // CPU 请求量 (cores) MemoryRequested string // 内存请求量 (e.g., "2Gi") GPURequested float64 // GPU 请求量 (cards) GPUMemoryRequested string // GPU 内存请求量 (e.g., "16Gi") CreatedAt time.Time UpdatedAt time.Time } // NewInstance 创建新实例 func NewInstance(workspaceID, ownerID, clusterID, registryID, chartReferenceID, valuesTemplateID, name, namespace, repository, chart, version string) *Instance { now := time.Now() return &Instance{ WorkspaceID: workspaceID, OwnerID: ownerID, ClusterID: clusterID, RegistryID: registryID, ChartReferenceID: chartReferenceID, ValuesTemplateID: valuesTemplateID, Name: name, Namespace: namespace, Repository: repository, Chart: chart, Version: version, Status: StatusPending, StatusReason: "Pending install", LastOperation: OperationInstall, Revision: 1, CPURequested: 0, MemoryRequested: "0Mi", GPURequested: 0, GPUMemoryRequested: "0Mi", CreatedAt: now, UpdatedAt: now, } } // SetValues 设置 Helm Values func (i *Instance) SetValues(values map[string]interface{}) { i.Values = values i.UpdatedAt = time.Now() } // SetValuesYAML 设置 YAML 格式的 Values 并解析到 Values map func (i *Instance) SetValuesYAML(yamlStr string) { i.ValuesYAML = yamlStr if yamlStr == "" { return } // 解析 YAML 到 map,确保 Helm 客户端能正确使用 var parsed map[string]interface{} if err := yaml.Unmarshal([]byte(yamlStr), &parsed); err != nil { log.Printf("[SetValuesYAML] WARNING: failed to parse YAML for instance %s: %s, yaml=%q", i.Name, err, yamlStr) return } if parsed == nil { return } // Merge into existing Values (user-provided takes precedence) if i.Values == nil { i.Values = make(map[string]interface{}) } for k, v := range parsed { // Only set if not already present (Values map takes precedence over YAML fallback) if _, exists := i.Values[k]; !exists { i.Values[k] = v } } i.UpdatedAt = time.Now() } // UpdateStatus 更新实例状态 func (i *Instance) UpdateStatus(status InstanceStatus, revision int) { i.Status = status i.Revision = revision i.UpdatedAt = time.Now() } // BeginOperation 标记开始执行某个操作 func (i *Instance) BeginOperation(op InstanceOperation, reason string) { i.LastOperation = op if pendingStatus := pendingStatusForOperation(op); pendingStatus != "" { i.Status = pendingStatus } if reason != "" { i.StatusReason = reason } i.LastError = "" i.UpdatedAt = time.Now() } // MarkSuccess 标记操作成功 func (i *Instance) MarkSuccess(status InstanceStatus, revision int, reason string) { if status != "" { i.Status = status } if revision > 0 { i.Revision = revision } i.StatusReason = reason i.LastError = "" i.UpdatedAt = time.Now() } // MarkFailure 标记操作失败 func (i *Instance) MarkFailure(reason string, err error) { i.Status = StatusFailed if reason != "" { i.StatusReason = reason } if err != nil { i.LastError = err.Error() } i.UpdatedAt = time.Now() } func pendingStatusForOperation(op InstanceOperation) InstanceStatus { switch op { case OperationInstall: return StatusPending case OperationUpgrade: return StatusUpgrading case OperationRollback: return StatusRollingBack case OperationDelete: return StatusTerminating default: return "" } } // Upgrade 升级实例 func (i *Instance) Upgrade(version string, values map[string]interface{}) { i.Version = version if values != nil { i.Values = values } i.BeginOperation(OperationUpgrade, "Pending upgrade") } // ValidateReleaseName 验证 Helm Release 名称是否符合 RFC 1123 DNS 子域名规范 // Helm release 名称必须: // - 只能包含小写字母(a-z)、数字(0-9)和连字符(-) // - 不能以连字符开头或结尾 // - 长度不超过 53 个字符 func ValidateReleaseName(name string) error { if name == "" { return ErrInvalidInstanceName } // 检查长度(RFC 1123 DNS 子域名最大长度为 63,但 Helm 限制为 53) if len(name) > 53 { return ErrInvalidInstanceName } // 不能以连字符开头或结尾 if strings.HasPrefix(name, "-") || strings.HasSuffix(name, "-") { return ErrInvalidInstanceName } // 只能包含小写字母、数字和连字符 for _, r := range name { if !(unicode.IsLower(r) || unicode.IsDigit(r) || r == '-') { return ErrInvalidInstanceName } } return nil } // Validate 验证实例配置 func (i *Instance) Validate() error { if i.ClusterID == "" { return ErrInvalidClusterID } if err := ValidateReleaseName(i.Name); err != nil { return err } if i.Namespace == "" { return ErrInvalidNamespace } if i.Chart == "" { return ErrInvalidChart } if i.Version == "" { return ErrInvalidVersion } return nil } // ReleaseHistory Helm Release 历史记录 type ReleaseHistory struct { Revision int Updated time.Time Status InstanceStatus Chart string AppVersion string Description string }