internal: Annotate all components with groups

This commit ensures that the compiler has to check every component
against the groups they implement by explicitly annotating the
appropriate interfaces.
This commit is contained in:
Tom Wiesing 2022-11-30 11:08:46 +01:00
parent 3455f491ca
commit 890022ae64
No known key found for this signature in database
29 changed files with 163 additions and 33 deletions

View file

@ -13,6 +13,12 @@ import (
func (dis *Distillery) init() {
dis.poolInit.Do(func() {
dis.pool.Init = component.Init
lazy.RegisterPoolGroup[component.Backupable](&dis.pool)
lazy.RegisterPoolGroup[component.Snapshotable](&dis.pool)
lazy.RegisterPoolGroup[component.DistilleryFetcher](&dis.pool)
lazy.RegisterPoolGroup[component.Installable](&dis.pool)
lazy.RegisterPoolGroup[component.Provisionable](&dis.pool)
lazy.RegisterPoolGroup[component.Servable](&dis.pool)
})
}

View file

@ -16,6 +16,10 @@ type Control struct {
Servables []component.Servable
}
var (
_ component.Installable = (*Control)(nil)
)
func (control Control) Path() string {
return filepath.Join(control.Still.Config.DeployRoot, "core", "dis")
}

View file

@ -24,6 +24,10 @@ type Home struct {
homeBytes lazy.Lazy[[]byte]
}
var (
_ component.Servable = (*Home)(nil)
)
func (*Home) Routes() []string { return []string{"/"} }
func (home *Home) Handler(ctx context.Context, route string, io stream.IOStream) (http.Handler, error) {

View file

@ -26,6 +26,11 @@ type Info struct {
SnapshotsLog *logger.Logger
}
var (
_ component.DistilleryFetcher = (*Info)(nil)
_ component.Servable = (*Info)(nil)
)
func (*Info) Routes() []string { return []string{"/dis/"} }
func (info *Info) Handler(ctx context.Context, route string, io stream.IOStream) (handler http.Handler, err error) {

View file

@ -15,6 +15,10 @@ type Static struct {
component.Base
}
var (
_ component.Servable = (*Static)(nil)
)
func (*Static) Routes() []string { return []string{"/static/"} }
//go:embed dist

View file

@ -13,6 +13,10 @@ type Bookkeeping struct {
component.Base
}
var (
_ component.Snapshotable = (*Bookkeeping)(nil)
)
// SnapshotNeedsRunning returns if this Snapshotable requires a running instance.
func (Bookkeeping) SnapshotNeedsRunning() bool { return false }

View file

@ -12,6 +12,10 @@ type Config struct {
component.Base
}
var (
_ = (component.Backupable)((*Config)(nil))
)
func (*Config) BackupName() string {
return "config"
}

View file

@ -10,6 +10,10 @@ type Filesystem struct {
component.Base
}
var (
_ component.Snapshotable = (*Filesystem)(nil)
)
// SnapshotNeedsRunning returns if this Snapshotable requires a running instance.
func (Filesystem) SnapshotNeedsRunning() bool { return false }

View file

@ -14,6 +14,10 @@ type Pathbuilders struct {
Instances *instances.Instances
}
var (
_ component.Snapshotable = (*Pathbuilders)(nil)
)
func (Pathbuilders) SnapshotNeedsRunning() bool { return true }
func (Pathbuilders) SnapshotName() string { return "pathbuilders" }

View file

@ -26,6 +26,10 @@ type Resolver struct {
handler lazy.Lazy[wdresolve.ResolveHandler] // handler
}
var (
_ component.Servable = (*Resolver)(nil)
)
func (resolver *Resolver) Routes() []string { return []string{"/go/", "/wisski/get/"} }
func (resolver *Resolver) Handler(ctx context.Context, route string, io stream.IOStream) (http.Handler, error) {

View file

@ -17,6 +17,10 @@ type Solr struct {
PollInterval time.Duration // duration to wait for during wait
}
var (
_ component.Installable = (*Solr)(nil)
)
func (s *Solr) Path() string {
return filepath.Join(s.Still.Config.DeployRoot, "core", "solr")
}

View file

@ -20,6 +20,12 @@ type SQL struct {
lazyNetwork lazy.Lazy[string]
}
var (
_ component.Backupable = (*SQL)(nil)
_ component.Snapshotable = (*SQL)(nil)
_ component.Installable = (*SQL)(nil)
)
func (sql *SQL) Path() string {
return filepath.Join(sql.Still.Config.DeployRoot, "core", "sql")
}

View file

@ -14,6 +14,10 @@ type SSH2 struct {
Instances *instances.Instances
}
var (
_ component.Installable = (*SSH2)(nil)
)
// GlobalKeys returns the global authorized keys
func (s *SSH2) GlobalKeys() ([]ssh.PublicKey, error) {
file, err := s.Environment.Open(s.Config.GlobalAuthorizedKeysFile)

View file

@ -17,6 +17,12 @@ type Triplestore struct {
PollInterval time.Duration // duration to wait for during wait
}
var (
_ component.Backupable = (*Triplestore)(nil)
_ component.Snapshotable = (*Triplestore)(nil)
_ component.Installable = (*Triplestore)(nil)
)
func (ts *Triplestore) Path() string {
return filepath.Join(ts.Still.Config.DeployRoot, "core", "triplestore")
}

View file

@ -15,6 +15,10 @@ type Web struct {
component.Base
}
var (
_ component.Installable = (*Web)(nil)
)
func (web *Web) Path() string {
return filepath.Join(web.Still.Config.DeployRoot, "core", "web")
}

View file

@ -45,6 +45,10 @@ type LastCronFetcher struct {
Drush *Drush
}
var (
_ ingredient.WissKIFetcher = (*LastCronFetcher)(nil)
)
func (lbr *LastCronFetcher) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (err error) {
if flags.Quick {
return

View file

@ -56,6 +56,10 @@ type LastUpdateFetcher struct {
Drush *Drush
}
var (
_ ingredient.WissKIFetcher = (*LastUpdateFetcher)(nil)
)
func (lbr *LastUpdateFetcher) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (err error) {
info.LastUpdate, err = lbr.Drush.LastUpdate(flags.Context)
return

View file

@ -23,6 +23,10 @@ type RunningFetcher struct {
Barrel *Barrel
}
var (
_ ingredient.WissKIFetcher = (*RunningFetcher)(nil)
)
func (rf *RunningFetcher) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (err error) {
info.Running, err = rf.Barrel.Running(flags.Context)
return

View file

@ -17,6 +17,10 @@ type SSH struct {
Barrel *barrel.Barrel
}
var (
_ ingredient.WissKIFetcher = (*SSH)(nil)
)
func (ssh *SSH) Keys() ([]ssh.PublicKey, error) {
file, err := ssh.Environment.Open(ssh.Barrel.AuthorizedKeysPath())
if environment.IsNotExist(err) {

View file

@ -20,6 +20,10 @@ type Info struct {
Analytics *lazy.PoolAnalytics
}
var (
_ ingredient.WissKIFetcher = (*Info)(nil)
)
// Information fetches information about this WissKI.
// TODO: Rework this to be able to determine what kind of information is available.
func (wisski *Info) Information(ctx context.Context, quick bool) (info status.WissKI, err error) {

View file

@ -11,6 +11,10 @@ type SnapshotsFetcher struct {
Info *Info
}
var (
_ ingredient.WissKIFetcher = (*SnapshotsFetcher)(nil)
)
func (lbr *SnapshotsFetcher) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (err error) {
if flags.Quick {
return

View file

@ -13,6 +13,10 @@ type Locker struct {
ingredient.Base
}
var (
_ = (ingredient.WissKIFetcher)((*Locker)(nil))
)
var Locked = exit.Error{
Message: "WissKI Instance is locked for administrative operations",
ExitCode: exit.ExitGeneric,

View file

@ -17,6 +17,10 @@ type Pathbuilder struct {
PHP *php.PHP
}
var (
_ ingredient.WissKIFetcher = (*Pathbuilder)(nil)
)
//go:embed pathbuilder.php
var pathbuilderPHP string

View file

@ -25,6 +25,10 @@ type Prefixes struct {
MStore *mstore.MStore
}
var (
_ ingredient.WissKIFetcher = (*Prefixes)(nil)
)
// NoPrefix checks if this WissKI instance is excluded from generating prefixes.
// TODO: Move this to the database!
func (prefixes *Prefixes) NoPrefix() bool {

View file

@ -16,6 +16,10 @@ type Stats struct {
PHP *php.PHP
}
var (
_ ingredient.WissKIFetcher = (*Stats)(nil)
)
//go:embed stats.php
var statsPHP string

View file

@ -18,6 +18,10 @@ type Users struct {
PHP *php.PHP
}
var (
_ ingredient.WissKIFetcher = (*Users)(nil)
)
//go:embed users.php
var usersPHP string

View file

@ -14,6 +14,7 @@ import (
func (wisski *WissKI) init() {
wisski.poolInit.Do(func() {
wisski.pool.Init = ingredient.Init
lazy.RegisterPoolGroup[ingredient.WissKIFetcher](&wisski.pool)
})
}

View file

@ -2,6 +2,8 @@ package lazy
import (
"reflect"
"github.com/tkw1536/goprogram/lib/reflectx"
)
// Pool represents a pool of laziliy initialized and potentially referencing Component instances.
@ -18,12 +20,23 @@ type Pool[Component any, InitParams any] struct {
// Init is called on every component to be initialized.
Init func(Component, InitParams) Component
// Analytics are written on the first retrieval operation on this Pool
Analytics PoolAnalytics
// Analytics are written on the first retrieval operation on this Pool.
//
// Contains all groups and structs that are referenced during initialization.
// To add extra groups, call RegisterPoolGroup.
Analytics PoolAnalytics
extraGroups []reflect.Type
all Lazy[[]Component]
}
// RegisterPoolGroup registers the given group type to be added to the pools' analytics.
//
// Only groups not referenced during initialization need to be registered explicitly.
func RegisterPoolGroup[Group any, Component any, InitParams any](p *Pool[Component, InitParams]) {
p.extraGroups = append(p.extraGroups, reflectx.TypeOf[Group]())
}
// All initializes or returns all components stored in this pool.
//
// The All function should return an array of calls to [Make] with the provided context.
@ -48,7 +61,7 @@ func (p *Pool[Component, InitParams]) All(Params InitParams, All func(context *P
context.process(all)
// write out analytics
context.anal(&p.Analytics)
context.anal(&p.Analytics, p.extraGroups)
return all
})
}
@ -58,10 +71,6 @@ type PoolContext[Component any] struct {
init func(Component) Component // initializes a new component
all func(context *PoolContext[Component]) []Component // initializes all components
// cache for metas
// function to return all components
metaCache map[reflect.Type]meta[Component]
cache map[string]Component // cached components
queue []delayedInit[Component] // init queue

View file

@ -29,7 +29,7 @@ type PoolAnalyticsGroup struct {
}
// anal writes analytics about this context to anal
func (context *PoolContext[Component]) anal(anal *PoolAnalytics) {
func (context *PoolContext[Component]) anal(anal *PoolAnalytics, groups []reflect.Type) {
anal.Components = make(map[string]*PoolAnalyticsComponent, len(context.metaCache))
anal.Groups = make(map[string]*PoolAnalyticsGroup)
@ -51,6 +51,10 @@ func (context *PoolContext[Component]) anal(anal *PoolAnalytics) {
}
}
// collect interfaces to analyze
ifaces := make([]reflect.Type, len(groups))
copy(ifaces, groups)
// take all of the components out of the cache
for _, meta := range context.metaCache {
anal.Components[meta.Name].Type = meta.Name
@ -59,34 +63,39 @@ func (context *PoolContext[Component]) anal(anal *PoolAnalytics) {
})
anal.Components[meta.Name].IFields = collection.MapValues(meta.IFields, func(key string, iface reflect.Type) string {
name := nameOf(iface)
if _, ok := anal.Groups[name]; !ok {
types := collection.FilterClone(tpPointers, func(tp reflect.Type) bool {
return tp.AssignableTo(iface)
})
anal.Groups[name] = &PoolAnalyticsGroup{
Type: name,
Components: collection.MapSlice(types, func(tp reflect.Type) string {
cname := nameOf(tp.Elem())
anal.Components[cname].Groups = append(anal.Components[cname].Groups, name)
return cname
}),
}
mcount := iface.NumMethod()
anal.Groups[name].Methods = make(map[string]string, mcount)
for i := 0; i < mcount; i++ {
method := iface.Method(i)
anal.Groups[name].Methods[method.Name] = method.Type.String()
}
}
return name
ifaces = append(ifaces, iface)
return nameOf(iface)
})
}
// and analyze all ifaces
for _, iface := range ifaces {
name := nameOf(iface)
if _, ok := anal.Groups[name]; ok {
continue
}
types := collection.FilterClone(tpPointers, func(tp reflect.Type) bool {
return tp.AssignableTo(iface)
})
anal.Groups[name] = &PoolAnalyticsGroup{
Type: name,
Components: collection.MapSlice(types, func(tp reflect.Type) string {
cname := nameOf(tp.Elem())
anal.Components[cname].Groups = append(anal.Components[cname].Groups, name)
return cname
}),
}
mcount := iface.NumMethod()
anal.Groups[name].Methods = make(map[string]string, mcount)
for i := 0; i < mcount; i++ {
method := iface.Method(i)
anal.Groups[name].Methods[method.Name] = method.Type.String()
}
}
for _, comp := range anal.Components {
slices.Sort(comp.Groups)
}