Skip to content

Commit

Permalink
fix(promotions): log event for Argo CD Application (#1848)
Browse files Browse the repository at this point in the history
Signed-off-by: Hidde Beydals <[email protected]>
  • Loading branch information
hiddeco authored May 1, 2024
1 parent 384c85a commit a6ced29
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 6 deletions.
5 changes: 5 additions & 0 deletions internal/controller/argocd/api/v1alpha1/audit_events.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package v1alpha1

const (
EventReasonOperationStarted = "OperationStarted"
)
74 changes: 68 additions & 6 deletions internal/controller/promotion/argocd.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import (
"path"
"sort"
"strings"
"time"

"github.com/gobwas/glob"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"

Expand Down Expand Up @@ -57,6 +59,7 @@ type argoCDMechanism struct {
patch client.Patch,
opts ...client.PatchOption,
) error
logAppEventFn func(ctx context.Context, app *argocd.Application, user, reason, message string)
}

// newArgoCDMechanism returns an implementation of the Mechanism interface that
Expand All @@ -71,6 +74,7 @@ func newArgoCDMechanism(argocdClient client.Client) Mechanism {
a.applyArgoCDSourceUpdateFn = applyArgoCDSourceUpdate
if argocdClient != nil {
a.argoCDAppPatchFn = argocdClient.Patch
a.logAppEventFn = a.logAppEvent
}
return a
}
Expand Down Expand Up @@ -304,8 +308,7 @@ func (a *argoCDMechanism) doSingleUpdate(
app.Spec.Sources[i] = source
}
}
app.ObjectMeta.Annotations[argocd.AnnotationKeyRefresh] =
string(argocd.RefreshTypeHard)
app.ObjectMeta.Annotations[argocd.AnnotationKeyRefresh] = string(argocd.RefreshTypeHard)
app.Operation = &argocd.Operation{
InitiatedBy: argocd.OperationInitiator{
Username: applicationOperationInitiator,
Expand Down Expand Up @@ -333,8 +336,7 @@ func (a *argoCDMechanism) doSingleUpdate(
app.Operation.Sync.Revisions = []string{app.Spec.Source.TargetRevision}
}
for _, source := range app.Spec.Sources {
app.Operation.Sync.Revisions =
append(app.Operation.Sync.Revisions, source.TargetRevision)
app.Operation.Sync.Revisions = append(app.Operation.Sync.Revisions, source.TargetRevision)
}
if err = a.argoCDAppPatchFn(
ctx,
Expand All @@ -343,11 +345,71 @@ func (a *argoCDMechanism) doSingleUpdate(
); err != nil {
return fmt.Errorf("error patching Argo CD Application %q: %w", app.Name, err)
}
logging.LoggerFromContext(ctx).WithField("app", app.Name).
Debug("patched Argo CD Application")
logging.LoggerFromContext(ctx).WithField("app", app.Name).Debug("patched Argo CD Application")

// NB: This attempts to mimic the behavior of the Argo CD API server,
// which logs an event when a sync is initiated. However, we do not
// have access to the same enriched event data the Argo CD API server
// has, so we are limited to logging an event with the best
// information we have at hand.
// xref: https://github.com/argoproj/argo-cd/blob/44894e9e438bca5adccf58d2f904adc63365805c/server/application/application.go#L1887-L1895
// nolint:lll
//
// TODO(hidde): It is not clear what we should do if we have a list of
// sources.
message := "initiated sync"
if app.Spec.Source != nil {
message += " to " + app.Spec.Source.TargetRevision
}
a.logAppEventFn(ctx, app, "kargo-controller", argocd.EventReasonOperationStarted, message)

return nil
}

func (a *argoCDMechanism) logAppEvent(ctx context.Context, app *argocd.Application, user, reason, message string) {
logger := logging.LoggerFromContext(ctx).WithField("app", app.Name)

// xref: https://github.com/argoproj/argo-cd/blob/44894e9e438bca5adccf58d2f904adc63365805c/server/application/application.go#L2145-L2147
// nolint:lll
if user == "" {
user = "Unknown user"
}

t := metav1.Time{Time: time.Now()}
event := corev1.Event{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%v.%x", app.Name, t.UnixNano()),
// xref: https://github.com/argoproj/argo-cd/blob/44894e9e438bca5adccf58d2f904adc63365805c/util/argo/audit_logger.go#L118-L124
// nolint:lll
Annotations: map[string]string{
"user": user,
},
},
Source: corev1.EventSource{
Component: user,
},
InvolvedObject: corev1.ObjectReference{
APIVersion: argocd.GroupVersion.String(),
Kind: app.TypeMeta.Kind,
Namespace: app.ObjectMeta.Namespace,
Name: app.ObjectMeta.Name,
UID: app.ObjectMeta.UID,
ResourceVersion: app.ObjectMeta.ResourceVersion,
},
FirstTimestamp: t,
LastTimestamp: t,
Count: 1,
// xref: https://github.com/argoproj/argo-cd/blob/44894e9e438bca5adccf58d2f904adc63365805c/server/application/application.go#L2148
// nolint:lll
Message: user + " " + message,
Type: corev1.EventTypeNormal,
Reason: reason,
}
if err := a.argocdClient.Create(context.Background(), &event); err != nil {
logger.Errorf("unable to create %q event for Argo CD Application: %v", reason, err)
}
}

func getApplicationFn(
argocdClient client.Client,
) func(
Expand Down
89 changes: 89 additions & 0 deletions internal/controller/promotion/argocd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand Down Expand Up @@ -873,6 +874,7 @@ func TestArgoCDDoSingleUpdate(t *testing.T) {
) error {
return nil
},
logAppEventFn: func(context.Context, *argocd.Application, string, string, string) {},
},
stageMeta: metav1.ObjectMeta{
Name: "fake-name",
Expand All @@ -898,6 +900,93 @@ func TestArgoCDDoSingleUpdate(t *testing.T) {
}
}

func TestLogAppEvent(t *testing.T) {
testCases := []struct {
name string
app *argocd.Application
user string
eventReason string
eventMessage string
assertions func(*testing.T, client.Client, *argocd.Application)
}{
{
name: "success",
app: &argocd.Application{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
},
ObjectMeta: metav1.ObjectMeta{
Name: "fake-name",
Namespace: "fake-namespace",
UID: "fake-uid",
ResourceVersion: "fake-resource-version",
},
},
user: "fake-user",
eventReason: "fake-reason",
eventMessage: "fake-message",
assertions: func(t *testing.T, c client.Client, app *argocd.Application) {
events := &corev1.EventList{}
require.NoError(t, c.List(context.TODO(), events))
require.Len(t, events.Items, 1)

event := events.Items[0]
require.Equal(t, corev1.ObjectReference{
APIVersion: argocd.GroupVersion.String(),
Kind: app.TypeMeta.Kind,
Name: app.ObjectMeta.Name,
Namespace: app.ObjectMeta.Namespace,
UID: app.ObjectMeta.UID,
ResourceVersion: app.ObjectMeta.ResourceVersion,
}, event.InvolvedObject)
require.NotNil(t, event.FirstTimestamp)
require.NotNil(t, event.LastTimestamp)
require.Equal(t, 1, int(event.Count))
require.Equal(t, corev1.EventTypeNormal, event.Type)
require.Equal(t, "fake-reason", event.Reason)
require.Equal(t, "fake-user fake-message", event.Message)
},
},
{
name: "unknown user",
app: &argocd.Application{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
},
ObjectMeta: metav1.ObjectMeta{
Name: "fake-name",
Namespace: "fake-namespace",
UID: "fake-uid",
ResourceVersion: "fake-resource-version",
},
},
eventReason: "fake-reason",
eventMessage: "fake-message",
assertions: func(t *testing.T, c client.Client, _ *argocd.Application) {
events := &corev1.EventList{}
require.NoError(t, c.List(context.TODO(), events))
require.Len(t, events.Items, 1)

event := events.Items[0]
require.Equal(t, "Unknown user fake-message", event.Message)
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
c := fake.NewClientBuilder().Build()
(&argoCDMechanism{argocdClient: c}).logAppEvent(
context.Background(),
testCase.app,
testCase.user,
testCase.eventReason,
testCase.eventMessage,
)
testCase.assertions(t, c, testCase.app)
})
}
}

func TestAuthorizeArgoCDAppUpdate(t *testing.T) {
permErr := "does not permit mutation"
parseErr := "unable to parse"
Expand Down

0 comments on commit a6ced29

Please sign in to comment.