Files
ayatori/internal/database/domain/binding/binding.go
T
panxiao81 7e9e8e828b
Verify / test (pull_request) Successful in 11m39s
Verify / lint (pull_request) Successful in 12m51s
Verify / database-integration (pull_request) Successful in 13m12s
feat: 接入 Database 三资源 API 与分层绑定协调
确定单库单账号、集群级 Database、资源侧先写绑定和凭据定位合同。领域层承载纯规则,service 协調流程,Kubernetes adapter 负责资源呈现与版本保护。

验证:全量 make test、三轮 race、真实 API server 并发与重启补写、最小 RBAC/watch、lint 和文档检查通过。供应、凭据交付及删除清理尚未实现,保留 DeletionPending/finalizer 边界。
2026-09-25 04:09:43 +00:00

137 lines
3.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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
}