Skip to content

Commit

Permalink
Merge pull request #2 from thediveo/develop
Browse files Browse the repository at this point in the history
fix: remove dep on docker/cli (#1)
  • Loading branch information
thediveo authored Jan 30, 2024
2 parents aa2d408 + c20a320 commit 3b35f03
Show file tree
Hide file tree
Showing 7 changed files with 551 additions and 19 deletions.
28 changes: 16 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,18 +118,6 @@ battle-proven tools for using Docker images and containers in Go tests?
policy](https://golang.org/doc/devel/release.html#policy), that is, major
versions _N_ and _N_-1 (where _N_ is the current major version).

## Hacking It

This project comes with comprehensive unit tests, also covering leak checks
using Gomega's
[`gleak`](https://onsi.github.io/gomega/#codegleakcode-finding-leaked-goroutines)
package.

> **Note:** do **not run parallel tests** for multiple packages. `make test`
> ensures to run all package tests always sequentially, but in case you run `go
test` yourself, please don't forget `-p 1` when testing multiple packages in
> one, _erm_, go.
## Contributing

Please see [CONTRIBUTING.md](CONTRIBUTING.md).
Expand All @@ -138,3 +126,19 @@ Please see [CONTRIBUTING.md](CONTRIBUTING.md).

`morbyd` is Copyright 2024 Harald Albrecht, and licensed under the Apache
License, Version 2.0.

The package `github.com/thediveo/morbyd/run/dockercli` is [Copyright 2013-2017
Docker, Inc.](https://github.com/moby/moby/blob/v25.0.1/LICENSE) and licensed
under the Apache License Version 2.0, with the elements listed below coming from
the [github.com/docker/cli](https://github.com/docker/cli) module in order to
work around import dependency versioning problems due to `@docker/cli` using a
managed `vendor/` directory, but not providing a `go.mod` and the associated
guarantees:
- [opts/mount.go](https://github.com/docker/cli/blob/v25.0.1/opts/mount.go);
removed the logrus dependency.
- [opts/network.go](https://github.com/docker/cli/blob/v25.0.1/opts/network.go)
- a subset of
[cli/compose/types/types.go](https://github.com/docker/cli/blob/v25.0.1/cli/compose/types/types.go):
type `ServiceVolumeConfig`, with direct dependency types `ServiceVolumeBind`,
`ServiceVolumeVolume`, `ServiceVolumeTmpfs`, and `ServiceVolumeCluster`.
- [cli/compose/loader/volume.go](https://github.com/docker/cli/blob/v25.0.1/cli/compose/loader/volume.go)
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ go 1.20
require (
github.com/docker/cli v25.0.1+incompatible
github.com/docker/docker v25.0.1+incompatible
github.com/docker/go-units v0.5.0
github.com/moby/buildkit v0.12.4
github.com/onsi/ginkgo/v2 v2.15.0
github.com/onsi/gomega v1.31.1
github.com/pkg/errors v0.9.1
github.com/thediveo/fdooze v0.3.1
github.com/thediveo/success v1.0.2
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
Expand All @@ -21,7 +23,6 @@ require (
github.com/containerd/log v0.1.0 // indirect
github.com/distribution/reference v0.5.0 // indirect
github.com/docker/go-connections v0.5.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/stdr v1.2.2 // indirect
Expand All @@ -40,7 +41,6 @@ require (
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc3 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
Expand Down
224 changes: 224 additions & 0 deletions run/dockercli/mountopt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
// Source: https://github.com/docker/cli/blob/v25.0.1/opts/mount.go
//
// Apache License 2.0, https://github.com/docker/cli/blob/master/LICENSE
//
// Removed: logrus dependency
package dockercli

import (
"encoding/csv"
"errors"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"

mounttypes "github.com/docker/docker/api/types/mount"
"github.com/docker/go-units"
)

// MountOpt is a Value type for parsing mounts
type MountOpt struct {
values []mounttypes.Mount
}

// Set a new mount value
//
//nolint:gocyclo
func (m *MountOpt) Set(value string) error {
csvReader := csv.NewReader(strings.NewReader(value))
fields, err := csvReader.Read()
if err != nil {
return err
}

mount := mounttypes.Mount{}

volumeOptions := func() *mounttypes.VolumeOptions {
if mount.VolumeOptions == nil {
mount.VolumeOptions = &mounttypes.VolumeOptions{
Labels: make(map[string]string),
}
}
if mount.VolumeOptions.DriverConfig == nil {
mount.VolumeOptions.DriverConfig = &mounttypes.Driver{}
}
return mount.VolumeOptions
}

bindOptions := func() *mounttypes.BindOptions {
if mount.BindOptions == nil {
mount.BindOptions = new(mounttypes.BindOptions)
}
return mount.BindOptions
}

tmpfsOptions := func() *mounttypes.TmpfsOptions {
if mount.TmpfsOptions == nil {
mount.TmpfsOptions = new(mounttypes.TmpfsOptions)
}
return mount.TmpfsOptions
}

setValueOnMap := func(target map[string]string, value string) {
k, v, _ := strings.Cut(value, "=")
if k != "" {
target[k] = v
}
}

mount.Type = mounttypes.TypeVolume // default to volume mounts
// Set writable as the default
for _, field := range fields {
key, val, ok := strings.Cut(field, "=")

// TODO(thaJeztah): these options should not be case-insensitive.
key = strings.ToLower(key)

if !ok {
switch key {
case "readonly", "ro":
mount.ReadOnly = true
continue
case "volume-nocopy":
volumeOptions().NoCopy = true
continue
case "bind-nonrecursive":
bindOptions().NonRecursive = true
continue
default:
return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
}
}

switch key {
case "type":
mount.Type = mounttypes.Type(strings.ToLower(val))
case "source", "src":
mount.Source = val
if strings.HasPrefix(val, "."+string(filepath.Separator)) || val == "." {
if abs, err := filepath.Abs(val); err == nil {
mount.Source = abs
}
}
case "target", "dst", "destination":
mount.Target = val
case "readonly", "ro":
mount.ReadOnly, err = strconv.ParseBool(val)
if err != nil {
return fmt.Errorf("invalid value for %s: %s", key, val)
}
case "consistency":
mount.Consistency = mounttypes.Consistency(strings.ToLower(val))
case "bind-propagation":
bindOptions().Propagation = mounttypes.Propagation(strings.ToLower(val))
case "bind-nonrecursive":
bindOptions().NonRecursive, err = strconv.ParseBool(val)
if err != nil {
return fmt.Errorf("invalid value for %s: %s", key, val)
}
case "bind-recursive":
switch val {
case "enabled": // read-only mounts are recursively read-only if Engine >= v25 && kernel >= v5.12, otherwise writable
// NOP
case "disabled": // alias of bind-nonrecursive=true
bindOptions().NonRecursive = true
case "writable": // conforms to the default read-only bind-mount of Docker v24; read-only mounts are recursively mounted but not recursively read-only
bindOptions().ReadOnlyNonRecursive = true
case "readonly": // force recursively read-only, or raise an error
bindOptions().ReadOnlyForceRecursive = true
// TODO: implicitly set propagation and error if the user specifies a propagation in a future refactor/UX polish pass
// https://github.com/docker/cli/pull/4316#discussion_r1341974730
default:
return fmt.Errorf("invalid value for %s: %s (must be \"enabled\", \"disabled\", \"writable\", or \"readonly\")",
key, val)
}
case "volume-nocopy":
volumeOptions().NoCopy, err = strconv.ParseBool(val)
if err != nil {
return fmt.Errorf("invalid value for volume-nocopy: %s", val)
}
case "volume-label":
setValueOnMap(volumeOptions().Labels, val)
case "volume-driver":
volumeOptions().DriverConfig.Name = val
case "volume-opt":
if volumeOptions().DriverConfig.Options == nil {
volumeOptions().DriverConfig.Options = make(map[string]string)
}
setValueOnMap(volumeOptions().DriverConfig.Options, val)
case "tmpfs-size":
sizeBytes, err := units.RAMInBytes(val)
if err != nil {
return fmt.Errorf("invalid value for %s: %s", key, val)
}
tmpfsOptions().SizeBytes = sizeBytes
case "tmpfs-mode":
ui64, err := strconv.ParseUint(val, 8, 32)
if err != nil {
return fmt.Errorf("invalid value for %s: %s", key, val)
}
tmpfsOptions().Mode = os.FileMode(ui64)
default:
return fmt.Errorf("unexpected key '%s' in '%s'", key, field)
}
}

if mount.Type == "" {
return fmt.Errorf("type is required")
}

if mount.Target == "" {
return fmt.Errorf("target is required")
}

if mount.VolumeOptions != nil && mount.Type != mounttypes.TypeVolume {
return fmt.Errorf("cannot mix 'volume-*' options with mount type '%s'", mount.Type)
}
if mount.BindOptions != nil && mount.Type != mounttypes.TypeBind {
return fmt.Errorf("cannot mix 'bind-*' options with mount type '%s'", mount.Type)
}
if mount.TmpfsOptions != nil && mount.Type != mounttypes.TypeTmpfs {
return fmt.Errorf("cannot mix 'tmpfs-*' options with mount type '%s'", mount.Type)
}

if mount.BindOptions != nil {
if mount.BindOptions.ReadOnlyNonRecursive {
if !mount.ReadOnly {
return errors.New("option 'bind-recursive=writable' requires 'readonly' to be specified in conjunction")
}
}
if mount.BindOptions.ReadOnlyForceRecursive {
if !mount.ReadOnly {
return errors.New("option 'bind-recursive=readonly' requires 'readonly' to be specified in conjunction")
}
if mount.BindOptions.Propagation != mounttypes.PropagationRPrivate {
return errors.New("option 'bind-recursive=readonly' requires 'bind-propagation=rprivate' to be specified in conjunction")
}
}
}

m.values = append(m.values, mount)
return nil
}

// Type returns the type of this option
func (m *MountOpt) Type() string {
return "mount"
}

// String returns a string repr of this option
func (m *MountOpt) String() string {
mounts := []string{}
for _, mount := range m.values {
repr := fmt.Sprintf("%s %s %s", mount.Type, mount.Source, mount.Target)
mounts = append(mounts, repr)
}
return strings.Join(mounts, ", ")
}

// Value returns the mounts
func (m *MountOpt) Value() []mounttypes.Mount {
return m.values
}
Loading

0 comments on commit 3b35f03

Please sign in to comment.