volume.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. package convert
  2. import (
  3. "fmt"
  4. "strings"
  5. "github.com/docker/docker/api/types/mount"
  6. composetypes "github.com/docker/docker/cli/compose/types"
  7. )
  8. type volumes map[string]composetypes.VolumeConfig
  9. // Volumes from compose-file types to engine api types
  10. func Volumes(serviceVolumes []string, stackVolumes volumes, namespace Namespace) ([]mount.Mount, error) {
  11. var mounts []mount.Mount
  12. for _, volumeSpec := range serviceVolumes {
  13. mount, err := convertVolumeToMount(volumeSpec, stackVolumes, namespace)
  14. if err != nil {
  15. return nil, err
  16. }
  17. mounts = append(mounts, mount)
  18. }
  19. return mounts, nil
  20. }
  21. func convertVolumeToMount(volumeSpec string, stackVolumes volumes, namespace Namespace) (mount.Mount, error) {
  22. var source, target string
  23. var mode []string
  24. // TODO: split Windows path mappings properly
  25. parts := strings.SplitN(volumeSpec, ":", 3)
  26. for _, part := range parts {
  27. if strings.TrimSpace(part) == "" {
  28. return mount.Mount{}, fmt.Errorf("invalid volume: %s", volumeSpec)
  29. }
  30. }
  31. switch len(parts) {
  32. case 3:
  33. source = parts[0]
  34. target = parts[1]
  35. mode = strings.Split(parts[2], ",")
  36. case 2:
  37. source = parts[0]
  38. target = parts[1]
  39. case 1:
  40. target = parts[0]
  41. }
  42. if source == "" {
  43. // Anonymous volume
  44. return mount.Mount{
  45. Type: mount.TypeVolume,
  46. Target: target,
  47. }, nil
  48. }
  49. // TODO: catch Windows paths here
  50. if strings.HasPrefix(source, "/") {
  51. return mount.Mount{
  52. Type: mount.TypeBind,
  53. Source: source,
  54. Target: target,
  55. ReadOnly: isReadOnly(mode),
  56. BindOptions: getBindOptions(mode),
  57. }, nil
  58. }
  59. stackVolume, exists := stackVolumes[source]
  60. if !exists {
  61. return mount.Mount{}, fmt.Errorf("undefined volume: %s", source)
  62. }
  63. var volumeOptions *mount.VolumeOptions
  64. if stackVolume.External.Name != "" {
  65. source = stackVolume.External.Name
  66. } else {
  67. volumeOptions = &mount.VolumeOptions{
  68. Labels: AddStackLabel(namespace, stackVolume.Labels),
  69. NoCopy: isNoCopy(mode),
  70. }
  71. if stackVolume.Driver != "" {
  72. volumeOptions.DriverConfig = &mount.Driver{
  73. Name: stackVolume.Driver,
  74. Options: stackVolume.DriverOpts,
  75. }
  76. }
  77. source = namespace.Scope(source)
  78. }
  79. return mount.Mount{
  80. Type: mount.TypeVolume,
  81. Source: source,
  82. Target: target,
  83. ReadOnly: isReadOnly(mode),
  84. VolumeOptions: volumeOptions,
  85. }, nil
  86. }
  87. func modeHas(mode []string, field string) bool {
  88. for _, item := range mode {
  89. if item == field {
  90. return true
  91. }
  92. }
  93. return false
  94. }
  95. func isReadOnly(mode []string) bool {
  96. return modeHas(mode, "ro")
  97. }
  98. func isNoCopy(mode []string) bool {
  99. return modeHas(mode, "nocopy")
  100. }
  101. func getBindOptions(mode []string) *mount.BindOptions {
  102. for _, item := range mode {
  103. for _, propagation := range mount.Propagations {
  104. if mount.Propagation(item) == propagation {
  105. return &mount.BindOptions{Propagation: mount.Propagation(item)}
  106. }
  107. }
  108. }
  109. return nil
  110. }