fix(kubernetes/cli): fix a data-race BE-12259 (#1227)

release/2.33
andres-portainer 2025-09-18 10:22:29 -03:00 committed by GitHub
parent 1e47df6611
commit 68b9fef3f0
29 changed files with 192 additions and 67 deletions

View File

@ -145,21 +145,33 @@ func (kcl *KubeClient) GetNonAdminNamespaces(userID int, teamIDs []int, isRestri
}
// GetIsKubeAdmin retrieves true if client is admin
func (client *KubeClient) GetIsKubeAdmin() bool {
return client.IsKubeAdmin
func (kcl *KubeClient) GetIsKubeAdmin() bool {
kcl.mu.Lock()
defer kcl.mu.Unlock()
return kcl.isKubeAdmin
}
// UpdateIsKubeAdmin sets whether the kube client is admin
func (client *KubeClient) SetIsKubeAdmin(isKubeAdmin bool) {
client.IsKubeAdmin = isKubeAdmin
func (kcl *KubeClient) SetIsKubeAdmin(isKubeAdmin bool) {
kcl.mu.Lock()
defer kcl.mu.Unlock()
kcl.isKubeAdmin = isKubeAdmin
}
// GetClientNonAdminNamespaces retrieves non-admin namespaces
func (client *KubeClient) GetClientNonAdminNamespaces() []string {
return client.NonAdminNamespaces
func (kcl *KubeClient) GetClientNonAdminNamespaces() []string {
kcl.mu.Lock()
defer kcl.mu.Unlock()
return kcl.nonAdminNamespaces
}
// UpdateClientNonAdminNamespaces sets the client non admin namespace list
func (client *KubeClient) SetClientNonAdminNamespaces(nonAdminNamespaces []string) {
client.NonAdminNamespaces = nonAdminNamespaces
func (kcl *KubeClient) SetClientNonAdminNamespaces(nonAdminNamespaces []string) {
kcl.mu.Lock()
defer kcl.mu.Unlock()
kcl.nonAdminNamespaces = nonAdminNamespaces
}

View File

@ -5,7 +5,9 @@ import (
"testing"
portainer "github.com/portainer/portainer/api"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
ktypes "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kfake "k8s.io/client-go/kubernetes/fake"
@ -65,3 +67,27 @@ func Test_NamespaceAccessPoliciesDeleteNamespace_updatesPortainerConfig_whenConf
})
}
}
func TestKubeAdmin(t *testing.T) {
kcl := &KubeClient{}
require.False(t, kcl.GetIsKubeAdmin())
kcl.SetIsKubeAdmin(true)
require.True(t, kcl.GetIsKubeAdmin())
kcl.SetIsKubeAdmin(false)
require.False(t, kcl.GetIsKubeAdmin())
}
func TestClientNonAdminNamespaces(t *testing.T) {
kcl := &KubeClient{}
require.Empty(t, kcl.GetClientNonAdminNamespaces())
nss := []string{"ns1", "ns2"}
kcl.SetClientNonAdminNamespaces(nss)
require.Equal(t, nss, kcl.GetClientNonAdminNamespaces())
kcl.SetClientNonAdminNamespaces([]string{})
require.Empty(t, kcl.GetClientNonAdminNamespaces())
}

View File

@ -28,7 +28,7 @@ type PortainerApplicationResources struct {
// if the user is an admin, all namespaces in the current k8s environment(endpoint) are fetched using the fetchApplications function.
// otherwise, namespaces the non-admin user has access to will be used to filter the applications based on the allowed namespaces.
func (kcl *KubeClient) GetApplications(namespace, nodeName string) ([]models.K8sApplication, error) {
if kcl.IsKubeAdmin {
if kcl.GetIsKubeAdmin() {
return kcl.fetchApplications(namespace, nodeName)
}
@ -64,9 +64,13 @@ func (kcl *KubeClient) fetchApplications(namespace, nodeName string) ([]models.K
// fetchApplicationsForNonAdmin fetches the applications in the namespaces the user has access to.
// This function is called when the user is not an admin.
func (kcl *KubeClient) fetchApplicationsForNonAdmin(namespace, nodeName string) ([]models.K8sApplication, error) {
log.Debug().Msgf("Fetching applications for non-admin user: %v", kcl.NonAdminNamespaces)
nonAdminNamespaces := kcl.GetClientNonAdminNamespaces()
if len(kcl.NonAdminNamespaces) == 0 {
log.Debug().
Strs("non_admin_namespaces", nonAdminNamespaces).
Msg("fetching applications for non-admin user")
if len(nonAdminNamespaces) == 0 {
return nil, nil
}

View File

@ -310,7 +310,7 @@ func TestGetApplications(t *testing.T) {
kubeClient := &KubeClient{
cli: fakeClient,
instanceID: "test-instance",
IsKubeAdmin: true,
isKubeAdmin: true,
}
// Test cases
@ -385,8 +385,8 @@ func TestGetApplications(t *testing.T) {
kubeClient := &KubeClient{
cli: fakeClient,
instanceID: "test-instance",
IsKubeAdmin: false,
NonAdminNamespaces: []string{namespace1},
isKubeAdmin: false,
nonAdminNamespaces: []string{namespace1},
}
// Test that only resources from allowed namespace are returned
@ -445,7 +445,7 @@ func TestGetApplications(t *testing.T) {
kubeClient := &KubeClient{
cli: fakeClient,
instanceID: "test-instance",
IsKubeAdmin: true,
isKubeAdmin: true,
}
// Test filtering by node name

View File

@ -42,8 +42,8 @@ type (
cli kubernetes.Interface
instanceID string
mu sync.Mutex
IsKubeAdmin bool
NonAdminNamespaces []string
isKubeAdmin bool
nonAdminNamespaces []string
}
)
@ -147,6 +147,7 @@ func (factory *ClientFactory) GetProxyKubeClient(endpointID, userID string) (*Ku
if ok {
return client.(*KubeClient), true
}
return nil, false
}
@ -179,8 +180,8 @@ func (factory *ClientFactory) CreateKubeClientFromKubeConfig(clusterID string, k
return &KubeClient{
cli: cli,
instanceID: factory.instanceID,
IsKubeAdmin: IsKubeAdmin,
NonAdminNamespaces: NonAdminNamespaces,
isKubeAdmin: IsKubeAdmin,
nonAdminNamespaces: NonAdminNamespaces,
}, nil
}
@ -193,7 +194,7 @@ func (factory *ClientFactory) createCachedPrivilegedKubeClient(endpoint *portain
return &KubeClient{
cli: cli,
instanceID: factory.instanceID,
IsKubeAdmin: true,
isKubeAdmin: true,
}, nil
}
@ -371,6 +372,7 @@ func (factory *ClientFactory) MigrateEndpointIngresses(e *portainer.Endpoint, da
log.Error().Err(err).Msgf("Error getting ingresses in environment %d", environment.ID)
return err
}
for _, ingress := range ingresses {
oldController, ok := ingress.Annotations["ingress.portainer.io/ingress-type"]
if !ok {

View File

@ -16,7 +16,7 @@ import (
// GetClusterRoles gets all the clusterRoles for at the cluster level in a k8s endpoint.
// It returns a list of K8sClusterRole objects.
func (kcl *KubeClient) GetClusterRoles() ([]models.K8sClusterRole, error) {
if kcl.IsKubeAdmin {
if kcl.GetIsKubeAdmin() {
return kcl.fetchClusterRoles()
}

View File

@ -16,7 +16,7 @@ import (
// GetClusterRoleBindings gets all the clusterRoleBindings for at the cluster level in a k8s endpoint.
// It returns a list of K8sClusterRoleBinding objects.
func (kcl *KubeClient) GetClusterRoleBindings() ([]models.K8sClusterRoleBinding, error) {
if kcl.IsKubeAdmin {
if kcl.GetIsKubeAdmin() {
return kcl.fetchClusterRoleBindings()
}

View File

@ -16,18 +16,23 @@ import (
// if the user is an admin, all configMaps in the current k8s environment(endpoint) are fetched using the fetchConfigMaps function.
// otherwise, namespaces the non-admin user has access to will be used to filter the configMaps based on the allowed namespaces.
func (kcl *KubeClient) GetConfigMaps(namespace string) ([]models.K8sConfigMap, error) {
if kcl.IsKubeAdmin {
if kcl.GetIsKubeAdmin() {
return kcl.fetchConfigMaps(namespace)
}
return kcl.fetchConfigMapsForNonAdmin(namespace)
}
// fetchConfigMapsForNonAdmin fetches the configMaps in the namespaces the user has access to.
// This function is called when the user is not an admin.
func (kcl *KubeClient) fetchConfigMapsForNonAdmin(namespace string) ([]models.K8sConfigMap, error) {
log.Debug().Msgf("Fetching configMaps for non-admin user: %v", kcl.NonAdminNamespaces)
nonAdminNamespaces := kcl.GetClientNonAdminNamespaces()
if len(kcl.NonAdminNamespaces) == 0 {
log.Debug().
Strs("non_admin_namespaces", nonAdminNamespaces).
Msg("fetching configMaps for non-admin user")
if len(nonAdminNamespaces) == 0 {
return nil, nil
}

View File

@ -15,7 +15,7 @@ import (
// If the user is a kube admin, it returns all cronjobs in the namespace
// Otherwise, it returns only the cronjobs in the non-admin namespaces
func (kcl *KubeClient) GetCronJobs(namespace string) ([]models.K8sCronJob, error) {
if kcl.IsKubeAdmin {
if kcl.GetIsKubeAdmin() {
return kcl.fetchCronJobs(namespace)
}

View File

@ -18,7 +18,7 @@ func (kcl *KubeClient) TestFetchCronJobs(t *testing.T) {
t.Run("admin client can fetch Cron Jobs from all namespaces", func(t *testing.T) {
kcl.cli = kfake.NewSimpleClientset()
kcl.instanceID = "test"
kcl.IsKubeAdmin = true
kcl.isKubeAdmin = true
cronJobs, err := kcl.GetCronJobs("")
if err != nil {
@ -31,8 +31,8 @@ func (kcl *KubeClient) TestFetchCronJobs(t *testing.T) {
t.Run("non-admin client can fetch Cron Jobs from the default namespace only", func(t *testing.T) {
kcl.cli = kfake.NewSimpleClientset()
kcl.instanceID = "test"
kcl.IsKubeAdmin = false
kcl.NonAdminNamespaces = []string{"default"}
kcl.isKubeAdmin = false
kcl.SetClientNonAdminNamespaces([]string{"default"})
cronJobs, err := kcl.GetCronJobs("")
if err != nil {

View File

@ -12,7 +12,7 @@ import (
// If the user is a kube admin, it returns all events in the namespace
// Otherwise, it returns only the events in the non-admin namespaces
func (kcl *KubeClient) GetEvents(namespace string, resourceId string) ([]models.K8sEvent, error) {
if kcl.IsKubeAdmin {
if kcl.GetIsKubeAdmin() {
return kcl.fetchAllEvents(namespace, resourceId)
}
@ -22,7 +22,7 @@ func (kcl *KubeClient) GetEvents(namespace string, resourceId string) ([]models.
// fetchEventsForNonAdmin returns all events in the given namespace and resource
// It returns only the events in the non-admin namespaces
func (kcl *KubeClient) fetchEventsForNonAdmin(namespace string, resourceId string) ([]models.K8sEvent, error) {
if len(kcl.NonAdminNamespaces) == 0 {
if len(kcl.GetClientNonAdminNamespaces()) == 0 {
return nil, nil
}

View File

@ -19,7 +19,7 @@ func TestGetEvents(t *testing.T) {
kcl := &KubeClient{
cli: kfake.NewSimpleClientset(),
instanceID: "instance",
IsKubeAdmin: true,
isKubeAdmin: true,
}
event := corev1.Event{
InvolvedObject: corev1.ObjectReference{UID: "resourceId"},
@ -49,8 +49,8 @@ func TestGetEvents(t *testing.T) {
kcl := &KubeClient{
cli: kfake.NewSimpleClientset(),
instanceID: "instance",
IsKubeAdmin: false,
NonAdminNamespaces: []string{"nonAdmin"},
isKubeAdmin: false,
nonAdminNamespaces: []string{"nonAdmin"},
}
event := corev1.Event{
InvolvedObject: corev1.ObjectReference{UID: "resourceId"},
@ -81,8 +81,8 @@ func TestGetEvents(t *testing.T) {
kcl := &KubeClient{
cli: kfake.NewSimpleClientset(),
instanceID: "instance",
IsKubeAdmin: false,
NonAdminNamespaces: []string{"nonAdmin"},
isKubeAdmin: false,
nonAdminNamespaces: []string{"nonAdmin"},
}
event := corev1.Event{
InvolvedObject: corev1.ObjectReference{UID: "resourceId"},

View File

@ -87,17 +87,22 @@ func (kcl *KubeClient) GetIngress(namespace, ingressName string) (models.K8sIngr
// GetIngresses gets all the ingresses for a given namespace in a k8s endpoint.
func (kcl *KubeClient) GetIngresses(namespace string) ([]models.K8sIngressInfo, error) {
if kcl.IsKubeAdmin {
if kcl.GetIsKubeAdmin() {
return kcl.fetchIngresses(namespace)
}
return kcl.fetchIngressesForNonAdmin(namespace)
}
// fetchIngressesForNonAdmin gets all the ingresses for non-admin users in a k8s endpoint.
func (kcl *KubeClient) fetchIngressesForNonAdmin(namespace string) ([]models.K8sIngressInfo, error) {
log.Debug().Msgf("Fetching ingresses for non-admin user: %v", kcl.NonAdminNamespaces)
nonAdminNamespaces := kcl.GetClientNonAdminNamespaces()
if len(kcl.NonAdminNamespaces) == 0 {
log.Debug().
Strs("non_admin_namespaces", nonAdminNamespaces).
Msg("fetching ingresses for non-admin user")
if len(nonAdminNamespaces) == 0 {
return nil, nil
}

View File

@ -0,0 +1,15 @@
package cli
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestGetIngresses(t *testing.T) {
kcl := &KubeClient{}
ingresses, err := kcl.GetIngresses("default")
require.NoError(t, err)
require.Empty(t, ingresses)
}

View File

@ -19,7 +19,7 @@ import (
// If the user is a kube admin, it returns all jobs in the namespace
// Otherwise, it returns only the jobs in the non-admin namespaces
func (kcl *KubeClient) GetJobs(namespace string, includeCronJobChildren bool) ([]models.K8sJob, error) {
if kcl.IsKubeAdmin {
if kcl.GetIsKubeAdmin() {
return kcl.fetchJobs(namespace, includeCronJobChildren)
}

View File

@ -21,7 +21,7 @@ func (kcl *KubeClient) TestFetchJobs(t *testing.T) {
t.Run("admin client can fetch jobs from all namespaces", func(t *testing.T) {
kcl.cli = kfake.NewSimpleClientset()
kcl.instanceID = "test"
kcl.IsKubeAdmin = true
kcl.isKubeAdmin = true
jobs, err := kcl.GetJobs("", false)
if err != nil {
@ -34,8 +34,8 @@ func (kcl *KubeClient) TestFetchJobs(t *testing.T) {
t.Run("non-admin client can fetch jobs from the default namespace only", func(t *testing.T) {
kcl.cli = kfake.NewSimpleClientset()
kcl.instanceID = "test"
kcl.IsKubeAdmin = false
kcl.NonAdminNamespaces = []string{"default"}
kcl.isKubeAdmin = false
kcl.SetClientNonAdminNamespaces([]string{"default"})
jobs, err := kcl.GetJobs("", false)
if err != nil {

View File

@ -40,9 +40,10 @@ func defaultSystemNamespaces() map[string]struct{} {
// if the user is an admin, all namespaces in the current k8s environment(endpoint) are fetched using the fetchNamespaces function.
// otherwise, namespaces the non-admin user has access to will be used to filter the namespaces based on the allowed namespaces.
func (kcl *KubeClient) GetNamespaces() (map[string]portainer.K8sNamespaceInfo, error) {
if kcl.IsKubeAdmin {
if kcl.GetIsKubeAdmin() {
return kcl.fetchNamespaces()
}
return kcl.fetchNamespacesForNonAdmin()
}
@ -52,7 +53,7 @@ func (kcl *KubeClient) fetchNamespacesForNonAdmin() (map[string]portainer.K8sNam
Str("context", "fetchNamespacesForNonAdmin").
Msg("Fetching namespaces for non-admin user")
if len(kcl.NonAdminNamespaces) == 0 {
if len(kcl.GetClientNonAdminNamespaces()) == 0 {
return nil, nil
}
@ -142,6 +143,7 @@ func (kcl *KubeClient) CreateNamespace(info models.K8sNamespaceDetails) (*corev1
Str("context", "CreateNamespace").
Str("Namespace", info.Name).
Msg("Failed to create the namespace")
return nil, err
}
@ -157,7 +159,7 @@ func (kcl *KubeClient) CreateNamespace(info models.K8sNamespaceDetails) (*corev1
return namespace, nil
}
// UpdateIngress updates an ingress in a given namespace in a k8s endpoint.
// UpdateNamespace updates a namespace in a k8s endpoint.
func (kcl *KubeClient) UpdateNamespace(info models.K8sNamespaceDetails) (*corev1.Namespace, error) {
portainerLabels := map[string]string{
namespaceNameLabel: stackutils.SanitizeLabel(info.Name),
@ -420,8 +422,10 @@ func (kcl *KubeClient) CombineNamespaceWithResourceQuota(namespace portainer.K8s
// buildNonAdminNamespacesMap builds a map of non-admin namespaces.
// the map is used to filter the namespaces based on the allowed namespaces.
func (kcl *KubeClient) buildNonAdminNamespacesMap() map[string]struct{} {
nonAdminNamespaceSet := make(map[string]struct{}, len(kcl.NonAdminNamespaces))
for _, namespace := range kcl.NonAdminNamespaces {
nonAdminNamespaces := kcl.GetClientNonAdminNamespaces()
nonAdminNamespaceSet := make(map[string]struct{}, len(nonAdminNamespaces))
for _, namespace := range nonAdminNamespaces {
if !isSystemDefaultNamespace(namespace) {
nonAdminNamespaceSet[namespace] = struct{}{}
}

View File

@ -176,6 +176,7 @@ func Test_ToggleSystemState(t *testing.T) {
expectedPolicies := map[string]portainer.K8sNamespaceAccessPolicy{
"ns2": {UserAccessPolicies: portainer.UserAccessPolicies{2: {RoleID: 0}}},
}
actualPolicies, err := kcl.GetNamespaceAccessPolicies()
assert.NoError(t, err, "failed to fetch policies")
assert.Equal(t, expectedPolicies, actualPolicies)

View File

@ -46,9 +46,9 @@ func (kcl *KubeClient) GetNodesLimits() (portainer.K8sNodesLimits, error) {
// GetMaxResourceLimits gets the maximum CPU and Memory limits(unused resources) of all nodes in the current k8s environment(endpoint) connection, minus the accumulated resourcequotas for all namespaces except the one we're editing (skipNamespace)
// if skipNamespace is set to "" then all namespaces are considered
func (client *KubeClient) GetMaxResourceLimits(skipNamespace string, overCommitEnabled bool, resourceOverCommitPercent int) (portainer.K8sNodeLimits, error) {
func (kcl *KubeClient) GetMaxResourceLimits(skipNamespace string, overCommitEnabled bool, resourceOverCommitPercent int) (portainer.K8sNodeLimits, error) {
limits := portainer.K8sNodeLimits{}
nodes, err := client.cli.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{})
nodes, err := kcl.cli.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{})
if err != nil {
return limits, err
}
@ -62,7 +62,7 @@ func (client *KubeClient) GetMaxResourceLimits(skipNamespace string, overCommitE
limits.Memory = memory / 1000000 // B to MB
if !overCommitEnabled {
namespaces, err := client.cli.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
namespaces, err := kcl.cli.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
if err != nil {
return limits, err
}
@ -77,7 +77,7 @@ func (client *KubeClient) GetMaxResourceLimits(skipNamespace string, overCommitE
}
// minus accumulated resourcequotas for all namespaces except the one we're editing
resourceQuota, err := client.cli.CoreV1().ResourceQuotas(namespace.Name).List(context.TODO(), metav1.ListOptions{})
resourceQuota, err := kcl.cli.CoreV1().ResourceQuotas(namespace.Name).List(context.TODO(), metav1.ListOptions{})
if err != nil {
log.Debug().Msgf("error getting resourcequota for namespace %s: %s", namespace.Name, err)
continue // skip it

View File

@ -59,6 +59,7 @@ func Test_waitForPodStatus(t *testing.T) {
ctx, cancelFunc := context.WithTimeout(context.TODO(), 0*time.Second)
defer cancelFunc()
err = k.waitForPodStatus(ctx, v1.PodRunning, podSpec)
if !errors.Is(err, context.DeadlineExceeded) {
t.Errorf("waitForPodStatus should throw deadline exceeded error; err=%s", err)

View File

@ -15,18 +15,23 @@ import (
// if the user is an admin, all resource quotas in all namespaces are fetched.
// otherwise, namespaces the non-admin user has access to will be used to filter the resource quotas.
func (kcl *KubeClient) GetResourceQuotas(namespace string) (*[]corev1.ResourceQuota, error) {
if kcl.IsKubeAdmin {
if kcl.GetIsKubeAdmin() {
return kcl.fetchResourceQuotas(namespace)
}
return kcl.fetchResourceQuotasForNonAdmin(namespace)
}
// fetchResourceQuotasForNonAdmin gets the resource quotas in the current k8s environment(endpoint) for a non-admin user.
// the role of the user must have read access to the resource quotas in the defined namespaces.
func (kcl *KubeClient) fetchResourceQuotasForNonAdmin(namespace string) (*[]corev1.ResourceQuota, error) {
log.Debug().Msgf("Fetching resource quotas for non-admin user: %v", kcl.NonAdminNamespaces)
nonAdminNamespaces := kcl.GetClientNonAdminNamespaces()
if len(kcl.NonAdminNamespaces) == 0 {
log.Debug().
Strs("non_admin_namespaces", nonAdminNamespaces).
Msg("fetching resource quotas for non-admin user")
if len(nonAdminNamespaces) == 0 {
return nil, nil
}

View File

@ -15,7 +15,7 @@ import (
// GetRoles gets all the roles for either at the cluster level or a given namespace in a k8s endpoint.
// It returns a list of K8sRole objects.
func (kcl *KubeClient) GetRoles(namespace string) ([]models.K8sRole, error) {
if kcl.IsKubeAdmin {
if kcl.GetIsKubeAdmin() {
return kcl.fetchRoles(namespace)
}

View File

@ -15,7 +15,7 @@ import (
// GetRoleBindings gets all the roleBindings for either at the cluster level or a given namespace in a k8s endpoint.
// It returns a list of K8sRoleBinding objects.
func (kcl *KubeClient) GetRoleBindings(namespace string) ([]models.K8sRoleBinding, error) {
if kcl.IsKubeAdmin {
if kcl.GetIsKubeAdmin() {
return kcl.fetchRoleBindings(namespace)
}

View File

@ -23,18 +23,23 @@ const (
// if the user is an admin, all secrets in the current k8s environment(endpoint) are fetched using the getSecrets function.
// otherwise, namespaces the non-admin user has access to will be used to filter the secrets based on the allowed namespaces.
func (kcl *KubeClient) GetSecrets(namespace string) ([]models.K8sSecret, error) {
if kcl.IsKubeAdmin {
if kcl.GetIsKubeAdmin() {
return kcl.getSecrets(namespace)
}
return kcl.getSecretsForNonAdmin(namespace)
}
// getSecretsForNonAdmin fetches the secrets in the namespaces the user has access to.
// This function is called when the user is not an admin.
func (kcl *KubeClient) getSecretsForNonAdmin(namespace string) ([]models.K8sSecret, error) {
log.Debug().Msgf("Fetching secrets for non-admin user: %v", kcl.NonAdminNamespaces)
nonAdminNamespaces := kcl.GetClientNonAdminNamespaces()
if len(kcl.NonAdminNamespaces) == 0 {
log.Debug().
Strs("non_admin_namespaces", nonAdminNamespaces).
Msg("fetching secrets for non-admin user")
if len(nonAdminNamespaces) == 0 {
return nil, nil
}

View File

@ -15,9 +15,10 @@ import (
// GetServices gets all the services for either at the cluster level or a given namespace in a k8s endpoint.
// It returns a list of K8sServiceInfo objects.
func (kcl *KubeClient) GetServices(namespace string) ([]models.K8sServiceInfo, error) {
if kcl.IsKubeAdmin {
if kcl.GetIsKubeAdmin() {
return kcl.fetchServices(namespace)
}
return kcl.fetchServicesForNonAdmin(namespace)
}
@ -25,9 +26,13 @@ func (kcl *KubeClient) GetServices(namespace string) ([]models.K8sServiceInfo, e
// the namespace will be coming from NonAdminNamespaces as non-admin users are restricted to certain namespaces.
// it returns a list of K8sServiceInfo objects.
func (kcl *KubeClient) fetchServicesForNonAdmin(namespace string) ([]models.K8sServiceInfo, error) {
log.Debug().Msgf("Fetching services for non-admin user: %v", kcl.NonAdminNamespaces)
nonAdminNamespaces := kcl.GetClientNonAdminNamespaces()
if len(kcl.NonAdminNamespaces) == 0 {
log.Debug().
Strs("non_admin_namespaces", nonAdminNamespaces).
Msg("fetching services for non-admin user")
if len(nonAdminNamespaces) == 0 {
return nil, nil
}

View File

@ -16,7 +16,7 @@ import (
// GetServiceAccounts gets all the service accounts for either at the cluster level or a given namespace in a k8s endpoint.
// It returns a list of K8sServiceAccount objects.
func (kcl *KubeClient) GetServiceAccounts(namespace string) ([]models.K8sServiceAccount, error) {
if kcl.IsKubeAdmin {
if kcl.GetIsKubeAdmin() {
return kcl.fetchServiceAccounts(namespace)
}

View File

@ -0,0 +1,15 @@
package cli
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestGetServices(t *testing.T) {
kcl := &KubeClient{}
services, err := kcl.GetServices("default")
require.NoError(t, err)
require.Empty(t, services)
}

View File

@ -18,9 +18,10 @@ import (
// 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 {
if kcl.GetIsKubeAdmin() {
return kcl.fetchVolumes(namespace)
}
return kcl.fetchVolumesForNonAdmin(namespace)
}
@ -48,9 +49,13 @@ func (kcl *KubeClient) GetVolume(namespace, volumeName string) (*models.K8sVolum
// 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)
nonAdminNamespaces := kcl.GetClientNonAdminNamespaces()
if len(kcl.NonAdminNamespaces) == 0 {
log.Debug().
Strs("non_admin_namespaces", nonAdminNamespaces).
Msg("fetching volumes for non-admin user")
if len(nonAdminNamespaces) == 0 {
return nil, nil
}

View File

@ -0,0 +1,15 @@
package cli
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestGetVolumes(t *testing.T) {
kcl := &KubeClient{}
volumes, err := kcl.GetVolumes("default")
require.NoError(t, err)
require.Empty(t, volumes)
}