1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-23 15:29:42 +02:00

fix(kubernetes): Config maps and secrets show as unused BE-11684 (#596)

Co-authored-by: stevensbkang <skan070@gmail.com>
This commit is contained in:
James Player 2025-04-08 12:52:21 +12:00 committed by GitHub
parent 264ff5457b
commit 64c796a8c3
6 changed files with 91 additions and 133 deletions

View file

@ -146,13 +146,11 @@ func (handler *Handler) getAllKubernetesConfigMaps(r *http.Request) ([]models.K8
} }
if isUsed { if isUsed {
configMapsWithApplications, err := cli.CombineConfigMapsWithApplications(configMaps) err = cli.SetConfigMapsIsUsed(&configMaps)
if err != nil { if err != nil {
log.Error().Err(err).Str("context", "getAllKubernetesConfigMaps").Msg("Unable to combine configMaps with associated applications") 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 nil, httperror.InternalServerError("Unable to combine configMaps with associated applications", err)
} }
return configMapsWithApplications, nil
} }
return configMaps, nil return configMaps, nil

View file

@ -130,13 +130,11 @@ func (handler *Handler) getAllKubernetesSecrets(r *http.Request) ([]models.K8sSe
} }
if isUsed { if isUsed {
secretsWithApplications, err := cli.CombineSecretsWithApplications(secrets) err = cli.SetSecretsIsUsed(&secrets)
if err != nil { if err != nil {
log.Error().Err(err).Str("context", "GetAllKubernetesSecrets").Msg("Unable to combine secrets with associated applications") 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 nil, httperror.InternalServerError("unable to combine secrets with associated applications. Error: ", err)
} }
return secretsWithApplications, nil
} }
return secrets, nil return secrets, nil

View file

@ -153,46 +153,6 @@ func (kcl *KubeClient) GetApplicationsResource(namespace, node string) (models.K
return resource, nil 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 // 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) { func (kcl *KubeClient) ConvertPodToApplication(pod corev1.Pod, portainerApplicationResources PortainerApplicationResources, withResource bool) (*models.K8sApplication, error) {
if isReplicaSetOwner(pod) { 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) { func (kcl *KubeClient) GetApplicationConfigurationOwnersFromConfigMap(configMap models.K8sConfigMap, pods []corev1.Pod, replicaSets []appsv1.ReplicaSet) ([]models.K8sConfigurationOwnerResource, error) {
configurationOwners := []models.K8sConfigurationOwnerResource{} configurationOwners := []models.K8sConfigurationOwnerResource{}
for _, pod := range pods { for _, pod := range pods {
if pod.Namespace == configMap.Namespace { if isPodUsingConfigMap(&pod, configMap) {
if isPodUsingConfigMap(&pod, configMap.Name) { kind := "Pod"
application, err := kcl.ConvertPodToApplication(pod, PortainerApplicationResources{ name := pod.Name
ReplicaSets: replicaSets,
}, false)
if err != nil {
return nil, err
}
if application != nil { if len(pod.OwnerReferences) > 0 {
configurationOwners = append(configurationOwners, models.K8sConfigurationOwnerResource{ kind = pod.OwnerReferences[0].Kind
Name: application.Name, name = pod.OwnerReferences[0].Name
ResourceKind: application.Kind,
Id: application.UID,
})
}
} }
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) { func (kcl *KubeClient) GetApplicationConfigurationOwnersFromSecret(secret models.K8sSecret, pods []corev1.Pod, replicaSets []appsv1.ReplicaSet) ([]models.K8sConfigurationOwnerResource, error) {
configurationOwners := []models.K8sConfigurationOwnerResource{} configurationOwners := []models.K8sConfigurationOwnerResource{}
for _, pod := range pods { for _, pod := range pods {
if pod.Namespace == secret.Namespace { if isPodUsingSecret(&pod, secret) {
if isPodUsingSecret(&pod, secret.Name) { kind := "Pod"
application, err := kcl.ConvertPodToApplication(pod, PortainerApplicationResources{ name := pod.Name
ReplicaSets: replicaSets,
}, false)
if err != nil {
return nil, err
}
if application != nil { if len(pod.OwnerReferences) > 0 {
configurationOwners = append(configurationOwners, models.K8sConfigurationOwnerResource{ kind = pod.OwnerReferences[0].Kind
Name: application.Name, name = pod.OwnerReferences[0].Name
ResourceKind: application.Kind,
Id: application.UID,
})
}
} }
if isReplicaSetOwner(pod) {
updateOwnerReferenceToDeployment(&pod, replicaSets)
}
configurationOwners = append(configurationOwners, models.K8sConfigurationOwnerResource{
Name: name,
ResourceKind: kind,
})
} }
} }

View file

@ -7,6 +7,7 @@ import (
models "github.com/portainer/portainer/api/http/models/kubernetes" models "github.com/portainer/portainer/api/http/models/kubernetes"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
@ -95,35 +96,28 @@ func parseConfigMap(configMap *corev1.ConfigMap, withData bool) models.K8sConfig
return result 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. // 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. // 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. // otherwise, the config map is returned as is.
func (kcl *KubeClient) CombineConfigMapsWithApplications(configMaps []models.K8sConfigMap) ([]models.K8sConfigMap, error) { func (kcl *KubeClient) SetConfigMapsIsUsed(configMaps *[]models.K8sConfigMap) error {
updatedConfigMaps := make([]models.K8sConfigMap, len(configMaps))
portainerApplicationResources, err := kcl.fetchAllApplicationsListResources("", metav1.ListOptions{}) portainerApplicationResources, err := kcl.fetchAllApplicationsListResources("", metav1.ListOptions{})
if err != nil { 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 { for i := range *configMaps {
updatedConfigMap := configMap configMap := &(*configMaps)[i]
applicationConfigurationOwners, err := kcl.GetApplicationConfigurationOwnersFromConfigMap(configMap, portainerApplicationResources.Pods, portainerApplicationResources.ReplicaSets) for _, pod := range portainerApplicationResources.Pods {
if err != nil { if isPodUsingConfigMap(&pod, *configMap) {
return nil, fmt.Errorf("an error occurred during the CombineConfigMapsWithApplications operation, unable to get applications from config map. Error: %w", err) 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. // CombineConfigMapWithApplications combines the config map with the applications that use it.
@ -141,20 +135,22 @@ func (kcl *KubeClient) CombineConfigMapWithApplications(configMap models.K8sConf
break break
} }
var replicaSets *appsv1.ReplicaSetList
if containsReplicaSetOwner { 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 { if err != nil {
return models.K8sConfigMap{}, fmt.Errorf("an error occurred during the CombineConfigMapWithApplications operation, unable to get replica sets. Error: %w", err) 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) applicationConfigurationOwners, err := kcl.GetApplicationConfigurationOwnersFromConfigMap(configMap, pods.Items, replicaSets.Items)
if err != nil { 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) 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 { if len(applicationConfigurationOwners) > 0 {
configMap.ConfigurationOwnerResources = applicationConfigurationOwners configMap.ConfigurationOwnerResources = applicationConfigurationOwners
} configMap.IsUsed = true
} }
return configMap, nil return configMap, nil

View file

@ -7,6 +7,7 @@ import (
"time" "time"
portainer "github.com/portainer/portainer/api" portainer "github.com/portainer/portainer/api"
models "github.com/portainer/portainer/api/http/models/kubernetes"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rs/zerolog/log" "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 // 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 { 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 return true
} }
} }
for _, container := range pod.Spec.Containers { for _, container := range pod.Spec.Containers {
for _, env := range container.Env { 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 return true
} }
} }
@ -254,16 +259,20 @@ func isPodUsingConfigMap(pod *corev1.Pod, configMapName string) bool {
} }
// isPodUsingSecret checks if a pod is using a specific Secret // 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 { 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 return true
} }
} }
for _, container := range pod.Spec.Containers { for _, container := range pod.Spec.Containers {
for _, env := range container.Env { 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 return true
} }
} }

View file

@ -8,6 +8,7 @@ import (
models "github.com/portainer/portainer/api/http/models/kubernetes" models "github.com/portainer/portainer/api/http/models/kubernetes"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors" k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -111,34 +112,28 @@ func parseSecret(secret *corev1.Secret, withData bool) models.K8sSecret {
return result 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. // 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. // 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. // otherwise, the secret is returned as is.
func (kcl *KubeClient) CombineSecretsWithApplications(secrets []models.K8sSecret) ([]models.K8sSecret, error) { func (kcl *KubeClient) SetSecretsIsUsed(secrets *[]models.K8sSecret) error {
updatedSecrets := make([]models.K8sSecret, len(secrets))
portainerApplicationResources, err := kcl.fetchAllApplicationsListResources("", metav1.ListOptions{}) portainerApplicationResources, err := kcl.fetchAllApplicationsListResources("", metav1.ListOptions{})
if err != nil { 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 { for i := range *secrets {
updatedSecret := secret secret := &(*secrets)[i]
applicationConfigurationOwners, err := kcl.GetApplicationConfigurationOwnersFromSecret(secret, portainerApplicationResources.Pods, portainerApplicationResources.ReplicaSets) for _, pod := range portainerApplicationResources.Pods {
if err != nil { if isPodUsingSecret(&pod, *secret) {
return nil, fmt.Errorf("an error occurred during the CombineSecretsWithApplications operation, unable to get applications from secret. Error: %w", err) 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. // CombineSecretWithApplications combines the secret with the applications that use it.
@ -156,20 +151,22 @@ func (kcl *KubeClient) CombineSecretWithApplications(secret models.K8sSecret) (m
break break
} }
var replicaSets *appsv1.ReplicaSetList
if containsReplicaSetOwner { 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 { if err != nil {
return models.K8sSecret{}, fmt.Errorf("an error occurred during the CombineSecretWithApplications operation, unable to get replica sets. Error: %w", err) 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) applicationConfigurationOwners, err := kcl.GetApplicationConfigurationOwnersFromSecret(secret, pods.Items, replicaSets.Items)
if err != nil { if err != nil {
return models.K8sSecret{}, fmt.Errorf("an error occurred during the CombineSecretWithApplications operation, unable to get applications from secret. Error: %w", err) 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 { if len(applicationConfigurationOwners) > 0 {
secret.ConfigurationOwnerResources = applicationConfigurationOwners secret.ConfigurationOwnerResources = applicationConfigurationOwners
} secret.IsUsed = true
} }
return secret, nil return secret, nil