refactor: 集中显式注入并归位凭据领域规则
Verify / test (pull_request) Successful in 13m29s
Verify / lint (pull_request) Successful in 14m13s
Verify / database-integration (pull_request) Successful in 15m48s

This commit is contained in:
2026-09-27 19:16:48 +00:00
parent 72ce3eda40
commit 7834cab97f
34 changed files with 612 additions and 259 deletions
@@ -5,8 +5,7 @@ import (
"errors"
"fmt"
"git.ddupan.top/panxiao81/ayatori/internal/database/domain/binding"
"git.ddupan.top/panxiao81/ayatori/internal/database/domain/instance"
credentialdomain "git.ddupan.top/panxiao81/ayatori/internal/database/domain/credential"
)
var (
@@ -17,54 +16,28 @@ var (
ErrCredentialUncertain = errors.New("credential creation outcome is uncertain; manual resolution required")
)
const (
CredentialsReady = "CredentialsReady"
CredentialCreationStarted = "CreationStarted"
CredentialPrepared = "CredentialPrepared"
)
type CredentialLocation struct {
Mount string
Path string
}
// CredentialStore 只表达本用例需要的凭据操作,不提供覆盖或删除。
// version=0 的读取只用于观察是否已有值,成功不能作为认领依据。
type CredentialStore interface {
ProvisionLocation(string) (CredentialLocation, error)
ReadCredential(context.Context, CredentialLocation, int64) (ApplicationCredential, error)
CreateCredential(context.Context, CredentialLocation, ApplicationCredential) (int64, error)
}
type CredentialInstance struct {
binding.Instance
Generation int64
Endpoint instance.Endpoint
ProvisionLocation(string) (credentialdomain.Location, error)
ReadCredential(context.Context, credentialdomain.Location, int64) (credentialdomain.ApplicationCredential, error)
CreateCredential(context.Context, credentialdomain.Location, credentialdomain.ApplicationCredential) (int64, error)
}
// CredentialRecord 是同一轮观察的事实,状态中永远不保存密码。
type CredentialRecord struct {
Database BindingDatabase
Tenant *BindingTenant
Instance *CredentialInstance
DatabaseProtected bool
TenantProtected bool
Status CredentialStatus
}
type CredentialStatus struct {
Location *CredentialLocation
Version int64
Ready bool
Reason string
Message string
credentialdomain.Target
Revision string
TenantGeneration int64
InstanceGeneration int64
Status credentialdomain.State
}
// CredentialResources 的写入必须检查 Database UID/resourceVersion,保留其他状态。
// CheckCurrent 在外部操作前后回读本轮三个资源,拒绝陈旧快照;它不是跨系统事务。
type CredentialResources interface {
Load(context.Context, string) (*CredentialRecord, error)
Save(context.Context, *CredentialRecord, CredentialStatus) (*CredentialRecord, error)
Save(context.Context, *CredentialRecord, credentialdomain.State) (*CredentialRecord, error)
CheckCurrent(context.Context, *CredentialRecord) error
}
@@ -75,37 +48,35 @@ type CredentialPreparation struct {
func (s CredentialPreparation) Reconcile(ctx context.Context, name string) error {
record, err := s.Resources.Load(ctx, name)
if err != nil || record == nil || record.Database.Source != "Provision" {
if err != nil || record == nil || !record.RequiresPreparation() {
return err
}
// 未完成创建的重入不猜测后端结果。即使进程在实际发请求前退出,也需要人工核实。
if record.Status.Version == 0 && record.Status.Reason == binding.Conflict {
return nil // 保留首次冲突的具体原因,不因后端恢复而重入创建。
if state, canContinue := record.Status.Resume(); !canContinue {
if state == record.Status {
return nil
}
return s.report(ctx, record, state.Phase, state.Message)
}
if record.Status.Version == 0 && record.Status.Reason == CredentialCreationStarted {
return s.report(ctx, record, binding.Conflict,
"凭据创建未留下成功确认;请核对固定位置与后端历史并人工处理,未重新生成密码")
}
if issue := record.check(); issue != nil {
return s.report(ctx, record, issue.Reason, issue.Message)
if issue := record.Check(); issue != nil {
return s.report(ctx, record, issue.Phase, issue.Message)
}
location, err := s.Store.ProvisionLocation(record.Database.Identity.UID)
if err != nil {
return s.report(ctx, record, binding.DependencyUnavailable, "凭据存储位置配置无效,未执行外部写入")
return s.report(ctx, record, credentialdomain.Unavailable, "凭据存储位置配置无效,未执行外部写入")
}
if issue := record.Status.CheckLocation(location); issue != nil {
return s.report(ctx, record, issue.Phase, issue.Message)
}
if record.Status.Location == nil {
status := record.Status
status.Location = &location
status.Ready, status.Reason, status.Message = false, "LocationPinned", "凭据位置已固定,等待创建"
status = status.WithPhase(credentialdomain.Pinned, "凭据位置已固定,等待创建")
record, err = s.Resources.Save(ctx, record, status)
if err != nil {
return err
}
} else if *record.Status.Location != location {
return s.report(ctx, record, binding.DependencyUnavailable,
"部署配置与固定凭据位置不一致;请恢复原 mount/path 配置,未迁移或改密")
}
if record.Status.Version > 0 {
if record.Status.Confirmed() {
return s.observe(ctx, record)
}
return s.create(ctx, record)
@@ -113,19 +84,18 @@ func (s CredentialPreparation) Reconcile(ctx context.Context, name string) error
func (s CredentialPreparation) create(ctx context.Context, record *CredentialRecord) error {
_, err := s.Store.ReadCredential(ctx, *record.Status.Location, 0)
if err == nil || errors.Is(err, ErrApplicationCredentialInvalid) {
return s.report(ctx, record, binding.Conflict, "固定位置已有未确认的凭据;请人工核实,未认领或覆盖")
if err == nil || errors.Is(err, credentialdomain.ErrApplicationCredentialInvalid) {
return s.report(ctx, record, credentialdomain.Conflict, "固定位置已有未确认的凭据;请人工核实,未认领或覆盖")
}
if !errors.Is(err, ErrCredentialNotFound) {
return s.report(ctx, record, binding.DependencyUnavailable, "创建前无法确认凭据位置是否为空,等待依赖恢复")
return s.report(ctx, record, credentialdomain.Unavailable, "创建前无法确认凭据位置是否为空,等待依赖恢复")
}
credential, err := GenerateApplicationCredential(record.Database.LoginRole, record.Database.Name, record.Instance.Endpoint)
credential, err := credentialdomain.GenerateApplicationCredential(record.Database.LoginRole, record.Database.Name, record.Instance.Endpoint)
if err != nil {
return s.report(ctx, record, "InvalidTarget", "应用凭据目标无效,未执行外部写入")
return s.report(ctx, record, credentialdomain.InvalidTarget, "应用凭据目标无效,未执行外部写入")
}
status := record.Status
status.Ready, status.Reason = false, CredentialCreationStarted
status.Message = "凭据创建已开始;尚无成功确认时不得重入创建"
status = status.WithPhase(credentialdomain.Creating, "凭据创建已开始;尚无成功确认时不得重入创建")
record, err = s.Resources.Save(ctx, record, status)
if err != nil {
return err
@@ -136,46 +106,47 @@ func (s CredentialPreparation) create(ctx context.Context, record *CredentialRec
version, err := s.Store.CreateCredential(ctx, *record.Status.Location, credential)
if errors.Is(err, ErrCredentialUnavailable) {
// 适配器只在明确未执行写入(认证拒绝或请求前取消)时返回此错误。
return s.report(ctx, record, binding.DependencyUnavailable, "凭据创建在执行前被拒绝,等待认证或权限恢复")
return s.report(ctx, record, credentialdomain.Unavailable, "凭据创建在执行前被拒绝,等待认证或权限恢复")
}
if err != nil || version != 1 {
return s.report(ctx, record, binding.Conflict,
if err != nil {
return s.report(ctx, record, credentialdomain.Conflict,
"凭据创建冲突或结果不确定;请核对固定位置的版本历史,未认领、覆盖或重新生成密码")
}
confirmed, issue := record.Status.Created(version)
if issue != nil {
return s.report(ctx, record, issue.Phase, issue.Message)
}
if err := s.Resources.CheckCurrent(ctx, record); err != nil {
return err
}
status = record.Status
status.Version, status.Ready, status.Reason = version, true, CredentialPrepared
status.Message = "凭据已创建并回读确认;尚未创建 PostgreSQL 资源或交付给 Tenant"
_, err = s.Resources.Save(ctx, record, status)
_, err = s.Resources.Save(ctx, record, confirmed)
return err
}
func (s CredentialPreparation) observe(ctx context.Context, record *CredentialRecord) error {
credential, err := s.Store.ReadCredential(ctx, *record.Status.Location, record.Status.Version)
if errors.Is(err, ErrCredentialConflict) || errors.Is(err, ErrCredentialNotFound) {
return s.report(ctx, record, binding.Conflict, "已确认凭据消失、版本变化或内容无效;请人工核实,未生成替代密码")
return s.report(ctx, record, credentialdomain.Conflict, "已确认凭据消失、版本变化或内容无效;请人工核实,未生成替代密码")
}
if err != nil {
return s.report(ctx, record, binding.DependencyUnavailable, "已确认凭据暂时无法读取;保留确认版本,等待依赖恢复")
return s.report(ctx, record, credentialdomain.Unavailable, "已确认凭据暂时无法读取;保留确认版本,等待依赖恢复")
}
if !credential.MatchesTarget(record.Database.LoginRole, record.Database.Name, record.Instance.Endpoint) {
return s.report(ctx, record, binding.Conflict, "已确认凭据与当前 Instance/database/loginRole 不一致;请人工核实,未修改凭据")
if issue := record.CheckCredential(credential); issue != nil {
return s.report(ctx, record, issue.Phase, issue.Message)
}
if err := s.Resources.CheckCurrent(ctx, record); err != nil {
return err
}
status := record.Status
status.Ready, status.Reason = true, CredentialPrepared
status.Phase = credentialdomain.Prepared
status.Message = "已确认凭据可读取;尚未验证 PostgreSQL 资源或完成 Tenant 交付"
_, err = s.Resources.Save(ctx, record, status)
return err
}
func (s CredentialPreparation) report(ctx context.Context, record *CredentialRecord, reason, message string) error {
func (s CredentialPreparation) report(ctx context.Context, record *CredentialRecord, phase credentialdomain.Phase, message string) error {
status := record.Status
status.Ready, status.Reason = false, reason
status.Phase = phase
status.Message = fmt.Sprintf("Database %s:%s", record.Database.Identity.Name, message)
_, err := s.Resources.Save(ctx, record, status)
return err