Scert/backend/internal/deployment/recovery_preflight.go

87 lines
2.9 KiB
Go
Raw Permalink 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 deployment
import (
"context"
"errors"
"fmt"
)
// validateTransactionLocks 在任何文件变更或解锁之前核验完整锁集合。
// 已完成核验的收尾阶段允许锁已不存在,但存在的无归属锁绝不自动接管。
func validateTransactionLocks(ctx context.Context, client Client, record TransactionRecord, allowMissing bool) error {
if record.Evidence == nil || !validDigest(record.Evidence.LockToken) {
return errors.New("缺少可信部署锁证据")
}
for _, path := range record.LockPaths {
exists, err := client.Exists(ctx, path)
if err != nil {
return fmt.Errorf("检查部署锁失败:%w", err)
}
if !exists && allowMissing {
continue
}
if err := checkOwnedLock(ctx, client, path, record.Evidence.LockToken, false); err != nil {
return fmt.Errorf("部署锁归属预检失败 %s:%w", path, err)
}
}
return nil
}
// validateRollbackFiles 先核验所有旧文件和目标文件,再允许逐个还原,避免部分覆盖现场。
func validateRollbackFiles(ctx context.Context, client Client, record TransactionRecord) error {
if err := validateTransactionLocks(ctx, client, record, false); err != nil {
return err
}
if err := validateTemporaryFiles(ctx, client, record); err != nil {
return err
}
for _, backup := range record.Backups {
backupExists, err := validateRecoveryBackup(ctx, client, backup)
if err != nil {
return err
}
targetExists, err := client.Exists(ctx, backup.Original)
if err != nil {
return fmt.Errorf("检查待恢复目标失败:%w", err)
}
if backup.OriginalExists && !backupExists {
if !targetExists {
return fmt.Errorf("备份文件和目标文件均不存在:%s", backup.Original)
}
if err := checkFileDigest(ctx, client, backup.Original, backup.OriginalDigest); err != nil {
return fmt.Errorf("无法证明旧文件已恢复:%w", err)
}
continue
}
if !targetExists {
continue
}
if err := checkFileDigest(ctx, client, backup.Original, record.Evidence.Files[backup.Original]); err != nil {
if !backup.OriginalExists {
return fmt.Errorf("新增目标文件身份不一致:%w", err)
}
if oldErr := checkFileDigest(ctx, client, backup.Original, backup.OriginalDigest); oldErr != nil {
return fmt.Errorf("目标既不是本次部署文件也不是原始文件:%w", oldErr)
}
}
}
return nil
}
func validateRecoveryBackup(ctx context.Context, client Client, backup TransactionBackup) (bool, error) {
exists, err := client.Exists(ctx, backup.Saved)
if err != nil {
return false, fmt.Errorf("检查恢复备份失败:%w", err)
}
if !exists {
return false, nil
}
if !backup.OriginalExists {
return true, fmt.Errorf("发现不属于本次事务的备份文件:%s", backup.Saved)
}
if err := checkFileDigest(ctx, client, backup.Saved, backup.OriginalDigest); err != nil {
return true, fmt.Errorf("恢复备份身份不一致:%w", err)
}
return true, nil
}