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:
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
47
docs/notifications.md
Normal 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.
|
33
pkg/notifications/bonfire.go
Normal file
33
pkg/notifications/bonfire.go
Normal 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() {}
|
41
pkg/notifications/notifications.go
Normal file
41
pkg/notifications/notifications.go
Normal 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()
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user