split container update preparation

This commit is contained in:
2026-08-22 16:09:16 +08:00
parent afa371b250
commit eac689cf4b
2 changed files with 72 additions and 40 deletions
+1
View File
@@ -192,5 +192,6 @@
- daemon server 将请求读取、进度回调、更新/重启分派、诊断分派拆开,保留协议错误文本与响应行为。
- `runServe` 将 native/container 运行时构建与资源关闭拆出,避免入口函数承担全部分支。
- 容器槽位现场核对拆为容器检查、已提交部署校验和活动槽位选择,减少单函数分支嵌套。
- 容器镜像更新拆为输入校验、事务恢复、镜像拉取、槽位准备和事务创建阶段,行为与幂等键保持不变。
验证:`GOCACHE=/tmp/yms-go-cache go test ./...``git diff --check` 通过。
+71 -40
View File
@@ -72,85 +72,111 @@ type persistedContainerRequest struct {
// 是否转发容器启动日志;report 用于回传实时进度,可为 nil。返回值为本次更新
// 对应的事务记录以及错误。若已存在同镜像的活动事务则复用续跑。
func (u *Updater) UpdateContainerImage(ctx context.Context, imageReference string, startLog bool, report ProgressReporter) (transaction.Transaction, error) {
if err := u.validateImageUpdate(imageReference); err != nil {
return transaction.Transaction{}, err
}
if record, request, resumed, err := u.resumeContainerImage(ctx, imageReference, report); resumed || err != nil {
return u.finishResumedContainer(ctx, record, request, err, report)
}
resolved, err := u.pullImageForUpdate(ctx, imageReference, report)
if err != nil {
return transaction.Transaction{}, err
}
prepared, err := u.prepareContainerImage(ctx, imageReference, resolved, startLog)
if err != nil {
return transaction.Transaction{}, err
}
return u.createContainerImageTransaction(ctx, prepared, report)
}
func (u *Updater) validateImageUpdate(imageReference string) error {
if u.containerExecutor == nil || u.engine == nil {
return transaction.Transaction{}, errors.New("container backend updater is not configured")
return errors.New("container backend updater is not configured")
}
if strings.TrimSpace(imageReference) != imageReference || imageReference == "" {
return transaction.Transaction{}, errors.New("exact container image reference is required")
}
if err := u.validateContainerInputs(); err != nil {
return transaction.Transaction{}, err
return errors.New("exact container image reference is required")
}
return u.validateContainerInputs()
}
func (u *Updater) resumeContainerImage(ctx context.Context, imageReference string, report ProgressReporter) (transaction.Transaction, persistedContainerRequest, bool, error) {
active, err := u.store.ActiveTransaction(ctx)
if err == nil {
var request persistedContainerRequest
if decodeErr := decodeContainerRequest(active.Request, &request); decodeErr != nil {
return active, decodeErr
}
if active.Service != serviceBackend || request.InputType != inputTypeContainerImage {
return active, &transaction.ActiveTransactionError{TransactionID: active.ID}
}
if request.ImageReference != imageReference {
return active, &transaction.ActiveTransactionError{TransactionID: active.ID}
}
return u.runContainerUpdate(ctx, active, request, false, report)
if errors.Is(err, transaction.ErrNotFound) {
return transaction.Transaction{}, persistedContainerRequest{}, false, nil
}
if !errors.Is(err, transaction.ErrNotFound) {
return transaction.Transaction{}, err
if err != nil {
return transaction.Transaction{}, persistedContainerRequest{}, true, err
}
var request persistedContainerRequest
if err := decodeContainerRequest(active.Request, &request); err != nil {
return active, request, true, err
}
if active.Service != serviceBackend || request.InputType != inputTypeContainerImage || request.ImageReference != imageReference {
return active, request, true, &transaction.ActiveTransactionError{TransactionID: active.ID}
}
return active, request, true, nil
}
func (u *Updater) finishResumedContainer(ctx context.Context, record transaction.Transaction, request persistedContainerRequest, err error, report ProgressReporter) (transaction.Transaction, error) {
if err != nil {
return record, err
}
return u.runContainerUpdate(ctx, record, request, false, report)
}
func (u *Updater) pullImageForUpdate(ctx context.Context, imageReference string, report ProgressReporter) (resolvedImage, error) {
reportProgress(report, Progress{Message: "Pulling backend image " + imageReference})
resolved, err := u.pullAndResolveImage(ctx, imageReference)
if err != nil {
return transaction.Transaction{}, err
if err == nil {
reportProgress(report, Progress{Message: "Backend image resolved: " + resolved.ImmutableReference})
}
reportProgress(report, Progress{Message: "Backend image resolved: " + resolved.ImmutableReference})
return resolved, err
}
type preparedContainerImage struct {
transactionID string
request persistedContainerRequest
resolved resolvedImage
}
func (u *Updater) prepareContainerImage(ctx context.Context, imageReference string, resolved resolvedImage, startLog bool) (preparedContainerImage, error) {
before, err := u.gateway.Read()
if err != nil {
return transaction.Transaction{}, err
return preparedContainerImage{}, err
}
activeSlot, err := u.config.Backend.SlotForPort(before.ActivePort)
if err != nil {
return transaction.Transaction{}, err
return preparedContainerImage{}, err
}
deployment, deploymentErr := u.store.BackendContainerDeployment(ctx)
hasDeployment := deploymentErr == nil
if deploymentErr != nil && !errors.Is(deploymentErr, transaction.ErrNotFound) {
return transaction.Transaction{}, deploymentErr
return preparedContainerImage{}, deploymentErr
}
hasHistory, err := u.store.HasCommittedBackendContainerTransactionHistory(ctx)
if err != nil {
return transaction.Transaction{}, err
return preparedContainerImage{}, err
}
targetPort, targetSlot, previousContainer, err := u.resolveContainerSlots(
ctx,
before.ActivePort,
activeSlot,
deployment,
hasDeployment,
hasHistory,
)
targetPort, targetSlot, previousContainer, err := u.resolveContainerSlots(ctx, before.ActivePort, activeSlot, deployment, hasDeployment, hasHistory)
if err != nil {
return transaction.Transaction{}, err
return preparedContainerImage{}, err
}
afterContent, err := hostnginx.RenderBackendPort(before.Content, targetPort)
if err != nil {
return transaction.Transaction{}, err
return preparedContainerImage{}, err
}
transactionID := rand.Text()
transactionRoot := filepath.Join(u.workRoot, transactionID)
if err := os.MkdirAll(transactionRoot, 0o750); err != nil {
return transaction.Transaction{}, fmt.Errorf("create container backend transaction directory: %w", err)
return preparedContainerImage{}, fmt.Errorf("create container backend transaction directory: %w", err)
}
beforePath := filepath.Join(transactionRoot, "gateway.before.conf")
afterPath := filepath.Join(transactionRoot, "gateway.after.conf")
if err := writeImmutableFile(beforePath, before.Content, 0o640); err != nil {
return transaction.Transaction{}, err
return preparedContainerImage{}, err
}
if err := writeImmutableFile(afterPath, afterContent, 0o640); err != nil {
return transaction.Transaction{}, err
return preparedContainerImage{}, err
}
request := persistedContainerRequest{
InputType: inputTypeContainerImage, ImageReference: imageReference,
@@ -162,12 +188,17 @@ func (u *Updater) UpdateContainerImage(ctx context.Context, imageReference strin
GatewayBeforePath: beforePath, GatewayAfterPath: afterPath,
GatewayReceiptPath: filepath.Join(transactionRoot, "gateway.applied"),
}
return preparedContainerImage{transactionID: transactionID, request: request, resolved: resolved}, nil
}
func (u *Updater) createContainerImageTransaction(ctx context.Context, prepared preparedContainerImage, report ProgressReporter) (transaction.Transaction, error) {
request, resolved := prepared.request, prepared.resolved
content, err := json.Marshal(request)
if err != nil {
return transaction.Transaction{}, fmt.Errorf("encode container backend update request: %w", err)
}
record, created, err := u.store.CreateTransaction(ctx, transaction.CreateRequest{
ID: transactionID, IdempotencyKey: serviceBackend + ":container:" + resolved.Digest,
ID: prepared.transactionID, IdempotencyKey: serviceBackend + ":container:" + resolved.Digest,
Source: sourceLocalCLI, Service: serviceBackend, Request: content,
})
if err != nil {