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() { func (dis *Distillery) init() {
dis.poolInit.Do(func() { dis.poolInit.Do(func() {
dis.pool.Init = component.Init 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 Servables []component.Servable
} }
var (
_ component.Installable = (*Control)(nil)
)
func (control Control) Path() string { func (control Control) Path() string {
return filepath.Join(control.Still.Config.DeployRoot, "core", "dis") return filepath.Join(control.Still.Config.DeployRoot, "core", "dis")
} }

View file

@ -24,6 +24,10 @@ type Home struct {
homeBytes lazy.Lazy[[]byte] homeBytes lazy.Lazy[[]byte]
} }
var (
_ component.Servable = (*Home)(nil)
)
func (*Home) Routes() []string { return []string{"/"} } func (*Home) Routes() []string { return []string{"/"} }
func (home *Home) Handler(ctx context.Context, route string, io stream.IOStream) (http.Handler, error) { 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 SnapshotsLog *logger.Logger
} }
var (
_ component.DistilleryFetcher = (*Info)(nil)
_ component.Servable = (*Info)(nil)
)
func (*Info) Routes() []string { return []string{"/dis/"} } func (*Info) Routes() []string { return []string{"/dis/"} }
func (info *Info) Handler(ctx context.Context, route string, io stream.IOStream) (handler http.Handler, err error) { 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 component.Base
} }
var (
_ component.Servable = (*Static)(nil)
)
func (*Static) Routes() []string { return []string{"/static/"} } func (*Static) Routes() []string { return []string{"/static/"} }
//go:embed dist //go:embed dist

View file

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

View file

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

View file

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

View file

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

View file

@ -26,6 +26,10 @@ type Resolver struct {
handler lazy.Lazy[wdresolve.ResolveHandler] // handler handler lazy.Lazy[wdresolve.ResolveHandler] // handler
} }
var (
_ component.Servable = (*Resolver)(nil)
)
func (resolver *Resolver) Routes() []string { return []string{"/go/", "/wisski/get/"} } 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) { 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 PollInterval time.Duration // duration to wait for during wait
} }
var (
_ component.Installable = (*Solr)(nil)
)
func (s *Solr) Path() string { func (s *Solr) Path() string {
return filepath.Join(s.Still.Config.DeployRoot, "core", "solr") return filepath.Join(s.Still.Config.DeployRoot, "core", "solr")
} }

View file

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

View file

@ -14,6 +14,10 @@ type SSH2 struct {
Instances *instances.Instances Instances *instances.Instances
} }
var (
_ component.Installable = (*SSH2)(nil)
)
// GlobalKeys returns the global authorized keys // GlobalKeys returns the global authorized keys
func (s *SSH2) GlobalKeys() ([]ssh.PublicKey, error) { func (s *SSH2) GlobalKeys() ([]ssh.PublicKey, error) {
file, err := s.Environment.Open(s.Config.GlobalAuthorizedKeysFile) 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 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 { func (ts *Triplestore) Path() string {
return filepath.Join(ts.Still.Config.DeployRoot, "core", "triplestore") return filepath.Join(ts.Still.Config.DeployRoot, "core", "triplestore")
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -20,6 +20,10 @@ type Info struct {
Analytics *lazy.PoolAnalytics Analytics *lazy.PoolAnalytics
} }
var (
_ ingredient.WissKIFetcher = (*Info)(nil)
)
// Information fetches information about this WissKI. // Information fetches information about this WissKI.
// TODO: Rework this to be able to determine what kind of information is available. // 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) { 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 Info *Info
} }
var (
_ ingredient.WissKIFetcher = (*SnapshotsFetcher)(nil)
)
func (lbr *SnapshotsFetcher) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (err error) { func (lbr *SnapshotsFetcher) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (err error) {
if flags.Quick { if flags.Quick {
return return

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,6 +2,8 @@ package lazy
import ( import (
"reflect" "reflect"
"github.com/tkw1536/goprogram/lib/reflectx"
) )
// Pool represents a pool of laziliy initialized and potentially referencing Component instances. // 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 is called on every component to be initialized.
Init func(Component, InitParams) Component Init func(Component, InitParams) Component
// Analytics are written on the first retrieval operation on this Pool // Analytics are written on the first retrieval operation on this Pool.
Analytics PoolAnalytics //
// Contains all groups and structs that are referenced during initialization.
// To add extra groups, call RegisterPoolGroup.
Analytics PoolAnalytics
extraGroups []reflect.Type
all Lazy[[]Component] 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. // 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. // 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) context.process(all)
// write out analytics // write out analytics
context.anal(&p.Analytics) context.anal(&p.Analytics, p.extraGroups)
return all return all
}) })
} }
@ -58,10 +71,6 @@ type PoolContext[Component any] struct {
init func(Component) Component // initializes a new component init func(Component) Component // initializes a new component
all func(context *PoolContext[Component]) []Component // initializes all components all func(context *PoolContext[Component]) []Component // initializes all components
// cache for metas
// function to return all components
metaCache map[reflect.Type]meta[Component] metaCache map[reflect.Type]meta[Component]
cache map[string]Component // cached components cache map[string]Component // cached components
queue []delayedInit[Component] // init queue queue []delayedInit[Component] // init queue

View file

@ -29,7 +29,7 @@ type PoolAnalyticsGroup struct {
} }
// anal writes analytics about this context to anal // 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.Components = make(map[string]*PoolAnalyticsComponent, len(context.metaCache))
anal.Groups = make(map[string]*PoolAnalyticsGroup) 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 // take all of the components out of the cache
for _, meta := range context.metaCache { for _, meta := range context.metaCache {
anal.Components[meta.Name].Type = meta.Name 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 { anal.Components[meta.Name].IFields = collection.MapValues(meta.IFields, func(key string, iface reflect.Type) string {
name := nameOf(iface) ifaces = append(ifaces, iface)
return 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
}) })
} }
// 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 { for _, comp := range anal.Components {
slices.Sort(comp.Groups) slices.Sort(comp.Groups)
} }