Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

Commit

Permalink
Merge pull request #9 from alexmt/4-trigger-subscriptions
Browse files Browse the repository at this point in the history
feat: Trigger specific subscriptions
  • Loading branch information
Alexander Matyushentsev authored Jan 12, 2020
2 parents dde3bfa + e8b44c7 commit e0dd8b9
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 13 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## v0.3.0 (Not released)

### Features
* Trigger specific subscriptions

### Other
* Move repo and docker image to https://github.com/argoproj-labs/argocd-notifications

## v0.2.1 (2019-12-29)

Expand Down
30 changes: 20 additions & 10 deletions controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,37 +160,47 @@ func (c *notificationController) notify(title string, body string, recipient str
return notifier.Send(title, body, parts[1])
}

func getRecipientsFromAnnotations(annotations map[string]string) []string {
func getRecipientsFromAnnotations(annotations map[string]string, trigger string) []string {
recipients := make([]string, 0)
for _, recipient := range strings.Split(annotations[recipientsAnnotation], ",") {
if recipient = strings.TrimSpace(recipient); recipient != "" {
recipients = append(recipients, recipient)
for k, annotation := range annotations {
if !strings.HasSuffix(k, recipientsAnnotation) {
continue
}
if name := strings.TrimRight(k[0:len(k)-len(recipientsAnnotation)], "."); name != "" && name != trigger {
continue
}

for _, recipient := range strings.Split(annotation, ",") {
if recipient = strings.TrimSpace(recipient); recipient != "" {
recipients = append(recipients, recipient)
}
}
}

return recipients
}

func (c *notificationController) getRecipients(app *unstructured.Unstructured) map[string]bool {
func (c *notificationController) getRecipients(app *unstructured.Unstructured, trigger string) map[string]bool {
recipients := make(map[string]bool)
if annotations := app.GetAnnotations(); annotations != nil {
for _, recipient := range getRecipientsFromAnnotations(annotations) {
for _, recipient := range getRecipientsFromAnnotations(annotations, trigger) {
recipients[recipient] = true
}
}
projName, ok, err := unstructured.NestedString(app.Object, "spec", "project")
if !ok && err != nil {
if !ok || err != nil {
return recipients
}
projObj, ok, err := c.appProjInformer.GetIndexer().GetByKey(fmt.Sprintf("%s/%s", app.GetNamespace(), projName))
if ok && err != nil {
if !ok || err != nil {
return recipients
}
proj, ok := projObj.(*unstructured.Unstructured)
if !ok {
return recipients
}
if annotations := proj.GetAnnotations(); annotations != nil {
for _, recipient := range getRecipientsFromAnnotations(annotations) {
for _, recipient := range getRecipientsFromAnnotations(annotations, trigger) {
recipients[recipient] = true
}
}
Expand All @@ -208,7 +218,7 @@ func (c *notificationController) processApp(app *unstructured.Unstructured, logE
if err != nil {
logEntry.Debugf("Failed to execute condition of trigger %s: %v", triggerKey, err)
}
recipients := c.getRecipients(app)
recipients := c.getRecipients(app, triggerKey)
triggerAnnotation := fmt.Sprintf("%s.%s", triggerKey, annotationPostfix)
logEntry.Infof("Trigger %s result: %v", triggerKey, triggered)
if triggered {
Expand Down
38 changes: 36 additions & 2 deletions controller/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ func newController(t *testing.T, ctx context.Context, client dynamic.Interface)
if err != nil {
return nil, nil, nil, err
}
err = c.Init(ctx)
if err != nil {
return nil, nil, nil, err
}
return c.(*notificationController), trigger, notifier, err
}

Expand Down Expand Up @@ -108,6 +112,38 @@ func TestRemovesAnnotationIfNoTrigger(t *testing.T) {
assert.Empty(t, app.GetAnnotations()[fmt.Sprintf("mock.%s", annotationPostfix)])
}

func TestGetRecipients(t *testing.T) {
ctx, cancel := context.WithCancel(context.TODO())
defer cancel()
app := NewApp("test", WithProject("default"), WithAnnotations(map[string]string{
recipientsAnnotation: "slack:test1",
}))
appProj := NewProject("default", WithAnnotations(map[string]string{
recipientsAnnotation: "slack:test2",
fmt.Sprintf("on-app-sync-unknown.%s", recipientsAnnotation): "slack:test3",
}))
ctrl, _, _, err := newController(t, ctx, fake.NewSimpleDynamicClient(runtime.NewScheme(), app, appProj))
assert.NoError(t, err)

recipients := ctrl.getRecipients(app, "on-app-health-degraded")
assert.Equal(t, map[string]bool{"slack:test1": true, "slack:test2": true}, recipients)
}

func TestGetRecipientsFromAnnotations_NoTriggerNameInAnnotation(t *testing.T) {
recipients := getRecipientsFromAnnotations(
map[string]string{recipientsAnnotation: "slack:test"}, "on-app-sync-unknown")
assert.ElementsMatch(t, recipients, []string{"slack:test"})
}

func TestGetRecipientsFromAnnotations_HasTriggerNameInAnnotation(t *testing.T) {
recipients := getRecipientsFromAnnotations(map[string]string{
recipientsAnnotation: "slack:test",
fmt.Sprintf("on-app-sync-unknown.%s", recipientsAnnotation): "slack:test1",
fmt.Sprintf("on-app-health-degraded.%s", recipientsAnnotation): "slack:test2",
}, "on-app-sync-unknown")
assert.ElementsMatch(t, recipients, []string{"slack:test", "slack:test1"})
}

func TestUpdatedAnnotationsSavedAsPatch(t *testing.T) {
ctx, cancel := context.WithCancel(context.TODO())
defer cancel()
Expand All @@ -127,8 +163,6 @@ func TestUpdatedAnnotationsSavedAsPatch(t *testing.T) {
assert.NoError(t, err)

trigger.EXPECT().Triggered(gomock.Any()).Return(false, nil).AnyTimes()
err = ctrl.Init(ctx)
assert.NoError(t, err)

go ctrl.Run(ctx, 1)

Expand Down
14 changes: 14 additions & 0 deletions docs/recipients.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,20 @@ metadata:
recipients.argocd-notifications.argoproj.io: slack:<sample-channel-name>
```
## Trigger Specific Subscription (v0.3)
It is possible to subscribe recipient to a specific trigger instead of all triggers. The annotation key should be
prefixed with `<trigger-name>.`. The example below demonstrates how to receive only `on-sync-failed` trigger
notifications:

```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
annotations:
on-sync-failed.recipients.argocd-notifications.argoproj.io: email:<sample-email>
```

## Notification Services

Each recipient is prefixed with the notification service type such as `slack` or `email`. The notification services are
Expand Down
19 changes: 18 additions & 1 deletion testing/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@ const (
TestNamespace = "default"
)

func WithAnnotations(annotations map[string]string) func(app *unstructured.Unstructured) {
func WithAnnotations(annotations map[string]string) func(obj *unstructured.Unstructured) {
return func(app *unstructured.Unstructured) {
app.SetAnnotations(annotations)
}
}

func WithProject(project string) func(app *unstructured.Unstructured) {
return func(app *unstructured.Unstructured) {
_ = unstructured.SetNestedField(app.Object, project, "spec", "project")
}
}

func WithSyncStatus(status string) func(app *unstructured.Unstructured) {
return func(app *unstructured.Unstructured) {
_ = unstructured.SetNestedField(app.Object, status, "status", "sync", "status")
Expand Down Expand Up @@ -43,3 +49,14 @@ func NewApp(name string, modifiers ...func(app *unstructured.Unstructured)) *uns
}
return &app
}

func NewProject(name string, modifiers ...func(app *unstructured.Unstructured)) *unstructured.Unstructured {
proj := unstructured.Unstructured{}
proj.SetGroupVersionKind(schema.GroupVersionKind{Group: "argoproj.io", Kind: "appproject", Version: "v1alpha1"})
proj.SetName(name)
proj.SetNamespace(TestNamespace)
for i := range modifiers {
modifiers[i](&proj)
}
return &proj
}

0 comments on commit e0dd8b9

Please sign in to comment.