control: Move serves into a separate components

This commit is contained in:
Tom Wiesing 2022-10-04 11:36:45 +02:00
parent 6f409be8b2
commit 845e927117
No known key found for this signature in database
12 changed files with 210 additions and 127 deletions

View file

@ -39,7 +39,7 @@ func (upc updateprefixconfig) Run(context wisski_distillery.Context) error {
} }
ddis := dis.Control() ddis := dis.Control()
target := ddis.ResolverConfigPath() target := dis.Resolver().ConfigPath()
// print the configuration // print the configuration
config, err := dis.Core.Environment.Create(target, environment.DefaultFilePerm) config, err := dis.Core.Environment.Create(target, environment.DefaultFilePerm)

View file

@ -5,7 +5,6 @@ import (
"path/filepath" "path/filepath"
"github.com/FAU-CDI/wisski-distillery/internal/component" "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/internal/core"
"github.com/FAU-CDI/wisski-distillery/pkg/environment" "github.com/FAU-CDI/wisski-distillery/pkg/environment"
) )
@ -14,9 +13,9 @@ import (
type Control struct { type Control struct {
component.ComponentBase component.ComponentBase
Instances *instances.Instances Servables []component.Servable
ResolverFile string ResolverFile string // TODO: this shouldn't be needed!
} }
func (control Control) Name() string { func (control Control) Name() string {

View file

@ -8,6 +8,7 @@ import (
"strings" "strings"
"time" "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/component/instances"
"github.com/FAU-CDI/wisski-distillery/internal/config" "github.com/FAU-CDI/wisski-distillery/internal/config"
"github.com/FAU-CDI/wisski-distillery/internal/models" "github.com/FAU-CDI/wisski-distillery/internal/models"
@ -16,7 +17,17 @@ import (
"golang.org/x/sync/errgroup" "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() mux := http.NewServeMux()
// handle everything under /dis/! // handle everything under /dis/!
@ -29,7 +40,7 @@ func (control *Control) info(io stream.IOStream) (http.Handler, error) {
}) })
// static stuff // static stuff
static, err := control.disStatic() static, err := info.disStatic()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -37,22 +48,22 @@ func (control *Control) info(io stream.IOStream) (http.Handler, error) {
// render everything // render everything
mux.Handle("/dis/index", httpx.HTMLHandler[disIndex]{ mux.Handle("/dis/index", httpx.HTMLHandler[disIndex]{
Handler: control.disIndex, Handler: info.disIndex,
Template: indexTemplate, Template: indexTemplate,
}) })
mux.Handle("/dis/instance/", httpx.HTMLHandler[disInstance]{ mux.Handle("/dis/instance/", httpx.HTMLHandler[disInstance]{
Handler: control.disInstance, Handler: info.disInstance,
Template: instanceTemplate, Template: instanceTemplate,
}) })
// api -- for future usage // api -- for future usage
mux.Handle("/dis/api/v1/instance/get/", httpx.JSON(control.getinstance)) mux.Handle("/dis/api/v1/instance/get/", httpx.JSON(info.getinstance))
mux.Handle("/dis/api/v1/instance/all", httpx.JSON(control.allinstances)) mux.Handle("/dis/api/v1/instance/all", httpx.JSON(info.allinstances))
// ensure that everyone is logged in! // ensure that everyone is logged in!
return httpx.BasicAuth(mux, "WissKI Distillery Admin", func(user, pass string) bool { 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 }), nil
} }
@ -71,12 +82,12 @@ type disIndex struct {
Backups []models.Snapshot 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 var group errgroup.Group
group.Go(func() error { group.Go(func() error {
// load instances // load instances
idx.Instances, err = dis.allinstances(r) idx.Instances, err = info.allinstances(r)
if err != nil { if err != nil {
return err return err
} }
@ -96,12 +107,12 @@ func (dis *Control) disIndex(r *http.Request) (idx disIndex, err error) {
// get the log entries // get the log entries
group.Go(func() (err error) { group.Go(func() (err error) {
idx.Backups, err = dis.Instances.SnapshotLogFor("") idx.Backups, err = info.Instances.SnapshotLogFor("")
return return
}) })
// get the static properties // get the static properties
idx.Config = dis.Config idx.Config = info.Config
// current time // current time
idx.Time = time.Now().UTC() idx.Time = time.Now().UTC()
@ -120,13 +131,13 @@ type disInstance struct {
Info instances.WissKIInfo 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! // find the slug as the last component of path!
slug := strings.TrimSuffix(r.URL.Path, "/") slug := strings.TrimSuffix(r.URL.Path, "/")
slug = slug[strings.LastIndex(slug, "/")+1:] slug = slug[strings.LastIndex(slug, "/")+1:]
// find the instance itself! // find the instance itself!
instance, err := dis.Instances.WissKI(slug) instance, err := info.Instances.WissKI(slug)
if err == instances.ErrWissKINotFound { if err == instances.ErrWissKINotFound {
return is, httpx.ErrNotFound return is, httpx.ErrNotFound
} }
@ -150,7 +161,7 @@ func (dis *Control) disInstance(r *http.Request) (is disInstance, err error) {
//go:embed html/static //go:embed html/static
var htmlStaticFS embed.FS var htmlStaticFS embed.FS
func (*Control) disStatic() (http.Handler, error) { func (*Info) disStatic() (http.Handler, error) {
fs, err := fs.Sub(htmlStaticFS, "html/static") fs, err := fs.Sub(htmlStaticFS, "html/static")
if err != nil { if err != nil {
return nil, err return nil, err
@ -167,29 +178,29 @@ var indexTemplate = template.Must(template.New("index.html").Parse(indexTemplate
var instanceTemplateString string var instanceTemplateString string
var instanceTemplate = template.Must(template.New("instance.html").Parse(instanceTemplateString)) 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! // find the slug as the last component of path!
slug := strings.TrimSuffix(r.URL.Path, "/") slug := strings.TrimSuffix(r.URL.Path, "/")
slug = slug[strings.LastIndex(slug, "/")+1:] slug = slug[strings.LastIndex(slug, "/")+1:]
// load the wisski instance! // load the wisski instance!
wisski, err := dis.Instances.WissKI(strings.TrimSuffix(slug, "/")) wisski, err := info.Instances.WissKI(strings.TrimSuffix(slug, "/"))
if err == instances.ErrWissKINotFound { if err == instances.ErrWissKINotFound {
return info, httpx.ErrNotFound return iinfo, httpx.ErrNotFound
} }
if err != nil { if err != nil {
return info, err return iinfo, err
} }
// get info about it! // get info about it!
return wisski.Info(false) 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 var errgroup errgroup.Group
// list all the instances // list all the instances
all, err := dis.Instances.All() all, err := info.Instances.All()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -6,14 +6,30 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/FAU-CDI/wisski-distillery/internal/component"
"github.com/FAU-CDI/wisski-distillery/internal/component/instances"
"github.com/tkw1536/goprogram/stream" "github.com/tkw1536/goprogram/stream"
) )
// self returns the handler for the self overrides // SelfHandler implements serving the '/' route
func (control Control) self(io stream.IOStream) (redirect Redirect, err error) { 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 // open the overrides file
overrides, err := control.Environment.Open(control.Config.SelfOverridesFile) overrides, err := sh.Environment.Open(sh.Config.SelfOverridesFile)
io.Printf("loading overrides from %q\n", control.Config.SelfOverridesFile) io.Printf("loading overrides from %q\n", sh.Config.SelfOverridesFile)
if err != nil { if err != nil {
return redirect, err return redirect, err
} }
@ -21,18 +37,18 @@ func (control Control) self(io stream.IOStream) (redirect Redirect, err error) {
// decode the overrides file // decode the overrides file
if err := json.NewDecoder(overrides).Decode(&redirect.Overrides); err != nil { if err := json.NewDecoder(overrides).Decode(&redirect.Overrides); err != nil {
return redirect, err return nil, err
} }
if redirect.Overrides == nil { if redirect.Overrides == nil {
redirect.Overrides = make(map[string]string) redirect.Overrides = make(map[string]string)
} }
redirect.Overrides[""] = control.Config.SelfRedirect.String() redirect.Overrides[""] = sh.Config.SelfRedirect.String()
// create a redirect server // create a redirect server
redirect.Fallback, err = control.selfFallback() redirect.Fallback, err = sh.selfFallback()
if err != nil { if err != nil {
return redirect, err return nil, err
} }
redirect.Absolute = false redirect.Absolute = false
redirect.Permanent = false redirect.Permanent = false
@ -41,22 +57,22 @@ func (control Control) self(io stream.IOStream) (redirect Redirect, err error) {
return redirect, nil return redirect, nil
} }
func (control *Control) selfFallback() (http.Handler, error) { func (sh *SelfHandler) selfFallback() (http.Handler, error) {
return http.HandlerFunc(control.serveFallback), nil return http.HandlerFunc(sh.serveFallback), nil
} }
var notFoundText = []byte("not found") 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 == "" { if slug == "" {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
w.Write(notFoundText) w.Write(notFoundText)
return return
} }
if ok, _ := control.Instances.Has(slug); !ok { if ok, _ := sh.Instances.Has(slug); !ok {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "WissKI %q not found\n", slug) fmt.Fprintf(w, "WissKI %q not found\n", slug)
return return

View file

@ -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
}

View file

@ -7,33 +7,21 @@ import (
) )
// Server returns an http.Mux that implements the main server instance // Server returns an http.Mux that implements the main server instance
func (control Control) Server(io stream.IOStream) (http.Handler, error) { // Logging messages are directed to io.
// self server func (control *Control) Server(io stream.IOStream) (*http.ServeMux, error) {
self, err := control.self(io) // create a new mux
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
mux := http.NewServeMux() 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 return mux, nil
} }

View file

@ -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)
}

View file

@ -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)
}

View file

@ -1,4 +1,4 @@
// Package config provides the distillery configuration // Package config contains distillery configuration
package config package config
import ( import (

View file

@ -1,6 +1,8 @@
package config package config
import "strings" import (
"strings"
)
// This file contains domain related derived configuration values. // This file contains domain related derived configuration values.

View file

@ -7,6 +7,7 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/component" "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/control"
"github.com/FAU-CDI/wisski-distillery/internal/component/instances" "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/snapshots"
"github.com/FAU-CDI/wisski-distillery/internal/component/sql" "github.com/FAU-CDI/wisski-distillery/internal/component/sql"
"github.com/FAU-CDI/wisski-distillery/internal/component/ssh" "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 { func (dis *Distillery) cControl(thread int32) *control.Control {
return component.InitComponent(&dis.pool, thread, dis.Core, func(control *control.Control, thread int32) { return component.InitComponent(&dis.pool, thread, dis.Core, func(control *control.Control, thread int32) {
control.ResolverFile = core.PrefixConfig 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 // ALL COMPONENTS
// //
@ -96,7 +104,6 @@ func (dis *Distillery) cSnapshotManager(thread int32) *snapshots.Manager {
func (dis *Distillery) cComponents(thread int32) []component.Component { func (dis *Distillery) cComponents(thread int32) []component.Component {
return []component.Component{ return []component.Component{
dis.cWeb(thread), dis.cWeb(thread),
dis.cControl(thread),
dis.cSSH(thread), dis.cSSH(thread),
dis.cTriplestore(thread), dis.cTriplestore(thread),
dis.cSQL(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) { c(dis, thread, func(pbs *snapshots.Pathbuilders, thread int32) {
pbs.Instances = dis.cInstances(thread) 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) 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) { func getComponentSubtype[C component.Component](dis *Distillery, thread int32) (components []C) {
all := dis.cComponents(thread) all := dis.cComponents(thread)

View file

@ -7,6 +7,7 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/component" "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/control"
"github.com/FAU-CDI/wisski-distillery/internal/component/instances" "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/snapshots"
"github.com/FAU-CDI/wisski-distillery/internal/component/sql" "github.com/FAU-CDI/wisski-distillery/internal/component/sql"
"github.com/FAU-CDI/wisski-distillery/internal/component/ssh" "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 { func (dis *Distillery) Control() *control.Control {
return dis.cControl(dis.thread()) return dis.cControl(dis.thread())
} }
func (dis *Distillery) Resolver() *resolver.Resolver {
return dis.cResolver(dis.thread())
}
func (dis *Distillery) SSH() *ssh.SSH { func (dis *Distillery) SSH() *ssh.SSH {
return dis.cSSH(dis.thread()) return dis.cSSH(dis.thread())
} }