Files
yms-daemon/internal/containerengine/moby.go
T

207 lines
5.9 KiB
Go
Raw Normal View History

2026-08-15 20:58:01 +08:00
package containerengine
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
cerrdefs "github.com/containerd/errdefs"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/jsonstream"
"github.com/moby/moby/api/types/mount"
"github.com/moby/moby/client"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// MobyEngine adapts the official Docker Engine Go client.
type MobyEngine struct {
client *client.Client
}
// NewMobyEngine creates a client from Docker's documented environment variables.
// API negotiation remains enabled, including when DOCKER_HOST selects a non-default socket.
func NewMobyEngine() (*MobyEngine, error) {
apiClient, err := client.New(client.FromEnv)
if err != nil {
return nil, fmt.Errorf("create Docker Engine client: %w", err)
}
return &MobyEngine{client: apiClient}, nil
}
func (e *MobyEngine) Ping(ctx context.Context) error {
if _, err := e.client.Ping(ctx, client.PingOptions{NegotiateAPIVersion: true}); err != nil {
return fmt.Errorf("ping Docker Engine: %w", err)
}
return nil
}
func (e *MobyEngine) LoadImage(ctx context.Context, input io.Reader) error {
if input == nil {
return errors.New("image archive reader is required")
}
response, err := e.client.ImageLoad(ctx, input)
if err != nil {
return fmt.Errorf("load image archive: %w", err)
}
defer response.Close()
if err := decodeImageLoadResponse(response); err != nil {
return fmt.Errorf("load image archive response: %w", err)
}
return nil
}
func decodeImageLoadResponse(input io.Reader) error {
decoder := json.NewDecoder(input)
for {
var message jsonstream.Message
if err := decoder.Decode(&message); err != nil {
if errors.Is(err, io.EOF) {
return nil
}
return fmt.Errorf("decode Docker JSON stream: %w", err)
}
if message.Error != nil {
return message.Error
}
}
}
func (e *MobyEngine) InspectImage(ctx context.Context, reference string) (Image, error) {
response, err := e.client.ImageInspect(ctx, reference)
if err != nil {
return Image{}, engineError("inspect image", err)
}
descriptorDigest := ""
if response.Descriptor != nil {
descriptorDigest = response.Descriptor.Digest.String()
}
return Image{
ID: response.ID,
RepoDigests: append([]string(nil), response.RepoDigests...),
DescriptorDigest: descriptorDigest,
Platform: Platform{
OS: response.Os,
Architecture: response.Architecture,
Variant: response.Variant,
},
}, nil
}
func (e *MobyEngine) CreateContainer(ctx context.Context, spec ContainerSpec) (Container, error) {
apiMounts := make([]mount.Mount, 0, len(spec.Mounts))
for _, item := range spec.Mounts {
apiMounts = append(apiMounts, mount.Mount{
Type: mount.Type(item.Type),
Source: item.Source,
Target: item.Target,
ReadOnly: item.ReadOnly,
})
}
result, err := e.client.ContainerCreate(ctx, client.ContainerCreateOptions{
Config: &container.Config{
Env: append([]string(nil), spec.Environment...),
Labels: cloneMap(spec.Labels),
},
HostConfig: &container.HostConfig{
NetworkMode: container.NetworkMode(spec.NetworkMode),
RestartPolicy: container.RestartPolicy{
Name: container.RestartPolicyMode(spec.RestartPolicy.Name),
MaximumRetryCount: spec.RestartPolicy.MaximumRetryCount,
},
Mounts: apiMounts,
},
Platform: &ocispec.Platform{
OS: spec.Platform.OS,
Architecture: spec.Platform.Architecture,
Variant: spec.Platform.Variant,
},
Name: spec.Name,
Image: spec.ImageReference,
})
if err != nil {
return Container{}, engineError("create container", err)
}
return e.InspectContainer(ctx, result.ID)
}
func (e *MobyEngine) StartContainer(ctx context.Context, idOrName string) error {
if _, err := e.client.ContainerStart(ctx, idOrName, client.ContainerStartOptions{}); err != nil {
return engineError("start container", err)
}
return nil
}
func (e *MobyEngine) InspectContainer(ctx context.Context, idOrName string) (Container, error) {
result, err := e.client.ContainerInspect(ctx, idOrName, client.ContainerInspectOptions{})
if err != nil {
return Container{}, engineError("inspect container", err)
}
response := result.Container
record := Container{
ID: response.ID,
Name: response.Name,
ImageID: response.Image,
Platform: response.Platform,
}
if response.State != nil {
record.Running = response.State.Running
record.Dead = response.State.Dead
record.Status = string(response.State.Status)
}
if response.Config != nil {
record.ImageReference = response.Config.Image
record.Environment = append([]string(nil), response.Config.Env...)
record.Labels = cloneMap(response.Config.Labels)
}
if response.HostConfig != nil {
record.NetworkMode = string(response.HostConfig.NetworkMode)
record.RestartPolicy = RestartPolicy{
Name: string(response.HostConfig.RestartPolicy.Name),
MaximumRetryCount: response.HostConfig.RestartPolicy.MaximumRetryCount,
}
}
for _, item := range response.Mounts {
record.Mounts = append(record.Mounts, Mount{
Type: string(item.Type),
Source: item.Source,
Target: item.Destination,
ReadOnly: !item.RW,
})
}
return record, nil
}
func (e *MobyEngine) RemoveContainer(ctx context.Context, idOrName string, force bool) error {
_, err := e.client.ContainerRemove(ctx, idOrName, client.ContainerRemoveOptions{Force: force})
if err != nil {
return engineError("remove container", err)
}
return nil
}
func (e *MobyEngine) Close() error {
return e.client.Close()
}
func engineError(action string, err error) error {
if cerrdefs.IsNotFound(err) {
return fmt.Errorf("%s: %w: %v", action, ErrNotFound, err)
}
return fmt.Errorf("%s: %w", action, err)
}
func cloneMap(source map[string]string) map[string]string {
if source == nil {
return nil
}
result := make(map[string]string, len(source))
for key, value := range source {
result[key] = value
}
return result
}
var _ Engine = (*MobyEngine)(nil)