emissary/internal/agent/controller/app/app_handler.go

193 lines
5.0 KiB
Go

package app
import (
"bytes"
"context"
"database/sql"
"path/filepath"
"sync"
"text/template"
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec"
appSpec "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec"
"forge.cadoles.com/Cadoles/emissary/internal/jwk"
"forge.cadoles.com/arcad/edge/pkg/app"
"forge.cadoles.com/arcad/edge/pkg/bus"
"forge.cadoles.com/arcad/edge/pkg/bus/memory"
edgeHTTP "forge.cadoles.com/arcad/edge/pkg/http"
"forge.cadoles.com/arcad/edge/pkg/module"
appModule "forge.cadoles.com/arcad/edge/pkg/module/app"
"forge.cadoles.com/arcad/edge/pkg/module/auth"
"forge.cadoles.com/arcad/edge/pkg/module/blob"
"forge.cadoles.com/arcad/edge/pkg/module/cast"
fetchModule "forge.cadoles.com/arcad/edge/pkg/module/fetch"
"forge.cadoles.com/arcad/edge/pkg/module/net"
"forge.cadoles.com/arcad/edge/pkg/storage/sqlite"
"github.com/Masterminds/sprig/v3"
"github.com/dop251/goja"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/pkg/errors"
)
const defaultSQLiteParams = "?_pragma=foreign_keys(1)&_pragma=journal_mode(WAL)&_txlock=immediate"
func (c *Controller) getHandlerOptions(ctx context.Context, appKey string, specs *spec.Spec) ([]edgeHTTP.HandlerOptionFunc, error) {
dataDir, err := c.ensureAppDataDir(ctx, appKey)
if err != nil {
return nil, errors.Wrap(err, "could not retrieve app data dir")
}
dbFile := filepath.Join(dataDir, appKey+".sqlite")
db, err := sqlite.Open(dbFile + defaultSQLiteParams)
if err != nil {
return nil, errors.Wrapf(err, "could not open database file '%s'", dbFile)
}
keySet, err := getAuthKeySet(specs.Config)
if err != nil {
return nil, errors.Wrap(err, "could not retrieve auth key set")
}
bundles := make([]string, 0, len(specs.Apps))
for appKey, app := range specs.Apps {
path := c.getAppBundlePath(appKey, app.Format)
bundles = append(bundles, path)
}
bus := memory.NewBus()
modules := c.getAppModules(bus, db, specs, keySet)
options := []edgeHTTP.HandlerOptionFunc{
edgeHTTP.WithBus(bus),
edgeHTTP.WithServerModules(modules...),
}
return options, nil
}
func getAuthKeySet(config *spec.Config) (jwk.Set, error) {
keySet := jwk.NewSet()
if config == nil {
return nil, nil
}
auth := config.Auth
if auth == nil {
return nil, nil
}
switch {
case auth.Local != nil:
var (
key jwk.Key
err error
)
switch typedKey := auth.Local.Key.(type) {
case string:
key, err = jwk.FromRaw([]byte(typedKey))
if err != nil {
return nil, errors.Wrap(err, "could not parse local auth key")
}
if err := key.Set(jwk.AlgorithmKey, jwa.HS256); err != nil {
return nil, errors.WithStack(err)
}
default:
return nil, errors.Errorf("unexpected key type '%T'", auth.Local.Key)
}
if err := keySet.AddKey(key); err != nil {
return nil, errors.WithStack(err)
}
}
return keySet, nil
}
func createGetAppURL(specs *spec.Spec) GetURLFunc {
var (
compileOnce sync.Once
urlTemplate *template.Template
err error
)
return func(ctx context.Context, manifest *app.Manifest) (string, error) {
if err != nil {
return "", errors.WithStack(err)
}
var appURLTemplate string
if specs.Config == nil || specs.Config.AppURLTemplate == "" {
appURLTemplate = `http://{{ last ( splitList "." ( toString .Manifest.ID ) ) }}.local`
} else {
appURLTemplate = specs.Config.AppURLTemplate
}
compileOnce.Do(func() {
urlTemplate, err = template.New("").Funcs(sprig.TxtFuncMap()).Parse(appURLTemplate)
})
var buf bytes.Buffer
data := struct {
Manifest *app.Manifest
Specs *spec.Spec
}{
Manifest: manifest,
Specs: specs,
}
if err := urlTemplate.Execute(&buf, data); err != nil {
return "", errors.WithStack(err)
}
return buf.String(), nil
}
}
func (c *Controller) getAppModules(bus bus.Bus, db *sql.DB, spec *appSpec.Spec, keySet jwk.Set) []app.ServerModuleFactory {
ds := sqlite.NewDocumentStoreWithDB(db)
bs := sqlite.NewBlobStoreWithDB(db)
return []app.ServerModuleFactory{
module.ContextModuleFactory(),
module.ConsoleModuleFactory(),
cast.CastModuleFactory(),
module.LifecycleModuleFactory(),
net.ModuleFactory(bus),
module.RPCModuleFactory(bus),
module.StoreModuleFactory(ds),
blob.ModuleFactory(bus, bs),
module.Extends(
auth.ModuleFactory(
auth.WithJWT(func() (jwk.Set, error) {
return keySet, nil
}),
),
func(o *goja.Object) {
if err := o.Set("CLAIM_TENANT", "arcad_tenant"); err != nil {
panic(errors.New("could not set 'CLAIM_TENANT' property"))
}
if err := o.Set("CLAIM_ENTRYPOINT", "arcad_entrypoint"); err != nil {
panic(errors.New("could not set 'CLAIM_ENTRYPOINT' property"))
}
if err := o.Set("CLAIM_ROLE", "arcad_role"); err != nil {
panic(errors.New("could not set 'CLAIM_ROLE' property"))
}
if err := o.Set("CLAIM_PREFERRED_USERNAME", "preferred_username"); err != nil {
panic(errors.New("could not set 'CLAIM_PREFERRED_USERNAME' property"))
}
},
),
appModule.ModuleFactory(c.appRepository),
fetchModule.ModuleFactory(bus),
}
}