sermoni

"Service monitor" / cronjob status service
Log | Files | Refs

commit a55dd0f1080ed58a0a2dea4a11bed354ed27c424
parent 3d6bc0e631ba154d31263043b77d6d7b65d6b2d1
Author: Vetle Haflan <vetle@haflan.dev>
Date:   Mon, 13 Apr 2020 14:58:09 +0200

More work on requests and request handlers

Including reportEvent and getEvents MVP and a bash script for sending
report requests with curl (pretty much the entire client application)

Diffstat:
Minternal/events/events.go | 11++---------
Minternal/events/events_test.go | 10++++------
Minternal/http/events.go | 42++++++++++++++++++++++++++++++++++++++----
Minternal/http/http.go | 4+++-
Minternal/services/services_test.go | 7+++----
Areport.sh | 22++++++++++++++++++++++
Mui/src/requests.js | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
7 files changed, 148 insertions(+), 53 deletions(-)

diff --git a/internal/events/events.go b/internal/events/events.go @@ -5,7 +5,6 @@ import ( "fmt" "log" "sermoni/internal/database" - "sermoni/internal/services" "strconv" "go.etcd.io/bbolt" @@ -69,14 +68,9 @@ func Delete(idInt uint64) error { }) } -// Add a new service event if the token matches any services in the database -// The event.Service will be set to the service ID automatically, given a valid token -func Add(serviceToken string, event *Event) error { +// Add persists a new event to database after generating an ID for it +func Add(event *Event) error { db := database.GetDB() - service := services.GetByToken(serviceToken) - if service == nil { - return errors.New("no service found for the given token") - } return db.Update(func(tx *bbolt.Tx) error { b := tx.Bucket(database.BucketKeyEvents) if b == nil { @@ -90,7 +84,6 @@ func Add(serviceToken string, event *Event) error { } id := []byte(strconv.FormatUint(idInt, 10)) event.ID = idInt - event.Service = service.ID // Create the event bucket and fill it with data from event eb, err := b.CreateBucket(id) diff --git a/internal/events/events_test.go b/internal/events/events_test.go @@ -3,6 +3,7 @@ package events import ( "fmt" "os" + "sermoni/internal/config" "sermoni/internal/database" "sermoni/internal/services" "testing" @@ -79,12 +80,9 @@ func TestMain(m *testing.M) { // (Re)create the test database testDB := "test.db" os.Remove(testDB) - var err error - if err = database.Init(testDB); err != nil { - print("Couldn't initialize test database") - os.Exit(1) - } - err = services.Add(serviceToken, &services.Service{ + database.Open(testDB) + config.InitConfig() + err := services.Add(serviceToken, &services.Service{ Name: "test @ dev-laptop", Description: "Service used for testing only", }) diff --git a/internal/http/events.go b/internal/http/events.go @@ -2,10 +2,16 @@ package http import ( "encoding/json" + "fmt" + "io/ioutil" + "log" "net/http" "sermoni/internal/events" + "sermoni/internal/services" ) +const headerServiceToken = "Service-Token" + func getEvents(w http.ResponseWriter, r *http.Request) { // Create a mapping from service id to name /* Eventually? @@ -17,8 +23,8 @@ func getEvents(w http.ResponseWriter, r *http.Request) { */ events := events.GetAll() - json.Marshal(events) - return + b, _ := json.Marshal(events) + w.Write(b) } // TODO: This is still a placeholder! @@ -29,9 +35,37 @@ func deleteEvent(w http.ResponseWriter, r *http.Request) { err := events.Delete(id) fmt.Println(err) */ - return } func reportEvent(w http.ResponseWriter, r *http.Request) { - return + tokens := r.Header[headerServiceToken] + if len(tokens) == 0 { + w.WriteHeader(http.StatusUnauthorized) + msg := fmt.Sprintf("%v: No service token given\n", http.StatusUnauthorized) + w.Write([]byte(msg)) + return + } + service := services.GetByToken(tokens[0]) + if service == nil { + msg := fmt.Sprintf("%v: No service for the given token\n", http.StatusUnauthorized) + w.Write([]byte(msg)) + return + } + content, err := ioutil.ReadAll(r.Body) + check(err) + // TODO: This should deffo not panic on error! + event := new(events.Event) + err = json.Unmarshal(content, event) + check(err) + event.Service = service.ID + err = events.Add(event) + check(err) + log.Printf("New event registered, id = %v\n", event.ID) } + +// For later reference: +// Instead of anonymous structs for each JSON object to be written, +// a simple map can be used instead, something like (untested!): +// b, _ := json.Marshal(&map[string]string{ +// "message": http.StatusUnauthorized + ": No valid token" +// }) diff --git a/internal/http/http.go b/internal/http/http.go @@ -33,8 +33,10 @@ func StartServer(port int) { router.Handle("/events", auth(getEvents)).Methods(http.MethodGet) router.Handle("/events/{id:[0-9]+}", auth(deleteEvent)).Methods(http.MethodDelete) + // POSTS to /events is how services should report events to the monitor + // This should not be accessible from the website + router.HandleFunc("/events", reportEvent).Methods(http.MethodPost) - router.HandleFunc("/report", reportEvent).Methods(http.MethodPost) http.Handle("/", router) log.Fatal(http.ListenAndServe(fmt.Sprintf(":%v", port), nil)) } diff --git a/internal/services/services_test.go b/internal/services/services_test.go @@ -3,6 +3,7 @@ package services import ( "fmt" "os" + "sermoni/internal/config" "sermoni/internal/database" "strconv" "testing" @@ -134,10 +135,8 @@ func TestMain(m *testing.M) { // (Re)create the test database testDB := "test.db" os.Remove(testDB) - if err := database.Init(testDB); err != nil { - print("Couldn't initialize test database") - os.Exit(1) - } + database.Open(testDB) + config.InitConfig() defer database.Close() os.Exit(m.Run()) } diff --git a/report.sh b/report.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# Script for reporting events to sermoni + +sermoni=http://localhost:8080 + +token=$1 +status=$2 +title=$3 +details=$4 +timestamp="$(date +%s)" + +read -d '' JSONDATA << EOF +{ + \"status\": \"$status\", + \"timestamp\": $timestamp, + \"title\": \"$title\", + \"details\": \"$details\" +} +EOF + +curl -H "Service-Token: $token" -d "$JSONDATA" $sermoni/events diff --git a/ui/src/requests.js b/ui/src/requests.js @@ -1,4 +1,80 @@ + +export default { + init, + login, + getServices, + postService, + deleteService, +} + +/***** Login and authentication *****/ + +var csrfToken; + +function init(successHandler, errorHandler) { + request({ + url: "/init", + success: (data) => { + csrfToken = data.csrftoken; + if (successHandler) { + successHandler(data); + } + }, + error: errorHandler + }); +} + +function login(passphrase, successHandler, errorHandler) { + request({ + url: "/login", + method: "POST", + data: { passphrase: passphrase }, + success: successHandler, + error: errorHandler + }); +} + +function logout(passphrase, successHandler, errorHandler) { + request({ + url: "/logout", + method: "POST", + success: successHandler, + error: errorHandler + }); +} + + +/***** Service management *****/ + +function getServices() { + +} + +function postService(service) { + +} + +function deleteService(id) { + +} + +/***** Event management *****/ + +// (TODO) + +function getEvents() { + +} + +function deleteEvent(id) { + +} + + + +/***** Request utils *****/ + /** * Create URL parameters from a JSON object */ @@ -17,8 +93,6 @@ function params(parameterObject) { return parameters.toString(); } -var csrfToken; - // Send a request to server. Takes request description, jsonRequest, // on the following format // { @@ -66,30 +140,3 @@ function request(jsonRequest) { } } -function init(successHandler, errorHandler) { - request({ - url: "/init", - success: (data) => { - csrfToken = data.csrftoken; - if (successHandler) { - successHandler(data); - } - }, - error: errorHandler - }); -} - -function login(passphrase, successHandler, errorHandler) { - request({ - url: "/login", - method: "POST", - data: { passphrase: passphrase }, - success: successHandler, - error: errorHandler - }); -} - -export default { - init, - login, -}