| package convert |
| |
| import ( |
| "fmt" |
| "strings" |
| |
| "github.com/docker/docker/api/types/mount" |
| composetypes "github.com/docker/docker/cli/compose/types" |
| ) |
| |
| type volumes map[string]composetypes.VolumeConfig |
| |
| // Volumes from compose-file types to engine api types |
| func Volumes(serviceVolumes []string, stackVolumes volumes, namespace Namespace) ([]mount.Mount, error) { |
| var mounts []mount.Mount |
| |
| for _, volumeSpec := range serviceVolumes { |
| mount, err := convertVolumeToMount(volumeSpec, stackVolumes, namespace) |
| if err != nil { |
| return nil, err |
| } |
| mounts = append(mounts, mount) |
| } |
| return mounts, nil |
| } |
| |
| func convertVolumeToMount(volumeSpec string, stackVolumes volumes, namespace Namespace) (mount.Mount, error) { |
| var source, target string |
| var mode []string |
| |
| // TODO: split Windows path mappings properly |
| parts := strings.SplitN(volumeSpec, ":", 3) |
| |
| for _, part := range parts { |
| if strings.TrimSpace(part) == "" { |
| return mount.Mount{}, fmt.Errorf("invalid volume: %s", volumeSpec) |
| } |
| } |
| |
| switch len(parts) { |
| case 3: |
| source = parts[0] |
| target = parts[1] |
| mode = strings.Split(parts[2], ",") |
| case 2: |
| source = parts[0] |
| target = parts[1] |
| case 1: |
| target = parts[0] |
| } |
| |
| if source == "" { |
| // Anonymous volume |
| return mount.Mount{ |
| Type: mount.TypeVolume, |
| Target: target, |
| }, nil |
| } |
| |
| // TODO: catch Windows paths here |
| if strings.HasPrefix(source, "/") { |
| return mount.Mount{ |
| Type: mount.TypeBind, |
| Source: source, |
| Target: target, |
| ReadOnly: isReadOnly(mode), |
| BindOptions: getBindOptions(mode), |
| }, nil |
| } |
| |
| stackVolume, exists := stackVolumes[source] |
| if !exists { |
| return mount.Mount{}, fmt.Errorf("undefined volume: %s", source) |
| } |
| |
| var volumeOptions *mount.VolumeOptions |
| if stackVolume.External.Name != "" { |
| source = stackVolume.External.Name |
| } else { |
| volumeOptions = &mount.VolumeOptions{ |
| Labels: AddStackLabel(namespace, stackVolume.Labels), |
| NoCopy: isNoCopy(mode), |
| } |
| |
| if stackVolume.Driver != "" { |
| volumeOptions.DriverConfig = &mount.Driver{ |
| Name: stackVolume.Driver, |
| Options: stackVolume.DriverOpts, |
| } |
| } |
| source = namespace.Scope(source) |
| } |
| return mount.Mount{ |
| Type: mount.TypeVolume, |
| Source: source, |
| Target: target, |
| ReadOnly: isReadOnly(mode), |
| VolumeOptions: volumeOptions, |
| }, nil |
| } |
| |
| func modeHas(mode []string, field string) bool { |
| for _, item := range mode { |
| if item == field { |
| return true |
| } |
| } |
| return false |
| } |
| |
| func isReadOnly(mode []string) bool { |
| return modeHas(mode, "ro") |
| } |
| |
| func isNoCopy(mode []string) bool { |
| return modeHas(mode, "nocopy") |
| } |
| |
| func getBindOptions(mode []string) *mount.BindOptions { |
| for _, item := range mode { |
| for _, propagation := range mount.Propagations { |
| if mount.Propagation(item) == propagation { |
| return &mount.BindOptions{Propagation: mount.Propagation(item)} |
| } |
| } |
| } |
| return nil |
| } |