From eac689cf4b9784e125491c0ab30e17f5253927b9 Mon Sep 17 00:00:00 2001 From: Zhan Ziyang Date: Sat, 22 Aug 2026 16:09:16 +0800 Subject: [PATCH] split container update preparation --- AGENTS_CHANGELOG.md | 1 + internal/backendupdate/container.go | 111 ++++++++++++++++++---------- 2 files changed, 72 insertions(+), 40 deletions(-) diff --git a/AGENTS_CHANGELOG.md b/AGENTS_CHANGELOG.md index 3946984..ede80f3 100644 --- a/AGENTS_CHANGELOG.md +++ b/AGENTS_CHANGELOG.md @@ -192,5 +192,6 @@ - daemon server 将请求读取、进度回调、更新/重启分派、诊断分派拆开,保留协议错误文本与响应行为。 - `runServe` 将 native/container 运行时构建与资源关闭拆出,避免入口函数承担全部分支。 - 容器槽位现场核对拆为容器检查、已提交部署校验和活动槽位选择,减少单函数分支嵌套。 +- 容器镜像更新拆为输入校验、事务恢复、镜像拉取、槽位准备和事务创建阶段,行为与幂等键保持不变。 验证:`GOCACHE=/tmp/yms-go-cache go test ./...`、`git diff --check` 通过。 diff --git a/internal/backendupdate/container.go b/internal/backendupdate/container.go index b9871d6..afc63dd 100644 --- a/internal/backendupdate/container.go +++ b/internal/backendupdate/container.go @@ -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 {