Skip to content

Commit

Permalink
Merge pull request #20 from RobinHeidenis/feat/add-cron-scheduling
Browse files Browse the repository at this point in the history
feat: 🎸 add periodic notification schedule parameter
  • Loading branch information
RobinHeidenis authored Dec 6, 2023
2 parents f580eac + 99760fd commit eb74529
Show file tree
Hide file tree
Showing 10 changed files with 75 additions and 49 deletions.
7 changes: 5 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
name: Build
run-name: Build heimdall for ${{ github.actor }} on ${{ github.base.ref }}
run-name: Build Heimdall
on:
push:
branches-ignore:
- main
workflow_call:
paths:
- 'cmd/**'
- 'internal/**'
- 'pkg/**'
jobs:
build:
name: Build
Expand Down
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,16 +154,17 @@ sudo ln -s ~/.docker/desktop/docker.sock /var/run/docker.sock
## Customisation
Heimdall can be customised by using flags or environment variables. Either works, but flags take precedence over environment variables. Short flags can be used in place of the longer flags to save some typing.

| Long flag | Short flag | Environment Variable | Default | Required | Explanation |
|---------------------------|------------|----------------------------------|-----------|--------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `--periodic-notification` | `-n` | `HEIMDALL_PERIODIC_NOTIFICATION` | `false` | No | Enable periodic notifications |
| `--notification-interval` | `-i` | `HEIMDALL_NOTIFICATION_INTERVAL` | `60` | Only if periodic notifications are enabled and you want a different value than default | How often (in minutes) periodic notifications should be sent |
| `--all-containers` | `-a` | `HEIMDALL_ALL_CONTAINERS` | `false` | Only if periodic notifications are enabled and you want periodic notifications on all containers | Enable periodic notification reporting on all containers, including stopped ones |
| `--retry` | `-r` | `HEIMDALL_RETRY` | `10` | No | How long Heimdall should sleep before retrying in case the Docker event stream ends unexpectedly |
| `--provider` | `-p` | `HEIMDALL_PROVIDER` | `discord` | No | What notification provider should be used. Possible values: `discord` |
| `--webhook-url` | `-w` | `HEIMDALL_WEBHOOK_URL` | - | Yes | What URL Heimdall should use to send notifications to |
| `--hostname` | `-h` | `HEIMDALL_HOSTNAME` | - | No, but recommended in case you run Heimdall on multiple devices | The name of the host Heimdall is running on. This will cause Heimdall to send status updates with the device name in them: `Docker Container Status Update (Hostname)` |
| `--debug` | `-d` | `HEIMDALL_DEBUG` | `false` | No | Enable extra debug messages |
| Long flag | Short flag | Environment Variable | Default | Required | Explanation |
|---------------------------|------------|----------------------------------|-----------|--------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `--periodic-notification` | `-n` | `HEIMDALL_PERIODIC_NOTIFICATION` | `false` | No | Enable periodic notifications |
| `--notification-schedule` | `-s` | `HEIMDALL_NOTIFICATION_SCHEDULE` | `@hourly` | Only if periodic notifications are enabled and you want a schedule | The schedule for periodic notifications. Supports cron expressions (e.g. `0 9 * * 1`), @interval expressions (e.g. `@hourly`, `@montly`), and @every expressions (e.g. `@every 1h30m10s`). [Learn more](https://pkg.go.dev/github.com/robfig/cron#hdr-CRON_Expression_Format) |
| `--all-containers` | `-a` | `HEIMDALL_ALL_CONTAINERS` | `false` | Only if periodic notifications are enabled and you want periodic notifications on all containers | Enable periodic notification reporting on all containers, including stopped ones |
| `--retry` | `-r` | `HEIMDALL_RETRY` | `10` | No | How long Heimdall should sleep before retrying in case the Docker event stream ends unexpectedly |
| `--provider` | `-p` | `HEIMDALL_PROVIDER` | `discord` | No | What notification provider should be used. Possible values: `discord` |
| `--webhook-url` | `-w` | `HEIMDALL_WEBHOOK_URL` | - | Yes | What URL Heimdall should use to send notifications to |
| `--hostname` | `-h` | `HEIMDALL_HOSTNAME` | - | No, but recommended in case you run Heimdall on multiple devices | The name of the host Heimdall is running on. This will cause Heimdall to send status updates with the device name in them: `Docker Container Status Update (Hostname)` |
| `--debug` | `-d` | `HEIMDALL_DEBUG` | `false` | No | Enable extra debug messages |
| `--notification-interval` | `-i` | `HEIMDALL_NOTIFICATION_INTERVAL` | `60` | No. This parameter is DEPRECATED. use --notification-schedule instead | How often (in minutes) periodic notifications should be sent |


## Technologies
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ require (
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/robfig/cron/v3 v3.0.0 // indirect
github.com/stretchr/testify v1.8.4 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.19.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E=
github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
Expand Down
4 changes: 3 additions & 1 deletion pkg/heimdall/IProvider.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package heimdall

import "time"

type IProvider interface {
SendPeriodicContainerStatusUpdate(updateTable string)
SendPeriodicContainerStatusUpdate(updateTable string, nextInvocation time.Time)
SendContainerEventNotification(event ContainerEvent)
}
5 changes: 3 additions & 2 deletions pkg/heimdall/discord.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package heimdall
import (
"fmt"
"github.com/gtuk/discordwebhook"
"time"
)

type DiscordProvider struct {
Expand Down Expand Up @@ -32,11 +33,11 @@ func getHostnameAdditionText() string {
return hostnameAddition
}

func (p DiscordProvider) SendPeriodicContainerStatusUpdate(updateTable string) {
func (p DiscordProvider) SendPeriodicContainerStatusUpdate(updateTable string, nextInvocation time.Time) {
authorName := "Docker Container Status" + getHostnameAdditionText()
authorIconUrl := "https://www.docker.com/wp-content/uploads/2023/04/cropped-Docker-favicon-32x32.png"
title := "Periodic status update"
description := fmt.Sprintf("```\n%s```", updateTable)
description := fmt.Sprintf("```\n%s```\nNext notification at <t:%d:f>, <t:%d:R>", updateTable, nextInvocation.Unix(), nextInvocation.Unix())

author := discordwebhook.Author{
Name: &authorName,
Expand Down
24 changes: 21 additions & 3 deletions pkg/heimdall/heimdall.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/docker/docker/client"
"github.com/peterbourgon/ff/v4"
"github.com/peterbourgon/ff/v4/ffhelp"
"github.com/robfig/cron/v3"
"io"
"os"
"strconv"
Expand All @@ -17,6 +18,7 @@ var DebugMode bool

var PeriodicNotifications bool
var PeriodicNotificationInterval int
var PeriodicNotificationSchedule string
var AllContainers bool

var Retry int
Expand All @@ -33,6 +35,7 @@ func init() {
fs.BoolVar(&DebugMode, 'd', "debug", "Enable debug mode")
fs.BoolVar(&PeriodicNotifications, 'n', "periodic-notification", "Enable periodic notifications about the state of containers")
fs.IntVar(&PeriodicNotificationInterval, 'i', "notification-interval", 60, "Interval in minutes between periodic notifications")
fs.StringVar(&PeriodicNotificationSchedule, 's', "notification-schedule", "@hourly", "Cron schedule for periodic notifications")
fs.BoolVar(&AllContainers, 'a', "all-containers", "Enable periodic notifications for all containers, including stopped ones")
fs.IntVar(&Retry, 'r', "retry", 10, "Retry in seconds when the docker event stream ends")
fs.StringVar(&Hostname, 'h', "hostname", "", "Hostname to use in notifications. Useful when running multiple instances of Heimdall")
Expand All @@ -51,6 +54,12 @@ func init() {
if AllContainers {
Info("All containers option specified, sending notifications about all containers, including stopped ones")
}
if PeriodicNotificationInterval != 60 {
Warn("The periodic notification interval parameter is deprecated. Use --notification-schedule instead")
if PeriodicNotificationSchedule != "" {
Warn("--notification-schedule is specified. This overwrites the periodic notification interval parameter")
}
}
} else {
if PeriodicNotificationInterval != 60 {
Warn("Periodic notifications disabled. Ignoring notification interval.")
Expand Down Expand Up @@ -89,9 +98,19 @@ func Start() {
go EventRoutine(cli, ctx, eventChannel, logChannel, errorChannel)

if PeriodicNotifications {
ticker := time.NewTicker(time.Duration(PeriodicNotificationInterval) * time.Minute)
c := cron.New()

go PeriodicCheckRoutine(*ticker, cli, ctx)
var interval = "@every " + strconv.Itoa(PeriodicNotificationInterval) + "m"
if PeriodicNotificationSchedule != "" {
interval = PeriodicNotificationSchedule
}

id, err := c.AddFunc(interval, func() { PeriodicCheckRoutine(cli, ctx, c) })
if err != nil {
Error(err.Error())
}
c.Start()
c.Entry(id).Job.Run()
}

go StartHealthCheckServer()
Expand Down Expand Up @@ -122,5 +141,4 @@ func Start() {
Debug(logLine)
}
}

}
60 changes: 29 additions & 31 deletions pkg/heimdall/periodCheckRoutine.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,49 +5,47 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/robfig/cron/v3"
"slices"
"sort"
"strings"
"time"
)

func PeriodicCheckRoutine(ticker time.Ticker, cli *client.Client, ctx context.Context) {
for {
<-ticker.C

containerList, err := cli.ContainerList(ctx, types.ContainerListOptions{
All: AllContainers,
})
if err != nil {
panic(err)
}

t := table.NewWriter()
t.AppendHeader(table.Row{"#", "Container", "Status"})
t.SetStyle(table.StyleRounded)
func PeriodicCheckRoutine(cli *client.Client, ctx context.Context, cron *cron.Cron) {
containerList, err := cli.ContainerList(ctx, types.ContainerListOptions{
All: AllContainers,
})
if err != nil {
panic(err)
}

sort.Slice(containerList, func(i, j int) bool {
return strings.Compare(containerList[i].State, containerList[j].State) < 0
})
t := table.NewWriter()
t.AppendHeader(table.Row{"#", "Container", "Status"})
t.SetStyle(table.StyleRounded)

slices.Reverse(containerList)
sort.Slice(containerList, func(i, j int) bool {
return strings.Compare(containerList[i].State, containerList[j].State) < 0
})

for i, listContainer := range containerList {
inspectedContainer, err := cli.ContainerInspect(ctx, listContainer.ID)
if err != nil {
panic(err)
}
slices.Reverse(containerList)

var health = inspectedContainer.State.Status
for i, listContainer := range containerList {
inspectedContainer, err := cli.ContainerInspect(ctx, listContainer.ID)
if err != nil {
panic(err)
}

if inspectedContainer.State.Health != nil && health != "exited" {
health = inspectedContainer.State.Health.Status
}
var health = inspectedContainer.State.Status

t.AppendRow(table.Row{i, strings.Split(inspectedContainer.Name, "/")[1], HealthStatus(health)})
t.AppendSeparator()
if inspectedContainer.State.Health != nil && health != "exited" {
health = inspectedContainer.State.Health.Status
}

Provider.SendPeriodicContainerStatusUpdate(t.Render())
t.AppendRow(table.Row{i, strings.Split(inspectedContainer.Name, "/")[1], HealthStatus(health)})
t.AppendSeparator()
}

nextInvocation := cron.Entries()[0].Next

Provider.SendPeriodicContainerStatusUpdate(t.Render(), nextInvocation)
}
Binary file modified public/container-list-all.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/container-list.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit eb74529

Please sign in to comment.