diff --git a/format/volume.go b/format/volume.go index 0083a62c..0fa879f6 100644 --- a/format/volume.go +++ b/format/volume.go @@ -41,6 +41,29 @@ func ParseVolume(spec string) (types.ServiceVolumeConfig, error) { return volume, nil } + if strings.Count(spec, "/dev") == 2 { + parts := strings.Split(spec, ":/dev") + source := parts[0] + target := "/dev" + parts[1] + + if err := populateFieldFromBuffer(':', []rune(source), &volume); err != nil { + populateType(&volume) + return volume, fmt.Errorf("invalid spec: %s: %w", spec, err) + } + + if err := populateFieldFromBuffer(endOfSpec, []rune(target), &volume); err != nil { + populateType(&volume) + return volume, fmt.Errorf("invalid spec: %s: %w", spec, err) + } + + targetParts := strings.Split(target, ":") + if len(targetParts) > 1 { + parseOptions(targetParts[len(targetParts)-1], &volume) + } + populateType(&volume) + return volume, nil + } + var buffer []rune for _, char := range spec + string(endOfSpec) { switch { @@ -83,6 +106,11 @@ func populateFieldFromBuffer(char rune, buffer []rune, volume *types.ServiceVolu case char == ':': return errors.New("too many colons") } + parseOptions(strBuffer, volume) + return nil +} + +func parseOptions(strBuffer string, volume *types.ServiceVolumeConfig) { for _, option := range strings.Split(strBuffer, ",") { switch option { case "ro": @@ -98,16 +126,6 @@ func populateFieldFromBuffer(char rune, buffer []rune, volume *types.ServiceVolu // ignore unknown options } } - return nil -} - -var Propagations = []string{ - types.PropagationRPrivate, - types.PropagationPrivate, - types.PropagationRShared, - types.PropagationShared, - types.PropagationRSlave, - types.PropagationSlave, } type setBindOptionFunc func(bind *types.ServiceVolumeBind, option string) diff --git a/format/volume_test.go b/format/volume_test.go index 3e6dc336..3ac52860 100644 --- a/format/volume_test.go +++ b/format/volume_test.go @@ -97,6 +97,21 @@ func TestParseVolumeRelativeBindMountWindows(t *testing.T) { } } +func TestParseVolumeWithDeviceWithMultipleColons(t *testing.T) { + volume, err := ParseVolume("/dev/usb-0:1.3:1.0-port0:/dev/usb-0:1.3:1.0-port0") + expected := types.ServiceVolumeConfig{ + Type: "bind", + Source: "/dev/usb-0:1.3:1.0-port0", + Target: "/dev/usb-0:1.3:1.0-port0", + Bind: &types.ServiceVolumeBind{ + CreateHostPath: true, + Propagation: "", + }, + } + assert.NilError(t, err) + assert.Check(t, is.DeepEqual(expected, volume)) +} + func TestParseVolumeWithBindOptions(t *testing.T) { volume, err := ParseVolume("/source:/target:slave") expected := types.ServiceVolumeConfig{ diff --git a/override/uncity.go b/override/uncity.go index 620a70a2..303b10c7 100644 --- a/override/uncity.go +++ b/override/uncity.go @@ -42,7 +42,7 @@ func init() { unique["services.*.build.labels"] = keyValueIndexer unique["services.*.cap_add"] = keyValueIndexer unique["services.*.cap_drop"] = keyValueIndexer - unique["services.*.devices"] = volumeIndexer + unique["services.*.devices"] = devicesIndexer unique["services.*.configs"] = mountIndexer("") unique["services.*.deploy.labels"] = keyValueIndexer unique["services.*.dns"] = keyValueIndexer @@ -139,6 +139,24 @@ func volumeIndexer(y any, p tree.Path) (string, error) { return "", nil } +func devicesIndexer(y any, p tree.Path) (string, error) { + switch value := y.(type) { + case map[string]any: + target, ok := value["target"].(string) + if !ok { + return "", fmt.Errorf("service devices %s is missing a mount target", p) + } + return target, nil + case string: + volume, err := format.ParseVolume(value) + if err != nil { + return "", err + } + return volume.Target, nil + } + return "", nil +} + func exposeIndexer(a any, path tree.Path) (string, error) { switch v := a.(type) { case string: