mirror of
https://github.com/portainer/portainer.git
synced 2025-07-24 07:49:41 +02:00
refactor(k8s): namespace core logic (#12142)
Co-authored-by: testA113 <aliharriss1995@gmail.com> Co-authored-by: Anthony Lapenna <anthony.lapenna@portainer.io> Co-authored-by: James Carppe <85850129+jamescarppe@users.noreply.github.com> Co-authored-by: Ali <83188384+testA113@users.noreply.github.com>
This commit is contained in:
parent
da010f3d08
commit
ea228c3d6d
276 changed files with 9241 additions and 3361 deletions
258
api/kubernetes/cli/volumes.go
Normal file
258
api/kubernetes/cli/volumes.go
Normal file
|
@ -0,0 +1,258 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
models "github.com/portainer/portainer/api/http/models/kubernetes"
|
||||
"github.com/rs/zerolog/log"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
storagev1 "k8s.io/api/storage/v1"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// GetVolumes gets the volumes in the current k8s environment(endpoint).
|
||||
// If the user is an admin, it fetches all the volumes in the cluster.
|
||||
// If the user is not an admin, it fetches the volumes in the namespaces the user has access to.
|
||||
// It returns a list of K8sVolumeInfo.
|
||||
func (kcl *KubeClient) GetVolumes(namespace string) ([]models.K8sVolumeInfo, error) {
|
||||
if kcl.IsKubeAdmin {
|
||||
return kcl.fetchVolumes(namespace)
|
||||
}
|
||||
return kcl.fetchVolumesForNonAdmin(namespace)
|
||||
}
|
||||
|
||||
// GetVolume gets the volume with the given name and namespace.
|
||||
func (kcl *KubeClient) GetVolume(namespace, volumeName string) (*models.K8sVolumeInfo, error) {
|
||||
persistentVolumeClaim, err := kcl.cli.CoreV1().PersistentVolumeClaims(namespace).Get(context.TODO(), volumeName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if k8serrors.IsNotFound(err) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
persistentVolumesMap, storageClassesMap, err := kcl.fetchPersistentVolumesAndStorageClassesMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
volume := parseVolume(persistentVolumeClaim, persistentVolumesMap, storageClassesMap)
|
||||
return &volume, nil
|
||||
}
|
||||
|
||||
// fetchVolumesForNonAdmin fetches the volumes in the namespaces the user has access to.
|
||||
// This function is called when the user is not an admin.
|
||||
// It fetches all the persistent volume claims, persistent volumes and storage classes in the namespaces the user has access to.
|
||||
func (kcl *KubeClient) fetchVolumesForNonAdmin(namespace string) ([]models.K8sVolumeInfo, error) {
|
||||
log.Debug().Msgf("Fetching volumes for non-admin user: %v", kcl.NonAdminNamespaces)
|
||||
|
||||
if len(kcl.NonAdminNamespaces) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
volumes, err := kcl.fetchVolumes(namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonAdminNamespaceSet := kcl.buildNonAdminNamespacesMap()
|
||||
results := make([]models.K8sVolumeInfo, 0)
|
||||
for _, volume := range volumes {
|
||||
if _, ok := nonAdminNamespaceSet[volume.PersistentVolumeClaim.Namespace]; ok {
|
||||
results = append(results, volume)
|
||||
}
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// fetchVolumes fetches all the persistent volume claims, persistent volumes and storage classes in the given namespace.
|
||||
// It returns a list of K8sVolumeInfo.
|
||||
// This function is called by fetchVolumesForAdmin and fetchVolumesForNonAdmin.
|
||||
func (kcl *KubeClient) fetchVolumes(namespace string) ([]models.K8sVolumeInfo, error) {
|
||||
volumes := make([]models.K8sVolumeInfo, 0)
|
||||
persistentVolumeClaims, err := kcl.cli.CoreV1().PersistentVolumeClaims(namespace).List(context.TODO(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(persistentVolumeClaims.Items) > 0 {
|
||||
persistentVolumesMap, storageClassesMap, err := kcl.fetchPersistentVolumesAndStorageClassesMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, persistentVolumeClaim := range persistentVolumeClaims.Items {
|
||||
volumes = append(volumes, parseVolume(&persistentVolumeClaim, persistentVolumesMap, storageClassesMap))
|
||||
}
|
||||
}
|
||||
|
||||
return volumes, nil
|
||||
}
|
||||
|
||||
// parseVolume parses the given persistent volume claim and returns a K8sVolumeInfo.
|
||||
// This function is called by fetchVolumes.
|
||||
// It returns a K8sVolumeInfo.
|
||||
func parseVolume(persistentVolumeClaim *corev1.PersistentVolumeClaim, persistentVolumesMap map[string]models.K8sPersistentVolume, storageClassesMap map[string]models.K8sStorageClass) models.K8sVolumeInfo {
|
||||
volume := models.K8sVolumeInfo{}
|
||||
volumeClaim := parsePersistentVolumeClaim(persistentVolumeClaim)
|
||||
|
||||
if volumeClaim.VolumeName != "" {
|
||||
persistentVolume, ok := persistentVolumesMap[volumeClaim.VolumeName]
|
||||
if ok {
|
||||
volume.PersistentVolume = persistentVolume
|
||||
}
|
||||
}
|
||||
|
||||
if volumeClaim.StorageClass != nil {
|
||||
storageClass, ok := storageClassesMap[*volumeClaim.StorageClass]
|
||||
if ok {
|
||||
volume.StorageClass = storageClass
|
||||
}
|
||||
}
|
||||
|
||||
volume.PersistentVolumeClaim = volumeClaim
|
||||
return volume
|
||||
}
|
||||
|
||||
// parsePersistentVolumeClaim parses the given persistent volume claim and returns a K8sPersistentVolumeClaim.
|
||||
func parsePersistentVolumeClaim(volume *corev1.PersistentVolumeClaim) models.K8sPersistentVolumeClaim {
|
||||
storage := volume.Spec.Resources.Requests[corev1.ResourceStorage]
|
||||
return models.K8sPersistentVolumeClaim{
|
||||
ID: string(volume.UID),
|
||||
Name: volume.Name,
|
||||
Namespace: volume.Namespace,
|
||||
CreationDate: volume.CreationTimestamp.Time,
|
||||
Storage: storage.Value(),
|
||||
AccessModes: volume.Spec.AccessModes,
|
||||
VolumeName: volume.Spec.VolumeName,
|
||||
ResourcesRequests: &volume.Spec.Resources.Requests,
|
||||
StorageClass: volume.Spec.StorageClassName,
|
||||
VolumeMode: volume.Spec.VolumeMode,
|
||||
OwningApplications: nil,
|
||||
Phase: volume.Status.Phase,
|
||||
}
|
||||
}
|
||||
|
||||
// parsePersistentVolume parses the given persistent volume and returns a K8sPersistentVolume.
|
||||
func parsePersistentVolume(volume *corev1.PersistentVolume) models.K8sPersistentVolume {
|
||||
return models.K8sPersistentVolume{
|
||||
Name: volume.Name,
|
||||
Annotations: volume.Annotations,
|
||||
AccessModes: volume.Spec.AccessModes,
|
||||
Capacity: volume.Spec.Capacity,
|
||||
ClaimRef: volume.Spec.ClaimRef,
|
||||
StorageClassName: volume.Spec.StorageClassName,
|
||||
PersistentVolumeReclaimPolicy: volume.Spec.PersistentVolumeReclaimPolicy,
|
||||
VolumeMode: volume.Spec.VolumeMode,
|
||||
CSI: volume.Spec.CSI,
|
||||
}
|
||||
}
|
||||
|
||||
// buildPersistentVolumesMap builds a map of persistent volumes.
|
||||
func (kcl *KubeClient) buildPersistentVolumesMap(persistentVolumes *corev1.PersistentVolumeList) map[string]models.K8sPersistentVolume {
|
||||
persistentVolumesMap := make(map[string]models.K8sPersistentVolume)
|
||||
for _, persistentVolume := range persistentVolumes.Items {
|
||||
persistentVolumesMap[persistentVolume.Name] = parsePersistentVolume(&persistentVolume)
|
||||
}
|
||||
|
||||
return persistentVolumesMap
|
||||
}
|
||||
|
||||
// parseStorageClass parses the given storage class and returns a K8sStorageClass.
|
||||
func parseStorageClass(storageClass *storagev1.StorageClass) models.K8sStorageClass {
|
||||
return models.K8sStorageClass{
|
||||
Name: storageClass.Name,
|
||||
Provisioner: storageClass.Provisioner,
|
||||
ReclaimPolicy: storageClass.ReclaimPolicy,
|
||||
AllowVolumeExpansion: storageClass.AllowVolumeExpansion,
|
||||
}
|
||||
}
|
||||
|
||||
// buildStorageClassesMap builds a map of storage classes.
|
||||
func (kcl *KubeClient) buildStorageClassesMap(storageClasses *storagev1.StorageClassList) map[string]models.K8sStorageClass {
|
||||
storageClassesMap := make(map[string]models.K8sStorageClass)
|
||||
for _, storageClass := range storageClasses.Items {
|
||||
storageClassesMap[storageClass.Name] = parseStorageClass(&storageClass)
|
||||
}
|
||||
|
||||
return storageClassesMap
|
||||
}
|
||||
|
||||
// fetchPersistentVolumesAndStorageClassesMap fetches all the persistent volumes and storage classes in the cluster.
|
||||
// It returns a map of persistent volumes and a map of storage classes.
|
||||
func (kcl *KubeClient) fetchPersistentVolumesAndStorageClassesMap() (map[string]models.K8sPersistentVolume, map[string]models.K8sStorageClass, error) {
|
||||
persistentVolumes, err := kcl.cli.CoreV1().PersistentVolumes().List(context.TODO(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
persistentVolumesMap := kcl.buildPersistentVolumesMap(persistentVolumes)
|
||||
|
||||
storageClasses, err := kcl.cli.StorageV1().StorageClasses().List(context.TODO(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
storageClassesMap := kcl.buildStorageClassesMap(storageClasses)
|
||||
|
||||
return persistentVolumesMap, storageClassesMap, nil
|
||||
}
|
||||
|
||||
// CombineVolumesWithApplications combines the volumes with the applications that use them.
|
||||
func (kcl *KubeClient) CombineVolumesWithApplications(volumes *[]models.K8sVolumeInfo) (*[]models.K8sVolumeInfo, error) {
|
||||
pods, err := kcl.cli.CoreV1().Pods("").List(context.Background(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
if k8serrors.IsNotFound(err) {
|
||||
return volumes, nil
|
||||
}
|
||||
log.Error().Err(err).Msg("Failed to list pods across the cluster")
|
||||
return nil, fmt.Errorf("an error occurred during the CombineServicesWithApplications operation, unable to list pods across the cluster. Error: %w", err)
|
||||
}
|
||||
|
||||
hasReplicaSetOwnerReference := containsReplicaSetOwnerReference(pods)
|
||||
replicaSetItems := make([]appsv1.ReplicaSet, 0)
|
||||
if hasReplicaSetOwnerReference {
|
||||
replicaSets, err := kcl.cli.AppsV1().ReplicaSets("").List(context.Background(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to list replica sets across the cluster")
|
||||
return nil, fmt.Errorf("an error occurred during the CombineVolumesWithApplications operation, unable to list replica sets across the cluster. Error: %w", err)
|
||||
}
|
||||
replicaSetItems = replicaSets.Items
|
||||
}
|
||||
|
||||
return kcl.updateVolumesWithOwningApplications(volumes, pods, replicaSetItems)
|
||||
}
|
||||
|
||||
// updateVolumesWithOwningApplications updates the volumes with the applications that use them.
|
||||
func (kcl *KubeClient) updateVolumesWithOwningApplications(volumes *[]models.K8sVolumeInfo, pods *corev1.PodList, replicaSetItems []appsv1.ReplicaSet) (*[]models.K8sVolumeInfo, error) {
|
||||
for i, volume := range *volumes {
|
||||
for _, pod := range pods.Items {
|
||||
if pod.Spec.Volumes != nil {
|
||||
for _, podVolume := range pod.Spec.Volumes {
|
||||
if podVolume.PersistentVolumeClaim != nil && podVolume.PersistentVolumeClaim.ClaimName == volume.PersistentVolumeClaim.Name && pod.Namespace == volume.PersistentVolumeClaim.Namespace {
|
||||
application, err := kcl.ConvertPodToApplication(pod, replicaSetItems, []appsv1.Deployment{}, []appsv1.StatefulSet{}, []appsv1.DaemonSet{}, []corev1.Service{}, false)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to convert pod to application")
|
||||
return nil, fmt.Errorf("an error occurred during the CombineServicesWithApplications operation, unable to convert pod to application. Error: %w", err)
|
||||
}
|
||||
// Check if the application already exists in the OwningApplications slice
|
||||
exists := false
|
||||
for _, existingApp := range (*volumes)[i].PersistentVolumeClaim.OwningApplications {
|
||||
if existingApp.Name == application.Name && existingApp.Namespace == application.Namespace {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exists && application != nil {
|
||||
(*volumes)[i].PersistentVolumeClaim.OwningApplications = append((*volumes)[i].PersistentVolumeClaim.OwningApplications, *application)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return volumes, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue