github.com/itscaro/cli@v0.0.0-20190705081621-c9db0fe93829/opts/mount.go (about) 1 package opts 2 3 import ( 4 "encoding/csv" 5 "fmt" 6 "os" 7 "strconv" 8 "strings" 9 10 mounttypes "github.com/docker/docker/api/types/mount" 11 "github.com/docker/go-units" 12 ) 13 14 // MountOpt is a Value type for parsing mounts 15 type MountOpt struct { 16 values []mounttypes.Mount 17 } 18 19 // Set a new mount value 20 // nolint: gocyclo 21 func (m *MountOpt) Set(value string) error { 22 csvReader := csv.NewReader(strings.NewReader(value)) 23 fields, err := csvReader.Read() 24 if err != nil { 25 return err 26 } 27 28 mount := mounttypes.Mount{} 29 30 volumeOptions := func() *mounttypes.VolumeOptions { 31 if mount.VolumeOptions == nil { 32 mount.VolumeOptions = &mounttypes.VolumeOptions{ 33 Labels: make(map[string]string), 34 } 35 } 36 if mount.VolumeOptions.DriverConfig == nil { 37 mount.VolumeOptions.DriverConfig = &mounttypes.Driver{} 38 } 39 return mount.VolumeOptions 40 } 41 42 bindOptions := func() *mounttypes.BindOptions { 43 if mount.BindOptions == nil { 44 mount.BindOptions = new(mounttypes.BindOptions) 45 } 46 return mount.BindOptions 47 } 48 49 tmpfsOptions := func() *mounttypes.TmpfsOptions { 50 if mount.TmpfsOptions == nil { 51 mount.TmpfsOptions = new(mounttypes.TmpfsOptions) 52 } 53 return mount.TmpfsOptions 54 } 55 56 setValueOnMap := func(target map[string]string, value string) { 57 parts := strings.SplitN(value, "=", 2) 58 if len(parts) == 1 { 59 target[value] = "" 60 } else { 61 target[parts[0]] = parts[1] 62 } 63 } 64 65 mount.Type = mounttypes.TypeVolume // default to volume mounts 66 // Set writable as the default 67 for _, field := range fields { 68 parts := strings.SplitN(field, "=", 2) 69 key := strings.ToLower(parts[0]) 70 71 if len(parts) == 1 { 72 switch key { 73 case "readonly", "ro": 74 mount.ReadOnly = true 75 continue 76 case "volume-nocopy": 77 volumeOptions().NoCopy = true 78 continue 79 case "bind-nonrecursive": 80 bindOptions().NonRecursive = true 81 continue 82 } 83 } 84 85 if len(parts) != 2 { 86 return fmt.Errorf("invalid field '%s' must be a key=value pair", field) 87 } 88 89 value := parts[1] 90 switch key { 91 case "type": 92 mount.Type = mounttypes.Type(strings.ToLower(value)) 93 case "source", "src": 94 mount.Source = value 95 case "target", "dst", "destination": 96 mount.Target = value 97 case "readonly", "ro": 98 mount.ReadOnly, err = strconv.ParseBool(value) 99 if err != nil { 100 return fmt.Errorf("invalid value for %s: %s", key, value) 101 } 102 case "consistency": 103 mount.Consistency = mounttypes.Consistency(strings.ToLower(value)) 104 case "bind-propagation": 105 bindOptions().Propagation = mounttypes.Propagation(strings.ToLower(value)) 106 case "bind-nonrecursive": 107 bindOptions().NonRecursive, err = strconv.ParseBool(value) 108 if err != nil { 109 return fmt.Errorf("invalid value for %s: %s", key, value) 110 } 111 case "volume-nocopy": 112 volumeOptions().NoCopy, err = strconv.ParseBool(value) 113 if err != nil { 114 return fmt.Errorf("invalid value for volume-nocopy: %s", value) 115 } 116 case "volume-label": 117 setValueOnMap(volumeOptions().Labels, value) 118 case "volume-driver": 119 volumeOptions().DriverConfig.Name = value 120 case "volume-opt": 121 if volumeOptions().DriverConfig.Options == nil { 122 volumeOptions().DriverConfig.Options = make(map[string]string) 123 } 124 setValueOnMap(volumeOptions().DriverConfig.Options, value) 125 case "tmpfs-size": 126 sizeBytes, err := units.RAMInBytes(value) 127 if err != nil { 128 return fmt.Errorf("invalid value for %s: %s", key, value) 129 } 130 tmpfsOptions().SizeBytes = sizeBytes 131 case "tmpfs-mode": 132 ui64, err := strconv.ParseUint(value, 8, 32) 133 if err != nil { 134 return fmt.Errorf("invalid value for %s: %s", key, value) 135 } 136 tmpfsOptions().Mode = os.FileMode(ui64) 137 default: 138 return fmt.Errorf("unexpected key '%s' in '%s'", key, field) 139 } 140 } 141 142 if mount.Type == "" { 143 return fmt.Errorf("type is required") 144 } 145 146 if mount.Target == "" { 147 return fmt.Errorf("target is required") 148 } 149 150 if mount.VolumeOptions != nil && mount.Type != mounttypes.TypeVolume { 151 return fmt.Errorf("cannot mix 'volume-*' options with mount type '%s'", mount.Type) 152 } 153 if mount.BindOptions != nil && mount.Type != mounttypes.TypeBind { 154 return fmt.Errorf("cannot mix 'bind-*' options with mount type '%s'", mount.Type) 155 } 156 if mount.TmpfsOptions != nil && mount.Type != mounttypes.TypeTmpfs { 157 return fmt.Errorf("cannot mix 'tmpfs-*' options with mount type '%s'", mount.Type) 158 } 159 160 m.values = append(m.values, mount) 161 return nil 162 } 163 164 // Type returns the type of this option 165 func (m *MountOpt) Type() string { 166 return "mount" 167 } 168 169 // String returns a string repr of this option 170 func (m *MountOpt) String() string { 171 mounts := []string{} 172 for _, mount := range m.values { 173 repr := fmt.Sprintf("%s %s %s", mount.Type, mount.Source, mount.Target) 174 mounts = append(mounts, repr) 175 } 176 return strings.Join(mounts, ", ") 177 } 178 179 // Value returns the mounts 180 func (m *MountOpt) Value() []mounttypes.Mount { 181 return m.values 182 }