mirror of
https://github.com/garethgeorge/backrest.git
synced 2025-12-12 16:55:39 +00:00
188 lines
5.1 KiB
Go
188 lines
5.1 KiB
Go
package tasks
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"text/template"
|
|
"time"
|
|
|
|
"al.essio.dev/pkg/shellescape"
|
|
v1 "github.com/garethgeorge/backrest/gen/go/v1"
|
|
"github.com/garethgeorge/backrest/pkg/restic"
|
|
)
|
|
|
|
// HookVars is the set of variables that are available to a hook. Some of these are optional.
|
|
// NOTE: names of HookVars may change between versions of backrest. This is not a guaranteed stable API.
|
|
// when names change hooks will require updating.
|
|
type HookVars struct {
|
|
Task string // the name of the task that triggered the hook.
|
|
Event v1.Hook_Condition // the event that triggered the hook.
|
|
Repo *v1.Repo // the v1.Repo that triggered the hook.
|
|
Plan *v1.Plan // the v1.Plan that triggered the hook.
|
|
SnapshotId string // the snapshot ID that triggered the hook.
|
|
SnapshotStats *restic.BackupProgressEntry // the summary of the backup operation.
|
|
CurTime time.Time // the current time as time.Time
|
|
Duration time.Duration // the duration of the operation that triggered the hook.
|
|
Error string // the error that caused the hook to run as a string.
|
|
}
|
|
|
|
func (v HookVars) EventName(cond v1.Hook_Condition) string {
|
|
switch cond {
|
|
case v1.Hook_CONDITION_SNAPSHOT_START:
|
|
return "snapshot start"
|
|
case v1.Hook_CONDITION_SNAPSHOT_END:
|
|
return "snapshot end"
|
|
case v1.Hook_CONDITION_ANY_ERROR:
|
|
return "error"
|
|
case v1.Hook_CONDITION_SNAPSHOT_ERROR:
|
|
return "snapshot error"
|
|
case v1.Hook_CONDITION_SNAPSHOT_WARNING:
|
|
return "snapshot warning"
|
|
case v1.Hook_CONDITION_SNAPSHOT_SUCCESS:
|
|
return "snapshot success"
|
|
case v1.Hook_CONDITION_CHECK_START:
|
|
return "check start"
|
|
case v1.Hook_CONDITION_CHECK_ERROR:
|
|
return "check error"
|
|
case v1.Hook_CONDITION_CHECK_SUCCESS:
|
|
return "check success"
|
|
case v1.Hook_CONDITION_PRUNE_START:
|
|
return "prune start"
|
|
case v1.Hook_CONDITION_PRUNE_ERROR:
|
|
return "prune error"
|
|
case v1.Hook_CONDITION_PRUNE_SUCCESS:
|
|
return "prune success"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
func (v HookVars) FormatTime(t time.Time) string {
|
|
return t.Format(time.RFC3339)
|
|
}
|
|
|
|
func (v HookVars) FormatDuration(d time.Duration) string {
|
|
return d.Truncate(time.Millisecond).String()
|
|
}
|
|
|
|
func (v HookVars) number(n any) int {
|
|
switch n := n.(type) {
|
|
case int:
|
|
return n
|
|
case int32:
|
|
return int(n)
|
|
case int64:
|
|
return int(n)
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func (v HookVars) FormatSizeBytes(val any) string {
|
|
size := v.number(val)
|
|
sizes := []string{"B", "KB", "MB", "GB", "TB", "PB"}
|
|
i := 0
|
|
prev := size
|
|
for size > 1000 {
|
|
size /= 1000
|
|
prev = size
|
|
i++
|
|
}
|
|
return fmt.Sprintf("%d.%03d %s", size, prev, sizes[i])
|
|
}
|
|
|
|
func (v HookVars) IsError(cond v1.Hook_Condition) bool {
|
|
return cond == v1.Hook_CONDITION_ANY_ERROR || cond == v1.Hook_CONDITION_SNAPSHOT_ERROR
|
|
}
|
|
|
|
func (v HookVars) ShellEscape(s string) string {
|
|
return shellescape.Quote(s)
|
|
}
|
|
|
|
func (v HookVars) JsonMarshal(s any) string {
|
|
b, err := json.Marshal(s)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
func (v HookVars) Summary() (string, error) {
|
|
switch v.Event {
|
|
case v1.Hook_CONDITION_SNAPSHOT_START:
|
|
return v.renderTemplate(templateForSnapshotStart)
|
|
case v1.Hook_CONDITION_SNAPSHOT_END, v1.Hook_CONDITION_SNAPSHOT_WARNING, v1.Hook_CONDITION_SNAPSHOT_SUCCESS:
|
|
return v.renderTemplate(templateForSnapshotEnd)
|
|
default:
|
|
return v.renderTemplate(templateDefault)
|
|
}
|
|
}
|
|
|
|
func (v HookVars) renderTemplate(templ string) (string, error) {
|
|
t, err := template.New("t").Parse(templ)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
err = t.Execute(&buf, v)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return buf.String(), nil
|
|
}
|
|
|
|
var templateDefault = `
|
|
{{ if .Error -}}
|
|
Backrest Error
|
|
Task: {{ .Task }} at {{ .FormatTime .CurTime }}
|
|
Event: {{ .EventName .Event }}
|
|
Repo: {{ .Repo.Id }}
|
|
Error: {{ .Error }}
|
|
{{ else -}}
|
|
Backrest Notification
|
|
Task: {{ .Task }} at {{ .FormatTime .CurTime }}
|
|
Event: {{ .EventName .Event }}
|
|
{{ end }}
|
|
`
|
|
|
|
var templateForSnapshotEnd = `
|
|
Backrest Snapshot Notification
|
|
Task: {{ .Task }} at {{ .FormatTime .CurTime }}
|
|
Event: {{ .EventName .Event }}
|
|
Snapshot: {{ .SnapshotId }}
|
|
{{ if .Error -}}
|
|
Error: {{ .Error }}
|
|
{{ else -}}
|
|
{{ if .SnapshotStats -}}
|
|
|
|
Overview:
|
|
- Data added: {{ .FormatSizeBytes .SnapshotStats.DataAdded }}
|
|
- Total files processed: {{ .SnapshotStats.TotalFilesProcessed }}
|
|
- Total bytes processed: {{ .FormatSizeBytes .SnapshotStats.TotalBytesProcessed }}
|
|
|
|
Backup Statistics:
|
|
- Files new: {{ .SnapshotStats.FilesNew }}
|
|
- Files changed: {{ .SnapshotStats.FilesChanged }}
|
|
- Files unmodified: {{ .SnapshotStats.FilesUnmodified }}
|
|
- Dirs new: {{ .SnapshotStats.DirsNew }}
|
|
- Dirs changed: {{ .SnapshotStats.DirsChanged }}
|
|
- Dirs unmodified: {{ .SnapshotStats.DirsUnmodified }}
|
|
- Data blobs: {{ .SnapshotStats.DataBlobs }}
|
|
- Tree blobs: {{ .SnapshotStats.TreeBlobs }}
|
|
- Total duration: {{ .SnapshotStats.TotalDuration }}s
|
|
{{ end }}
|
|
{{ end }}`
|
|
|
|
var templateForSnapshotStart = `
|
|
Backrest Notification for Snapshot Start
|
|
Task: "{{ .Task }}" at {{ .FormatTime .CurTime }}
|
|
Event: {{ .EventName .Event }}
|
|
Repo: {{ .Repo.Id }}
|
|
Plan: {{ .Plan.Id }}
|
|
Paths:
|
|
{{ range .Plan.Paths -}}
|
|
- {{ . }}
|
|
{{ end }}`
|