Scert/backend/internal/deployment/operation.go

86 lines
2.5 KiB
Go

package deployment
import (
"context"
"fmt"
)
// acquireOperation 覆盖整个部署、站点核验及收尾;人工执行器必须共用此入口。
// 它不替代数据库持久化占用和远端文件锁,等待可以取消,不能提前释放。
func (s *Service) acquireOperation(ctx context.Context) (func(), error) {
if s == nil {
return nil, ErrRecoveryDispositionUnavailable
}
if err := ctx.Err(); err != nil {
return nil, err
}
s.operationOnce.Do(func() { s.operationGate = make(chan struct{}, 1) })
select {
case <-ctx.Done():
return nil, ctx.Err()
case s.operationGate <- struct{}{}:
if err := ctx.Err(); err != nil {
<-s.operationGate
return nil, err
}
return func() { <-s.operationGate }, nil
}
}
func operationFailure(targets []Target, err error) Report {
report := Report{Total: len(targets), Failed: len(targets), Status: "failed", Results: make([]Result, 0, len(targets))}
for _, target := range targets {
report.Results = append(report.Results, Result{
ServerID: target.Server.ID, Server: target.Server.Name, Status: "failed",
FailureCode: FailureUnavailable, Message: fmt.Sprintf("未获得部署操作许可:%v", err),
})
}
return report
}
type recoveryClaimReader interface {
HasRecoveryClaim(context.Context, string) (bool, error)
}
func (s *Service) checkRecoveryClaim(ctx context.Context, id string) error {
if err := ctx.Err(); err != nil {
return err
}
reader, ok := s.journal.(recoveryClaimReader)
if !ok {
return nil
}
claimed, err := reader.HasRecoveryClaim(ctx, id)
if err != nil {
return ErrRecoveryDispositionUnavailable
}
if claimed {
return ErrRecoveryDispositionBusy
}
return ctx.Err()
}
// checkDeploymentClaims 在连接远端前拦截仍有人工占用的服务器。
// 同地址同端口的别名也须检查;凭据或路径修改不能绕过原服务器占用。
func (s *Service) checkDeploymentClaims(ctx context.Context, server Server) error {
if _, ok := s.journal.(recoveryClaimReader); !ok {
return nil
}
records, err := s.journal.ListPendingDeploymentTransactions(ctx)
if err != nil {
return ErrRecoveryDispositionUnavailable
}
for _, record := range records {
sameServer := record.ServerID == server.ID
if record.Evidence != nil {
sameServer = sameServer || (record.Evidence.Address == server.Address && record.Evidence.Port == server.Port)
}
if sameServer {
if err := s.checkRecoveryClaim(ctx, record.ID); err != nil {
return err
}
}
}
return ctx.Err()
}