Session report collection and report templates (#981)

* wip: notification stats

* make report notifications optional

* linting/documentation fixes

* linting/documentation fixes

* merge types.Container and container.Interface

* smaller naming/format fixes

* use typed image/container IDs

* simplify notifier and update tests

* add missed doc comments

* lint fixes

* remove unused constructors

* rename old/new current/latest
This commit is contained in:
nils måsén 2021-06-27 09:05:01 +02:00 committed by GitHub
parent d0ecc23d72
commit e3dd8d688a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 853 additions and 598 deletions

View file

@ -0,0 +1,82 @@
package session
import wt "github.com/containrrr/watchtower/pkg/types"
// State indicates what the current state is of the container
type State int
// State enum values
const (
// UnknownState is only used to represent an uninitialized State value
UnknownState State = iota
SkippedState
ScannedState
UpdatedState
FailedState
FreshState
StaleState
)
// 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()
}
// State returns the current State that the container is in
func (u *ContainerStatus) State() string {
switch u.state {
case SkippedState:
return "Skipped"
case ScannedState:
return "Scanned"
case UpdatedState:
return "Updated"
case FailedState:
return "Failed"
case FreshState:
return "Fresh"
case StaleState:
return "Stale"
default:
return "Unknown"
}
}

56
pkg/session/progress.go Normal file
View file

@ -0,0 +1,56 @@
package session
import (
"github.com/containrrr/watchtower/pkg/types"
)
// Progress contains the current session container status
type Progress map[types.ContainerID]*ContainerStatus
// UpdateFromContainer sets various status fields from their corresponding container equivalents
func UpdateFromContainer(cont types.Container, newImage types.ImageID, state State) *ContainerStatus {
return &ContainerStatus{
containerID: cont.ID(),
containerName: cont.Name(),
imageName: cont.ImageName(),
oldImage: cont.SafeImageID(),
newImage: newImage,
state: state,
}
}
// AddSkipped adds a container to the Progress with the state set as skipped
func (m Progress) AddSkipped(cont types.Container, err error) {
update := UpdateFromContainer(cont, cont.SafeImageID(), SkippedState)
update.error = err
m.Add(update)
}
// AddScanned adds a container to the Progress with the state set as scanned
func (m Progress) AddScanned(cont types.Container, newImage types.ImageID) {
m.Add(UpdateFromContainer(cont, newImage, ScannedState))
}
// UpdateFailed updates the containers passed, setting their state as failed with the supplied error
func (m Progress) UpdateFailed(failures map[types.ContainerID]error) {
for id, err := range failures {
update := m[id]
update.error = err
update.state = FailedState
}
}
// Add a container to the map using container ID as the key
func (m Progress) Add(update *ContainerStatus) {
m[update.containerID] = update
}
// MarkForUpdate marks the container identified by containerID for update
func (m Progress) MarkForUpdate(containerID types.ContainerID) {
m[containerID].state = UpdatedState
}
// Report creates a new Report from a Progress instance
func (m Progress) Report() types.Report {
return NewReport(m)
}

90
pkg/session/report.go Normal file
View file

@ -0,0 +1,90 @@
package session
import (
"github.com/containrrr/watchtower/pkg/types"
"sort"
)
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
}
// NewReport creates a types.Report from the supplied Progress
func NewReport(progress Progress) types.Report {
report := &report{
scanned: []types.ContainerReport{},
updated: []types.ContainerReport{},
failed: []types.ContainerReport{},
skipped: []types.ContainerReport{},
stale: []types.ContainerReport{},
fresh: []types.ContainerReport{},
}
for _, update := range progress {
if update.state == SkippedState {
report.skipped = append(report.skipped, update)
continue
}
report.scanned = append(report.scanned, update)
if update.newImage == update.oldImage {
update.state = FreshState
report.fresh = append(report.fresh, update)
continue
}
switch update.state {
case UpdatedState:
report.updated = append(report.updated, update)
case FailedState:
report.failed = append(report.failed, update)
default:
update.state = StaleState
report.stale = append(report.stale, update)
}
}
sort.Sort(sortableContainers(report.scanned))
sort.Sort(sortableContainers(report.updated))
sort.Sort(sortableContainers(report.failed))
sort.Sort(sortableContainers(report.skipped))
sort.Sort(sortableContainers(report.stale))
sort.Sort(sortableContainers(report.fresh))
return report
}
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] }