Skip to content

Commit 521917d

Browse files
committed
docker client dependency injection
1 parent 58eac6d commit 521917d

File tree

8 files changed

+77
-58
lines changed

8 files changed

+77
-58
lines changed

cmd/api/api/api_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@ func newTestService(t *testing.T) *ApiService {
1616
DataDir: t.TempDir(),
1717
}
1818

19+
// Create Docker client for testing (may be nil if not available)
20+
dockerClient, _ := images.NewDockerClient()
21+
1922
return &ApiService{
2023
Config: cfg,
21-
ImageManager: images.NewManager(cfg.DataDir),
24+
ImageManager: images.NewManager(cfg.DataDir, dockerClient),
2225
InstanceManager: instances.NewManager(cfg.DataDir),
2326
VolumeManager: volumes.NewManager(cfg.DataDir),
2427
}

cmd/api/config/config.go

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,13 @@ import (
77
)
88

99
type Config struct {
10-
Port string
11-
DataDir string
12-
BridgeName string
13-
SubnetCIDR string
14-
SubnetGateway string
15-
ContainerdSocket string
16-
JwtSecret string
17-
DNSServer string
10+
Port string
11+
DataDir string
12+
BridgeName string
13+
SubnetCIDR string
14+
SubnetGateway string
15+
JwtSecret string
16+
DNSServer string
1817
}
1918

2019
// Load loads configuration from environment variables
@@ -24,14 +23,13 @@ func Load() *Config {
2423
_ = godotenv.Load()
2524

2625
cfg := &Config{
27-
Port: getEnv("PORT", "8080"),
28-
DataDir: getEnv("DATA_DIR", "/var/lib/hypeman"),
29-
BridgeName: getEnv("BRIDGE_NAME", "vmbr0"),
30-
SubnetCIDR: getEnv("SUBNET_CIDR", "192.168.100.0/24"),
31-
SubnetGateway: getEnv("SUBNET_GATEWAY", "192.168.100.1"),
32-
ContainerdSocket: getEnv("CONTAINERD_SOCKET", "/run/containerd/containerd.sock"),
33-
JwtSecret: getEnv("JWT_SECRET", ""),
34-
DNSServer: getEnv("DNS_SERVER", "1.1.1.1"),
26+
Port: getEnv("PORT", "8080"),
27+
DataDir: getEnv("DATA_DIR", "/var/lib/hypeman"),
28+
BridgeName: getEnv("BRIDGE_NAME", "vmbr0"),
29+
SubnetCIDR: getEnv("SUBNET_CIDR", "192.168.100.0/24"),
30+
SubnetGateway: getEnv("SUBNET_GATEWAY", "192.168.100.1"),
31+
JwtSecret: getEnv("JWT_SECRET", ""),
32+
DNSServer: getEnv("DNS_SERVER", "1.1.1.1"),
3533
}
3634

3735
return cfg

cmd/api/wire.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func initializeApp() (*application, func(), error) {
3232
providers.ProvideLogger,
3333
providers.ProvideContext,
3434
providers.ProvideConfig,
35+
providers.ProvideDockerClient,
3536
providers.ProvideImageManager,
3637
providers.ProvideInstanceManager,
3738
providers.ProvideVolumeManager,

cmd/api/wire_gen.go

Lines changed: 5 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/images/docker.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,27 @@ import (
1111
"github.com/docker/docker/pkg/archive"
1212
)
1313

14-
// dockerClient wraps Docker API operations
15-
type dockerClient struct {
14+
// DockerClient wraps Docker API operations
15+
type DockerClient struct {
1616
cli *client.Client
1717
}
1818

19-
// newDockerClient creates a new Docker client using environment variables
20-
func newDockerClient() (*dockerClient, error) {
19+
// NewDockerClient creates a new Docker client using environment variables
20+
func NewDockerClient() (*DockerClient, error) {
2121
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
2222
if err != nil {
2323
return nil, fmt.Errorf("create docker client: %w", err)
2424
}
25-
return &dockerClient{cli: cli}, nil
25+
return &DockerClient{cli: cli}, nil
2626
}
2727

28-
// close closes the Docker client
29-
func (d *dockerClient) close() error {
28+
// Close closes the Docker client
29+
func (d *DockerClient) Close() error {
3030
return d.cli.Close()
3131
}
3232

3333
// pullAndExport pulls an OCI image and exports its rootfs to a directory
34-
func (d *dockerClient) pullAndExport(ctx context.Context, imageRef, exportDir string) (*containerMetadata, error) {
34+
func (d *DockerClient) pullAndExport(ctx context.Context, imageRef, exportDir string) (*containerMetadata, error) {
3535
// Pull the image
3636
pullReader, err := d.cli.ImagePull(ctx, imageRef, image.PullOptions{})
3737
if err != nil {

lib/images/manager.go

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@ type Manager interface {
2323
}
2424

2525
type manager struct {
26-
dataDir string
26+
dataDir string
27+
dockerClient *DockerClient
2728
}
2829

29-
// NewManager creates a new image manager
30-
func NewManager(dataDir string) Manager {
30+
// NewManager creates a new image manager with Docker client
31+
func NewManager(dataDir string, dockerClient *DockerClient) Manager {
3132
return &manager{
32-
dataDir: dataDir,
33+
dataDir: dataDir,
34+
dockerClient: dockerClient,
3335
}
3436
}
3537

@@ -60,18 +62,11 @@ func (m *manager) CreateImage(ctx context.Context, req oapi.CreateImageRequest)
6062
return nil, ErrAlreadyExists
6163
}
6264

63-
// 3. Connect to Docker
64-
client, err := newDockerClient()
65-
if err != nil {
66-
return nil, fmt.Errorf("connect to docker: %w", err)
67-
}
68-
defer client.close()
69-
70-
// 4. Pull image and export rootfs to temp directory
65+
// 3. Pull image and export rootfs to temp directory
7166
tempDir := filepath.Join(os.TempDir(), fmt.Sprintf("hypeman-image-%s-%d", *imageID, time.Now().Unix()))
7267
defer os.RemoveAll(tempDir) // cleanup temp dir
7368

74-
containerMeta, err := client.pullAndExport(ctx, req.Name, tempDir)
69+
containerMeta, err := m.dockerClient.pullAndExport(ctx, req.Name, tempDir)
7570
if err != nil {
7671
return nil, fmt.Errorf("pull and export: %w", err)
7772
}

lib/images/manager_test.go

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ import (
1111
)
1212

1313
func TestCreateImage(t *testing.T) {
14-
skipIfNoDocker(t)
14+
dockerClient := skipIfNoDocker(t)
1515

1616
dataDir := t.TempDir()
17-
mgr := NewManager(dataDir)
17+
mgr := NewManager(dataDir, dockerClient)
1818

1919
ctx := context.Background()
2020
req := oapi.CreateImageRequest{
@@ -41,10 +41,10 @@ func TestCreateImage(t *testing.T) {
4141
}
4242

4343
func TestCreateImageWithCustomID(t *testing.T) {
44-
skipIfNoDocker(t)
44+
dockerClient := skipIfNoDocker(t)
4545

4646
dataDir := t.TempDir()
47-
mgr := NewManager(dataDir)
47+
mgr := NewManager(dataDir, dockerClient)
4848

4949
ctx := context.Background()
5050
customID := "my-custom-alpine"
@@ -60,10 +60,10 @@ func TestCreateImageWithCustomID(t *testing.T) {
6060
}
6161

6262
func TestCreateImageDuplicate(t *testing.T) {
63-
skipIfNoDocker(t)
63+
dockerClient := skipIfNoDocker(t)
6464

6565
dataDir := t.TempDir()
66-
mgr := NewManager(dataDir)
66+
mgr := NewManager(dataDir, dockerClient)
6767

6868
ctx := context.Background()
6969
req := oapi.CreateImageRequest{
@@ -80,10 +80,10 @@ func TestCreateImageDuplicate(t *testing.T) {
8080
}
8181

8282
func TestListImages(t *testing.T) {
83-
skipIfNoDocker(t)
83+
dockerClient := skipIfNoDocker(t)
8484

8585
dataDir := t.TempDir()
86-
mgr := NewManager(dataDir)
86+
mgr := NewManager(dataDir, dockerClient)
8787

8888
ctx := context.Background()
8989

@@ -107,10 +107,10 @@ func TestListImages(t *testing.T) {
107107
}
108108

109109
func TestGetImage(t *testing.T) {
110-
skipIfNoDocker(t)
110+
dockerClient := skipIfNoDocker(t)
111111

112112
dataDir := t.TempDir()
113-
mgr := NewManager(dataDir)
113+
mgr := NewManager(dataDir, dockerClient)
114114

115115
ctx := context.Background()
116116
req := oapi.CreateImageRequest{
@@ -130,8 +130,13 @@ func TestGetImage(t *testing.T) {
130130
}
131131

132132
func TestGetImageNotFound(t *testing.T) {
133+
dockerClient, _ := NewDockerClient()
134+
if dockerClient != nil {
135+
defer dockerClient.Close()
136+
}
137+
133138
dataDir := t.TempDir()
134-
mgr := NewManager(dataDir)
139+
mgr := NewManager(dataDir, dockerClient)
135140

136141
ctx := context.Background()
137142

@@ -141,10 +146,10 @@ func TestGetImageNotFound(t *testing.T) {
141146
}
142147

143148
func TestDeleteImage(t *testing.T) {
144-
skipIfNoDocker(t)
149+
dockerClient := skipIfNoDocker(t)
145150

146151
dataDir := t.TempDir()
147-
mgr := NewManager(dataDir)
152+
mgr := NewManager(dataDir, dockerClient)
148153

149154
ctx := context.Background()
150155
req := oapi.CreateImageRequest{
@@ -169,8 +174,13 @@ func TestDeleteImage(t *testing.T) {
169174
}
170175

171176
func TestDeleteImageNotFound(t *testing.T) {
177+
dockerClient, _ := NewDockerClient()
178+
if dockerClient != nil {
179+
defer dockerClient.Close()
180+
}
181+
172182
dataDir := t.TempDir()
173-
mgr := NewManager(dataDir)
183+
mgr := NewManager(dataDir, dockerClient)
174184

175185
ctx := context.Background()
176186

@@ -200,19 +210,22 @@ func TestGenerateImageID(t *testing.T) {
200210
}
201211

202212
// skipIfNoDocker skips the test if Docker is not available or accessible
203-
func skipIfNoDocker(t *testing.T) {
213+
// Returns a DockerClient for use in tests
214+
func skipIfNoDocker(t *testing.T) *DockerClient {
204215
// Try to connect to Docker to verify we have permission
205-
client, err := newDockerClient()
216+
client, err := NewDockerClient()
206217
if err != nil {
207218
t.Skipf("cannot connect to docker: %v, skipping test", err)
208219
}
209-
defer client.close()
210220

211221
// Verify we can actually use Docker by pinging it
212222
ctx := context.Background()
213223
_, err = client.cli.Ping(ctx)
214224
if err != nil {
225+
client.Close()
215226
t.Skipf("docker not available: %v, skipping test", err)
216227
}
228+
229+
return client
217230
}
218231

lib/providers/providers.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,14 @@ func ProvideConfig() *config.Config {
2929
return config.Load()
3030
}
3131

32+
// ProvideDockerClient provides a Docker client
33+
func ProvideDockerClient() (*images.DockerClient, error) {
34+
return images.NewDockerClient()
35+
}
36+
3237
// ProvideImageManager provides the image manager
33-
func ProvideImageManager(cfg *config.Config) images.Manager {
34-
return images.NewManager(cfg.DataDir)
38+
func ProvideImageManager(cfg *config.Config, dockerClient *images.DockerClient) images.Manager {
39+
return images.NewManager(cfg.DataDir, dockerClient)
3540
}
3641

3742
// ProvideInstanceManager provides the instance manager

0 commit comments

Comments
 (0)