mirror of
https://github.com/argoproj/argo-cd
synced 2026-04-21 08:57:17 +00:00
fix: create events with argocd namespace (#26667)
Signed-off-by: Patroklos Papapetrou <ppapapetrou76@gmail.com>
This commit is contained in:
parent
1f124986d9
commit
e01714590d
6 changed files with 249 additions and 41 deletions
|
|
@ -205,7 +205,7 @@ func NewApplicationController(
|
|||
statusRefreshJitter: appResyncJitter,
|
||||
refreshRequestedApps: make(map[string]CompareWith),
|
||||
refreshRequestedAppsMutex: &sync.Mutex{},
|
||||
auditLogger: argo.NewAuditLogger(kubeClientset, common.CommandApplicationController, enableK8sEvent),
|
||||
auditLogger: argo.NewAuditLogger(kubeClientset, namespace, common.CommandApplicationController, enableK8sEvent),
|
||||
settingsMgr: settingsMgr,
|
||||
selfHealTimeout: selfHealTimeout,
|
||||
selfHealBackoff: selfHealBackoff,
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ func NewServer(
|
|||
kubectl: kubectl,
|
||||
enf: enf,
|
||||
projectLock: projectLock,
|
||||
auditLogger: argo.NewAuditLogger(kubeclientset, "argocd-server", enableK8sEvent),
|
||||
auditLogger: argo.NewAuditLogger(kubeclientset, namespace, "argocd-server", enableK8sEvent),
|
||||
settingsMgr: settingsMgr,
|
||||
projInformer: projInformer,
|
||||
enabledNamespaces: enabledNamespaces,
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@ func NewServer(
|
|||
appsetLister: appsetLister,
|
||||
appSetBroadcaster: appSetBroadcaster,
|
||||
projectLock: projectLock,
|
||||
auditLogger: argo.NewAuditLogger(kubeclientset, "argocd-server", enableK8sEvent),
|
||||
auditLogger: argo.NewAuditLogger(kubeclientset, namespace, "argocd-server", enableK8sEvent),
|
||||
enabledNamespaces: enabledNamespaces,
|
||||
clusterInformer: clusterInformer,
|
||||
GitSubmoduleEnabled: gitSubmoduleEnabled,
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ type Server struct {
|
|||
func NewServer(ns string, kubeclientset kubernetes.Interface, appclientset appclientset.Interface, enf *rbac.Enforcer, projectLock sync.KeyLock, sessionMgr *session.SessionManager, policyEnf *rbacpolicy.RBACPolicyEnforcer,
|
||||
projInformer cache.SharedIndexInformer, settingsMgr *settings.SettingsManager, db db.ArgoDB, enableK8sEvent []string,
|
||||
) *Server {
|
||||
auditLogger := argo.NewAuditLogger(kubeclientset, "argocd-server", enableK8sEvent)
|
||||
auditLogger := argo.NewAuditLogger(kubeclientset, ns, "argocd-server", enableK8sEvent)
|
||||
return &Server{
|
||||
enf: enf, policyEnf: policyEnf, appclientset: appclientset, kubeclientset: kubeclientset, ns: ns, projectLock: projectLock, auditLogger: auditLogger, sessionMgr: sessionMgr,
|
||||
projInformer: projInformer, settingsMgr: settingsMgr, db: db,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
type AuditLogger struct {
|
||||
kIf kubernetes.Interface
|
||||
component string
|
||||
namespace string
|
||||
enableEventLog map[string]bool
|
||||
}
|
||||
|
||||
|
|
@ -63,6 +64,22 @@ func (l *AuditLogger) logEvent(objMeta ObjectRef, gvk schema.GroupVersionKind, i
|
|||
logCtx = logCtx.WithField("name", objMeta.Name)
|
||||
}
|
||||
t := metav1.Time{Time: time.Now()}
|
||||
|
||||
// Determine which namespace to create the event in
|
||||
eventNamespace := objMeta.Namespace
|
||||
// For resource events (non-Application, non-AppProject, non-ApplicationSet),
|
||||
// create events in the ArgoCD namespace to support multi-cluster
|
||||
if gvk.Kind != application.ApplicationKind &&
|
||||
gvk.Kind != application.AppProjectKind &&
|
||||
gvk.Kind != application.ApplicationSetKind {
|
||||
eventNamespace = l.namespace
|
||||
// Add the original namespace to annotations for reference
|
||||
if logFields == nil {
|
||||
logFields = make(map[string]string)
|
||||
}
|
||||
logFields["resource-namespace"] = objMeta.Namespace
|
||||
}
|
||||
|
||||
event := corev1.Event{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("%v.%x", objMeta.Name, t.UnixNano()),
|
||||
|
|
@ -75,7 +92,7 @@ func (l *AuditLogger) logEvent(objMeta ObjectRef, gvk schema.GroupVersionKind, i
|
|||
InvolvedObject: corev1.ObjectReference{
|
||||
Kind: gvk.Kind,
|
||||
Name: objMeta.Name,
|
||||
Namespace: objMeta.Namespace,
|
||||
Namespace: objMeta.Namespace, // Keep the original namespace in InvolvedObject
|
||||
ResourceVersion: objMeta.ResourceVersion,
|
||||
APIVersion: gvk.GroupVersion().String(),
|
||||
UID: objMeta.UID,
|
||||
|
|
@ -88,7 +105,7 @@ func (l *AuditLogger) logEvent(objMeta ObjectRef, gvk schema.GroupVersionKind, i
|
|||
Reason: info.Reason,
|
||||
}
|
||||
logCtx.Info(message)
|
||||
_, err := l.kIf.CoreV1().Events(objMeta.Namespace).Create(context.Background(), &event, metav1.CreateOptions{})
|
||||
_, err := l.kIf.CoreV1().Events(eventNamespace).Create(context.Background(), &event, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
logCtx.Errorf("Unable to create audit event: %v", err)
|
||||
return
|
||||
|
|
@ -178,10 +195,11 @@ func (l *AuditLogger) LogAppProjEvent(proj *v1alpha1.AppProject, info EventInfo,
|
|||
l.logEvent(objectMeta, v1alpha1.AppProjectSchemaGroupVersionKind, info, message, nil, nil)
|
||||
}
|
||||
|
||||
func NewAuditLogger(kIf kubernetes.Interface, component string, enableK8sEvent []string) *AuditLogger {
|
||||
func NewAuditLogger(kIf kubernetes.Interface, namespace, component string, enableK8sEvent []string) *AuditLogger {
|
||||
return &AuditLogger{
|
||||
kIf: kIf,
|
||||
component: component,
|
||||
namespace: namespace,
|
||||
enableEventLog: setK8sEventList(enableK8sEvent),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,15 @@ package argo
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
|
||||
|
|
@ -17,6 +20,8 @@ import (
|
|||
const (
|
||||
_somecomponent = "somecomponent"
|
||||
_test = "test"
|
||||
_argocdNs = "argocd"
|
||||
_targetNs = "target-namespace"
|
||||
)
|
||||
|
||||
var testEnableEventLog = []string{_somecomponent, _test}
|
||||
|
|
@ -63,18 +68,21 @@ func captureLogEntries(run func()) string {
|
|||
}
|
||||
|
||||
func TestNewAuditLogger(t *testing.T) {
|
||||
logger := NewAuditLogger(fake.NewClientset(), _somecomponent, testEnableEventLog)
|
||||
logger := NewAuditLogger(fake.NewClientset(), _argocdNs, _somecomponent, testEnableEventLog)
|
||||
assert.NotNil(t, logger)
|
||||
assert.Equal(t, _argocdNs, logger.namespace)
|
||||
assert.Equal(t, _somecomponent, logger.component)
|
||||
}
|
||||
|
||||
func TestLogAppProjEvent(t *testing.T) {
|
||||
logger := NewAuditLogger(fake.NewClientset(), _somecomponent, testEnableEventLog)
|
||||
fakeClient := fake.NewClientset()
|
||||
logger := NewAuditLogger(fakeClient, _argocdNs, _somecomponent, testEnableEventLog)
|
||||
assert.NotNil(t, logger)
|
||||
|
||||
proj := argoappv1.AppProject{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "default",
|
||||
Namespace: "argocd",
|
||||
Namespace: _argocdNs,
|
||||
ResourceVersion: "1",
|
||||
UID: "a-b-c-d-e",
|
||||
},
|
||||
|
|
@ -88,34 +96,33 @@ func TestLogAppProjEvent(t *testing.T) {
|
|||
Type: "info",
|
||||
}
|
||||
|
||||
output := captureLogEntries(func() {
|
||||
logger.LogAppProjEvent(&proj, ei, "This is a test message", "")
|
||||
captureLogEntries(func() {
|
||||
logger.LogAppProjEvent(&proj, ei, "This is a test message", "admin")
|
||||
})
|
||||
|
||||
assert.Contains(t, output, "level=info")
|
||||
assert.Contains(t, output, "project=default")
|
||||
assert.Contains(t, output, "reason=test")
|
||||
assert.Contains(t, output, "type=info")
|
||||
assert.Contains(t, output, "msg=\"This is a test message\"")
|
||||
// Verify event was created in the AppProject's namespace (argocd)
|
||||
events, err := fakeClient.CoreV1().Events(_argocdNs).List(context.Background(), metav1.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, events.Items, 1)
|
||||
|
||||
ei.Reason = "Unknown"
|
||||
|
||||
// If K8s Event Disable Log
|
||||
output = captureLogEntries(func() {
|
||||
logger.LogAppProjEvent(&proj, ei, "This is a test message", "")
|
||||
})
|
||||
|
||||
assert.Empty(t, output)
|
||||
event := events.Items[0]
|
||||
assert.Equal(t, "default", event.InvolvedObject.Name)
|
||||
assert.Equal(t, _argocdNs, event.InvolvedObject.Namespace)
|
||||
assert.Equal(t, _argocdNs, event.Namespace) // Event created in argocd namespace
|
||||
assert.Equal(t, "This is a test message", event.Message)
|
||||
assert.Equal(t, "test", event.Reason)
|
||||
assert.Equal(t, "info", event.Type)
|
||||
}
|
||||
|
||||
func TestLogAppEvent(t *testing.T) {
|
||||
logger := NewAuditLogger(fake.NewClientset(), _somecomponent, testEnableEventLog)
|
||||
fakeClient := fake.NewClientset()
|
||||
logger := NewAuditLogger(fakeClient, _argocdNs, _somecomponent, testEnableEventLog)
|
||||
assert.NotNil(t, logger)
|
||||
|
||||
app := argoappv1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "testapp",
|
||||
Namespace: "argocd",
|
||||
Namespace: _argocdNs,
|
||||
ResourceVersion: "1",
|
||||
UID: "a-b-c-d-e",
|
||||
},
|
||||
|
|
@ -131,23 +138,29 @@ func TestLogAppEvent(t *testing.T) {
|
|||
Reason: _test,
|
||||
Type: "info",
|
||||
}
|
||||
|
||||
output := captureLogEntries(func() {
|
||||
logger.LogAppEvent(&app, ei, "This is a test message", "", nil)
|
||||
captureLogEntries(func() {
|
||||
logger.LogAppEvent(&app, ei, "This is a test message", "admin", nil)
|
||||
})
|
||||
|
||||
assert.Contains(t, output, "level=info")
|
||||
assert.Contains(t, output, "application=testapp")
|
||||
assert.Contains(t, output, "dest-namespace=testns")
|
||||
assert.Contains(t, output, "dest-server=\"https://127.0.0.1:6443\"")
|
||||
assert.Contains(t, output, "reason=test")
|
||||
assert.Contains(t, output, "type=info")
|
||||
assert.Contains(t, output, "msg=\"This is a test message\"")
|
||||
// Verify event was created in the Application's namespace (argocd)
|
||||
events, err := fakeClient.CoreV1().Events(_argocdNs).List(context.Background(), metav1.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, events.Items, 1)
|
||||
|
||||
ei.Reason = "Unknown"
|
||||
event := events.Items[0]
|
||||
assert.Equal(t, "testapp", event.InvolvedObject.Name)
|
||||
assert.Equal(t, _argocdNs, event.InvolvedObject.Namespace)
|
||||
assert.Equal(t, _argocdNs, event.Namespace) // Event created in argocd namespace
|
||||
assert.Equal(t, "This is a test message", event.Message)
|
||||
assert.Equal(t, "test", event.Reason)
|
||||
assert.Equal(t, "info", event.Type)
|
||||
assert.Equal(t, "admin", event.Annotations["user"])
|
||||
assert.Equal(t, "https://127.0.0.1:6443", event.Annotations["dest-server"])
|
||||
assert.Equal(t, "testns", event.Annotations["dest-namespace"])
|
||||
|
||||
// If K8s Event Disable Log
|
||||
output = captureLogEntries(func() {
|
||||
ei.Reason = "Unknown"
|
||||
output := captureLogEntries(func() {
|
||||
logger.LogAppEvent(&app, ei, "This is a test message", "", nil)
|
||||
})
|
||||
|
||||
|
|
@ -155,7 +168,7 @@ func TestLogAppEvent(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLogResourceEvent(t *testing.T) {
|
||||
logger := NewAuditLogger(fake.NewClientset(), _somecomponent, testEnableEventLog)
|
||||
logger := NewAuditLogger(fake.NewClientset(), _argocdNs, _somecomponent, testEnableEventLog)
|
||||
assert.NotNil(t, logger)
|
||||
|
||||
res := argoappv1.ResourceNode{
|
||||
|
|
@ -164,7 +177,7 @@ func TestLogResourceEvent(t *testing.T) {
|
|||
Version: "v1alpha1",
|
||||
Kind: "SignatureKey",
|
||||
Name: "testapp",
|
||||
Namespace: "argocd",
|
||||
Namespace: _argocdNs,
|
||||
UID: "a-b-c-d-e",
|
||||
},
|
||||
}
|
||||
|
|
@ -193,3 +206,180 @@ func TestLogResourceEvent(t *testing.T) {
|
|||
|
||||
assert.Empty(t, output)
|
||||
}
|
||||
|
||||
func TestLogResourceEvent_MultiCluster_CreatesEventInArgocdNamespace(t *testing.T) {
|
||||
fakeClient := fake.NewClientset()
|
||||
logger := NewAuditLogger(fakeClient, _argocdNs, _somecomponent, []string{EventReasonResourceActionRan})
|
||||
|
||||
res := argoappv1.ResourceNode{
|
||||
ResourceRef: argoappv1.ResourceRef{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
Name: "my-deployment",
|
||||
Namespace: _targetNs, // Resource is in a different namespace/cluster
|
||||
UID: "deploy-uid-123",
|
||||
},
|
||||
}
|
||||
|
||||
ei := EventInfo{
|
||||
Reason: EventReasonResourceActionRan,
|
||||
Type: corev1.EventTypeNormal,
|
||||
}
|
||||
captureLogEntries(func() {
|
||||
logger.LogResourceEvent(&res, ei, "Resource action executed", "admin")
|
||||
})
|
||||
|
||||
events, err := fakeClient.CoreV1().Events(_targetNs).List(context.Background(), metav1.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, events.Items, "No event should be created in target namespace")
|
||||
|
||||
events, err = fakeClient.CoreV1().Events(_argocdNs).List(context.Background(), metav1.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, events.Items, 1, "Event should be created in ArgoCD namespace")
|
||||
|
||||
event := events.Items[0]
|
||||
assert.Equal(t, "my-deployment", event.InvolvedObject.Name)
|
||||
assert.Equal(t, _targetNs, event.InvolvedObject.Namespace, "InvolvedObject should preserve original namespace")
|
||||
assert.Equal(t, _argocdNs, event.Namespace, "Event itself should be in ArgoCD namespace")
|
||||
assert.Equal(t, "Resource action executed", event.Message)
|
||||
|
||||
assert.Equal(t, _targetNs, event.Annotations["resource-namespace"])
|
||||
}
|
||||
|
||||
func TestLogAppSetEvent_CreatesEventInAppSetNamespace(t *testing.T) {
|
||||
fakeClient := fake.NewClientset()
|
||||
logger := NewAuditLogger(fakeClient, _argocdNs, _somecomponent, testEnableEventLog)
|
||||
|
||||
appset := argoappv1.ApplicationSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-appset",
|
||||
Namespace: _argocdNs,
|
||||
ResourceVersion: "1",
|
||||
UID: "appset-uid-123",
|
||||
},
|
||||
}
|
||||
|
||||
ei := EventInfo{
|
||||
Reason: _test,
|
||||
Type: corev1.EventTypeNormal,
|
||||
}
|
||||
captureLogEntries(func() {
|
||||
logger.LogAppSetEvent(&appset, ei, "ApplicationSet event test", "admin")
|
||||
})
|
||||
|
||||
// Verify event was created in the ApplicationSet's namespace (argocd)
|
||||
events, err := fakeClient.CoreV1().Events(_argocdNs).List(context.Background(), metav1.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, events.Items, 1)
|
||||
|
||||
event := events.Items[0]
|
||||
assert.Equal(t, "my-appset", event.InvolvedObject.Name)
|
||||
assert.Equal(t, _argocdNs, event.InvolvedObject.Namespace)
|
||||
assert.Equal(t, _argocdNs, event.Namespace) // Event created in argocd namespace
|
||||
assert.Equal(t, "ApplicationSet event test", event.Message)
|
||||
}
|
||||
|
||||
func TestLogResourceEvent_DifferentKinds_AllInArgocdNamespace(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
resourceKind string
|
||||
resourceGroup string
|
||||
resourceNs string
|
||||
}{
|
||||
{
|
||||
name: "Pod in remote namespace",
|
||||
resourceKind: "Pod",
|
||||
resourceGroup: "",
|
||||
resourceNs: "production",
|
||||
},
|
||||
{
|
||||
name: "Service in remote namespace",
|
||||
resourceKind: "Service",
|
||||
resourceGroup: "",
|
||||
resourceNs: "staging",
|
||||
},
|
||||
{
|
||||
name: "Deployment in remote namespace",
|
||||
resourceKind: "Deployment",
|
||||
resourceGroup: "apps",
|
||||
resourceNs: "default",
|
||||
},
|
||||
{
|
||||
name: "ConfigMap in remote namespace",
|
||||
resourceKind: "ConfigMap",
|
||||
resourceGroup: "",
|
||||
resourceNs: "kube-system",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
fakeClient := fake.NewClientset()
|
||||
logger := NewAuditLogger(fakeClient, _argocdNs, _somecomponent, []string{EventReasonResourceActionRan})
|
||||
|
||||
res := argoappv1.ResourceNode{
|
||||
ResourceRef: argoappv1.ResourceRef{
|
||||
Group: tc.resourceGroup,
|
||||
Version: "v1",
|
||||
Kind: tc.resourceKind,
|
||||
Name: "test-resource",
|
||||
Namespace: tc.resourceNs,
|
||||
UID: "test-uid",
|
||||
},
|
||||
}
|
||||
|
||||
ei := EventInfo{
|
||||
Reason: EventReasonResourceActionRan,
|
||||
Type: corev1.EventTypeNormal,
|
||||
}
|
||||
captureLogEntries(func() {
|
||||
logger.LogResourceEvent(&res, ei, "Action ran", "admin")
|
||||
})
|
||||
|
||||
events, err := fakeClient.CoreV1().Events(_argocdNs).List(context.Background(), metav1.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, events.Items, 1)
|
||||
|
||||
event := events.Items[0]
|
||||
assert.Equal(t, _argocdNs, event.Namespace, "Event should be in ArgoCD namespace for %s", tc.resourceKind)
|
||||
assert.Equal(t, tc.resourceNs, event.InvolvedObject.Namespace, "InvolvedObject should preserve original namespace")
|
||||
assert.Equal(t, tc.resourceNs, event.Annotations["resource-namespace"])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogResourceEvent_EmptyNamespace(t *testing.T) {
|
||||
fakeClient := fake.NewClientset()
|
||||
logger := NewAuditLogger(fakeClient, _argocdNs, _somecomponent, []string{EventReasonResourceActionRan})
|
||||
|
||||
// Cluster-scoped resource (no namespace)
|
||||
res := argoappv1.ResourceNode{
|
||||
ResourceRef: argoappv1.ResourceRef{
|
||||
Group: "rbac.authorization.k8s.io",
|
||||
Version: "v1",
|
||||
Kind: "ClusterRole",
|
||||
Name: "cluster-admin",
|
||||
Namespace: "", // Cluster-scoped resource
|
||||
UID: "clusterrole-uid",
|
||||
},
|
||||
}
|
||||
|
||||
ei := EventInfo{
|
||||
Reason: EventReasonResourceActionRan,
|
||||
Type: corev1.EventTypeNormal,
|
||||
}
|
||||
captureLogEntries(func() {
|
||||
logger.LogResourceEvent(&res, ei, "Cluster role action", "admin")
|
||||
})
|
||||
|
||||
// Event should be created in ArgoCD namespace (not empty namespace)
|
||||
events, err := fakeClient.CoreV1().Events(_argocdNs).List(context.Background(), metav1.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, events.Items, 1)
|
||||
|
||||
event := events.Items[0]
|
||||
assert.Equal(t, _argocdNs, event.Namespace)
|
||||
assert.Empty(t, event.InvolvedObject.Namespace) // ClusterRole has no namespace
|
||||
assert.Empty(t, event.Annotations["resource-namespace"])
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue