Scert/backend/internal/deployment/recovery_result.go

80 lines
2.8 KiB
Go

package deployment
import (
"context"
"strings"
"time"
)
// RecoveryDispositionSummary 是本机查询的明确输出边界,不包含请求键、绑定摘要或操作者凭据。
type RecoveryDispositionSummary struct {
ID string `json:"id"`
TransactionID string `json:"transaction_id"`
ServerID string `json:"server_id"`
Action RecoveryAction `json:"action"`
Status string `json:"status"`
ResultCode string `json:"result_code"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (r RecoveryDisposition) Summary() RecoveryDispositionSummary {
return RecoveryDispositionSummary{
ID: r.Intent.ID, TransactionID: r.Intent.TransactionID, ServerID: r.Intent.ServerID,
Action: r.Intent.Action, Status: r.Status, ResultCode: r.ResultCode,
CreatedAt: r.CreatedAt, UpdatedAt: r.UpdatedAt,
}
}
func validDispositionID(id string) bool {
return validDigest(id) && id == strings.ToLower(id) && id != strings.Repeat("0", 64)
}
func (r RecoveryDispositionSummary) Validate() error {
if !validDispositionID(r.ID) || !validInspectionID(r.TransactionID) || !validRecoverySummary(r.ServerID) ||
!validRecoveryAction(r.Action) || r.CreatedAt.IsZero() || r.UpdatedAt.IsZero() || r.UpdatedAt.Before(r.CreatedAt) {
return ErrRecoveryDispositionInvalid
}
intent := RecoveryDispositionIntent{Action: r.Action}
switch r.Status {
case "running":
if r.ResultCode == "" {
return nil
}
case "succeeded":
if r.ResultCode != RecoveryResultRejected && r.ResultCode != RecoveryResultUncertain && intent.AllowsResult(r.ResultCode) {
return nil
}
case RecoveryResultRejected, RecoveryResultUncertain:
if r.ResultCode == r.Status {
return nil
}
}
return ErrRecoveryDispositionInvalid
}
// GetRecoveryDisposition 不取得写操作门,执行中或重启后均可查询;查询本身不会重放动作或释放占用。
func (s *Service) GetRecoveryDisposition(ctx context.Context, id string) (RecoveryDispositionSummary, error) {
if !validDispositionID(id) {
return RecoveryDispositionSummary{}, ErrRecoveryDispositionInvalid
}
if s == nil {
return RecoveryDispositionSummary{}, ErrRecoveryDispositionUnavailable
}
journal, ok := s.journal.(RecoveryDispositionJournal)
if !ok {
return RecoveryDispositionSummary{}, ErrRecoveryDispositionUnavailable
}
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
item, err := journal.GetRecoveryDisposition(ctx, id)
if err != nil || ctx.Err() != nil || item.Intent.ID != id || item.Intent.Validate() != nil {
return RecoveryDispositionSummary{}, ErrRecoveryDispositionUnavailable
}
summary := item.Summary()
if summary.Validate() != nil {
return RecoveryDispositionSummary{}, ErrRecoveryDispositionUnavailable
}
return summary, nil
}