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