diff --git a/cmd/update_prefix_config.go b/cmd/update_prefix_config.go index b6d7ab6..02e1dff 100644 --- a/cmd/update_prefix_config.go +++ b/cmd/update_prefix_config.go @@ -39,7 +39,7 @@ func (upc updateprefixconfig) Run(context wisski_distillery.Context) error { } ddis := dis.Control() - target := ddis.ResolverConfigPath() + target := dis.Resolver().ConfigPath() // print the configuration config, err := dis.Core.Environment.Create(target, environment.DefaultFilePerm) diff --git a/internal/component/control/control.go b/internal/component/control/control.go index 8a03695..2301832 100644 --- a/internal/component/control/control.go +++ b/internal/component/control/control.go @@ -5,7 +5,6 @@ import ( "path/filepath" "github.com/FAU-CDI/wisski-distillery/internal/component" - "github.com/FAU-CDI/wisski-distillery/internal/component/instances" "github.com/FAU-CDI/wisski-distillery/internal/core" "github.com/FAU-CDI/wisski-distillery/pkg/environment" ) @@ -14,9 +13,9 @@ import ( type Control struct { component.ComponentBase - Instances *instances.Instances + Servables []component.Servable - ResolverFile string + ResolverFile string // TODO: this shouldn't be needed! } func (control Control) Name() string { diff --git a/internal/component/control/info.go b/internal/component/control/extras_info.go similarity index 74% rename from internal/component/control/info.go rename to internal/component/control/extras_info.go index c001c5c..3444669 100644 --- a/internal/component/control/info.go +++ b/internal/component/control/extras_info.go @@ -8,6 +8,7 @@ import ( "strings" "time" + "github.com/FAU-CDI/wisski-distillery/internal/component" "github.com/FAU-CDI/wisski-distillery/internal/component/instances" "github.com/FAU-CDI/wisski-distillery/internal/config" "github.com/FAU-CDI/wisski-distillery/internal/models" @@ -16,7 +17,17 @@ import ( "golang.org/x/sync/errgroup" ) -func (control *Control) info(io stream.IOStream) (http.Handler, error) { +type Info struct { + component.ComponentBase + + Instances *instances.Instances +} + +func (Info) Name() string { return "control-info" } + +func (*Info) Routes() []string { return []string{"/dis/"} } + +func (info *Info) Handler(route string, io stream.IOStream) (http.Handler, error) { mux := http.NewServeMux() // handle everything under /dis/! @@ -29,7 +40,7 @@ func (control *Control) info(io stream.IOStream) (http.Handler, error) { }) // static stuff - static, err := control.disStatic() + static, err := info.disStatic() if err != nil { return nil, err } @@ -37,22 +48,22 @@ func (control *Control) info(io stream.IOStream) (http.Handler, error) { // render everything mux.Handle("/dis/index", httpx.HTMLHandler[disIndex]{ - Handler: control.disIndex, + Handler: info.disIndex, Template: indexTemplate, }) mux.Handle("/dis/instance/", httpx.HTMLHandler[disInstance]{ - Handler: control.disInstance, + Handler: info.disInstance, Template: instanceTemplate, }) // api -- for future usage - mux.Handle("/dis/api/v1/instance/get/", httpx.JSON(control.getinstance)) - mux.Handle("/dis/api/v1/instance/all", httpx.JSON(control.allinstances)) + mux.Handle("/dis/api/v1/instance/get/", httpx.JSON(info.getinstance)) + mux.Handle("/dis/api/v1/instance/all", httpx.JSON(info.allinstances)) // ensure that everyone is logged in! return httpx.BasicAuth(mux, "WissKI Distillery Admin", func(user, pass string) bool { - return user == control.Config.DisAdminUser && pass == control.Config.DisAdminPassword + return user == info.Config.DisAdminUser && pass == info.Config.DisAdminPassword }), nil } @@ -71,12 +82,12 @@ type disIndex struct { Backups []models.Snapshot } -func (dis *Control) disIndex(r *http.Request) (idx disIndex, err error) { +func (info *Info) disIndex(r *http.Request) (idx disIndex, err error) { var group errgroup.Group group.Go(func() error { // load instances - idx.Instances, err = dis.allinstances(r) + idx.Instances, err = info.allinstances(r) if err != nil { return err } @@ -96,12 +107,12 @@ func (dis *Control) disIndex(r *http.Request) (idx disIndex, err error) { // get the log entries group.Go(func() (err error) { - idx.Backups, err = dis.Instances.SnapshotLogFor("") + idx.Backups, err = info.Instances.SnapshotLogFor("") return }) // get the static properties - idx.Config = dis.Config + idx.Config = info.Config // current time idx.Time = time.Now().UTC() @@ -120,13 +131,13 @@ type disInstance struct { Info instances.WissKIInfo } -func (dis *Control) disInstance(r *http.Request) (is disInstance, err error) { +func (info *Info) disInstance(r *http.Request) (is disInstance, err error) { // find the slug as the last component of path! slug := strings.TrimSuffix(r.URL.Path, "/") slug = slug[strings.LastIndex(slug, "/")+1:] // find the instance itself! - instance, err := dis.Instances.WissKI(slug) + instance, err := info.Instances.WissKI(slug) if err == instances.ErrWissKINotFound { return is, httpx.ErrNotFound } @@ -150,7 +161,7 @@ func (dis *Control) disInstance(r *http.Request) (is disInstance, err error) { //go:embed html/static var htmlStaticFS embed.FS -func (*Control) disStatic() (http.Handler, error) { +func (*Info) disStatic() (http.Handler, error) { fs, err := fs.Sub(htmlStaticFS, "html/static") if err != nil { return nil, err @@ -167,29 +178,29 @@ var indexTemplate = template.Must(template.New("index.html").Parse(indexTemplate var instanceTemplateString string var instanceTemplate = template.Must(template.New("instance.html").Parse(instanceTemplateString)) -func (dis *Control) getinstance(r *http.Request) (info instances.WissKIInfo, err error) { +func (info *Info) getinstance(r *http.Request) (iinfo instances.WissKIInfo, err error) { // find the slug as the last component of path! slug := strings.TrimSuffix(r.URL.Path, "/") slug = slug[strings.LastIndex(slug, "/")+1:] // load the wisski instance! - wisski, err := dis.Instances.WissKI(strings.TrimSuffix(slug, "/")) + wisski, err := info.Instances.WissKI(strings.TrimSuffix(slug, "/")) if err == instances.ErrWissKINotFound { - return info, httpx.ErrNotFound + return iinfo, httpx.ErrNotFound } if err != nil { - return info, err + return iinfo, err } // get info about it! return wisski.Info(false) } -func (dis *Control) allinstances(*http.Request) (infos []instances.WissKIInfo, err error) { +func (info *Info) allinstances(*http.Request) (infos []instances.WissKIInfo, err error) { var errgroup errgroup.Group // list all the instances - all, err := dis.Instances.All() + all, err := info.Instances.All() if err != nil { return nil, err } diff --git a/internal/component/control/self.go b/internal/component/control/extras_self.go similarity index 72% rename from internal/component/control/self.go rename to internal/component/control/extras_self.go index 007c0c0..af39b0a 100644 --- a/internal/component/control/self.go +++ b/internal/component/control/extras_self.go @@ -6,14 +6,30 @@ import ( "net/http" "strings" + "github.com/FAU-CDI/wisski-distillery/internal/component" + "github.com/FAU-CDI/wisski-distillery/internal/component/instances" "github.com/tkw1536/goprogram/stream" ) -// self returns the handler for the self overrides -func (control Control) self(io stream.IOStream) (redirect Redirect, err error) { +// SelfHandler implements serving the '/' route +type SelfHandler struct { + component.ComponentBase + + Instances *instances.Instances +} + +func (SelfHandler) Name() string { return "control-self" } + +func (*SelfHandler) Routes() []string { return []string{"/"} } + +func (sh *SelfHandler) Handler(route string, io stream.IOStream) (http.Handler, error) { + // create a redirect + var redirect Redirect + var err error + // open the overrides file - overrides, err := control.Environment.Open(control.Config.SelfOverridesFile) - io.Printf("loading overrides from %q\n", control.Config.SelfOverridesFile) + overrides, err := sh.Environment.Open(sh.Config.SelfOverridesFile) + io.Printf("loading overrides from %q\n", sh.Config.SelfOverridesFile) if err != nil { return redirect, err } @@ -21,18 +37,18 @@ func (control Control) self(io stream.IOStream) (redirect Redirect, err error) { // decode the overrides file if err := json.NewDecoder(overrides).Decode(&redirect.Overrides); err != nil { - return redirect, err + return nil, err } if redirect.Overrides == nil { redirect.Overrides = make(map[string]string) } - redirect.Overrides[""] = control.Config.SelfRedirect.String() + redirect.Overrides[""] = sh.Config.SelfRedirect.String() // create a redirect server - redirect.Fallback, err = control.selfFallback() + redirect.Fallback, err = sh.selfFallback() if err != nil { - return redirect, err + return nil, err } redirect.Absolute = false redirect.Permanent = false @@ -41,22 +57,22 @@ func (control Control) self(io stream.IOStream) (redirect Redirect, err error) { return redirect, nil } -func (control *Control) selfFallback() (http.Handler, error) { - return http.HandlerFunc(control.serveFallback), nil +func (sh *SelfHandler) selfFallback() (http.Handler, error) { + return http.HandlerFunc(sh.serveFallback), nil } var notFoundText = []byte("not found") -func (control *Control) serveFallback(w http.ResponseWriter, r *http.Request) { +func (sh *SelfHandler) serveFallback(w http.ResponseWriter, r *http.Request) { - slug := control.Config.SlugFromHost(r.Host) + slug := sh.Config.SlugFromHost(r.Host) if slug == "" { w.WriteHeader(http.StatusNotFound) w.Write(notFoundText) return } - if ok, _ := control.Instances.Has(slug); !ok { + if ok, _ := sh.Instances.Has(slug); !ok { w.WriteHeader(http.StatusNotFound) fmt.Fprintf(w, "WissKI %q not found\n", slug) return diff --git a/internal/component/control/resolver.go b/internal/component/control/resolver.go deleted file mode 100644 index 933d008..0000000 --- a/internal/component/control/resolver.go +++ /dev/null @@ -1,60 +0,0 @@ -package control - -import ( - "fmt" - "path/filepath" - "regexp" - - "github.com/FAU-CDI/wdresolve" - "github.com/FAU-CDI/wdresolve/resolvers" - "github.com/tkw1536/goprogram/stream" -) - -func (control Control) ResolverConfigPath() string { - return filepath.Join(control.Path(), control.ResolverFile) -} - -func (control Control) resolver(io stream.IOStream) (p wdresolve.ResolveHandler, err error) { - p.TrustXForwardedProto = true - - fallback := &resolvers.Regexp{ - Data: map[string]string{}, - } - - // handle the default domain name! - domainName := control.Config.DefaultDomain - if domainName != "" { - fallback.Data[fmt.Sprintf("^https?://(.*)\\.%s", regexp.QuoteMeta(domainName))] = fmt.Sprintf("https://$1.%s", domainName) - io.Printf("registering default domain %s\n", domainName) - } - - // handle the extra domains! - for _, domain := range control.Config.SelfExtraDomains { - fallback.Data[fmt.Sprintf("^https?://(.*)\\.%s", regexp.QuoteMeta(domain))] = fmt.Sprintf("https://$1.%s", domainName) - io.Printf("registering legacy domain %s\n", domain) - } - - // open the prefix file - prefixFile := control.ResolverConfigPath() - fs, err := control.Environment.Open(prefixFile) - io.Println("loading prefixes from ", prefixFile) - if err != nil { - return p, err - } - defer fs.Close() - - // read the prefixes - // TODO: Do we want to load these without a file? - prefixes, err := resolvers.ReadPrefixes(fs) - if err != nil { - return p, err - } - - // and use that as the resolver! - p.Resolver = resolvers.InOrder{ - prefixes, - fallback, - } - - return p, nil -} diff --git a/internal/component/control/server.go b/internal/component/control/server.go index e1c7b1d..f18a513 100644 --- a/internal/component/control/server.go +++ b/internal/component/control/server.go @@ -7,33 +7,21 @@ import ( ) // Server returns an http.Mux that implements the main server instance -func (control Control) Server(io stream.IOStream) (http.Handler, error) { - // self server - self, err := control.self(io) - if err != nil { - return nil, err - } - - resolver, err := control.resolver(io) - if err != nil { - return nil, err - } - - info, err := control.info(io) - if err != nil { - return nil, err - } - - // resolver - +// Logging messages are directed to io. +func (control *Control) Server(io stream.IOStream) (*http.ServeMux, error) { + // create a new mux mux := http.NewServeMux() - mux.Handle("/", self) - - mux.Handle("/go/", resolver) - mux.Handle("/wisski/get/", resolver) - - // TODO: Fix me! - mux.Handle("/dis/", info) + // add all the servable routes! + for _, s := range control.Servables { + for _, route := range s.Routes() { + io.Printf("mounting %s\n", route) + handler, err := s.Handler(route, io) + if err != nil { + return nil, err + } + mux.Handle(route, handler) + } + } return mux, nil } diff --git a/internal/component/resolver/resolver.go b/internal/component/resolver/resolver.go new file mode 100644 index 0000000..03ced27 --- /dev/null +++ b/internal/component/resolver/resolver.go @@ -0,0 +1,84 @@ +package resolver + +import ( + "fmt" + "io/fs" + "net/http" + "path/filepath" + "regexp" + + "github.com/FAU-CDI/wdresolve" + "github.com/FAU-CDI/wdresolve/resolvers" + "github.com/FAU-CDI/wisski-distillery/internal/component" + "github.com/FAU-CDI/wisski-distillery/internal/component/control" + "github.com/FAU-CDI/wisski-distillery/pkg/lazy" + "github.com/tkw1536/goprogram/stream" +) + +type Resolver struct { + component.ComponentBase + + Control *control.Control + ResolverFile string + + handler lazy.Lazy[wdresolve.ResolveHandler] +} + +func (Resolver) Name() string { return "resolver" } + +func (resolver *Resolver) Routes() []string { return []string{"/go/", "/wisski/get/"} } + +func (resolver *Resolver) Handler(route string, io stream.IOStream) (http.Handler, error) { + var err error + return resolver.handler.Get(func() (p wdresolve.ResolveHandler) { + p.TrustXForwardedProto = true + + fallback := &resolvers.Regexp{ + Data: map[string]string{}, + } + + // handle the default domain name! + domainName := resolver.Config.DefaultDomain + if domainName != "" { + fallback.Data[fmt.Sprintf("^https?://(.*)\\.%s", regexp.QuoteMeta(domainName))] = fmt.Sprintf("https://$1.%s", domainName) + io.Printf("registering default domain %s\n", domainName) + } + + // handle the extra domains! + for _, domain := range resolver.Config.SelfExtraDomains { + fallback.Data[fmt.Sprintf("^https?://(.*)\\.%s", regexp.QuoteMeta(domain))] = fmt.Sprintf("https://$1.%s", domainName) + io.Printf("registering legacy domain %s\n", domain) + } + + configPath := resolver.ConfigPath() + { + // load the prefix path! + var fs fs.File + fs, err = resolver.Environment.Open(configPath) + io.Println("loading prefixes from ", configPath) + if err != nil { + return + } + defer fs.Close() + + // read the file + var prefixes resolvers.Prefix + prefixes, err = resolvers.ReadPrefixes(fs) + if err != nil { + return + } + + // and use that as the resolver! + p.Resolver = resolvers.InOrder{ + prefixes, + fallback, + } + + return p + } + }), err +} + +func (resolver *Resolver) ConfigPath() string { + return filepath.Join(resolver.Control.Path(), resolver.ResolverFile) +} diff --git a/internal/component/server.go b/internal/component/server.go new file mode 100644 index 0000000..42b5e70 --- /dev/null +++ b/internal/component/server.go @@ -0,0 +1,18 @@ +package component + +import ( + "net/http" + + "github.com/tkw1536/goprogram/stream" +) + +// Servable implements a component with a Serve method +type Servable interface { + Component + + // Routes returns the routes served by this servable + Routes() []string + + // Handler returns the handler for the requested route + Handler(route string, io stream.IOStream) (http.Handler, error) +} diff --git a/internal/config/config.go b/internal/config/config.go index 66993c7..2acf4b1 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,4 +1,4 @@ -// Package config provides the distillery configuration +// Package config contains distillery configuration package config import ( diff --git a/internal/config/domains.go b/internal/config/domains.go index 906c0f5..a465d25 100644 --- a/internal/config/domains.go +++ b/internal/config/domains.go @@ -1,6 +1,8 @@ package config -import "strings" +import ( + "strings" +) // This file contains domain related derived configuration values. diff --git a/internal/dis/component.go b/internal/dis/component.go index 52f2368..3a17f2c 100644 --- a/internal/dis/component.go +++ b/internal/dis/component.go @@ -7,6 +7,7 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/component" "github.com/FAU-CDI/wisski-distillery/internal/component/control" "github.com/FAU-CDI/wisski-distillery/internal/component/instances" + "github.com/FAU-CDI/wisski-distillery/internal/component/resolver" "github.com/FAU-CDI/wisski-distillery/internal/component/snapshots" "github.com/FAU-CDI/wisski-distillery/internal/component/sql" "github.com/FAU-CDI/wisski-distillery/internal/component/ssh" @@ -49,7 +50,7 @@ func (dis *Distillery) cWeb(thread int32) *web.Web { func (dis *Distillery) cControl(thread int32) *control.Control { return component.InitComponent(&dis.pool, thread, dis.Core, func(control *control.Control, thread int32) { control.ResolverFile = core.PrefixConfig - control.Instances = dis.cInstances(thread) + control.Servables = dis.cServables(thread) }) } @@ -89,6 +90,13 @@ func (dis *Distillery) cSnapshotManager(thread int32) *snapshots.Manager { }) } +func (dis *Distillery) cResolver(thread int32) *resolver.Resolver { + return component.InitComponent(&dis.pool, thread, dis.Core, func(resolver *resolver.Resolver, thread int32) { + resolver.Control = dis.cControl(thread) + resolver.ResolverFile = core.PrefixConfig + }) +} + // // ALL COMPONENTS // @@ -96,7 +104,6 @@ func (dis *Distillery) cSnapshotManager(thread int32) *snapshots.Manager { func (dis *Distillery) cComponents(thread int32) []component.Component { return []component.Component{ dis.cWeb(thread), - dis.cControl(thread), dis.cSSH(thread), dis.cTriplestore(thread), dis.cSQL(thread), @@ -110,6 +117,16 @@ func (dis *Distillery) cComponents(thread int32) []component.Component { c(dis, thread, func(pbs *snapshots.Pathbuilders, thread int32) { pbs.Instances = dis.cInstances(thread) }), + + // Control server + dis.cControl(thread), + c(dis, thread, func(sh *control.SelfHandler, thread int32) { + sh.Instances = dis.cInstances(thread) + }), + dis.cResolver(thread), + c(dis, thread, func(info *control.Info, thread int32) { + info.Instances = dis.cInstances(thread) + }), } } @@ -137,6 +154,10 @@ func (dis *Distillery) cSnapshotable(thread int32) []component.Snapshotable { return getComponentSubtype[component.Snapshotable](dis, thread) } +func (dis *Distillery) cServables(thread int32) []component.Servable { + return getComponentSubtype[component.Servable](dis, thread) +} + func getComponentSubtype[C component.Component](dis *Distillery, thread int32) (components []C) { all := dis.cComponents(thread) diff --git a/internal/dis/distillery.go b/internal/dis/distillery.go index f347bdd..f8d78d5 100644 --- a/internal/dis/distillery.go +++ b/internal/dis/distillery.go @@ -7,6 +7,7 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/component" "github.com/FAU-CDI/wisski-distillery/internal/component/control" "github.com/FAU-CDI/wisski-distillery/internal/component/instances" + "github.com/FAU-CDI/wisski-distillery/internal/component/resolver" "github.com/FAU-CDI/wisski-distillery/internal/component/snapshots" "github.com/FAU-CDI/wisski-distillery/internal/component/sql" "github.com/FAU-CDI/wisski-distillery/internal/component/ssh" @@ -49,6 +50,9 @@ func (dis *Distillery) Context() context.Context { func (dis *Distillery) Control() *control.Control { return dis.cControl(dis.thread()) } +func (dis *Distillery) Resolver() *resolver.Resolver { + return dis.cResolver(dis.thread()) +} func (dis *Distillery) SSH() *ssh.SSH { return dis.cSSH(dis.thread()) }