github.com/docker/docker-ce@v17.12.1-ce-rc2+incompatible/components/cli/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 } 80 } 81 82 if len(parts) != 2 { 83 return fmt.Errorf("invalid field '%s' must be a key=value pair", field) 84 } 85 86 value := parts[1] 87 switch key { 88 case "type": 89 mount.Type = mounttypes.Type(strings.ToLower(value)) 90 case "source", "src": 91 mount.Source = value 92 case "target", "dst", "destination": 93 mount.Target = value 94 case "readonly", "ro": 95 mount.ReadOnly, err = strconv.ParseBool(value) 96 if err != nil { 97 return fmt.Errorf("invalid value for %s: %s", key, value) 98 } 99 case "consistency": 100 mount.Consistency = mounttypes.Consistency(strings.ToLower(value)) 101 case "bind-propagation": 102 bindOptions().Propagation = mounttypes.Propagation(strings.ToLower(value)) 103 case "volume-nocopy": 104 volumeOptions().NoCopy, err = strconv.ParseBool(value) 105 if err != nil { 106 return fmt.Errorf("invalid value for volume-nocopy: %s", value) 107 } 108 case "volume-label": 109 setValueOnMap(volumeOptions().Labels, value) 110 case "volume-driver": 111 volumeOptions().DriverConfig.Name = value 112 case "volume-opt": 113 if volumeOptions().DriverConfig.Options == nil { 114 volumeOptions().DriverConfig.Options = make(map[string]string) 115 } 116 setValueOnMap(volumeOptions().DriverConfig.Options, value) 117 case "tmpfs-size": 118 sizeBytes, err := units.RAMInBytes(value) 119 if err != nil { 120 return fmt.Errorf("invalid value for %s: %s", key, value) 121 } 122 tmpfsOptions().SizeBytes = sizeBytes 123 case "tmpfs-mode": 124 ui64, err := strconv.ParseUint(value, 8, 32) 125 if err != nil { 126 return fmt.Errorf("invalid value for %s: %s", key, value) 127 } 128 tmpfsOptions().Mode = os.FileMode(ui64) 129 default: 130 return fmt.Errorf("unexpected key '%s' in '%s'", key, field) 131 } 132 } 133 134 if mount.Type == "" { 135 return fmt.Errorf("type is required") 136 } 137 138 if mount.Target == "" { 139 return fmt.Errorf("target is required") 140 } 141 142 if mount.VolumeOptions != nil && mount.Type != mounttypes.TypeVolume { 143 return fmt.Errorf("cannot mix 'volume-*' options with mount type '%s'", mount.Type) 144 } 145 if mount.BindOptions != nil && mount.Type != mounttypes.TypeBind { 146 return fmt.Errorf("cannot mix 'bind-*' options with mount type '%s'", mount.Type) 147 } 148 if mount.TmpfsOptions != nil && mount.Type != mounttypes.TypeTmpfs { 149 return fmt.Errorf("cannot mix 'tmpfs-*' options with mount type '%s'", mount.Type) 150 } 151 152 m.values = append(m.values, mount) 153 return nil 154 } 155 156 // Type returns the type of this option 157 func (m *MountOpt) Type() string { 158 return "mount" 159 } 160 161 // String returns a string repr of this option 162 func (m *MountOpt) String() string { 163 mounts := []string{} 164 for _, mount := range m.values { 165 repr := fmt.Sprintf("%s %s %s", mount.Type, mount.Source, mount.Target) 166 mounts = append(mounts, repr) 167 } 168 return strings.Join(mounts, ", ") 169 } 170 171 // Value returns the mounts 172 func (m *MountOpt) Value() []mounttypes.Mount { 173 return m.values 174 }