diff --git a/common/logger.go b/common/logger.go index 89dc5a3..a43af41 100644 --- a/common/logger.go +++ b/common/logger.go @@ -80,9 +80,9 @@ func ParseEventLog(msg string, extraFields ...interface{}) (eventLog string) { // If there are extra fields, convert them to a JSON string and unmarshal into logEvent extra := fmt.Sprintf("%s", extraFields...) - if err = json.Unmarshal([]byte(extra), &logEvent); err != nil && extra != "" { + if err = json.Unmarshal([]byte(extra), &logEvent); err != nil && extra != "" && extra != "[]" { // If there is an error in parsing the extra fields, log the error - log.Printf("\n[ERROR] Failed to parse log extra data(%T): %s\tlog(%T):\n%v to Logz.io.\nRelated error:\n%v", extra, logEvent, extra, logEvent, err) + log.Printf("\n[ERROR] Failed to parse log extra data(%T): %s\tlog(%T):\n%v to Logz.io.\nRelated error:\n%v", extra, extra, logEvent, logEvent, err) } } @@ -102,9 +102,9 @@ func ParseEventLog(msg string, extraFields ...interface{}) (eventLog string) { } // Convert the parsed event log byte slice to a string - eventLog = fmt.Sprintf("%s", string(parsedEventLog)) + //eventLog = fmt.Sprintf("%s", string(parsedEventLog)) - return eventLog + return string(parsedEventLog) } func SendLog(msg string, extraFields ...interface{}) { @@ -124,9 +124,5 @@ func SendLog(msg string, extraFields ...interface{}) { wg.Add(1) wg.Wait() } - } else { - // If the logz.io logger is not configured, log a message and do not send the log - log.Printf("Logz.io logger isn't configured.\nLog won't be sent:\n%s", msg) } - } diff --git a/main_test.go b/main_test.go index b49b04c..397b472 100644 --- a/main_test.go +++ b/main_test.go @@ -20,13 +20,4 @@ func TestDeployEvents(t *testing.T) { } } - - //// - // - //common.DynamicClient = common.ConfigureClusterDynamicClient() - //if common.DynamicClient != nil { - // resources.AddEventHandlers() - //} - // - //common.LogzioLogger.Stop() } diff --git a/mockLogzioListener/listener.go b/mockLogzioListener/listener.go index f377d41..31b92b3 100644 --- a/mockLogzioListener/listener.go +++ b/mockLogzioListener/listener.go @@ -40,7 +40,6 @@ func (h *ListenerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Define the structure of the expected request body type RequestBody struct { - Message string `json:"message"` } // Read the request body diff --git a/resources/resourceInformer.go b/resources/resourceInformer.go index 60fc518..cb66a21 100644 --- a/resources/resourceInformer.go +++ b/resources/resourceInformer.go @@ -45,6 +45,7 @@ func createResourceInformer(resourceGVR schema.GroupVersionResource, clusterClie // AddInformerEventHandler adds a new event handler to a given resource informer. // It logs events when a resource is added, updated, or deleted. func AddInformerEventHandler(resourceInformer cache.SharedIndexInformer) (synced bool) { + var parsedEventLog []byte // Check if the resource informer is nil if resourceInformer == nil { log.Println("[ERROR] Resource informer is nil") @@ -61,7 +62,7 @@ func AddInformerEventHandler(resourceInformer cache.SharedIndexInformer) (synced mux.RLock() defer mux.RUnlock() if synced { - StructResourceLog(map[string]interface{}{ + _, parsedEventLog = StructResourceLog(map[string]interface{}{ "eventType": "ADDED", "newObject": obj, }) @@ -72,7 +73,7 @@ func AddInformerEventHandler(resourceInformer cache.SharedIndexInformer) (synced mux.RLock() defer mux.RUnlock() if synced { - StructResourceLog(map[string]interface{}{ + _, parsedEventLog = StructResourceLog(map[string]interface{}{ "eventType": "MODIFIED", "newObject": newObj, "oldObject": oldObj, @@ -84,7 +85,7 @@ func AddInformerEventHandler(resourceInformer cache.SharedIndexInformer) (synced mux.RLock() defer mux.RUnlock() if synced { - StructResourceLog(map[string]interface{}{ + _, parsedEventLog = StructResourceLog(map[string]interface{}{ "eventType": "DELETED", "newObject": obj, }) @@ -97,7 +98,7 @@ func AddInformerEventHandler(resourceInformer cache.SharedIndexInformer) (synced common.SendLog(fmt.Sprintf("[ERROR] Failed to add event handler for informer.\nERROR:\n%v", err)) return } - + common.SendLog(string(parsedEventLog)) // Create a new context that will get cancelled when an interrupt signal is received ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() @@ -131,6 +132,7 @@ func AddInformerEventHandler(resourceInformer cache.SharedIndexInformer) (synced return synced } + func AddEventHandlers() { // Creates informer for each cluster API and events handler for each informer resourceAPIList := map[string]string{ @@ -179,14 +181,32 @@ func AddEventHandlers() { } // EventObject transforms a raw object into a KubernetesEvent object. -// It takes a map representing the raw object and a boolean indicating whether the object is new or not. +// It takes a map representing the raw object and a boolean indicating whether the object is new or n +// ot. func EventObject(rawObj map[string]interface{}, isNew bool) (resourceObject common.KubernetesEvent) { + // Check if the raw object or its "newObject" and "oldObject" fields are nil - if rawObj == nil || rawObj["newObject"] == nil || rawObj["oldObject"] == nil { - log.Println("[ERROR] rawObj is nil or does not have required fields: newObject/oldObject.") + // Check if the raw object is nil + if rawObj == nil { + log.Println("[ERROR] rawObj is nil.") + // Return an empty KubernetesEvent object if the raw object is invalid + return resourceObject + } + + // Check if the newObject field of rawObj is nil, if it is required + if isNew && rawObj["newObject"] == nil { + log.Println("[ERROR] rawObj does not have required field: newObject.") + // Return an empty KubernetesEvent object if the raw object is invalid + return resourceObject + } + + // Check if the oldObject field of rawObj is nil, if it is required + if !isNew && rawObj["oldObject"] == nil { + log.Println("[ERROR] rawObj does not have required field: oldObject.") // Return an empty KubernetesEvent object if the raw object is invalid return resourceObject } + // Initialize an empty unstructured object rawUnstructuredObj := unstructured.Unstructured{} // Initialize a buffer to store the JSON-encoded raw object @@ -197,9 +217,7 @@ func EventObject(rawObj map[string]interface{}, isNew bool) (resourceObject comm // Unmarshal the JSON-encoded raw object into a KubernetesEvent object err := json.Unmarshal(buffer.Bytes(), &resourceObject) if err != nil { - // Log the error if unmarshalling fails - log.Printf("Failed to unmarshal resource object: %v", err) - // handle error as necessary + log.Printf("Failed to unmarshal resource object:\n%v\nError:\n%v", rawObj, err) } else { // If unmarshalling is successful, determine whether to set the unstructured object's content based on the "isNew" flag if isNew { @@ -214,45 +232,72 @@ func EventObject(rawObj map[string]interface{}, isNew bool) (resourceObject comm return resourceObject } -func StructResourceLog(event map[string]interface{}) (isStructured bool) { - var msg string +// StructResourceLog receives an event and logs it in a structured format. +func StructResourceLog(event map[string]interface{}) (isStructured bool, marshaledEvent []byte) { + + // Check if event is nil if event == nil { log.Println("[ERROR] Event is nil") - return false + return false, nil } + + // Assert that event["eventType"] is a string eventType, ok := event["eventType"].(string) if !ok { log.Println("[ERROR] eventType is not a string") - return false + return false, nil } + + // Initialize an empty LogEvent logEvent := &common.LogEvent{} + + // Marshal the event to a string eventStr, _ := json.Marshal(event) - json.Unmarshal(eventStr, logEvent) + + // Unmarshal the string back to a logEvent + err := json.Unmarshal(eventStr, logEvent) + if err != nil { + + return false, nil + } + + // Get the new event object newEventObj := EventObject(logEvent.NewObject, true) + + // Get the resource details from the new event object resourceKind := newEventObj.Kind resourceName := newEventObj.KubernetesMetadata.Name resourceNamespace := newEventObj.KubernetesMetadata.Namespace newResourceVersion := newEventObj.ResourceVersion + + var msg string + // If event is a modification event, get the old event object and parse the event message accordingly if eventType == "MODIFIED" { oldEventObj := EventObject(logEvent.OldObject, false) oldResourceName := oldEventObj.KubernetesMetadata.Name oldResourceNamespace := oldEventObj.KubernetesMetadata.Namespace oldResourceVersion := oldEventObj.KubernetesMetadata.ResourceVersion msg = common.ParseEventMessage(eventType, oldResourceName, resourceKind, oldResourceNamespace, newResourceVersion, oldResourceVersion) - } else { + // If event is not a modification event, parse the event message with only the new event object msg = common.ParseEventMessage(eventType, resourceName, resourceKind, resourceNamespace, newResourceVersion) } + + // Get the related cluster services for the resource event["relatedClusterServices"] = GetClusterRelatedResources(resourceKind, resourceName, resourceNamespace) + event["message"] = msg - marshaledEvent, err := json.Marshal(event) + // Marshal the event to a string + marshaledEvent, err = json.Marshal(event) if err != nil { log.Printf("[ERROR] Failed to marshel resource event logs.\nERROR:\n%v", err) } - common.SendLog(msg, marshaledEvent) + + // Mark the goroutine as done defer wg.Done() - isStructured = true - return isStructured + // Return true indicating the log is structured + isStructured = true + return isStructured, marshaledEvent } diff --git a/resources/resourceInformer_test.go b/resources/resourceInformer_test.go index b6b2420..d593f5d 100644 --- a/resources/resourceInformer_test.go +++ b/resources/resourceInformer_test.go @@ -1,7 +1,8 @@ package resources import ( - "github.com/stretchr/testify/mock" + "encoding/json" + "fmt" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -9,15 +10,15 @@ import ( fakeDynamic "k8s.io/client-go/dynamic/fake" "k8s.io/client-go/tools/cache" "log" + "sigs.k8s.io/yaml" "testing" ) -func createFakeResourceInformer(gvr schema.GroupVersionResource) cache.SharedIndexInformer { - fakeDynamicClient := fakeDynamic.NewSimpleDynamicClient(runtime.NewScheme()) +func createFakeResourceInformer(gvr schema.GroupVersionResource, fakeDynamicClient *fakeDynamic.FakeDynamicClient) (fakeResourceInformer cache.SharedIndexInformer) { factory := dynamicinformer.NewFilteredDynamicSharedInformerFactory(fakeDynamicClient, 0, corev1.NamespaceAll, nil) - fakeResourceInformer := factory.ForResource(gvr).Informer() + fakeResourceInformer = factory.ForResource(gvr).Informer() if fakeResourceInformer == nil { - log.Printf("[ERROR] Resource Informer was not created") + log.Fatalf("[ERROR] Resource Informer was not created") // program will exit if this happens } else { log.Printf("Resource Informer created successfully") } @@ -25,60 +26,70 @@ func createFakeResourceInformer(gvr schema.GroupVersionResource) cache.SharedInd } func TestCreateResourceInformer(t *testing.T) { + fakeDynamicClient := fakeDynamic.NewSimpleDynamicClient(runtime.NewScheme()) resourceGVR := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"} - informer := createFakeResourceInformer(resourceGVR) + informer := createFakeResourceInformer(resourceGVR, fakeDynamicClient) if informer == nil { - t.Errorf("Failed to create resource informer") + t.Fatalf("Failed to create resource informer") // test will fail if this happens } } -// Define an interface that includes the function you want to mock -type InformerCreator interface { - createFakeResourceInformer(gvr schema.GroupVersionResource, dynamicClient *fakeDynamic.FakeDynamicClient) cache.SharedIndexInformer -} +func TestEventObject(t *testing.T) { + testDeployment := GetTestDeployment() + // Marshal the struct to JSON + jsonData, err := yaml.Marshal(testDeployment) + if err != nil { + fmt.Printf("error: %s", err) + return + } + + // Unmarshal the JSON to a map + var deploymentMap map[string]interface{} + err = yaml.Unmarshal(jsonData, &deploymentMap) + if err != nil { + fmt.Printf("error: %s", err) + return + } + deploymentMap["eventType"] = "ADDED" + deploymentMap["kind"] = "Deployment" + deploymentMap["newObject"] = &deploymentMap + eventObject := EventObject(deploymentMap, true) + + if eventObject.Kind != "Deployment" { + t.Errorf("Failed to create event object, expected kind Deployment, got %s", eventObject.Kind) + } + + if eventObject.KubernetesMetadata.Name != "test-deployment" { + t.Errorf("Failed to create event object, expected name test-deployment, got %s", eventObject.KubernetesMetadata.Name) + } -// Have your mock type implement the interface -type MockInformerCreator struct { - mock.Mock + if eventObject.KubernetesMetadata.Namespace != "default" { + t.Errorf("Failed to create event object, expected namespace default, got %s", eventObject.KubernetesMetadata.Namespace) + } } -// Replace createResourceInformer with an instance of the interface +func TestStructResourceLog(t *testing.T) { + var deploymentMap map[string]interface{} + testDeployment := GetTestDeployment() + jsonDeployment, err := json.Marshal(testDeployment) + if err != nil { + t.Errorf("Failed to marshal test deployment.\nError:\n %v", err) + } -func TestAddEventHandlers(t *testing.T) { - // Create a new mock informer creator - fakeDynamicClient := fakeDynamic.NewSimpleDynamicClient(runtime.NewScheme()) - mockInformerCreator := new(MockInformerCreator) - mockInformer := createFakeResourceInformer(schema.GroupVersionResource{Group: "", Version: "v1", Resource: "deployments"}) - // Define what should be returned when the mock is called - mockInformerCreator.On("CreateFakeResourceInformer", mock.Anything, mock.Anything).Return(mockInformer) + err = json.Unmarshal(jsonDeployment, &deploymentMap) + if err != nil { + t.Errorf("Failed to unmarshal test deployment.\nError:\n %v", err) + } + deploymentEventMap := map[string]interface{}{ - // Run the function that you're testing - AddEventHandlers() + "eventType": "ADDED", + "kind": "Deployment", + "newObject": deploymentMap, + } + isStructured, _ := StructResourceLog(deploymentEventMap) - // Check that the mock was called with the expected parameters - mockInformerCreator.AssertCalled(t, "CreateFakeResourceInformer", schema.GroupVersionResource{Group: "", Version: "v1", Resource: "configmaps"}, fakeDynamicClient) - mockInformerCreator.AssertCalled(t, "CreateFakeResourceInformer", schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}, fakeDynamicClient) - // ... add more assertions here ... -} -func (m *MockInformerCreator) CreateFakeResourceInformer(gvr schema.GroupVersionResource, dynamicClient *fakeDynamic.FakeDynamicClient) cache.SharedIndexInformer { - args := m.Called(gvr, dynamicClient) - return args.Get(0).(cache.SharedIndexInformer) + if !isStructured { + t.Errorf("Failed to structure resource log") + } } - -//func TestAddInformerEventHandler(t *testing.T) { -// resourceGVR := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"} -// mockDynamicClient := fakeDynamic.NewSimpleDynamicClient(runtime.NewScheme()) -// -// informer := CreateFakeResourceInformer(resourceGVR, mockDynamicClient) -// -// if informer == nil { -// t.Errorf("Failed to create resource informer") -// } -// -// synced := AddInformerEventHandler(informer) -// -// if !synced { -// t.Errorf("Failed to add event handler for informer") -// } -//} diff --git a/resources/resourceServices.go b/resources/resourceServices.go index 5c3a136..f44ca0f 100644 --- a/resources/resourceServices.go +++ b/resources/resourceServices.go @@ -6,8 +6,6 @@ import ( "reflect" ) -// Similarly, define methods for other workload types... - func GetSecretRelatedWorkloads(secretName string, workloads []Workload) (relatedWorkloads []string) { // Create a map of workload names to workloads. workloadsMap := map[string]Workload{} @@ -149,7 +147,7 @@ func ServiceAccountRelatedWorkloads(serviceAccountName string) (relatedWorkloads } // ClusterRoleBinding Kind - +// ClusterRoleBindingRelatedWorkloads gets all the workloads in the cluster related to a specific ClusterRoleBinding. func ClusterRoleBindingRelatedWorkloads(clusterRoleBindingName string) (relatedWorkloads common.RelatedClusterServices) { // clusterRoleBinding := GetClusterRoleBinding(clusterRoleBindingName) @@ -168,8 +166,8 @@ func ClusterRoleBindingRelatedWorkloads(clusterRoleBindingName string) (relatedW return relatedWorkloads } -// ClusterRole Kind - +// ClusterRoleRelatedWorkloads ClusterRole Kind +// ClusterRoleRelatedWorkloads gets all the workloads in the cluster related to a specific ClusterRole. func ClusterRoleRelatedWorkloads(clusterRoleName string) (relatedWorkloads common.RelatedClusterServices) { clusterRoleBindings := GetClusterRoleBindings()