mirror of
https://github.com/portainer/portainer.git
synced 2025-07-19 05:19:39 +02:00
fix(kubernetes): events api to call the backend [R8S-243] (#563)
This commit is contained in:
parent
32ef208278
commit
07dfd981a2
26 changed files with 750 additions and 217 deletions
|
@ -143,3 +143,23 @@ func (kcl *KubeClient) GetNonAdminNamespaces(userID int, teamIDs []int, isRestri
|
|||
|
||||
return nonAdminNamespaces, nil
|
||||
}
|
||||
|
||||
// GetIsKubeAdmin retrieves true if client is admin
|
||||
func (client *KubeClient) GetIsKubeAdmin() bool {
|
||||
return client.IsKubeAdmin
|
||||
}
|
||||
|
||||
// UpdateIsKubeAdmin sets whether the kube client is admin
|
||||
func (client *KubeClient) SetIsKubeAdmin(isKubeAdmin bool) {
|
||||
client.IsKubeAdmin = isKubeAdmin
|
||||
}
|
||||
|
||||
// GetClientNonAdminNamespaces retrieves non-admin namespaces
|
||||
func (client *KubeClient) GetClientNonAdminNamespaces() []string {
|
||||
return client.NonAdminNamespaces
|
||||
}
|
||||
|
||||
// UpdateClientNonAdminNamespaces sets the client non admin namespace list
|
||||
func (client *KubeClient) SetClientNonAdminNamespaces(nonAdminNamespaces []string) {
|
||||
client.NonAdminNamespaces = nonAdminNamespaces
|
||||
}
|
||||
|
|
|
@ -82,6 +82,10 @@ func (factory *ClientFactory) RemoveKubeClient(endpointID portainer.EndpointID)
|
|||
factory.endpointProxyClients.Delete(strconv.Itoa(int(endpointID)))
|
||||
}
|
||||
|
||||
func (factory *ClientFactory) GetAddrHTTPS() string {
|
||||
return factory.AddrHTTPS
|
||||
}
|
||||
|
||||
// GetPrivilegedKubeClient checks if an existing client is already registered for the environment(endpoint) and returns it if one is found.
|
||||
// If no client is registered, it will create a new client, register it, and returns it.
|
||||
func (factory *ClientFactory) GetPrivilegedKubeClient(endpoint *portainer.Endpoint) (*KubeClient, error) {
|
||||
|
|
93
api/kubernetes/cli/event.go
Normal file
93
api/kubernetes/cli/event.go
Normal file
|
@ -0,0 +1,93 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
models "github.com/portainer/portainer/api/http/models/kubernetes"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// GetEvents gets all the Events for a given namespace and resource
|
||||
// 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 {
|
||||
return kcl.fetchAllEvents(namespace, resourceId)
|
||||
}
|
||||
|
||||
return kcl.fetchEventsForNonAdmin(namespace, resourceId)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
events, err := kcl.fetchAllEvents(namespace, resourceId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonAdminNamespaceSet := kcl.buildNonAdminNamespacesMap()
|
||||
results := make([]models.K8sEvent, 0)
|
||||
for _, event := range events {
|
||||
if _, ok := nonAdminNamespaceSet[event.Namespace]; ok {
|
||||
results = append(results, event)
|
||||
}
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// fetchEventsForNonAdmin returns all events in the given namespace and resource
|
||||
// It returns all events in the namespace and resource
|
||||
func (kcl *KubeClient) fetchAllEvents(namespace string, resourceId string) ([]models.K8sEvent, error) {
|
||||
options := metav1.ListOptions{}
|
||||
if resourceId != "" {
|
||||
options.FieldSelector = "involvedObject.uid=" + resourceId
|
||||
}
|
||||
|
||||
list, err := kcl.cli.CoreV1().Events(namespace).List(context.TODO(), options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results := make([]models.K8sEvent, 0)
|
||||
for _, event := range list.Items {
|
||||
results = append(results, parseEvent(&event))
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func parseEvent(event *corev1.Event) models.K8sEvent {
|
||||
result := models.K8sEvent{
|
||||
Type: event.Type,
|
||||
Name: event.Name,
|
||||
Message: event.Message,
|
||||
Reason: event.Reason,
|
||||
Namespace: event.Namespace,
|
||||
EventTime: event.EventTime.UTC(),
|
||||
Kind: event.Kind,
|
||||
Count: event.Count,
|
||||
UID: string(event.ObjectMeta.GetUID()),
|
||||
InvolvedObjectKind: models.K8sEventInvolvedObject{
|
||||
Kind: event.InvolvedObject.Kind,
|
||||
UID: string(event.InvolvedObject.UID),
|
||||
Name: event.InvolvedObject.Name,
|
||||
Namespace: event.InvolvedObject.Namespace,
|
||||
},
|
||||
}
|
||||
|
||||
if !event.LastTimestamp.Time.IsZero() {
|
||||
result.LastTimestamp = &event.LastTimestamp.Time
|
||||
}
|
||||
if !event.FirstTimestamp.Time.IsZero() {
|
||||
result.FirstTimestamp = &event.FirstTimestamp.Time
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
108
api/kubernetes/cli/event_test.go
Normal file
108
api/kubernetes/cli/event_test.go
Normal file
|
@ -0,0 +1,108 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
kfake "k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
// TestGetEvents tests the GetEvents method
|
||||
// It creates a fake Kubernetes client and passes it to the GetEvents method
|
||||
// It then logs the fetched events and validated the data returned
|
||||
func TestGetEvents(t *testing.T) {
|
||||
t.Run("can get events for resource id when admin", func(t *testing.T) {
|
||||
kcl := &KubeClient{
|
||||
cli: kfake.NewSimpleClientset(),
|
||||
instanceID: "instance",
|
||||
IsKubeAdmin: true,
|
||||
}
|
||||
event := corev1.Event{
|
||||
InvolvedObject: corev1.ObjectReference{UID: "resourceId"},
|
||||
Action: "something",
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "myEvent"},
|
||||
EventTime: metav1.NowMicro(),
|
||||
Type: "warning",
|
||||
Message: "This event has a very serious warning",
|
||||
}
|
||||
_, err := kcl.cli.CoreV1().Events("default").Create(context.TODO(), &event, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create Event: %v", err)
|
||||
}
|
||||
|
||||
events, err := kcl.GetEvents("default", "resourceId")
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to fetch Cron Jobs: %v", err)
|
||||
}
|
||||
t.Logf("Fetched Events: %v", events)
|
||||
require.Equal(t, 1, len(events), "Expected to return 1 event")
|
||||
assert.Equal(t, event.Message, events[0].Message, "Expected Message to be equal to event message created")
|
||||
assert.Equal(t, event.Type, events[0].Type, "Expected Type to be equal to event type created")
|
||||
assert.Equal(t, event.EventTime.UTC(), events[0].EventTime, "Expected EventTime to be saved as a string from event time created")
|
||||
})
|
||||
t.Run("can get kubernetes events for non admin namespace when non admin", func(t *testing.T) {
|
||||
kcl := &KubeClient{
|
||||
cli: kfake.NewSimpleClientset(),
|
||||
instanceID: "instance",
|
||||
IsKubeAdmin: false,
|
||||
NonAdminNamespaces: []string{"nonAdmin"},
|
||||
}
|
||||
event := corev1.Event{
|
||||
InvolvedObject: corev1.ObjectReference{UID: "resourceId"},
|
||||
Action: "something",
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: "nonAdmin", Name: "myEvent"},
|
||||
EventTime: metav1.NowMicro(),
|
||||
Type: "warning",
|
||||
Message: "This event has a very serious warning",
|
||||
}
|
||||
_, err := kcl.cli.CoreV1().Events("nonAdmin").Create(context.TODO(), &event, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create Event: %v", err)
|
||||
}
|
||||
|
||||
events, err := kcl.GetEvents("nonAdmin", "resourceId")
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to fetch Cron Jobs: %v", err)
|
||||
}
|
||||
t.Logf("Fetched Events: %v", events)
|
||||
require.Equal(t, 1, len(events), "Expected to return 1 event")
|
||||
assert.Equal(t, event.Message, events[0].Message, "Expected Message to be equal to event message created")
|
||||
assert.Equal(t, event.Type, events[0].Type, "Expected Type to be equal to event type created")
|
||||
assert.Equal(t, event.EventTime.UTC(), events[0].EventTime, "Expected EventTime to be saved as a string from event time created")
|
||||
})
|
||||
|
||||
t.Run("cannot get kubernetes events for admin namespace when non admin", func(t *testing.T) {
|
||||
kcl := &KubeClient{
|
||||
cli: kfake.NewSimpleClientset(),
|
||||
instanceID: "instance",
|
||||
IsKubeAdmin: false,
|
||||
NonAdminNamespaces: []string{"nonAdmin"},
|
||||
}
|
||||
event := corev1.Event{
|
||||
InvolvedObject: corev1.ObjectReference{UID: "resourceId"},
|
||||
Action: "something",
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: "admin", Name: "myEvent"},
|
||||
EventTime: metav1.NowMicro(),
|
||||
Type: "warning",
|
||||
Message: "This event has a very serious warning",
|
||||
}
|
||||
_, err := kcl.cli.CoreV1().Events("admin").Create(context.TODO(), &event, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create Event: %v", err)
|
||||
}
|
||||
|
||||
events, err := kcl.GetEvents("admin", "resourceId")
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to fetch Cron Jobs: %v", err)
|
||||
}
|
||||
t.Logf("Fetched Events: %v", events)
|
||||
assert.Equal(t, 0, len(events), "Expected to return 0 events")
|
||||
})
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue