Skip to content

Commit f62cd65

Browse files
authored
Merge pull request #12 from stacklok/separate-interfaces
refactor: Separate interfaces from implementation for container runtime
2 parents c1b8b59 + d8e85d0 commit f62cd65

File tree

10 files changed

+175
-190
lines changed

10 files changed

+175
-190
lines changed

cmd/vt/list.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"github.com/stacklok/vibetool/pkg/client"
1515
"github.com/stacklok/vibetool/pkg/container"
16+
rt "github.com/stacklok/vibetool/pkg/container/runtime"
1617
"github.com/stacklok/vibetool/pkg/labels"
1718
)
1819

@@ -62,7 +63,7 @@ func listCmdFunc(_ *cobra.Command, _ []string) error {
6263
}
6364

6465
// Filter containers to only show those managed by Vibe Tool
65-
var vibeToolContainers []container.ContainerInfo
66+
var vibeToolContainers []rt.ContainerInfo
6667
for _, c := range containers {
6768
if labels.IsVibeToolContainer(c.Labels) {
6869
vibeToolContainers = append(vibeToolContainers, c)
@@ -71,7 +72,7 @@ func listCmdFunc(_ *cobra.Command, _ []string) error {
7172

7273
// Filter containers if not showing all
7374
if !listAll {
74-
var runningContainers []container.ContainerInfo
75+
var runningContainers []rt.ContainerInfo
7576
for _, c := range vibeToolContainers {
7677
if c.State == "running" {
7778
runningContainers = append(runningContainers, c)
@@ -97,7 +98,7 @@ func listCmdFunc(_ *cobra.Command, _ []string) error {
9798
}
9899

99100
// printJSONOutput prints container information in JSON format
100-
func printJSONOutput(containers []container.ContainerInfo) error {
101+
func printJSONOutput(containers []rt.ContainerInfo) error {
101102
var output []ContainerOutput
102103

103104
for _, c := range containers {
@@ -155,7 +156,7 @@ func printJSONOutput(containers []container.ContainerInfo) error {
155156
}
156157

157158
// printTextOutput prints container information in text format
158-
func printTextOutput(containers []container.ContainerInfo) {
159+
func printTextOutput(containers []rt.ContainerInfo) {
159160
// Create a tabwriter for pretty output
160161
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
161162
fmt.Fprintln(w, "CONTAINER ID\tNAME\tIMAGE\tSTATE\tTRANSPORT\tPORT\tURL")

cmd/vt/stop.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/spf13/cobra"
99

1010
"github.com/stacklok/vibetool/pkg/container"
11+
rt "github.com/stacklok/vibetool/pkg/container/runtime"
1112
"github.com/stacklok/vibetool/pkg/labels"
1213
"github.com/stacklok/vibetool/pkg/process"
1314
)
@@ -29,7 +30,7 @@ func init() {
2930
}
3031

3132
// findContainerID finds the container ID by name or ID prefix
32-
func findContainerID(ctx context.Context, runtime container.Runtime, containerName string) (string, error) {
33+
func findContainerID(ctx context.Context, runtime rt.Runtime, containerName string) (string, error) {
3334
// List containers to find the one with the given name
3435
containers, err := runtime.ListContainers(ctx)
3536
if err != nil {
@@ -59,7 +60,7 @@ func findContainerID(ctx context.Context, runtime container.Runtime, containerNa
5960
}
6061

6162
// getContainerBaseName gets the base container name from the container labels
62-
func getContainerBaseName(ctx context.Context, runtime container.Runtime, containerID string) (string, error) {
63+
func getContainerBaseName(ctx context.Context, runtime rt.Runtime, containerID string) (string, error) {
6364
containers, err := runtime.ListContainers(ctx)
6465
if err != nil {
6566
return "", fmt.Errorf("failed to list containers: %v", err)
@@ -103,7 +104,7 @@ func stopProxyProcess(containerBaseName string) {
103104
}
104105

105106
// stopContainer stops the container
106-
func stopContainer(ctx context.Context, runtime container.Runtime, containerID, containerName string) error {
107+
func stopContainer(ctx context.Context, runtime rt.Runtime, containerID, containerName string) error {
107108
fmt.Printf("Stopping container %s...\n", containerName)
108109
if err := runtime.StopContainer(ctx, containerID); err != nil {
109110
return fmt.Errorf("failed to stop container: %v", err)
Lines changed: 102 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
// Package container provides utilities for managing containers,
1+
// Package docker provides Docker-specific implementation of container runtime,
22
// including creating, starting, stopping, and monitoring containers.
3-
package container
3+
package docker
44

55
import (
66
"context"
@@ -21,6 +21,7 @@ import (
2121
"github.com/docker/docker/client"
2222
"github.com/docker/go-connections/nat"
2323

24+
"github.com/stacklok/vibetool/pkg/container/runtime"
2425
"github.com/stacklok/vibetool/pkg/permissions"
2526
)
2627

@@ -36,7 +37,7 @@ const (
3637

3738
// Client implements the Runtime interface for container operations
3839
type Client struct {
39-
runtimeType RuntimeType
40+
runtimeType runtime.Type
4041
socketPath string
4142
client *client.Client
4243
}
@@ -53,7 +54,7 @@ func NewClient(ctx context.Context) (*Client, error) {
5354
}
5455

5556
// NewClientWithSocketPath creates a new container client with a specific socket path
56-
func NewClientWithSocketPath(ctx context.Context, socketPath string, runtimeType RuntimeType) (*Client, error) {
57+
func NewClientWithSocketPath(ctx context.Context, socketPath string, runtimeType runtime.Type) (*Client, error) {
5758
// Create a custom HTTP client that uses the Unix socket
5859
httpClient := &http.Client{
5960
Transport: &http.Transport{
@@ -99,32 +100,32 @@ func (c *Client) ping(ctx context.Context) error {
99100
}
100101

101102
// findContainerSocket finds a container socket path, preferring Podman over Docker
102-
func findContainerSocket() (string, RuntimeType, error) {
103+
func findContainerSocket() (string, runtime.Type, error) {
103104
// Try Podman sockets first
104105
// Check standard Podman location
105106
if _, err := os.Stat(PodmanSocketPath); err == nil {
106-
return PodmanSocketPath, RuntimeTypePodman, nil
107+
return PodmanSocketPath, runtime.TypePodman, nil
107108
}
108109

109110
// Check XDG_RUNTIME_DIR location for Podman
110111
if xdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR"); xdgRuntimeDir != "" {
111112
xdgSocketPath := filepath.Join(xdgRuntimeDir, PodmanXDGRuntimeSocketPath)
112113
if _, err := os.Stat(xdgSocketPath); err == nil {
113-
return xdgSocketPath, RuntimeTypePodman, nil
114+
return xdgSocketPath, runtime.TypePodman, nil
114115
}
115116
}
116117

117118
// Check user-specific location for Podman
118119
if home := os.Getenv("HOME"); home != "" {
119120
userSocketPath := filepath.Join(home, ".local/share/containers/podman/machine/podman.sock")
120121
if _, err := os.Stat(userSocketPath); err == nil {
121-
return userSocketPath, RuntimeTypePodman, nil
122+
return userSocketPath, runtime.TypePodman, nil
122123
}
123124
}
124125

125126
// Try Docker socket as fallback
126127
if _, err := os.Stat(DockerSocketPath); err == nil {
127-
return DockerSocketPath, RuntimeTypeDocker, nil
128+
return DockerSocketPath, runtime.TypeDocker, nil
128129
}
129130

130131
return "", "", ErrRuntimeNotFound
@@ -142,7 +143,7 @@ func convertEnvVars(envVars map[string]string) []string {
142143
}
143144

144145
// convertMounts converts internal mount format to Docker mount format
145-
func convertMounts(mounts []Mount) []mount.Mount {
146+
func convertMounts(mounts []runtime.Mount) []mount.Mount {
146147
result := make([]mount.Mount, 0, len(mounts))
147148
for _, m := range mounts {
148149
result = append(result, mount.Mount{
@@ -174,7 +175,7 @@ func setupExposedPorts(config *container.Config, exposedPorts map[string]struct{
174175
}
175176

176177
// setupPortBindings configures port bindings for a container
177-
func setupPortBindings(hostConfig *container.HostConfig, portBindings map[string][]PortBinding) error {
178+
func setupPortBindings(hostConfig *container.HostConfig, portBindings map[string][]runtime.PortBinding) error {
178179
if len(portBindings) == 0 {
179180
return nil
180181
}
@@ -209,7 +210,7 @@ func (c *Client) CreateContainer(
209210
envVars, labels map[string]string,
210211
permissionProfile *permissions.Profile,
211212
transportType string,
212-
options *CreateContainerOptions,
213+
options *runtime.CreateContainerOptions,
213214
) (string, error) {
214215
// Get permission config from profile
215216
permissionConfig, err := c.getPermissionConfigFromProfile(permissionProfile, transportType)
@@ -281,7 +282,7 @@ func (c *Client) StartContainer(ctx context.Context, containerID string) error {
281282
}
282283

283284
// ListContainers lists containers
284-
func (c *Client) ListContainers(ctx context.Context) ([]ContainerInfo, error) {
285+
func (c *Client) ListContainers(ctx context.Context) ([]runtime.ContainerInfo, error) {
285286
// Create filter for vibetool containers
286287
filterArgs := filters.NewArgs()
287288
filterArgs.Add("label", "vibetool=true")
@@ -296,7 +297,7 @@ func (c *Client) ListContainers(ctx context.Context) ([]ContainerInfo, error) {
296297
}
297298

298299
// Convert to our ContainerInfo format
299-
result := make([]ContainerInfo, 0, len(containers))
300+
result := make([]runtime.ContainerInfo, 0, len(containers))
300301
for _, c := range containers {
301302
// Extract container name (remove leading slash)
302303
name := ""
@@ -306,9 +307,9 @@ func (c *Client) ListContainers(ctx context.Context) ([]ContainerInfo, error) {
306307
}
307308

308309
// Extract port mappings
309-
ports := make([]PortMapping, 0, len(c.Ports))
310+
ports := make([]runtime.PortMapping, 0, len(c.Ports))
310311
for _, p := range c.Ports {
311-
ports = append(ports, PortMapping{
312+
ports = append(ports, runtime.PortMapping{
312313
ContainerPort: int(p.PrivatePort),
313314
HostPort: int(p.PublicPort),
314315
Protocol: p.Type,
@@ -318,7 +319,7 @@ func (c *Client) ListContainers(ctx context.Context) ([]ContainerInfo, error) {
318319
// Convert creation time
319320
created := time.Unix(c.Created, 0)
320321

321-
result = append(result, ContainerInfo{
322+
result = append(result, runtime.ContainerInfo{
322323
ID: c.ID,
323324
Name: name,
324325
Image: c.Image,
@@ -394,19 +395,19 @@ func (c *Client) IsContainerRunning(ctx context.Context, containerID string) (bo
394395
}
395396

396397
// GetContainerInfo gets container information
397-
func (c *Client) GetContainerInfo(ctx context.Context, containerID string) (ContainerInfo, error) {
398+
func (c *Client) GetContainerInfo(ctx context.Context, containerID string) (runtime.ContainerInfo, error) {
398399
// Inspect container
399400
info, err := c.client.ContainerInspect(ctx, containerID)
400401
if err != nil {
401402
// Check if the error is because the container doesn't exist
402403
if client.IsErrNotFound(err) {
403-
return ContainerInfo{}, NewContainerError(ErrContainerNotFound, containerID, "container not found")
404+
return runtime.ContainerInfo{}, NewContainerError(ErrContainerNotFound, containerID, "container not found")
404405
}
405-
return ContainerInfo{}, NewContainerError(err, containerID, fmt.Sprintf("failed to inspect container: %v", err))
406+
return runtime.ContainerInfo{}, NewContainerError(err, containerID, fmt.Sprintf("failed to inspect container: %v", err))
406407
}
407408

408409
// Extract port mappings
409-
ports := make([]PortMapping, 0)
410+
ports := make([]runtime.PortMapping, 0)
410411
for containerPort, bindings := range info.NetworkSettings.Ports {
411412
for _, binding := range bindings {
412413
hostPort := 0
@@ -415,7 +416,7 @@ func (c *Client) GetContainerInfo(ctx context.Context, containerID string) (Cont
415416
fmt.Printf("Warning: Failed to parse host port %s: %v\n", binding.HostPort, err)
416417
}
417418

418-
ports = append(ports, PortMapping{
419+
ports = append(ports, runtime.PortMapping{
419420
ContainerPort: containerPort.Int(),
420421
HostPort: hostPort,
421422
Protocol: containerPort.Proto(),
@@ -429,7 +430,7 @@ func (c *Client) GetContainerInfo(ctx context.Context, containerID string) (Cont
429430
created = time.Time{} // Use zero time if parsing fails
430431
}
431432

432-
return ContainerInfo{
433+
return runtime.ContainerInfo{
433434
ID: info.ID,
434435
Name: strings.TrimPrefix(info.Name, "/"),
435436
Image: info.Config.Image,
@@ -544,7 +545,7 @@ func (c *Client) PullImage(ctx context.Context, imageName string) error {
544545
// getPermissionConfigFromProfile converts a permission profile to a container permission config
545546
// with transport-specific settings (internal function)
546547
// addReadOnlyMounts adds read-only mounts to the permission config
547-
func (*Client) addReadOnlyMounts(config *PermissionConfig, mounts []permissions.MountDeclaration) {
548+
func (*Client) addReadOnlyMounts(config *runtime.PermissionConfig, mounts []permissions.MountDeclaration) {
548549
for _, mountDecl := range mounts {
549550
source, target, err := mountDecl.Parse()
550551
if err != nil {
@@ -565,7 +566,7 @@ func (*Client) addReadOnlyMounts(config *PermissionConfig, mounts []permissions.
565566
continue
566567
}
567568

568-
config.Mounts = append(config.Mounts, Mount{
569+
config.Mounts = append(config.Mounts, runtime.Mount{
569570
Source: source,
570571
Target: target,
571572
ReadOnly: true,
@@ -574,7 +575,7 @@ func (*Client) addReadOnlyMounts(config *PermissionConfig, mounts []permissions.
574575
}
575576

576577
// addReadWriteMounts adds read-write mounts to the permission config
577-
func (*Client) addReadWriteMounts(config *PermissionConfig, mounts []permissions.MountDeclaration) {
578+
func (*Client) addReadWriteMounts(config *runtime.PermissionConfig, mounts []permissions.MountDeclaration) {
578579
for _, mountDecl := range mounts {
579580
source, target, err := mountDecl.Parse()
580581
if err != nil {
@@ -608,7 +609,7 @@ func (*Client) addReadWriteMounts(config *PermissionConfig, mounts []permissions
608609

609610
// If not already mounted, add a new mount
610611
if !alreadyMounted {
611-
config.Mounts = append(config.Mounts, Mount{
612+
config.Mounts = append(config.Mounts, runtime.Mount{
612613
Source: source,
613614
Target: target,
614615
ReadOnly: false,
@@ -640,10 +641,14 @@ func (*Client) needsNetworkAccess(profile *permissions.Profile, transportType st
640641
return false
641642
}
642643

643-
func (c *Client) getPermissionConfigFromProfile(profile *permissions.Profile, transportType string) (*PermissionConfig, error) {
644+
// getPermissionConfigFromProfile converts a permission profile to a container permission config
645+
func (c *Client) getPermissionConfigFromProfile(
646+
profile *permissions.Profile,
647+
transportType string,
648+
) (*runtime.PermissionConfig, error) {
644649
// Start with a default permission config
645-
config := &PermissionConfig{
646-
Mounts: []Mount{},
650+
config := &runtime.PermissionConfig{
651+
Mounts: []runtime.Mount{},
647652
NetworkMode: "none",
648653
CapDrop: []string{"ALL"},
649654
CapAdd: []string{},
@@ -666,3 +671,70 @@ func (c *Client) getPermissionConfigFromProfile(profile *permissions.Profile, tr
666671

667672
return config, nil
668673
}
674+
675+
// Error types for container operations
676+
var (
677+
// ErrContainerNotFound is returned when a container is not found
678+
ErrContainerNotFound = fmt.Errorf("container not found")
679+
680+
// ErrContainerAlreadyExists is returned when a container already exists
681+
ErrContainerAlreadyExists = fmt.Errorf("container already exists")
682+
683+
// ErrContainerNotRunning is returned when a container is not running
684+
ErrContainerNotRunning = fmt.Errorf("container not running")
685+
686+
// ErrContainerAlreadyRunning is returned when a container is already running
687+
ErrContainerAlreadyRunning = fmt.Errorf("container already running")
688+
689+
// ErrRuntimeNotFound is returned when a container runtime is not found
690+
ErrRuntimeNotFound = fmt.Errorf("container runtime not found")
691+
692+
// ErrInvalidRuntimeType is returned when an invalid runtime type is specified
693+
ErrInvalidRuntimeType = fmt.Errorf("invalid runtime type")
694+
695+
// ErrAttachFailed is returned when attaching to a container fails
696+
ErrAttachFailed = fmt.Errorf("failed to attach to container")
697+
698+
// ErrContainerExited is returned when a container has exited unexpectedly
699+
ErrContainerExited = fmt.Errorf("container exited unexpectedly")
700+
)
701+
702+
// ContainerError represents an error related to container operations
703+
type ContainerError struct {
704+
// Err is the underlying error
705+
Err error
706+
// ContainerID is the ID of the container
707+
ContainerID string
708+
// Message is an optional error message
709+
Message string
710+
}
711+
712+
// Error returns the error message
713+
func (e *ContainerError) Error() string {
714+
if e.Message != "" {
715+
if e.ContainerID != "" {
716+
return fmt.Sprintf("%s: %s (container: %s)", e.Err, e.Message, e.ContainerID)
717+
}
718+
return fmt.Sprintf("%s: %s", e.Err, e.Message)
719+
}
720+
721+
if e.ContainerID != "" {
722+
return fmt.Sprintf("%s (container: %s)", e.Err, e.ContainerID)
723+
}
724+
725+
return e.Err.Error()
726+
}
727+
728+
// Unwrap returns the underlying error
729+
func (e *ContainerError) Unwrap() error {
730+
return e.Err
731+
}
732+
733+
// NewContainerError creates a new container error
734+
func NewContainerError(err error, containerID, message string) *ContainerError {
735+
return &ContainerError{
736+
Err: err,
737+
ContainerID: containerID,
738+
Message: message,
739+
}
740+
}

0 commit comments

Comments
 (0)