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