feat: backend container executor implement
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user