fix: clarify reconciliation and preserve container image tags
This commit is contained in:
@@ -56,6 +56,8 @@ const (
|
||||
ImageAcquisitionLoad = "load"
|
||||
// ImageAcquisitionPull 表示通过远程仓库拉取方式获取后端镜像。
|
||||
ImageAcquisitionPull = "pull"
|
||||
// ImageAcquisitionPresent 表示镜像已在本地,不执行拉取或加载,只核对镜像身份。
|
||||
ImageAcquisitionPresent = "present"
|
||||
)
|
||||
|
||||
// Request 携带 update 包与本地部署配置提供的精确取值。
|
||||
@@ -67,6 +69,9 @@ type Request struct {
|
||||
ArchivePath string
|
||||
// ImageReference 后端镜像的精确引用,执行器不解析其语义。
|
||||
ImageReference string
|
||||
// DisplayImageReference 容器配置中用于可读展示的镜像引用。非空时仅影响 docker ps 的显示,
|
||||
// 实际镜像身份仍必须由 ExpectedImageDigest 校验通过。
|
||||
DisplayImageReference string
|
||||
// ExpectedImageDigest 后端镜像期望的清单摘要。
|
||||
ExpectedImageDigest string
|
||||
// Platform 后端镜像显式指定的操作系统与架构。
|
||||
@@ -239,12 +244,23 @@ func (e *Executor) prepare(ctx context.Context, transactionID string, request Re
|
||||
if _, err := e.coordinator.ExecuteStep(ctx, transactionID, pullIntent(request), operation); err != nil {
|
||||
return err
|
||||
}
|
||||
case ImageAcquisitionPresent:
|
||||
// 镜像已在本地,无外部副作用,跳过拉取,后续 InspectImage 时核对摘要。
|
||||
}
|
||||
|
||||
image, err := e.engine.InspectImage(ctx, request.ImageReference)
|
||||
if err != nil {
|
||||
return fmt.Errorf("inspect prepared backend image: %w", err)
|
||||
}
|
||||
if request.ImageAcquisition == ImageAcquisitionPresent {
|
||||
matches, err := imageMatches(image, request.ExpectedImageDigest, request.Platform)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !matches {
|
||||
return fmt.Errorf("present backend image %s does not match expected digest %s", request.ImageReference, request.ExpectedImageDigest)
|
||||
}
|
||||
}
|
||||
removeOperation := &removeContainerOperation{engine: e.engine, name: request.ContainerName}
|
||||
if _, err := e.coordinator.ExecuteStep(ctx, transactionID, removeIntent(request), removeOperation); err != nil {
|
||||
return err
|
||||
@@ -258,27 +274,18 @@ func (e *Executor) prepare(ctx context.Context, transactionID string, request Re
|
||||
return err
|
||||
}
|
||||
|
||||
// startAndCheck 先启动非活动后端容器,若请求要求读取启动日志则逐行回传,
|
||||
// 最后执行健康检查步骤等待后端 Actuator 健康。
|
||||
// startAndCheck 先启动非活动后端容器,若请求要求读取启动日志则在后台跟随回传,
|
||||
// 随后执行健康检查步骤等待后端 Actuator 健康,健康检查结束后停止日志跟随。
|
||||
func (e *Executor) startAndCheck(ctx context.Context, transactionID string, request Request) error {
|
||||
startOperation := &startContainerOperation{engine: e.engine, name: request.ContainerName}
|
||||
if _, err := e.coordinator.ExecuteStep(ctx, transactionID, startIntent(request), startOperation); err != nil {
|
||||
return err
|
||||
}
|
||||
var cancelLog context.CancelFunc
|
||||
if request.StartLog && request.LogReporter != nil {
|
||||
logs, err := e.engine.ContainerLogs(ctx, request.ContainerName)
|
||||
if err == nil {
|
||||
scanner := bufio.NewScanner(logs)
|
||||
for scanner.Scan() {
|
||||
request.LogReporter(scanner.Text())
|
||||
}
|
||||
_ = logs.Close()
|
||||
if err := scanner.Err(); err != nil {
|
||||
return fmt.Errorf("read container startup logs: %w", err)
|
||||
}
|
||||
} else {
|
||||
request.LogReporter("unable to read container startup logs: " + err.Error())
|
||||
}
|
||||
var logCtx context.Context
|
||||
logCtx, cancelLog = context.WithCancel(ctx)
|
||||
go e.streamContainerLogs(logCtx, request.ContainerName, request.LogReporter)
|
||||
}
|
||||
healthOperation := &healthOperation{
|
||||
engine: e.engine,
|
||||
@@ -288,9 +295,27 @@ func (e *Executor) startAndCheck(ctx context.Context, transactionID string, requ
|
||||
timeout: healthTimeout,
|
||||
}
|
||||
_, err := e.coordinator.ExecuteStep(ctx, transactionID, healthIntent(request), healthOperation)
|
||||
if cancelLog != nil {
|
||||
cancelLog()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// streamContainerLogs 跟随读取指定容器的日志并逐行上报,直到 ctx 被取消。
|
||||
// 它由 startAndCheck 在后台 goroutine 中调用,健康检查期间持续吐出容器启动日志。
|
||||
func (e *Executor) streamContainerLogs(ctx context.Context, name string, report func(string)) {
|
||||
logs, err := e.engine.ContainerLogs(ctx, name)
|
||||
if err != nil {
|
||||
report("unable to read container startup logs: " + err.Error())
|
||||
return
|
||||
}
|
||||
defer logs.Close()
|
||||
scanner := bufio.NewScanner(logs)
|
||||
for scanner.Scan() {
|
||||
report(scanner.Text())
|
||||
}
|
||||
}
|
||||
|
||||
// validateRequest 对请求字段做静态校验,确保所有取值精确且自洽。
|
||||
// 任一字段不符合要求时返回描述性错误。
|
||||
func validateRequest(request Request) error {
|
||||
@@ -303,6 +328,10 @@ func validateRequest(request Request) error {
|
||||
if request.ArchivePath != "" {
|
||||
return errors.New("pull image acquisition does not accept an archive path")
|
||||
}
|
||||
case ImageAcquisitionPresent:
|
||||
if request.ArchivePath != "" {
|
||||
return errors.New("present image acquisition does not accept an archive path")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported image acquisition: %q", request.ImageAcquisition)
|
||||
}
|
||||
@@ -394,9 +423,13 @@ func directDirectory(path, description string) error {
|
||||
// containerSpec 根据请求构造后端容器的完整规格,包括名称、镜像引用、平台、环境变量、
|
||||
// 宿主机网络模式、重启策略、绑定挂载、用户与停止超时。
|
||||
func containerSpec(request Request) containerengine.ContainerSpec {
|
||||
imageReference := request.ImageReference
|
||||
if request.DisplayImageReference != "" {
|
||||
imageReference = request.DisplayImageReference
|
||||
}
|
||||
return containerengine.ContainerSpec{
|
||||
Name: request.ContainerName,
|
||||
ImageReference: request.ImageReference,
|
||||
ImageReference: imageReference,
|
||||
Platform: request.Platform,
|
||||
Environment: []string{
|
||||
request.PortEnvironmentKey + "=" + strconv.Itoa(request.Port),
|
||||
|
||||
Reference in New Issue
Block a user