feat: 接入 Database 三资源 API 与分层绑定协调
确定单库单账号、集群级 Database、资源侧先写绑定和凭据定位合同。领域层承载纯规则,service 协調流程,Kubernetes adapter 负责资源呈现与版本保护。 验证:全量 make test、三轮 race、真实 API server 并发与重启补写、最小 RBAC/watch、lint 和文档检查通过。供应、凭据交付及删除清理尚未实现,保留 DeletionPending/finalizer 边界。
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user