...

Source file src/github.com/redhat-developer/odo/pkg/dev/kubedev/storage/utils.go

Documentation: github.com/redhat-developer/odo/pkg/dev/kubedev/storage

     1  package storage
     2  
     3  import (
     4  	"fmt"
     5  	"path/filepath"
     6  	"sort"
     7  	"strings"
     8  
     9  	"github.com/devfile/library/v2/pkg/devfile/generator"
    10  	devfileParser "github.com/devfile/library/v2/pkg/devfile/parser"
    11  	parsercommon "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
    12  	dfutil "github.com/devfile/library/v2/pkg/util"
    13  
    14  	"github.com/redhat-developer/odo/pkg/configAutomount"
    15  	"github.com/redhat-developer/odo/pkg/kclient"
    16  	odolabels "github.com/redhat-developer/odo/pkg/labels"
    17  	"github.com/redhat-developer/odo/pkg/storage"
    18  
    19  	corev1 "k8s.io/api/core/v1"
    20  	kerrors "k8s.io/apimachinery/pkg/api/errors"
    21  	"k8s.io/apimachinery/pkg/api/resource"
    22  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    23  )
    24  
    25  // VolumeInfo is a struct to hold the pvc name and the volume name to create a volume.
    26  // To be moved to devfile/library.
    27  type VolumeInfo struct {
    28  	PVCName    string
    29  	VolumeName string
    30  }
    31  
    32  // GetVolumeInfos returns the PVC name attached to the `odo-projects` directory and a map of other PVCs
    33  func GetVolumeInfos(pvcs []corev1.PersistentVolumeClaim) (odoSourcePVCName string, infos map[string]VolumeInfo, _ error) {
    34  	infos = make(map[string]VolumeInfo)
    35  	for _, pvc := range pvcs {
    36  		// check if the pvc is in the terminating state
    37  		if pvc.DeletionTimestamp != nil {
    38  			continue
    39  		}
    40  
    41  		generatedVolumeName, e := generateVolumeNameFromPVC(pvc.Name)
    42  		if e != nil {
    43  			return "", nil, fmt.Errorf("unable to generate volume name from pvc name: %w", e)
    44  		}
    45  
    46  		storageName := odolabels.GetStorageName(pvc.Labels)
    47  		if storageName == storage.OdoSourceVolume {
    48  			odoSourcePVCName = pvc.Name
    49  			continue
    50  		}
    51  
    52  		infos[storageName] = VolumeInfo{
    53  			PVCName:    pvc.Name,
    54  			VolumeName: generatedVolumeName,
    55  		}
    56  	}
    57  	return odoSourcePVCName, infos, nil
    58  }
    59  
    60  // GetPersistentVolumesAndVolumeMounts gets the PVC volumes and updates the containers with the volume mounts.
    61  // volumeNameToVolInfo is a map of the devfile volume name to the volume info containing the pvc name and the volume name.
    62  // To be moved to devfile/library.
    63  func GetPersistentVolumesAndVolumeMounts(devfileObj devfileParser.DevfileObj, containers []corev1.Container, initContainers []corev1.Container, volumeNameToVolInfo map[string]VolumeInfo, options parsercommon.DevfileOptions) ([]corev1.Volume, error) {
    64  
    65  	containerComponents, err := devfileObj.Data.GetDevfileContainerComponents(options)
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  
    70  	var pvcVols []corev1.Volume
    71  
    72  	// We need to sort volumes to create Deployment in a deterministic way
    73  	keys := make([]string, 0, len(volumeNameToVolInfo))
    74  	for k := range volumeNameToVolInfo {
    75  		keys = append(keys, k)
    76  	}
    77  	sort.Strings(keys)
    78  
    79  	for _, volName := range keys {
    80  		volInfo := volumeNameToVolInfo[volName]
    81  		pvcVols = append(pvcVols, getPVC(volInfo.VolumeName, volInfo.PVCName))
    82  
    83  		// containerNameToMountPaths is a map of the Devfile container name to their Devfile Volume Mount Paths for a given Volume Name
    84  		containerNameToMountPaths := make(map[string][]string)
    85  		for _, containerComp := range containerComponents {
    86  			for _, volumeMount := range containerComp.Container.VolumeMounts {
    87  				if volName == volumeMount.Name {
    88  					containerNameToMountPaths[containerComp.Name] = append(containerNameToMountPaths[containerComp.Name], generator.GetVolumeMountPath(volumeMount))
    89  				}
    90  			}
    91  		}
    92  
    93  		addVolumeMountToContainers(containers, initContainers, volInfo.VolumeName, containerNameToMountPaths)
    94  	}
    95  	return pvcVols, nil
    96  }
    97  
    98  func GetEphemeralVolumesAndVolumeMounts(devfileObj devfileParser.DevfileObj, containers []corev1.Container, initContainers []corev1.Container, ephemerals map[string]storage.Storage, options parsercommon.DevfileOptions) ([]corev1.Volume, error) {
    99  	containerComponents, err := devfileObj.Data.GetDevfileContainerComponents(options)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	var emptydirVols []corev1.Volume
   104  
   105  	// We need to sort volumes to create Deployment in a deterministic way
   106  	keys := make([]string, 0, len(ephemerals))
   107  	for k := range ephemerals {
   108  		keys = append(keys, k)
   109  	}
   110  	sort.Strings(keys)
   111  
   112  	for _, volName := range keys {
   113  		volInfo := ephemerals[volName]
   114  		emptyDir, err := getEmptyDir(volInfo.Name, volInfo.Spec.Size)
   115  		if err != nil {
   116  			return nil, err
   117  		}
   118  		emptydirVols = append(emptydirVols, emptyDir)
   119  
   120  		// containerNameToMountPaths is a map of the Devfile container name to their Devfile Volume Mount Paths for a given Volume Name
   121  		containerNameToMountPaths := make(map[string][]string)
   122  		for _, containerComp := range containerComponents {
   123  			for _, volumeMount := range containerComp.Container.VolumeMounts {
   124  				if volName == volumeMount.Name {
   125  					containerNameToMountPaths[containerComp.Name] = append(containerNameToMountPaths[containerComp.Name], generator.GetVolumeMountPath(volumeMount))
   126  				}
   127  			}
   128  		}
   129  
   130  		addVolumeMountToContainers(containers, initContainers, volInfo.Name, containerNameToMountPaths)
   131  	}
   132  	return emptydirVols, nil
   133  }
   134  
   135  // getPVC gets a pvc type volume with the given volume name and pvc name.
   136  func getPVC(volumeName, pvcName string) corev1.Volume {
   137  
   138  	return corev1.Volume{
   139  		Name: volumeName,
   140  		VolumeSource: corev1.VolumeSource{
   141  			PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
   142  				ClaimName: pvcName,
   143  			},
   144  		},
   145  	}
   146  }
   147  
   148  // getEmptyDir gets an emptyDir type volume with the given volume name and size.
   149  // size should be parseable as a Kubernetes `Quantity` or an error will be returned
   150  func getEmptyDir(volumeName string, size string) (corev1.Volume, error) {
   151  
   152  	emptyDir := &corev1.EmptyDirVolumeSource{}
   153  	qty, err := resource.ParseQuantity(size)
   154  	if err != nil {
   155  		return corev1.Volume{}, err
   156  	}
   157  	emptyDir.SizeLimit = &qty
   158  	return corev1.Volume{
   159  		Name: volumeName,
   160  		VolumeSource: corev1.VolumeSource{
   161  			EmptyDir: emptyDir,
   162  		},
   163  	}, nil
   164  }
   165  
   166  // addVolumeMountToContainers adds the Volume Mounts in containerNameToMountPaths to the containers for a given pvc and volumeName
   167  // containerNameToMountPaths is a map of a container name to an array of its Mount Paths.
   168  // To be moved to devfile/library.
   169  func addVolumeMountToContainers(containers []corev1.Container, initContainers []corev1.Container, volumeName string, containerNameToMountPaths map[string][]string) {
   170  
   171  	for containerName, mountPaths := range containerNameToMountPaths {
   172  		for i := range containers {
   173  			if containers[i].Name == containerName {
   174  				for _, mountPath := range mountPaths {
   175  					containers[i].VolumeMounts = append(containers[i].VolumeMounts, corev1.VolumeMount{
   176  						Name:      volumeName,
   177  						MountPath: mountPath,
   178  						SubPath:   "",
   179  					},
   180  					)
   181  				}
   182  			}
   183  		}
   184  		for i := range initContainers {
   185  			if strings.HasPrefix(initContainers[i].Name, containerName) {
   186  				for _, mountPath := range mountPaths {
   187  					initContainers[i].VolumeMounts = append(initContainers[i].VolumeMounts, corev1.VolumeMount{
   188  						Name:      volumeName,
   189  						MountPath: mountPath,
   190  						SubPath:   "",
   191  					},
   192  					)
   193  				}
   194  			}
   195  		}
   196  	}
   197  }
   198  
   199  // generateVolumeNameFromPVC generates a volume name based on the pvc name
   200  func generateVolumeNameFromPVC(pvc string) (volumeName string, err error) {
   201  	volumeName, err = dfutil.NamespaceOpenShiftObject(pvc, "vol")
   202  	if err != nil {
   203  		return "", err
   204  	}
   205  	return
   206  }
   207  
   208  // HandleOdoSourceStorage creates or deletes the volume containing project sources, based on the preference setting
   209  // - if Ephemeral preference is true, any PVC with labels "component=..." and "odo-source-pvc=odo-projects" is removed
   210  // - if Ephemeral preference is false and no PVC with matching labels exists, it is created
   211  func HandleOdoSourceStorage(client kclient.ClientInterface, storageClient storage.Client, componentName string, isEphemeral bool) error {
   212  	selector := odolabels.Builder().WithComponentName(componentName).WithSourcePVC(storage.OdoSourceVolume).Selector()
   213  	pvcs, err := client.ListPVCs(selector)
   214  	if err != nil && !kerrors.IsNotFound(err) {
   215  		return err
   216  	}
   217  
   218  	if !isEphemeral {
   219  		if len(pvcs) == 0 {
   220  			err := storageClient.Create(storage.Storage{
   221  				ObjectMeta: metav1.ObjectMeta{
   222  					Name: storage.OdoSourceVolume,
   223  				},
   224  				Spec: storage.StorageSpec{
   225  					Size: storage.OdoSourceVolumeSize,
   226  				},
   227  			})
   228  
   229  			if err != nil {
   230  				return err
   231  			}
   232  		} else if len(pvcs) > 1 {
   233  			return fmt.Errorf("number of source volumes shouldn't be greater than 1")
   234  		}
   235  	} else {
   236  		if len(pvcs) > 0 {
   237  			for _, pvc := range pvcs {
   238  				err := client.DeletePVC(pvc.Name)
   239  				if err != nil {
   240  					return err
   241  				}
   242  			}
   243  		}
   244  	}
   245  	return nil
   246  }
   247  
   248  func GetAutomountVolumes(configAutomountClient configAutomount.Client, containers, initContainers []corev1.Container) ([]corev1.Volume, error) {
   249  	volumesInfos, err := configAutomountClient.GetAutomountingVolumes()
   250  	if err != nil {
   251  		return nil, err
   252  	}
   253  
   254  	var volumes []corev1.Volume
   255  	for _, volumeInfo := range volumesInfos {
   256  		switch volumeInfo.VolumeType {
   257  		case configAutomount.VolumeTypePVC:
   258  			volumes = mountPVC(volumeInfo, containers, initContainers, volumes)
   259  		case configAutomount.VolumeTypeSecret:
   260  			volumes = mountSecret(volumeInfo, containers, initContainers, volumes)
   261  		case configAutomount.VolumeTypeConfigmap:
   262  			volumes = mountConfigMap(volumeInfo, containers, initContainers, volumes)
   263  		}
   264  	}
   265  	return volumes, nil
   266  }
   267  
   268  func mountPVC(volumeInfo configAutomount.AutomountInfo, containers, initContainers []corev1.Container, volumes []corev1.Volume) []corev1.Volume {
   269  	volumeName := "auto-pvc-" + volumeInfo.VolumeName
   270  
   271  	inAllContainers(containers, initContainers, func(container *corev1.Container) {
   272  		addVolumeMountToContainer(container, corev1.VolumeMount{
   273  			Name:      volumeName,
   274  			MountPath: volumeInfo.MountPath,
   275  			ReadOnly:  volumeInfo.ReadOnly,
   276  		})
   277  	})
   278  
   279  	volumes = append(volumes, corev1.Volume{
   280  		Name: volumeName,
   281  		VolumeSource: corev1.VolumeSource{
   282  			PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
   283  				ClaimName: volumeInfo.VolumeName,
   284  			},
   285  		},
   286  	})
   287  	return volumes
   288  }
   289  
   290  func mountSecret(volumeInfo configAutomount.AutomountInfo, containers, initContainers []corev1.Container, volumes []corev1.Volume) []corev1.Volume {
   291  	switch volumeInfo.MountAs {
   292  	case configAutomount.MountAsFile:
   293  		return mountSecretAsFile(volumeInfo, containers, initContainers, volumes)
   294  	case configAutomount.MountAsEnv:
   295  		return mountSecretAsEnv(volumeInfo, containers, initContainers, volumes)
   296  	case configAutomount.MountAsSubpath:
   297  		return mountSecretAsSubpath(volumeInfo, containers, initContainers, volumes)
   298  	}
   299  	return volumes
   300  }
   301  
   302  func mountSecretAsFile(volumeInfo configAutomount.AutomountInfo, containers, initContainers []corev1.Container, volumes []corev1.Volume) []corev1.Volume {
   303  	volumeName := "auto-secret-" + volumeInfo.VolumeName
   304  
   305  	inAllContainers(containers, initContainers, func(container *corev1.Container) {
   306  		addVolumeMountToContainer(container, corev1.VolumeMount{
   307  			Name:      volumeName,
   308  			MountPath: volumeInfo.MountPath,
   309  			ReadOnly:  volumeInfo.ReadOnly,
   310  		})
   311  	})
   312  
   313  	volumes = append(volumes, corev1.Volume{
   314  		Name: volumeName,
   315  		VolumeSource: corev1.VolumeSource{
   316  			Secret: &corev1.SecretVolumeSource{
   317  				SecretName:  volumeInfo.VolumeName,
   318  				DefaultMode: volumeInfo.MountAccessMode,
   319  			},
   320  		},
   321  	})
   322  	return volumes
   323  }
   324  
   325  func mountSecretAsEnv(volumeInfo configAutomount.AutomountInfo, containers, initContainers []corev1.Container, volumes []corev1.Volume) []corev1.Volume {
   326  	inAllContainers(containers, initContainers, func(container *corev1.Container) {
   327  		addEnvFromToContainer(container, corev1.EnvFromSource{
   328  			SecretRef: &corev1.SecretEnvSource{
   329  				LocalObjectReference: corev1.LocalObjectReference{
   330  					Name: volumeInfo.VolumeName,
   331  				},
   332  			},
   333  		})
   334  	})
   335  	return volumes
   336  }
   337  
   338  func mountSecretAsSubpath(volumeInfo configAutomount.AutomountInfo, containers, initContainers []corev1.Container, volumes []corev1.Volume) []corev1.Volume {
   339  	volumeName := "auto-secret-" + volumeInfo.VolumeName
   340  
   341  	inAllContainers(containers, initContainers, func(container *corev1.Container) {
   342  		for _, key := range volumeInfo.Keys {
   343  			addVolumeMountToContainer(container, corev1.VolumeMount{
   344  				Name:      volumeName,
   345  				MountPath: filepath.ToSlash(filepath.Join(volumeInfo.MountPath, key)),
   346  				SubPath:   key,
   347  				ReadOnly:  volumeInfo.ReadOnly,
   348  			})
   349  		}
   350  	})
   351  
   352  	volumes = append(volumes, corev1.Volume{
   353  		Name: volumeName,
   354  		VolumeSource: corev1.VolumeSource{
   355  			Secret: &corev1.SecretVolumeSource{
   356  				SecretName:  volumeInfo.VolumeName,
   357  				DefaultMode: volumeInfo.MountAccessMode,
   358  			},
   359  		},
   360  	})
   361  	return volumes
   362  }
   363  
   364  func mountConfigMap(volumeInfo configAutomount.AutomountInfo, containers, initContainers []corev1.Container, volumes []corev1.Volume) []corev1.Volume {
   365  	switch volumeInfo.MountAs {
   366  	case configAutomount.MountAsFile:
   367  		return mountConfigMapAsFile(volumeInfo, containers, initContainers, volumes)
   368  	case configAutomount.MountAsEnv:
   369  		return mountConfigMapAsEnv(volumeInfo, containers, initContainers, volumes)
   370  	case configAutomount.MountAsSubpath:
   371  		return mountConfigMapAsSubpath(volumeInfo, containers, initContainers, volumes)
   372  	}
   373  	return volumes
   374  }
   375  
   376  func mountConfigMapAsFile(volumeInfo configAutomount.AutomountInfo, containers, initContainers []corev1.Container, volumes []corev1.Volume) []corev1.Volume {
   377  	volumeName := "auto-cm-" + volumeInfo.VolumeName
   378  
   379  	inAllContainers(containers, initContainers, func(container *corev1.Container) {
   380  		addVolumeMountToContainer(container, corev1.VolumeMount{
   381  			Name:      volumeName,
   382  			MountPath: volumeInfo.MountPath,
   383  			ReadOnly:  volumeInfo.ReadOnly,
   384  		})
   385  	})
   386  
   387  	volumes = append(volumes, corev1.Volume{
   388  		Name: volumeName,
   389  		VolumeSource: corev1.VolumeSource{
   390  			ConfigMap: &corev1.ConfigMapVolumeSource{
   391  				DefaultMode: volumeInfo.MountAccessMode,
   392  				LocalObjectReference: corev1.LocalObjectReference{
   393  					Name: volumeInfo.VolumeName,
   394  				},
   395  			},
   396  		},
   397  	})
   398  	return volumes
   399  }
   400  
   401  func mountConfigMapAsEnv(volumeInfo configAutomount.AutomountInfo, containers, initContainers []corev1.Container, volumes []corev1.Volume) []corev1.Volume {
   402  	inAllContainers(containers, initContainers, func(container *corev1.Container) {
   403  		addEnvFromToContainer(container, corev1.EnvFromSource{
   404  			ConfigMapRef: &corev1.ConfigMapEnvSource{
   405  				LocalObjectReference: corev1.LocalObjectReference{
   406  					Name: volumeInfo.VolumeName,
   407  				},
   408  			},
   409  		})
   410  	})
   411  	return volumes
   412  }
   413  
   414  func mountConfigMapAsSubpath(volumeInfo configAutomount.AutomountInfo, containers, initContainers []corev1.Container, volumes []corev1.Volume) []corev1.Volume {
   415  	volumeName := "auto-cm-" + volumeInfo.VolumeName
   416  
   417  	inAllContainers(containers, initContainers, func(container *corev1.Container) {
   418  		for _, key := range volumeInfo.Keys {
   419  			addVolumeMountToContainer(container, corev1.VolumeMount{
   420  				Name:      volumeName,
   421  				MountPath: filepath.ToSlash(filepath.Join(volumeInfo.MountPath, key)),
   422  				SubPath:   key,
   423  				ReadOnly:  volumeInfo.ReadOnly,
   424  			})
   425  		}
   426  	})
   427  
   428  	volumes = append(volumes, corev1.Volume{
   429  		Name: volumeName,
   430  		VolumeSource: corev1.VolumeSource{
   431  			ConfigMap: &corev1.ConfigMapVolumeSource{
   432  				LocalObjectReference: corev1.LocalObjectReference{
   433  					Name: volumeInfo.VolumeName,
   434  				},
   435  				DefaultMode: volumeInfo.MountAccessMode,
   436  			},
   437  		},
   438  	})
   439  	return volumes
   440  }
   441  
   442  func inAllContainers(containers, initContainers []corev1.Container, f func(container *corev1.Container)) {
   443  	for i := range containers {
   444  		f(&containers[i])
   445  	}
   446  	for i := range initContainers {
   447  		f(&initContainers[i])
   448  	}
   449  }
   450  
   451  func addVolumeMountToContainer(container *corev1.Container, volumeMount corev1.VolumeMount) {
   452  	container.VolumeMounts = append(container.VolumeMounts, volumeMount)
   453  }
   454  
   455  func addEnvFromToContainer(container *corev1.Container, envFrom corev1.EnvFromSource) {
   456  	container.EnvFrom = append(container.EnvFrom, envFrom)
   457  }
   458  

View as plain text