github.com/panekj/cli@v0.0.0-20230304125325-467dd2f3797e/cli/compose/convert/volume.go (about) 1 package convert 2 3 import ( 4 "strings" 5 6 composetypes "github.com/docker/cli/cli/compose/types" 7 "github.com/docker/docker/api/types/mount" 8 "github.com/pkg/errors" 9 ) 10 11 type volumes map[string]composetypes.VolumeConfig 12 13 // Volumes from compose-file types to engine api types 14 func Volumes(serviceVolumes []composetypes.ServiceVolumeConfig, stackVolumes volumes, namespace Namespace) ([]mount.Mount, error) { 15 var mounts []mount.Mount 16 17 for _, volumeConfig := range serviceVolumes { 18 mount, err := convertVolumeToMount(volumeConfig, stackVolumes, namespace) 19 if err != nil { 20 return nil, err 21 } 22 mounts = append(mounts, mount) 23 } 24 return mounts, nil 25 } 26 27 func createMountFromVolume(volume composetypes.ServiceVolumeConfig) mount.Mount { 28 return mount.Mount{ 29 Type: mount.Type(volume.Type), 30 Target: volume.Target, 31 ReadOnly: volume.ReadOnly, 32 Source: volume.Source, 33 Consistency: mount.Consistency(volume.Consistency), 34 } 35 } 36 37 func handleVolumeToMount( 38 volume composetypes.ServiceVolumeConfig, 39 stackVolumes volumes, 40 namespace Namespace, 41 ) (mount.Mount, error) { 42 result := createMountFromVolume(volume) 43 44 if volume.Tmpfs != nil { 45 return mount.Mount{}, errors.New("tmpfs options are incompatible with type volume") 46 } 47 if volume.Bind != nil { 48 return mount.Mount{}, errors.New("bind options are incompatible with type volume") 49 } 50 if volume.Cluster != nil { 51 return mount.Mount{}, errors.New("cluster options are incompatible with type volume") 52 } 53 // Anonymous volumes 54 if volume.Source == "" { 55 return result, nil 56 } 57 58 stackVolume, exists := stackVolumes[volume.Source] 59 if !exists { 60 return mount.Mount{}, errors.Errorf("undefined volume %q", volume.Source) 61 } 62 63 result.Source = namespace.Scope(volume.Source) 64 result.VolumeOptions = &mount.VolumeOptions{} 65 66 if volume.Volume != nil { 67 result.VolumeOptions.NoCopy = volume.Volume.NoCopy 68 } 69 70 if stackVolume.Name != "" { 71 result.Source = stackVolume.Name 72 } 73 74 // External named volumes 75 if stackVolume.External.External { 76 return result, nil 77 } 78 79 result.VolumeOptions.Labels = AddStackLabel(namespace, stackVolume.Labels) 80 if stackVolume.Driver != "" || stackVolume.DriverOpts != nil { 81 result.VolumeOptions.DriverConfig = &mount.Driver{ 82 Name: stackVolume.Driver, 83 Options: stackVolume.DriverOpts, 84 } 85 } 86 87 return result, nil 88 } 89 90 func handleBindToMount(volume composetypes.ServiceVolumeConfig) (mount.Mount, error) { 91 result := createMountFromVolume(volume) 92 93 if volume.Source == "" { 94 return mount.Mount{}, errors.New("invalid bind source, source cannot be empty") 95 } 96 if volume.Volume != nil { 97 return mount.Mount{}, errors.New("volume options are incompatible with type bind") 98 } 99 if volume.Tmpfs != nil { 100 return mount.Mount{}, errors.New("tmpfs options are incompatible with type bind") 101 } 102 if volume.Cluster != nil { 103 return mount.Mount{}, errors.New("cluster options are incompatible with type bind") 104 } 105 if volume.Bind != nil { 106 result.BindOptions = &mount.BindOptions{ 107 Propagation: mount.Propagation(volume.Bind.Propagation), 108 } 109 } 110 return result, nil 111 } 112 113 func handleTmpfsToMount(volume composetypes.ServiceVolumeConfig) (mount.Mount, error) { 114 result := createMountFromVolume(volume) 115 116 if volume.Source != "" { 117 return mount.Mount{}, errors.New("invalid tmpfs source, source must be empty") 118 } 119 if volume.Bind != nil { 120 return mount.Mount{}, errors.New("bind options are incompatible with type tmpfs") 121 } 122 if volume.Volume != nil { 123 return mount.Mount{}, errors.New("volume options are incompatible with type tmpfs") 124 } 125 if volume.Cluster != nil { 126 return mount.Mount{}, errors.New("cluster options are incompatible with type tmpfs") 127 } 128 if volume.Tmpfs != nil { 129 result.TmpfsOptions = &mount.TmpfsOptions{ 130 SizeBytes: volume.Tmpfs.Size, 131 } 132 } 133 return result, nil 134 } 135 136 func handleNpipeToMount(volume composetypes.ServiceVolumeConfig) (mount.Mount, error) { 137 result := createMountFromVolume(volume) 138 139 if volume.Source == "" { 140 return mount.Mount{}, errors.New("invalid npipe source, source cannot be empty") 141 } 142 if volume.Volume != nil { 143 return mount.Mount{}, errors.New("volume options are incompatible with type npipe") 144 } 145 if volume.Tmpfs != nil { 146 return mount.Mount{}, errors.New("tmpfs options are incompatible with type npipe") 147 } 148 if volume.Bind != nil { 149 result.BindOptions = &mount.BindOptions{ 150 Propagation: mount.Propagation(volume.Bind.Propagation), 151 } 152 } 153 return result, nil 154 } 155 156 func handleClusterToMount( 157 volume composetypes.ServiceVolumeConfig, 158 stackVolumes volumes, 159 namespace Namespace, 160 ) (mount.Mount, error) { 161 if volume.Source == "" { 162 return mount.Mount{}, errors.New("invalid cluster source, source cannot be empty") 163 } 164 if volume.Tmpfs != nil { 165 return mount.Mount{}, errors.New("tmpfs options are incompatible with type cluster") 166 } 167 if volume.Bind != nil { 168 return mount.Mount{}, errors.New("bind options are incompatible with type cluster") 169 } 170 if volume.Volume != nil { 171 return mount.Mount{}, errors.New("volume options are incompatible with type cluster") 172 } 173 174 result := createMountFromVolume(volume) 175 result.ClusterOptions = &mount.ClusterOptions{} 176 177 if !strings.HasPrefix(volume.Source, "group:") { 178 // if the volume is a cluster volume and the source is a volumegroup, we 179 // will ignore checking to see if such a volume is defined. the volume 180 // group isn't namespaced, and there's no simple way to indicate that 181 // external volumes with a given group exist. 182 stackVolume, exists := stackVolumes[volume.Source] 183 if !exists { 184 return mount.Mount{}, errors.Errorf("undefined volume %q", volume.Source) 185 } 186 187 // if the volume is not specified with a group source, we may namespace 188 // the name, if one is not otherwise specified. 189 if stackVolume.Name != "" { 190 result.Source = stackVolume.Name 191 } else { 192 result.Source = namespace.Scope(volume.Source) 193 } 194 } 195 196 return result, nil 197 } 198 199 func convertVolumeToMount( 200 volume composetypes.ServiceVolumeConfig, 201 stackVolumes volumes, 202 namespace Namespace, 203 ) (mount.Mount, error) { 204 switch volume.Type { 205 case "volume", "": 206 return handleVolumeToMount(volume, stackVolumes, namespace) 207 case "bind": 208 return handleBindToMount(volume) 209 case "tmpfs": 210 return handleTmpfsToMount(volume) 211 case "npipe": 212 return handleNpipeToMount(volume) 213 case "cluster": 214 return handleClusterToMount(volume, stackVolumes, namespace) 215 } 216 return mount.Mount{}, errors.New("volume type must be volume, bind, tmpfs, npipe, or cluster") 217 }