Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhancement: Create hosts dynamically #126

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ Optional
The maximum number of checks which are executed before changing to a hard state.
* `--icinga_reconnect`/`SIGNALILO_ICINGA_RECONNECT`:
If it's set, Signalilo to waits for a reconnect instead of switching immediately to another URL.
* `--icinga_create_hosts`/`SIGNALILO_ICINGA_CREATE_HOSTS`:
If set, Signalilo will automatically create hosts dynamically based on a label (default: false).
* `--icinga_create_hosts_label`/`SIGNALILO_ICINGA_CREATE_HOSTS_LABEL`:
Label used as hostname to create hosts if `--icinga_create_hosts` is enabled (default: instance).
* `--alertmanager_port`/`SIGNALILO_ALERTMANAGER_PORT`:
Port on which Signalilo listens to incoming webhooks (default 8888).
* `--alertmanager_bearer_token`/`SIGNALILO_ALERTMANAGER_BEARER_TOKEN`:
Expand Down Expand Up @@ -307,6 +311,20 @@ Signalilo will try to parse the value of that label as a [Go duration].
If the value is parsed successfully, Signalilo will create an Icinga service check with active checks enabled and with the check interval set to the parsed duration plus ten percent.
We add ten percent to the parsed duration to account for network latencies etc., which could otherwise lead to flapping heartbeat checks.

### Dynamic Host Creation

Signalilo supports dynamic creation of Icinga2 hosts based on a label
included in the alert. This allows for easy and automatic creation of host
objects in Icinga2 as needed, simplifying the process of associating alerts and
systems.

To enable this feature, set the `icinga_create_hosts` flag to true and set
`icinga_create_hosts_label` to the label that should be used as the hostname to
create the host (default: *instance*). When an alert is received with a label
matching the specified label, Signalilo will attempt to create a host with the
specified name. If a host with that name already exists, the alert will be
associated with that host. If the host does not exist, Signalilo will attempt
to create one before associating the alert with it.

[Go duration]: https://golang.org/pkg/time/#ParseDuration

Expand Down
2 changes: 2 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ type SignaliloConfig struct {
CheckCommand string
MaxCheckAttempts int
Reconnect time.Duration
CreateHosts bool
CreateHostsLabel string
}

func ConfigInitialize(configuration Configuration) {
Expand Down
2 changes: 2 additions & 0 deletions serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ func configureServeCommand(app *kingpin.Application) {
serve.Flag("icinga_password", "Icinga Password").Envar("SIGNALILO_ICINGA_PASSWORD").Required().StringVar(&s.config.IcingaConfig.Password)
serve.Flag("icinga_insecure_tls", "Skip Icinga TLS verification").Envar("SIGNALILO_ICINGA_INSECURE_TLS").Default("false").BoolVar(&s.config.IcingaConfig.InsecureTLS)
serve.Flag("icinga_x509_verify_cn", "Use CN when verifying certificates. Overrides the default go1.15 behavior of rejecting certificates without matching SAN.").Envar("SIGNALILO_ICINGA_X509_VERIFY_CN").Default("true").BoolVar(&s.config.IcingaConfig.X509VerifyCN)
serve.Flag("icinga_create_hosts", "Create hosts dynamically based on a label").Envar("SIGNALILO_ICINGA_CREATE_HOSTS").Default("false").BoolVar(&s.config.CreateHosts)
serve.Flag("icinga_create_hosts_label", "Label used as hostname to create hosts").Envar("SIGNALILO_ICINGA_CREATE_HOSTS_LABEL").Default("instance").StringVar(&s.config.CreateHostsLabel)
serve.Flag("icinga_disable_keepalives", "Disable HTTP keepalives").Envar("SIGNALILO_ICINGA_DISABLE_KEEPALIVES").Default("false").BoolVar(&s.config.IcingaConfig.DisableKeepAlives)
serve.Flag("icinga_display_name_as_service_name", "Leave display name as service name").Envar("SIGNALILO_ICINGA_DISPLAY_NAME_AS_SERVICE_NAME").Default("false").BoolVar(&s.config.DisplayNameAsServiceName)
serve.Flag("icinga_debug", "Enable debug-level logging for icinga2 client library").Envar("SIGNALILO_ICINGA_DEBUG").Default("false").BoolVar(&s.config.IcingaConfig.Debug)
Expand Down
32 changes: 32 additions & 0 deletions webhook/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,39 @@ func Webhook(w http.ResponseWriter, r *http.Request, c config.Configuration) {
l.V(2).Infof("Grouped alerts without matching alertname: %d alerts", len(data.Alerts))
}

// For dynamic creation of hosts. This map contains the hosts we have
// already seen.
hosts := map[string]struct{}{
serviceHost: {},
}

for _, alert := range data.Alerts {
if c.GetConfig().CreateHosts {
if name, ok := alert.Labels[c.GetConfig().CreateHostsLabel]; ok {
l.V(2).Infof("Using dynamic host %v", name)
if _, ok := hosts[name]; !ok {
// We haven't seen this host yet.
host, err = icinga.GetHost(name)
if err != nil {
// Host does not exist.
err := icinga.CreateHost(icinga2.Host{
Name: name,
DisplayName: name,
Notes: "Created by signalilo.",
Address: name,
})
if err != nil {
l.Errorf("Could not create service host %v: %v\n", host, err)
asJSON(w, http.StatusInternalServerError, err.Error())
return
}
}
hosts[name] = struct{}{}
}
serviceHost = name
}
}

l.V(2).Infof("Processing %v alert: alertname=%v, severity=%v, message=%v",
alert.Status,
alert.Labels["alertname"],
Expand Down