mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2025-09-13 08:34:45 +08:00
Simple notification framework (#297)
* bonfire notifications working * make interface to make more extensible * some docs * typo * rename typo
This commit is contained in:
parent
91e2cf67ef
commit
9dbd4a3066
6 changed files with 148 additions and 7 deletions
|
@ -38,7 +38,7 @@ func CreateDomains(args CreateDomainsArgs) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
registrars, dnsProviders, _, err := InitializeProviders(args.CredsFile, cfg)
|
registrars, dnsProviders, _, _, err := InitializeProviders(args.CredsFile, cfg, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/StackExchange/dnscontrol/models"
|
"github.com/StackExchange/dnscontrol/models"
|
||||||
"github.com/StackExchange/dnscontrol/pkg/nameservers"
|
"github.com/StackExchange/dnscontrol/pkg/nameservers"
|
||||||
"github.com/StackExchange/dnscontrol/pkg/normalize"
|
"github.com/StackExchange/dnscontrol/pkg/normalize"
|
||||||
|
"github.com/StackExchange/dnscontrol/pkg/notifications"
|
||||||
"github.com/StackExchange/dnscontrol/pkg/printer"
|
"github.com/StackExchange/dnscontrol/pkg/printer"
|
||||||
"github.com/StackExchange/dnscontrol/providers"
|
"github.com/StackExchange/dnscontrol/providers"
|
||||||
"github.com/StackExchange/dnscontrol/providers/config"
|
"github.com/StackExchange/dnscontrol/providers/config"
|
||||||
|
@ -31,12 +32,18 @@ type PreviewArgs struct {
|
||||||
GetDNSConfigArgs
|
GetDNSConfigArgs
|
||||||
GetCredentialsArgs
|
GetCredentialsArgs
|
||||||
FilterArgs
|
FilterArgs
|
||||||
|
Notify bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (args *PreviewArgs) flags() []cli.Flag {
|
func (args *PreviewArgs) flags() []cli.Flag {
|
||||||
flags := args.GetDNSConfigArgs.flags()
|
flags := args.GetDNSConfigArgs.flags()
|
||||||
flags = append(flags, args.GetCredentialsArgs.flags()...)
|
flags = append(flags, args.GetCredentialsArgs.flags()...)
|
||||||
flags = append(flags, args.FilterArgs.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
|
return flags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +96,7 @@ func run(args PreviewArgs, push bool, interactive bool, out printer.CLI) error {
|
||||||
if PrintValidationErrors(errs) {
|
if PrintValidationErrors(errs) {
|
||||||
return fmt.Errorf("Exiting due to validation errors")
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -130,7 +137,7 @@ DomainLoop:
|
||||||
continue DomainLoop
|
continue DomainLoop
|
||||||
}
|
}
|
||||||
totalCorrections += len(corrections)
|
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)
|
run := args.shouldRunProvider(domain.Registrar, domain, nonDefaultProviders)
|
||||||
out.StartRegistrar(domain.Registrar, !run)
|
out.StartRegistrar(domain.Registrar, !run)
|
||||||
|
@ -156,11 +163,12 @@ DomainLoop:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
totalCorrections += len(corrections)
|
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") != "" {
|
if os.Getenv("TEAMCITY_VERSION") != "" {
|
||||||
fmt.Fprintf(os.Stderr, "##teamcity[buildStatus status='SUCCESS' text='%d corrections']", totalCorrections)
|
fmt.Fprintf(os.Stderr, "##teamcity[buildStatus status='SUCCESS' text='%d corrections']", totalCorrections)
|
||||||
}
|
}
|
||||||
|
notifier.Done()
|
||||||
out.Debugf("Done. %d corrections.\n", totalCorrections)
|
out.Debugf("Done. %d corrections.\n", totalCorrections)
|
||||||
if anyErrors {
|
if anyErrors {
|
||||||
return fmt.Errorf("Completed with errors")
|
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.
|
// 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.
|
// 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 providerConfigs map[string]map[string]string
|
||||||
|
var notificationCfg map[string]string
|
||||||
|
defer func() {
|
||||||
|
notify = notifications.Init(notificationCfg)
|
||||||
|
}()
|
||||||
providerConfigs, err = config.LoadProviderConfigs(credsFile)
|
providerConfigs, err = config.LoadProviderConfigs(credsFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if notifyFlag {
|
||||||
|
notificationCfg = providerConfigs["notifications"]
|
||||||
|
}
|
||||||
nonDefaultProviders = []string{}
|
nonDefaultProviders = []string{}
|
||||||
for name, vals := range providerConfigs {
|
for name, vals := range providerConfigs {
|
||||||
// add "_exclude_from_defaults":"true" to a provider to exclude it from being run unless
|
// 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
|
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
|
anyErrors = false
|
||||||
if len(corrections) == 0 {
|
if len(corrections) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for i, correction := range corrections {
|
for i, correction := range corrections {
|
||||||
out.PrintCorrection(i, correction)
|
out.PrintCorrection(i, correction)
|
||||||
|
var err error
|
||||||
if push {
|
if push {
|
||||||
if interactive && !out.PromptToRun() {
|
if interactive && !out.PromptToRun() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
err := correction.F()
|
err = correction.F()
|
||||||
out.EndCorrection(err)
|
out.EndCorrection(err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
anyErrors = true
|
anyErrors = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
notifier.Notify(domain, provider, correction.Msg, err, !push)
|
||||||
}
|
}
|
||||||
return anyErrors
|
return anyErrors
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,6 +129,9 @@ title: DnsControl
|
||||||
<li>
|
<li>
|
||||||
<a href="{{site.github.url}}/unittests">Testing</a>: Unit Testing for you DNS Data
|
<a href="{{site.github.url}}/unittests">Testing</a>: Unit Testing for you DNS Data
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{site.github.url}}/notifications">Notifications</a>: Be alerted when your domains are changed
|
||||||
|
</li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</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()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue