Skip to content

Commit

Permalink
Added warnings and better notifications (#15)
Browse files Browse the repository at this point in the history
* Added warnings and better notifications

* Better docs

* Updated demo pic

* better docs
  • Loading branch information
jlucaspains authored Sep 21, 2023
1 parent f3b21b8 commit 9f0dd48
Show file tree
Hide file tree
Showing 15 changed files with 332 additions and 149 deletions.
2 changes: 1 addition & 1 deletion backend/handlers/certCheckHandlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func (h Handlers) CheckStatus(w http.ResponseWriter, r *http.Request) {

log.Println("Received message for URL: " + params.Url)

result, err := shared.CheckCertStatus(params)
result, err := shared.CheckCertStatus(params, h.ExpirationWarningDays)

if err != nil {
h.JSON(w, http.StatusBadRequest, &models.ErrorResult{Errors: []string{err.Error()}})
Expand Down
3 changes: 2 additions & 1 deletion backend/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import (
)

type Handlers struct {
SiteList []string
SiteList []string
ExpirationWarningDays int
}

func (h Handlers) JSON(w http.ResponseWriter, statusCode int, data interface{}) {
Expand Down
146 changes: 146 additions & 0 deletions backend/jobs/CheckCertJob.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package jobs

import (
"fmt"
"log"
"time"

"github.com/adhocore/gronx"
"github.com/jlucaspains/sharp-cert-manager/models"
"github.com/jlucaspains/sharp-cert-manager/shared"
)

type Notifier interface {
Notify(result []CertCheckNotification) error
}

type CheckCertJob struct {
cron string
ticker *time.Ticker
gron gronx.Gronx
siteList []string
running bool
notifier Notifier
level Level
warningDays int
}

type Level int

const (
Info Level = iota
Warning
Error
)

var levels = map[string]Level{
"Info": Info,
"Warning": Warning,
"Error": Error,
}

type CertCheckNotification struct {
Hostname string
IsValid bool
Messages []string
ExpirationWarning bool
}

func (c *CheckCertJob) Init(schedule string, level string, warningDays int, siteList []string, notifier Notifier) error {
c.gron = gronx.New()

if schedule == "" || !c.gron.IsValid(schedule) {
log.Printf("A valid cron schedule is required in the format e.g.: * * * * *")
return fmt.Errorf("a valid cron schedule is required")
}

if notifier == nil {
log.Printf("A valid notifier is required")
return fmt.Errorf("a valid notifier is required")
}

levelValue, ok := levels[level]
if !ok {
levelValue = Warning
}

if warningDays <= 0 {
warningDays = 30
}

c.cron = schedule
c.siteList = siteList
c.ticker = time.NewTicker(time.Minute)
c.notifier = notifier
c.level = levelValue
c.warningDays = warningDays

return nil
}

func (c *CheckCertJob) Start() {
c.running = true
go func() {
for range c.ticker.C {
c.tryExecute()
}
}()
}

func (c *CheckCertJob) Stop() {
c.running = false
c.ticker.Stop()
}

func (c *CheckCertJob) tryExecute() {
due, _ := c.gron.IsDue(c.cron, time.Now().Truncate(time.Minute))

log.Printf("tryExecute job, isDue: %t", due)

if due {
c.execute()
}
}

func (c *CheckCertJob) execute() {
result := []CertCheckNotification{}
for _, url := range c.siteList {
params := models.CertCheckParams{Url: url}
checkStatus, err := shared.CheckCertStatus(params, c.warningDays)

if err != nil {
log.Printf("Error checking cert status: %s", err)
continue
}

log.Printf("Cert status for %s: %t", url, checkStatus.IsValid)

item := c.getNotificationModel(checkStatus)
if c.shouldNotify(item) {
result = append(result, item)
}
}

c.notifier.Notify(result)
}

func (c *CheckCertJob) shouldNotify(model CertCheckNotification) bool {
return c.level == Info || !model.IsValid || (c.level == Warning && model.ExpirationWarning)
}

func (c *CheckCertJob) getNotificationModel(certificate models.CertCheckResult) CertCheckNotification {
result := CertCheckNotification{
Hostname: certificate.Hostname,
IsValid: certificate.IsValid,
ExpirationWarning: certificate.ExpirationWarning,
Messages: certificate.ValidationIssues,
}

if certificate.IsValid && result.ExpirationWarning {
// Calculate CertEndDate days from today
days := int(time.Until(certificate.CertEndDate).Hours() / 24)
result.Messages = append(result.Messages, fmt.Sprintf("Certificate expires in %d days", days))
}

return result
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,50 +3,61 @@ package jobs
import (
"testing"

"github.com/jlucaspains/sharp-cert-manager/models"
"github.com/stretchr/testify/assert"
)

type mockNotifier struct {
executed bool
}

func (m *mockNotifier) Notify(result []models.CertCheckResult) error {
func (m *mockNotifier) Notify(result []CertCheckNotification) error {
m.executed = true
return nil
}

func TestJobInit(t *testing.T) {
checkCertJob := &CheckCertJob{}

checkCertJob.Init("* * * * *", []string{"https://blog.lpains.net"}, &mockNotifier{})
checkCertJob.Init("* * * * *", "", 1, []string{"https://blog.lpains.net"}, &mockNotifier{})

assert.Equal(t, "* * * * *", checkCertJob.cron)
assert.Equal(t, "https://blog.lpains.net", checkCertJob.siteList[0])

checkCertJob.ticker.Stop()
}

func TestJobInitDefaultWarningDays(t *testing.T) {
checkCertJob := &CheckCertJob{}

checkCertJob.Init("* * * * *", "", 0, []string{"https://blog.lpains.net"}, &mockNotifier{})

assert.Equal(t, "* * * * *", checkCertJob.cron)
assert.Equal(t, "https://blog.lpains.net", checkCertJob.siteList[0])
assert.Equal(t, 30, checkCertJob.warningDays)

checkCertJob.ticker.Stop()
}

func TestJobInitBadCron(t *testing.T) {
checkCertJob := &CheckCertJob{}

err := checkCertJob.Init("* * * *", []string{"https://blog.lpains.net"}, &mockNotifier{})
err := checkCertJob.Init("* * * *", "", 0, []string{"https://blog.lpains.net"}, &mockNotifier{})

assert.Equal(t, "a valid cron schedule is required", err.Error())
}

func TestJobInitBadNotifier(t *testing.T) {
checkCertJob := &CheckCertJob{}

err := checkCertJob.Init("* * * * *", []string{"https://blog.lpains.net"}, nil)
err := checkCertJob.Init("* * * * *", "", 0, []string{"https://blog.lpains.net"}, nil)

assert.Equal(t, "a valid notifier is required", err.Error())
}

func TestJobStartStop(t *testing.T) {
checkCertJob := &CheckCertJob{}

err := checkCertJob.Init("* * * * *", []string{"https://blog.lpains.net"}, &mockNotifier{})
err := checkCertJob.Init("* * * * *", "", 0, []string{"https://blog.lpains.net"}, &mockNotifier{})
assert.Nil(t, err)
checkCertJob.Start()
assert.True(t, checkCertJob.running)
Expand All @@ -57,7 +68,7 @@ func TestJobStartStop(t *testing.T) {
func TestTryExecuteNotDue(t *testing.T) {
checkCertJob := &CheckCertJob{}
notifier := &mockNotifier{}
checkCertJob.Init("0 0 1 1 1", []string{"https://blog.lpains.net"}, &mockNotifier{})
checkCertJob.Init("0 0 1 1 1", "", 0, []string{"https://blog.lpains.net"}, &mockNotifier{})
checkCertJob.notifier = notifier
checkCertJob.tryExecute()

Expand All @@ -67,7 +78,17 @@ func TestTryExecuteNotDue(t *testing.T) {
func TestTryExecuteDue(t *testing.T) {
checkCertJob := &CheckCertJob{}
notifier := &mockNotifier{}
checkCertJob.Init("* * * * *", []string{"https://blog.lpains.net"}, &mockNotifier{})
checkCertJob.Init("* * * * *", "", 0, []string{"https://blog.lpains.net"}, &mockNotifier{})
checkCertJob.notifier = notifier
checkCertJob.tryExecute()

assert.True(t, notifier.executed)
}

func TestTryExecuteDueWarning(t *testing.T) {
checkCertJob := &CheckCertJob{}
notifier := &mockNotifier{}
checkCertJob.Init("* * * * *", "", 10000, []string{"https://blog.lpains.net"}, &mockNotifier{})
checkCertJob.notifier = notifier
checkCertJob.tryExecute()

Expand Down
88 changes: 0 additions & 88 deletions backend/jobs/checkCerts.go

This file was deleted.

Loading

0 comments on commit 9f0dd48

Please sign in to comment.