From 59074a275f99cbf201fbe2bf443e512e167221d1 Mon Sep 17 00:00:00 2001 From: Pasha Sviderski Date: Thu, 2 Oct 2025 15:51:54 +1000 Subject: [PATCH] chore(images): initialise Docker service with containerd client --- go.mod | 8 +++--- internal/containerd/client.go | 39 ++++++++++++++++++++++++++++++ internal/machine/docker/server.go | 32 +++++++++--------------- internal/machine/docker/service.go | 14 ++++++++--- internal/machine/machine.go | 17 ++++++++++--- 5 files changed, 78 insertions(+), 32 deletions(-) create mode 100644 internal/containerd/client.go diff --git a/go.mod b/go.mod index 3cd978cc..0c358ea1 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,9 @@ require ( github.com/charmbracelet/huh v0.6.0 github.com/charmbracelet/lipgloss v1.1.0 github.com/compose-spec/compose-go/v2 v2.4.5 + github.com/containerd/containerd/v2 v2.1.1 + github.com/containerd/errdefs v1.0.0 + github.com/containerd/log v0.1.0 github.com/containerd/platforms v1.0.0-rc.1 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/deckarep/golang-set/v2 v2.8.0 @@ -24,6 +27,7 @@ require ( github.com/docker/docker v27.4.0-rc.2+incompatible github.com/docker/go-connections v0.5.0 github.com/docker/go-units v0.5.0 + github.com/dustin/go-humanize v1.0.1 github.com/goccy/go-yaml v1.17.1 github.com/google/go-cmp v0.7.0 github.com/google/go-containerregistry v0.20.2 @@ -101,12 +105,9 @@ require ( github.com/containerd/console v1.0.4 // indirect github.com/containerd/containerd v1.7.24 // indirect github.com/containerd/containerd/api v1.9.0 // indirect - github.com/containerd/containerd/v2 v2.1.1 // indirect github.com/containerd/continuity v0.4.5 // indirect - github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/fifo v1.1.0 // indirect - github.com/containerd/log v0.1.0 // indirect github.com/containerd/plugin v1.0.0 // indirect github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect github.com/containerd/ttrpc v1.2.7 // indirect @@ -127,7 +128,6 @@ require ( github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/docker/go-metrics v0.0.1 // indirect - github.com/dustin/go-humanize v1.0.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/fatih/color v1.17.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect diff --git a/internal/containerd/client.go b/internal/containerd/client.go new file mode 100644 index 00000000..2ac47240 --- /dev/null +++ b/internal/containerd/client.go @@ -0,0 +1,39 @@ +package containerd + +import ( + "fmt" + "time" + + "github.com/containerd/containerd/v2/client" +) + +// Client is a containerd client for image operations. +type Client struct { + Client *client.Client +} + +// NewClient creates a new containerd client connected to the given socket path. +func NewClient(sockPath string) (*Client, error) { + if sockPath == "" { + return nil, fmt.Errorf("containerd socket path is required") + } + + c, err := client.New( + sockPath, + client.WithDefaultNamespace("moby"), + client.WithTimeout(60*time.Second), + ) + if err != nil { + return nil, err + } + + return &Client{Client: c}, nil +} + +// Close closes the containerd client connection. +func (c *Client) Close() error { + if c.Client != nil { + return c.Client.Close() + } + return nil +} diff --git a/internal/machine/docker/server.go b/internal/machine/docker/server.go index 1b218cd8..c5f2492a 100644 --- a/internal/machine/docker/server.go +++ b/internal/machine/docker/server.go @@ -63,25 +63,16 @@ type Server struct { waitForNetworkReady func(ctx context.Context) error } -// ServerOption configures the Docker server. -type ServerOption func(*Server) - -// WithNetworkReady sets the network readiness check function. -func WithNetworkReady(networkReady func() bool) ServerOption { - return func(s *Server) { - s.networkReady = networkReady - } -} - -// WithWaitForNetworkReady sets the network readiness wait function. -func WithWaitForNetworkReady(waitForNetworkReady func(ctx context.Context) error) ServerOption { - return func(s *Server) { - s.waitForNetworkReady = waitForNetworkReady - } +type ServerOptions struct { + // TODO: verify if we still need the network readiness checks as the cluster controller ensures the network + // is ready before starting the network API server. It may still be needed when communicating with the local + // API server but in this case we should probably fail until the cluster is initialised. + NetworkReady func() bool + WaitForNetworkReady func(ctx context.Context) error } // NewServer creates a new Docker gRPC server with the provided Docker service. -func NewServer(service *Service, db *sqlx.DB, internalDNSIP func() netip.Addr, opts ...ServerOption) *Server { +func NewServer(service *Service, db *sqlx.DB, internalDNSIP func() netip.Addr, opts ServerOptions) *Server { s := &Server{ client: service.Client, service: service, @@ -89,9 +80,8 @@ func NewServer(service *Service, db *sqlx.DB, internalDNSIP func() netip.Addr, o internalDNSIP: internalDNSIP, } - for _, opt := range opts { - opt(s) - } + s.networkReady = opts.NetworkReady + s.waitForNetworkReady = opts.WaitForNetworkReady return s } @@ -731,7 +721,9 @@ func (s *Server) injectConfigs(ctx context.Context, containerID string, configs } // Copy the config content directly into the container - if err := s.copyContentToContainer(ctx, containerID, config.Content, targetPath, uid, gid, fileMode); err != nil { + if err := s.copyContentToContainer( + ctx, containerID, config.Content, targetPath, uid, gid, fileMode, + ); err != nil { return fmt.Errorf("copy config file '%s' to container: %w", config.Name, err) } diff --git a/internal/machine/docker/service.go b/internal/machine/docker/service.go index dc2cb10c..43541059 100644 --- a/internal/machine/docker/service.go +++ b/internal/machine/docker/service.go @@ -12,21 +12,27 @@ import ( "github.com/docker/docker/api/types/filters" "github.com/docker/docker/client" "github.com/jmoiron/sqlx" + "github.com/psviderski/uncloud/internal/containerd" "github.com/psviderski/uncloud/pkg/api" ) // Service provides higher-level Docker operations that extends Docker API with Uncloud-specific data // from the machine database. type Service struct { + // Client is a Docker client for managing Docker resources. Client *client.Client - db *sqlx.DB + // containerd is a containerd client for accessing containerd images. + containerd *containerd.Client + // db is a connection to the machine database. + db *sqlx.DB } // NewService creates a new Docker service instance. -func NewService(client *client.Client, db *sqlx.DB) *Service { +func NewService(client *client.Client, containerdClient *containerd.Client, db *sqlx.DB) *Service { return &Service{ - Client: client, - db: db, + Client: client, + containerd: containerdClient, + db: db, } } diff --git a/internal/machine/machine.go b/internal/machine/machine.go index 28c30728..7fb66cd4 100644 --- a/internal/machine/machine.go +++ b/internal/machine/machine.go @@ -17,6 +17,7 @@ import ( "github.com/docker/docker/client" "github.com/docker/go-connections/sockets" + "github.com/psviderski/uncloud/internal/containerd" "github.com/psviderski/uncloud/internal/corrosion" "github.com/psviderski/uncloud/internal/docker" "github.com/psviderski/uncloud/internal/fs" @@ -255,7 +256,14 @@ func NewMachine(config *Config) (*Machine, error) { return nil, fmt.Errorf("init machine database: %w", err) } - dockerService := machinedocker.NewService(config.DockerClient, db) + if config.ContainerdSockPath == "" { + return nil, errors.New("containerd socket path must be configured") + } + containerdClient, err := containerd.NewClient(config.ContainerdSockPath) + if err != nil { + return nil, fmt.Errorf("create containerd client: %w", err) + } + dockerService := machinedocker.NewService(config.DockerClient, containerdClient, db) // Init a local gRPC proxy server that proxies requests to the local or remote machine API servers. proxyDirector := apiproxy.NewDirector(config.MachineSockPath, constants.MachineAPIPort) @@ -283,9 +291,10 @@ func NewMachine(config *Config) (*Machine, error) { internalDNSIP := func() netip.Addr { return m.IP() } - m.dockerServer = machinedocker.NewServer(dockerService, db, internalDNSIP, - machinedocker.WithNetworkReady(m.IsNetworkReady), - machinedocker.WithWaitForNetworkReady(m.WaitForNetworkReady)) + m.dockerServer = machinedocker.NewServer(dockerService, db, internalDNSIP, machinedocker.ServerOptions{ + NetworkReady: m.IsNetworkReady, + WaitForNetworkReady: m.WaitForNetworkReady, + }) caddyServer := caddyconfig.NewServer(caddyconfig.NewService(config.CaddyConfigDir)) m.localMachineServer = newGRPCServer(m, c, m.dockerServer, caddyServer)