Skip to content

Commit

Permalink
feat: Migrate argocd notifications to argocd (argoproj#7744)
Browse files Browse the repository at this point in the history
feat: Migrate argocd notifications to argocd (argoproj#7744)

Signed-off-by: pashavictorovich <[email protected]>
  • Loading branch information
pasha-codefresh authored Nov 30, 2021
1 parent d346fce commit 0f2f9a9
Show file tree
Hide file tree
Showing 90 changed files with 6,787 additions and 148 deletions.
13 changes: 12 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,16 @@ openapigen: ensure-gopath
export GO111MODULE=off
./hack/update-openapi.sh

.PHONY: notification-catalog
notification-catalog:
go run ./hack/gen-catalog catalog

.PHONY: notification-docs
notification-docs:
go run ./hack/gen-docs
go run ./hack/gen-catalog docs


.PHONY: clientgen
clientgen: ensure-gopath
export GO111MODULE=off
Expand All @@ -195,8 +205,9 @@ clientgen: ensure-gopath
clidocsgen: ensure-gopath
go run tools/cmd-docs/main.go


.PHONY: codegen-local
codegen-local: ensure-gopath mod-vendor-local gogen protogen clientgen openapigen clidocsgen manifests-local
codegen-local: ensure-gopath mod-vendor-local notification-docs notification-catalog gogen protogen clientgen openapigen clidocsgen manifests-local
rm -rf vendor/

.PHONY: codegen
Expand Down
132 changes: 132 additions & 0 deletions cmd/argocd-notification/commands/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package commands

import (
"context"
"fmt"
"net/http"
"os"
"strings"

service "github.com/argoproj/argo-cd/v2/util/notification/argocd"

notificationscontroller "github.com/argoproj/argo-cd/v2/notification_controller/controller"

controller "github.com/argoproj/notifications-engine/pkg/controller"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
"k8s.io/client-go/tools/clientcmd"
)

const (
defaultMetricsPort = 9001
)

func addK8SFlagsToCmd(cmd *cobra.Command) clientcmd.ClientConfig {
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig
overrides := clientcmd.ConfigOverrides{}
kflags := clientcmd.RecommendedConfigOverrideFlags("")
cmd.PersistentFlags().StringVar(&loadingRules.ExplicitPath, "kubeconfig", "", "Path to a kube config. Only required if out-of-cluster")
clientcmd.BindOverrideFlags(&overrides, cmd.PersistentFlags(), kflags)
return clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, &overrides, os.Stdin)
}

func NewCommand() *cobra.Command {
var (
clientConfig clientcmd.ClientConfig
processorsCount int
namespace string
appLabelSelector string
logLevel string
logFormat string
metricsPort int
argocdRepoServer string
argocdRepoServerPlaintext bool
argocdRepoServerStrictTLS bool
configMapName string
secretName string
)
var command = cobra.Command{
Use: "controller",
Short: "Starts Argo CD Notifications controller",
RunE: func(c *cobra.Command, args []string) error {
restConfig, err := clientConfig.ClientConfig()
if err != nil {
return err
}
dynamicClient, err := dynamic.NewForConfig(restConfig)
if err != nil {
return err
}
k8sClient, err := kubernetes.NewForConfig(restConfig)
if err != nil {
return err
}
if namespace == "" {
namespace, _, err = clientConfig.Namespace()
if err != nil {
return err
}
}
level, err := log.ParseLevel(logLevel)
if err != nil {
return err
}
log.SetLevel(level)

switch strings.ToLower(logFormat) {
case "json":
log.SetFormatter(&log.JSONFormatter{})
case "text":
if os.Getenv("FORCE_LOG_COLORS") == "1" {
log.SetFormatter(&log.TextFormatter{ForceColors: true})
}
default:
return fmt.Errorf("Unknown log format '%s'", logFormat)
}

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

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

go func() {
log.Fatal(http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", metricsPort), http.DefaultServeMux))
}()
log.Infof("serving metrics on port %d", metricsPort)
log.Infof("loading configuration %d", metricsPort)

ctrl := notificationscontroller.NewController(k8sClient, dynamicClient, argocdService, namespace, appLabelSelector, registry, secretName, configMapName)
err = ctrl.Init(context.Background())
if err != nil {
return err
}

go ctrl.Run(context.Background(), processorsCount)
<-context.Background().Done()
return nil
},
}
clientConfig = addK8SFlagsToCmd(&command)
command.Flags().IntVar(&processorsCount, "processors-count", 1, "Processors count.")
command.Flags().StringVar(&appLabelSelector, "app-label-selector", "", "App label selector.")
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().StringVar(&logFormat, "logformat", "text", "Set the logging format. One of: text|json")
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")
command.Flags().BoolVar(&argocdRepoServerPlaintext, "argocd-repo-server-plaintext", false, "Use a plaintext client (non-TLS) to connect to repository server")
command.Flags().BoolVar(&argocdRepoServerStrictTLS, "argocd-repo-server-strict-tls", false, "Perform strict validation of TLS certificates when connecting to repo server")
command.Flags().StringVar(&configMapName, "config-map-name", "argocd-notifications-cm", "Set notifications ConfigMap name")
command.Flags().StringVar(&secretName, "secret-name", "argocd-notifications-secret", "Set notifications Secret name")
return &command
}
1 change: 1 addition & 0 deletions cmd/argocd/commands/admin/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ func NewAdminCommand() *cobra.Command {
command.AddCommand(NewImportCommand())
command.AddCommand(NewExportCommand())
command.AddCommand(NewDashboardCommand())
command.AddCommand(NewNotificationsCommand())

command.Flags().StringVar(&cmdutil.LogFormat, "logformat", "text", "Set the logging format. One of: text|json")
command.Flags().StringVar(&cmdutil.LogLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
Expand Down
51 changes: 51 additions & 0 deletions cmd/argocd/commands/admin/notifications.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package admin

import (
"log"

"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"

service "github.com/argoproj/argo-cd/v2/util/notification/argocd"
settings "github.com/argoproj/argo-cd/v2/util/notification/settings"

"github.com/argoproj/notifications-engine/pkg/cmd"
"github.com/spf13/cobra"
)

var (
applications = schema.GroupVersionResource{Group: "argoproj.io", Version: "v1alpha1", Resource: "applications"}
)

func NewNotificationsCommand() *cobra.Command {
var (
argocdRepoServer string
argocdRepoServerPlaintext bool
argocdRepoServerStrictTLS bool
)

var argocdService service.Service
toolsCommand := cmd.NewToolsCommand(
"argocd-notifications",
"argocd-notifications",
applications,
settings.GetFactorySettings(argocdService, "argocd-notifications-secret", "argocd-notifications-cm"), func(clientConfig clientcmd.ClientConfig) {
k8sCfg, err := clientConfig.ClientConfig()
if err != nil {
log.Fatalf("Failed to parse k8s config: %v", err)
}
ns, _, err := clientConfig.Namespace()
if err != nil {
log.Fatalf("Failed to parse k8s config: %v", err)
}
argocdService, err = service.NewArgoCDService(kubernetes.NewForConfigOrDie(k8sCfg), ns, argocdRepoServer, argocdRepoServerPlaintext, argocdRepoServerStrictTLS)
if err != nil {
log.Fatalf("Failed to initalize Argo CD service: %v", err)
}
})
toolsCommand.PersistentFlags().StringVar(&argocdRepoServer, "argocd-repo-server", "argocd-repo-server:8081", "Argo CD repo server address")
toolsCommand.PersistentFlags().BoolVar(&argocdRepoServerPlaintext, "argocd-repo-server-plaintext", false, "Use a plaintext client (non-TLS) to connect to repository server")
toolsCommand.PersistentFlags().BoolVar(&argocdRepoServerStrictTLS, "argocd-repo-server-strict-tls", false, "Perform strict validation of TLS certificates when connecting to repo server")
return toolsCommand
}
3 changes: 3 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
appcontroller "github.com/argoproj/argo-cd/v2/cmd/argocd-application-controller/commands"
cmpserver "github.com/argoproj/argo-cd/v2/cmd/argocd-cmp-server/commands"
dex "github.com/argoproj/argo-cd/v2/cmd/argocd-dex/commands"
notification "github.com/argoproj/argo-cd/v2/cmd/argocd-notification/commands"
reposerver "github.com/argoproj/argo-cd/v2/cmd/argocd-repo-server/commands"
apiserver "github.com/argoproj/argo-cd/v2/cmd/argocd-server/commands"
cli "github.com/argoproj/argo-cd/v2/cmd/argocd/commands"
Expand Down Expand Up @@ -39,6 +40,8 @@ func main() {
command = cmpserver.NewCommand()
case "argocd-dex":
command = dex.NewCommand()
case "argocd-notification":
command = notification.NewCommand()
default:
command = cli.NewCommand()
}
Expand Down
14 changes: 0 additions & 14 deletions docs/operator-manual/notifications.md

This file was deleted.

95 changes: 95 additions & 0 deletions docs/operator-manual/notifications/argocd-notifications-cm.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-notifications-cm
data:
# Triggers define the condition when the notification should be sent and list of templates required to generate the message
# Recipients can subscribe to the trigger and specify the required message template and destination notification service.
trigger.on-sync-status-unknown: |
- when: app.status.sync.status == 'Unknown'
send: [my-custom-template]
# Optional 'oncePer' property ensure that notification is sent only once per specified field value
# E.g. following is triggered once per sync revision
trigger.on-deployed: |
- when: app.status.operationState.phase in ['Succeeded'] and app.status.health.status == 'Healthy'
oncePer: app.status.sync.revision
send: [app-sync-succeeded]
# Templates are used to generate the notification template message
template.my-custom-template: |
message: |
Application details: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}.
# Templates might have notification service specific fields. E.g. slack message might include annotations
template.my-custom-template-slack-template: |
message: |
Application {{.app.metadata.name}} sync is {{.app.status.sync.status}}.
Application details: {{.context.argocdUrl}}/applications/{{.app.metadata.name}}.
email:
subject: Application {{.app.metadata.name}} sync status is {{.app.status.sync.status}}
slack:
attachments: |
[{
"title": "{{.app.metadata.name}}",
"title_link": "{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
"color": "#18be52"
}]
# Holds list of triggers that are used by default if trigger is not specified explicitly in the subscription
defaultTriggers: |
- on-sync-status-unknown
# Notification services are used to deliver message.
# Service definition might reference values from argocd-notifications-secret Secret using $my-key format
# Service format key is: service.<type>.<optional-custom-name>
# Slack
service.slack: |
token: $slack-token
username: <override-username> # optional username
icon: <override-icon> # optional icon for the message (supports both emoij and url notation)
# Slack based notifier with name mattermost
service.slack.mattermost: |
apiURL: https://my-mattermost-url.com/api
token: $slack-token
username: <override-username> # optional username
icon: <override-icon> # optional icon for the message (supports both emoij and url notation)
# Email
service.email: |
host: smtp.gmail.com
port: 587
from: <myemail>@gmail.com
username: $email-username
password: $email-password
# Opsgenie
service.opsgenie: |
apiUrl: api.opsgenie.com
apiKeys:
$opsgenie-team-id: $opsgenie-team-api-key
...
# Telegram
service.telegram: |
token: $telegram-token
# Context holds list of variables that can be referenced in templates
context: |
argocdUrl: https://cd.apps.argoproj.io/
# Contains centrally managed global application subscriptions
subscriptions: |
# subscription for on-sync-status-unknown trigger notifications
- recipients:
- slack:test2
- email:[email protected]
triggers:
- on-sync-status-unknown
# subscription restricted to applications with matching labels only
- recipients:
- slack:test3
selector: test=true
triggers:
- on-sync-status-unknown
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
apiVersion: v1
kind: Secret
metadata:
name: argocd-notifications-secret
stringData:
slack-token: <my-slack-token>
email-username: <myemail>@gmail.com
email-password: <mypassword>

type: Opaque
Loading

0 comments on commit 0f2f9a9

Please sign in to comment.