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

feat: support loading commit metadata #87

Merged
merged 1 commit into from
May 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion builtin/templates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func triggerWithTemplate(name string) (triggers.Trigger, error) {
Name: "test",
Template: name,
Condition: "true",
}})
}}, nil)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion builtin/triggers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func TestBuiltInTriggers(t *testing.T) {
if testCase, ok := testCases[trigger.Name]; !ok {
t.Fatalf("No tests for trigger %s", trigger.Name)
} else {
builtInTriggers, err := triggers.GetTriggers(Templates, []triggers.NotificationTrigger{trigger})
builtInTriggers, err := triggers.GetTriggers(Templates, []triggers.NotificationTrigger{trigger}, nil)
assert.NoError(t, err)
item := builtInTriggers[trigger.Name]
for i := range testCase.negativeInputs {
Expand Down
15 changes: 12 additions & 3 deletions cmd/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/argoproj-labs/argocd-notifications/builtin"
"github.com/argoproj-labs/argocd-notifications/controller"
"github.com/argoproj-labs/argocd-notifications/notifiers"
"github.com/argoproj-labs/argocd-notifications/shared/argocd"
"github.com/argoproj-labs/argocd-notifications/shared/cmd"
"github.com/argoproj-labs/argocd-notifications/shared/settings"
"github.com/argoproj-labs/argocd-notifications/triggers"
Expand Down Expand Up @@ -52,6 +53,7 @@ func newControllerCommand() *cobra.Command {
appLabelSelector string
logLevel string
metricsPort int
argocdRepoServer string
)
var command = cobra.Command{
Use: "controller",
Expand Down Expand Up @@ -80,6 +82,12 @@ func newControllerCommand() *cobra.Command {
}
log.SetLevel(level)

argocdService, err := argocd.NewArgoCDService(k8sClient, namespace, argocdRepoServer)
if err != nil {
return err
}
defer argocdService.Close()

registry := controller.NewMetricsRegistry()
http.Handle("/metrics", promhttp.HandlerFor(prometheus.Gatherers{registry, prometheus.DefaultGatherer}, promhttp.HandlerOpts{}))

Expand All @@ -90,7 +98,7 @@ func newControllerCommand() *cobra.Command {
log.Infof("loading configuration %d", metricsPort)

var cancelPrev context.CancelFunc
watchConfig(context.Background(), k8sClient, namespace, func(triggers map[string]triggers.Trigger, notifiers map[string]notifiers.Notifier, cfg *settings.Config) error {
watchConfig(context.Background(), argocdService, k8sClient, namespace, func(triggers map[string]triggers.Trigger, notifiers map[string]notifiers.Notifier, cfg *settings.Config) error {
if cancelPrev != nil {
log.Info("Settings had been updated. Restarting controller...")
cancelPrev()
Expand Down Expand Up @@ -121,10 +129,11 @@ func newControllerCommand() *cobra.Command {
command.Flags().StringVar(&namespace, "namespace", "", "Namespace which controller handles. Current namespace if empty.")
command.Flags().StringVar(&logLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
command.Flags().IntVar(&metricsPort, "metrics-port", defaultMetricsPort, "Metrics port")
command.Flags().StringVar(&argocdRepoServer, "argocd-repo-server", "argocd-repo-server:8081", "Argo CD repo server address")
return &command
}

func watchConfig(ctx context.Context, clientset kubernetes.Interface, namespace string, callback func(map[string]triggers.Trigger, map[string]notifiers.Notifier, *settings.Config) error) {
func watchConfig(ctx context.Context, argocdService argocd.Service, clientset kubernetes.Interface, namespace string, callback func(map[string]triggers.Trigger, map[string]notifiers.Notifier, *settings.Config) error) {
var secret *v1.Secret
var configMap *v1.ConfigMap
lock := &sync.Mutex{}
Expand All @@ -138,7 +147,7 @@ func watchConfig(ctx context.Context, clientset kubernetes.Interface, namespace
configMap = newConfigMap
}
if secret != nil && configMap != nil {
if t, n, c, err := settings.ParseConfig(configMap, secret, defaultCfg); err == nil {
if t, n, c, err := settings.ParseConfig(configMap, secret, defaultCfg, argocdService); err == nil {
if err = callback(t, n, c); err != nil {
log.Fatalf("Failed to start controller: %v", err)
}
Expand Down
8 changes: 7 additions & 1 deletion cmd/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,22 @@ import (
"context"
"testing"

"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"

"github.com/argoproj-labs/argocd-notifications/notifiers"
"github.com/argoproj-labs/argocd-notifications/shared/argocd/mocks"
"github.com/argoproj-labs/argocd-notifications/shared/settings"
"github.com/argoproj-labs/argocd-notifications/triggers"
)

func TestWatchConfig(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
configMap := &v1.ConfigMap{
Expand Down Expand Up @@ -46,8 +51,9 @@ templates:

triggersMap := make(map[string]triggers.Trigger)
notifiersMap := make(map[string]notifiers.Notifier)
argocdService := mocks.NewMockService(ctrl)
clientset := fake.NewSimpleClientset(configMap, secret)
watchConfig(ctx, clientset, "default", func(t map[string]triggers.Trigger, n map[string]notifiers.Notifier, cfg *settings.Config) error {
watchConfig(ctx, argocdService, clientset, "default", func(t map[string]triggers.Trigger, n map[string]notifiers.Notifier, cfg *settings.Config) error {
triggersMap = t
notifiersMap = n
return nil
Expand Down
42 changes: 40 additions & 2 deletions cmd/tools/context.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package tools

import (
"context"
"io"
"io/ioutil"
"path/filepath"
"sync"

"github.com/ghodss/yaml"
v1 "k8s.io/api/core/v1"
Expand All @@ -14,18 +16,54 @@ import (
"k8s.io/client-go/tools/clientcmd"

"github.com/argoproj-labs/argocd-notifications/notifiers"
"github.com/argoproj-labs/argocd-notifications/shared/argocd"
"github.com/argoproj-labs/argocd-notifications/shared/clients"
"github.com/argoproj-labs/argocd-notifications/shared/settings"
"github.com/argoproj-labs/argocd-notifications/triggers"
"github.com/argoproj-labs/argocd-notifications/triggers/expr/shared"
)

type clientsSource = func() (kubernetes.Interface, dynamic.Interface, string, error)

type commandContext struct {
configMapPath string
secretPath string
defaultCfg settings.Config
stdout io.Writer
stderr io.Writer
getK8SClients func() (kubernetes.Interface, dynamic.Interface, string, error)
getK8SClients clientsSource
argocdService *lazyArgocdServiceInitializer
}

type lazyArgocdServiceInitializer struct {
argocdRepoServer *string
argocdService argocd.Service
init sync.Once
getK8SClients clientsSource
}

func (svc *lazyArgocdServiceInitializer) initArgoCDService() error {
k8sClient, _, ns, err := svc.getK8SClients()
if err != nil {
return err
}
argocdService, err := argocd.NewArgoCDService(k8sClient, ns, *svc.argocdRepoServer)
if err != nil {
return err
}
svc.argocdService = argocdService
return nil
}

func (svc *lazyArgocdServiceInitializer) GetCommitMetadata(ctx context.Context, repoURL string, commitSHA string) (*shared.CommitMetadata, error) {
var err error
svc.init.Do(func() {
err = svc.initArgoCDService()
})
if err != nil {
return nil, err
}
return svc.argocdService.GetCommitMetadata(ctx, repoURL, commitSHA)
}

func getK8SClients(clientConfig clientcmd.ClientConfig) (kubernetes.Interface, dynamic.Interface, string, error) {
Expand Down Expand Up @@ -93,7 +131,7 @@ func (c *commandContext) getConfig() (map[string]triggers.Trigger, map[string]no
}
}

return settings.ParseConfig(&configMap, &secret, c.defaultCfg)
return settings.ParseConfig(&configMap, &secret, c.defaultCfg, &lazyArgocdServiceInitializer{})
}

func (c *commandContext) loadApplication(application string) (*unstructured.Unstructured, error) {
Expand Down
10 changes: 6 additions & 4 deletions cmd/tools/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ argocd-notifications tools template notify app-sync-succeeded guestbook
Name: "__test__",
Template: name,
Condition: "true",
}})
}}, cmdContext.argocdService)
if err != nil {
_, _ = fmt.Fprintf(cmdContext.stderr, "failed to parse config: %v\n", err)
return nil
Expand All @@ -90,18 +90,20 @@ argocd-notifications tools template notify app-sync-succeeded guestbook
notifierType := parts[0]
notifier, ok := notifiersByName[notifierType]
if !ok {
_, _ = fmt.Fprintf(cmdContext.stderr, "%s is not valid recipient type.", notifierType)
_, _ = fmt.Fprintf(cmdContext.stderr, "%s is not valid recipient type.\n", notifierType)
return nil
}

ctx := sharedrecipients.CopyStringMap(config.Context)
ctx["notificationType"] = notifierType
notification, err := trigger.FormatNotification(app, ctx)
if err != nil {
_, _ = fmt.Fprintf(cmdContext.stderr, "failed to format notification: %v", err)
_, _ = fmt.Fprintf(cmdContext.stderr, "failed to format notification: %v\n", err)
return nil
}
if err = notifier.Send(*notification, parts[1]); err != nil {
_, _ = fmt.Fprintf(cmdContext.stderr, "failed to notify '%s': %v", recipient, err)
_, _ = fmt.Fprintf(cmdContext.stderr, "failed to notify '%s': %v\n", recipient, err)
return nil
}
}

Expand Down
13 changes: 9 additions & 4 deletions cmd/tools/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,12 @@ func printFormatted(input interface{}, output string, out io.Writer) error {

func NewToolsCommand(defaultCfg settings.Config) *cobra.Command {
var (
cmdContext = commandContext{
defaultCfg: defaultCfg,
stdout: os.Stdout,
stderr: os.Stderr,
argocdRepoServer string
cmdContext = commandContext{
defaultCfg: defaultCfg,
stdout: os.Stdout,
stderr: os.Stderr,
argocdService: &lazyArgocdServiceInitializer{argocdRepoServer: &argocdRepoServer},
}
)
var command = cobra.Command{
Expand All @@ -72,9 +74,12 @@ func NewToolsCommand(defaultCfg settings.Config) *cobra.Command {
"config-map", "", "argocd-notifications-cm.yaml file path")
command.PersistentFlags().StringVar(&cmdContext.secretPath,
"secret", "", "argocd-notifications-secret.yaml file path. Use empty secret if provided value is ':empty'")
command.PersistentFlags().StringVar(&argocdRepoServer,
"argocd-repo-server", "argocd-repo-server:8081", "Argo CD repo server address")
clientConfig := cmd.AddK8SFlagsToCmd(&command)
cmdContext.getK8SClients = func() (kubernetes.Interface, dynamic.Interface, string, error) {
return getK8SClients(clientConfig)
}
cmdContext.argocdService.getK8SClients = cmdContext.getK8SClients
return &command
}
3 changes: 2 additions & 1 deletion docs/argocd-notifications-cm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@ data:
body: |
Application details: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}.
# Override one field in built-in template
- name: app-sync-status
- name: on-sync-succeeded
title: Application {{.app.metadata.name}} sync status is {{.app.status.sync.status}}
body: "{{call .git.GetCommitMetadata .app.status.sync.revision}}"
54 changes: 54 additions & 0 deletions docs/triggers_and_templates/functions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
## Functions

Both templates and triggers have access to the set of functions.

Trigger example:

```yaml
name: app-operation-stuck
condition: time.Now().Sub(time.Parse(app.status.operationState.startedAt)).Minutes() >= 5
template: my-template
```

Template example:
```yaml
name: my-template
title: Application {{.app.metadata.name}} sync status is {{.app.status.sync.status}}
body: "Author: {{(call .repo.GetCommitMetadata .app.status.sync.revision).Author}}"
```

### **time**
Time related functions.

<hr>
**`time.Now() Time`**

Executes function built-in Golang [time.Now](https://golang.org/pkg/time/#Now) function.
Returns an instance of Golang [Time](https://golang.org/pkg/time/#Time).

<hr>
**`time.Parse(val string) Time`**

Parses specified string using RFC3339 layout. Returns an instance of Golang [Time](https://golang.org/pkg/time/#Time).

### **repo**
Functions that provide additional information about Application source repository.
<hr>
**`repo.RepoURLToHTTPS(url string) string`**

Transforms given GIT URL into HTTPs format.

<hr>
**`repo.FullNameByRepoURL(url string) string`**

Returns repository URL full name `(<owner>/<repoName>)`. Currently supports only Github, Gitlab and Bitbucket.

<hr>
**`repo.GetCommitMetadata(sha string) CommitMetadata`**

Returns commit metadata. The commit must belong to the application source repository. `CommitMetadata` fields:

* `Message string` commit message
* `Author string` - commit author
* `Date time.Time` - commit creation date
* `Tags []string` - Associated tags
15 changes: 1 addition & 14 deletions docs/triggers_and_templates/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,6 @@ evaluation is powered by [antonmedv/expr](https://github.com/antonmedv/expr). Th
at [Language-Definition.md](https://github.com/antonmedv/expr/blob/master/docs/Language-Definition.md).
* **enabled** - flag that indicates if trigger is enabled or not. By default trigger is enabled.

### Functions

Following functions can be used with-in the condition expression:

**time** (v0.5+)

* `time.Now` - executes function built-in Golang [time.Now](https://golang.org/pkg/time/#Now) function.
* `time.Parse` - parses specified string using RFC3339 layout

**repo** (v0.6+)

* `repo.RepoURLToHTTPS` - transforms given GIT URL into HTTPs format
* `repo.FullNameByRepoURL` - returns repository URL full name (<owner>/<repoName>). Currently supports only Github, Gitlab and Bitbucket.

## Templates

The notification template is used to generate the notification content. The template is leveraging
Expand All @@ -61,3 +47,4 @@ Each template has access to the `app` and `context` fields:

- `app` holds the application object.
- `context` is user defined string map and might include any string keys and values.

9 changes: 8 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,39 @@ require (
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/sprig v2.22.0+incompatible
github.com/antonmedv/expr v1.4.1
github.com/argoproj/argo-cd v1.5.4
github.com/argoproj/pkg v0.0.0-20200424003221-9b858eff18a1 // indirect
github.com/evanphx/json-patch v4.5.0+incompatible // indirect
github.com/ghodss/yaml v1.0.0
github.com/gobwas/glob v0.2.3 // indirect
github.com/gogo/protobuf v1.3.1 // indirect
github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7 // indirect
github.com/golang/mock v1.3.1
github.com/googleapis/gnostic v0.3.1 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.2.0 // indirect
github.com/huandu/xstrings v1.3.0 // indirect
github.com/imdario/mergo v0.3.8 // indirect
github.com/jstemmer/go-junit-report v0.9.1 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/magiconair/properties v1.8.0
github.com/mitchellh/copystructure v1.0.0 // indirect
github.com/nlopes/slack v0.6.0
github.com/olekukonko/tablewriter v0.0.4
github.com/opsgenie/opsgenie-go-sdk-v2 v1.0.5
github.com/prometheus/client_golang v1.6.0
github.com/robfig/cron v1.2.0 // indirect
github.com/sirupsen/logrus v1.4.2
github.com/spf13/cobra v0.0.5
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.4.0
github.com/whilp/git-urls v0.0.0-20191001220047-6db9661140c0
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 // indirect
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 // indirect
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 // indirect
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
gomodules.xyz/notify v0.1.0
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/src-d/go-git.v4 v4.13.1 // indirect
k8s.io/api v0.0.0-20191114100352-16d7abae0d2a
k8s.io/apimachinery v0.0.0-20191028221656-72ed19daf4bb
k8s.io/client-go v0.0.0-20191114101535-6c5935290e33
Expand Down
Loading