确定单库单账号、集群级 Database、资源侧先写绑定和凭据定位合同。领域层承载纯规则,service 协調流程,Kubernetes adapter 负责资源呈现与版本保护。 验证:全量 make test、三轮 race、真实 API server 并发与重启补写、最小 RBAC/watch、lint 和文档检查通过。供应、凭据交付及删除清理尚未实现,保留 DeletionPending/finalizer 边界。
137 lines
3.8 KiB
Go
137 lines
3.8 KiB
Go
// Package binding 定义资源与申请的纯绑定规则,不访问 Kubernetes 或数据库。
|
||
package binding
|
||
|
||
import (
|
||
"fmt"
|
||
"regexp"
|
||
)
|
||
|
||
const (
|
||
Binding = "Binding"
|
||
Bound = "Bound"
|
||
Deleting = "Deleting"
|
||
Conflict = "Conflict"
|
||
DependencyUnavailable = "DependencyUnavailable"
|
||
)
|
||
|
||
type Identity struct {
|
||
Name string
|
||
UID string
|
||
}
|
||
|
||
type TenantIdentity struct {
|
||
Namespace string
|
||
Name string
|
||
UID string
|
||
}
|
||
|
||
// Request 保留用户输入;Resolve 产生默认值已确定的目标,不修改原请求。
|
||
type Request struct {
|
||
Provision *ProvisionRequest
|
||
ExistingDatabase string
|
||
}
|
||
|
||
type ProvisionRequest struct {
|
||
Instance string
|
||
Database string
|
||
LoginRole string
|
||
}
|
||
|
||
type Target struct {
|
||
Name string
|
||
Provision *ProvisionRequest
|
||
}
|
||
|
||
var identifier = regexp.MustCompile(`^[a-z][a-z0-9_]{0,62}$`)
|
||
|
||
func (r Request) Resolve(tenant TenantIdentity) (Target, error) {
|
||
if (r.Provision == nil) == (r.ExistingDatabase == "") {
|
||
return Target{}, fmt.Errorf("必须且只能选择动态申请或已有 Database")
|
||
}
|
||
if r.Provision == nil {
|
||
return Target{Name: r.ExistingDatabase}, nil
|
||
}
|
||
provision := *r.Provision
|
||
if provision.Database == "" {
|
||
provision.Database = tenant.Name
|
||
}
|
||
if provision.LoginRole == "" {
|
||
provision.LoginRole = tenant.Name
|
||
}
|
||
if !identifier.MatchString(provision.Database) || !identifier.MatchString(provision.LoginRole) {
|
||
return Target{}, fmt.Errorf("动态 database/loginRole 必须符合 PostgreSQL identifier 规则;省略时使用 Tenant 名称")
|
||
}
|
||
return Target{Name: DynamicDatabaseName(tenant.UID), Provision: &provision}, nil
|
||
}
|
||
|
||
func DynamicDatabaseName(tenantUID string) string { return "tenant-" + tenantUID }
|
||
|
||
type Tenant struct {
|
||
Identity TenantIdentity
|
||
Request Request
|
||
Phase string
|
||
Deleting bool
|
||
Database *Identity
|
||
}
|
||
|
||
// Database 是绑定所需的资源事实,不包含存储版本、Conditions 或客户端对象。
|
||
type Database struct {
|
||
Identity Identity
|
||
Instance string
|
||
InstanceUID string
|
||
Name string
|
||
LoginRole string
|
||
Source string
|
||
Tenant *TenantIdentity
|
||
Phase string
|
||
Deleting bool
|
||
Ready bool
|
||
}
|
||
|
||
type Instance struct {
|
||
Identity Identity
|
||
Deleting bool
|
||
Ready bool
|
||
}
|
||
|
||
type Issue struct {
|
||
Reason string
|
||
Message string
|
||
}
|
||
|
||
func (d Database) MatchesProvision(target Target, tenant TenantIdentity) bool {
|
||
return target.Provision != nil && d.Source == "Provision" && d.Instance == target.Provision.Instance &&
|
||
d.Name == target.Provision.Database && d.LoginRole == target.Provision.LoginRole &&
|
||
d.Tenant != nil && *d.Tenant == tenant
|
||
}
|
||
|
||
func (i Instance) Check(database *Database) *Issue {
|
||
if i.Deleting || !i.Ready {
|
||
return &Issue{DependencyUnavailable, "Instance 正在删除或尚无当前版本的 Ready 观察"}
|
||
}
|
||
if database != nil && database.InstanceUID != "" && database.InstanceUID != i.Identity.UID {
|
||
return &Issue{Conflict, "Instance UID 已变化;请核实实例身份,未迁移或接管资源"}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (d Database) CanBind(tenant Tenant) *Issue {
|
||
if tenant.Database != nil && tenant.Database.UID != d.Identity.UID {
|
||
return &Issue{Conflict, fmt.Sprintf("Database %s 的 UID 与已记录绑定不同;请核实同名重建,未接管新对象", d.Identity.Name)}
|
||
}
|
||
if d.Deleting || d.Phase == "Released" || d.Phase == Deleting {
|
||
return &Issue{Conflict, "Database 正在删除或处于 Released;请由管理员核实并处理,未重新分配"}
|
||
}
|
||
if d.Tenant != nil {
|
||
if *d.Tenant != tenant.Identity {
|
||
return &Issue{Conflict, fmt.Sprintf("Database %s 已绑定 Tenant %s/%s(UID %s);未抢占",
|
||
d.Identity.Name, d.Tenant.Namespace, d.Tenant.Name, d.Tenant.UID)}
|
||
}
|
||
return nil
|
||
}
|
||
if d.Phase != "Available" || !d.Ready {
|
||
return &Issue{DependencyUnavailable, "Database 尚未完成验证并进入 Available,等待资源观察"}
|
||
}
|
||
return nil
|
||
}
|