191 lines
4.9 KiB
Go
191 lines
4.9 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"
|
||
|
"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"
|
||
|
)
|
||
|
|
||
|
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)
|
||
|
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)
|
||
|
}
|
||
|
|
||
|
getAppURL := createGetAppURL(specs)
|
||
|
|
||
|
bus := memory.NewBus()
|
||
|
modules := getAppModules(bus, db, specs, keySet, getAppURL, bundles)
|
||
|
|
||
|
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 getAppModules(bus bus.Bus, db *sql.DB, spec *appSpec.Spec, keySet jwk.Set, getAppURL GetURLFunc, bundles []string) []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(NewAppRepository(getAppURL, bundles...)),
|
||
|
}
|
||
|
}
|