From 64c796a8c36fc8fddafd9752c1028b1b7223018b Mon Sep 17 00:00:00 2001 From: James Player Date: Tue, 8 Apr 2025 12:52:21 +1200 Subject: [PATCH] fix(kubernetes): Config maps and secrets show as unused BE-11684 (#596) Co-authored-by: stevensbkang --- api/http/handler/kubernetes/configmaps.go | 4 +- api/http/handler/kubernetes/secrets.go | 4 +- api/kubernetes/cli/applications.go | 100 +++++++--------------- api/kubernetes/cli/configmap.go | 48 +++++------ api/kubernetes/cli/pod.go | 21 +++-- api/kubernetes/cli/secret.go | 47 +++++----- 6 files changed, 91 insertions(+), 133 deletions(-) diff --git a/api/http/handler/kubernetes/configmaps.go b/api/http/handler/kubernetes/configmaps.go index 633afe92d..bd10ae15d 100644 --- a/api/http/handler/kubernetes/configmaps.go +++ b/api/http/handler/kubernetes/configmaps.go @@ -146,13 +146,11 @@ func (handler *Handler) getAllKubernetesConfigMaps(r *http.Request) ([]models.K8 } if isUsed { - configMapsWithApplications, err := cli.CombineConfigMapsWithApplications(configMaps) + err = cli.SetConfigMapsIsUsed(&configMaps) if err != nil { log.Error().Err(err).Str("context", "getAllKubernetesConfigMaps").Msg("Unable to combine configMaps with associated applications") return nil, httperror.InternalServerError("Unable to combine configMaps with associated applications", err) } - - return configMapsWithApplications, nil } return configMaps, nil diff --git a/api/http/handler/kubernetes/secrets.go b/api/http/handler/kubernetes/secrets.go index 1375e9e6b..782e6107e 100644 --- a/api/http/handler/kubernetes/secrets.go +++ b/api/http/handler/kubernetes/secrets.go @@ -130,13 +130,11 @@ func (handler *Handler) getAllKubernetesSecrets(r *http.Request) ([]models.K8sSe } if isUsed { - secretsWithApplications, err := cli.CombineSecretsWithApplications(secrets) + err = cli.SetSecretsIsUsed(&secrets) if err != nil { log.Error().Err(err).Str("context", "GetAllKubernetesSecrets").Msg("Unable to combine secrets with associated applications") return nil, httperror.InternalServerError("unable to combine secrets with associated applications. Error: ", err) } - - return secretsWithApplications, nil } return secrets, nil diff --git a/api/kubernetes/cli/applications.go b/api/kubernetes/cli/applications.go index c08329a7b..8dd4d72b2 100644 --- a/api/kubernetes/cli/applications.go +++ b/api/kubernetes/cli/applications.go @@ -153,46 +153,6 @@ func (kcl *KubeClient) GetApplicationsResource(namespace, node string) (models.K return resource, nil } -// GetApplicationsFromConfigMap gets a list of applications that use a specific ConfigMap -// by checking all pods in the same namespace as the ConfigMap -func (kcl *KubeClient) GetApplicationNamesFromConfigMap(configMap models.K8sConfigMap, pods []corev1.Pod, replicaSets []appsv1.ReplicaSet) ([]string, error) { - applications := []string{} - for _, pod := range pods { - if pod.Namespace == configMap.Namespace { - if isPodUsingConfigMap(&pod, configMap.Name) { - application, err := kcl.ConvertPodToApplication(pod, PortainerApplicationResources{ - ReplicaSets: replicaSets, - }, false) - if err != nil { - return nil, err - } - applications = append(applications, application.Name) - } - } - } - - return applications, nil -} - -func (kcl *KubeClient) GetApplicationNamesFromSecret(secret models.K8sSecret, pods []corev1.Pod, replicaSets []appsv1.ReplicaSet) ([]string, error) { - applications := []string{} - for _, pod := range pods { - if pod.Namespace == secret.Namespace { - if isPodUsingSecret(&pod, secret.Name) { - application, err := kcl.ConvertPodToApplication(pod, PortainerApplicationResources{ - ReplicaSets: replicaSets, - }, false) - if err != nil { - return nil, err - } - applications = append(applications, application.Name) - } - } - } - - return applications, nil -} - // ConvertPodToApplication converts a pod to an application, updating owner references if necessary func (kcl *KubeClient) ConvertPodToApplication(pod corev1.Pod, portainerApplicationResources PortainerApplicationResources, withResource bool) (*models.K8sApplication, error) { if isReplicaSetOwner(pod) { @@ -473,23 +433,23 @@ func (kcl *KubeClient) GetApplicationFromServiceSelector(pods []corev1.Pod, serv func (kcl *KubeClient) GetApplicationConfigurationOwnersFromConfigMap(configMap models.K8sConfigMap, pods []corev1.Pod, replicaSets []appsv1.ReplicaSet) ([]models.K8sConfigurationOwnerResource, error) { configurationOwners := []models.K8sConfigurationOwnerResource{} for _, pod := range pods { - if pod.Namespace == configMap.Namespace { - if isPodUsingConfigMap(&pod, configMap.Name) { - application, err := kcl.ConvertPodToApplication(pod, PortainerApplicationResources{ - ReplicaSets: replicaSets, - }, false) - if err != nil { - return nil, err - } + if isPodUsingConfigMap(&pod, configMap) { + kind := "Pod" + name := pod.Name - if application != nil { - configurationOwners = append(configurationOwners, models.K8sConfigurationOwnerResource{ - Name: application.Name, - ResourceKind: application.Kind, - Id: application.UID, - }) - } + if len(pod.OwnerReferences) > 0 { + kind = pod.OwnerReferences[0].Kind + name = pod.OwnerReferences[0].Name } + + if isReplicaSetOwner(pod) { + updateOwnerReferenceToDeployment(&pod, replicaSets) + } + + configurationOwners = append(configurationOwners, models.K8sConfigurationOwnerResource{ + Name: name, + ResourceKind: kind, + }) } } @@ -501,23 +461,23 @@ func (kcl *KubeClient) GetApplicationConfigurationOwnersFromConfigMap(configMap func (kcl *KubeClient) GetApplicationConfigurationOwnersFromSecret(secret models.K8sSecret, pods []corev1.Pod, replicaSets []appsv1.ReplicaSet) ([]models.K8sConfigurationOwnerResource, error) { configurationOwners := []models.K8sConfigurationOwnerResource{} for _, pod := range pods { - if pod.Namespace == secret.Namespace { - if isPodUsingSecret(&pod, secret.Name) { - application, err := kcl.ConvertPodToApplication(pod, PortainerApplicationResources{ - ReplicaSets: replicaSets, - }, false) - if err != nil { - return nil, err - } + if isPodUsingSecret(&pod, secret) { + kind := "Pod" + name := pod.Name - if application != nil { - configurationOwners = append(configurationOwners, models.K8sConfigurationOwnerResource{ - Name: application.Name, - ResourceKind: application.Kind, - Id: application.UID, - }) - } + if len(pod.OwnerReferences) > 0 { + kind = pod.OwnerReferences[0].Kind + name = pod.OwnerReferences[0].Name } + + if isReplicaSetOwner(pod) { + updateOwnerReferenceToDeployment(&pod, replicaSets) + } + + configurationOwners = append(configurationOwners, models.K8sConfigurationOwnerResource{ + Name: name, + ResourceKind: kind, + }) } } diff --git a/api/kubernetes/cli/configmap.go b/api/kubernetes/cli/configmap.go index eb0eec935..fafa81346 100644 --- a/api/kubernetes/cli/configmap.go +++ b/api/kubernetes/cli/configmap.go @@ -7,6 +7,7 @@ import ( 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" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -95,35 +96,28 @@ func parseConfigMap(configMap *corev1.ConfigMap, withData bool) models.K8sConfig return result } -// CombineConfigMapsWithApplications combines the config maps with the applications that use them. +// SetConfigMapsIsUsed combines the config maps with the applications that use them. // the function fetches all the pods and replica sets in the cluster and checks if the config map is used by any of the pods. // if the config map is used by a pod, the application that uses the pod is added to the config map. // otherwise, the config map is returned as is. -func (kcl *KubeClient) CombineConfigMapsWithApplications(configMaps []models.K8sConfigMap) ([]models.K8sConfigMap, error) { - updatedConfigMaps := make([]models.K8sConfigMap, len(configMaps)) - +func (kcl *KubeClient) SetConfigMapsIsUsed(configMaps *[]models.K8sConfigMap) error { portainerApplicationResources, err := kcl.fetchAllApplicationsListResources("", metav1.ListOptions{}) if err != nil { - return nil, fmt.Errorf("an error occurred during the CombineConfigMapsWithApplications operation, unable to fetch pods and replica sets. Error: %w", err) + return fmt.Errorf("an error occurred during the SetConfigMapsIsUsed operation, unable to fetch Portainer application resources. Error: %w", err) } - for index, configMap := range configMaps { - updatedConfigMap := configMap + for i := range *configMaps { + configMap := &(*configMaps)[i] - applicationConfigurationOwners, err := kcl.GetApplicationConfigurationOwnersFromConfigMap(configMap, portainerApplicationResources.Pods, portainerApplicationResources.ReplicaSets) - if err != nil { - return nil, fmt.Errorf("an error occurred during the CombineConfigMapsWithApplications operation, unable to get applications from config map. Error: %w", err) + for _, pod := range portainerApplicationResources.Pods { + if isPodUsingConfigMap(&pod, *configMap) { + configMap.IsUsed = true + break + } } - - if len(applicationConfigurationOwners) > 0 { - updatedConfigMap.ConfigurationOwnerResources = applicationConfigurationOwners - updatedConfigMap.IsUsed = true - } - - updatedConfigMaps[index] = updatedConfigMap } - return updatedConfigMaps, nil + return nil } // CombineConfigMapWithApplications combines the config map with the applications that use it. @@ -141,20 +135,22 @@ func (kcl *KubeClient) CombineConfigMapWithApplications(configMap models.K8sConf break } + var replicaSets *appsv1.ReplicaSetList if containsReplicaSetOwner { - replicaSets, err := kcl.cli.AppsV1().ReplicaSets(configMap.Namespace).List(context.Background(), metav1.ListOptions{}) + replicaSets, err = kcl.cli.AppsV1().ReplicaSets(configMap.Namespace).List(context.Background(), metav1.ListOptions{}) if err != nil { return models.K8sConfigMap{}, fmt.Errorf("an error occurred during the CombineConfigMapWithApplications operation, unable to get replica sets. Error: %w", err) } + } - applicationConfigurationOwners, err := kcl.GetApplicationConfigurationOwnersFromConfigMap(configMap, pods.Items, replicaSets.Items) - if err != nil { - return models.K8sConfigMap{}, fmt.Errorf("an error occurred during the CombineConfigMapWithApplications operation, unable to get applications from config map. Error: %w", err) - } + applicationConfigurationOwners, err := kcl.GetApplicationConfigurationOwnersFromConfigMap(configMap, pods.Items, replicaSets.Items) + if err != nil { + return models.K8sConfigMap{}, fmt.Errorf("an error occurred during the CombineConfigMapWithApplications operation, unable to get applications from config map. Error: %w", err) + } - if len(applicationConfigurationOwners) > 0 { - configMap.ConfigurationOwnerResources = applicationConfigurationOwners - } + if len(applicationConfigurationOwners) > 0 { + configMap.ConfigurationOwnerResources = applicationConfigurationOwners + configMap.IsUsed = true } return configMap, nil diff --git a/api/kubernetes/cli/pod.go b/api/kubernetes/cli/pod.go index bb3e46077..eb8992124 100644 --- a/api/kubernetes/cli/pod.go +++ b/api/kubernetes/cli/pod.go @@ -7,6 +7,7 @@ import ( "time" portainer "github.com/portainer/portainer/api" + models "github.com/portainer/portainer/api/http/models/kubernetes" "github.com/pkg/errors" "github.com/rs/zerolog/log" @@ -235,16 +236,20 @@ func (kcl *KubeClient) fetchResourcesWithOwnerReferences(namespace string, podLi } // isPodUsingConfigMap checks if a pod is using a specific ConfigMap -func isPodUsingConfigMap(pod *corev1.Pod, configMapName string) bool { +func isPodUsingConfigMap(pod *corev1.Pod, configMap models.K8sConfigMap) bool { + if pod.Namespace != configMap.Namespace { + return false + } + for _, volume := range pod.Spec.Volumes { - if volume.ConfigMap != nil && volume.ConfigMap.Name == configMapName { + if volume.ConfigMap != nil && volume.ConfigMap.Name == configMap.Name { return true } } for _, container := range pod.Spec.Containers { for _, env := range container.Env { - if env.ValueFrom != nil && env.ValueFrom.ConfigMapKeyRef != nil && env.ValueFrom.ConfigMapKeyRef.Name == configMapName { + if env.ValueFrom != nil && env.ValueFrom.ConfigMapKeyRef != nil && env.ValueFrom.ConfigMapKeyRef.Name == configMap.Name { return true } } @@ -254,16 +259,20 @@ func isPodUsingConfigMap(pod *corev1.Pod, configMapName string) bool { } // isPodUsingSecret checks if a pod is using a specific Secret -func isPodUsingSecret(pod *corev1.Pod, secretName string) bool { +func isPodUsingSecret(pod *corev1.Pod, secret models.K8sSecret) bool { + if pod.Namespace != secret.Namespace { + return false + } + for _, volume := range pod.Spec.Volumes { - if volume.Secret != nil && volume.Secret.SecretName == secretName { + if volume.Secret != nil && volume.Secret.SecretName == secret.Name { return true } } for _, container := range pod.Spec.Containers { for _, env := range container.Env { - if env.ValueFrom != nil && env.ValueFrom.SecretKeyRef != nil && env.ValueFrom.SecretKeyRef.Name == secretName { + if env.ValueFrom != nil && env.ValueFrom.SecretKeyRef != nil && env.ValueFrom.SecretKeyRef.Name == secret.Name { return true } } diff --git a/api/kubernetes/cli/secret.go b/api/kubernetes/cli/secret.go index 5b7bf4fef..5bcd386cb 100644 --- a/api/kubernetes/cli/secret.go +++ b/api/kubernetes/cli/secret.go @@ -8,6 +8,7 @@ import ( 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" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -111,34 +112,28 @@ func parseSecret(secret *corev1.Secret, withData bool) models.K8sSecret { return result } -// CombineSecretsWithApplications combines the secrets with the applications that use them. +// SetSecretsIsUsed combines the secrets with the applications that use them. // the function fetches all the pods and replica sets in the cluster and checks if the secret is used by any of the pods. // if the secret is used by a pod, the application that uses the pod is added to the secret. // otherwise, the secret is returned as is. -func (kcl *KubeClient) CombineSecretsWithApplications(secrets []models.K8sSecret) ([]models.K8sSecret, error) { - updatedSecrets := make([]models.K8sSecret, len(secrets)) - +func (kcl *KubeClient) SetSecretsIsUsed(secrets *[]models.K8sSecret) error { portainerApplicationResources, err := kcl.fetchAllApplicationsListResources("", metav1.ListOptions{}) if err != nil { - return nil, fmt.Errorf("an error occurred during the CombineSecretsWithApplications operation, unable to fetch pods and replica sets. Error: %w", err) + return fmt.Errorf("an error occurred during the SetSecretsIsUsed operation, unable to fetch Portainer application resources. Error: %w", err) } - for index, secret := range secrets { - updatedSecret := secret + for i := range *secrets { + secret := &(*secrets)[i] - applicationConfigurationOwners, err := kcl.GetApplicationConfigurationOwnersFromSecret(secret, portainerApplicationResources.Pods, portainerApplicationResources.ReplicaSets) - if err != nil { - return nil, fmt.Errorf("an error occurred during the CombineSecretsWithApplications operation, unable to get applications from secret. Error: %w", err) + for _, pod := range portainerApplicationResources.Pods { + if isPodUsingSecret(&pod, *secret) { + secret.IsUsed = true + break + } } - - if len(applicationConfigurationOwners) > 0 { - updatedSecret.ConfigurationOwnerResources = applicationConfigurationOwners - } - - updatedSecrets[index] = updatedSecret } - return updatedSecrets, nil + return nil } // CombineSecretWithApplications combines the secret with the applications that use it. @@ -156,20 +151,22 @@ func (kcl *KubeClient) CombineSecretWithApplications(secret models.K8sSecret) (m break } + var replicaSets *appsv1.ReplicaSetList if containsReplicaSetOwner { - replicaSets, err := kcl.cli.AppsV1().ReplicaSets(secret.Namespace).List(context.Background(), metav1.ListOptions{}) + replicaSets, err = kcl.cli.AppsV1().ReplicaSets(secret.Namespace).List(context.Background(), metav1.ListOptions{}) if err != nil { return models.K8sSecret{}, fmt.Errorf("an error occurred during the CombineSecretWithApplications operation, unable to get replica sets. Error: %w", err) } + } - applicationConfigurationOwners, err := kcl.GetApplicationConfigurationOwnersFromSecret(secret, pods.Items, replicaSets.Items) - if err != nil { - return models.K8sSecret{}, fmt.Errorf("an error occurred during the CombineSecretWithApplications operation, unable to get applications from secret. Error: %w", err) - } + applicationConfigurationOwners, err := kcl.GetApplicationConfigurationOwnersFromSecret(secret, pods.Items, replicaSets.Items) + if err != nil { + return models.K8sSecret{}, fmt.Errorf("an error occurred during the CombineSecretWithApplications operation, unable to get applications from secret. Error: %w", err) + } - if len(applicationConfigurationOwners) > 0 { - secret.ConfigurationOwnerResources = applicationConfigurationOwners - } + if len(applicationConfigurationOwners) > 0 { + secret.ConfigurationOwnerResources = applicationConfigurationOwners + secret.IsUsed = true } return secret, nil