mirror of
https://github.com/containrrr/watchtower.git
synced 2026-01-26 10:46:09 +01:00
restructure and fix empty report
This commit is contained in:
parent
0776077ac5
commit
3bcc86cd8f
12 changed files with 359 additions and 343 deletions
|
|
@ -1,66 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/containrrr/watchtower/pkg/types"
|
||||
)
|
||||
|
||||
type Data struct {
|
||||
Entries []*LogEntry
|
||||
StaticData StaticData
|
||||
Report types.Report
|
||||
}
|
||||
|
||||
type StaticData struct {
|
||||
Title string
|
||||
Host string
|
||||
}
|
||||
|
||||
type LogEntry struct {
|
||||
Message string
|
||||
Data map[string]any
|
||||
Time time.Time
|
||||
Level LogLevel
|
||||
}
|
||||
|
||||
type LogLevel string
|
||||
|
||||
const (
|
||||
TraceLevel LogLevel = "trace"
|
||||
DebugLevel LogLevel = "debug"
|
||||
InfoLevel LogLevel = "info"
|
||||
WarnLevel LogLevel = "warning"
|
||||
ErrorLevel LogLevel = "error"
|
||||
FatalLevel LogLevel = "fatal"
|
||||
PanicLevel LogLevel = "panic"
|
||||
)
|
||||
|
||||
func LevelsFromString(str string) []LogLevel {
|
||||
levels := make([]LogLevel, 0, len(str))
|
||||
for _, c := range str {
|
||||
switch c {
|
||||
case 'p':
|
||||
levels = append(levels, PanicLevel)
|
||||
case 'f':
|
||||
levels = append(levels, FatalLevel)
|
||||
case 'e':
|
||||
levels = append(levels, ErrorLevel)
|
||||
case 'w':
|
||||
levels = append(levels, WarnLevel)
|
||||
case 'i':
|
||||
levels = append(levels, InfoLevel)
|
||||
case 'd':
|
||||
levels = append(levels, DebugLevel)
|
||||
case 't':
|
||||
levels = append(levels, TraceLevel)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return levels
|
||||
}
|
||||
|
||||
func (level LogLevel) String() string {
|
||||
return string(level)
|
||||
}
|
||||
|
|
@ -8,10 +8,12 @@ import (
|
|||
"os"
|
||||
|
||||
"github.com/containrrr/watchtower/internal/meta"
|
||||
"github.com/containrrr/watchtower/pkg/notifications/preview"
|
||||
"github.com/containrrr/watchtower/pkg/notifications/preview/data"
|
||||
)
|
||||
|
||||
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 entries string
|
||||
|
|
@ -36,7 +38,7 @@ func main() {
|
|||
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 {
|
||||
fmt.Fprintf(os.Stderr, "Failed to read template file %q: %v\n", flag.Arg(0), err)
|
||||
os.Exit(1)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/containrrr/watchtower/internal/meta"
|
||||
"github.com/containrrr/watchtower/pkg/notifications/preview"
|
||||
"github.com/containrrr/watchtower/pkg/notifications/preview/data"
|
||||
|
||||
"syscall/js"
|
||||
)
|
||||
|
|
@ -29,30 +31,30 @@ func jsTplPrev(this js.Value, args []js.Value) any {
|
|||
input := args[0].String()
|
||||
|
||||
statesArg := args[1]
|
||||
var states []State
|
||||
var states []data.State
|
||||
|
||||
if statesArg.Type() == js.TypeString {
|
||||
states = StatesFromString(statesArg.String())
|
||||
states = data.StatesFromString(statesArg.String())
|
||||
} else {
|
||||
for i := 0; i < statesArg.Length(); i++ {
|
||||
state := State(statesArg.Index(i).String())
|
||||
state := data.State(statesArg.Index(i).String())
|
||||
states = append(states, state)
|
||||
}
|
||||
}
|
||||
|
||||
levelsArg := args[2]
|
||||
var levels []LogLevel
|
||||
var levels []data.LogLevel
|
||||
|
||||
if levelsArg.Type() == js.TypeString {
|
||||
levels = LevelsFromString(statesArg.String())
|
||||
levels = data.LevelsFromString(statesArg.String())
|
||||
} else {
|
||||
for i := 0; i < levelsArg.Length(); i++ {
|
||||
level := LogLevel(levelsArg.Index(i).String())
|
||||
level := data.LogLevel(levelsArg.Index(i).String())
|
||||
levels = append(levels, level)
|
||||
}
|
||||
}
|
||||
|
||||
result, err := TplPrev(input, states, levels)
|
||||
result, err := preview.Render(input, states, levels)
|
||||
if err != nil {
|
||||
return "Error: " + err.Error()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,178 +0,0 @@
|
|||
package main
|
||||
|
||||
var containerNames = []string{
|
||||
"cyberscribe",
|
||||
"datamatrix",
|
||||
"nexasync",
|
||||
"quantumquill",
|
||||
"aerosphere",
|
||||
"virtuos",
|
||||
"fusionflow",
|
||||
"neuralink",
|
||||
"pixelpulse",
|
||||
"synthwave",
|
||||
"codecraft",
|
||||
"zapzone",
|
||||
"robologic",
|
||||
"dreamstream",
|
||||
"infinisync",
|
||||
"megamesh",
|
||||
"novalink",
|
||||
"xenogenius",
|
||||
"ecosim",
|
||||
"innovault",
|
||||
"techtracer",
|
||||
"fusionforge",
|
||||
"quantumquest",
|
||||
"neuronest",
|
||||
"codefusion",
|
||||
"datadyno",
|
||||
"pixelpioneer",
|
||||
"vortexvision",
|
||||
"cybercraft",
|
||||
"synthsphere",
|
||||
"infinitescript",
|
||||
"roborhythm",
|
||||
"dreamengine",
|
||||
"aquasync",
|
||||
"geniusgrid",
|
||||
"megamind",
|
||||
"novasync-pro",
|
||||
"xenonwave",
|
||||
"ecologic",
|
||||
"innoscan",
|
||||
}
|
||||
|
||||
var companyNames = []string{
|
||||
"techwave",
|
||||
"codecrafters",
|
||||
"innotechlabs",
|
||||
"fusionsoft",
|
||||
"cyberpulse",
|
||||
"quantumscribe",
|
||||
"datadynamo",
|
||||
"neuralink",
|
||||
"pixelpro",
|
||||
"synthwizards",
|
||||
"virtucorplabs",
|
||||
"robologic",
|
||||
"dreamstream",
|
||||
"novanest",
|
||||
"megamind",
|
||||
"xenonwave",
|
||||
"ecologic",
|
||||
"innosync",
|
||||
"techgenius",
|
||||
"nexasoft",
|
||||
"codewave",
|
||||
"zapzone",
|
||||
"techsphere",
|
||||
"aquatech",
|
||||
"quantumcraft",
|
||||
"neuronest",
|
||||
"datafusion",
|
||||
"pixelpioneer",
|
||||
"synthsphere",
|
||||
"infinitescribe",
|
||||
"roborhythm",
|
||||
"dreamengine",
|
||||
"vortexvision",
|
||||
"geniusgrid",
|
||||
"megamesh",
|
||||
"novasync",
|
||||
"xenogeniuslabs",
|
||||
"ecosim",
|
||||
"innovault",
|
||||
}
|
||||
|
||||
var errorMessages = []string{
|
||||
"Error 404: Resource not found",
|
||||
"Critical Error: System meltdown imminent",
|
||||
"Error 500: Internal server error",
|
||||
"Invalid input: Please check your data",
|
||||
"Access denied: Unauthorized access detected",
|
||||
"Network connection lost: Please check your connection",
|
||||
"Error 403: Forbidden access",
|
||||
"Fatal error: System crash imminent",
|
||||
"File not found: Check the file path",
|
||||
"Invalid credentials: Authentication failed",
|
||||
"Error 502: Bad Gateway",
|
||||
"Database connection failed: Please try again later",
|
||||
"Security breach detected: Take immediate action",
|
||||
"Error 400: Bad request",
|
||||
"Out of memory: Close unnecessary applications",
|
||||
"Invalid configuration: Check your settings",
|
||||
"Error 503: Service unavailable",
|
||||
"File is read-only: Cannot modify",
|
||||
"Data corruption detected: Backup your data",
|
||||
"Error 401: Unauthorized",
|
||||
"Disk space full: Free up disk space",
|
||||
"Connection timeout: Retry your request",
|
||||
"Error 504: Gateway timeout",
|
||||
"File access denied: Permission denied",
|
||||
"Unexpected error: Please contact support",
|
||||
"Error 429: Too many requests",
|
||||
"Invalid URL: Check the URL format",
|
||||
"Database query failed: Try again later",
|
||||
"Error 408: Request timeout",
|
||||
"File is in use: Close the file and try again",
|
||||
"Invalid parameter: Check your input",
|
||||
"Error 502: Proxy error",
|
||||
"Database connection lost: Reconnect and try again",
|
||||
"File size exceeds limit: Reduce the file size",
|
||||
"Error 503: Overloaded server",
|
||||
"Operation aborted: Try again",
|
||||
"Invalid API key: Check your API key",
|
||||
"Error 507: Insufficient storage",
|
||||
"Database deadlock: Retry your transaction",
|
||||
"Error 405: Method not allowed",
|
||||
"File format not supported: Choose a different format",
|
||||
"Unknown error: Contact system administrator",
|
||||
}
|
||||
|
||||
var skippedMessages = []string{
|
||||
"Fear of introducing new bugs",
|
||||
"Don't have time for the update process",
|
||||
"Current version works fine for my needs",
|
||||
"Concerns about compatibility with other software",
|
||||
"Limited bandwidth for downloading updates",
|
||||
"Worries about losing custom settings or configurations",
|
||||
"Lack of trust in the software developer's updates",
|
||||
"Dislike changes to the user interface",
|
||||
"Avoiding potential subscription fees",
|
||||
"Suspicion of hidden data collection in updates",
|
||||
"Apprehension about changes in privacy policies",
|
||||
"Prefer the older version's features or design",
|
||||
"Worry about software becoming more resource-intensive",
|
||||
"Avoiding potential changes in licensing terms",
|
||||
"Waiting for initial bugs to be resolved in the update",
|
||||
"Concerns about update breaking third-party plugins or extensions",
|
||||
"Belief that the software is already secure enough",
|
||||
"Don't want to relearn how to use the software",
|
||||
"Fear of losing access to older file formats",
|
||||
"Avoiding the hassle of having to update multiple devices",
|
||||
}
|
||||
|
||||
var logMessages = []string{
|
||||
"Checking for available updates...",
|
||||
"Downloading update package...",
|
||||
"Verifying update integrity...",
|
||||
"Preparing to install update...",
|
||||
"Backing up existing configuration...",
|
||||
"Installing update...",
|
||||
"Update installation complete.",
|
||||
"Applying configuration settings...",
|
||||
"Cleaning up temporary files...",
|
||||
"Update successful! Software is now up-to-date.",
|
||||
"Restarting the application...",
|
||||
"Restart complete. Enjoy the latest features!",
|
||||
"Update rollback complete. Your software remains at the previous version.",
|
||||
}
|
||||
|
||||
var logErrors = []string{
|
||||
"Unable to check for updates. Please check your internet connection.",
|
||||
"Update package download failed. Try again later.",
|
||||
"Update verification failed. Please contact support.",
|
||||
"Update installation failed. Rolling back to the previous version...",
|
||||
"Your configuration settings may have been reset to defaults.",
|
||||
}
|
||||
|
|
@ -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