Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions containerdUtils/containerdUtils.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
//
// Copyright: (C) 2024 Nestybox Inc. All rights reserved.
//

package containerdUtils

import (
"fmt"
"os"

"github.com/BurntSushi/toml"
"github.com/pelletier/go-toml/v2"
)

// Location of containerd config file
Expand Down Expand Up @@ -53,7 +54,7 @@ func parseDataRoot(path string) (string, error) {
defer f.Close()

// parse the "root"
if _, err := toml.NewDecoder(f).Decode(&config); err != nil {
if err := toml.NewDecoder(f).Decode(&config); err != nil {
return "", fmt.Errorf("could not decode %s: %w", path, err)
}

Expand Down
17 changes: 8 additions & 9 deletions containerdUtils/containerdUtils_test.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
package containerdUtils

import (
"io/ioutil"
"os"
"testing"
)

func TestGetDataRoot(t *testing.T) {
tests := []struct {
name string
configPath string
configContent string
expectedRoot string
expectError bool
name string
configPath string
configContent string
expectedRoot string
expectError bool
}{
{
name: "Config with root entry",
name: "Config with root entry",
configPath: "/etc/containerd/containerd.toml",
configContent: `
version = 2
Expand All @@ -41,7 +40,7 @@ imports = ["/etc/containerd/runtime_*.toml", "./debug.toml"]
expectError: false,
},
{
name: "Config without root entry",
name: "Config without root entry",
configPath: "/etc/containerd/config.toml",
configContent: `
version = 2
Expand Down Expand Up @@ -74,7 +73,7 @@ imports = ["/etc/containerd/runtime_*.toml", "./debug.toml"]

// Create a temporary config file if content is provided
if tt.configContent != "" {
tmpFile, err := ioutil.TempFile("", "config-*.toml")
tmpFile, err := os.CreateTemp("", "config-*.toml")
if err != nil {
t.Fatalf("Failed to create temp file: %v", err)
}
Expand Down
2 changes: 1 addition & 1 deletion containerdUtils/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ module github.com/nestybox/sysbox-libs/containerdUtils

go 1.21.3

require github.com/BurntSushi/toml v1.4.0
require github.com/pelletier/go-toml/v2 v2.3.1
4 changes: 2 additions & 2 deletions containerdUtils/go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc=
github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
60 changes: 21 additions & 39 deletions dockerUtils/dockerUtils.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,12 @@ import (
"fmt"
"os"
"path/filepath"
"slices"
"strings"
"time"

"github.com/nestybox/sysbox-libs/utils"

"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/client"
"github.com/moby/moby/api/types/volume"
"github.com/moby/moby/client"
)

// Set to true during testing only
Expand Down Expand Up @@ -61,26 +58,18 @@ type Docker struct {

// DockerConnect establishes a session with the Docker daemon.
func DockerConnect() (*Docker, error) {

// Profiling shows Docker takes on average ~10ms to respond to a single
// client; with up to 1000 concurrent clients, it takes ~400ms to respond on
// average (see the TestDockerConnectDelay() test in dockerUtils_test.go).
// Thus we set the timeout to 1 sec; if it doesn't respond in this time, it
// likely means Docker is not present.
timeout := time.Duration(1 * time.Second)

cli, err := client.NewClientWithOpts(
client.FromEnv,
client.WithTimeout(timeout),
client.WithAPIVersionNegotiation(),
)

cli, err := client.New(client.FromEnv, client.WithTimeout(1*time.Second))
if err != nil {
return nil, newDockerErr(DockerConnErr, fmt.Sprintf("failed to connect to Docker API: %v", err))
}

// Get the docker data root dir (usually /var/lib/docker)
info, err := cli.Info(context.Background())
res, err := cli.Info(context.Background(), client.InfoOptions{})
if err != nil {
err2 := cli.Close()
if err2 != nil {
Expand All @@ -91,7 +80,7 @@ func DockerConnect() (*Docker, error) {

return &Docker{
cli: cli,
dataRoot: info.DockerRootDir,
dataRoot: res.Info.DockerRootDir,
}, nil
}

Expand All @@ -111,21 +100,17 @@ func (d *Docker) GetDataRoot() string {
// ContainerGetImageID returns the image ID of the given container; may be
// called during container creation.
func (d *Docker) ContainerGetImageID(containerID string) (string, error) {

filter := filters.NewArgs()
filter.Add("id", containerID)

containers, err := d.cli.ContainerList(context.Background(), container.ListOptions{
res, err := d.cli.ContainerList(context.Background(), client.ContainerListOptions{
All: true, // required since container may not yet be running
Filters: filter,
Filters: make(client.Filters).Add("id", containerID),
})

if err != nil {
return "", newDockerErr(DockerContInfoErr, err.Error())
}

containers := res.Items
if len(containers) == 0 {
return "", newDockerErr(DockerContInfoErr, fmt.Sprintf("container %s found", containerID))
return "", newDockerErr(DockerContInfoErr, fmt.Sprintf("container %s not found", containerID))
} else if len(containers) > 1 {
return "", newDockerErr(DockerContInfoErr, fmt.Sprintf("more than one container matches ID %s: %v", containerID, containers))
}
Expand All @@ -136,14 +121,14 @@ func (d *Docker) ContainerGetImageID(containerID string) (string, error) {
// ContainerGetInfo returns info for the given container. Must be called
// after the container is created.
func (d *Docker) ContainerGetInfo(containerID string) (*ContainerInfo, error) {

info, err := d.cli.ContainerInspect(context.Background(), containerID)
res, err := d.cli.ContainerInspect(context.Background(), containerID, client.ContainerInspectOptions{})
if err != nil {
return nil, err
}
info := res.Container

rootfs := ""
if info.GraphDriver.Name == "overlay2" {
if info.GraphDriver != nil && info.GraphDriver.Name == "overlay2" {
rootfs = info.GraphDriver.Data["MergedDir"]
}

Expand All @@ -156,21 +141,18 @@ func (d *Docker) ContainerGetInfo(containerID string) (*ContainerInfo, error) {
// ListVolumesAt lists Docker volumes with the given host mount point (which implies
// volumes using the "local" driver only).
func (d *Docker) ListVolumesAt(mountPoint string) ([]volume.Volume, error) {

filterArgs := filters.NewArgs()
filterArgs.Add("driver", "local")

// List volumes using the filter
volumeList, err := d.cli.VolumeList(context.Background(), volume.ListOptions{Filters: filterArgs})
res, err := d.cli.VolumeList(context.Background(), client.VolumeListOptions{
Filters: make(client.Filters).Add("driver", "local"),
})
if err != nil {
return nil, err
}

// Filter volumes by mount point
var filteredVolumes []volume.Volume
for _, vol := range volumeList.Volumes {
for _, vol := range res.Items {
if vol.Mountpoint == mountPoint {
filteredVolumes = append(filteredVolumes, *vol)
filteredVolumes = append(filteredVolumes, vol)
break
}
}
Expand All @@ -188,7 +170,7 @@ func ContainerIsDocker(id, rootfs string) (bool, error) {
if err == nil {
defer docker.Disconnect()
_, err := docker.ContainerGetImageID(id)
return (err == nil), nil
return err == nil, nil
}

// The connection to Docker can fail when containers are restarted
Expand Down Expand Up @@ -221,7 +203,7 @@ func isDockerRootfs(rootfs string) (bool, error) {
maxFilesPerDir := 30 // the docker data root dir has typically 10->20 subdirs in it
path := rootfs

for i := 0; i < searchLevels; i++ {
for range searchLevels {
path = filepath.Dir(path)

dir, err := os.Open(path)
Expand All @@ -236,7 +218,7 @@ func isDockerRootfs(rootfs string) (bool, error) {

isDocker := true
for _, dockerDir := range dockerDirs {
if !utils.StringSliceContains(filenames, dockerDir) {
if !slices.Contains(filenames, dockerDir) {
isDocker = false
}
}
Expand Down
31 changes: 20 additions & 11 deletions dockerUtils/dockerUtils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ import (
"testing"
"time"

"github.com/docker/docker/api/types/volume"
"github.com/stretchr/testify/assert"
"github.com/moby/moby/client"
)

func TestGetContainer(t *testing.T) {
Expand Down Expand Up @@ -107,28 +106,38 @@ func TestListVolumesAt(t *testing.T) {
// Prepare by creating a volume to test against
volName := "testvolume"
ctx := context.Background()
_, err = docker.cli.VolumeCreate(ctx, volume.CreateOptions{Name: volName, Driver: "local"})
assert.NoError(t, err, "should be able to create a volume")
_, err = docker.cli.VolumeCreate(ctx, client.VolumeCreateOptions{Name: volName, Driver: "local"})
if err != nil {
t.Fatalf("should be able to create a volume: %v", err)
}

// Clean up after test
defer func() {
err := docker.cli.VolumeRemove(ctx, volName, true)
assert.NoError(t, err, "should be able to remove the volume")
_, err := docker.cli.VolumeRemove(ctx, volName, client.VolumeRemoveOptions{Force: true})
if err != nil {
t.Fatalf("should be able to remove the volume: %v", err)
}
}()

// Test the function
mountPoint := filepath.Join("/var/lib/docker/volumes/", volName, "_data")
volumes, err := docker.ListVolumesAt(mountPoint)
assert.NoError(t, err, "should not have an error listing volumes")
assert.True(t, len(volumes) > 0, "should find at least one volume")
if err != nil {
t.Fatalf("should not have an error listing volumes: %v", err)
}
if len(volumes) == 0 {
t.Fatalf("should have at least one volume")
}
found := false
for _, vol := range volumes {
if vol.Name == volName && vol.Mountpoint == mountPoint {
found = true
break
}
}
assert.True(t, found, "should find the test volume in the filtered list")
if !found {
t.Fatalf("should have found volume %s in %s", volName, mountPoint)
}
}

func TestDockerConnectDelay(t *testing.T) {
Expand All @@ -138,15 +147,15 @@ func TestDockerConnectDelay(t *testing.T) {
maxDelay := 500 * time.Millisecond
delayCh := make(chan time.Duration, numWorkers)

for i := 0; i < numWorkers; i++ {
for range numWorkers {
wg.Add(1)
go dockerConnectWorker(&wg, delayCh)
}

wg.Wait()

sum := 0 * time.Second
for i := 0; i < numWorkers; i++ {
for range numWorkers {
sum += <-delayCh
}
avg := sum / time.Duration(numWorkers)
Expand Down
49 changes: 16 additions & 33 deletions dockerUtils/go.mod
Original file line number Diff line number Diff line change
@@ -1,46 +1,29 @@
module github.com/nestybox/sysbox-libs/dockerUtils

go 1.21

toolchain go1.21.3
go 1.24

require (
github.com/docker/docker v26.0.0+incompatible
github.com/nestybox/sysbox-libs/utils v0.0.0-00010101000000-000000000000
github.com/stretchr/testify v1.9.0
github.com/moby/moby/api v1.54.2
github.com/moby/moby/client v0.4.1
)

require (
github.com/Microsoft/go-winio v0.4.16 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/docker/go-connections v0.7.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/opencontainers/runtime-spec v1.0.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
go.opentelemetry.io/otel v1.26.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0 // indirect
go.opentelemetry.io/otel/metric v1.26.0 // indirect
go.opentelemetry.io/otel/sdk v1.26.0 // indirect
go.opentelemetry.io/otel/trace v1.26.0 // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gotest.tools/v3 v3.0.3 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
golang.org/x/sys v0.33.0 // indirect
)

replace github.com/nestybox/sysbox-libs/utils => ../utils
Loading