diff --git a/runtime/docker/docker.go b/runtime/docker/docker.go index 5b4a681f77..f79de0ee06 100644 --- a/runtime/docker/docker.go +++ b/runtime/docker/docker.go @@ -17,6 +17,7 @@ import ( "time" "github.com/docker/docker/api/types/image" + "github.com/docker/docker/api/types/mount" "github.com/docker/go-units" "golang.org/x/sys/unix" @@ -503,8 +504,17 @@ func (d *DockerRuntime) CreateContainer(ctx context.Context, node *types.NodeCon Soft: int64(rlimit.Max), } resources.Ulimits = []*units.Ulimit{&ulimit} + + bindMounts, volumeMounts, err := d.convertMounts(node.Binds) + if err != nil { + log.Errorf("Cannot convert mounts %v: %v", node.Binds, err) + bindMounts = nil + volumeMounts = nil + } + containerHostConfig := &container.HostConfig{ - Binds: node.Binds, + Binds: bindMounts, + Mounts: volumeMounts, PortBindings: node.PortBindings, Sysctls: node.Sysctls, Privileged: true, @@ -1150,3 +1160,58 @@ func (*DockerRuntime) GetCooCBindMounts() types.Binds { types.NewBind("/run/netns", "/run/netns", ""), } } + +// convertMounts takes a list of filesystem bind mounts from the node "binds" +// field in docker/clab format (src:dest:options) and separates them into bind +// mounts (for HostConfig.Binds) and volumes (for HostConfig.Mounts) This +// maintains backards compatibility with the existing containerlab topology +// schema where anonymous volumes are sort of supported via the "binds" field. +// While this does work, it is not technically correct as volumes (anonymous or +// named) should be specified in the "volumes" field. +func (*DockerRuntime) convertMounts(binds []string) ([]string, []mount.Mount, error) { + if len(binds) == 0 { + return nil, nil, nil + } + log.Debugf("convertMounts function received binds %v", binds) + + var bindMounts []string + var volumes []mount.Mount + + // Note: we don't do any input validation here + for _, bind := range binds { + b, err := types.NewBindFromString(bind) + if err != nil { + return nil, nil, err + } + + if b.Src() == "" { + // This is an anonymous volume - use Mount API + volMount := mount.Mount{ + Target: b.Dst(), + Type: mount.TypeVolume, + } + + // Handle volume-specific options (https://docs.docker.com/engine/storage/volumes/#options-for---volume). + if b.Mode() != "" { + options := strings.Split(b.Mode(), ",") + for _, option := range options { + if option == "ro" { + volMount.ReadOnly = true + } else if option == "volume-nocopy" { + volMount.VolumeOptions = &mount.VolumeOptions{ + NoCopy: true, + } + } + } + } + volumes = append(volumes, volMount) + log.Debugf("parsed bind %q as a volume mount %+v", bind, volMount) + } else { + // This is a bind mount - use legacy Binds format + bindMounts = append(bindMounts, bind) + log.Debugf("parsed bind %q as a bind mount", bind) + } + } + + return bindMounts, volumes, nil +} diff --git a/runtime/podman/util.go b/runtime/podman/util.go index 67b21bee28..67333e7f51 100644 --- a/runtime/podman/util.go +++ b/runtime/podman/util.go @@ -285,21 +285,20 @@ func (*PodmanRuntime) convertMounts(_ context.Context, mounts []string) ([]specs mntSpec := make([]specs.Mount, len(mounts)) // Note: we don't do any input validation here for i, mnt := range mounts { - mntSplit := strings.SplitN(mnt, ":", 3) - - if len(mntSplit) == 1 { - return nil, fmt.Errorf("%w: %s", errInvalidBind, mnt) + bind, err := types.NewBind(mnt) + if err != nil { + return nil, err } mntSpec[i] = specs.Mount{ - Destination: mntSplit[1], + Destination: bind.Dst(), Type: "bind", - Source: mntSplit[0], + Source: bind.Src(), } // when options are provided in the bind mount spec - if len(mntSplit) == 3 { - mntSpec[i].Options = strings.Split(mntSplit[2], ",") + if bind.Mode() != "" { + mntSpec[i].Options = strings.Split(bind.Mode(), ",") } } log.Debugf("convertMounts method received mounts %v and produced %+v as a result", mounts, mntSpec)