http report wip

This commit is contained in:
nils måsén 2021-06-27 15:30:23 +02:00
parent e3dd8d688a
commit efaf7190ee
25 changed files with 350 additions and 284 deletions

View file

@ -1,6 +1,9 @@
package session
import wt "github.com/containrrr/watchtower/pkg/types"
import (
wt "github.com/containrrr/watchtower/pkg/types"
"strings"
)
// State indicates what the current state is of the container
type State int
@ -19,51 +22,17 @@ const (
// 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 wt.ContainerID
Name string
OldImageID wt.ImageID
NewImageID wt.ImageID
ImageName string
Error 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 {
func (state State) String() string {
switch state {
case SkippedState:
return "Skipped"
case ScannedState:
@ -80,3 +49,12 @@ func (u *ContainerStatus) State() string {
return "Unknown"
}
}
// MarshalJSON marshals State as a string
func (state State) MarshalJSON() ([]byte, error) {
sb := strings.Builder{}
sb.WriteString(`"`)
sb.WriteString(state.String())
sb.WriteString(`"`)
return []byte(sb.String()), nil
}

View file

@ -10,19 +10,19 @@ 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,
ID: cont.ID(),
Name: cont.Name(),
ImageName: cont.ImageName(),
OldImageID: cont.SafeImageID(),
NewImageID: 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
update.Error = err
m.Add(update)
}
@ -35,22 +35,17 @@ func (m Progress) AddScanned(cont types.Container, newImage types.ImageID) {
func (m Progress) UpdateFailed(failures map[types.ContainerID]error) {
for id, err := range failures {
update := m[id]
update.error = err
update.state = FailedState
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
m[update.ID] = 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)
m[containerID].State = UpdatedState
}

View file

@ -1,90 +1,78 @@
package session
import (
"github.com/containrrr/watchtower/pkg/types"
"sort"
"time"
)
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
type Report struct {
Started time.Time
Ended time.Time
Trigger Trigger
Scanned []*ContainerStatus
Updated []*ContainerStatus
Failed []*ContainerStatus
Skipped []*ContainerStatus
Stale []*ContainerStatus
Fresh []*ContainerStatus
}
// 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{},
// s.Started, time.Now().UTC(), s.Trigger, s.Progress
func NewReport(started, ended time.Time, trigger Trigger, progress Progress) *Report {
report := &Report{
Started: started,
Ended: ended,
Trigger: trigger,
Scanned: []*ContainerStatus{},
Updated: []*ContainerStatus{},
Failed: []*ContainerStatus{},
Skipped: []*ContainerStatus{},
Stale: []*ContainerStatus{},
Fresh: []*ContainerStatus{},
}
for _, update := range progress {
if update.state == SkippedState {
report.skipped = append(report.skipped, update)
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)
report.Scanned = append(report.Scanned, update)
if update.NewImageID == update.OldImageID {
update.State = FreshState
report.Fresh = append(report.Fresh, update)
continue
}
switch update.state {
switch update.State {
case UpdatedState:
report.updated = append(report.updated, update)
report.Updated = append(report.Updated, update)
case FailedState:
report.failed = append(report.failed, update)
report.Failed = append(report.Failed, update)
default:
update.state = StaleState
report.stale = append(report.stale, update)
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))
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
type sortableContainers []*ContainerStatus
// 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() }
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] }

24
pkg/session/session.go Normal file
View file

@ -0,0 +1,24 @@
package session
import (
"time"
)
type Session struct {
Trigger Trigger
Started time.Time
Progress Progress
}
func New(trigger Trigger) *Session {
return &Session{
Started: time.Now().UTC(),
Trigger: trigger,
Progress: Progress{},
}
}
// Report creates a new Report from a Session instance
func (s Session) Report() *Report {
return NewReport(s.Started, time.Now().UTC(), s.Trigger, s.Progress)
}

34
pkg/session/trigger.go Normal file
View file

@ -0,0 +1,34 @@
package session
import "strings"
type Trigger int
const (
SchedulerTrigger Trigger = iota
APITrigger
StartupTrigger
)
// String returns a string representation of the Trigger
func (trigger Trigger) String() string {
switch trigger {
case SchedulerTrigger:
return "Scheduler"
case APITrigger:
return "API"
case StartupTrigger:
return "Startup"
default:
return "Unknown"
}
}
// MarshalJSON marshals Trigger as a quoted string
func (trigger Trigger) MarshalJSON() ([]byte, error) {
sb := strings.Builder{}
sb.WriteString(`"`)
sb.WriteString(trigger.String())
sb.WriteString(`"`)
return []byte(sb.String()), nil
}