1
0
mirror of https://github.com/StackExchange/dnscontrol.git synced 2024-05-11 05:55:12 +00:00

Simple notification framework (#297)

* bonfire notifications working

* make interface to make more extensible

* some docs

* typo

* rename typo
This commit is contained in:
Craig Peterson
2018-01-11 11:15:19 -05:00
committed by GitHub
parent 91e2cf67ef
commit 9dbd4a3066
6 changed files with 148 additions and 7 deletions

View File

@ -38,7 +38,7 @@ func CreateDomains(args CreateDomainsArgs) error {
if err != nil {
return err
}
registrars, dnsProviders, _, err := InitializeProviders(args.CredsFile, cfg)
registrars, dnsProviders, _, _, err := InitializeProviders(args.CredsFile, cfg, false)
if err != nil {
return err
}

View File

@ -8,6 +8,7 @@ import (
"github.com/StackExchange/dnscontrol/models"
"github.com/StackExchange/dnscontrol/pkg/nameservers"
"github.com/StackExchange/dnscontrol/pkg/normalize"
"github.com/StackExchange/dnscontrol/pkg/notifications"
"github.com/StackExchange/dnscontrol/pkg/printer"
"github.com/StackExchange/dnscontrol/providers"
"github.com/StackExchange/dnscontrol/providers/config"
@ -31,12 +32,18 @@ type PreviewArgs struct {
GetDNSConfigArgs
GetCredentialsArgs
FilterArgs
Notify bool
}
func (args *PreviewArgs) flags() []cli.Flag {
flags := args.GetDNSConfigArgs.flags()
flags = append(flags, args.GetCredentialsArgs.flags()...)
flags = append(flags, args.FilterArgs.flags()...)
flags = append(flags, cli.BoolFlag{
Name: "notify",
Destination: &args.Notify,
Usage: `set to true to send notifications to configured destinations`,
})
return flags
}
@ -89,7 +96,7 @@ func run(args PreviewArgs, push bool, interactive bool, out printer.CLI) error {
if PrintValidationErrors(errs) {
return fmt.Errorf("Exiting due to validation errors")
}
registrars, dnsProviders, nonDefaultProviders, err := InitializeProviders(args.CredsFile, cfg)
registrars, dnsProviders, nonDefaultProviders, notifier, err := InitializeProviders(args.CredsFile, cfg, args.Notify)
if err != nil {
return err
}
@ -130,7 +137,7 @@ DomainLoop:
continue DomainLoop
}
totalCorrections += len(corrections)
anyErrors = printOrRunCorrections(corrections, out, push, interactive) || anyErrors
anyErrors = printOrRunCorrections(domain.Name, prov, corrections, out, push, interactive, notifier) || anyErrors
}
run := args.shouldRunProvider(domain.Registrar, domain, nonDefaultProviders)
out.StartRegistrar(domain.Registrar, !run)
@ -156,11 +163,12 @@ DomainLoop:
continue
}
totalCorrections += len(corrections)
anyErrors = printOrRunCorrections(corrections, out, push, interactive) || anyErrors
anyErrors = printOrRunCorrections(domain.Name, domain.Registrar, corrections, out, push, interactive, notifier) || anyErrors
}
if os.Getenv("TEAMCITY_VERSION") != "" {
fmt.Fprintf(os.Stderr, "##teamcity[buildStatus status='SUCCESS' text='%d corrections']", totalCorrections)
}
notifier.Done()
out.Debugf("Done. %d corrections.\n", totalCorrections)
if anyErrors {
return fmt.Errorf("Completed with errors")
@ -170,12 +178,19 @@ DomainLoop:
// InitializeProviders takes a creds file path and a DNSConfig object. Creates all providers with the proper types, and returns them.
// nonDefaultProviders is a list of providers that should not be run unless explicitly asked for by flags.
func InitializeProviders(credsFile string, cfg *models.DNSConfig) (registrars map[string]providers.Registrar, dnsProviders map[string]providers.DNSServiceProvider, nonDefaultProviders []string, err error) {
func InitializeProviders(credsFile string, cfg *models.DNSConfig, notifyFlag bool) (registrars map[string]providers.Registrar, dnsProviders map[string]providers.DNSServiceProvider, nonDefaultProviders []string, notify notifications.Notifier, err error) {
var providerConfigs map[string]map[string]string
var notificationCfg map[string]string
defer func() {
notify = notifications.Init(notificationCfg)
}()
providerConfigs, err = config.LoadProviderConfigs(credsFile)
if err != nil {
return
}
if notifyFlag {
notificationCfg = providerConfigs["notifications"]
}
nonDefaultProviders = []string{}
for name, vals := range providerConfigs {
// add "_exclude_from_defaults":"true" to a provider to exclude it from being run unless
@ -195,23 +210,25 @@ func InitializeProviders(credsFile string, cfg *models.DNSConfig) (registrars ma
return
}
func printOrRunCorrections(corrections []*models.Correction, out printer.CLI, push bool, interactive bool) (anyErrors bool) {
func printOrRunCorrections(domain string, provider string, corrections []*models.Correction, out printer.CLI, push bool, interactive bool, notifier notifications.Notifier) (anyErrors bool) {
anyErrors = false
if len(corrections) == 0 {
return false
}
for i, correction := range corrections {
out.PrintCorrection(i, correction)
var err error
if push {
if interactive && !out.PromptToRun() {
continue
}
err := correction.F()
err = correction.F()
out.EndCorrection(err)
if err != nil {
anyErrors = true
}
}
notifier.Notify(domain, provider, correction.Msg, err, !push)
}
return anyErrors
}

View File

@ -129,6 +129,9 @@ title: DnsControl
<li>
<a href="{{site.github.url}}/unittests">Testing</a>: Unit Testing for you DNS Data
</li>
<li>
<a href="{{site.github.url}}/notifications">Notifications</a>: Be alerted when your domains are changed
</li>
</ul>
</div>

47
docs/notifications.md Normal file
View File

@ -0,0 +1,47 @@
---
layout: default
title: Notifications
---
# Notifications
DNSControl has build in support for notifications when changes are made. This allows you to post messages in team chat, or send emails when dns changes are made.
Notifications are written in the [notifications package](https://github.com/StackExchange/dnscontrol/tree/master/pkg/notifications), and is a really simple interface to implement if you want to add
new types or destinations.
## Configuration
Notifications are set up in your credentials json file. They will use the `notifications` key to look for keys or configuration needed for various notification types.
```
"r53": {
...
},
"gcloud": {
...
} ,
"notifications":{
"bonfire_url": "https://chat.meta.stackexchange.com/feeds/rooms/123?key=xyz"
}
```
You also must run `dnscontrol preview` or `dnscontrol push` with the `-notify` flag to enable notification sending at all.
## Notification types
### Bonfire
This is stack overflow's built in chat system. This is probably not useful for most people.
Configure `bonfire_url` to be the full url including room and api key.
## Future work
Yes, this seems pretty limited right now in what it can do. We didn't want to add a bunch of notification types if nobody was going to use them. The good news is, it should
be really simple to add more. We gladly welcome any PRs with new notification destinations. Some easy possibilities:
- Email
- Slack
- Generic Webhooks
Please update this documentation if you add anything.

View File

@ -0,0 +1,33 @@
package notifications
import (
"fmt"
"net/http"
"strings"
)
func init() {
initers = append(initers, func(cfg map[string]string) Notifier {
if url, ok := cfg["bonfire_url"]; ok {
return bonfireNotifier(url)
}
return nil
})
}
// bonfire notifier for stack exchange internal chat. String is just url with room and token in it
type bonfireNotifier string
func (b bonfireNotifier) Notify(domain, provider, msg string, err error, preview bool) {
var payload string
if preview {
payload = fmt.Sprintf(`**Preview: %s[%s] -** %s`, domain, provider, msg)
} else if err != nil {
payload = fmt.Sprintf(`**ERROR running correction on %s[%s] -** (%s) Error: %s`, domain, provider, msg, err)
} else {
payload = fmt.Sprintf(`Successfully ran correction for %s[%s] - %s`, domain, provider, msg)
}
http.Post(string(b), "text/markdown", strings.NewReader(payload))
}
func (b bonfireNotifier) Done() {}

View File

@ -0,0 +1,41 @@
package notifications
// Notifier is a type that can send a notification
type Notifier interface {
// Notify will be called after a correction is performed.
// It will be given the correction's message, the result of executing it,
// and a flag for whether this is a preview or if it actually ran.
// If preview is true, err will always be nil.
Notify(domain, provider string, message string, err error, preview bool)
// Done will be called exactly once after all notifications are done. This will allow "batched" notifiers to flush and send
Done()
}
// new notification types should add themselves to this array
var initers = []func(map[string]string) Notifier{}
// Init will take the given config map (from creds.json notifications key) and create a single Notifier with
// all notifications it has full config for.
func Init(config map[string]string) Notifier {
notifiers := multiNotifier{}
for _, i := range initers {
n := i(config)
if n != nil {
notifiers = append(notifiers, n)
}
}
return notifiers
}
type multiNotifier []Notifier
func (m multiNotifier) Notify(domain, provider string, message string, err error, preview bool) {
for _, n := range m {
n.Notify(domain, provider, message, err, preview)
}
}
func (m multiNotifier) Done() {
for _, n := range m {
n.Done()
}
}