mirror of
https://github.com/containrrr/watchtower.git
synced 2025-09-22 05:40:50 +02:00
restructure and fix empty report
This commit is contained in:
parent
0776077ac5
commit
3bcc86cd8f
12 changed files with 359 additions and 343 deletions
|
@ -89,10 +89,13 @@
|
||||||
console.log("Levels: %o", levels);
|
console.log("Levels: %o", levels);
|
||||||
const output = WATCHTOWER.tplprev(input, states, levels);
|
const output = WATCHTOWER.tplprev(input, states, levels);
|
||||||
console.log('Output: \n%o', output);
|
console.log('Output: \n%o', output);
|
||||||
document.querySelector('#result').innerText = output;
|
if (output.length) {
|
||||||
|
document.querySelector('#result').innerText = output;
|
||||||
|
} else {
|
||||||
|
document.querySelector('#result').innerHTML = '<i>empty (would not be sent as a notification)</i>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const formSubmitted = (e) => {
|
const formSubmitted = (e) => {
|
||||||
console.log("Event: %o", e);
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
updatePreview();
|
updatePreview();
|
||||||
}
|
}
|
||||||
|
@ -115,7 +118,7 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<form id="tplprev" onchange="inputUpdated()" onsubmit="formSubmitted(event)" style="margin: 0;display: flex; flex-direction: column; row-gap: 1rem; box-sizing: border-box; position: relative; margin-right: -13.3rem">
|
<form id="tplprev" onchange="updatePreview()" onsubmit="formSubmitted(event)" style="margin: 0;display: flex; flex-direction: column; row-gap: 1rem; box-sizing: border-box; position: relative; margin-right: -13.3rem">
|
||||||
<pre id="loading" style="position: absolute; inset: 0; display: flex; padding: 1rem; box-sizing: border-box; background: var(--md-code-bg-color); margin-top: 0">loading wasm...</pre>
|
<pre id="loading" style="position: absolute; inset: 0; display: flex; padding: 1rem; box-sizing: border-box; background: var(--md-code-bg-color); margin-top: 0">loading wasm...</pre>
|
||||||
|
|
||||||
|
|
||||||
|
|
143
pkg/notifications/preview/data/data.go
Normal file
143
pkg/notifications/preview/data/data.go
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"math/rand"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containrrr/watchtower/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type previewData struct {
|
||||||
|
rand *rand.Rand
|
||||||
|
lastTime time.Time
|
||||||
|
report *report
|
||||||
|
containerCount int
|
||||||
|
Entries []*logEntry
|
||||||
|
StaticData staticData
|
||||||
|
}
|
||||||
|
|
||||||
|
type staticData struct {
|
||||||
|
Title string
|
||||||
|
Host string
|
||||||
|
}
|
||||||
|
|
||||||
|
// New initializes a new preview data struct
|
||||||
|
func New() *previewData {
|
||||||
|
return &previewData{
|
||||||
|
rand: rand.New(rand.NewSource(1)),
|
||||||
|
lastTime: time.Now().Add(-30 * time.Minute),
|
||||||
|
report: nil,
|
||||||
|
containerCount: 0,
|
||||||
|
Entries: []*logEntry{},
|
||||||
|
StaticData: staticData{
|
||||||
|
Title: "Title",
|
||||||
|
Host: "Host",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFromState adds a container status entry to the report with the given state
|
||||||
|
func (pb *previewData) AddFromState(state State) {
|
||||||
|
cid := types.ContainerID(pb.generateID())
|
||||||
|
old := types.ImageID(pb.generateID())
|
||||||
|
new := types.ImageID(pb.generateID())
|
||||||
|
name := pb.generateName()
|
||||||
|
image := pb.generateImageName(name)
|
||||||
|
var err error
|
||||||
|
if state == FailedState {
|
||||||
|
err = errors.New(pb.randomEntry(errorMessages))
|
||||||
|
} else if state == SkippedState {
|
||||||
|
err = errors.New(pb.randomEntry(skippedMessages))
|
||||||
|
}
|
||||||
|
pb.addContainer(containerStatus{
|
||||||
|
containerID: cid,
|
||||||
|
oldImage: old,
|
||||||
|
newImage: new,
|
||||||
|
containerName: name,
|
||||||
|
imageName: image,
|
||||||
|
error: err,
|
||||||
|
state: state,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pb *previewData) addContainer(c containerStatus) {
|
||||||
|
if pb.report == nil {
|
||||||
|
pb.report = &report{}
|
||||||
|
}
|
||||||
|
switch c.state {
|
||||||
|
case ScannedState:
|
||||||
|
pb.report.scanned = append(pb.report.scanned, &c)
|
||||||
|
case UpdatedState:
|
||||||
|
pb.report.updated = append(pb.report.updated, &c)
|
||||||
|
case FailedState:
|
||||||
|
pb.report.failed = append(pb.report.failed, &c)
|
||||||
|
case SkippedState:
|
||||||
|
pb.report.skipped = append(pb.report.skipped, &c)
|
||||||
|
case StaleState:
|
||||||
|
pb.report.stale = append(pb.report.stale, &c)
|
||||||
|
case FreshState:
|
||||||
|
pb.report.fresh = append(pb.report.fresh, &c)
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pb.containerCount += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddLogEntry adds a preview log entry of the given level
|
||||||
|
func (pd *previewData) AddLogEntry(level LogLevel) {
|
||||||
|
var msg string
|
||||||
|
switch level {
|
||||||
|
case FatalLevel:
|
||||||
|
fallthrough
|
||||||
|
case ErrorLevel:
|
||||||
|
fallthrough
|
||||||
|
case WarnLevel:
|
||||||
|
msg = pd.randomEntry(logErrors)
|
||||||
|
default:
|
||||||
|
msg = pd.randomEntry(logMessages)
|
||||||
|
}
|
||||||
|
pd.Entries = append(pd.Entries, &logEntry{
|
||||||
|
Message: msg,
|
||||||
|
Data: map[string]any{},
|
||||||
|
Time: pd.generateTime(),
|
||||||
|
Level: level,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report returns a preview report
|
||||||
|
func (pb *previewData) Report() types.Report {
|
||||||
|
return pb.report
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pb *previewData) generateID() string {
|
||||||
|
buf := make([]byte, 32)
|
||||||
|
_, _ = pb.rand.Read(buf)
|
||||||
|
return hex.EncodeToString(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pb *previewData) generateTime() time.Time {
|
||||||
|
pb.lastTime = pb.lastTime.Add(time.Duration(pb.rand.Intn(30)) * time.Second)
|
||||||
|
return pb.lastTime
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pb *previewData) randomEntry(arr []string) string {
|
||||||
|
return arr[pb.rand.Intn(len(arr))]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pb *previewData) generateName() string {
|
||||||
|
index := pb.containerCount
|
||||||
|
if index <= len(containerNames) {
|
||||||
|
return "/" + containerNames[index]
|
||||||
|
}
|
||||||
|
suffix := index / len(containerNames)
|
||||||
|
index %= len(containerNames)
|
||||||
|
return "/" + containerNames[index] + strconv.FormatInt(int64(suffix), 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pb *previewData) generateImageName(name string) string {
|
||||||
|
index := pb.containerCount % len(organizationNames)
|
||||||
|
return organizationNames[index] + name + ":latest"
|
||||||
|
}
|
|
@ -1,29 +1,17 @@
|
||||||
package main
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containrrr/watchtower/pkg/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Data struct {
|
type logEntry struct {
|
||||||
Entries []*LogEntry
|
|
||||||
StaticData StaticData
|
|
||||||
Report types.Report
|
|
||||||
}
|
|
||||||
|
|
||||||
type StaticData struct {
|
|
||||||
Title string
|
|
||||||
Host string
|
|
||||||
}
|
|
||||||
|
|
||||||
type LogEntry struct {
|
|
||||||
Message string
|
Message string
|
||||||
Data map[string]any
|
Data map[string]any
|
||||||
Time time.Time
|
Time time.Time
|
||||||
Level LogLevel
|
Level LogLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogLevel is the analog of logrus.Level
|
||||||
type LogLevel string
|
type LogLevel string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -36,6 +24,7 @@ const (
|
||||||
PanicLevel LogLevel = "panic"
|
PanicLevel LogLevel = "panic"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// LevelsFromString parses a string of level characters and returns a slice of the corresponding log levels
|
||||||
func LevelsFromString(str string) []LogLevel {
|
func LevelsFromString(str string) []LogLevel {
|
||||||
levels := make([]LogLevel, 0, len(str))
|
levels := make([]LogLevel, 0, len(str))
|
||||||
for _, c := range str {
|
for _, c := range str {
|
||||||
|
@ -61,6 +50,7 @@ func LevelsFromString(str string) []LogLevel {
|
||||||
return levels
|
return levels
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String returns the log level as a string
|
||||||
func (level LogLevel) String() string {
|
func (level LogLevel) String() string {
|
||||||
return string(level)
|
return string(level)
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package data
|
||||||
|
|
||||||
var containerNames = []string{
|
var containerNames = []string{
|
||||||
"cyberscribe",
|
"cyberscribe",
|
||||||
|
@ -43,7 +43,7 @@ var containerNames = []string{
|
||||||
"innoscan",
|
"innoscan",
|
||||||
}
|
}
|
||||||
|
|
||||||
var companyNames = []string{
|
var organizationNames = []string{
|
||||||
"techwave",
|
"techwave",
|
||||||
"codecrafters",
|
"codecrafters",
|
||||||
"innotechlabs",
|
"innotechlabs",
|
110
pkg/notifications/preview/data/report.go
Normal file
110
pkg/notifications/preview/data/report.go
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/containrrr/watchtower/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// State is the outcome of a container in a session report
|
||||||
|
type State string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ScannedState State = "scanned"
|
||||||
|
UpdatedState State = "updated"
|
||||||
|
FailedState State = "failed"
|
||||||
|
SkippedState State = "skipped"
|
||||||
|
StaleState State = "stale"
|
||||||
|
FreshState State = "fresh"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StatesFromString parses a string of state characters and returns a slice of the corresponding report states
|
||||||
|
func StatesFromString(str string) []State {
|
||||||
|
states := make([]State, 0, len(str))
|
||||||
|
for _, c := range str {
|
||||||
|
switch c {
|
||||||
|
case 'c':
|
||||||
|
states = append(states, ScannedState)
|
||||||
|
case 'u':
|
||||||
|
states = append(states, UpdatedState)
|
||||||
|
case 'e':
|
||||||
|
states = append(states, FailedState)
|
||||||
|
case 'k':
|
||||||
|
states = append(states, SkippedState)
|
||||||
|
case 't':
|
||||||
|
states = append(states, StaleState)
|
||||||
|
case 'f':
|
||||||
|
states = append(states, FreshState)
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return states
|
||||||
|
}
|
||||||
|
|
||||||
|
type report struct {
|
||||||
|
scanned []types.ContainerReport
|
||||||
|
updated []types.ContainerReport
|
||||||
|
failed []types.ContainerReport
|
||||||
|
skipped []types.ContainerReport
|
||||||
|
stale []types.ContainerReport
|
||||||
|
fresh []types.ContainerReport
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *report) Scanned() []types.ContainerReport {
|
||||||
|
return r.scanned
|
||||||
|
}
|
||||||
|
func (r *report) Updated() []types.ContainerReport {
|
||||||
|
return r.updated
|
||||||
|
}
|
||||||
|
func (r *report) Failed() []types.ContainerReport {
|
||||||
|
return r.failed
|
||||||
|
}
|
||||||
|
func (r *report) Skipped() []types.ContainerReport {
|
||||||
|
return r.skipped
|
||||||
|
}
|
||||||
|
func (r *report) Stale() []types.ContainerReport {
|
||||||
|
return r.stale
|
||||||
|
}
|
||||||
|
func (r *report) Fresh() []types.ContainerReport {
|
||||||
|
return r.fresh
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *report) All() []types.ContainerReport {
|
||||||
|
allLen := len(r.scanned) + len(r.updated) + len(r.failed) + len(r.skipped) + len(r.stale) + len(r.fresh)
|
||||||
|
all := make([]types.ContainerReport, 0, allLen)
|
||||||
|
|
||||||
|
presentIds := map[types.ContainerID][]string{}
|
||||||
|
|
||||||
|
appendUnique := func(reports []types.ContainerReport) {
|
||||||
|
for _, cr := range reports {
|
||||||
|
if _, found := presentIds[cr.ID()]; found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
all = append(all, cr)
|
||||||
|
presentIds[cr.ID()] = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
appendUnique(r.updated)
|
||||||
|
appendUnique(r.failed)
|
||||||
|
appendUnique(r.skipped)
|
||||||
|
appendUnique(r.stale)
|
||||||
|
appendUnique(r.fresh)
|
||||||
|
appendUnique(r.scanned)
|
||||||
|
|
||||||
|
sort.Sort(sortableContainers(all))
|
||||||
|
|
||||||
|
return all
|
||||||
|
}
|
||||||
|
|
||||||
|
type sortableContainers []types.ContainerReport
|
||||||
|
|
||||||
|
// Len implements sort.Interface.Len
|
||||||
|
func (s sortableContainers) Len() int { return len(s) }
|
||||||
|
|
||||||
|
// Less implements sort.Interface.Less
|
||||||
|
func (s sortableContainers) Less(i, j int) bool { return s[i].ID() < s[j].ID() }
|
||||||
|
|
||||||
|
// Swap implements sort.Interface.Swap
|
||||||
|
func (s sortableContainers) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
44
pkg/notifications/preview/data/status.go
Normal file
44
pkg/notifications/preview/data/status.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package data
|
||||||
|
|
||||||
|
import wt "github.com/containrrr/watchtower/pkg/types"
|
||||||
|
|
||||||
|
type containerStatus struct {
|
||||||
|
containerID wt.ContainerID
|
||||||
|
oldImage wt.ImageID
|
||||||
|
newImage wt.ImageID
|
||||||
|
containerName string
|
||||||
|
imageName string
|
||||||
|
error
|
||||||
|
state State
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *containerStatus) ID() wt.ContainerID {
|
||||||
|
return u.containerID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *containerStatus) Name() string {
|
||||||
|
return u.containerName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *containerStatus) CurrentImageID() wt.ImageID {
|
||||||
|
return u.oldImage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *containerStatus) LatestImageID() wt.ImageID {
|
||||||
|
return u.newImage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *containerStatus) ImageName() string {
|
||||||
|
return u.imageName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *containerStatus) Error() string {
|
||||||
|
if u.error == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return u.error.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *containerStatus) State() string {
|
||||||
|
return string(u.state)
|
||||||
|
}
|
36
pkg/notifications/preview/tplprev.go
Normal file
36
pkg/notifications/preview/tplprev.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package preview
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/containrrr/watchtower/pkg/notifications/preview/data"
|
||||||
|
"github.com/containrrr/watchtower/pkg/notifications/templates"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Render(input string, states []data.State, loglevels []data.LogLevel) (string, error) {
|
||||||
|
|
||||||
|
data := data.New()
|
||||||
|
|
||||||
|
tpl, err := template.New("").Funcs(templates.Funcs).Parse(input)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to parse template: %e", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, state := range states {
|
||||||
|
data.AddFromState(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, level := range loglevels {
|
||||||
|
data.AddLogEntry(level)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf strings.Builder
|
||||||
|
err = tpl.Execute(&buf, data)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to execute template: %e", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
|
@ -8,10 +8,12 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/containrrr/watchtower/internal/meta"
|
"github.com/containrrr/watchtower/internal/meta"
|
||||||
|
"github.com/containrrr/watchtower/pkg/notifications/preview"
|
||||||
|
"github.com/containrrr/watchtower/pkg/notifications/preview/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Fprintf(os.Stderr, "watchtower/tplprev v%v\n\n", meta.Version)
|
fmt.Fprintf(os.Stderr, "watchtower/tplprev %v\n\n", meta.Version)
|
||||||
|
|
||||||
var states string
|
var states string
|
||||||
var entries string
|
var entries string
|
||||||
|
@ -36,7 +38,7 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := TplPrev(string(input), StatesFromString(states), LevelsFromString(entries))
|
result, err := preview.Render(string(input), data.StatesFromString(states), data.LevelsFromString(entries))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to read template file %q: %v\n", flag.Arg(0), err)
|
fmt.Fprintf(os.Stderr, "Failed to read template file %q: %v\n", flag.Arg(0), err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/containrrr/watchtower/internal/meta"
|
"github.com/containrrr/watchtower/internal/meta"
|
||||||
|
"github.com/containrrr/watchtower/pkg/notifications/preview"
|
||||||
|
"github.com/containrrr/watchtower/pkg/notifications/preview/data"
|
||||||
|
|
||||||
"syscall/js"
|
"syscall/js"
|
||||||
)
|
)
|
||||||
|
@ -29,30 +31,30 @@ func jsTplPrev(this js.Value, args []js.Value) any {
|
||||||
input := args[0].String()
|
input := args[0].String()
|
||||||
|
|
||||||
statesArg := args[1]
|
statesArg := args[1]
|
||||||
var states []State
|
var states []data.State
|
||||||
|
|
||||||
if statesArg.Type() == js.TypeString {
|
if statesArg.Type() == js.TypeString {
|
||||||
states = StatesFromString(statesArg.String())
|
states = data.StatesFromString(statesArg.String())
|
||||||
} else {
|
} else {
|
||||||
for i := 0; i < statesArg.Length(); i++ {
|
for i := 0; i < statesArg.Length(); i++ {
|
||||||
state := State(statesArg.Index(i).String())
|
state := data.State(statesArg.Index(i).String())
|
||||||
states = append(states, state)
|
states = append(states, state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
levelsArg := args[2]
|
levelsArg := args[2]
|
||||||
var levels []LogLevel
|
var levels []data.LogLevel
|
||||||
|
|
||||||
if levelsArg.Type() == js.TypeString {
|
if levelsArg.Type() == js.TypeString {
|
||||||
levels = LevelsFromString(statesArg.String())
|
levels = data.LevelsFromString(statesArg.String())
|
||||||
} else {
|
} else {
|
||||||
for i := 0; i < levelsArg.Length(); i++ {
|
for i := 0; i < levelsArg.Length(); i++ {
|
||||||
level := LogLevel(levelsArg.Index(i).String())
|
level := data.LogLevel(levelsArg.Index(i).String())
|
||||||
levels = append(levels, level)
|
levels = append(levels, level)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := TplPrev(input, states, levels)
|
result, err := preview.Render(input, states, levels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "Error: " + err.Error()
|
return "Error: " + err.Error()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,205 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
|
||||||
"math/rand"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/containrrr/watchtower/pkg/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
type reportBuilder struct {
|
|
||||||
rand *rand.Rand
|
|
||||||
report Report
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReportBuilder() *reportBuilder {
|
|
||||||
return &reportBuilder{
|
|
||||||
report: Report{},
|
|
||||||
rand: rand.New(rand.NewSource(1)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type buildAction func(*reportBuilder)
|
|
||||||
|
|
||||||
func (rb *reportBuilder) Build() types.Report {
|
|
||||||
return &rb.report
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rb *reportBuilder) AddFromState(state State) {
|
|
||||||
cid := types.ContainerID(rb.generateID())
|
|
||||||
old := types.ImageID(rb.generateID())
|
|
||||||
new := types.ImageID(rb.generateID())
|
|
||||||
name := rb.generateName()
|
|
||||||
image := rb.generateImageName(name)
|
|
||||||
var err error
|
|
||||||
if state == FailedState {
|
|
||||||
err = errors.New(rb.randomEntry(errorMessages))
|
|
||||||
} else if state == SkippedState {
|
|
||||||
err = errors.New(rb.randomEntry(skippedMessages))
|
|
||||||
}
|
|
||||||
rb.AddContainer(ContainerStatus{
|
|
||||||
containerID: cid,
|
|
||||||
oldImage: old,
|
|
||||||
newImage: new,
|
|
||||||
containerName: name,
|
|
||||||
imageName: image,
|
|
||||||
error: err,
|
|
||||||
state: state,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rb *reportBuilder) AddContainer(c ContainerStatus) {
|
|
||||||
switch c.state {
|
|
||||||
case ScannedState:
|
|
||||||
rb.report.scanned = append(rb.report.scanned, &c)
|
|
||||||
case UpdatedState:
|
|
||||||
rb.report.updated = append(rb.report.updated, &c)
|
|
||||||
case FailedState:
|
|
||||||
rb.report.failed = append(rb.report.failed, &c)
|
|
||||||
case SkippedState:
|
|
||||||
rb.report.skipped = append(rb.report.skipped, &c)
|
|
||||||
case StaleState:
|
|
||||||
rb.report.stale = append(rb.report.stale, &c)
|
|
||||||
case FreshState:
|
|
||||||
rb.report.fresh = append(rb.report.fresh, &c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rb *reportBuilder) generateID() string {
|
|
||||||
buf := make([]byte, 32)
|
|
||||||
_, _ = rb.rand.Read(buf)
|
|
||||||
return hex.EncodeToString(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rb *reportBuilder) randomEntry(arr []string) string {
|
|
||||||
return arr[rb.rand.Intn(len(arr))]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rb *reportBuilder) generateName() string {
|
|
||||||
index := rb.containerCount()
|
|
||||||
if index <= len(containerNames) {
|
|
||||||
return containerNames[index]
|
|
||||||
}
|
|
||||||
suffix := index / len(containerNames)
|
|
||||||
index %= len(containerNames)
|
|
||||||
return containerNames[index] + strconv.FormatInt(int64(suffix), 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rb *reportBuilder) generateImageName(name string) string {
|
|
||||||
index := rb.containerCount()
|
|
||||||
return companyNames[index%len(companyNames)] + "/" + strings.ToLower(name) + ":latest"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rb *reportBuilder) containerCount() int {
|
|
||||||
return len(rb.report.scanned) +
|
|
||||||
len(rb.report.updated) +
|
|
||||||
len(rb.report.failed) +
|
|
||||||
len(rb.report.skipped) +
|
|
||||||
len(rb.report.stale) +
|
|
||||||
len(rb.report.fresh)
|
|
||||||
}
|
|
||||||
|
|
||||||
type State string
|
|
||||||
|
|
||||||
const (
|
|
||||||
ScannedState State = "scanned"
|
|
||||||
UpdatedState State = "updated"
|
|
||||||
FailedState State = "failed"
|
|
||||||
SkippedState State = "skipped"
|
|
||||||
StaleState State = "stale"
|
|
||||||
FreshState State = "fresh"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Report struct {
|
|
||||||
scanned []types.ContainerReport
|
|
||||||
updated []types.ContainerReport
|
|
||||||
failed []types.ContainerReport
|
|
||||||
skipped []types.ContainerReport
|
|
||||||
stale []types.ContainerReport
|
|
||||||
fresh []types.ContainerReport
|
|
||||||
}
|
|
||||||
|
|
||||||
func StatesFromString(str string) []State {
|
|
||||||
states := make([]State, 0, len(str))
|
|
||||||
for _, c := range str {
|
|
||||||
switch c {
|
|
||||||
case 'c':
|
|
||||||
states = append(states, ScannedState)
|
|
||||||
case 'u':
|
|
||||||
states = append(states, UpdatedState)
|
|
||||||
case 'e':
|
|
||||||
states = append(states, FailedState)
|
|
||||||
case 'k':
|
|
||||||
states = append(states, SkippedState)
|
|
||||||
case 't':
|
|
||||||
states = append(states, StaleState)
|
|
||||||
case 'f':
|
|
||||||
states = append(states, FreshState)
|
|
||||||
default:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return states
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Report) Scanned() []types.ContainerReport {
|
|
||||||
return r.scanned
|
|
||||||
}
|
|
||||||
func (r *Report) Updated() []types.ContainerReport {
|
|
||||||
return r.updated
|
|
||||||
}
|
|
||||||
func (r *Report) Failed() []types.ContainerReport {
|
|
||||||
return r.failed
|
|
||||||
}
|
|
||||||
func (r *Report) Skipped() []types.ContainerReport {
|
|
||||||
return r.skipped
|
|
||||||
}
|
|
||||||
func (r *Report) Stale() []types.ContainerReport {
|
|
||||||
return r.stale
|
|
||||||
}
|
|
||||||
func (r *Report) Fresh() []types.ContainerReport {
|
|
||||||
return r.fresh
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Report) All() []types.ContainerReport {
|
|
||||||
allLen := len(r.scanned) + len(r.updated) + len(r.failed) + len(r.skipped) + len(r.stale) + len(r.fresh)
|
|
||||||
all := make([]types.ContainerReport, 0, allLen)
|
|
||||||
|
|
||||||
presentIds := map[types.ContainerID][]string{}
|
|
||||||
|
|
||||||
appendUnique := func(reports []types.ContainerReport) {
|
|
||||||
for _, cr := range reports {
|
|
||||||
if _, found := presentIds[cr.ID()]; found {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
all = append(all, cr)
|
|
||||||
presentIds[cr.ID()] = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
appendUnique(r.updated)
|
|
||||||
appendUnique(r.failed)
|
|
||||||
appendUnique(r.skipped)
|
|
||||||
appendUnique(r.stale)
|
|
||||||
appendUnique(r.fresh)
|
|
||||||
appendUnique(r.scanned)
|
|
||||||
|
|
||||||
sort.Sort(sortableContainers(all))
|
|
||||||
|
|
||||||
return all
|
|
||||||
}
|
|
||||||
|
|
||||||
type sortableContainers []types.ContainerReport
|
|
||||||
|
|
||||||
// Len implements sort.Interface.Len
|
|
||||||
func (s sortableContainers) Len() int { return len(s) }
|
|
||||||
|
|
||||||
// Less implements sort.Interface.Less
|
|
||||||
func (s sortableContainers) Less(i, j int) bool { return s[i].ID() < s[j].ID() }
|
|
||||||
|
|
||||||
// Swap implements sort.Interface.Swap
|
|
||||||
func (s sortableContainers) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
|
@ -1,51 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import wt "github.com/containrrr/watchtower/pkg/types"
|
|
||||||
|
|
||||||
// ContainerStatus contains the container state during a session
|
|
||||||
type ContainerStatus struct {
|
|
||||||
containerID wt.ContainerID
|
|
||||||
oldImage wt.ImageID
|
|
||||||
newImage wt.ImageID
|
|
||||||
containerName string
|
|
||||||
imageName string
|
|
||||||
error
|
|
||||||
state State
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID returns the container ID
|
|
||||||
func (u *ContainerStatus) ID() wt.ContainerID {
|
|
||||||
return u.containerID
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the container name
|
|
||||||
func (u *ContainerStatus) Name() string {
|
|
||||||
return u.containerName
|
|
||||||
}
|
|
||||||
|
|
||||||
// CurrentImageID returns the image ID that the container used when the session started
|
|
||||||
func (u *ContainerStatus) CurrentImageID() wt.ImageID {
|
|
||||||
return u.oldImage
|
|
||||||
}
|
|
||||||
|
|
||||||
// LatestImageID returns the newest image ID found during the session
|
|
||||||
func (u *ContainerStatus) LatestImageID() wt.ImageID {
|
|
||||||
return u.newImage
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImageName returns the name:tag that the container uses
|
|
||||||
func (u *ContainerStatus) ImageName() string {
|
|
||||||
return u.imageName
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error returns the error (if any) that was encountered for the container during a session
|
|
||||||
func (u *ContainerStatus) Error() string {
|
|
||||||
if u.error == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return u.error.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *ContainerStatus) State() string {
|
|
||||||
return string(u.state)
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/containrrr/watchtower/pkg/notifications/templates"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TplPrev(input string, states []State, loglevels []LogLevel) (string, error) {
|
|
||||||
|
|
||||||
rb := ReportBuilder()
|
|
||||||
|
|
||||||
tpl, err := template.New("").Funcs(templates.Funcs).Parse(input)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to parse template: %e", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, state := range states {
|
|
||||||
rb.AddFromState(state)
|
|
||||||
}
|
|
||||||
|
|
||||||
var entries []*LogEntry
|
|
||||||
for _, level := range loglevels {
|
|
||||||
var msg string
|
|
||||||
if level <= WarnLevel {
|
|
||||||
msg = rb.randomEntry(logErrors)
|
|
||||||
} else {
|
|
||||||
msg = rb.randomEntry(logMessages)
|
|
||||||
}
|
|
||||||
entries = append(entries, &LogEntry{
|
|
||||||
Message: msg,
|
|
||||||
Data: map[string]any{},
|
|
||||||
Time: time.Now(),
|
|
||||||
Level: level,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
report := rb.Build()
|
|
||||||
data := Data{
|
|
||||||
Entries: entries,
|
|
||||||
StaticData: StaticData{
|
|
||||||
Title: "Title",
|
|
||||||
Host: "Host",
|
|
||||||
},
|
|
||||||
Report: report,
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf strings.Builder
|
|
||||||
err = tpl.Execute(&buf, data)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to execute template: %e", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.String(), nil
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue