github.com/containers/podman/v4@v4.9.4/pkg/specgen/generate/kube/volume.go (about) 1 //go:build !remote 2 // +build !remote 3 4 package kube 5 6 import ( 7 "errors" 8 "fmt" 9 "os" 10 11 "github.com/containers/common/pkg/parse" 12 "github.com/containers/common/pkg/secrets" 13 "github.com/containers/podman/v4/libpod" 14 v1 "github.com/containers/podman/v4/pkg/k8s.io/api/core/v1" 15 16 "github.com/sirupsen/logrus" 17 "sigs.k8s.io/yaml" 18 ) 19 20 const ( 21 // https://kubernetes.io/docs/concepts/storage/volumes/#hostpath 22 kubeDirectoryPermission = 0755 23 // https://kubernetes.io/docs/concepts/storage/volumes/#hostpath 24 kubeFilePermission = 0644 25 ) 26 27 //nolint:revive 28 type KubeVolumeType int 29 30 const ( 31 KubeVolumeTypeBindMount KubeVolumeType = iota 32 KubeVolumeTypeNamed 33 KubeVolumeTypeConfigMap 34 KubeVolumeTypeBlockDevice 35 KubeVolumeTypeCharDevice 36 KubeVolumeTypeSecret 37 KubeVolumeTypeEmptyDir 38 ) 39 40 //nolint:revive 41 type KubeVolume struct { 42 // Type of volume to create 43 Type KubeVolumeType 44 // Path for bind mount or volume name for named volume 45 Source string 46 // Items to add to a named volume created where the key is the file name and the value is the data 47 // This is only used when there are volumes in the yaml that refer to a configmap 48 // Example: if configmap has data "SPECIAL_LEVEL: very" then the file name is "SPECIAL_LEVEL" and the 49 // data in that file is "very". 50 Items map[string][]byte 51 // If the volume is optional, we can move on if it is not found 52 // Only used when there are volumes in a yaml that refer to a configmap 53 Optional bool 54 // DefaultMode sets the permissions on files created for the volume 55 // This is optional and defaults to 0644 56 DefaultMode int32 57 } 58 59 // Create a KubeVolume from an HostPathVolumeSource 60 func VolumeFromHostPath(hostPath *v1.HostPathVolumeSource, mountLabel string) (*KubeVolume, error) { 61 if hostPath.Type != nil { 62 switch *hostPath.Type { 63 case v1.HostPathDirectoryOrCreate: 64 if err := os.MkdirAll(hostPath.Path, kubeDirectoryPermission); err != nil { 65 return nil, err 66 } 67 // Label a newly created volume 68 if err := libpod.LabelVolumePath(hostPath.Path, mountLabel); err != nil { 69 return nil, fmt.Errorf("giving %s a label: %w", hostPath.Path, err) 70 } 71 case v1.HostPathFileOrCreate: 72 if _, err := os.Stat(hostPath.Path); os.IsNotExist(err) { 73 f, err := os.OpenFile(hostPath.Path, os.O_RDONLY|os.O_CREATE, kubeFilePermission) 74 if err != nil { 75 return nil, fmt.Errorf("creating HostPath: %w", err) 76 } 77 if err := f.Close(); err != nil { 78 logrus.Warnf("Error in closing newly created HostPath file: %v", err) 79 } 80 } 81 // unconditionally label a newly created volume 82 83 if err := libpod.LabelVolumePath(hostPath.Path, mountLabel); err != nil { 84 return nil, fmt.Errorf("giving %s a label: %w", hostPath.Path, err) 85 } 86 case v1.HostPathSocket: 87 st, err := os.Stat(hostPath.Path) 88 if err != nil { 89 return nil, fmt.Errorf("checking HostPathSocket: %w", err) 90 } 91 if st.Mode()&os.ModeSocket != os.ModeSocket { 92 return nil, fmt.Errorf("checking HostPathSocket: path %s is not a socket", hostPath.Path) 93 } 94 case v1.HostPathBlockDev: 95 dev, err := os.Stat(hostPath.Path) 96 if err != nil { 97 return nil, fmt.Errorf("checking HostPathBlockDevice: %w", err) 98 } 99 if dev.Mode()&os.ModeCharDevice == os.ModeCharDevice { 100 return nil, fmt.Errorf("checking HostPathDevice: path %s is not a block device", hostPath.Path) 101 } 102 return &KubeVolume{ 103 Type: KubeVolumeTypeBlockDevice, 104 Source: hostPath.Path, 105 }, nil 106 case v1.HostPathCharDev: 107 dev, err := os.Stat(hostPath.Path) 108 if err != nil { 109 return nil, fmt.Errorf("checking HostPathCharDevice: %w", err) 110 } 111 if dev.Mode()&os.ModeCharDevice != os.ModeCharDevice { 112 return nil, fmt.Errorf("checking HostPathCharDevice: path %s is not a character device", hostPath.Path) 113 } 114 return &KubeVolume{ 115 Type: KubeVolumeTypeCharDevice, 116 Source: hostPath.Path, 117 }, nil 118 case v1.HostPathDirectory: 119 case v1.HostPathFile: 120 case v1.HostPathUnset: 121 // do nothing here because we will verify the path exists in validateVolumeHostDir 122 break 123 default: 124 return nil, fmt.Errorf("invalid HostPath type %v", hostPath.Type) 125 } 126 } 127 128 if err := parse.ValidateVolumeHostDir(hostPath.Path); err != nil { 129 return nil, fmt.Errorf("in parsing HostPath in YAML: %w", err) 130 } 131 132 return &KubeVolume{ 133 Type: KubeVolumeTypeBindMount, 134 Source: hostPath.Path, 135 }, nil 136 } 137 138 // VolumeFromSecret creates a new kube volume from a kube secret. 139 func VolumeFromSecret(secretSource *v1.SecretVolumeSource, secretsManager *secrets.SecretsManager) (*KubeVolume, error) { 140 kv := &KubeVolume{ 141 Type: KubeVolumeTypeSecret, 142 Source: secretSource.SecretName, 143 Items: map[string][]byte{}, 144 DefaultMode: v1.SecretVolumeSourceDefaultMode, 145 } 146 // Set the defaultMode if set in the kube yaml 147 validMode, err := isValidDefaultMode(secretSource.DefaultMode) 148 if err != nil { 149 return nil, fmt.Errorf("invalid DefaultMode for secret %q: %w", secretSource.SecretName, err) 150 } 151 if validMode { 152 kv.DefaultMode = *secretSource.DefaultMode 153 } 154 155 // returns a byte array of a kube secret data, meaning this needs to go into a string map 156 _, secretByte, err := secretsManager.LookupSecretData(secretSource.SecretName) 157 if err != nil { 158 if errors.Is(err, secrets.ErrNoSuchSecret) && secretSource.Optional != nil && *secretSource.Optional { 159 kv.Optional = true 160 return kv, nil 161 } 162 return nil, err 163 } 164 165 secret := &v1.Secret{} 166 167 err = yaml.Unmarshal(secretByte, secret) 168 if err != nil { 169 return nil, err 170 } 171 172 // If there are Items specified in the volumeSource, that overwrites the Data from the Secret 173 if len(secretSource.Items) > 0 { 174 for _, item := range secretSource.Items { 175 if val, ok := secret.Data[item.Key]; ok { 176 kv.Items[item.Path] = val 177 } else if val, ok := secret.StringData[item.Key]; ok { 178 kv.Items[item.Path] = []byte(val) 179 } 180 } 181 } else { 182 // add key: value pairs to the items array 183 for key, entry := range secret.Data { 184 kv.Items[key] = entry 185 } 186 187 for key, entry := range secret.StringData { 188 kv.Items[key] = []byte(entry) 189 } 190 } 191 192 return kv, nil 193 } 194 195 // Create a KubeVolume from a PersistentVolumeClaimVolumeSource 196 func VolumeFromPersistentVolumeClaim(claim *v1.PersistentVolumeClaimVolumeSource) (*KubeVolume, error) { 197 return &KubeVolume{ 198 Type: KubeVolumeTypeNamed, 199 Source: claim.ClaimName, 200 }, nil 201 } 202 203 func VolumeFromConfigMap(configMapVolumeSource *v1.ConfigMapVolumeSource, configMaps []v1.ConfigMap) (*KubeVolume, error) { 204 var configMap *v1.ConfigMap 205 kv := &KubeVolume{ 206 Type: KubeVolumeTypeConfigMap, 207 Items: map[string][]byte{}, 208 DefaultMode: v1.ConfigMapVolumeSourceDefaultMode, 209 } 210 for _, cm := range configMaps { 211 if cm.Name == configMapVolumeSource.Name { 212 matchedCM := cm 213 // Set the source to the config map name 214 kv.Source = cm.Name 215 configMap = &matchedCM 216 break 217 } 218 } 219 // Set the defaultMode if set in the kube yaml 220 validMode, err := isValidDefaultMode(configMapVolumeSource.DefaultMode) 221 if err != nil { 222 return nil, fmt.Errorf("invalid DefaultMode for configMap %q: %w", configMapVolumeSource.Name, err) 223 } 224 if validMode { 225 kv.DefaultMode = *configMapVolumeSource.DefaultMode 226 } 227 228 if configMap == nil { 229 // If the volumeSource was optional, move on even if a matching configmap wasn't found 230 if configMapVolumeSource.Optional != nil && *configMapVolumeSource.Optional { 231 kv.Source = configMapVolumeSource.Name 232 kv.Optional = *configMapVolumeSource.Optional 233 return kv, nil 234 } 235 return nil, fmt.Errorf("no such ConfigMap %q", configMapVolumeSource.Name) 236 } 237 238 // don't allow keys from "data" and "binaryData" to overlap 239 for k := range configMap.Data { 240 if _, ok := configMap.BinaryData[k]; ok { 241 return nil, fmt.Errorf("the ConfigMap %q is invalid: duplicate key %q present in data and binaryData", configMap.Name, k) 242 } 243 } 244 245 // If there are Items specified in the volumeSource, that overwrites the Data from the configmap 246 if len(configMapVolumeSource.Items) > 0 { 247 for _, item := range configMapVolumeSource.Items { 248 if val, ok := configMap.Data[item.Key]; ok { 249 kv.Items[item.Path] = []byte(val) 250 } else if val, ok := configMap.BinaryData[item.Key]; ok { 251 kv.Items[item.Path] = val 252 } 253 } 254 } else { 255 for k, v := range configMap.Data { 256 kv.Items[k] = []byte(v) 257 } 258 for k, v := range configMap.BinaryData { 259 kv.Items[k] = v 260 } 261 } 262 return kv, nil 263 } 264 265 // Create a kubeVolume for an emptyDir volume 266 func VolumeFromEmptyDir(emptyDirVolumeSource *v1.EmptyDirVolumeSource, name string) (*KubeVolume, error) { 267 return &KubeVolume{Type: KubeVolumeTypeEmptyDir, Source: name}, nil 268 } 269 270 // Create a KubeVolume from one of the supported VolumeSource 271 func VolumeFromSource(volumeSource v1.VolumeSource, configMaps []v1.ConfigMap, secretsManager *secrets.SecretsManager, volName, mountLabel string) (*KubeVolume, error) { 272 switch { 273 case volumeSource.HostPath != nil: 274 return VolumeFromHostPath(volumeSource.HostPath, mountLabel) 275 case volumeSource.PersistentVolumeClaim != nil: 276 return VolumeFromPersistentVolumeClaim(volumeSource.PersistentVolumeClaim) 277 case volumeSource.ConfigMap != nil: 278 return VolumeFromConfigMap(volumeSource.ConfigMap, configMaps) 279 case volumeSource.Secret != nil: 280 return VolumeFromSecret(volumeSource.Secret, secretsManager) 281 case volumeSource.EmptyDir != nil: 282 return VolumeFromEmptyDir(volumeSource.EmptyDir, volName) 283 default: 284 return nil, errors.New("HostPath, ConfigMap, EmptyDir, Secret, and PersistentVolumeClaim are currently the only supported VolumeSource") 285 } 286 } 287 288 // Create a map of volume name to KubeVolume 289 func InitializeVolumes(specVolumes []v1.Volume, configMaps []v1.ConfigMap, secretsManager *secrets.SecretsManager, mountLabel string) (map[string]*KubeVolume, error) { 290 volumes := make(map[string]*KubeVolume) 291 292 for _, specVolume := range specVolumes { 293 volume, err := VolumeFromSource(specVolume.VolumeSource, configMaps, secretsManager, specVolume.Name, mountLabel) 294 if err != nil { 295 return nil, fmt.Errorf("failed to create volume %q: %w", specVolume.Name, err) 296 } 297 298 volumes[specVolume.Name] = volume 299 } 300 301 return volumes, nil 302 } 303 304 // isValidDefaultMode returns true if mode is between 0 and 0777 305 func isValidDefaultMode(mode *int32) (bool, error) { 306 if mode == nil { 307 return false, nil 308 } 309 if *mode >= 0 && *mode <= int32(os.ModePerm) { 310 return true, nil 311 } 312 return false, errors.New("must be between 0000 and 0777") 313 }