130 lines
4.0 KiB
Go
130 lines
4.0 KiB
Go
package credential
|
|
|
|
import (
|
|
"git.ddupan.top/panxiao81/ayatori/internal/database/domain/binding"
|
|
"git.ddupan.top/panxiao81/ayatori/internal/database/domain/instance"
|
|
)
|
|
|
|
type Location struct {
|
|
Mount string
|
|
Path string
|
|
}
|
|
|
|
// Phase 表达凭据准备进度,不依赖 Kubernetes Condition 的类型或 Reason。
|
|
type Phase uint8
|
|
|
|
const (
|
|
Pending Phase = iota
|
|
Pinned
|
|
Creating
|
|
Prepared
|
|
Conflict
|
|
Unavailable
|
|
Stopped
|
|
InvalidTarget
|
|
)
|
|
|
|
type State struct {
|
|
Location *Location
|
|
Version int64
|
|
Phase Phase
|
|
Message string
|
|
}
|
|
|
|
func (s State) WithPhase(phase Phase, message string) State {
|
|
s.Phase, s.Message = phase, message
|
|
return s
|
|
}
|
|
|
|
func (s State) Confirmed() bool { return s.Version > 0 }
|
|
|
|
// Created 只接受首次创建并回读得到的版本,不能把后续写入认作首次供应。
|
|
func (s State) Created(version int64) (State, *Issue) {
|
|
if version != 1 {
|
|
return s, &Issue{Conflict, "凭据创建冲突或结果不确定;请核对固定位置的版本历史,未认领、覆盖或重新生成密码"}
|
|
}
|
|
s.Version = version
|
|
return s.WithPhase(Prepared, "凭据已创建并回读确认;尚未创建 PostgreSQL 资源或交付给 Tenant"), nil
|
|
}
|
|
|
|
// Resume 决定新一轮是否可以继续。未确认的创建不能靠读取成功认领。
|
|
func (s State) Resume() (State, bool) {
|
|
if s.Version != 0 {
|
|
return s, true
|
|
}
|
|
switch s.Phase {
|
|
case Conflict:
|
|
return s, false
|
|
case Creating:
|
|
return s.WithPhase(Conflict, "凭据创建未留下成功确认;请核对固定位置与后端历史并人工处理,未重新生成密码"), false
|
|
default:
|
|
return s, true
|
|
}
|
|
}
|
|
|
|
func (s State) CheckLocation(configured Location) *Issue {
|
|
if s.Location != nil && *s.Location != configured {
|
|
return &Issue{Unavailable, "部署配置与固定凭据位置不一致;请恢复原 mount/path 配置,未迁移或改密"}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type Instance struct {
|
|
binding.Instance
|
|
Endpoint instance.Endpoint
|
|
}
|
|
|
|
// Target 只包含供应资格所需事实,不含 resourceVersion、Conditions 或 repository 对象。
|
|
type Target struct {
|
|
Database binding.Database
|
|
Tenant *binding.Tenant
|
|
Instance *Instance
|
|
DatabaseProtected bool
|
|
TenantProtected bool
|
|
}
|
|
|
|
type Issue struct {
|
|
Phase Phase
|
|
Message string
|
|
}
|
|
|
|
func (t Target) RequiresPreparation() bool { return t.Database.Source == "Provision" }
|
|
|
|
func (t Target) CheckCredential(value ApplicationCredential) *Issue {
|
|
if t.Instance == nil || !value.MatchesTarget(t.Database.LoginRole, t.Database.Name, t.Instance.Endpoint) {
|
|
return &Issue{Conflict, "已确认凭据与当前 Instance/database/loginRole 不一致;请人工核实,未修改凭据"}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t Target) Check() *Issue {
|
|
database := t.Database
|
|
if database.Deleting || database.Phase == binding.Deleting || database.Phase == "Released" {
|
|
return &Issue{Stopped, "Database 正在删除或已释放;保留凭据与 finalizer,不执行供应或清理"}
|
|
}
|
|
if database.Tenant == nil || t.Tenant == nil || t.Tenant.Database == nil {
|
|
return &Issue{Unavailable, "等待 Database 与 Tenant 双向绑定完成"}
|
|
}
|
|
if *database.Tenant != t.Tenant.Identity || *t.Tenant.Database != database.Identity {
|
|
return &Issue{Conflict, "双向绑定的名称或 UID 不匹配,未创建凭据"}
|
|
}
|
|
if t.Tenant.Deleting || t.Tenant.Phase != binding.Bound || !t.DatabaseProtected || !t.TenantProtected {
|
|
return &Issue{Stopped, "Tenant 未完成绑定、正在删除或缺少 finalizer 保护,未创建凭据"}
|
|
}
|
|
request, err := t.Tenant.Request.Resolve(t.Tenant.Identity)
|
|
if err != nil || (request.Provision != nil && !database.MatchesProvision(request, t.Tenant.Identity)) || request.Name != database.Identity.Name {
|
|
return &Issue{Conflict, "Tenant 申请与 Database 目标不一致,未创建凭据"}
|
|
}
|
|
if t.Instance == nil || database.InstanceUID == "" {
|
|
return &Issue{Unavailable, "等待 Instance 与已记录的实例身份"}
|
|
}
|
|
if issue := t.Instance.Check(&database); issue != nil {
|
|
phase := Unavailable
|
|
if issue.Reason == binding.Conflict {
|
|
phase = Conflict
|
|
}
|
|
return &Issue{phase, issue.Message}
|
|
}
|
|
return nil
|
|
}
|