restructure and fix empty report

This commit is contained in:
nils måsén 2023-10-02 15:30:28 +02:00
parent 0776077ac5
commit 3bcc86cd8f
12 changed files with 359 additions and 343 deletions

View file

@ -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)
}

View file

@ -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)

View file

@ -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()
}

View file

@ -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.",
}

View file

@ -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] }

View file

@ -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)
}

View file

@ -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
}