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