~fnux/matrix-alertmanager-receiver

560f0db15bb27cc5a07340aad191fd1a6b292d91 — Timothée Floure 6 months ago a89b9ff
Refactor, read configuration from file
3 files changed, 91 insertions(+), 56 deletions(-)

M .gitignore
A config.toml.sample
M main.go
M .gitignore => .gitignore +1 -0
@@ 1,1 1,2 @@
matrix-alertmanager-receiver
config.toml

A config.toml.sample => config.toml.sample +5 -0
@@ 0,0 1,5 @@
Homeserver = "https://staging.matrix.ungleich.cloud"
TargetRoomID = "!jHFKHemgIAaDJekoxN:matrix-staging.ungleich.ch"
MXID = "@fnux:matrix-staging.ungleich.ch"
MXToken = "secret"
HTTPPort = 9088

M main.go => main.go +85 -56
@@ 5,98 5,127 @@ import (
	"flag"
	"fmt"
	"log"
	"io/ioutil"
	"net/http"
	"encoding/json"
	"github.com/prometheus/alertmanager/template"
	"github.com/BurntSushi/toml"
	"github.com/matrix-org/gomatrix"
)

var logger *log.Logger

type Configuration struct {
	Homeserver string
	TargetRoomID string

	MXID string
	MXToken string

	HTTPPort int
	HTTPToken string
}

func generateMatrixMessageBody(alert template.Alert) string {
	return alert.Status + " // " + alert.Annotations["summary"]
}

func main() {
	// Initialize logger.
	var logger *log.Logger = log.New(os.Stdout, "", log.Flags())

	// Handle command-line arguments.
	var homeserver = flag.String("homeserver", "https://matrix.org", "Address of Matrix homeserver")
	var user = flag.String("user", "", "Full MXID (e.g. @example.domain.tld) of Matrix user")
	var token = flag.String("token", "", "Access Token of Matrix user")
	var target = flag.String("target-room", "", "Matrix room to be notified of alerts.")
	var port = flag.Int("port", 9088, "HTTP port to listen on (incoming alertmanager webhooks)")
	flag.Parse()

	if *user == "" {
		logger.Fatal("Matrix user is required. See --help for usage.")
	}
	if *token == "" {
		logger.Fatal("Matrix access token is required. See --help for usage.")
	}
	if *target== "" {
		logger.Fatal("Matrix target room is required. See --help for usage.")
	}

	// Initialize Matrix client.
	matrixClient, err := gomatrix.NewClient(*homeserver, *user, *token)
func getMatrixClient(homeserver string, user string, token string, targetRoomID string) *gomatrix.Client {
	logger.Printf("Connecting to Matrix Homserver %v as %v.", homeserver, user)
	matrixClient, err := gomatrix.NewClient(homeserver, user, token)
	if err != nil {
		logger.Fatalf("Could not log in to Matrix (%v): %v", *homeserver, err)
		logger.Fatalf("Could not log in to Matrix Homeserver (%v): %v", homeserver, err)
	}

	joinedRooms, err := matrixClient.JoinedRooms()
	if err != nil {
		logger.Fatalf("Could not fetch joined rooms: %v", err)
		logger.Fatalf("Could not fetch Matrix rooms: %v", err)
	}

	alreadyJoinedTarget := false
	for _, roomID := range joinedRooms.JoinedRooms {
		// FIXME: will only work if target is a roomID, not an alias.
		if *target == roomID {
		if targetRoomID == roomID {
			alreadyJoinedTarget = true
		}
	}

	if !alreadyJoinedTarget {
		logger.Printf("Trying to join %v...", *target)
		_, err := matrixClient.JoinRoom(*target, "", nil)
	if alreadyJoinedTarget {
		logger.Printf("%v is already part of %v.", user, targetRoomID,)
	} else {
		logger.Printf("Joining %v.", targetRoomID)
		_, err := matrixClient.JoinRoom(targetRoomID, "", nil)
		if err != nil {
			logger.Fatalf("Failed to join %v: %v", *target, err)
			logger.Fatalf("Failed to join %v: %v", targetRoomID, err)
		}
	}

	// Initialize HTTP serve (= listen for incoming requests).
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, `Hi! I receive prometheus-alertmanager webhooks on /alert and forward them to Matrix.
	return matrixClient
}

func handleIncomingHooks( w http.ResponseWriter, r *http.Request,
	matrixClient *gomatrix.Client, targetRoomID string) {

You will find more details on: http://git.sr.ht/~fnux/matrix-prometheus-alertmanager`)
	})
	if r.Method != http.MethodPost {
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}

	http.HandleFunc("/alert", func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			w.WriteHeader(http.StatusMethodNotAllowed)
			return
		}
	payload := template.Data{}
	if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
		w.WriteHeader(http.StatusBadRequest)
	}

	logger.Printf("Received valid hook from %v", r.RemoteAddr)

		payload := template.Data{}
		if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
			w.WriteHeader(http.StatusBadRequest)
	for _, alert := range payload.Alerts {
		body := generateMatrixMessageBody(alert)
		logger.Printf("> %v", body)
		_, err := matrixClient.SendText(targetRoomID, body)
		if err != nil {
			logger.Printf(">> Could not forward to Matrix: %v", err)
		}
	}

	w.WriteHeader(http.StatusOK)
}

func main() {
	// Initialize logger.
	logger = log.New(os.Stdout, "", log.Flags())

	// We use a configuration file since we need to specify secrets, and read
	// everything else from it to keep things simple.
	var configPath = flag.String("config", "/etc/matrix-alertmanager-receiver.toml", "Path to configuration file")
	flag.Parse()

		logger.Printf("Received valid hook from %v", r.RemoteAddr)
	logger.Printf("Reading configuration from %v.", *configPath)
	raw, err := ioutil.ReadFile(*configPath)
	if err != nil {
		logger.Fatalf("Could not read configuration file (%v): %v", *configPath, err)
	}

	var config Configuration
	md, err := toml.Decode(string(raw), &config)
	if err != nil {
		logger.Fatalf("Could not parse configuration file (%v): %v", *configPath, err)
	}

		for _, alert := range payload.Alerts {
			body := generateMatrixMessageBody(alert)
			logger.Printf("> %v", body)
			_, err := matrixClient.SendText(*target, body)
			if err != nil {
				logger.Fatalf("Could not forward to Matrix: %v", err)
			}
	for _, field := range []string{"Homeserver", "MXID", "MXToken", "TargetRoomID", "HTTPPort"} {
		if ! md.IsDefined(field) {
			logger.Fatalf("Field %v is not set in config. Exiting.", field)
		}
	}

		w.WriteHeader(http.StatusOK)
	// Initialize Matrix client.
	matrixClient := getMatrixClient(
		config.Homeserver, config.MXID, config.MXToken, config.TargetRoomID)

	// Initialize HTTP server.
	http.HandleFunc("/alert", func(w http.ResponseWriter, r *http.Request) {
		handleIncomingHooks(w, r, matrixClient, config.TargetRoomID)
	})

	var listenAddr = fmt.Sprintf(":%v", *port)
	var listenAddr = fmt.Sprintf(":%v", config.HTTPPort)
	logger.Printf("Listening for HTTP requests (webhooks) on %v", listenAddr)
	log.Fatal(http.ListenAndServe(listenAddr, nil))
	logger.Fatal(http.ListenAndServe(listenAddr, nil))
}