github.com/ali-iotechsys/cli@v20.10.0+incompatible/cli/compose/loader/volume.go (about) 1 package loader 2 3 import ( 4 "strings" 5 "unicode" 6 "unicode/utf8" 7 8 "github.com/docker/cli/cli/compose/types" 9 "github.com/docker/docker/api/types/mount" 10 "github.com/pkg/errors" 11 ) 12 13 const endOfSpec = rune(0) 14 15 // ParseVolume parses a volume spec without any knowledge of the target platform 16 func ParseVolume(spec string) (types.ServiceVolumeConfig, error) { 17 volume := types.ServiceVolumeConfig{} 18 19 switch len(spec) { 20 case 0: 21 return volume, errors.New("invalid empty volume spec") 22 case 1, 2: 23 volume.Target = spec 24 volume.Type = string(mount.TypeVolume) 25 return volume, nil 26 } 27 28 buffer := []rune{} 29 for _, char := range spec + string(endOfSpec) { 30 switch { 31 case isWindowsDrive(buffer, char): 32 buffer = append(buffer, char) 33 case char == ':' || char == endOfSpec: 34 if err := populateFieldFromBuffer(char, buffer, &volume); err != nil { 35 populateType(&volume) 36 return volume, errors.Wrapf(err, "invalid spec: %s", spec) 37 } 38 buffer = []rune{} 39 default: 40 buffer = append(buffer, char) 41 } 42 } 43 44 populateType(&volume) 45 return volume, nil 46 } 47 48 func isWindowsDrive(buffer []rune, char rune) bool { 49 return char == ':' && len(buffer) == 1 && unicode.IsLetter(buffer[0]) 50 } 51 52 func populateFieldFromBuffer(char rune, buffer []rune, volume *types.ServiceVolumeConfig) error { 53 strBuffer := string(buffer) 54 switch { 55 case len(buffer) == 0: 56 return errors.New("empty section between colons") 57 // Anonymous volume 58 case volume.Source == "" && char == endOfSpec: 59 volume.Target = strBuffer 60 return nil 61 case volume.Source == "": 62 volume.Source = strBuffer 63 return nil 64 case volume.Target == "": 65 volume.Target = strBuffer 66 return nil 67 case char == ':': 68 return errors.New("too many colons") 69 } 70 for _, option := range strings.Split(strBuffer, ",") { 71 switch option { 72 case "ro": 73 volume.ReadOnly = true 74 case "rw": 75 volume.ReadOnly = false 76 case "nocopy": 77 volume.Volume = &types.ServiceVolumeVolume{NoCopy: true} 78 default: 79 if isBindOption(option) { 80 volume.Bind = &types.ServiceVolumeBind{Propagation: option} 81 } 82 // ignore unknown options 83 } 84 } 85 return nil 86 } 87 88 func isBindOption(option string) bool { 89 for _, propagation := range mount.Propagations { 90 if mount.Propagation(option) == propagation { 91 return true 92 } 93 } 94 return false 95 } 96 97 func populateType(volume *types.ServiceVolumeConfig) { 98 switch { 99 // Anonymous volume 100 case volume.Source == "": 101 volume.Type = string(mount.TypeVolume) 102 case isFilePath(volume.Source): 103 volume.Type = string(mount.TypeBind) 104 default: 105 volume.Type = string(mount.TypeVolume) 106 } 107 } 108 109 func isFilePath(source string) bool { 110 switch source[0] { 111 case '.', '/', '~': 112 return true 113 } 114 if len([]rune(source)) == 1 { 115 return false 116 } 117 118 // windows named pipes 119 if strings.HasPrefix(source, `\\`) { 120 return true 121 } 122 123 first, nextIndex := utf8.DecodeRuneInString(source) 124 return isWindowsDrive([]rune{first}, rune(source[nextIndex])) 125 }