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  }