mirror of https://github.com/portainer/portainer
fix(kubernetes/cli): fix a data-race BE-12259 (#1227)
parent
1e47df6611
commit
68b9fef3f0
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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"},
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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{}{}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
Loading…
Reference in New Issue