diff --git a/cmd/info.go b/cmd/info.go index 6ee1db3..139a0eb 100644 --- a/cmd/info.go +++ b/cmd/info.go @@ -24,7 +24,7 @@ func (info) Description() wisski_distillery.Description { NeedsDistillery: true, }, Command: "info", - Description: "Provide information about a single repository", + Description: "Provide information about a single instance", } } diff --git a/cmd/status.go b/cmd/status.go new file mode 100644 index 0000000..0515f67 --- /dev/null +++ b/cmd/status.go @@ -0,0 +1,48 @@ +package cmd + +import ( + "encoding/json" + + wisski_distillery "github.com/FAU-CDI/wisski-distillery" + "github.com/FAU-CDI/wisski-distillery/internal/cli" +) + +// Info is then 'info' command +var Status wisski_distillery.Command = cStatus{} + +type cStatus struct { + JSON bool `short:"j" long:"json" description:"Print status as JSON instead of as string"` +} + +func (cStatus) Description() wisski_distillery.Description { + return wisski_distillery.Description{ + Requirements: cli.Requirements{ + NeedsDistillery: true, + }, + Command: "status", + Description: "Provide information about the distillery as a whole", + } +} + +func (s cStatus) Run(context wisski_distillery.Context) error { + status, _, err := context.Environment.Info().Status(true) + if err != nil { + return err + } + + if s.JSON { + json.NewEncoder(context.Stdout).Encode(status) + return nil + } + + context.Printf("Total Instances: %v\n", status.TotalCount) + context.Printf(" (running): %v\n", status.RunningCount) + context.Printf(" (stopped): %v\n", status.StoppedCount) + + context.Printf("Backups: (count %d)\n", len(status.Backups)) + for _, s := range status.Backups { + context.Printf("- %s (slug %q, taken %s, packed %v)\n", s.Path, s.Slug, s.Created.String(), s.Packed) + } + + return nil +} diff --git a/cmd/wdcli/main.go b/cmd/wdcli/main.go index b9d282b..b1dab6f 100644 --- a/cmd/wdcli/main.go +++ b/cmd/wdcli/main.go @@ -57,6 +57,9 @@ func init() { // servers wdcli.Register(cmd.Server) wdcli.Register(cmd.SSH) + + // status + wdcli.Register(cmd.Status) } // an error when no arguments are provided. diff --git a/internal/dis/component/control/info/index.go b/internal/dis/component/control/info/index.go index f02c4c1..2bd83ee 100644 --- a/internal/dis/component/control/info/index.go +++ b/internal/dis/component/control/info/index.go @@ -6,9 +6,8 @@ import ( _ "embed" - "github.com/FAU-CDI/wisski-distillery/internal/config" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static" - "github.com/FAU-CDI/wisski-distillery/internal/models" "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient" "golang.org/x/sync/errgroup" ) @@ -21,31 +20,29 @@ var indexTemplate = static.AssetsControlIndex.MustParseShared( ) type indexContext struct { - Time time.Time - - Config *config.Config - + component.Observation Instances []ingredient.Information - - TotalCount int - RunningCount int - StoppedCount int - - Backups []models.Export } -func (nfo *Info) index(r *http.Request) (idx indexContext, err error) { +func (info *Info) index(r *http.Request) (idx indexContext, err error) { + idx.Observation, idx.Instances, err = info.Status(true) + return +} + +// Status produces a new observation of the distillery, and a new information of all instances +// The information on all instances is passed the given quick flag. +func (info *Info) Status(QuickInformation bool) (observation component.Observation, information []ingredient.Information, err error) { var group errgroup.Group group.Go(func() error { // list all the instances - all, err := nfo.Instances.All() + all, err := info.Instances.All() if err != nil { return err } // get all of their info! - idx.Instances = make([]ingredient.Information, len(all)) + information = make([]ingredient.Information, len(all)) for i, instance := range all { { i := i @@ -53,36 +50,43 @@ func (nfo *Info) index(r *http.Request) (idx indexContext, err error) { // store the info for this group! group.Go(func() (err error) { - idx.Instances[i], err = instance.Info().Information(true) + information[i], err = instance.Info().Information(true) return err }) } } - return nil }) - // get the log entries - group.Go(func() (err error) { - idx.Backups, err = nfo.SnapshotsLog.For("") - return - }) + // gather all the observations + var flags component.ObservationFlags + for _, o := range info.Obervers { + o := o + group.Go(func() error { + return o.Observe(flags, &observation) + }) + } - // get the static properties - idx.Config = nfo.Config - idx.Time = time.Now().UTC() + // wait for all the observes to finish + if err := group.Wait(); err != nil { + return component.Observation{}, nil, err + } - group.Wait() - - // count how many are running and how many are stopped - for _, i := range idx.Instances { + // count overall instances + for _, i := range information { if i.Running { - idx.RunningCount++ + observation.RunningCount++ } else { - idx.StoppedCount++ + observation.StoppedCount++ } } - idx.TotalCount = len(idx.Instances) + observation.TotalCount = len(information) return } + +func (nfo *Info) Observe(flags component.ObservationFlags, observation *component.Observation) error { + observation.Time = time.Now().UTC() + observation.Config = nfo.Config + return nil +} diff --git a/internal/dis/component/control/info/info.go b/internal/dis/component/control/info/info.go index 7065053..f3d1723 100644 --- a/internal/dis/component/control/info/info.go +++ b/internal/dis/component/control/info/info.go @@ -18,6 +18,7 @@ type Info struct { component.Base Analytics *lazy.PoolAnalytics + Obervers []component.Observer Exporter *exporter.Exporter Instances *instances.Instances diff --git a/internal/dis/component/exporter/logger/logger.go b/internal/dis/component/exporter/logger/logger.go index f71bfbe..49fac81 100644 --- a/internal/dis/component/exporter/logger/logger.go +++ b/internal/dis/component/exporter/logger/logger.go @@ -76,3 +76,9 @@ func (log *Logger) Add(export models.Export) error { } return nil } + +// Observe writes the SnapshotLog into the given observation +func (logger *Logger) Observe(flags component.ObservationFlags, observation *component.Observation) (err error) { + observation.Backups, err = logger.For("") + return +} diff --git a/internal/dis/component/watcher.go b/internal/dis/component/watcher.go new file mode 100644 index 0000000..f5fade2 --- /dev/null +++ b/internal/dis/component/watcher.go @@ -0,0 +1,34 @@ +package component + +import ( + "time" + + "github.com/FAU-CDI/wisski-distillery/internal/config" + "github.com/FAU-CDI/wisski-distillery/internal/models" +) + +// Observer is a component with an Observe method +type Observer interface { + Component + + // Observe observes this distillery component and writes the result into observation + // Distinct Observers must write into distinct fields. + Observe(flags ObservationFlags, observation *Observation) error +} + +type ObservationFlags struct{} + +// Observation represents fetched information about the distillery +type Observation struct { + Time time.Time // Time this obervation was built + + // Configuration of the distillery + Config *config.Config + + // number of instances + TotalCount int + RunningCount int + StoppedCount int + + Backups []models.Export // list of backups +} diff --git a/internal/dis/distillery.go b/internal/dis/distillery.go index 150373b..09892cd 100644 --- a/internal/dis/distillery.go +++ b/internal/dis/distillery.go @@ -96,6 +96,9 @@ func (dis *Distillery) Updatable() []component.Updatable { func (dis *Distillery) Provisionable() []component.Provisionable { return exportAll[component.Provisionable](dis) } +func (dis *Distillery) Info() *info.Info { + return export[*info.Info](dis) +} // // All components