feat: backend container executor implement

This commit is contained in:
2026-08-15 20:58:01 +08:00
parent b4e18c6f0c
commit fbce91d797
10 changed files with 1662 additions and 1 deletions
+200
View File
@@ -0,0 +1,200 @@
package backendexecutor
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"slices"
"sync"
"time"
"yms-daemon/internal/containerengine"
"yms-daemon/internal/healthcheck"
"yms-daemon/internal/transaction"
)
type loadImageOperation struct {
engine containerengine.Engine
archivePath string
imageReference string
expectedDigest string
platform containerengine.Platform
}
func (o *loadImageOperation) Apply(ctx context.Context) error {
archive, err := os.Open(o.archivePath)
if err != nil {
return fmt.Errorf("open image archive: %w", err)
}
defer archive.Close()
return o.engine.LoadImage(ctx, archive)
}
func (o *loadImageOperation) Inspect(ctx context.Context) (transaction.Inspection, error) {
image, err := o.engine.InspectImage(ctx, o.imageReference)
if errors.Is(err, containerengine.ErrNotFound) {
return transaction.Inspection{Status: transaction.InspectionNotApplied}, nil
}
if err != nil {
return transaction.Inspection{}, err
}
matches, err := imageMatches(image, o.expectedDigest, o.platform)
if err != nil {
return transaction.Inspection{}, err
}
result := resultJSON(struct {
ImageID string `json:"imageId"`
RepoDigests []string `json:"repoDigests"`
DescriptorDigest string `json:"descriptorDigest"`
Platform containerengine.Platform `json:"platform"`
}{image.ID, image.RepoDigests, image.DescriptorDigest, image.Platform})
if !matches {
return transaction.Inspection{Status: transaction.InspectionNotApplied, Result: result}, nil
}
return transaction.Inspection{Status: transaction.InspectionApplied, Result: result}, nil
}
type createContainerOperation struct {
engine containerengine.Engine
expectedImage containerengine.Image
spec containerengine.ContainerSpec
}
func (o *createContainerOperation) Apply(ctx context.Context) error {
_, err := o.engine.CreateContainer(ctx, o.spec)
return err
}
func (o *createContainerOperation) Inspect(ctx context.Context) (transaction.Inspection, error) {
record, err := o.engine.InspectContainer(ctx, o.spec.Name)
if errors.Is(err, containerengine.ErrNotFound) {
return transaction.Inspection{Status: transaction.InspectionNotApplied}, nil
}
if err != nil {
return transaction.Inspection{}, err
}
result := containerResult(record)
if !containerMatches(record, o.expectedImage.ID, o.spec) {
return transaction.Inspection{Status: transaction.InspectionUnknown, Result: result}, nil
}
return transaction.Inspection{Status: transaction.InspectionApplied, Result: result}, nil
}
type startContainerOperation struct {
engine containerengine.Engine
name string
}
func (o *startContainerOperation) Apply(ctx context.Context) error {
return o.engine.StartContainer(ctx, o.name)
}
func (o *startContainerOperation) Inspect(ctx context.Context) (transaction.Inspection, error) {
record, err := o.engine.InspectContainer(ctx, o.name)
if errors.Is(err, containerengine.ErrNotFound) {
return transaction.Inspection{Status: transaction.InspectionNotApplied}, nil
}
if err != nil {
return transaction.Inspection{}, err
}
result := containerResult(record)
if record.Running && !record.Dead {
return transaction.Inspection{Status: transaction.InspectionApplied, Result: result}, nil
}
return transaction.Inspection{Status: transaction.InspectionNotApplied, Result: result}, nil
}
type healthOperation struct {
engine containerengine.Engine
checker *healthcheck.ActuatorChecker
name string
endpoint string
timeout time.Duration
mu sync.Mutex
confirmedReport healthcheck.ActuatorReport
confirmed bool
}
func (o *healthOperation) Apply(ctx context.Context) error {
report, err := o.checker.Wait(ctx, o.endpoint, o.timeout, o.running)
if err != nil {
return err
}
o.mu.Lock()
o.confirmedReport = report
o.confirmed = true
o.mu.Unlock()
return nil
}
func (o *healthOperation) Inspect(ctx context.Context) (transaction.Inspection, error) {
o.mu.Lock()
if o.confirmed {
report := o.confirmedReport
o.mu.Unlock()
return healthInspection(report, true, nil)
}
o.mu.Unlock()
report, ready, err := o.checker.Check(ctx, o.endpoint, o.running)
return healthInspection(report, ready, err)
}
func (o *healthOperation) running(ctx context.Context) (bool, error) {
record, err := o.engine.InspectContainer(ctx, o.name)
if errors.Is(err, containerengine.ErrNotFound) {
return false, nil
}
if err != nil {
return false, err
}
return record.Running && !record.Dead, nil
}
func healthInspection(report healthcheck.ActuatorReport, ready bool, err error) (transaction.Inspection, error) {
result := resultJSON(report)
if errors.Is(err, healthcheck.ErrWorkloadStopped) {
return transaction.Inspection{Status: transaction.InspectionNotApplied, Result: result}, nil
}
if err != nil {
return transaction.Inspection{Status: transaction.InspectionNotApplied, Result: result}, nil
}
if !ready {
return transaction.Inspection{Status: transaction.InspectionNotApplied, Result: result}, nil
}
return transaction.Inspection{Status: transaction.InspectionApplied, Result: result}, nil
}
func containerMatches(record containerengine.Container, expectedImageID string, spec containerengine.ContainerSpec) bool {
if record.ImageID != expectedImageID || record.NetworkMode != spec.NetworkMode || record.RestartPolicy != spec.RestartPolicy {
return false
}
for _, expected := range spec.Environment {
if !slices.Contains(record.Environment, expected) {
return false
}
}
for _, expected := range spec.Mounts {
if !slices.Contains(record.Mounts, expected) {
return false
}
}
return true
}
func containerResult(record containerengine.Container) json.RawMessage {
return resultJSON(struct {
ID string `json:"id"`
ImageID string `json:"imageId"`
Running bool `json:"running"`
Dead bool `json:"dead"`
Status string `json:"status"`
}{record.ID, record.ImageID, record.Running, record.Dead, record.Status})
}
var _ transaction.Operation = (*loadImageOperation)(nil)
var _ transaction.Operation = (*createContainerOperation)(nil)
var _ transaction.Operation = (*startContainerOperation)(nil)
var _ transaction.Operation = (*healthOperation)(nil)