Files
odo/pkg/storage/kubernetes.go
Philippe Martin 7a721348b3 Automount volumes (#6698)
* Simplify AddOdoProjectVolume and AddOdoMandatoryVolume

* Rename / Clarify HandleEphemeralStorage function

* Regroup volume-specific code

* Move volume specific code to a separated function

* Add a new module configAutomount

* Automount PVC (without options)

* Add unit tests

* Separate functions

* Mount secrets

* Mount configmaps

* Specific mount path

* MountAs annotation

* Mounting cm/secret as env

* Refacto: use inAllContainers + replace result with volume

* Mounting cm/secret as subpath

* Read-only

* Integration tests

* Rename label

* Automount during odo deploy Exec command

* Add documentation

* Fix TODO

* Review

* Fix indentation

* Rename labels/annotations
2023-04-18 07:58:45 -04:00

186 lines
5.4 KiB
Go

package storage
import (
"fmt"
"strings"
"github.com/devfile/library/v2/pkg/devfile/generator"
v1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog"
"github.com/redhat-developer/odo/pkg/kclient"
odolabels "github.com/redhat-developer/odo/pkg/labels"
)
// kubernetesClient contains information required for devfile based Storage operations
type kubernetesClient struct {
generic
client kclient.ClientInterface
// if we don't have access to the local config
// we can use the deployment to call List() and
// directly list storage from the cluster without the local config
deployment *v1.Deployment
}
var _ Client = (*kubernetesClient)(nil)
// Create creates a pvc from the given Storage
func (k kubernetesClient) Create(storage Storage) error {
if k.componentName == "" || k.appName == "" {
return fmt.Errorf("the component name and the app name should be provided")
}
pvcName, err := generatePVCName(storage.Name, k.componentName, k.appName)
if err != nil {
return err
}
labels := odolabels.GetLabels(k.componentName, k.appName, k.runtime, odolabels.ComponentDevMode, false)
odolabels.AddStorageInfo(labels, storage.Name, strings.Contains(storage.Name, OdoSourceVolume))
objectMeta := generator.GetObjectMeta(pvcName, k.client.GetCurrentNamespace(), labels, nil)
quantity, err := resource.ParseQuantity(storage.Spec.Size)
if err != nil {
return fmt.Errorf("unable to parse size: %v: %w", storage.Spec.Size, err)
}
pvcParams := generator.PVCParams{
ObjectMeta: objectMeta,
Quantity: quantity,
}
pvc := generator.GetPVC(pvcParams)
// Create PVC
klog.V(2).Infof("Creating a PVC with name %v and labels %v", pvcName, labels)
_, err = k.client.CreatePVC(*pvc)
if err != nil {
return fmt.Errorf("unable to create PVC: %w", err)
}
return nil
}
// Delete deletes the pvc belonging to the given Storage
func (k kubernetesClient) Delete(name string) error {
pvcName, err := getPVCNameFromStorageName(k.client, name)
if err != nil {
return err
}
// delete the associated PVC with the component
err = k.client.DeletePVC(pvcName)
if err != nil {
return fmt.Errorf("unable to delete PVC %v: %w", pvcName, err)
}
return nil
}
// List lists pvc based Storage from the cluster
func (k kubernetesClient) List() (StorageList, error) {
if k.deployment == nil {
var err error
selector := odolabels.GetSelector(k.componentName, k.appName, odolabels.ComponentAnyMode, true)
k.deployment, err = k.client.GetOneDeploymentFromSelector(selector)
if err != nil {
if _, ok := err.(*kclient.DeploymentNotFoundError); ok {
return StorageList{}, nil
}
return StorageList{}, err
}
}
initContainerVolumeMounts := make(map[string]bool)
for _, container := range k.deployment.Spec.Template.Spec.InitContainers {
for _, volumeMount := range container.VolumeMounts {
initContainerVolumeMounts[volumeMount.Name] = true
}
}
containerVolumeMounts := make(map[string]bool)
for _, container := range k.deployment.Spec.Template.Spec.Containers {
for _, volumeMount := range container.VolumeMounts {
containerVolumeMounts[volumeMount.Name] = true
}
}
var storage []Storage
var volumeMounts []Storage
for _, container := range k.deployment.Spec.Template.Spec.Containers {
for _, volumeMount := range container.VolumeMounts {
// avoid the volume mounts:
// - only from the init containers
// - of source volume
// - of automounted volumes
_, initOK := initContainerVolumeMounts[volumeMount.Name]
_, ok := containerVolumeMounts[volumeMount.Name]
if (!ok && initOK) ||
volumeMount.Name == OdoSourceVolume ||
volumeMount.Name == SharedDataVolumeName ||
strings.HasPrefix(volumeMount.Name, "auto-") {
continue
}
volumeMounts = append(volumeMounts, Storage{
ObjectMeta: metav1.ObjectMeta{Name: volumeMount.Name},
Spec: StorageSpec{
Path: volumeMount.MountPath,
ContainerName: container.Name,
},
})
}
}
if len(volumeMounts) <= 0 {
return StorageList{}, nil
}
selector := odolabels.SelectorBuilder().WithComponent(k.componentName).WithoutSourcePVC(OdoSourceVolume).Selector()
pvcs, err := k.client.ListPVCs(selector)
if err != nil {
return StorageList{}, fmt.Errorf("unable to get PVC using selector %q: %w", selector, err)
}
// to track volume mounts used by a PVC
validVolumeMounts := make(map[string]bool)
for _, pvc := range pvcs {
found := false
for _, volumeMount := range volumeMounts {
if volumeMount.Name == pvc.Name+"-vol" {
// this volume mount is used by a PVC
validVolumeMounts[volumeMount.Name] = true
found = true
size := pvc.Spec.Resources.Requests[corev1.ResourceStorage]
storage = append(storage, NewStorageWithContainer(odolabels.GetDevfileStorageName(pvc.Labels), size.String(), volumeMount.Spec.Path, volumeMount.Spec.ContainerName, nil))
}
}
if !found {
return StorageList{}, fmt.Errorf("mount path for pvc %s not found", pvc.Name)
}
}
// to track non PVC volumes
for _, volume := range k.deployment.Spec.Template.Spec.Volumes {
if volume.PersistentVolumeClaim == nil {
validVolumeMounts[volume.Name] = true
}
}
for _, volumeMount := range volumeMounts {
if _, ok := validVolumeMounts[volumeMount.Name]; !ok {
return StorageList{}, fmt.Errorf("pvc not found for mount path %s", volumeMount.Name)
}
}
return StorageList{Items: storage}, nil
}